conformal_component/
synth.rs

1//! Abstractions for processors that generate audio.
2
3use std::ops::RangeInclusive;
4
5use crate::{
6    Processor,
7    audio::BufferMut,
8    events::{self, Event, Events},
9    parameters::{
10        self, NumericBufferState, PiecewiseLinearCurvePoint, SwitchBufferState, TimedValue,
11    },
12};
13
14/// Numeric expression controllers that affect all playing notes of the synth.
15#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
16pub enum NumericGlobalExpression {
17    /// The global pitch bend.
18    ///
19    /// This ranges from -1.0 to 1.0, and represents the current state of the
20    /// pitch bend controller. How to interpret this value in semitones
21    /// precisely is up to each synth.
22    ///
23    /// Note that there is also a per-note pitch bend expression parameter,
24    /// this should be combined with the global pitch bend to get the total
25    /// amount of bend for each note.
26    PitchBend,
27
28    /// The mod wheel.
29    ///
30    /// This ranges from 0.0 to 1.0, and represents the current state of the
31    /// mod wheel.
32    ModWheel,
33
34    /// The expression pedal.
35    ///
36    /// This ranges from 0.0 to 1.0, and represents the current state of the
37    /// expression pedal.
38    ExpressionPedal,
39
40    /// Aftertouch, or "pressure" in some DAW UIs.
41    ///
42    /// This ranges from 0.0 to 1.0, and represents the current state of the
43    /// global aftertouch.
44    ///
45    /// Note that there is also a per-note aftertouch expression parameter,
46    /// this should be combined with the global aftertouch to get the total
47    /// amount of aftertouch for each note.
48    Aftertouch,
49
50    /// Timbre, or "slide" in some DAW UIs.
51    ///
52    /// This ranges from 0.0 to 1.0, and represents the current state of the
53    /// global timbre control.
54    ///
55    /// Note that there is also a per-note timbre expression parameter,
56    /// this should be combined with the global timbre to get the total
57    /// amount of timbre for each note.
58    Timbre,
59}
60
61/// Numeric expression controllers that affect a single note of the synth.
62#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
63pub enum NumericPerNoteExpression {
64    /// Pitch bend note expression.
65    ///
66    /// This corresponds to the [`NumericGlobalExpression::PitchBend`] controller and should
67    /// change the tuning of the note.
68    ///
69    /// This is expressed in semitones away from the root note of the note (which may itself
70    /// be tuned).
71    PitchBend,
72
73    /// Vertical movement note expression, meant to control some sort of timbre of the synth.
74    ///
75    /// This is called "slide" in some DAW UIs.
76    ///
77    /// This corresponds to the [`NumericGlobalExpression::Timbre`] controller, and
78    /// its effects must be combined with the global controller.
79    ///
80    /// This value varies from 0->1, 0 being the bottommost position,
81    /// and 1 being the topmost position.
82    Timbre,
83
84    /// Depthwise note expression.
85    ///
86    /// This is called "Pressure" in some DAW UIs.
87    ///
88    /// This value varies from 0->1, 0 being neutral, and 1 being the maximum depth.
89    ///
90    /// This corresponds to the [`NumericGlobalExpression::Aftertouch`] controller which
91    /// affects all notes. The total effect must be a combination of this per-note note
92    /// expression and the global controller.
93    Aftertouch,
94}
95
96/// Get the valid range for a numeric per-note expression.
97#[must_use]
98pub fn valid_range_for_per_note_expression(
99    expression: NumericPerNoteExpression,
100) -> RangeInclusive<f32> {
101    match expression {
102        NumericPerNoteExpression::PitchBend => -128.0..=128.0,
103        NumericPerNoteExpression::Timbre | NumericPerNoteExpression::Aftertouch => 0.0..=1.0,
104    }
105}
106
107/// Switch expression controllers available on each synth.
108#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
109pub enum SwitchGlobalExpression {
110    /// The sustain pedal.
111    ///
112    /// This represents the current state of the sustain pedal controller.
113    SustainPedal,
114}
115
116/// Extention to the [`parameters::States`] trait for synths.
117pub trait SynthParamStates: parameters::States {
118    /// Get the current value of a numeric global expression controller.
119    fn get_numeric_global_expression(&self, expression: NumericGlobalExpression) -> f32;
120
121    /// Get the current value of a switch global expression controller.
122    fn get_switch_global_expression(&self, expression: SwitchGlobalExpression) -> bool;
123
124    /// Get the current value of a numeric per-note expression controller.
125    fn get_numeric_expression_for_note(
126        &self,
127        expression: NumericPerNoteExpression,
128        note_id: events::NoteID,
129    ) -> f32;
130}
131
132/// A trait for metadata during an audio processing call
133pub trait HandleEventsContext {
134    /// The events to handle
135    fn events(&self) -> impl Iterator<Item = events::Data> + Clone;
136
137    /// Parameter state
138    fn parameters(&self) -> &impl SynthParamStates;
139}
140
141/// Extension to the [`parameters::BufferStates`] trait for synths.
142pub trait SynthParamBufferStates: parameters::BufferStates {
143    /// Get the current value of a numeric global expression controller.
144    fn get_numeric_global_expression(
145        &self,
146        expression: NumericGlobalExpression,
147    ) -> NumericBufferState<impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone>;
148
149    /// Get the current value of a switch global expression controller.
150    fn get_switch_global_expression(
151        &self,
152        expression: SwitchGlobalExpression,
153    ) -> SwitchBufferState<impl Iterator<Item = TimedValue<bool>> + Clone>;
154
155    /// Get the current value of a numeric per-note expression controller.
156    fn get_numeric_expression_for_note(
157        &self,
158        expression: NumericPerNoteExpression,
159        note_id: events::NoteID,
160    ) -> NumericBufferState<impl Iterator<Item = PiecewiseLinearCurvePoint> + Clone>;
161}
162
163/// A trait for metadata during an audio processing call
164pub trait ProcessContext {
165    /// The events for this processing call
166    fn events(&self) -> Events<impl Iterator<Item = Event> + Clone>;
167
168    /// Parameter states for this call
169    ///
170    /// In order to consume the parameters, you can use the [`crate::pzip`] macro
171    /// to convert the parameters into an iterator of tuples that represent
172    /// the state of the parameters at each sample.
173    fn parameters(&self) -> &impl SynthParamBufferStates;
174}
175
176/// A trait for synthesizers
177///
178/// A synthesizer is a processor that creates audio from a series of _events_,
179/// such as Note On, or Note Off.
180pub trait Synth: Processor {
181    /// Handle parameter changes and events without processing any data.
182    /// Must not allocate or block.
183    fn handle_events(&mut self, context: &impl HandleEventsContext);
184
185    /// Process a buffer of events into a buffer of audio. Must not allocate or block.
186    ///
187    /// Note that `events` will be sorted by `sample_offset`
188    ///
189    /// `output` will be received in an undetermined state and must
190    /// be filled with audio by the processor during this call.
191    ///
192    /// The sample rate of the audio was provided in `environment.sampling_rate`
193    /// in the call to `crate::Component::create_processor`.
194    ///
195    /// Note that it's guaranteed that `output` will be no longer than
196    /// `environment.max_samples_per_process_call` provided in the call to
197    /// `crate::Component::create_processor`.
198    fn process(&mut self, context: &impl ProcessContext, output: &mut impl BufferMut);
199}