sim_lib_sound_render/
model.rs1use std::io::Write;
2use std::time::Duration;
3
4use sim_lib_sound_bridge::ScheduledTone;
5use sim_lib_sound_core::Tone;
6
7use crate::SoundRenderError;
8
9#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11pub struct RendererOptions {
12 pub sample_rate: u32,
14 pub channels: u8,
16}
17
18impl RendererOptions {
19 pub fn new(sample_rate: u32, channels: u8) -> Result<Self, SoundRenderError> {
22 if sample_rate == 0 {
23 return Err(SoundRenderError::InvalidSampleRate);
24 }
25 if !(1..=2).contains(&channels) {
26 return Err(SoundRenderError::InvalidChannelCount);
27 }
28 Ok(Self {
29 sample_rate,
30 channels,
31 })
32 }
33}
34
35impl Default for RendererOptions {
36 fn default() -> Self {
37 Self {
38 sample_rate: 44_100,
39 channels: 2,
40 }
41 }
42}
43
44#[derive(Copy, Clone, Debug, PartialEq, Eq)]
47pub struct PcmRenderer {
48 pub sample_rate: u32,
50 pub channels: u8,
52}
53
54impl PcmRenderer {
55 pub fn new(options: RendererOptions) -> Result<Self, SoundRenderError> {
57 let _ = RendererOptions::new(options.sample_rate, options.channels)?;
58 Ok(Self {
59 sample_rate: options.sample_rate,
60 channels: options.channels,
61 })
62 }
63
64 pub fn render_tone(&self, tone: &Tone) -> Vec<f32> {
79 self.render_tone_with_pan(tone, 0.0)
80 }
81
82 pub fn render_mix(&self, tones: &[ScheduledTone]) -> Vec<f32> {
85 let frames = tones
86 .iter()
87 .map(|scheduled| {
88 start_frame(self.sample_rate, scheduled.start)
89 + tone_frames(self.sample_rate, &scheduled.tone)
90 })
91 .max()
92 .unwrap_or(0);
93 let mut mix = vec![0.0_f32; frames * usize::from(self.channels)];
94 for scheduled in tones {
95 let rendered = self.render_tone_with_pan(&scheduled.tone, scheduled.pan);
96 let offset =
97 start_frame(self.sample_rate, scheduled.start) * usize::from(self.channels);
98 for (index, sample) in rendered.iter().enumerate() {
99 if let Some(slot) = mix.get_mut(offset + index) {
100 *slot += *sample;
101 }
102 }
103 }
104 mix
105 }
106
107 pub fn write_wav<W: Write>(
110 &self,
111 samples: &[f32],
112 mut writer: W,
113 ) -> Result<W, SoundRenderError> {
114 let sample_count =
115 u32::try_from(samples.len()).map_err(|_| SoundRenderError::BufferTooLarge)?;
116 let bytes_per_sample = 2_u16;
117 let block_align = self.channels as u16 * bytes_per_sample;
118 let byte_rate = self.sample_rate * u32::from(block_align);
119 let data_size = sample_count * u32::from(bytes_per_sample);
120 let riff_size = 36_u32 + data_size;
121 writer
122 .write_all(b"RIFF")
123 .map_err(|_| SoundRenderError::BufferTooLarge)?;
124 writer
125 .write_all(&riff_size.to_le_bytes())
126 .map_err(|_| SoundRenderError::BufferTooLarge)?;
127 writer
128 .write_all(b"WAVE")
129 .map_err(|_| SoundRenderError::BufferTooLarge)?;
130 writer
131 .write_all(b"fmt ")
132 .map_err(|_| SoundRenderError::BufferTooLarge)?;
133 writer
134 .write_all(&16_u32.to_le_bytes())
135 .map_err(|_| SoundRenderError::BufferTooLarge)?;
136 writer
137 .write_all(&1_u16.to_le_bytes())
138 .map_err(|_| SoundRenderError::BufferTooLarge)?;
139 writer
140 .write_all(&(self.channels as u16).to_le_bytes())
141 .map_err(|_| SoundRenderError::BufferTooLarge)?;
142 writer
143 .write_all(&self.sample_rate.to_le_bytes())
144 .map_err(|_| SoundRenderError::BufferTooLarge)?;
145 writer
146 .write_all(&byte_rate.to_le_bytes())
147 .map_err(|_| SoundRenderError::BufferTooLarge)?;
148 writer
149 .write_all(&block_align.to_le_bytes())
150 .map_err(|_| SoundRenderError::BufferTooLarge)?;
151 writer
152 .write_all(&(bytes_per_sample * 8).to_le_bytes())
153 .map_err(|_| SoundRenderError::BufferTooLarge)?;
154 writer
155 .write_all(b"data")
156 .map_err(|_| SoundRenderError::BufferTooLarge)?;
157 writer
158 .write_all(&data_size.to_le_bytes())
159 .map_err(|_| SoundRenderError::BufferTooLarge)?;
160 for sample in samples {
161 let pcm = (sample.clamp(-1.0, 1.0) * f32::from(i16::MAX)).round() as i16;
162 writer
163 .write_all(&pcm.to_le_bytes())
164 .map_err(|_| SoundRenderError::BufferTooLarge)?;
165 }
166 Ok(writer)
167 }
168
169 fn render_tone_with_pan(&self, tone: &Tone, pan: f32) -> Vec<f32> {
170 let frames = tone_frames(self.sample_rate, tone);
171 let mut out = vec![0.0_f32; frames * usize::from(self.channels)];
172 let (left_gain, right_gain) = pan_gains(pan);
173 for frame in 0..frames {
174 let time = Duration::from_secs_f64(frame as f64 / f64::from(self.sample_rate));
175 let env = tone.envelope.sample_level(time, tone.duration) as f32;
176 let mut mono = 0.0_f32;
177 for partial in &tone.partials {
178 let angle = std::f64::consts::TAU * partial.frequency.0 * time.as_secs_f64()
179 + partial.phase.0;
180 mono += (angle.sin() * partial.amplitude.0) as f32;
181 }
182 let sample = mono * env;
183 match self.channels {
184 1 => out[frame] = sample,
185 2 => {
186 let base = frame * 2;
187 out[base] = sample * left_gain;
188 out[base + 1] = sample * right_gain;
189 }
190 _ => unreachable!(),
191 }
192 }
193 out
194 }
195}
196
197fn tone_frames(sample_rate: u32, tone: &Tone) -> usize {
198 (tone.duration.as_secs_f64() * f64::from(sample_rate)).ceil() as usize
199}
200
201fn start_frame(sample_rate: u32, start: Duration) -> usize {
202 (start.as_secs_f64() * f64::from(sample_rate)).round() as usize
203}
204
205fn pan_gains(pan: f32) -> (f32, f32) {
206 let normalized = ((pan.clamp(-1.0, 1.0) + 1.0) * 0.5) * std::f32::consts::FRAC_PI_2;
207 (normalized.cos(), normalized.sin())
208}