use std::f32::consts::TAU;
use super::SoundData;
const RATE: u32 = 44_100;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Wave {
Sine,
Square,
Triangle,
Saw,
Noise,
}
#[derive(Debug, Clone)]
pub struct Synth {
wave: Wave,
from: f32,
to: f32,
length: f32,
attack: f32,
decay: f32,
release: f32,
volume: f32,
layers: Vec<Synth>,
next: Option<Box<Synth>>,
}
impl Synth {
pub fn new(wave: Wave, frequency: f32) -> Self {
Self {
wave,
from: frequency,
to: frequency,
length: 0.1,
attack: 0.002,
decay: 0.0,
release: 0.005,
volume: 0.5,
layers: Vec::new(),
next: None,
}
}
pub fn sine(frequency: f32) -> Self {
Self::new(Wave::Sine, frequency)
}
pub fn square(frequency: f32) -> Self {
Self::new(Wave::Square, frequency)
}
pub fn triangle(frequency: f32) -> Self {
Self::new(Wave::Triangle, frequency)
}
pub fn saw(frequency: f32) -> Self {
Self::new(Wave::Saw, frequency)
}
pub fn noise() -> Self {
Self::new(Wave::Noise, 0.0)
}
pub fn silence(seconds: f32) -> Self {
Self::sine(0.0).volume(0.0).length(seconds)
}
pub fn length(mut self, seconds: f32) -> Self {
self.length = seconds.max(0.0);
self
}
pub fn sweep_to(mut self, frequency: f32) -> Self {
self.to = frequency;
self
}
pub fn attack(mut self, seconds: f32) -> Self {
self.attack = seconds.max(0.0);
self
}
pub fn decay(mut self, rate: f32) -> Self {
self.decay = rate.max(0.0);
self
}
pub fn release(mut self, seconds: f32) -> Self {
self.release = seconds.max(0.0);
self
}
pub fn volume(mut self, volume: f32) -> Self {
self.volume = volume;
self
}
pub fn with(mut self, other: Synth) -> Self {
self.layers.push(other);
self
}
pub fn then(mut self, other: Synth) -> Self {
let next = match self.next.take() {
Some(next) => next.then(other),
None => other,
};
self.next = Some(Box::new(next));
self
}
fn render(&self) -> Vec<f32> {
let frames = (self.length * RATE as f32) as usize;
let mut out = Vec::with_capacity(frames);
let mut phase = 0.0f32;
for i in 0..frames {
let t = i as f32 / RATE as f32;
let progress = if frames > 1 { i as f32 / (frames - 1) as f32 } else { 0.0 };
let freq = self.from + (self.to - self.from) * progress;
phase = (phase + freq / RATE as f32).fract();
let raw = match self.wave {
Wave::Sine => (phase * TAU).sin(),
Wave::Square => {
if phase < 0.5 {
1.0
} else {
-1.0
}
}
Wave::Triangle => 1.0 - 4.0 * (phase - 0.5).abs(),
Wave::Saw => 2.0 * phase - 1.0,
Wave::Noise => fastrand::f32() * 2.0 - 1.0,
};
let mut env = 1.0;
if self.attack > 0.0 {
env *= (t / self.attack).min(1.0);
}
if self.decay > 0.0 {
env *= (-t * self.decay).exp();
}
let left = (frames - 1 - i) as f32 / RATE as f32;
if self.release > 0.0 && left < self.release {
env *= (left / self.release).max(0.0);
}
out.push(raw * env * self.volume);
}
for layer in &self.layers {
let other = layer.render();
if other.len() > out.len() {
out.resize(other.len(), 0.0);
}
for (o, s) in out.iter_mut().zip(other) {
*o += s;
}
}
if let Some(next) = &self.next {
out.extend(next.render());
}
out
}
pub fn build(&self) -> SoundData {
SoundData::from_samples(self.render(), 1, RATE)
}
}
impl From<Synth> for SoundData {
fn from(synth: Synth) -> Self {
synth.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lengths_add_up() {
let s = Synth::sine(440.0).length(0.1).then(Synth::noise().length(0.05)).then(Synth::saw(100.0).length(0.05));
assert!((s.build().duration() - 0.2).abs() < 0.001);
let layered = Synth::sine(440.0).length(0.1).with(Synth::sine(880.0).length(0.3));
assert!((layered.build().duration() - 0.3).abs() < 0.001);
}
#[test]
fn stays_in_range_and_fades() {
let data = Synth::square(300.0).volume(1.0).length(0.05).build();
assert!(data.samples.iter().all(|s| s.abs() <= 1.0));
assert!(data.samples.last().unwrap().abs() < 1e-3);
assert!(data.samples[0].abs() < 1e-3);
}
}