Function bodies 463 total
compute_rms_known_value function · rust · L269-L272 (4 LOC)src/audio/capture.rs
fn compute_rms_known_value() {
let rms = compute_rms(&[1.0, -1.0]);
assert!((rms - 1.0).abs() < 0.001);
}audio_state_rms_db_silence function · rust · L275-L285 (11 LOC)src/audio/capture.rs
fn audio_state_rms_db_silence() {
let state = AudioState {
live_rms: Arc::new(AtomicU32::new(0_f32.to_bits())),
stop: Arc::new(AtomicBool::new(false)),
waveform_buffer: Arc::new(Mutex::new(VecDeque::new())),
live_pitch: Arc::new(AtomicU32::new(0)),
sample_rate: 44100,
};
assert!(state.rms_db().is_infinite());
assert!(state.is_silent());
}audio_state_rms_db_signal function · rust · L288-L300 (13 LOC)src/audio/capture.rs
fn audio_state_rms_db_signal() {
let rms: f32 = 0.1; // -20 dB
let state = AudioState {
live_rms: Arc::new(AtomicU32::new(rms.to_bits())),
stop: Arc::new(AtomicBool::new(false)),
waveform_buffer: Arc::new(Mutex::new(VecDeque::new())),
live_pitch: Arc::new(AtomicU32::new(0)),
sample_rate: 44100,
};
let db = state.rms_db();
assert!((db - (-20.0)).abs() < 0.1);
assert!(!state.is_silent());
}audio_state_pitch_none_when_zero function · rust · L303-L312 (10 LOC)src/audio/capture.rs
fn audio_state_pitch_none_when_zero() {
let state = AudioState {
live_rms: Arc::new(AtomicU32::new(0_f32.to_bits())),
stop: Arc::new(AtomicBool::new(false)),
waveform_buffer: Arc::new(Mutex::new(VecDeque::new())),
live_pitch: Arc::new(AtomicU32::new(0)),
sample_rate: 44100,
};
assert!(state.pitch_hz().is_none());
}audio_state_pitch_some_when_set function · rust · L315-L325 (11 LOC)src/audio/capture.rs
fn audio_state_pitch_some_when_set() {
let hz: f32 = 440.0;
let state = AudioState {
live_rms: Arc::new(AtomicU32::new(0_f32.to_bits())),
stop: Arc::new(AtomicBool::new(false)),
waveform_buffer: Arc::new(Mutex::new(VecDeque::new())),
live_pitch: Arc::new(AtomicU32::new(hz.to_bits())),
sample_rate: 44100,
};
assert_eq!(state.pitch_hz(), Some(440.0));
}waveform_snapshot_empty function · rust · L328-L337 (10 LOC)src/audio/capture.rs
fn waveform_snapshot_empty() {
let state = AudioState {
live_rms: Arc::new(AtomicU32::new(0_f32.to_bits())),
stop: Arc::new(AtomicBool::new(false)),
waveform_buffer: Arc::new(Mutex::new(VecDeque::new())),
live_pitch: Arc::new(AtomicU32::new(0)),
sample_rate: 44100,
};
assert!(state.waveform_snapshot().is_empty());
}waveform_snapshot_returns_data function · rust · L340-L354 (15 LOC)src/audio/capture.rs
fn waveform_snapshot_returns_data() {
let mut buf = VecDeque::new();
buf.push_back(0.1);
buf.push_back(0.2);
let state = AudioState {
live_rms: Arc::new(AtomicU32::new(0_f32.to_bits())),
stop: Arc::new(AtomicBool::new(false)),
waveform_buffer: Arc::new(Mutex::new(buf)),
live_pitch: Arc::new(AtomicU32::new(0)),
sample_rate: 44100,
};
let snap = state.waveform_snapshot();
assert_eq!(snap.len(), 2);
assert!((snap[0] - 0.1).abs() < 0.001);
}Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
list_devices function · rust · L6-L71 (66 LOC)src/audio/devices.rs
pub fn list_devices() -> Result<()> {
let host = cpal::default_host();
let default_device = host.default_input_device();
let default_name = default_device
.as_ref()
.and_then(|d| d.name().ok())
.unwrap_or_default();
let devices: Vec<_> = host.input_devices()?.collect();
if devices.is_empty() {
eprintln!("No audio input devices found.");
return Ok(());
}
println!("{}", style("Audio Input Devices").bold());
println!();
for device in &devices {
let name = device.name().unwrap_or_else(|_| "<unknown>".into());
let is_default = name == default_name;
if is_default {
print!(" {} ", style("*").green().bold());
print!("{}", style(&name).green().bold());
} else {
print!(" {}", style(&name).bold());
}
println!();
match device.supported_input_configs() {
Ok(configs) => {
for cfg in configs {
run_sustain_exercise function · rust · L22-L147 (126 LOC)src/audio/exercise.rs
pub fn run_sustain_exercise(config: &AppConfig) -> Result<()> {
// --- Phase 1: Setup ---
let reference_mpt = load_reference_mpt();
println!();
println!(
"{}",
style("=== Sustained Phonation Exercise ===").bold()
);
println!();
println!(" Take a deep breath, then hold {} as long and steady as you can.", style("\"AAAH\"").cyan());
println!(" The exercise auto-stops when you go silent.");
println!();
if let Some(mpt) = reference_mpt {
println!(" Your last MPT: {:.1}s — try to match or beat it.", mpt);
} else {
println!(" No previous sessions found — just hold as long as you can.");
}
println!();
println!(
" Press {} when ready.",
style("Enter").green().bold()
);
crate::audio::recorder::wait_for_enter()?;
// --- Phase 2: TUI recording ---
let mut terminal = crate::tui::init()?;
let (audio_state, stream, collector) = capture::start_capture(false)?;
let sload_reference_mpt function · rust · L150-L160 (11 LOC)src/audio/exercise.rs
fn load_reference_mpt() -> Option<f32> {
let dates = storage::store::list_sessions().ok()?;
for date in dates.iter().rev() {
if let Ok(session) = storage::store::load_session(date) {
if let Some(ref s) = session.analysis.sustained {
return Some(s.mpt_seconds);
}
}
}
None
}format_comparison function · rust · L163-L176 (14 LOC)src/audio/exercise.rs
fn format_comparison(current: f32, reference: f32, unit: &str, higher_is_better: bool) -> String {
let diff = current - reference;
let sign = if diff >= 0.0 { "+" } else { "" };
let color_good = diff > 0.0 && higher_is_better || diff < 0.0 && !higher_is_better;
let text = format!("{sign}{diff:.1}{unit}");
if color_good {
style(text).green().to_string()
} else if diff.abs() < 0.1 {
style(text).dim().to_string()
} else {
style(text).red().to_string()
}
}print_metric function · rust · L179-L184 (6 LOC)src/audio/exercise.rs
fn print_metric(label: &str, value: &str, annotation: Option<String>) {
match annotation {
Some(ann) => println!(" {:12} {:>12} {}", style(label).bold(), value, ann),
None => println!(" {:12} {:>12}", style(label).bold(), value),
}
}rate_jitter function · rust · L187-L193 (7 LOC)src/audio/exercise.rs
fn rate_jitter(jitter: f32, thresholds: &crate::config::ThresholdConfig) -> String {
if jitter < thresholds.jitter_pathological {
style("normal").green().to_string()
} else {
style("elevated").yellow().to_string()
}
}rate_shimmer function · rust · L196-L202 (7 LOC)src/audio/exercise.rs
fn rate_shimmer(shimmer: f32, thresholds: &crate::config::ThresholdConfig) -> String {
if shimmer < thresholds.shimmer_pathological {
style("normal").green().to_string()
} else {
style("elevated").yellow().to_string()
}
}rate_cpps function · rust · L205-L213 (9 LOC)src/audio/exercise.rs
fn rate_cpps(cpps: f32) -> String {
if cpps >= 5.0 {
style("normal").green().to_string()
} else if cpps >= 3.0 {
style("mild dysphonia").yellow().to_string()
} else {
style("significant dysphonia").red().to_string()
}
}Same scanner, your repo: https://repobility.com — Repobility
rate_hnr function · rust · L216-L224 (9 LOC)src/audio/exercise.rs
fn rate_hnr(hnr: f32, thresholds: &crate::config::ThresholdConfig) -> String {
if hnr >= thresholds.hnr_normal {
style("healthy").green().to_string()
} else if hnr >= thresholds.hnr_low {
style("fair").yellow().to_string()
} else {
style("low").red().to_string()
}
}format_comparison_positive function · rust · L231-L234 (4 LOC)src/audio/exercise.rs
fn format_comparison_positive() {
let result = format_comparison(10.0, 8.0, "s", true);
assert!(result.contains("2.0s"));
}format_comparison_negative function · rust · L237-L240 (4 LOC)src/audio/exercise.rs
fn format_comparison_negative() {
let result = format_comparison(6.0, 8.0, "s", true);
assert!(result.contains("2.0s"));
}format_comparison_equal function · rust · L243-L246 (4 LOC)src/audio/exercise.rs
fn format_comparison_equal() {
let result = format_comparison(8.0, 8.0, "s", true);
assert!(result.contains("0.0s"));
}rate_jitter_normal function · rust · L249-L253 (5 LOC)src/audio/exercise.rs
fn rate_jitter_normal() {
let thresholds = crate::config::ThresholdConfig::default();
let result = rate_jitter(0.5, &thresholds);
assert!(result.contains("normal"));
}rate_jitter_elevated function · rust · L256-L260 (5 LOC)src/audio/exercise.rs
fn rate_jitter_elevated() {
let thresholds = crate::config::ThresholdConfig::default();
let result = rate_jitter(2.0, &thresholds);
assert!(result.contains("elevated"));
}rate_shimmer_normal function · rust · L263-L267 (5 LOC)src/audio/exercise.rs
fn rate_shimmer_normal() {
let thresholds = crate::config::ThresholdConfig::default();
let result = rate_shimmer(2.0, &thresholds);
assert!(result.contains("normal"));
}rate_shimmer_elevated function · rust · L270-L274 (5 LOC)src/audio/exercise.rs
fn rate_shimmer_elevated() {
let thresholds = crate::config::ThresholdConfig::default();
let result = rate_shimmer(5.0, &thresholds);
assert!(result.contains("elevated"));
}Repobility (the analyzer behind this table) · https://repobility.com
rate_hnr_healthy function · rust · L277-L281 (5 LOC)src/audio/exercise.rs
fn rate_hnr_healthy() {
let thresholds = crate::config::ThresholdConfig::default();
let result = rate_hnr(25.0, &thresholds);
assert!(result.contains("healthy"));
}rate_hnr_fair function · rust · L284-L288 (5 LOC)src/audio/exercise.rs
fn rate_hnr_fair() {
let thresholds = crate::config::ThresholdConfig::default();
let result = rate_hnr(12.0, &thresholds);
assert!(result.contains("fair"));
}rate_hnr_low function · rust · L291-L295 (5 LOC)src/audio/exercise.rs
fn rate_hnr_low() {
let thresholds = crate::config::ThresholdConfig::default();
let result = rate_hnr(3.0, &thresholds);
assert!(result.contains("low"));
}load_reference_mpt_no_sessions function · rust · L298-L300 (3 LOC)src/audio/exercise.rs
fn load_reference_mpt_no_sessions() {
let _ = load_reference_mpt();
}run_fatigue_exercise function · rust · L17-L154 (138 LOC)src/audio/fatigue_exercise.rs
pub fn run_fatigue_exercise(_config: &AppConfig) -> Result<()> {
println!();
println!("{}", style("=== Vocal Fatigue Test ===").bold());
println!();
println!(" You'll hold \"AAAH\" 5 times with 45s rest between trials.");
println!(" This measures if your voice tires with repeated use.");
println!();
println!(" Press {} when ready.", style("Enter").green().bold());
crate::audio::recorder::wait_for_enter()?;
// TUI phase
let mut terminal = crate::tui::init()?;
let (audio_state, stream, collector) = capture::start_capture(false)?;
let sample_rate = audio_state.sample_rate;
let outcome = crate::tui::screens::fatigue::run(
&mut terminal,
&audio_state,
)?;
crate::tui::restore()?;
// Stop audio, collect samples
audio_state.stop.store(true, Ordering::Relaxed);
drop(stream);
let outcome = match outcome {
Some(o) => o,
None => {
drop(collector);
println!();run function · rust · L21-L161 (141 LOC)src/audio/mic_check.rs
pub fn run() -> Result<()> {
let host = cpal::default_host();
let device = host
.default_input_device()
.context("No default input device found")?;
let device_name = device.name().unwrap_or_else(|_| "<unknown>".into());
let config = device
.default_input_config()
.context("Failed to get default input config")?;
let sample_rate = config.sample_rate().0;
let channels = config.channels() as usize;
let format = config.sample_format();
println!(
" Device: {}",
style(&device_name).cyan().bold()
);
println!(" Config: {channels}ch, {sample_rate} Hz, {format:?}");
println!();
println!(
" Press {} to capture a 2-second sample.",
style("Enter").green().bold()
);
recorder::wait_for_enter()?;
println!();
// Channel to send captured samples from audio thread to main thread
let (tx, rx) = mpsc::channel::<Vec<f32>>();
// Build input stream based on sample play function · rust · L14-L51 (38 LOC)src/audio/playback.rs
pub fn play(target: &str, exercise: Option<&str>) -> Result<()> {
let path = resolve_play_path(target, exercise)?;
println!(
"Playing {}",
style(path.display()).green()
);
// OutputStream::try_default() opens the default output device.
// We must keep `_stream` alive — it's an RAII guard. If we used `_` instead
// of `_stream`, Rust would drop it immediately (underscore = "I don't need this").
// `_stream` (with a name) keeps it alive until the end of this function scope.
let (_stream, stream_handle) =
OutputStream::try_default().context("Failed to open audio output device")?;
// Sink gives us playback control (pause, stop, wait).
// It runs on a background thread managed by rodio.
let sink = Sink::try_new(&stream_handle).context("Failed to create audio sink")?;
let file = File::open(&path)
.with_context(|| format!("Failed to open: {}", path.display()))?;
let reader = BufReader::new(file);
// Dresolve_play_path function · rust · L54-L69 (16 LOC)src/audio/playback.rs
fn resolve_play_path(target: &str, exercise: Option<&str>) -> Result<PathBuf> {
// If target looks like a file path (contains a dot or slash), use it directly
if target.contains('/') || target.contains('.') {
return Ok(PathBuf::from(target));
}
// Otherwise treat it as a date — exercise name is required
let exercise = exercise.context(
"When playing by date, you must specify an exercise name.\n\
Usage: voicevo play 2026-02-08 sustained",
)?;
let date = util::resolve_date(Some(target))?;
paths::latest_attempt_path(&date, exercise)
.context(format!("No recordings found for {exercise} on {date}"))
}Repobility · open methodology · https://repobility.com/research/
resolve_direct_path function · rust · L76-L79 (4 LOC)src/audio/playback.rs
fn resolve_direct_path() {
let path = resolve_play_path("./test.wav", None).unwrap();
assert_eq!(path, PathBuf::from("./test.wav"));
}resolve_absolute_path function · rust · L82-L85 (4 LOC)src/audio/playback.rs
fn resolve_absolute_path() {
let path = resolve_play_path("/tmp/test.wav", None).unwrap();
assert_eq!(path, PathBuf::from("/tmp/test.wav"));
}resolve_date_with_exercise_no_recordings function · rust · L88-L92 (5 LOC)src/audio/playback.rs
fn resolve_date_with_exercise_no_recordings() {
// With no recordings on disk, latest_attempt_path returns None → error
let result = resolve_play_path("2099-01-01", Some("sustained"));
assert!(result.is_err());
}resolve_date_without_exercise_fails function · rust · L95-L98 (4 LOC)src/audio/playback.rs
fn resolve_date_without_exercise_fails() {
let result = resolve_play_path("2026-02-08", None);
assert!(result.is_err());
}record_exercise function · rust · L26-L113 (88 LOC)src/audio/recorder.rs
pub fn record_exercise(exercise: &str, date: &NaiveDate, config: &crate::config::AppConfig) -> Result<()> {
let path = paths::next_attempt_path(date, exercise);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
println!(
"{} {}",
style("Exercise:").bold(),
style(exercise).cyan()
);
println!(
"{} {}",
style("Date:").bold(),
date
);
println!(
"{} {}",
style("Output:").bold(),
path.display()
);
println!();
// Show exercise-specific instructions
match exercise {
"sustained" => {
println!(
" Take a deep breath, then hold {} as long as comfortable.",
style("\"AAAH\"").cyan()
);
println!();
}
"scale" => {
println!(
" Sing from your {} comfortable note up to your {},",
style("lowest").cyan(),
record_to_file function · rust · L122-L236 (115 LOC)src/audio/recorder.rs
pub fn record_to_file(path: &std::path::Path) -> Result<RecordingStats> {
let host = cpal::default_host();
let device = host
.default_input_device()
.context("No default input device found")?;
let config = device
.default_input_config()
.context("Failed to get default input config")?;
let sample_rate = config.sample_rate().0;
let channels = config.channels() as usize;
let format = config.sample_format();
// Channel for sending audio data from cpal callback to writer thread
let (tx, rx) = mpsc::channel::<Vec<f32>>();
// Stop signal: set to true when user presses Enter
let stop = Arc::new(AtomicBool::new(false));
let stop_for_stream = Arc::clone(&stop);
// Build the input stream. The closure captures tx by move.
let stream = match format {
SampleFormat::F32 => device.build_input_stream(
&config.into(),
move |data: &[f32], _: &cpal::InputCallbackInfo| {
wait_for_enter function · rust · L239-L254 (16 LOC)src/audio/recorder.rs
pub fn wait_for_enter() -> Result<()> {
crossterm::terminal::enable_raw_mode()?;
loop {
if event::poll(std::time::Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press && key.code == KeyCode::Enter {
break;
}
}
}
}
crossterm::terminal::disable_raw_mode()?;
Ok(())
}wait_for_keep_or_rerecord function · rust · L265-L286 (22 LOC)src/audio/recorder.rs
pub fn wait_for_keep_or_rerecord() -> Result<PostRecordChoice> {
crossterm::terminal::enable_raw_mode()?;
let choice = loop {
if event::poll(std::time::Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Enter => break PostRecordChoice::Keep,
KeyCode::Char('r') | KeyCode::Char('R') => {
break PostRecordChoice::Rerecord
}
_ => {}
}
}
}
}
};
crossterm::terminal::disable_raw_mode()?;
Ok(choice)
}Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
run_scale_exercise function · rust · L16-L125 (110 LOC)src/audio/scale_exercise.rs
pub fn run_scale_exercise(config: &AppConfig) -> Result<()> {
println!();
println!("{}", style("=== Chromatic Scale Exercise ===").bold());
println!();
println!(" Sing from your lowest note to your highest, then back down.");
println!(" The display shows your current pitch in real time.");
println!();
println!(" Press {} when ready.", style("Enter").green().bold());
crate::audio::recorder::wait_for_enter()?;
// TUI phase: recording with live pitch feedback
let mut terminal = crate::tui::init()?;
let (audio_state, stream, collector) = capture::start_capture(true)?;
let sample_rate = audio_state.sample_rate;
let outcome = crate::tui::screens::scale::run(&mut terminal, &audio_state)?;
crate::tui::restore()?;
// Stop audio, collect samples
audio_state.stop.store(true, Ordering::Relaxed);
drop(stream);
let outcome = match outcome {
Some(o) => o,
None => {
collector.join().map_err(|_|run_guided_session function · rust · L22-L149 (128 LOC)src/audio/session.rs
pub fn run_guided_session(date: &NaiveDate, config: &AppConfig) -> Result<()> {
let date_str = date.to_string();
println!();
println!(
"{}",
style("=== Voice Recovery Tracker — Recording Session ===").bold()
);
println!(" Date: {}", style(date).cyan());
println!();
// --- Step 1: Mic check ---
println!(
"{} {}",
style("Step 1/4:").bold(),
"Mic check"
);
println!();
mic_check::run()?;
println!();
println!(
" Press {} to continue to the exercises.",
style("Enter").green().bold()
);
recorder::wait_for_enter()?;
println!();
// --- Step 2: Sustained vowel ---
println!(
"{} {}",
style("Step 2/4:").bold(),
"Sustained vowel"
);
println!();
println!(
" Take a deep breath, then hold {} as long as comfortable.",
style("\"AAAH\"").cyan()
);
println!();
let sustained_stats = record_with_retry(date, "srecord_with_retry function · rust · L155-L189 (35 LOC)src/audio/session.rs
fn record_with_retry(
date: &NaiveDate,
exercise: &str,
) -> Result<recorder::RecordingStats> {
loop {
let path = paths::next_attempt_path(date, exercise);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let stats = record_exercise_with_path(&path)?;
println!(
" Press {} to keep, {} to re-record.",
style("Enter").green().bold(),
style("r").yellow().bold(),
);
match recorder::wait_for_keep_or_rerecord()? {
PostRecordChoice::Keep => {
println!();
return Ok(stats);
}
PostRecordChoice::Rerecord => {
std::fs::remove_file(&path)?;
println!(
" {} Re-recording {}...",
style("DISCARDED").yellow(),
exercise
);
println!();
}
}
}
}record_exercise_with_path function · rust · L192-L229 (38 LOC)src/audio/session.rs
fn record_exercise_with_path(
path: &std::path::Path,
) -> Result<recorder::RecordingStats> {
println!(
" Press {} when ready to record.",
style("Enter").green().bold()
);
recorder::wait_for_enter()?;
println!();
println!(
" {} Press {} to stop.",
style("*** RECORDING ***").red().bold(),
style("Enter").bold()
);
let stats = recorder::record_to_file(path)?;
println!(
" {}",
style("*** STOPPED ***").dim()
);
println!();
println!(
" Duration: {:.1}s | Peak: {:.1} dB | RMS: {:.1} dB",
stats.duration_secs, stats.peak_db, stats.rms_db
);
if stats.peak_db < -60.0 {
eprintln!(
" {} Recording appears silent!",
style("WARNING").red().bold()
);
}
println!();
Ok(stats)
}print_summary_row function · rust · L230-L236 (7 LOC)src/audio/session.rs
fn print_summary_row(name: &str, stats: &recorder::RecordingStats) {
println!(
" {:12} {:>9.1}s {:>9.1} {:>9.1}",
name, stats.duration_secs, stats.peak_db, stats.rms_db
);
}run_sz_exercise function · rust · L15-L102 (88 LOC)src/audio/sz_exercise.rs
pub fn run_sz_exercise(_config: &AppConfig) -> Result<()> {
println!();
println!("{}", style("=== S/Z Ratio Test ===").bold());
println!();
println!(" This test compares how long you can hold /s/ vs /z/.");
println!(" A ratio close to 1.0 means healthy vocal cord function.");
println!(" A ratio above 1.4 may indicate air leak through the vocal cords.");
println!();
println!(" Press {} when ready.", style("Enter").green().bold());
crate::audio::recorder::wait_for_enter()?;
// TUI phase
let mut terminal = crate::tui::init()?;
let (audio_state, stream, collector) = capture::start_capture(false)?;
let outcome = crate::tui::screens::sz::run(&mut terminal, &audio_state)?;
crate::tui::restore()?;
// Stop audio
audio_state.stop.store(true, Ordering::Relaxed);
drop(stream);
drop(collector);
let outcome = match outcome {
Some(o) => o,
None => {
println!();
println!(" Canrecording_spec function · rust · L7-L14 (8 LOC)src/audio/wav.rs
pub fn recording_spec(sample_rate: u32) -> WavSpec {
WavSpec {
channels: 1,
sample_rate,
bits_per_sample: 16,
sample_format: SampleFormat::Int,
}
}create_writer function · rust · L17-L25 (9 LOC)src/audio/wav.rs
pub fn create_writer(path: &Path, spec: WavSpec) -> Result<WavWriter<std::io::BufWriter<std::fs::File>>> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("Failed to create directory: {}", parent.display()))?;
}
WavWriter::create(path, spec)
.with_context(|| format!("Failed to create WAV file: {}", path.display()))
}Same scanner, your repo: https://repobility.com — Repobility
load_samples function · rust · L29-L51 (23 LOC)src/audio/wav.rs
pub fn load_samples(path: &Path) -> Result<(Vec<f32>, WavSpec)> {
let mut reader = WavReader::open(path)
.with_context(|| format!("Failed to open WAV file: {}", path.display()))?;
let spec = reader.spec();
let samples: Vec<f32> = match spec.sample_format {
SampleFormat::Int => {
let max_val = (1 << (spec.bits_per_sample - 1)) as f32;
reader
.samples::<i32>()
.map(|s| s.map(|v| v as f32 / max_val))
.collect::<hound::Result<Vec<_>>>()
.context("Failed to read WAV samples")?
}
SampleFormat::Float => reader
.samples::<f32>()
.collect::<hound::Result<Vec<_>>>()
.context("Failed to read WAV samples")?,
};
Ok((samples, spec))
}test_wav_path function · rust · L57-L63 (7 LOC)src/audio/wav.rs
fn test_wav_path() -> PathBuf {
// Use a temp dir so tests don't pollute the working directory
let dir = std::env::temp_dir().join("voicevo-tests");
std::fs::create_dir_all(&dir).unwrap();
dir.join("roundtrip.wav")
}wav_roundtrip function · rust · L66-L99 (34 LOC)src/audio/wav.rs
fn wav_roundtrip() {
let path = test_wav_path();
let spec = recording_spec(44100);
// Write a known signal: a short ramp
let original: Vec<f32> = (0..1000).map(|i| (i as f32 / 1000.0) * 2.0 - 1.0).collect();
{
let mut writer = create_writer(&path, spec).unwrap();
for &sample in &original {
// Convert f32 to i16 for writing
let s16 = (sample * i16::MAX as f32) as i16;
writer.write_sample(s16).unwrap();
}
writer.finalize().unwrap();
}
// Read back and verify
let (loaded, loaded_spec) = load_samples(&path).unwrap();
assert_eq!(loaded_spec.sample_rate, 44100);
assert_eq!(loaded_spec.channels, 1);
assert_eq!(loaded.len(), original.len());
// Verify samples match within quantization error (16-bit → ~0.00003 tolerance)
for (orig, loaded) in original.iter().zip(loaded.iter()) {