use std::io::Write;
use std::time::Duration;
use sim_lib_sound_bridge::ScheduledTone;
use sim_lib_sound_core::Tone;
use crate::SoundRenderError;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct RendererOptions {
pub sample_rate: u32,
pub channels: u8,
}
impl RendererOptions {
pub fn new(sample_rate: u32, channels: u8) -> Result<Self, SoundRenderError> {
if sample_rate == 0 {
return Err(SoundRenderError::InvalidSampleRate);
}
if !(1..=2).contains(&channels) {
return Err(SoundRenderError::InvalidChannelCount);
}
Ok(Self {
sample_rate,
channels,
})
}
}
impl Default for RendererOptions {
fn default() -> Self {
Self {
sample_rate: 44_100,
channels: 2,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct PcmRenderer {
pub sample_rate: u32,
pub channels: u8,
}
impl PcmRenderer {
pub fn new(options: RendererOptions) -> Result<Self, SoundRenderError> {
let _ = RendererOptions::new(options.sample_rate, options.channels)?;
Ok(Self {
sample_rate: options.sample_rate,
channels: options.channels,
})
}
pub fn render_tone(&self, tone: &Tone) -> Vec<f32> {
self.render_tone_with_pan(tone, 0.0)
}
pub fn render_mix(&self, tones: &[ScheduledTone]) -> Vec<f32> {
let frames = tones
.iter()
.map(|scheduled| {
start_frame(self.sample_rate, scheduled.start)
+ tone_frames(self.sample_rate, &scheduled.tone)
})
.max()
.unwrap_or(0);
let mut mix = vec![0.0_f32; frames * usize::from(self.channels)];
for scheduled in tones {
let rendered = self.render_tone_with_pan(&scheduled.tone, scheduled.pan);
let offset =
start_frame(self.sample_rate, scheduled.start) * usize::from(self.channels);
for (index, sample) in rendered.iter().enumerate() {
if let Some(slot) = mix.get_mut(offset + index) {
*slot += *sample;
}
}
}
mix
}
pub fn write_wav<W: Write>(
&self,
samples: &[f32],
mut writer: W,
) -> Result<W, SoundRenderError> {
let sample_count =
u32::try_from(samples.len()).map_err(|_| SoundRenderError::BufferTooLarge)?;
let bytes_per_sample = 2_u16;
let block_align = self.channels as u16 * bytes_per_sample;
let byte_rate = self.sample_rate * u32::from(block_align);
let data_size = sample_count * u32::from(bytes_per_sample);
let riff_size = 36_u32 + data_size;
writer
.write_all(b"RIFF")
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(&riff_size.to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(b"WAVE")
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(b"fmt ")
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(&16_u32.to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(&1_u16.to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(&(self.channels as u16).to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(&self.sample_rate.to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(&byte_rate.to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(&block_align.to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(&(bytes_per_sample * 8).to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(b"data")
.map_err(|_| SoundRenderError::BufferTooLarge)?;
writer
.write_all(&data_size.to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
for sample in samples {
let pcm = (sample.clamp(-1.0, 1.0) * f32::from(i16::MAX)).round() as i16;
writer
.write_all(&pcm.to_le_bytes())
.map_err(|_| SoundRenderError::BufferTooLarge)?;
}
Ok(writer)
}
fn render_tone_with_pan(&self, tone: &Tone, pan: f32) -> Vec<f32> {
let frames = tone_frames(self.sample_rate, tone);
let mut out = vec![0.0_f32; frames * usize::from(self.channels)];
let (left_gain, right_gain) = pan_gains(pan);
for frame in 0..frames {
let time = Duration::from_secs_f64(frame as f64 / f64::from(self.sample_rate));
let env = tone.envelope.sample_level(time, tone.duration) as f32;
let mut mono = 0.0_f32;
for partial in &tone.partials {
let angle = std::f64::consts::TAU * partial.frequency.0 * time.as_secs_f64()
+ partial.phase.0;
mono += (angle.sin() * partial.amplitude.0) as f32;
}
let sample = mono * env;
match self.channels {
1 => out[frame] = sample,
2 => {
let base = frame * 2;
out[base] = sample * left_gain;
out[base + 1] = sample * right_gain;
}
_ => unreachable!(),
}
}
out
}
}
fn tone_frames(sample_rate: u32, tone: &Tone) -> usize {
(tone.duration.as_secs_f64() * f64::from(sample_rate)).ceil() as usize
}
fn start_frame(sample_rate: u32, start: Duration) -> usize {
(start.as_secs_f64() * f64::from(sample_rate)).round() as usize
}
fn pan_gains(pan: f32) -> (f32, f32) {
let normalized = ((pan.clamp(-1.0, 1.0) + 1.0) * 0.5) * std::f32::consts::FRAC_PI_2;
(normalized.cos(), normalized.sin())
}