1use hound::{SampleFormat, WavReader, WavSpec, WavWriter};
7use std::path::Path;
8
9use crate::PedalProcessor;
10
11pub const DEFAULT_SAMPLE_RATE: u32 = 48_000;
13
14fn wav_spec(sample_rate: u32) -> WavSpec {
15 WavSpec {
16 channels: 1,
17 sample_rate,
18 bits_per_sample: 32,
19 sample_format: SampleFormat::Float,
20 }
21}
22
23pub fn sine_wave(freq_hz: f64, duration_secs: f64, sample_rate: u32) -> Vec<f64> {
25 let n = (duration_secs * sample_rate as f64) as usize;
26 let mut buf = Vec::with_capacity(n);
27 for i in 0..n {
28 let t = i as f64 / sample_rate as f64;
29 buf.push(0.5 * (2.0 * std::f64::consts::PI * freq_hz * t).sin());
30 }
31 buf
32}
33
34pub fn guitar_pluck(freq_hz: f64, duration_secs: f64, sample_rate: u32) -> Vec<f64> {
36 let n = (duration_secs * sample_rate as f64) as usize;
37 let mut buf = Vec::with_capacity(n);
38 for i in 0..n {
39 let t = i as f64 / sample_rate as f64;
40 let envelope = (-3.0 * t).exp(); let fundamental = (2.0 * std::f64::consts::PI * freq_hz * t).sin();
42 let h2 = 0.5 * (2.0 * std::f64::consts::PI * 2.0 * freq_hz * t).sin();
43 let h3 = 0.25 * (2.0 * std::f64::consts::PI * 3.0 * freq_hz * t).sin();
44 let h4 = 0.125 * (2.0 * std::f64::consts::PI * 4.0 * freq_hz * t).sin();
45 buf.push(0.4 * envelope * (fundamental + h2 + h3 + h4));
46 }
47 buf
48}
49
50pub fn read_wav_mono(path: &Path) -> Result<(Vec<f64>, u32), Box<dyn std::error::Error>> {
54 let mut reader = WavReader::open(path)?;
55 let spec = reader.spec();
56 let sample_rate = spec.sample_rate;
57 let channels = spec.channels as usize;
58
59 let samples = match spec.sample_format {
60 SampleFormat::Float => {
61 let all: Vec<f32> = reader.samples::<f32>().map(|s| s.unwrap()).collect();
62 mix_to_mono(&all.iter().map(|&s| s as f64).collect::<Vec<_>>(), channels)
63 }
64 SampleFormat::Int => {
65 let max_val = (1_i64 << (spec.bits_per_sample - 1)) as f64;
66 let all: Vec<i32> = reader.samples::<i32>().map(|s| s.unwrap()).collect();
67 let normalized: Vec<f64> = all.iter().map(|&s| s as f64 / max_val).collect();
68 mix_to_mono(&normalized, channels)
69 }
70 };
71
72 Ok((samples, sample_rate))
73}
74
75fn mix_to_mono(interleaved: &[f64], channels: usize) -> Vec<f64> {
77 if channels == 1 {
78 return interleaved.to_vec();
79 }
80 interleaved
81 .chunks(channels)
82 .map(|frame| frame.iter().sum::<f64>() / channels as f64)
83 .collect()
84}
85
86pub fn render_to_wav<P: PedalProcessor>(
88 pedal: &mut P,
89 input: &[f64],
90 path: &Path,
91 sample_rate: u32,
92) -> Result<(), Box<dyn std::error::Error>> {
93 pedal.set_sample_rate(sample_rate as crate::Wave);
94 pedal.reset();
95
96 let mut writer = WavWriter::create(path, wav_spec(sample_rate))?;
97 for &sample in input {
98 let out = pedal.process(sample as crate::Wave);
99 writer.write_sample(out as f32)?;
100 }
101 writer.finalize()?;
102 Ok(())
103}
104
105pub fn write_wav(
107 samples: &[f64],
108 path: &Path,
109 sample_rate: u32,
110) -> Result<(), Box<dyn std::error::Error>> {
111 let mut writer = WavWriter::create(path, wav_spec(sample_rate))?;
112 for &s in samples {
113 writer.write_sample(s as f32)?;
114 }
115 writer.finalize()?;
116 Ok(())
117}
118
119pub fn write_stereo_wav(
121 left: &[f64],
122 right: &[f64],
123 path: &Path,
124 sample_rate: u32,
125) -> Result<(), Box<dyn std::error::Error>> {
126 let spec = WavSpec {
127 channels: 2,
128 sample_rate,
129 bits_per_sample: 32,
130 sample_format: SampleFormat::Float,
131 };
132 let mut writer = WavWriter::create(path, spec)?;
133 let n = left.len().min(right.len());
134 for i in 0..n {
135 writer.write_sample(left[i] as f32)?;
136 writer.write_sample(right[i] as f32)?;
137 }
138 writer.finalize()?;
139 Ok(())
140}
141
142#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn sine_wave_length() {
152 let buf = sine_wave(440.0, 1.0, 48000);
153 assert_eq!(buf.len(), 48000);
154 }
155
156 #[test]
157 fn sine_wave_amplitude() {
158 let buf = sine_wave(440.0, 1.0, 48000);
159 let max = buf.iter().copied().fold(0.0_f64, |a, b| a.max(b.abs()));
160 assert!((max - 0.5).abs() < 0.01, "expected peak ≈ 0.5, got {max}");
161 }
162
163 #[test]
164 fn guitar_pluck_decays() {
165 let buf = guitar_pluck(82.41, 2.0, 48000);
166 let rms_start: f64 = buf[..1000].iter().map(|x| x * x).sum::<f64>() / 1000.0;
168 let rms_end: f64 = buf[buf.len() - 1000..].iter().map(|x| x * x).sum::<f64>() / 1000.0;
169 assert!(rms_start > rms_end * 10.0, "signal should decay");
170 }
171
172 #[test]
173 fn render_wav_roundtrip() {
174 let tmp = std::env::temp_dir().join("pedalkernel_test_render.wav");
175 let input = sine_wave(440.0, 0.1, 48000);
176 let pedal = crate::dsl::parse_pedal_file(include_str!(
178 "../examples/pedals/distortion/proco_rat.pedal"
179 ))
180 .expect("should parse RAT pedal");
181 let mut compiled =
182 crate::compiler::compile_pedal(&pedal, 48000.0).expect("should compile RAT pedal");
183 render_to_wav(&mut compiled, &input, &tmp, 48000).unwrap();
184
185 let reader = hound::WavReader::open(&tmp).unwrap();
187 assert_eq!(reader.spec().sample_rate, 48000);
188 assert_eq!(reader.len(), input.len() as u32);
189 let _ = std::fs::remove_file(&tmp);
190 }
191}