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;
6use sim_lib_sound_timbre::{Timbre, TimbreRenderError};
7
8use crate::SoundRenderError;
9
10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
12pub struct RendererOptions {
13 pub sample_rate: u32,
15 pub channels: u8,
17}
18
19impl RendererOptions {
20 pub fn new(sample_rate: u32, channels: u8) -> Result<Self, SoundRenderError> {
23 if sample_rate == 0 {
24 return Err(SoundRenderError::InvalidSampleRate);
25 }
26 if !(1..=2).contains(&channels) {
27 return Err(SoundRenderError::InvalidChannelCount);
28 }
29 Ok(Self {
30 sample_rate,
31 channels,
32 })
33 }
34}
35
36impl Default for RendererOptions {
37 fn default() -> Self {
38 Self {
39 sample_rate: 44_100,
40 channels: 2,
41 }
42 }
43}
44
45#[derive(Copy, Clone, Debug, PartialEq, Eq)]
48pub struct PcmRenderer {
49 sample_rate: u32,
51 channels: u8,
53}
54
55impl PcmRenderer {
56 pub fn new(options: RendererOptions) -> Result<Self, SoundRenderError> {
58 let _ = RendererOptions::new(options.sample_rate, options.channels)?;
59 Ok(Self {
60 sample_rate: options.sample_rate,
61 channels: options.channels,
62 })
63 }
64
65 pub fn sample_rate(self) -> u32 {
67 self.sample_rate
68 }
69
70 pub fn channels(self) -> u8 {
72 self.channels
73 }
74
75 pub fn render_tone(&self, tone: &Tone) -> Vec<f32> {
90 self.render_tone_with_pan(tone, 0.0)
91 }
92
93 pub fn render_timbre_preview(
96 &self,
97 timbre: &Timbre,
98 frequency: sim_lib_sound_core::Frequency,
99 duration: Duration,
100 ) -> Result<Vec<f32>, SoundRenderError> {
101 let tone = timbre
102 .try_render(frequency, duration)
103 .map_err(sound_timbre_error)?;
104 Ok(self.render_tone(&tone))
105 }
106
107 pub fn render_mix(&self, tones: &[ScheduledTone]) -> Vec<f32> {
110 let frames = tones
111 .iter()
112 .map(|scheduled| {
113 start_frame(self.sample_rate, scheduled.start)
114 + tone_frames(self.sample_rate, &scheduled.tone)
115 })
116 .max()
117 .unwrap_or(0);
118 let mut mix = vec![0.0_f32; frames * usize::from(self.channels)];
119 for scheduled in tones {
120 let rendered = self.render_tone_with_pan(&scheduled.tone, scheduled.pan);
121 let offset =
122 start_frame(self.sample_rate, scheduled.start) * usize::from(self.channels);
123 for (index, sample) in rendered.iter().enumerate() {
124 if let Some(slot) = mix.get_mut(offset + index) {
125 *slot += *sample;
126 }
127 }
128 }
129 mix
130 }
131
132 pub fn write_wav<W: Write>(
135 &self,
136 samples: &[f32],
137 mut writer: W,
138 ) -> Result<W, SoundRenderError> {
139 let channels = usize::from(self.channels);
140 if channels == 0 || !samples.len().is_multiple_of(channels) {
141 return Err(SoundRenderError::ChannelMisalignedSamples);
142 }
143 let sample_count =
144 u32::try_from(samples.len()).map_err(|_| SoundRenderError::BufferTooLarge)?;
145 let bytes_per_sample = 2_u16;
146 let block_align = u16::from(self.channels)
147 .checked_mul(bytes_per_sample)
148 .ok_or(SoundRenderError::BufferTooLarge)?;
149 let byte_rate = self
150 .sample_rate
151 .checked_mul(u32::from(block_align))
152 .ok_or(SoundRenderError::BufferTooLarge)?;
153 let data_size = sample_count
154 .checked_mul(u32::from(bytes_per_sample))
155 .ok_or(SoundRenderError::BufferTooLarge)?;
156 let riff_size = 36_u32
157 .checked_add(data_size)
158 .ok_or(SoundRenderError::BufferTooLarge)?;
159 writer
160 .write_all(b"RIFF")
161 .map_err(|_| SoundRenderError::BufferTooLarge)?;
162 writer
163 .write_all(&riff_size.to_le_bytes())
164 .map_err(|_| SoundRenderError::BufferTooLarge)?;
165 writer
166 .write_all(b"WAVE")
167 .map_err(|_| SoundRenderError::BufferTooLarge)?;
168 writer
169 .write_all(b"fmt ")
170 .map_err(|_| SoundRenderError::BufferTooLarge)?;
171 writer
172 .write_all(&16_u32.to_le_bytes())
173 .map_err(|_| SoundRenderError::BufferTooLarge)?;
174 writer
175 .write_all(&1_u16.to_le_bytes())
176 .map_err(|_| SoundRenderError::BufferTooLarge)?;
177 writer
178 .write_all(&u16::from(self.channels).to_le_bytes())
179 .map_err(|_| SoundRenderError::BufferTooLarge)?;
180 writer
181 .write_all(&self.sample_rate.to_le_bytes())
182 .map_err(|_| SoundRenderError::BufferTooLarge)?;
183 writer
184 .write_all(&byte_rate.to_le_bytes())
185 .map_err(|_| SoundRenderError::BufferTooLarge)?;
186 writer
187 .write_all(&block_align.to_le_bytes())
188 .map_err(|_| SoundRenderError::BufferTooLarge)?;
189 writer
190 .write_all(&(bytes_per_sample * 8).to_le_bytes())
191 .map_err(|_| SoundRenderError::BufferTooLarge)?;
192 writer
193 .write_all(b"data")
194 .map_err(|_| SoundRenderError::BufferTooLarge)?;
195 writer
196 .write_all(&data_size.to_le_bytes())
197 .map_err(|_| SoundRenderError::BufferTooLarge)?;
198 for sample in samples {
199 let pcm = (sample.clamp(-1.0, 1.0) * f32::from(i16::MAX)).round() as i16;
200 writer
201 .write_all(&pcm.to_le_bytes())
202 .map_err(|_| SoundRenderError::BufferTooLarge)?;
203 }
204 Ok(writer)
205 }
206
207 fn render_tone_with_pan(&self, tone: &Tone, pan: f32) -> Vec<f32> {
208 let frames = tone_frames(self.sample_rate, tone);
209 let mut out = vec![0.0_f32; frames * usize::from(self.channels)];
210 let (left_gain, right_gain) = pan_gains(pan);
211 for frame in 0..frames {
212 let time = Duration::from_secs_f64(frame as f64 / f64::from(self.sample_rate));
213 let env = tone.envelope.sample_level(time, tone.duration) as f32;
214 let mut mono = 0.0_f32;
215 for partial in &tone.partials {
216 let angle = std::f64::consts::TAU * partial.frequency.0 * time.as_secs_f64()
217 + partial.phase.0;
218 mono += (angle.sin() * partial.amplitude.0) as f32;
219 }
220 let sample = mono * env;
221 match self.channels {
222 1 => out[frame] = sample,
223 2 => {
224 let base = frame * 2;
225 out[base] = sample * left_gain;
226 out[base + 1] = sample * right_gain;
227 }
228 _ => unreachable!(),
229 }
230 }
231 out
232 }
233}
234
235fn sound_timbre_error(error: TimbreRenderError) -> SoundRenderError {
236 match error {
237 TimbreRenderError::SamplePitchRejected => SoundRenderError::TimbrePreviewRejected,
238 TimbreRenderError::EmptySample => SoundRenderError::EmptyTimbrePreview,
239 }
240}
241
242fn tone_frames(sample_rate: u32, tone: &Tone) -> usize {
243 (tone.duration.as_secs_f64() * f64::from(sample_rate)).ceil() as usize
244}
245
246fn start_frame(sample_rate: u32, start: Duration) -> usize {
247 (start.as_secs_f64() * f64::from(sample_rate)).round() as usize
248}
249
250fn pan_gains(pan: f32) -> (f32, f32) {
251 let normalized = ((pan.clamp(-1.0, 1.0) + 1.0) * 0.5) * std::f32::consts::FRAC_PI_2;
252 (normalized.cos(), normalized.sin())
253}