Compare commits

...

2 Commits

Author SHA1 Message Date
Skyler Lehmkuhl 9935c2f3bd resample recorded audio if it has a different sample rate (fix for wsapi persnicketiness) 2026-02-17 09:44:24 -05:00
Skyler Lehmkuhl 5a97ea76d5 add windows build script 2026-02-17 09:42:06 -05:00
2 changed files with 77 additions and 5 deletions

View File

@ -155,7 +155,7 @@ impl AudioSystem {
} }
}; };
// Get input config - use the input device's own default config // Get input config using the device's default (most compatible)
let input_config = match input_device.default_input_config() { let input_config = match input_device.default_input_config() {
Ok(config) => { Ok(config) => {
let cfg: cpal::StreamConfig = config.into(); let cfg: cpal::StreamConfig = config.into();
@ -165,7 +165,6 @@ impl AudioSystem {
eprintln!("Warning: Could not get input config: {}, recording will be disabled", e); eprintln!("Warning: Could not get input config: {}, recording will be disabled", e);
output_stream.play().map_err(|e| e.to_string())?; output_stream.play().map_err(|e| e.to_string())?;
// Spawn emitter thread if provided
if let Some(emitter) = event_emitter { if let Some(emitter) = event_emitter {
Self::spawn_emitter_thread(event_rx, emitter); Self::spawn_emitter_thread(event_rx, emitter);
} }
@ -180,13 +179,57 @@ impl AudioSystem {
} }
}; };
// Build input stream that feeds into the ringbuffer let input_sample_rate = input_config.sample_rate;
let input_channels = input_config.channels as u32;
let output_sample_rate = sample_rate;
let output_channels = channels;
let needs_resample = input_sample_rate != output_sample_rate || input_channels != output_channels;
if needs_resample {
eprintln!("[AUDIO] Input device: {}Hz {}ch -> resampling to {}Hz {}ch",
input_sample_rate, input_channels, output_sample_rate, output_channels);
}
// Build input stream with resampling if needed
let input_stream = match input_device let input_stream = match input_device
.build_input_stream( .build_input_stream(
&input_config, &input_config,
move |data: &[f32], _: &cpal::InputCallbackInfo| { move |data: &[f32], _: &cpal::InputCallbackInfo| {
for &sample in data { if !needs_resample {
let _ = input_tx.push(sample); for &sample in data {
let _ = input_tx.push(sample);
}
} else {
// Resample: linear interpolation from input rate to output rate
let in_ch = input_channels as usize;
let out_ch = output_channels as usize;
let ratio = output_sample_rate as f64 / input_sample_rate as f64;
let in_frames = data.len() / in_ch;
let out_frames = (in_frames as f64 * ratio) as usize;
for i in 0..out_frames {
let src_pos = i as f64 / ratio;
let src_idx = src_pos as usize;
let frac = (src_pos - src_idx as f64) as f32;
for ch in 0..out_ch {
// Map output channel to input channel
let in_ch_idx = ch.min(in_ch - 1);
let s0 = if src_idx < in_frames {
data[src_idx * in_ch + in_ch_idx]
} else {
0.0
};
let s1 = if src_idx + 1 < in_frames {
data[(src_idx + 1) * in_ch + in_ch_idx]
} else {
s0
};
let _ = input_tx.push(s0 + frac * (s1 - s0));
}
}
} }
}, },
|err| eprintln!("Input stream error: {}", err), |err| eprintln!("Input stream error: {}", err),

View File

@ -0,0 +1,29 @@
@echo off
REM Build script for Windows
REM Requires: FFmpeg 8.0.0 dev files in C:\ffmpeg, LLVM installed
REM FFmpeg location (headers + libs + DLLs)
if not defined FFMPEG_DIR set FFMPEG_DIR=C:\ffmpeg
REM LLVM/libclang for bindgen (ffmpeg-sys-next)
if not defined LIBCLANG_PATH set LIBCLANG_PATH=C:\Program Files\LLVM\bin
REM Validate prerequisites
if not exist "%FFMPEG_DIR%\include\libavcodec\avcodec.h" (
echo ERROR: FFmpeg dev files not found at %FFMPEG_DIR%
echo Download FFmpeg 8.0.0 shared+dev from https://github.com/GyanD/codexffmpeg/releases
echo and extract to %FFMPEG_DIR%
exit /b 1
)
if not exist "%LIBCLANG_PATH%\libclang.dll" (
echo ERROR: LLVM/libclang not found at %LIBCLANG_PATH%
echo Install with: winget install LLVM.LLVM
exit /b 1
)
echo Building Lightningbeam Editor...
echo FFMPEG_DIR=%FFMPEG_DIR%
echo LIBCLANG_PATH=%LIBCLANG_PATH%
cargo build --package lightningbeam-editor %*