Split the nih_plug::context module
So that structs are more logically grouped with their traits.
This commit is contained in:
parent
faa9742eee
commit
b676353589
|
@ -9,7 +9,12 @@ code then it will not be listed here.
|
|||
## [2022-10-22]
|
||||
|
||||
- The `Editor` trait and the `ParentWindowHandle` struct have been moved from
|
||||
`nih_plug::plugin` to a new `nih_plug::editor` module.
|
||||
`nih_plug::plugin` to a new `nih_plug::editor` module. If you only use the
|
||||
prelude module then you won't need to change anything.
|
||||
- The `nih_plug::context` module has been split up into
|
||||
`nih_plug::context::init`, `nih_plug::context::process`, and
|
||||
`nih_plug::context::gui` to make it clearer which structs go with which
|
||||
context. You again don't have to change anything if you use the prelude.
|
||||
- NIH-plug has gained support for asynchronously running background tasks in a
|
||||
simple, type-safe, and realtime-safe way. This sadly does mean that every
|
||||
`Plugin` instance now needs to define a `BackgroundTask` type definition and
|
||||
|
|
488
src/context.rs
488
src/context.rs
|
@ -2,249 +2,9 @@
|
|||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::midi::NoteEvent;
|
||||
use crate::params::internals::ParamPtr;
|
||||
use crate::params::Param;
|
||||
use crate::plugin::Plugin;
|
||||
use crate::wrapper::state::PluginState;
|
||||
|
||||
/// Callbacks the plugin can make while it is being initialized. This is passed to the plugin during
|
||||
/// [`Plugin::initialize()`][crate::plugin::Plugin::initialize()].
|
||||
//
|
||||
// # Safety
|
||||
//
|
||||
// The implementing wrapper needs to be able to handle concurrent requests, and it should perform
|
||||
// the actual callback within [MainThreadQueue::do_maybe_async].
|
||||
pub trait InitContext<P: Plugin> {
|
||||
/// Get the current plugin API.
|
||||
fn plugin_api(&self) -> PluginApi;
|
||||
|
||||
/// Run a task directly on this thread. This ensures that the task has finished executing before
|
||||
/// the plugin finishes initializing.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// There is no asynchronous alternative for this function as that may result in incorrect
|
||||
/// behavior when doing offline rendering.
|
||||
fn execute(&self, task: P::BackgroundTask);
|
||||
|
||||
/// Update the current latency of the plugin. If the plugin is currently processing audio, then
|
||||
/// this may cause audio playback to be restarted.
|
||||
fn set_latency_samples(&self, samples: u32);
|
||||
|
||||
/// Set the current voice **capacity** for this plugin (so not the number of currently active
|
||||
/// voices). This may only be called if
|
||||
/// [`ClapPlugin::CLAP_POLY_MODULATION_CONFIG`][crate::prelude::ClapPlugin::CLAP_POLY_MODULATION_CONFIG]
|
||||
/// is set. `capacity` must be between 1 and the configured maximum capacity. Changing this at
|
||||
/// runtime allows the host to better optimize polyphonic modulation, or to switch to strictly
|
||||
/// monophonic modulation when dropping the capacity down to 1.
|
||||
fn set_current_voice_capacity(&self, capacity: u32);
|
||||
}
|
||||
|
||||
/// Contains both context data and callbacks the plugin can use during processing. Most notably this
|
||||
/// is how a plugin sends and receives note events, gets transport information, and accesses
|
||||
/// sidechain inputs and auxiliary outputs. This is passed to the plugin during as part of
|
||||
/// [`Plugin::process()`][crate::plugin::Plugin::process()].
|
||||
//
|
||||
// # Safety
|
||||
//
|
||||
// The implementing wrapper needs to be able to handle concurrent requests, and it should perform
|
||||
// the actual callback within [MainThreadQueue::do_maybe_async].
|
||||
pub trait ProcessContext<P: Plugin> {
|
||||
/// Get the current plugin API.
|
||||
fn plugin_api(&self) -> PluginApi;
|
||||
|
||||
/// Run a task on a background thread. This allows defering expensive background tasks for
|
||||
/// alter. As long as creating the `task` is realtime-safe, this operation is too.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Scheduling the same task multiple times will cause those duplicate tasks to pile up. Try to
|
||||
/// either prevnt this from happening, or check whether the task still needs to be completed in
|
||||
/// your task executor.
|
||||
fn execute_async(&self, task: P::BackgroundTask);
|
||||
|
||||
/// Get information about the current transport position and status.
|
||||
fn transport(&self) -> &Transport;
|
||||
|
||||
/// Returns the next note event, if there is one. Use [`NoteEvent::timing()`] to get the event's
|
||||
/// timing within the buffer. Only available when
|
||||
/// [`Plugin::MIDI_INPUT`][crate::prelude::Plugin::MIDI_INPUT] is set.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// You will likely want to use this with a loop, since there may be zero, one, or more events
|
||||
/// for a sample:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut next_event = context.next_event();
|
||||
/// for (sample_id, channel_samples) in buffer.iter_samples().enumerate() {
|
||||
/// while let Some(event) = next_event {
|
||||
/// if event.timing() != sample_id as u32 {
|
||||
/// break;
|
||||
/// }
|
||||
///
|
||||
/// match event {
|
||||
/// NoteEvent::NoteOn { note, velocity, .. } => { ... },
|
||||
/// NoteEvent::NoteOff { note, .. } if note == 69 => { ... },
|
||||
/// NoteEvent::PolyPressure { note, pressure, .. } { ... },
|
||||
/// _ => (),
|
||||
/// }
|
||||
///
|
||||
/// next_event = context.next_event();
|
||||
/// }
|
||||
///
|
||||
/// // Do something with `channel_samples`...
|
||||
/// }
|
||||
///
|
||||
/// ProcessStatus::Normal
|
||||
/// ```
|
||||
fn next_event(&mut self) -> Option<NoteEvent>;
|
||||
|
||||
/// Send an event to the host. Only available when
|
||||
/// [`Plugin::MIDI_OUTPUT`][crate::prelude::Plugin::MIDI_INPUT] is set. Will not do anything
|
||||
/// otherwise.
|
||||
fn send_event(&mut self, event: NoteEvent);
|
||||
|
||||
/// Update the current latency of the plugin. If the plugin is currently processing audio, then
|
||||
/// this may cause audio playback to be restarted.
|
||||
fn set_latency_samples(&self, samples: u32);
|
||||
|
||||
/// Set the current voice **capacity** for this plugin (so not the number of currently active
|
||||
/// voices). This may only be called if
|
||||
/// [`ClapPlugin::CLAP_POLY_MODULATION_CONFIG`][crate::prelude::ClapPlugin::CLAP_POLY_MODULATION_CONFIG]
|
||||
/// is set. `capacity` must be between 1 and the configured maximum capacity. Changing this at
|
||||
/// runtime allows the host to better optimize polyphonic modulation, or to switch to strictly
|
||||
/// monophonic modulation when dropping the capacity down to 1.
|
||||
fn set_current_voice_capacity(&self, capacity: u32);
|
||||
|
||||
// TODO: Add this, this works similar to [GuiContext::set_parameter] but it adds the parameter
|
||||
// change to a queue (or directly to the VST3 plugin's parameter output queues) instead of
|
||||
// using main thread host automation (and all the locks involved there).
|
||||
// fn set_parameter<P: Param>(&self, param: &P, value: P::Plain);
|
||||
}
|
||||
|
||||
/// Callbacks the plugin can make when the user interacts with its GUI such as updating parameter
|
||||
/// values. This is passed to the plugin during [`Editor::spawn()`][crate::prelude::Editor::spawn()]. All of
|
||||
/// these functions assume they're being called from the main GUI thread.
|
||||
//
|
||||
// # Safety
|
||||
//
|
||||
// The implementing wrapper can assume that everything is being called from the main thread. Since
|
||||
// NIH-plug doesn't own the GUI event loop, this invariant cannot be part of the interface.
|
||||
pub trait GuiContext: Send + Sync + 'static {
|
||||
/// Get the current plugin API. This may be useful to display in the plugin's GUI as part of an
|
||||
/// about screen.
|
||||
fn plugin_api(&self) -> PluginApi;
|
||||
|
||||
/// Ask the host to resize the editor window to the size specified by
|
||||
/// [`Editor::size()`][crate::prelude::Editor::size()]. This will return false if the host
|
||||
/// somehow didn't like this and rejected the resize, in which case the window should revert to
|
||||
/// its old size. You should only actually resize your embedded window once this returns `true`.
|
||||
///
|
||||
/// TODO: Host->Plugin resizing has not been implemented yet
|
||||
fn request_resize(&self) -> bool;
|
||||
|
||||
/// Inform the host a parameter will be automated. Create a [`ParamSetter`] and use
|
||||
/// [`ParamSetter::begin_set_parameter()`] instead for a safe, user friendly API.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The implementing function still needs to check if `param` actually exists. This function is
|
||||
/// mostly marked as unsafe for API reasons.
|
||||
unsafe fn raw_begin_set_parameter(&self, param: ParamPtr);
|
||||
|
||||
/// Inform the host a parameter is being automated with an already normalized value. Create a
|
||||
/// [`ParamSetter`] and use [`ParamSetter::set_parameter()`] instead for a safe, user friendly
|
||||
/// API.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The implementing function still needs to check if `param` actually exists. This function is
|
||||
/// mostly marked as unsafe for API reasons.
|
||||
unsafe fn raw_set_parameter_normalized(&self, param: ParamPtr, normalized: f32);
|
||||
|
||||
/// Inform the host a parameter has been automated. Create a [`ParamSetter`] and use
|
||||
/// [`ParamSetter::end_set_parameter()`] instead for a safe, user friendly API.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The implementing function still needs to check if `param` actually exists. This function is
|
||||
/// mostly marked as unsafe for API reasons.
|
||||
unsafe fn raw_end_set_parameter(&self, param: ParamPtr);
|
||||
|
||||
/// Serialize the plugin's current state to a serde-serializable object. Useful for implementing
|
||||
/// preset handling within a plugin's GUI.
|
||||
fn get_state(&self) -> PluginState;
|
||||
|
||||
/// Restore the state from a previously serialized state object. This will block the GUI thread
|
||||
/// until the state has been restored and a parameter value rescan has been requested from the
|
||||
/// host. If the plugin is currently processing audio, then the parameter values will be
|
||||
/// restored at the end of the current processing cycle.
|
||||
fn set_state(&self, state: PluginState);
|
||||
}
|
||||
|
||||
/// Information about the plugin's transport. Depending on the plugin API and the host not all
|
||||
/// fields may be available.
|
||||
#[derive(Debug)]
|
||||
pub struct Transport {
|
||||
/// Whether the transport is currently running.
|
||||
pub playing: bool,
|
||||
/// Whether recording is enabled in the project.
|
||||
pub recording: bool,
|
||||
/// Whether the pre-roll is currently active, if the plugin API reports this information.
|
||||
pub preroll_active: Option<bool>,
|
||||
|
||||
/// The sample rate in Hertz. Also passed in
|
||||
/// [`Plugin::initialize()`][crate::prelude::Plugin::initialize()], so if you need this then you
|
||||
/// can also store that value.
|
||||
pub sample_rate: f32,
|
||||
/// The project's tempo in beats per minute.
|
||||
pub tempo: Option<f64>,
|
||||
/// The time signature's numerator.
|
||||
pub time_sig_numerator: Option<i32>,
|
||||
/// The time signature's denominator.
|
||||
pub time_sig_denominator: Option<i32>,
|
||||
|
||||
// XXX: VST3 also has a continuous time in samples that ignores loops, but we can't reconstruct
|
||||
// something similar in CLAP so it may be best to just ignore that so you can't rely on it
|
||||
/// The position in the song in samples. Can be used to calculate the time in seconds if needed.
|
||||
pub(crate) pos_samples: Option<i64>,
|
||||
/// The position in the song in quarter notes. Can be used to calculate the time in samples if
|
||||
/// needed.
|
||||
pub(crate) pos_seconds: Option<f64>,
|
||||
/// The position in the song in quarter notes. Can be calculated from the time in seconds and
|
||||
/// the tempo if needed.
|
||||
pub(crate) pos_beats: Option<f64>,
|
||||
/// The last bar's start position in beats. Can be calculated from the beat position and time
|
||||
/// signature if needed.
|
||||
pub(crate) bar_start_pos_beats: Option<f64>,
|
||||
/// The number of the bar at `bar_start_pos_beats`. This starts at 0 for the very first bar at
|
||||
/// the start of the song. Can be calculated from the beat position and time signature if
|
||||
/// needed.
|
||||
pub(crate) bar_number: Option<i32>,
|
||||
|
||||
/// The loop range in samples, if the loop is active and this information is available. None of
|
||||
/// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
|
||||
/// end is exclusive. Can be calculated from the other loop range information if needed.
|
||||
pub(crate) loop_range_samples: Option<(i64, i64)>,
|
||||
/// The loop range in seconds, if the loop is active and this information is available. None of
|
||||
/// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
|
||||
/// end is exclusive. Can be calculated from the other loop range information if needed.
|
||||
pub(crate) loop_range_seconds: Option<(f64, f64)>,
|
||||
/// The loop range in quarter notes, if the loop is active and this information is available.
|
||||
/// None of the plugin API docs mention whether this is exclusive or inclusive, but just assume
|
||||
/// that the end is exclusive. Can be calculated from the other loop range information if
|
||||
/// needed.
|
||||
pub(crate) loop_range_beats: Option<(f64, f64)>,
|
||||
}
|
||||
|
||||
/// A convenience helper for setting parameter values. Any changes made here will be broadcasted to
|
||||
/// the host and reflected in the plugin's [`Params`][crate::params::Params] object. These
|
||||
/// functions should only be called from the main thread.
|
||||
pub struct ParamSetter<'a> {
|
||||
pub raw_context: &'a dyn GuiContext,
|
||||
}
|
||||
pub mod gui;
|
||||
pub mod init;
|
||||
pub mod process;
|
||||
|
||||
/// The currently active plugin API. This may be useful to display in an about screen in the
|
||||
/// plugin's GUI for debugging purposes.
|
||||
|
@ -264,245 +24,3 @@ impl Display for PluginApi {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: These conversions have not really been tested yet, there might be an error in there somewhere
|
||||
impl Transport {
|
||||
/// Initialize the transport struct without any information.
|
||||
pub(crate) fn new(sample_rate: f32) -> Self {
|
||||
Self {
|
||||
playing: false,
|
||||
recording: false,
|
||||
preroll_active: None,
|
||||
|
||||
sample_rate,
|
||||
tempo: None,
|
||||
time_sig_numerator: None,
|
||||
time_sig_denominator: None,
|
||||
|
||||
pos_samples: None,
|
||||
pos_seconds: None,
|
||||
pos_beats: None,
|
||||
bar_start_pos_beats: None,
|
||||
bar_number: None,
|
||||
|
||||
loop_range_samples: None,
|
||||
loop_range_seconds: None,
|
||||
loop_range_beats: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The position in the song in samples. Will be calculated from other information if needed.
|
||||
pub fn pos_samples(&self) -> Option<i64> {
|
||||
match (
|
||||
self.pos_samples,
|
||||
self.pos_seconds,
|
||||
self.pos_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(Some(pos_samples), _, _, _) => Some(pos_samples),
|
||||
(_, Some(pos_seconds), _, _) => {
|
||||
Some((pos_seconds * self.sample_rate as f64).round() as i64)
|
||||
}
|
||||
(_, _, Some(pos_beats), Some(tempo)) => {
|
||||
Some((pos_beats / tempo * 60.0 * self.sample_rate as f64).round() as i64)
|
||||
}
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The position in the song in quarter notes. Will be calculated from other information if
|
||||
/// needed.
|
||||
pub fn pos_seconds(&self) -> Option<f64> {
|
||||
match (
|
||||
self.pos_samples,
|
||||
self.pos_seconds,
|
||||
self.pos_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(_, Some(pos_seconds), _, _) => Some(pos_seconds),
|
||||
(Some(pos_samples), _, _, _) => Some(pos_samples as f64 / self.sample_rate as f64),
|
||||
(_, _, Some(pos_beats), Some(tempo)) => Some(pos_beats / tempo * 60.0),
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The position in the song in quarter notes. Will be calculated from other information if
|
||||
/// needed.
|
||||
pub fn pos_beats(&self) -> Option<f64> {
|
||||
match (
|
||||
self.pos_samples,
|
||||
self.pos_seconds,
|
||||
self.pos_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(_, _, Some(pos_beats), _) => Some(pos_beats),
|
||||
(_, Some(pos_seconds), _, Some(tempo)) => Some(pos_seconds / 60.0 * tempo),
|
||||
(Some(pos_samples), _, _, Some(tempo)) => {
|
||||
Some(pos_samples as f64 / self.sample_rate as f64 / 60.0 * tempo)
|
||||
}
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The last bar's start position in beats. Will be calculated from other information if needed.
|
||||
pub fn bar_start_pos_beats(&self) -> Option<f64> {
|
||||
if self.bar_start_pos_beats.is_some() {
|
||||
return self.bar_start_pos_beats;
|
||||
}
|
||||
|
||||
match (
|
||||
self.time_sig_numerator,
|
||||
self.time_sig_denominator,
|
||||
self.pos_beats(),
|
||||
) {
|
||||
(Some(time_sig_numerator), Some(time_sig_denominator), Some(pos_beats)) => {
|
||||
let quarter_note_bar_length =
|
||||
time_sig_numerator as f64 / time_sig_denominator as f64 * 4.0;
|
||||
Some((pos_beats / quarter_note_bar_length).floor() * quarter_note_bar_length)
|
||||
}
|
||||
(_, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of the bar at `bar_start_pos_beats`. This starts at 0 for the very first bar at
|
||||
/// the start of the song. Will be calculated from other information if needed.
|
||||
pub fn bar_number(&self) -> Option<i32> {
|
||||
if self.bar_number.is_some() {
|
||||
return self.bar_number;
|
||||
}
|
||||
|
||||
match (
|
||||
self.time_sig_numerator,
|
||||
self.time_sig_denominator,
|
||||
self.pos_beats(),
|
||||
) {
|
||||
(Some(time_sig_numerator), Some(time_sig_denominator), Some(pos_beats)) => {
|
||||
let quarter_note_bar_length =
|
||||
time_sig_numerator as f64 / time_sig_denominator as f64 * 4.0;
|
||||
Some((pos_beats / quarter_note_bar_length).floor() as i32)
|
||||
}
|
||||
(_, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The loop range in samples, if the loop is active and this information is available. None of
|
||||
/// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
|
||||
/// end is exclusive. Will be calculated from other information if needed.
|
||||
pub fn loop_range_samples(&self) -> Option<(i64, i64)> {
|
||||
match (
|
||||
self.loop_range_samples,
|
||||
self.loop_range_seconds,
|
||||
self.loop_range_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(Some(loop_range_samples), _, _, _) => Some(loop_range_samples),
|
||||
(_, Some((start_seconds, end_seconds)), _, _) => Some((
|
||||
((start_seconds * self.sample_rate as f64).round() as i64),
|
||||
((end_seconds * self.sample_rate as f64).round() as i64),
|
||||
)),
|
||||
(_, _, Some((start_beats, end_beats)), Some(tempo)) => Some((
|
||||
(start_beats / tempo * 60.0 * self.sample_rate as f64).round() as i64,
|
||||
(end_beats / tempo * 60.0 * self.sample_rate as f64).round() as i64,
|
||||
)),
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The loop range in seconds, if the loop is active and this information is available. None of
|
||||
/// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
|
||||
/// end is exclusive. Will be calculated from other information if needed.
|
||||
pub fn loop_range_seconds(&self) -> Option<(f64, f64)> {
|
||||
match (
|
||||
self.loop_range_samples,
|
||||
self.loop_range_seconds,
|
||||
self.loop_range_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(_, Some(loop_range_seconds), _, _) => Some(loop_range_seconds),
|
||||
(Some((start_samples, end_samples)), _, _, _) => Some((
|
||||
start_samples as f64 / self.sample_rate as f64,
|
||||
end_samples as f64 / self.sample_rate as f64,
|
||||
)),
|
||||
(_, _, Some((start_beats, end_beats)), Some(tempo)) => {
|
||||
Some((start_beats / tempo * 60.0, end_beats / tempo * 60.0))
|
||||
}
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The loop range in quarter notes, if the loop is active and this information is available.
|
||||
/// None of the plugin API docs mention whether this is exclusive or inclusive, but just assume
|
||||
/// that the end is exclusive. Will be calculated from other information if needed.
|
||||
pub fn loop_range_beats(&self) -> Option<(f64, f64)> {
|
||||
match (
|
||||
self.loop_range_samples,
|
||||
self.loop_range_seconds,
|
||||
self.loop_range_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(_, _, Some(loop_range_beats), _) => Some(loop_range_beats),
|
||||
(_, Some((start_seconds, end_seconds)), _, Some(tempo)) => {
|
||||
Some((start_seconds / 60.0 * tempo, end_seconds / 60.0 * tempo))
|
||||
}
|
||||
(Some((start_samples, end_samples)), _, _, Some(tempo)) => Some((
|
||||
start_samples as f64 / self.sample_rate as f64 / 60.0 * tempo,
|
||||
end_samples as f64 / self.sample_rate as f64 / 60.0 * tempo,
|
||||
)),
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ParamSetter<'a> {
|
||||
pub fn new(context: &'a dyn GuiContext) -> Self {
|
||||
Self {
|
||||
raw_context: context,
|
||||
}
|
||||
}
|
||||
|
||||
/// Inform the host that you will start automating a parameter. This needs to be called before
|
||||
/// calling [`set_parameter()`][Self::set_parameter()] for the specified parameter.
|
||||
pub fn begin_set_parameter<P: Param>(&self, param: &P) {
|
||||
unsafe { self.raw_context.raw_begin_set_parameter(param.as_ptr()) };
|
||||
}
|
||||
|
||||
/// Set a parameter to the specified parameter value. You will need to call
|
||||
/// [`begin_set_parameter()`][Self::begin_set_parameter()] before and
|
||||
/// [`end_set_parameter()`][Self::end_set_parameter()] after calling this so the host can
|
||||
/// properly record automation for the parameter. This can be called multiple times in a row
|
||||
/// before calling [`end_set_parameter()`][Self::end_set_parameter()], for instance when moving
|
||||
/// a slider around.
|
||||
///
|
||||
/// This function assumes you're already calling this from a GUI thread. Calling any of these
|
||||
/// functions from any other thread may result in unexpected behavior.
|
||||
pub fn set_parameter<P: Param>(&self, param: &P, value: P::Plain) {
|
||||
let ptr = param.as_ptr();
|
||||
let normalized = param.preview_normalized(value);
|
||||
unsafe {
|
||||
self.raw_context
|
||||
.raw_set_parameter_normalized(ptr, normalized)
|
||||
};
|
||||
}
|
||||
|
||||
/// Set a parameter to an already normalized value. Works exactly the same as
|
||||
/// [`set_parameter()`][Self::set_parameter()] and needs to follow the same rules, but this may
|
||||
/// be useful when implementing a GUI.
|
||||
///
|
||||
/// This does not perform any snapping. Consider converting the normalized value to a plain
|
||||
/// value and setting that with [`set_parameter()`][Self::set_parameter()] instead so the
|
||||
/// normalized value known to the host matches `param.normalized_value()`.
|
||||
pub fn set_parameter_normalized<P: Param>(&self, param: &P, normalized: f32) {
|
||||
let ptr = param.as_ptr();
|
||||
unsafe {
|
||||
self.raw_context
|
||||
.raw_set_parameter_normalized(ptr, normalized)
|
||||
};
|
||||
}
|
||||
|
||||
/// Inform the host that you are done automating a parameter. This needs to be called after one
|
||||
/// or more [`set_parameter()`][Self::set_parameter()] calls for a parameter so the host knows
|
||||
/// the automation gesture has finished.
|
||||
pub fn end_set_parameter<P: Param>(&self, param: &P) {
|
||||
unsafe { self.raw_context.raw_end_set_parameter(param.as_ptr()) };
|
||||
}
|
||||
}
|
||||
|
|
127
src/context/gui.rs
Normal file
127
src/context/gui.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
//! A context passed to a plugin's editor.
|
||||
|
||||
use super::PluginApi;
|
||||
use crate::params::internals::ParamPtr;
|
||||
use crate::params::Param;
|
||||
use crate::wrapper::state::PluginState;
|
||||
|
||||
/// Callbacks the plugin can make when the user interacts with its GUI such as updating parameter
|
||||
/// values. This is passed to the plugin during [`Editor::spawn()`][crate::prelude::Editor::spawn()]. All of
|
||||
/// these functions assume they're being called from the main GUI thread.
|
||||
//
|
||||
// # Safety
|
||||
//
|
||||
// The implementing wrapper can assume that everything is being called from the main thread. Since
|
||||
// NIH-plug doesn't own the GUI event loop, this invariant cannot be part of the interface.
|
||||
pub trait GuiContext: Send + Sync + 'static {
|
||||
/// Get the current plugin API. This may be useful to display in the plugin's GUI as part of an
|
||||
/// about screen.
|
||||
fn plugin_api(&self) -> PluginApi;
|
||||
|
||||
/// Ask the host to resize the editor window to the size specified by
|
||||
/// [`Editor::size()`][crate::prelude::Editor::size()]. This will return false if the host
|
||||
/// somehow didn't like this and rejected the resize, in which case the window should revert to
|
||||
/// its old size. You should only actually resize your embedded window once this returns `true`.
|
||||
///
|
||||
/// TODO: Host->Plugin resizing has not been implemented yet
|
||||
fn request_resize(&self) -> bool;
|
||||
|
||||
/// Inform the host a parameter will be automated. Create a [`ParamSetter`] and use
|
||||
/// [`ParamSetter::begin_set_parameter()`] instead for a safe, user friendly API.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The implementing function still needs to check if `param` actually exists. This function is
|
||||
/// mostly marked as unsafe for API reasons.
|
||||
unsafe fn raw_begin_set_parameter(&self, param: ParamPtr);
|
||||
|
||||
/// Inform the host a parameter is being automated with an already normalized value. Create a
|
||||
/// [`ParamSetter`] and use [`ParamSetter::set_parameter()`] instead for a safe, user friendly
|
||||
/// API.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The implementing function still needs to check if `param` actually exists. This function is
|
||||
/// mostly marked as unsafe for API reasons.
|
||||
unsafe fn raw_set_parameter_normalized(&self, param: ParamPtr, normalized: f32);
|
||||
|
||||
/// Inform the host a parameter has been automated. Create a [`ParamSetter`] and use
|
||||
/// [`ParamSetter::end_set_parameter()`] instead for a safe, user friendly API.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The implementing function still needs to check if `param` actually exists. This function is
|
||||
/// mostly marked as unsafe for API reasons.
|
||||
unsafe fn raw_end_set_parameter(&self, param: ParamPtr);
|
||||
|
||||
/// Serialize the plugin's current state to a serde-serializable object. Useful for implementing
|
||||
/// preset handling within a plugin's GUI.
|
||||
fn get_state(&self) -> PluginState;
|
||||
|
||||
/// Restore the state from a previously serialized state object. This will block the GUI thread
|
||||
/// until the state has been restored and a parameter value rescan has been requested from the
|
||||
/// host. If the plugin is currently processing audio, then the parameter values will be
|
||||
/// restored at the end of the current processing cycle.
|
||||
fn set_state(&self, state: PluginState);
|
||||
}
|
||||
|
||||
/// A convenience helper for setting parameter values. Any changes made here will be broadcasted to
|
||||
/// the host and reflected in the plugin's [`Params`][crate::params::Params] object. These
|
||||
/// functions should only be called from the main thread.
|
||||
pub struct ParamSetter<'a> {
|
||||
pub raw_context: &'a dyn GuiContext,
|
||||
}
|
||||
|
||||
impl<'a> ParamSetter<'a> {
|
||||
pub fn new(context: &'a dyn GuiContext) -> Self {
|
||||
Self {
|
||||
raw_context: context,
|
||||
}
|
||||
}
|
||||
|
||||
/// Inform the host that you will start automating a parameter. This needs to be called before
|
||||
/// calling [`set_parameter()`][Self::set_parameter()] for the specified parameter.
|
||||
pub fn begin_set_parameter<P: Param>(&self, param: &P) {
|
||||
unsafe { self.raw_context.raw_begin_set_parameter(param.as_ptr()) };
|
||||
}
|
||||
|
||||
/// Set a parameter to the specified parameter value. You will need to call
|
||||
/// [`begin_set_parameter()`][Self::begin_set_parameter()] before and
|
||||
/// [`end_set_parameter()`][Self::end_set_parameter()] after calling this so the host can
|
||||
/// properly record automation for the parameter. This can be called multiple times in a row
|
||||
/// before calling [`end_set_parameter()`][Self::end_set_parameter()], for instance when moving
|
||||
/// a slider around.
|
||||
///
|
||||
/// This function assumes you're already calling this from a GUI thread. Calling any of these
|
||||
/// functions from any other thread may result in unexpected behavior.
|
||||
pub fn set_parameter<P: Param>(&self, param: &P, value: P::Plain) {
|
||||
let ptr = param.as_ptr();
|
||||
let normalized = param.preview_normalized(value);
|
||||
unsafe {
|
||||
self.raw_context
|
||||
.raw_set_parameter_normalized(ptr, normalized)
|
||||
};
|
||||
}
|
||||
|
||||
/// Set a parameter to an already normalized value. Works exactly the same as
|
||||
/// [`set_parameter()`][Self::set_parameter()] and needs to follow the same rules, but this may
|
||||
/// be useful when implementing a GUI.
|
||||
///
|
||||
/// This does not perform any snapping. Consider converting the normalized value to a plain
|
||||
/// value and setting that with [`set_parameter()`][Self::set_parameter()] instead so the
|
||||
/// normalized value known to the host matches `param.normalized_value()`.
|
||||
pub fn set_parameter_normalized<P: Param>(&self, param: &P, normalized: f32) {
|
||||
let ptr = param.as_ptr();
|
||||
unsafe {
|
||||
self.raw_context
|
||||
.raw_set_parameter_normalized(ptr, normalized)
|
||||
};
|
||||
}
|
||||
|
||||
/// Inform the host that you are done automating a parameter. This needs to be called after one
|
||||
/// or more [`set_parameter()`][Self::set_parameter()] calls for a parameter so the host knows
|
||||
/// the automation gesture has finished.
|
||||
pub fn end_set_parameter<P: Param>(&self, param: &P) {
|
||||
unsafe { self.raw_context.raw_end_set_parameter(param.as_ptr()) };
|
||||
}
|
||||
}
|
37
src/context/init.rs
Normal file
37
src/context/init.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
//! A context passed during plugin initialization.
|
||||
|
||||
use super::PluginApi;
|
||||
use crate::plugin::Plugin;
|
||||
|
||||
/// Callbacks the plugin can make while it is being initialized. This is passed to the plugin during
|
||||
/// [`Plugin::initialize()`][crate::plugin::Plugin::initialize()].
|
||||
//
|
||||
// # Safety
|
||||
//
|
||||
// The implementing wrapper needs to be able to handle concurrent requests, and it should perform
|
||||
// the actual callback within [MainThreadQueue::do_maybe_async].
|
||||
pub trait InitContext<P: Plugin> {
|
||||
/// Get the current plugin API.
|
||||
fn plugin_api(&self) -> PluginApi;
|
||||
|
||||
/// Run a task directly on this thread. This ensures that the task has finished executing before
|
||||
/// the plugin finishes initializing.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// There is no asynchronous alternative for this function as that may result in incorrect
|
||||
/// behavior when doing offline rendering.
|
||||
fn execute(&self, task: P::BackgroundTask);
|
||||
|
||||
/// Update the current latency of the plugin. If the plugin is currently processing audio, then
|
||||
/// this may cause audio playback to be restarted.
|
||||
fn set_latency_samples(&self, samples: u32);
|
||||
|
||||
/// Set the current voice **capacity** for this plugin (so not the number of currently active
|
||||
/// voices). This may only be called if
|
||||
/// [`ClapPlugin::CLAP_POLY_MODULATION_CONFIG`][crate::prelude::ClapPlugin::CLAP_POLY_MODULATION_CONFIG]
|
||||
/// is set. `capacity` must be between 1 and the configured maximum capacity. Changing this at
|
||||
/// runtime allows the host to better optimize polyphonic modulation, or to switch to strictly
|
||||
/// monophonic modulation when dropping the capacity down to 1.
|
||||
fn set_current_voice_capacity(&self, capacity: u32);
|
||||
}
|
330
src/context/process.rs
Normal file
330
src/context/process.rs
Normal file
|
@ -0,0 +1,330 @@
|
|||
//! A context passed during the process function.
|
||||
|
||||
use super::PluginApi;
|
||||
use crate::midi::NoteEvent;
|
||||
use crate::plugin::Plugin;
|
||||
|
||||
/// Contains both context data and callbacks the plugin can use during processing. Most notably this
|
||||
/// is how a plugin sends and receives note events, gets transport information, and accesses
|
||||
/// sidechain inputs and auxiliary outputs. This is passed to the plugin during as part of
|
||||
/// [`Plugin::process()`][crate::plugin::Plugin::process()].
|
||||
//
|
||||
// # Safety
|
||||
//
|
||||
// The implementing wrapper needs to be able to handle concurrent requests, and it should perform
|
||||
// the actual callback within [MainThreadQueue::do_maybe_async].
|
||||
pub trait ProcessContext<P: Plugin> {
|
||||
/// Get the current plugin API.
|
||||
fn plugin_api(&self) -> PluginApi;
|
||||
|
||||
/// Run a task on a background thread. This allows defering expensive background tasks for
|
||||
/// alter. As long as creating the `task` is realtime-safe, this operation is too.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Scheduling the same task multiple times will cause those duplicate tasks to pile up. Try to
|
||||
/// either prevnt this from happening, or check whether the task still needs to be completed in
|
||||
/// your task executor.
|
||||
fn execute_async(&self, task: P::BackgroundTask);
|
||||
|
||||
/// Get information about the current transport position and status.
|
||||
fn transport(&self) -> &Transport;
|
||||
|
||||
/// Returns the next note event, if there is one. Use [`NoteEvent::timing()`] to get the event's
|
||||
/// timing within the buffer. Only available when
|
||||
/// [`Plugin::MIDI_INPUT`][crate::prelude::Plugin::MIDI_INPUT] is set.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// You will likely want to use this with a loop, since there may be zero, one, or more events
|
||||
/// for a sample:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut next_event = context.next_event();
|
||||
/// for (sample_id, channel_samples) in buffer.iter_samples().enumerate() {
|
||||
/// while let Some(event) = next_event {
|
||||
/// if event.timing() != sample_id as u32 {
|
||||
/// break;
|
||||
/// }
|
||||
///
|
||||
/// match event {
|
||||
/// NoteEvent::NoteOn { note, velocity, .. } => { ... },
|
||||
/// NoteEvent::NoteOff { note, .. } if note == 69 => { ... },
|
||||
/// NoteEvent::PolyPressure { note, pressure, .. } { ... },
|
||||
/// _ => (),
|
||||
/// }
|
||||
///
|
||||
/// next_event = context.next_event();
|
||||
/// }
|
||||
///
|
||||
/// // Do something with `channel_samples`...
|
||||
/// }
|
||||
///
|
||||
/// ProcessStatus::Normal
|
||||
/// ```
|
||||
fn next_event(&mut self) -> Option<NoteEvent>;
|
||||
|
||||
/// Send an event to the host. Only available when
|
||||
/// [`Plugin::MIDI_OUTPUT`][crate::prelude::Plugin::MIDI_INPUT] is set. Will not do anything
|
||||
/// otherwise.
|
||||
fn send_event(&mut self, event: NoteEvent);
|
||||
|
||||
/// Update the current latency of the plugin. If the plugin is currently processing audio, then
|
||||
/// this may cause audio playback to be restarted.
|
||||
fn set_latency_samples(&self, samples: u32);
|
||||
|
||||
/// Set the current voice **capacity** for this plugin (so not the number of currently active
|
||||
/// voices). This may only be called if
|
||||
/// [`ClapPlugin::CLAP_POLY_MODULATION_CONFIG`][crate::prelude::ClapPlugin::CLAP_POLY_MODULATION_CONFIG]
|
||||
/// is set. `capacity` must be between 1 and the configured maximum capacity. Changing this at
|
||||
/// runtime allows the host to better optimize polyphonic modulation, or to switch to strictly
|
||||
/// monophonic modulation when dropping the capacity down to 1.
|
||||
fn set_current_voice_capacity(&self, capacity: u32);
|
||||
|
||||
// TODO: Add this, this works similar to [GuiContext::set_parameter] but it adds the parameter
|
||||
// change to a queue (or directly to the VST3 plugin's parameter output queues) instead of
|
||||
// using main thread host automation (and all the locks involved there).
|
||||
// fn set_parameter<P: Param>(&self, param: &P, value: P::Plain);
|
||||
}
|
||||
|
||||
/// Information about the plugin's transport. Depending on the plugin API and the host not all
|
||||
/// fields may be available.
|
||||
#[derive(Debug)]
|
||||
pub struct Transport {
|
||||
/// Whether the transport is currently running.
|
||||
pub playing: bool,
|
||||
/// Whether recording is enabled in the project.
|
||||
pub recording: bool,
|
||||
/// Whether the pre-roll is currently active, if the plugin API reports this information.
|
||||
pub preroll_active: Option<bool>,
|
||||
|
||||
/// The sample rate in Hertz. Also passed in
|
||||
/// [`Plugin::initialize()`][crate::prelude::Plugin::initialize()], so if you need this then you
|
||||
/// can also store that value.
|
||||
pub sample_rate: f32,
|
||||
/// The project's tempo in beats per minute.
|
||||
pub tempo: Option<f64>,
|
||||
/// The time signature's numerator.
|
||||
pub time_sig_numerator: Option<i32>,
|
||||
/// The time signature's denominator.
|
||||
pub time_sig_denominator: Option<i32>,
|
||||
|
||||
// XXX: VST3 also has a continuous time in samples that ignores loops, but we can't reconstruct
|
||||
// something similar in CLAP so it may be best to just ignore that so you can't rely on it
|
||||
/// The position in the song in samples. Can be used to calculate the time in seconds if needed.
|
||||
pub(crate) pos_samples: Option<i64>,
|
||||
/// The position in the song in quarter notes. Can be used to calculate the time in samples if
|
||||
/// needed.
|
||||
pub(crate) pos_seconds: Option<f64>,
|
||||
/// The position in the song in quarter notes. Can be calculated from the time in seconds and
|
||||
/// the tempo if needed.
|
||||
pub(crate) pos_beats: Option<f64>,
|
||||
/// The last bar's start position in beats. Can be calculated from the beat position and time
|
||||
/// signature if needed.
|
||||
pub(crate) bar_start_pos_beats: Option<f64>,
|
||||
/// The number of the bar at `bar_start_pos_beats`. This starts at 0 for the very first bar at
|
||||
/// the start of the song. Can be calculated from the beat position and time signature if
|
||||
/// needed.
|
||||
pub(crate) bar_number: Option<i32>,
|
||||
|
||||
/// The loop range in samples, if the loop is active and this information is available. None of
|
||||
/// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
|
||||
/// end is exclusive. Can be calculated from the other loop range information if needed.
|
||||
pub(crate) loop_range_samples: Option<(i64, i64)>,
|
||||
/// The loop range in seconds, if the loop is active and this information is available. None of
|
||||
/// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
|
||||
/// end is exclusive. Can be calculated from the other loop range information if needed.
|
||||
pub(crate) loop_range_seconds: Option<(f64, f64)>,
|
||||
/// The loop range in quarter notes, if the loop is active and this information is available.
|
||||
/// None of the plugin API docs mention whether this is exclusive or inclusive, but just assume
|
||||
/// that the end is exclusive. Can be calculated from the other loop range information if
|
||||
/// needed.
|
||||
pub(crate) loop_range_beats: Option<(f64, f64)>,
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
/// Initialize the transport struct without any information.
|
||||
pub(crate) fn new(sample_rate: f32) -> Self {
|
||||
Self {
|
||||
playing: false,
|
||||
recording: false,
|
||||
preroll_active: None,
|
||||
|
||||
sample_rate,
|
||||
tempo: None,
|
||||
time_sig_numerator: None,
|
||||
time_sig_denominator: None,
|
||||
|
||||
pos_samples: None,
|
||||
pos_seconds: None,
|
||||
pos_beats: None,
|
||||
bar_start_pos_beats: None,
|
||||
bar_number: None,
|
||||
|
||||
loop_range_samples: None,
|
||||
loop_range_seconds: None,
|
||||
loop_range_beats: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The position in the song in samples. Will be calculated from other information if needed.
|
||||
pub fn pos_samples(&self) -> Option<i64> {
|
||||
match (
|
||||
self.pos_samples,
|
||||
self.pos_seconds,
|
||||
self.pos_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(Some(pos_samples), _, _, _) => Some(pos_samples),
|
||||
(_, Some(pos_seconds), _, _) => {
|
||||
Some((pos_seconds * self.sample_rate as f64).round() as i64)
|
||||
}
|
||||
(_, _, Some(pos_beats), Some(tempo)) => {
|
||||
Some((pos_beats / tempo * 60.0 * self.sample_rate as f64).round() as i64)
|
||||
}
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The position in the song in quarter notes. Will be calculated from other information if
|
||||
/// needed.
|
||||
pub fn pos_seconds(&self) -> Option<f64> {
|
||||
match (
|
||||
self.pos_samples,
|
||||
self.pos_seconds,
|
||||
self.pos_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(_, Some(pos_seconds), _, _) => Some(pos_seconds),
|
||||
(Some(pos_samples), _, _, _) => Some(pos_samples as f64 / self.sample_rate as f64),
|
||||
(_, _, Some(pos_beats), Some(tempo)) => Some(pos_beats / tempo * 60.0),
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The position in the song in quarter notes. Will be calculated from other information if
|
||||
/// needed.
|
||||
pub fn pos_beats(&self) -> Option<f64> {
|
||||
match (
|
||||
self.pos_samples,
|
||||
self.pos_seconds,
|
||||
self.pos_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(_, _, Some(pos_beats), _) => Some(pos_beats),
|
||||
(_, Some(pos_seconds), _, Some(tempo)) => Some(pos_seconds / 60.0 * tempo),
|
||||
(Some(pos_samples), _, _, Some(tempo)) => {
|
||||
Some(pos_samples as f64 / self.sample_rate as f64 / 60.0 * tempo)
|
||||
}
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The last bar's start position in beats. Will be calculated from other information if needed.
|
||||
pub fn bar_start_pos_beats(&self) -> Option<f64> {
|
||||
if self.bar_start_pos_beats.is_some() {
|
||||
return self.bar_start_pos_beats;
|
||||
}
|
||||
|
||||
match (
|
||||
self.time_sig_numerator,
|
||||
self.time_sig_denominator,
|
||||
self.pos_beats(),
|
||||
) {
|
||||
(Some(time_sig_numerator), Some(time_sig_denominator), Some(pos_beats)) => {
|
||||
let quarter_note_bar_length =
|
||||
time_sig_numerator as f64 / time_sig_denominator as f64 * 4.0;
|
||||
Some((pos_beats / quarter_note_bar_length).floor() * quarter_note_bar_length)
|
||||
}
|
||||
(_, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of the bar at `bar_start_pos_beats`. This starts at 0 for the very first bar at
|
||||
/// the start of the song. Will be calculated from other information if needed.
|
||||
pub fn bar_number(&self) -> Option<i32> {
|
||||
if self.bar_number.is_some() {
|
||||
return self.bar_number;
|
||||
}
|
||||
|
||||
match (
|
||||
self.time_sig_numerator,
|
||||
self.time_sig_denominator,
|
||||
self.pos_beats(),
|
||||
) {
|
||||
(Some(time_sig_numerator), Some(time_sig_denominator), Some(pos_beats)) => {
|
||||
let quarter_note_bar_length =
|
||||
time_sig_numerator as f64 / time_sig_denominator as f64 * 4.0;
|
||||
Some((pos_beats / quarter_note_bar_length).floor() as i32)
|
||||
}
|
||||
(_, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The loop range in samples, if the loop is active and this information is available. None of
|
||||
/// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
|
||||
/// end is exclusive. Will be calculated from other information if needed.
|
||||
pub fn loop_range_samples(&self) -> Option<(i64, i64)> {
|
||||
match (
|
||||
self.loop_range_samples,
|
||||
self.loop_range_seconds,
|
||||
self.loop_range_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(Some(loop_range_samples), _, _, _) => Some(loop_range_samples),
|
||||
(_, Some((start_seconds, end_seconds)), _, _) => Some((
|
||||
((start_seconds * self.sample_rate as f64).round() as i64),
|
||||
((end_seconds * self.sample_rate as f64).round() as i64),
|
||||
)),
|
||||
(_, _, Some((start_beats, end_beats)), Some(tempo)) => Some((
|
||||
(start_beats / tempo * 60.0 * self.sample_rate as f64).round() as i64,
|
||||
(end_beats / tempo * 60.0 * self.sample_rate as f64).round() as i64,
|
||||
)),
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The loop range in seconds, if the loop is active and this information is available. None of
|
||||
/// the plugin API docs mention whether this is exclusive or inclusive, but just assume that the
|
||||
/// end is exclusive. Will be calculated from other information if needed.
|
||||
pub fn loop_range_seconds(&self) -> Option<(f64, f64)> {
|
||||
match (
|
||||
self.loop_range_samples,
|
||||
self.loop_range_seconds,
|
||||
self.loop_range_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(_, Some(loop_range_seconds), _, _) => Some(loop_range_seconds),
|
||||
(Some((start_samples, end_samples)), _, _, _) => Some((
|
||||
start_samples as f64 / self.sample_rate as f64,
|
||||
end_samples as f64 / self.sample_rate as f64,
|
||||
)),
|
||||
(_, _, Some((start_beats, end_beats)), Some(tempo)) => {
|
||||
Some((start_beats / tempo * 60.0, end_beats / tempo * 60.0))
|
||||
}
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The loop range in quarter notes, if the loop is active and this information is available.
|
||||
/// None of the plugin API docs mention whether this is exclusive or inclusive, but just assume
|
||||
/// that the end is exclusive. Will be calculated from other information if needed.
|
||||
pub fn loop_range_beats(&self) -> Option<(f64, f64)> {
|
||||
match (
|
||||
self.loop_range_samples,
|
||||
self.loop_range_seconds,
|
||||
self.loop_range_beats,
|
||||
self.tempo,
|
||||
) {
|
||||
(_, _, Some(loop_range_beats), _) => Some(loop_range_beats),
|
||||
(_, Some((start_seconds, end_seconds)), _, Some(tempo)) => {
|
||||
Some((start_seconds / 60.0 * tempo, end_seconds / 60.0 * tempo))
|
||||
}
|
||||
(Some((start_samples, end_samples)), _, _, Some(tempo)) => Some((
|
||||
start_samples as f64 / self.sample_rate as f64 / 60.0 * tempo,
|
||||
end_samples as f64 / self.sample_rate as f64 / 60.0 * tempo,
|
||||
)),
|
||||
(_, _, _, _) => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
|||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::context::GuiContext;
|
||||
use crate::context::gui::GuiContext;
|
||||
|
||||
/// An editor for a [`Plugin`][crate::prelude::Plugin].
|
||||
pub trait Editor: Send {
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::{InitContext, ProcessContext};
|
||||
use crate::context::init::InitContext;
|
||||
use crate::context::process::ProcessContext;
|
||||
use crate::editor::Editor;
|
||||
use crate::midi::MidiConfig;
|
||||
use crate::params::Params;
|
||||
|
|
|
@ -11,7 +11,9 @@ pub use crate::formatters;
|
|||
pub use crate::util;
|
||||
|
||||
pub use crate::buffer::Buffer;
|
||||
pub use crate::context::{GuiContext, InitContext, ParamSetter, PluginApi, ProcessContext};
|
||||
pub use crate::context::gui::{GuiContext, ParamSetter};
|
||||
pub use crate::context::init::InitContext;
|
||||
pub use crate::context::process::ProcessContext;
|
||||
// This also includes the derive macro
|
||||
pub use crate::editor::{Editor, ParentWindowHandle};
|
||||
pub use crate::midi::{control_change, MidiConfig, NoteEvent};
|
||||
|
|
|
@ -3,7 +3,10 @@ use std::collections::VecDeque;
|
|||
use std::sync::Arc;
|
||||
|
||||
use super::wrapper::{OutputParamEvent, Task, Wrapper};
|
||||
use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport};
|
||||
use crate::context::gui::GuiContext;
|
||||
use crate::context::init::InitContext;
|
||||
use crate::context::process::{ProcessContext, Transport};
|
||||
use crate::context::PluginApi;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::midi::NoteEvent;
|
||||
use crate::params::internals::ParamPtr;
|
||||
|
|
|
@ -77,7 +77,7 @@ use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContex
|
|||
use super::descriptor::PluginDescriptor;
|
||||
use super::util::ClapPtr;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::Transport;
|
||||
use crate::context::process::Transport;
|
||||
use crate::editor::{Editor, ParentWindowHandle};
|
||||
use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
|
||||
use crate::midi::{MidiConfig, NoteEvent};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::context::Transport;
|
||||
use crate::context::process::Transport;
|
||||
use crate::midi::NoteEvent;
|
||||
|
||||
mod cpal;
|
||||
|
|
|
@ -9,7 +9,7 @@ use rtrb::RingBuffer;
|
|||
use super::super::config::WrapperConfig;
|
||||
use super::Backend;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::Transport;
|
||||
use crate::context::process::Transport;
|
||||
use crate::midi::{MidiConfig, NoteEvent};
|
||||
use crate::plugin::{AuxiliaryIOConfig, BusConfig, Plugin};
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::time::{Duration, Instant};
|
|||
use super::super::config::WrapperConfig;
|
||||
use super::Backend;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::Transport;
|
||||
use crate::context::process::Transport;
|
||||
use crate::midi::NoteEvent;
|
||||
use crate::plugin::{AuxiliaryIOConfig, BusConfig, Plugin};
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use parking_lot::Mutex;
|
|||
use super::super::config::WrapperConfig;
|
||||
use super::Backend;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::Transport;
|
||||
use crate::context::process::Transport;
|
||||
use crate::midi::{MidiConfig, NoteEvent};
|
||||
use crate::plugin::Plugin;
|
||||
|
||||
|
|
|
@ -3,7 +3,10 @@ use std::sync::Arc;
|
|||
|
||||
use super::backend::Backend;
|
||||
use super::wrapper::{GuiTask, Wrapper};
|
||||
use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport};
|
||||
use crate::context::gui::GuiContext;
|
||||
use crate::context::init::InitContext;
|
||||
use crate::context::process::{ProcessContext, Transport};
|
||||
use crate::context::PluginApi;
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::midi::NoteEvent;
|
||||
use crate::params::internals::ParamPtr;
|
||||
|
|
|
@ -13,7 +13,7 @@ use std::thread;
|
|||
use super::backend::Backend;
|
||||
use super::config::WrapperConfig;
|
||||
use super::context::{WrapperGuiContext, WrapperInitContext, WrapperProcessContext};
|
||||
use crate::context::Transport;
|
||||
use crate::context::process::Transport;
|
||||
use crate::editor::{Editor, ParentWindowHandle};
|
||||
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
|
||||
use crate::midi::NoteEvent;
|
||||
|
|
|
@ -5,7 +5,10 @@ use std::sync::Arc;
|
|||
use vst3_sys::vst::IComponentHandler;
|
||||
|
||||
use super::inner::{Task, WrapperInner};
|
||||
use crate::context::{GuiContext, InitContext, PluginApi, ProcessContext, Transport};
|
||||
use crate::context::gui::GuiContext;
|
||||
use crate::context::init::InitContext;
|
||||
use crate::context::process::{ProcessContext, Transport};
|
||||
use crate::context::PluginApi;
|
||||
use crate::midi::NoteEvent;
|
||||
use crate::params::internals::ParamPtr;
|
||||
use crate::plugin::Vst3Plugin;
|
||||
|
|
|
@ -15,7 +15,7 @@ use super::param_units::ParamUnits;
|
|||
use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START};
|
||||
use super::view::WrapperView;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::Transport;
|
||||
use crate::context::process::Transport;
|
||||
use crate::editor::Editor;
|
||||
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
|
||||
use crate::midi::{MidiConfig, NoteEvent};
|
||||
|
|
|
@ -24,7 +24,7 @@ use super::util::{
|
|||
};
|
||||
use super::view::WrapperView;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::Transport;
|
||||
use crate::context::process::Transport;
|
||||
use crate::midi::{MidiConfig, NoteEvent};
|
||||
use crate::params::ParamFlags;
|
||||
use crate::plugin::{
|
||||
|
|
Loading…
Reference in a new issue