audio_code/audio/
synth.rs1use std::f32::consts::TAU;
4
5use super::SoundData;
6
7const RATE: u32 = 44_100;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Wave {
12 Sine,
14 Square,
16 Triangle,
18 Saw,
20 Noise,
22}
23
24#[derive(Debug, Clone)]
48pub struct Synth {
49 wave: Wave,
50 from: f32,
51 to: f32,
52 length: f32,
53 attack: f32,
54 decay: f32,
55 release: f32,
56 volume: f32,
57 layers: Vec<Synth>,
58 next: Option<Box<Synth>>,
59}
60
61impl Synth {
62 pub fn new(wave: Wave, frequency: f32) -> Self {
64 Self {
65 wave,
66 from: frequency,
67 to: frequency,
68 length: 0.1,
69 attack: 0.002,
70 decay: 0.0,
71 release: 0.005,
72 volume: 0.5,
73 layers: Vec::new(),
74 next: None,
75 }
76 }
77
78 pub fn sine(frequency: f32) -> Self {
80 Self::new(Wave::Sine, frequency)
81 }
82
83 pub fn square(frequency: f32) -> Self {
85 Self::new(Wave::Square, frequency)
86 }
87
88 pub fn triangle(frequency: f32) -> Self {
90 Self::new(Wave::Triangle, frequency)
91 }
92
93 pub fn saw(frequency: f32) -> Self {
95 Self::new(Wave::Saw, frequency)
96 }
97
98 pub fn noise() -> Self {
100 Self::new(Wave::Noise, 0.0)
101 }
102
103 pub fn silence(seconds: f32) -> Self {
105 Self::sine(0.0).volume(0.0).length(seconds)
106 }
107
108 pub fn length(mut self, seconds: f32) -> Self {
110 self.length = seconds.max(0.0);
111 self
112 }
113
114 pub fn sweep_to(mut self, frequency: f32) -> Self {
116 self.to = frequency;
117 self
118 }
119
120 pub fn attack(mut self, seconds: f32) -> Self {
122 self.attack = seconds.max(0.0);
123 self
124 }
125
126 pub fn decay(mut self, rate: f32) -> Self {
130 self.decay = rate.max(0.0);
131 self
132 }
133
134 pub fn release(mut self, seconds: f32) -> Self {
136 self.release = seconds.max(0.0);
137 self
138 }
139
140 pub fn volume(mut self, volume: f32) -> Self {
142 self.volume = volume;
143 self
144 }
145
146 pub fn with(mut self, other: Synth) -> Self {
148 self.layers.push(other);
149 self
150 }
151
152 pub fn then(mut self, other: Synth) -> Self {
154 let next = match self.next.take() {
155 Some(next) => next.then(other),
156 None => other,
157 };
158 self.next = Some(Box::new(next));
159 self
160 }
161
162 fn render(&self) -> Vec<f32> {
163 let frames = (self.length * RATE as f32) as usize;
164 let mut out = Vec::with_capacity(frames);
165 let mut phase = 0.0f32;
166 for i in 0..frames {
167 let t = i as f32 / RATE as f32;
168 let progress = if frames > 1 { i as f32 / (frames - 1) as f32 } else { 0.0 };
169 let freq = self.from + (self.to - self.from) * progress;
170 phase = (phase + freq / RATE as f32).fract();
171 let raw = match self.wave {
172 Wave::Sine => (phase * TAU).sin(),
173 Wave::Square => {
174 if phase < 0.5 {
175 1.0
176 } else {
177 -1.0
178 }
179 }
180 Wave::Triangle => 1.0 - 4.0 * (phase - 0.5).abs(),
181 Wave::Saw => 2.0 * phase - 1.0,
182 Wave::Noise => fastrand::f32() * 2.0 - 1.0,
183 };
184 let mut env = 1.0;
185 if self.attack > 0.0 {
186 env *= (t / self.attack).min(1.0);
187 }
188 if self.decay > 0.0 {
189 env *= (-t * self.decay).exp();
190 }
191 let left = (frames - 1 - i) as f32 / RATE as f32;
193 if self.release > 0.0 && left < self.release {
194 env *= (left / self.release).max(0.0);
195 }
196 out.push(raw * env * self.volume);
197 }
198 for layer in &self.layers {
199 let other = layer.render();
200 if other.len() > out.len() {
201 out.resize(other.len(), 0.0);
202 }
203 for (o, s) in out.iter_mut().zip(other) {
204 *o += s;
205 }
206 }
207 if let Some(next) = &self.next {
208 out.extend(next.render());
209 }
210 out
211 }
212
213 pub fn build(&self) -> SoundData {
215 SoundData::from_samples(self.render(), 1, RATE)
216 }
217}
218
219impl From<Synth> for SoundData {
220 fn from(synth: Synth) -> Self {
221 synth.build()
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn lengths_add_up() {
231 let s = Synth::sine(440.0).length(0.1).then(Synth::noise().length(0.05)).then(Synth::saw(100.0).length(0.05));
232 assert!((s.build().duration() - 0.2).abs() < 0.001);
233 let layered = Synth::sine(440.0).length(0.1).with(Synth::sine(880.0).length(0.3));
234 assert!((layered.build().duration() - 0.3).abs() < 0.001);
235 }
236
237 #[test]
238 fn stays_in_range_and_fades() {
239 let data = Synth::square(300.0).volume(1.0).length(0.05).build();
240 assert!(data.samples.iter().all(|s| s.abs() <= 1.0));
241 assert!(data.samples.last().unwrap().abs() < 1e-3);
242 assert!(data.samples[0].abs() < 1e-3);
243 }
244}