sonant/
synth.rs

1use crate::consts::{MAX_OVERLAPPING_NOTES, NUM_CHANNELS, NUM_INSTRUMENTS, PATTERN_LENGTH};
2use crate::song::{Envelope, Filter, Instrument, Song, Waveform};
3use arrayvec::ArrayVec;
4use core::{f32::consts::PI, num::Wrapping as w};
5use randomize::{Gen32 as _, PCG32};
6
7/// The main struct for audio synthesis.
8///
9/// `Synth` implements `Iterator`, so calling the `next` method on it will generate the next
10/// sample.
11///
12/// Currently only generates 2-channel f32 samples at the given `sample_rate`.
13#[derive(Debug)]
14pub struct Synth<'a> {
15    song: &'a Song,
16    random: PCG32,
17    sample_rate: f32,
18    sample_ratio: f32,
19    quarter_note_length: u32,
20    eighth_note_length: u32,
21
22    // TODO: Support seamless loops
23
24    // Iterator state
25    seq_count: usize,
26    note_count: usize,
27    sample_count: u32,
28    tracks: [TrackState; NUM_INSTRUMENTS],
29}
30
31/// Iterator state for a single instrument track.
32#[derive(Debug)]
33struct TrackState {
34    env: Envelope,
35
36    // Max simultaneous notes per track
37    notes: [Note; MAX_OVERLAPPING_NOTES],
38
39    delay_samples: u32,
40    delay_count: u32,
41
42    // Static frequencies
43    pan_freq: f32,
44    lfo_freq: f32,
45}
46
47/// Data structure for quarter notes, which includes the pitch and sample
48/// counter reference for waveform modulation. It also contains state for sample
49/// synthesis and filtering.
50#[derive(Debug)]
51struct Note {
52    pitch: u8,
53    sample_count: u32,
54    volume: f32,
55    swap_stereo: bool,
56
57    // Iterator state
58    osc_freq: [f32; 2],
59    osc_time: [f32; 2],
60    low: f32,
61    band: f32,
62}
63
64/// Sine wave generator
65fn osc_sin(value: f32) -> f32 {
66    libm::sinf((value + 0.5) * PI * 2.0)
67}
68
69/// Square wave generator
70fn osc_square(value: f32) -> f32 {
71    if osc_sin(value) < 0.0 {
72        -1.0
73    } else {
74        1.0
75    }
76}
77
78/// Saw wave generator
79fn osc_saw(value: f32) -> f32 {
80    let fract = value - libm::truncf(value);
81    (1.0 - fract) - 0.5
82}
83
84/// Triangle wave generator
85fn osc_tri(value: f32) -> f32 {
86    let fract = value - libm::truncf(value);
87    let v2 = fract * 4.0;
88
89    if v2 < 2.0 {
90        v2 - 1.0
91    } else {
92        3.0 - v2
93    }
94}
95
96/// Get a `note` frequency on the exponential scale defined by reference
97/// frequency `ref_freq` and reference pitch `ref_pitch`, using the interval
98/// `semitone`.
99fn get_frequency(ref_freq: f32, semitone: f32, note: u8, ref_pitch: u8) -> f32 {
100    ref_freq * libm::powf(semitone, f32::from(note) - f32::from(ref_pitch))
101}
102
103/// Get the absolute frequency for a note value on the 12-TET scale.
104fn get_note_frequency(note: u8) -> f32 {
105    const SEMITONE: f32 = 1.059_463_1; // Twelfth root of 2
106    get_frequency(1.0 / 256.0, SEMITONE, note, 128)
107}
108
109/// Get a sample from the waveform generator at time `t`
110fn get_osc_output(waveform: &Waveform, t: f32) -> f32 {
111    match waveform {
112        Waveform::Sine => osc_sin(t),
113        Waveform::Square => osc_square(t),
114        Waveform::Saw => osc_saw(t),
115        Waveform::Triangle => osc_tri(t),
116    }
117}
118
119impl TrackState {
120    fn new() -> Self {
121        let mut notes = ArrayVec::new();
122        for _ in 0..MAX_OVERLAPPING_NOTES {
123            notes.push(Note::new(0, 0, 0.0, false));
124        }
125        let notes = notes.into_inner().unwrap();
126
127        Self {
128            env: Envelope {
129                attack: 0,
130                sustain: 0,
131                release: 0,
132                master: 0.0,
133            },
134            notes,
135            delay_samples: 0,
136            delay_count: 0,
137            pan_freq: 0.0,
138            lfo_freq: 0.0,
139        }
140    }
141}
142
143impl Note {
144    fn new(pitch: u8, sample_count: u32, volume: f32, swap_stereo: bool) -> Self {
145        Self {
146            pitch,
147            sample_count,
148            volume,
149            swap_stereo,
150            osc_freq: [0.0; 2],
151            osc_time: [0.0; 2],
152            low: 0.0,
153            band: 0.0,
154        }
155    }
156}
157
158impl<'a> Synth<'a> {
159    /// Create a `Synth` that will play the provided `Song`.
160    /// The optional seed will be used for the noise generator.
161    /// `Synth` implements `Iterator` and generates two stereo samples at a time.
162    ///
163    /// ```no_run
164    /// use byteorder::{ByteOrder, NativeEndian};
165    /// use getrandom::getrandom;
166    /// use sonant::{Song, Synth};
167    ///
168    /// let song = Song::from_slice(include_bytes!("../examples/poseidon.snt"))?;
169    ///
170    /// // Create a seed for the PRNG
171    /// let mut seed = [0_u8; 16];
172    /// getrandom(&mut seed).expect("failed to getrandom");
173    /// let seed = (
174    ///     NativeEndian::read_u64(&seed[0..8]),
175    ///     NativeEndian::read_u64(&seed[8..16]),
176    /// );
177    ///
178    /// let synth = Synth::new(&song, seed, 44100.0);
179    /// for [sample_l, sample_r] in synth {
180    ///     // Do something with the samples
181    /// }
182    /// # Ok::<(), sonant::Error>(())
183    /// ```
184    #[must_use]
185    pub fn new(song: &'a Song, seed: (u64, u64), sample_rate: f32) -> Self {
186        let random = PCG32::new(seed.0, seed.1);
187        let sample_ratio = sample_rate / 44100.0;
188        let quarter_note_length = (sample_ratio * song.quarter_note_length as f32) as u32;
189        let eighth_note_length = quarter_note_length / 2;
190
191        let mut synth = Synth {
192            song,
193            random,
194            sample_rate,
195            sample_ratio,
196            quarter_note_length,
197            eighth_note_length,
198            seq_count: 0,
199            sample_count: 0,
200            note_count: 0,
201            tracks: Self::load_tracks(
202                song,
203                sample_ratio,
204                quarter_note_length as f32,
205                eighth_note_length as f32,
206            ),
207        };
208        synth.load_notes();
209
210        synth
211    }
212
213    /// Load the static state for each track.
214    fn load_tracks(
215        song: &Song,
216        sample_ratio: f32,
217        quarter_note_length: f32,
218        eighth_note_length: f32,
219    ) -> [TrackState; NUM_INSTRUMENTS] {
220        let mut tracks = ArrayVec::<_, NUM_INSTRUMENTS>::new();
221        for _ in 0..NUM_INSTRUMENTS {
222            tracks.push(TrackState::new());
223        }
224        let mut tracks = tracks.into_inner().unwrap();
225
226        for (i, inst) in song.instruments.iter().enumerate() {
227            // Configure attack, sustain, and release
228            tracks[i].env.attack = (inst.env.attack as f32 * sample_ratio) as u32;
229            tracks[i].env.sustain = (inst.env.sustain as f32 * sample_ratio) as u32;
230            tracks[i].env.release = (inst.env.release as f32 * sample_ratio) as u32;
231
232            // Configure delay
233            tracks[i].delay_samples = (f32::from(inst.fx.delay_time) * eighth_note_length) as u32;
234            tracks[i].delay_count = if inst.fx.delay_amount == 0.0 {
235                // Special case for zero repeats
236                0
237            } else if libm::fabsf(inst.fx.delay_amount - 1.0) < f32::EPSILON {
238                // Special case for infinite repeats
239                u32::MAX
240            } else if tracks[i].delay_samples == 0 {
241                // Special case for zero-delay time: only repeat once
242                1
243            } else {
244                // This gets the number of iterations required for the note
245                // volume to drop below the audible threshold.
246                let base = libm::logf(1.0 / inst.fx.delay_amount);
247                (libm::logf(256.0) / base) as u32
248            };
249
250            // Set LFO and panning frequencies
251            tracks[i].lfo_freq = get_frequency(1.0, 2.0, inst.lfo.freq, 8) / quarter_note_length;
252            tracks[i].pan_freq = get_frequency(1.0, 2.0, inst.fx.pan_freq, 8) / quarter_note_length;
253        }
254
255        tracks
256    }
257
258    /// Load the next set of notes into the iterator state.
259    fn load_notes(&mut self) {
260        let seq_count = self.seq_count;
261        if seq_count > self.song.seq_length {
262            return;
263        }
264
265        for i in 0..self.song.instruments.len() {
266            // Add the note
267            let note_count = self.note_count;
268            self.add_note(i, seq_count, note_count, 1.0, false);
269        }
270    }
271
272    /// Load delayed notes into the iterator state.
273    fn load_delayed_notes(&mut self) {
274        for (i, inst) in self.song.instruments.iter().enumerate() {
275            for round in 1..=self.tracks[i].delay_count {
276                // Compute the delay position
277                let delay = self.tracks[i].delay_samples * round;
278                if delay > self.sample_count {
279                    continue;
280                }
281
282                // Seek to the delayed note, and ensure it's aligned to the quarter note
283                let position = self.sample_count - delay;
284                if position % self.quarter_note_length != 0 {
285                    continue;
286                }
287
288                // Convert position into seq_count and note_count
289                let pattern_length = self.quarter_note_length * PATTERN_LENGTH as u32;
290                let seq_count = (position / pattern_length) as usize;
291                if seq_count > self.song.seq_length {
292                    continue;
293                }
294                let note_count = ((position % pattern_length) / self.quarter_note_length) as usize;
295
296                // Add the note
297                let volume = libm::powf(inst.fx.delay_amount, round as f32);
298                self.add_note(i, seq_count, note_count, volume, round % 2 == 1);
299            }
300        }
301    }
302
303    /// Get the index of the first empty note in the given `notes` slice.
304    fn get_note_slot(notes: &[Note]) -> usize {
305        // Find the first empty note
306        if let Some((i, _)) = notes.iter().enumerate().find(|(_, x)| x.pitch == 0) {
307            i
308        } else {
309            let iter = notes.iter().enumerate();
310            iter.min_by_key(|(_, x)| x.sample_count).unwrap().0
311        }
312    }
313
314    /// Add a note to track `i`.
315    fn add_note(
316        &mut self,
317        i: usize,
318        seq_count: usize,
319        note_count: usize,
320        volume: f32,
321        swap_stereo: bool,
322    ) {
323        let inst = &self.song.instruments[i];
324
325        // Get the pattern index
326        let p = inst.seq[seq_count];
327        if p == 0 {
328            return;
329        }
330
331        // Get the pattern
332        let pattern = &inst.pat[p - 1];
333
334        // Get the note pitch
335        let pitch = pattern.notes[note_count];
336        if pitch == 0 {
337            return;
338        }
339
340        // Create a new note
341        let j = Self::get_note_slot(&self.tracks[i].notes);
342        self.tracks[i].notes[j] = Note::new(pitch, self.sample_count, volume, swap_stereo);
343
344        // Set oscillator frequencies
345        let pitch = w(self.tracks[i].notes[j].pitch);
346        for o in 0..2 {
347            let pitch = (pitch + w(inst.osc[o].octave) + w(inst.osc[o].detune_freq)).0;
348            self.tracks[i].notes[j].osc_freq[o] =
349                get_note_frequency(pitch) * inst.osc[o].detune / self.sample_ratio;
350        }
351    }
352
353    /// Envelope
354    fn env(position: u32, inst_env: &Envelope) -> Option<(f32, f32)> {
355        let attack = inst_env.attack;
356        let sustain = inst_env.sustain;
357        let release = inst_env.release;
358
359        let mut env = 1.0;
360
361        if position < attack {
362            env = position as f32 / attack as f32;
363        } else if position >= attack + sustain + release {
364            return None;
365        } else if position >= attack + sustain {
366            let pos = (position - attack - sustain) as f32;
367            env -= pos / release as f32;
368        }
369
370        Some((env, env * env))
371    }
372
373    /// Oscillator 0
374    fn osc0(&mut self, inst: &Instrument, i: usize, j: usize, lfo: f32, env_sq: f32) -> f32 {
375        let r = get_osc_output(&inst.osc[0].waveform, self.tracks[i].notes[j].osc_time[0]);
376        let mut t = self.tracks[i].notes[j].osc_freq[0];
377
378        if inst.lfo.osc0_freq {
379            t += lfo;
380        }
381        if inst.osc[0].envelope {
382            t *= env_sq;
383        }
384        self.tracks[i].notes[j].osc_time[0] += t;
385
386        r * inst.osc[0].volume
387    }
388
389    /// Oscillator 1
390    fn osc1(&mut self, inst: &Instrument, i: usize, j: usize, env_sq: f32) -> f32 {
391        let r = get_osc_output(&inst.osc[1].waveform, self.tracks[i].notes[j].osc_time[1]);
392        let mut t = self.tracks[i].notes[j].osc_freq[1];
393
394        if inst.osc[1].envelope {
395            t *= env_sq;
396        }
397        self.tracks[i].notes[j].osc_time[1] += t;
398
399        r * inst.osc[1].volume
400    }
401
402    /// Filters
403    fn filters(&mut self, inst: &Instrument, i: usize, j: usize, lfo: f32, sample: f32) -> f32 {
404        let mut f = inst.fx.freq * self.sample_ratio;
405
406        if inst.lfo.fx_freq {
407            f *= lfo;
408        }
409        f = libm::sinf(f * PI / self.sample_rate) * 1.5;
410
411        let low = libm::fmaf(f, self.tracks[i].notes[j].band, self.tracks[i].notes[j].low);
412        let high = inst.fx.resonance * (sample - self.tracks[i].notes[j].band) - low;
413        let band = libm::fmaf(f, high, self.tracks[i].notes[j].band);
414
415        self.tracks[i].notes[j].low = low;
416        self.tracks[i].notes[j].band = band;
417
418        let sample = match inst.fx.filter {
419            Filter::None => sample,
420            Filter::HighPass => high,
421            Filter::LowPass => low,
422            Filter::BandPass => band,
423            Filter::Notch => low + high,
424        };
425
426        sample * inst.env.master
427    }
428
429    /// Generate samples for 2 channels using the given instrument.
430    fn generate_samples(
431        &mut self,
432        inst: &Instrument,
433        i: usize,
434        j: usize,
435        position: f32,
436    ) -> Option<[f32; NUM_CHANNELS]> {
437        // Envelope
438        let note_sample_count = self.tracks[i].notes[j].sample_count;
439        let (env, env_sq) = Self::env(self.sample_count - note_sample_count, &self.tracks[i].env)?;
440
441        // LFO
442        let lfo_freq = self.tracks[i].lfo_freq;
443        let lfo = libm::fmaf(
444            get_osc_output(&inst.lfo.waveform, lfo_freq * position),
445            inst.lfo.amount * self.sample_ratio,
446            0.5,
447        );
448
449        // Oscillator 0
450        let mut sample = self.osc0(inst, i, j, lfo, env_sq);
451
452        // Oscillator 1
453        sample += self.osc1(inst, i, j, env_sq);
454
455        // Noise oscillator
456        sample += osc_sin(self.random.next_f32_unit()) * inst.noise_fader * env;
457
458        // Envelope
459        sample *= env * self.tracks[i].notes[j].volume;
460
461        // Filters
462        sample += self.filters(inst, i, j, lfo, sample);
463
464        let pan_freq = self.tracks[i].pan_freq;
465        let pan_t = libm::fmaf(
466            osc_sin(pan_freq * position),
467            inst.fx.pan_amount * self.sample_ratio,
468            0.5,
469        );
470
471        if self.tracks[i].notes[j].swap_stereo {
472            Some([sample * (1.0 - pan_t), sample * pan_t])
473        } else {
474            Some([sample * pan_t, sample * (1.0 - pan_t)])
475        }
476    }
477
478    /// Update the sample generator. This is the main workhorse of the
479    /// synthesizer.
480    fn update(&mut self) -> [f32; NUM_CHANNELS] {
481        let amplitude = f32::from(i16::MAX);
482        let position = self.sample_count as f32;
483
484        // Output samples
485        let mut samples = [0.0; NUM_CHANNELS];
486
487        for (i, inst) in self.song.instruments.iter().enumerate() {
488            for j in 0..self.tracks[i].notes.len() {
489                if self.tracks[i].notes[j].pitch == 0 {
490                    continue;
491                }
492
493                if let Some(note_samples) = self.generate_samples(inst, i, j, position) {
494                    // Mix the samples
495                    for (i, sample) in samples.iter_mut().enumerate() {
496                        *sample += note_samples[i];
497                    }
498                } else {
499                    // Remove notes that have ended
500                    self.tracks[i].notes[j] = Note::new(0, 0, 0.0, false);
501                }
502            }
503        }
504
505        // Clip samples to [-1.0, 1.0]
506        for sample in &mut samples {
507            *sample = (*sample / amplitude).clamp(-1.0, 1.0);
508        }
509
510        samples
511    }
512}
513
514impl<'a> Iterator for Synth<'a> {
515    type Item = [f32; NUM_CHANNELS];
516
517    fn next(&mut self) -> Option<Self::Item> {
518        // Check for end of song
519        if self.seq_count > self.song.seq_length
520            && !self
521                .tracks
522                .iter()
523                .flat_map(|x| x.notes.iter())
524                .any(|x| x.pitch != 0)
525        {
526            return None;
527        }
528
529        // Generate the next sample
530        let samples = self.update();
531
532        // Advance to next sample
533        self.sample_count += 1;
534        let sample_in_quarter_note = self.sample_count % self.quarter_note_length;
535        if sample_in_quarter_note == 0 {
536            // Advance to next note
537            self.note_count += 1;
538            if self.note_count >= PATTERN_LENGTH {
539                self.note_count = 0;
540
541                // Advance to next pattern
542                self.seq_count += 1;
543            }
544
545            // Fetch the next set of notes
546            self.load_delayed_notes();
547            self.load_notes();
548        } else if sample_in_quarter_note == self.eighth_note_length {
549            // Fetch the next set of notes
550            self.load_delayed_notes();
551        }
552
553        Some(samples)
554    }
555}