use sdl3::audio::{AudioCallback, AudioFormat, AudioSpec, AudioStream};
use std::f32::consts::PI;
#[derive(Debug, Clone)]
pub struct ChirpParams {
pub start_freq_hz: f32,
pub end_freq_hz: f32,
pub duration_ms: u32,
pub volume: f32,
pub attack_ratio: f32,
pub decay_ratio: f32,
pub harmonics: Vec<f32>,
pub vibrato_rate_hz: f32,
pub vibrato_depth: f32,
}
impl Default for ChirpParams {
fn default() -> Self {
Self {
start_freq_hz: 2000.0,
end_freq_hz: 4000.0,
duration_ms: 200,
volume: 0.3,
attack_ratio: 0.1,
decay_ratio: 0.3,
harmonics: vec![1.0, 0.3, 0.1],
vibrato_rate_hz: 0.0,
vibrato_depth: 0.0,
}
}
}
impl ChirpParams {
pub fn new() -> Self {
Self::default()
}
pub fn start_freq(mut self, hz: f32) -> Self {
self.start_freq_hz = hz;
self
}
pub fn end_freq(mut self, hz: f32) -> Self {
self.end_freq_hz = hz;
self
}
pub fn duration(mut self, ms: u32) -> Self {
self.duration_ms = ms;
self
}
pub fn volume(mut self, v: f32) -> Self {
self.volume = v.clamp(0.0, 1.0);
self
}
pub fn attack(mut self, ratio: f32) -> Self {
self.attack_ratio = ratio.clamp(0.0, 1.0);
self
}
pub fn decay(mut self, ratio: f32) -> Self {
self.decay_ratio = ratio.clamp(0.0, 1.0);
self
}
pub fn harmonics(mut self, harms: Vec<f32>) -> Self {
self.harmonics = harms;
self
}
pub fn vibrato(mut self, rate_hz: f32, depth: f32) -> Self {
self.vibrato_rate_hz = rate_hz;
self.vibrato_depth = depth;
self
}
}
pub fn sparrow() -> ChirpParams {
ChirpParams::new()
.start_freq(2500.0)
.end_freq(5000.0)
.duration(150)
.attack(0.05)
.decay(0.4)
.harmonics(vec![1.0, 0.4, 0.15, 0.05])
}
pub fn robin() -> ChirpParams {
ChirpParams::new()
.start_freq(2000.0)
.end_freq(3500.0)
.duration(300)
.attack(0.15)
.decay(0.25)
.harmonics(vec![1.0, 0.5, 0.2])
.vibrato(8.0, 0.05)
}
pub fn warbler() -> ChirpParams {
ChirpParams::new()
.start_freq(3000.0)
.end_freq(6000.0)
.duration(180)
.attack(0.08)
.decay(0.35)
.harmonics(vec![1.0, 0.6, 0.25, 0.1, 0.03])
.vibrato(12.0, 0.08)
}
pub fn finch() -> ChirpParams {
ChirpParams::new()
.start_freq(3500.0)
.end_freq(7000.0)
.duration(120)
.attack(0.03)
.decay(0.5)
.harmonics(vec![1.0, 0.3, 0.1])
}
struct BirdChirp {
params: ChirpParams,
sample_rate: f32,
phase: f32,
sample_index: usize,
total_samples: usize,
buffer: Vec<f32>,
}
impl BirdChirp {
fn new(params: ChirpParams, sample_rate: f32) -> Self {
let total_samples = (params.duration_ms as f32 * sample_rate / 1000.0) as usize;
Self {
params,
sample_rate,
phase: 0.0,
sample_index: 0,
total_samples,
buffer: Vec::new(),
}
}
fn get_frequency(&self) -> f32 {
let progress = self.sample_index as f32 / self.total_samples as f32;
let freq_range = self.params.end_freq_hz - self.params.start_freq_hz;
let base_freq = self.params.start_freq_hz + freq_range * progress;
if self.params.vibrato_rate_hz > 0.0 {
let vibrato_phase = 2.0 * PI * self.params.vibrato_rate_hz * self.sample_index as f32
/ self.sample_rate;
let vibrato = vibrato_phase.sin() * self.params.vibrato_depth;
base_freq * (1.0 + vibrato)
} else {
base_freq
}
}
fn get_envelope(&self) -> f32 {
let progress = self.sample_index as f32 / self.total_samples as f32;
let attack_samples = (self.params.attack_ratio * self.total_samples as f32) as usize;
let decay_start = 1.0 - self.params.decay_ratio;
if self.sample_index < attack_samples {
self.sample_index as f32 / attack_samples as f32
} else if progress > decay_start {
let decay_progress = (progress - decay_start) / self.params.decay_ratio;
1.0 - decay_progress
} else {
1.0
}
}
fn generate_sample(&mut self) -> f32 {
if self.sample_index >= self.total_samples {
return 0.0;
}
let freq = self.get_frequency();
let envelope = self.get_envelope();
let mut sample = 0.0;
for (i, &harmonic_amp) in self.params.harmonics.iter().enumerate() {
let harmonic_phase = self.phase * (i + 1) as f32;
sample += (2.0 * PI * harmonic_phase).sin() * harmonic_amp;
}
let normalizer = self.params.harmonics.iter().sum::<f32>();
sample = sample / normalizer * envelope * self.params.volume;
self.phase += freq / self.sample_rate;
self.phase %= 1.0;
self.sample_index += 1;
sample
}
}
impl AudioCallback<f32> for BirdChirp {
fn callback(&mut self, stream: &mut AudioStream, requested: i32) {
self.buffer.resize(requested as usize, 0.0);
for i in 0..requested as usize {
self.buffer[i] = self.generate_sample();
}
stream.put_data_f32(&self.buffer).unwrap();
}
}
pub struct Synthesizer {
sdl_context: sdl3::Sdl,
}
impl Synthesizer {
pub fn new() -> Result<Self, String> {
let sdl_context = sdl3::init().map_err(|e| e.to_string())?;
Ok(Self { sdl_context })
}
pub fn play_chirp(&self, params: ChirpParams) -> Result<(), Box<dyn std::error::Error>> {
let audio_subsystem = self.sdl_context.audio()?;
let sample_rate = 48000;
let desired_spec = AudioSpec {
freq: Some(sample_rate),
channels: Some(1),
format: Some(AudioFormat::f32_sys()),
};
let chirp = BirdChirp::new(params.clone(), sample_rate as f32);
let device = audio_subsystem.open_playback_stream(&desired_spec, chirp)?;
device.resume()?;
let duration = std::time::Duration::from_millis(params.duration_ms as u64);
std::thread::sleep(duration);
Ok(())
}
}