use std::f64::consts::TAU;
use crate::genome::{RHYTHMS, SoundGenes};
pub const SAMPLE_RATE: u32 = 44_100;
const BASE_MIDI: i32 = 50;
const LADDER: [i32; 16] =
[0, 2, 4, 7, 9, 12, 14, 16, 19, 21, 24, 26, 28, 31, 33, 36];
const UNIT_MS: [f64; 4] = [95.0, 115.0, 135.0, 155.0];
const PATCHES: [(f64, f64, f64); 8] = [
(1.0, 0.0, 3.0),
(1.0, 1.5, 4.0),
(2.0, 2.0, 5.0),
(3.0, 1.2, 6.0),
(0.5, 1.0, 3.0),
(3.5, 2.5, 8.0),
(1.41, 3.0, 9.0),
(4.0, 0.8, 2.0),
];
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct Note {
pub midi: i32,
pub seconds: f64,
}
#[must_use]
pub fn melody(g: &SoundGenes) -> Vec<Note> {
let unit = UNIT_MS[usize::from(g.tempo)] / 1000.0;
let rhythm = RHYTHMS[usize::from(g.rhythm)];
std::iter::once(0)
.chain(g.notes.iter().map(|&n| LADDER[usize::from(n)]))
.zip(rhythm)
.map(|(step, units)| Note {
midi: BASE_MIDI + i32::from(g.root) + step,
seconds: f64::from(units) * unit,
})
.collect()
}
#[must_use]
pub fn synthesize(g: &SoundGenes) -> Vec<f32> {
let (ratio, index, decay) = PATCHES[usize::from(g.timbre)];
let sr = f64::from(SAMPLE_RATE);
let mut out = Vec::new();
for note in melody(g) {
let f = 440.0 * 2f64.powf(f64::from(note.midi - 69) / 12.0);
let n = (note.seconds * sr) as usize;
let release = (0.015 * sr) as usize;
for i in 0..n {
let t = i as f64 / sr;
let attack = (t / 0.005).min(1.0);
let tail = ((n - i) as f64 / release as f64).min(1.0);
let env = attack * tail * (-decay * t).exp();
let m = index * env * (TAU * f * ratio * t).sin();
out.push((0.6 * env * (TAU * f * t + m).sin()) as f32);
}
}
out.extend(std::iter::repeat_n(0.0, (0.2 * sr) as usize));
out
}
#[must_use]
pub fn to_wav(samples: &[f32]) -> Vec<u8> {
let data_len =
u32::try_from(samples.len() * 2).expect("wav data exceeds 4 GiB");
let mut w = Vec::with_capacity(44 + data_len as usize);
w.extend_from_slice(b"RIFF");
w.extend_from_slice(&(36 + data_len).to_le_bytes());
w.extend_from_slice(b"WAVEfmt ");
w.extend_from_slice(&16u32.to_le_bytes());
w.extend_from_slice(&1u16.to_le_bytes()); w.extend_from_slice(&1u16.to_le_bytes()); w.extend_from_slice(&SAMPLE_RATE.to_le_bytes());
w.extend_from_slice(&(SAMPLE_RATE * 2).to_le_bytes());
w.extend_from_slice(&2u16.to_le_bytes());
w.extend_from_slice(&16u16.to_le_bytes());
w.extend_from_slice(b"data");
w.extend_from_slice(&data_len.to_le_bytes());
for s in samples {
let v = (s.clamp(-1.0, 1.0) * f32::from(i16::MAX)) as i16;
w.extend_from_slice(&v.to_le_bytes());
}
w
}