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}