Skip to main content

nice_plug_core/context/
process.rs

1//! A context passed during the process function.
2
3use crate::{midi::PluginNoteEvent, plugin::Plugin};
4
5use super::PluginApi;
6
7/// Contains both context data and callbacks the plugin can use during processing. Most notably this
8/// is how a plugin sends and receives note events, gets transport information, and accesses
9/// sidechain inputs and auxiliary outputs. This is passed to the plugin during as part of
10/// [`Plugin::process()`][crate::plugin::Plugin::process()].
11//
12// # Safety
13//
14// The implementing wrapper needs to be able to handle concurrent requests, and it should perform
15// the actual callback within [MainThreadQueue::schedule_gui].
16pub trait ProcessContext<P: Plugin> {
17    /// Get the current plugin API.
18    fn plugin_api(&self) -> PluginApi;
19
20    /// Execute a task on a background thread using `[Plugin::task_executor]`. This allows you to
21    /// defer expensive tasks for later without blocking either the process function or the GUI
22    /// thread. As long as creating the `task` is realtime-safe, this operation is too.
23    ///
24    /// # Note
25    ///
26    /// Scheduling the same task multiple times will cause those duplicate tasks to pile up. Try to
27    /// either prevent this from happening, or check whether the task still needs to be completed in
28    /// your task executor.
29    fn execute_background(&self, task: P::BackgroundTask);
30
31    /// Execute a task on a background thread using `[Plugin::task_executor]`. As long as creating
32    /// the `task` is realtime-safe, this operation is too.
33    ///
34    /// # Note
35    ///
36    /// Scheduling the same task multiple times will cause those duplicate tasks to pile up. Try to
37    /// either prevent this from happening, or check whether the task still needs to be completed in
38    /// your task executor.
39    fn execute_gui(&self, task: P::BackgroundTask);
40
41    /// Get information about the current transport position and status.
42    fn transport(&self) -> &Transport;
43
44    /// Returns the next note event, if there is one. Use
45    /// [`NoteEvent::timing()`][crate::midi::NoteEvent::timing()] to get the event's timing
46    /// within the buffer. Only available when [`Plugin::MIDI_INPUT`] is set.
47    ///
48    /// # Usage
49    ///
50    /// You will likely want to use this with a loop, since there may be zero, one, or more events
51    /// for a sample:
52    ///
53    /// ```ignore
54    /// let mut next_event = context.next_event();
55    /// for (sample_id, channel_samples) in buffer.iter_samples().enumerate() {
56    ///     while let Some(event) = next_event {
57    ///         if event.timing() != sample_id as u32 {
58    ///             break;
59    ///         }
60    ///
61    ///         match event {
62    ///             NoteEvent::NoteOn { note, velocity, .. } => { ... },
63    ///             NoteEvent::NoteOff { note, .. } if note == 69 => { ... },
64    ///             NoteEvent::PolyPressure { note, pressure, .. } { ... },
65    ///             _ => (),
66    ///         }
67    ///
68    ///         next_event = context.next_event();
69    ///     }
70    ///
71    ///     // Do something with `channel_samples`...
72    /// }
73    ///
74    /// ProcessStatus::Normal
75    /// ```
76    fn next_event(&mut self) -> Option<PluginNoteEvent<P>>;
77
78    /// Send an event to the host. Only available when
79    /// [`Plugin::MIDI_OUTPUT`][crate::plugin::Plugin::MIDI_INPUT] is set. Will not do anything
80    /// otherwise.
81    fn send_event(&mut self, event: PluginNoteEvent<P>);
82
83    /// Update the current latency of the plugin. If the plugin is currently processing audio, then
84    /// this may cause audio playback to be restarted.
85    fn set_latency_samples(&self, samples: u32);
86
87    /// Set the current voice **capacity** for this plugin (so not the number of currently active
88    /// voices). This may only be called if `ClapPlugin::CLAP_POLY_MODULATION_CONFIG` is set.
89    /// `capacity` must be between 1 and the configured maximum capacity. Changing this at runtime
90    /// allows the host to better optimize polyphonic modulation, or to switch to strictly monophonic
91    /// modulation when dropping the capacity down to 1.
92    fn set_current_voice_capacity(&self, capacity: u32);
93
94    // TODO: Add this, this works similar to [GuiContext::set_parameter] but it adds the parameter
95    //       change to a queue (or directly to the VST3 plugin's parameter output queues) instead of
96    //       using main thread host automation (and all the locks involved there).
97    // fn set_parameter<P: Param>(&self, param: &P, value: P::Plain);
98}
99
100/// Information about the plugin's transport. Depending on the plugin API and the host not all
101/// fields may be available.
102#[derive(Debug)]
103pub struct Transport {
104    /// Whether the transport is currently running.
105    pub playing: bool,
106    /// Whether recording is enabled in the project.
107    pub recording: bool,
108    /// Whether the pre-roll is currently active, if the plugin API reports this information.
109    pub preroll_active: Option<bool>,
110
111    /// The sample rate in Hertz. Also passed in
112    /// [`Plugin::initialize()`][crate::plugin::Plugin::initialize()], so if you need this then you
113    /// can also store that value.
114    pub sample_rate: f32,
115    /// The project's tempo in beats per minute.
116    pub tempo: Option<f64>,
117    /// The time signature's numerator.
118    pub time_sig_numerator: Option<i32>,
119    /// The time signature's denominator.
120    pub time_sig_denominator: Option<i32>,
121
122    // XXX: VST3 also has a continuous time in samples that ignores loops, but we can't reconstruct
123    //      something similar in CLAP so it may be best to just ignore that so you can't rely on it
124    /// The position in the song in samples. Can be used to calculate the time in seconds if needed.
125    pub pos_samples: Option<i64>,
126    /// The position in the song in seconds. Can be used to calculate the time in samples if needed.
127    pub pos_seconds: Option<f64>,
128    /// The position in the song in quarter notes. Can be calculated from the time in seconds and
129    /// the tempo if needed.
130    pub pos_beats: Option<f64>,
131    /// The last bar's start position in beats. Can be calculated from the beat position and time
132    /// signature if needed.
133    pub bar_start_pos_beats: Option<f64>,
134    /// The number of the bar at `bar_start_pos_beats`. This starts at 0 for the very first bar at
135    /// the start of the song. Can be calculated from the beat position and time signature if
136    /// needed.
137    pub bar_number: Option<i32>,
138
139    /// The loop range in samples, if the loop is active and this information is available. None of
140    /// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
141    /// end is exclusive. Can be calculated from the other loop range information if needed.
142    pub loop_range_samples: Option<(i64, i64)>,
143    /// The loop range in seconds, if the loop is active and this information is available. None of
144    /// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
145    /// end is exclusive. Can be calculated from the other loop range information if needed.
146    pub loop_range_seconds: Option<(f64, f64)>,
147    /// The loop range in quarter notes, if the loop is active and this information is available.
148    /// None of the plugin API docs mention whether this is exclusive or inclusive, but just assume
149    /// that the end is exclusive. Can be calculated from the other loop range information if
150    /// needed.
151    pub loop_range_beats: Option<(f64, f64)>,
152}
153
154impl Transport {
155    /// Initialize the transport struct without any information.
156    pub fn new(sample_rate: f32) -> Self {
157        Self {
158            playing: false,
159            recording: false,
160            preroll_active: None,
161
162            sample_rate,
163            tempo: None,
164            time_sig_numerator: None,
165            time_sig_denominator: None,
166
167            pos_samples: None,
168            pos_seconds: None,
169            pos_beats: None,
170            bar_start_pos_beats: None,
171            bar_number: None,
172
173            loop_range_samples: None,
174            loop_range_seconds: None,
175            loop_range_beats: None,
176        }
177    }
178
179    /// The position in the song in samples. Will be calculated from other information if needed.
180    pub fn pos_samples(&self) -> Option<i64> {
181        match (
182            self.pos_samples,
183            self.pos_seconds,
184            self.pos_beats,
185            self.tempo,
186        ) {
187            (Some(pos_samples), _, _, _) => Some(pos_samples),
188            (_, Some(pos_seconds), _, _) => {
189                Some((pos_seconds * self.sample_rate as f64).round() as i64)
190            }
191            (_, _, Some(pos_beats), Some(tempo)) => {
192                Some((pos_beats / tempo * 60.0 * self.sample_rate as f64).round() as i64)
193            }
194            (_, _, _, _) => None,
195        }
196    }
197
198    /// The position in the song in seconds. Can be used to calculate the time in samples if needed.
199    pub fn pos_seconds(&self) -> Option<f64> {
200        match (
201            self.pos_samples,
202            self.pos_seconds,
203            self.pos_beats,
204            self.tempo,
205        ) {
206            (_, Some(pos_seconds), _, _) => Some(pos_seconds),
207            (Some(pos_samples), _, _, _) => Some(pos_samples as f64 / self.sample_rate as f64),
208            (_, _, Some(pos_beats), Some(tempo)) => Some(pos_beats / tempo * 60.0),
209            (_, _, _, _) => None,
210        }
211    }
212
213    /// The position in the song in quarter notes. Will be calculated from other information if
214    /// needed.
215    pub fn pos_beats(&self) -> Option<f64> {
216        match (
217            self.pos_samples,
218            self.pos_seconds,
219            self.pos_beats,
220            self.tempo,
221        ) {
222            (_, _, Some(pos_beats), _) => Some(pos_beats),
223            (_, Some(pos_seconds), _, Some(tempo)) => Some(pos_seconds / 60.0 * tempo),
224            (Some(pos_samples), _, _, Some(tempo)) => {
225                Some(pos_samples as f64 / self.sample_rate as f64 / 60.0 * tempo)
226            }
227            (_, _, _, _) => None,
228        }
229    }
230
231    /// The last bar's start position in beats. Will be calculated from other information if needed.
232    pub fn bar_start_pos_beats(&self) -> Option<f64> {
233        if self.bar_start_pos_beats.is_some() {
234            return self.bar_start_pos_beats;
235        }
236
237        match (
238            self.time_sig_numerator,
239            self.time_sig_denominator,
240            self.pos_beats(),
241        ) {
242            (Some(time_sig_numerator), Some(time_sig_denominator), Some(pos_beats)) => {
243                let quarter_note_bar_length =
244                    time_sig_numerator as f64 / time_sig_denominator as f64 * 4.0;
245                Some((pos_beats / quarter_note_bar_length).floor() * quarter_note_bar_length)
246            }
247            (_, _, _) => None,
248        }
249    }
250
251    /// The number of the bar at `bar_start_pos_beats`. This starts at 0 for the very first bar at
252    /// the start of the song. Will be calculated from other information if needed.
253    pub fn bar_number(&self) -> Option<i32> {
254        if self.bar_number.is_some() {
255            return self.bar_number;
256        }
257
258        match (
259            self.time_sig_numerator,
260            self.time_sig_denominator,
261            self.pos_beats(),
262        ) {
263            (Some(time_sig_numerator), Some(time_sig_denominator), Some(pos_beats)) => {
264                let quarter_note_bar_length =
265                    time_sig_numerator as f64 / time_sig_denominator as f64 * 4.0;
266                Some((pos_beats / quarter_note_bar_length).floor() as i32)
267            }
268            (_, _, _) => None,
269        }
270    }
271
272    /// The loop range in samples, if the loop is active and this information is available. None of
273    /// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
274    /// end is exclusive. Will be calculated from other information if needed.
275    pub fn loop_range_samples(&self) -> Option<(i64, i64)> {
276        match (
277            self.loop_range_samples,
278            self.loop_range_seconds,
279            self.loop_range_beats,
280            self.tempo,
281        ) {
282            (Some(loop_range_samples), _, _, _) => Some(loop_range_samples),
283            (_, Some((start_seconds, end_seconds)), _, _) => Some((
284                ((start_seconds * self.sample_rate as f64).round() as i64),
285                ((end_seconds * self.sample_rate as f64).round() as i64),
286            )),
287            (_, _, Some((start_beats, end_beats)), Some(tempo)) => Some((
288                (start_beats / tempo * 60.0 * self.sample_rate as f64).round() as i64,
289                (end_beats / tempo * 60.0 * self.sample_rate as f64).round() as i64,
290            )),
291            (_, _, _, _) => None,
292        }
293    }
294
295    /// The loop range in seconds, if the loop is active and this information is available. None of
296    /// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
297    /// end is exclusive. Will be calculated from other information if needed.
298    pub fn loop_range_seconds(&self) -> Option<(f64, f64)> {
299        match (
300            self.loop_range_samples,
301            self.loop_range_seconds,
302            self.loop_range_beats,
303            self.tempo,
304        ) {
305            (_, Some(loop_range_seconds), _, _) => Some(loop_range_seconds),
306            (Some((start_samples, end_samples)), _, _, _) => Some((
307                start_samples as f64 / self.sample_rate as f64,
308                end_samples as f64 / self.sample_rate as f64,
309            )),
310            (_, _, Some((start_beats, end_beats)), Some(tempo)) => {
311                Some((start_beats / tempo * 60.0, end_beats / tempo * 60.0))
312            }
313            (_, _, _, _) => None,
314        }
315    }
316
317    /// The loop range in quarter notes, if the loop is active and this information is available.
318    /// None of the plugin API docs mention whether this is exclusive or inclusive, but just assume
319    /// that the end is exclusive. Will be calculated from other information if needed.
320    pub fn loop_range_beats(&self) -> Option<(f64, f64)> {
321        match (
322            self.loop_range_samples,
323            self.loop_range_seconds,
324            self.loop_range_beats,
325            self.tempo,
326        ) {
327            (_, _, Some(loop_range_beats), _) => Some(loop_range_beats),
328            (_, Some((start_seconds, end_seconds)), _, Some(tempo)) => {
329                Some((start_seconds / 60.0 * tempo, end_seconds / 60.0 * tempo))
330            }
331            (Some((start_samples, end_samples)), _, _, Some(tempo)) => Some((
332                start_samples as f64 / self.sample_rate as f64 / 60.0 * tempo,
333                end_samples as f64 / self.sample_rate as f64 / 60.0 * tempo,
334            )),
335            (_, _, _, _) => None,
336        }
337    }
338}