reson/
synth.rs

1use crate::fade::FadeBuffer;
2use crate::tuning::Tuning;
3use crate::voice::Voice;
4use crate::{MidiEvent, Note};
5use std::sync::Arc;
6
7/// A polyphonic synthesizer.
8pub struct Synth<V: Voice + Clone> {
9    /// The configuration options.
10    opts: SynthOpts,
11    /// Buffer used to hold the output from each voice before mixing.
12    buffer: Vec<f32>,
13    /// The prototype voice used to instantiate new voices.
14    voice: V,
15    /// The bank of voices.
16    voices: Vec<VoiceHandle<V>>,
17    /// Monotonic counter used to track the order in which voices were triggered and released.
18    counter: usize,
19    /// Small buffer used to gracefully fade out stolen voices
20    fade_out: FadeBuffer<256>,
21    /// The current pitch bend ratio, to be multiplied with the base frequency of each voice.
22    pitch_bend: f32,
23    /// The sample rate.
24    sample_rate: u32,
25}
26
27/// Configuration options for [Synth].
28#[derive(Clone)]
29pub struct SynthOpts {
30    /// The tuning system, which relates notes to their pitch in Hz.
31    pub tuning: Arc<Tuning>,
32    /// The maximum number of samples that will be requested in one call to `process`.
33    /// Used for allocating the internal buffer.
34    pub max_block_size: usize,
35    /// The maximum number of voices that can be simultaneously played.
36    pub max_voices: usize,
37    /// If `true`, the synthesizer acts as a monophonic synth, despite the value of `max_voices`.
38    pub mono: bool,
39    /// The portamento setting.
40    pub portamento: Portamento,
41    /// The maximum pitch bend of a MIDI pitch bend event in semitones.
42    pub max_pitch_bend: f32,
43}
44
45/// The portamento setting for a synthesizer.
46#[derive(Copy, Clone)]
47pub enum Portamento {
48    /// Portamento is disabled.
49    Off,
50    /// The voice will glide from one note to the next in a fixed duration,
51    /// denoted in seconds.
52    Fixed(f32),
53    /// The voice will glide from one note to the next at a fixed rate,
54    /// denoted in seconds per octave.
55    Variable(f32),
56}
57
58struct VoiceHandle<V: Voice> {
59    voice: V,
60    phase: VoicePhase,
61    pitch: f32,
62    counter: usize,
63}
64
65#[derive(Clone, Copy, PartialEq, Eq)]
66enum VoicePhase {
67    On(Note),
68    Released(Note),
69    Off,
70}
71
72impl<V: Voice + Clone> Synth<V> {
73    /// Creates a new polyphonic synth with a fixed number of voices.
74    ///
75    /// # Parameters
76    /// * `opts` - Configuration options for the polyphonic synth.
77    /// * `voice` - A prototypical voice from which the bank of voices will be cloned.
78    pub fn new(opts: SynthOpts, voice: V) -> Self {
79        let mut out = Self {
80            opts,
81            buffer: vec![],
82            voice,
83            voices: vec![],
84            counter: 0,
85            fade_out: FadeBuffer::new(),
86            pitch_bend: 1.0,
87            sample_rate: 0,
88        };
89        out.update_opts(|_| {});
90        out
91    }
92
93    /// Updates the settings for the synth.
94    ///
95    /// This might result in the allocation of memory, if for example,
96    /// the maximum number of voices is increased or the maximum block size is increased.
97    pub fn update_opts(&mut self, f: impl FnOnce(&mut SynthOpts)) {
98        f(&mut self.opts);
99        self.voices.resize_with(self.opts.max_voices, || {
100            VoiceHandle::new(self.voice.clone())
101        });
102        self.buffer.resize(self.opts.max_block_size * 2, 0.0);
103    }
104
105    /// Updates the bank of voices by cloning the provided prototype voice.
106    ///
107    /// This results in all notes being immediately reset and silenced.
108    pub fn update_voice(&mut self, voice: V) {
109        self.voice = voice;
110        for voice in &mut self.voices {
111            *voice = VoiceHandle::new(self.voice.clone());
112        }
113    }
114
115    /// Sets the sample rate.
116    pub fn set_sample_rate(&mut self, sample_rate: u32) {
117        self.sample_rate = sample_rate;
118        self.voice.set_sample_rate(sample_rate);
119        for voice in &mut self.voices {
120            voice.set_sample_rate(sample_rate);
121        }
122    }
123
124    /// Triggers a note.
125    ///
126    /// # Parameters
127    /// * `note` - The MIDI note being triggered, between 0 and 127.
128    /// * `velocity` - The velocity of the note, between 0 and 127.
129    pub fn trigger(&mut self, note: Note, velocity: u8) {
130        let voice = self
131            .voices
132            .iter_mut()
133            .min_by_key(|v| v.priority(note))
134            .unwrap();
135        let pitch = self.opts.tuning.pitch(note);
136        voice.trigger(note, velocity, pitch, self.counter);
137        self.counter += 1;
138
139        // FIXME: Process fade out for voice stealing
140    }
141
142    /// Releases a note.
143    pub fn release(&mut self, note: Note) {
144        let voice = self.voices.iter_mut().find(|v| v.note_on() == Some(note));
145        if let Some(voice) = voice {
146            voice.release(self.counter);
147            self.counter += 1;
148        }
149    }
150
151    /// Sets the global pitch bend as a raw 14-bit MIDI value.
152    pub fn set_pitch_bend_raw(&mut self, value: u16) {
153        let semitones = ((value as f32 - 8192.0) / 8192.0) * self.opts.max_pitch_bend;
154        self.set_pitch_bend(semitones);
155    }
156
157    /// Sets the global pitch bend in semitones.
158    pub fn set_pitch_bend(&mut self, semitones: f32) {
159        self.pitch_bend = 2f32.powf(semitones / 12.0);
160    }
161
162    /// Processes a MIDI message.
163    pub fn midi_event(&mut self, event: MidiEvent) {
164        match event {
165            MidiEvent::NoteOn { note, velocity, .. } => self.trigger(note, velocity),
166            MidiEvent::NoteOff { note, .. } => self.release(note),
167            MidiEvent::PitchBend { value, .. } => self.set_pitch_bend_raw(value),
168        }
169    }
170
171    /// Synthesizes a block of audio into `output`.
172    pub fn process(&mut self, output: [&mut [f32]; 2]) {
173        let [left, right] = output;
174
175        let len = left.len();
176        assert_eq!(right.len(), len);
177        assert!(len <= self.opts.max_block_size);
178
179        // Prepare temporary buffers for each voice's output.
180        let (left_temp, right_temp) = self.buffer[..2 * len].split_at_mut(len);
181
182        // Track whether any audio has been written to output.
183        let mut written = false;
184
185        // Process each active voice in turn.
186        for handle in &mut self.voices {
187            if !handle.active() {
188                continue;
189            }
190            if written {
191                handle.process(self.pitch_bend, [left_temp, right_temp]);
192                add_buffers(left, left_temp);
193                add_buffers(right, right_temp);
194            } else {
195                handle.process(self.pitch_bend, [left, right]);
196                written = true;
197            }
198        }
199
200        // If no voices are sounding, ensure the output buffer is filled with silence.
201        if !written {
202            left.fill(0.0);
203            right.fill(0.0);
204        }
205
206        // Apply the fade buffer
207        self.fade_out.process([left, right]);
208    }
209}
210
211impl<V: Voice> VoiceHandle<V> {
212    fn new(voice: V) -> Self {
213        Self {
214            voice,
215            phase: VoicePhase::Off,
216            pitch: 0.0,
217            counter: 0,
218        }
219    }
220
221    /// Returns `true` if the voice is not producing silence.
222    fn active(&self) -> bool {
223        self.phase != VoicePhase::Off
224    }
225
226    /// Gets the note that the voice is currently playing, if it is in the `On` phase.
227    fn note_on(&self) -> Option<Note> {
228        if let VoicePhase::On(note) = self.phase {
229            Some(note)
230        } else {
231            None
232        }
233    }
234
235    /// Gets the priority used for voice allocation, with the lowest priority being preferred.
236    fn priority(&self, note: Note) -> usize {
237        match self.phase {
238            // Note has been re-triggered
239            VoicePhase::On(n) if n == note => 0,
240            // Unused voice
241            VoicePhase::Off => 1,
242            // Released voice for the same note
243            VoicePhase::Released(n) if n == note => 2,
244            // Oldest released note
245            VoicePhase::Released(_) => 3 + self.counter,
246            // Oldest triggered note
247            VoicePhase::On(_) => usize::MAX / 2 + self.counter,
248        }
249    }
250
251    /// Sets the sample rate.
252    fn set_sample_rate(&mut self, sample_rate: u32) {
253        self.voice.set_sample_rate(sample_rate);
254    }
255
256    /// Triggers a note.
257    fn trigger(&mut self, note: Note, velocity: u8, pitch: f32, counter: usize) {
258        self.voice.trigger(note, velocity);
259        self.phase = VoicePhase::On(note);
260        self.pitch = pitch; // TODO: Portamento
261        self.counter = counter;
262    }
263
264    /// Releases the current note.
265    pub fn release(&mut self, counter: usize) {
266        let note = match self.phase {
267            VoicePhase::On(note) => note,
268            VoicePhase::Released(note) => note,
269            VoicePhase::Off => return,
270        };
271
272        self.voice.release();
273        self.phase = VoicePhase::Released(note);
274        self.counter = counter;
275    }
276
277    /// Processes the voice into the provided output buffer.
278    fn process(&mut self, pitch_bend: f32, output: [&mut [f32]; 2]) {
279        let active = self.voice.process(self.pitch * pitch_bend, output);
280        if !active {
281            self.phase = VoicePhase::Off;
282        }
283    }
284}
285
286fn add_buffers(dst: &mut [f32], src: &[f32]) {
287    assert_eq!(src.len(), dst.len());
288    for i in 0..src.len() {
289        dst[i] += src[i];
290    }
291}