Skip to main content

nice_plug_core/context/
process.rs

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