diff --git a/src/hw/wasapi.rs b/src/hw/wasapi.rs index e506a07..8e1cfe9 100644 --- a/src/hw/wasapi.rs +++ b/src/hw/wasapi.rs @@ -866,31 +866,60 @@ fn build_float_mix_format( } let channels = mix.nChannels.max(1); let sample_rate = requested_rate.max(1); - let block_align = channels.saturating_mul(4); + + // Try 32-bit float first. + if let Ok(fmt) = try_wasapi_format(client, channels, sample_rate, 32, true, mode) { + return Ok(fmt); + } + warn!("WASAPI device does not support 32-bit float, falling back to 16-bit PCM"); + + // Fall back to 16-bit integer PCM. + try_wasapi_format(client, channels, sample_rate, 16, false, mode) + .map_err(|_| format!("WASAPI device does not support 32-bit float or 16-bit PCM at {sample_rate} Hz")) +} + +fn try_wasapi_format( + client: &IAudioClient, + channels: u16, + sample_rate: u32, + bits: u16, + is_float: bool, + mode: WasapiMode, +) -> Result { + let bytes_per_sample = (bits / 8) as u16; + let block_align = channels.saturating_mul(bytes_per_sample); let channel_mask = if channels <= 32 { (1_u32 << channels) - 1 } else { 0 }; + let share_mode = match mode { + WasapiMode::SharedLowLatency => AUDCLNT_SHAREMODE_SHARED, + WasapiMode::Exclusive => AUDCLNT_SHAREMODE_EXCLUSIVE, + }; + let mut format = WAVEFORMATEXTENSIBLE::default(); - format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE as u16; + if is_float { + format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE as u16; + format.Format.cbSize = (std::mem::size_of::() + - std::mem::size_of::()) + as u16; + format.Samples.wValidBitsPerSample = bits; + format.dwChannelMask = channel_mask; + format.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } else { + // Use plain WAVE_FORMAT_PCM for maximum compatibility. + format.Format.wFormatTag = 1; // WAVE_FORMAT_PCM + format.Format.cbSize = 0; + } format.Format.nChannels = channels; format.Format.nSamplesPerSec = sample_rate; format.Format.nAvgBytesPerSec = sample_rate.saturating_mul(block_align as u32); format.Format.nBlockAlign = block_align; - format.Format.wBitsPerSample = 32; - format.Format.cbSize = - (std::mem::size_of::() - std::mem::size_of::()) as u16; - format.Samples.wValidBitsPerSample = 32; - format.dwChannelMask = channel_mask; - format.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + format.Format.wBitsPerSample = bits; let mut closest: *mut WAVEFORMATEX = ptr::null_mut(); - let share_mode = match mode { - WasapiMode::SharedLowLatency => AUDCLNT_SHAREMODE_SHARED, - WasapiMode::Exclusive => AUDCLNT_SHAREMODE_EXCLUSIVE, - }; let closest_out = match mode { WasapiMode::SharedLowLatency => Some(&mut closest as *mut *mut WAVEFORMATEX), WasapiMode::Exclusive => None, @@ -902,9 +931,7 @@ fn build_float_mix_format( } } if supported.is_err() { - return Err(format!( - "WASAPI device does not support 32-bit float at {sample_rate} Hz" - )); + return Err("format not supported".to_string()); } Ok(format) diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 6d4bbff..905627e 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -96,9 +96,12 @@ pub fn scan_plugins(format: &str) -> Result, String> )); } - let json = String::from_utf8_lossy(&output.stdout); + // Plugin DLLs may write debug output to stdout before the JSON payload. + // Extract only the JSON portion (from the first '{' onwards). + let raw = String::from_utf8_lossy(&output.stdout); + let json = raw.find('{').map(|i| &raw[i..]).unwrap_or(&raw); let parsed: ScanOutput> = - serde_json::from_str(&json).map_err(|e| format!("failed to parse scan JSON: {e}"))?; + serde_json::from_str(json).map_err(|e| format!("failed to parse scan JSON: {e}"))?; for error in &parsed.errors { tracing::error!(