Add transport information for VST3 and CLAP
This is available through the process context.
This commit is contained in:
parent
713e778117
commit
e2605c8cee
7 changed files with 155 additions and 15 deletions
|
@ -15,6 +15,9 @@ use crate::plugin::NoteEvent;
|
|||
// 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 {
|
||||
/// Get information about the current transport position and status.
|
||||
fn transport(&self) -> &Transport;
|
||||
|
||||
/// Return the next note event, if there is one. The event contains the timing
|
||||
///
|
||||
/// TODO: Rethink this API, both in terms of ergonomics, and if we can do this in a way that
|
||||
|
@ -82,7 +85,7 @@ pub trait GuiContext: Send + Sync + 'static {
|
|||
|
||||
/// Information about the plugin's transport. Depending on the plugin API and the host not all
|
||||
/// fields may be available.
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct Transport {
|
||||
/// Whether the transport is currently running.
|
||||
pub playing: bool,
|
||||
|
@ -144,6 +147,30 @@ pub struct ParamSetter<'a> {
|
|||
|
||||
// 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 (
|
||||
|
|
|
@ -18,7 +18,6 @@ use crate::param::internals::Params;
|
|||
/// - Sidechain inputs
|
||||
/// - Multiple output busses
|
||||
/// - Special handling for offline processing
|
||||
/// - Transport and other context information in the process call
|
||||
/// - Sample accurate automation (this would be great, but sadly few hosts even support it so until
|
||||
/// they do we'll ignore that it's a thing)
|
||||
/// - Parameter hierarchies/groups
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::sync::atomic::Ordering;
|
|||
use std::sync::Arc;
|
||||
|
||||
use super::wrapper::{OutputParamChange, Task, Wrapper};
|
||||
use crate::context::{GuiContext, ProcessContext};
|
||||
use crate::context::{GuiContext, ProcessContext, Transport};
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::param::internals::ParamPtr;
|
||||
use crate::plugin::{ClapPlugin, NoteEvent};
|
||||
|
@ -22,6 +22,7 @@ pub(crate) struct WrapperGuiContext<P: ClapPlugin> {
|
|||
pub(crate) struct WrapperProcessContext<'a, P: ClapPlugin> {
|
||||
pub(super) wrapper: &'a Wrapper<P>,
|
||||
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
|
||||
pub(super) transport: Transport,
|
||||
}
|
||||
|
||||
impl<P: ClapPlugin> GuiContext for WrapperGuiContext<P> {
|
||||
|
@ -71,6 +72,10 @@ impl<P: ClapPlugin> GuiContext for WrapperGuiContext<P> {
|
|||
}
|
||||
|
||||
impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
|
||||
fn transport(&self) -> &Transport {
|
||||
&self.transport
|
||||
}
|
||||
|
||||
fn next_midi_event(&mut self) -> Option<NoteEvent> {
|
||||
self.input_events_guard.pop_front()
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@ use clap_sys::events::{
|
|||
clap_event_header, clap_event_note, clap_event_param_mod, clap_event_param_value,
|
||||
clap_input_events, clap_output_events, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_MIDI,
|
||||
CLAP_EVENT_NOTE_EXPRESSION, CLAP_EVENT_NOTE_OFF, CLAP_EVENT_NOTE_ON, CLAP_EVENT_PARAM_MOD,
|
||||
CLAP_EVENT_PARAM_VALUE, CLAP_EVENT_SHOULD_RECORD,
|
||||
CLAP_EVENT_PARAM_VALUE, CLAP_EVENT_SHOULD_RECORD, CLAP_TRANSPORT_HAS_BEATS_TIMELINE,
|
||||
CLAP_TRANSPORT_HAS_SECONDS_TIMELINE, CLAP_TRANSPORT_HAS_TEMPO,
|
||||
CLAP_TRANSPORT_HAS_TIME_SIGNATURE, CLAP_TRANSPORT_IS_LOOP_ACTIVE, CLAP_TRANSPORT_IS_PLAYING,
|
||||
CLAP_TRANSPORT_IS_RECORDING, CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL,
|
||||
};
|
||||
use clap_sys::ext::audio_ports::{
|
||||
clap_audio_port_info, clap_plugin_audio_ports, CLAP_AUDIO_PORT_IS_MAIN, CLAP_EXT_AUDIO_PORTS,
|
||||
|
@ -24,6 +27,7 @@ use clap_sys::ext::params::{
|
|||
};
|
||||
use clap_sys::ext::state::{clap_plugin_state, CLAP_EXT_STATE};
|
||||
use clap_sys::ext::thread_check::{clap_host_thread_check, CLAP_EXT_THREAD_CHECK};
|
||||
use clap_sys::fixedpoint::{CLAP_BEATTIME_FACTOR, CLAP_SECTIME_FACTOR};
|
||||
use clap_sys::host::clap_host;
|
||||
use clap_sys::id::{clap_id, CLAP_INVALID_ID};
|
||||
use clap_sys::plugin::clap_plugin;
|
||||
|
@ -59,6 +63,7 @@ use super::context::{WrapperGuiContext, WrapperProcessContext};
|
|||
use super::descriptor::PluginDescriptor;
|
||||
use super::util::ClapPtr;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::Transport;
|
||||
use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
|
||||
use crate::param::internals::ParamPtr;
|
||||
use crate::plugin::{
|
||||
|
@ -487,10 +492,11 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
Arc::new(WrapperGuiContext { wrapper: self })
|
||||
}
|
||||
|
||||
fn make_process_context(&self) -> WrapperProcessContext<'_, P> {
|
||||
fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P> {
|
||||
WrapperProcessContext {
|
||||
wrapper: self,
|
||||
input_events_guard: self.input_events.borrow_mut(),
|
||||
transport,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -731,7 +737,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
if wrapper.plugin.write().initialize(
|
||||
&bus_config,
|
||||
&buffer_config,
|
||||
&mut wrapper.make_process_context(),
|
||||
&mut wrapper.make_process_context(Transport::new(buffer_config.sample_rate)),
|
||||
) {
|
||||
// Preallocate enough room in the output slices vector so we can convert a `*mut *mut
|
||||
// f32` to a `&mut [&mut f32]` in the process call
|
||||
|
@ -864,8 +870,63 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
}
|
||||
}
|
||||
|
||||
// Some of the fields are left empty because CLAP does not provide this information, but
|
||||
// the methods on [`Transport`] can reconstruct these values from the other fields
|
||||
let sample_rate = wrapper
|
||||
.current_buffer_config
|
||||
.load()
|
||||
.expect("Process call without prior initialization call")
|
||||
.sample_rate;
|
||||
let mut transport = Transport::new(sample_rate);
|
||||
if !process.transport.is_null() {
|
||||
let context = &*process.transport;
|
||||
|
||||
transport.playing = context.flags & CLAP_TRANSPORT_IS_PLAYING != 0;
|
||||
transport.recording = context.flags & CLAP_TRANSPORT_IS_RECORDING != 0;
|
||||
transport.preroll_active =
|
||||
Some(context.flags & CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL != 0);
|
||||
if context.flags & CLAP_TRANSPORT_HAS_TEMPO != 0 {
|
||||
transport.tempo = Some(context.tempo);
|
||||
}
|
||||
if context.flags & CLAP_TRANSPORT_HAS_TIME_SIGNATURE != 0 {
|
||||
transport.time_sig_numerator = Some(context.tsig_num as i32);
|
||||
transport.time_sig_denominator = Some(context.tsig_denom as i32);
|
||||
}
|
||||
if context.flags & CLAP_TRANSPORT_HAS_BEATS_TIMELINE != 0 {
|
||||
transport.pos_beats =
|
||||
Some(context.song_pos_beats as f64 / CLAP_BEATTIME_FACTOR as f64);
|
||||
}
|
||||
if context.flags & CLAP_TRANSPORT_HAS_SECONDS_TIMELINE != 0 {
|
||||
transport.pos_seconds =
|
||||
Some(context.song_pos_seconds as f64 / CLAP_SECTIME_FACTOR as f64);
|
||||
}
|
||||
// TODO: CLAP does not mention whether this is behind a flag or not
|
||||
transport.bar_start_pos_beats =
|
||||
Some(context.bar_start as f64 / CLAP_BEATTIME_FACTOR as f64);
|
||||
transport.bar_number = Some(context.bar_number);
|
||||
// TODO: They also aren't very clear about this, but presumably if the loop is
|
||||
// active and the corresponding song transport information is available then
|
||||
// this is also available
|
||||
if context.flags & CLAP_TRANSPORT_IS_LOOP_ACTIVE != 0
|
||||
&& context.flags & CLAP_TRANSPORT_HAS_BEATS_TIMELINE != 0
|
||||
{
|
||||
transport.loop_range_beats = Some((
|
||||
context.loop_start_beats as f64 / CLAP_BEATTIME_FACTOR as f64,
|
||||
context.loop_end_beats as f64 / CLAP_BEATTIME_FACTOR as f64,
|
||||
));
|
||||
}
|
||||
if context.flags & CLAP_TRANSPORT_IS_LOOP_ACTIVE != 0
|
||||
&& context.flags & CLAP_TRANSPORT_HAS_SECONDS_TIMELINE != 0
|
||||
{
|
||||
transport.loop_range_seconds = Some((
|
||||
context.loop_start_seconds as f64 / CLAP_SECTIME_FACTOR as f64,
|
||||
context.loop_end_seconds as f64 / CLAP_SECTIME_FACTOR as f64,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut plugin = wrapper.plugin.write();
|
||||
let mut context = wrapper.make_process_context();
|
||||
let mut context = wrapper.make_process_context(transport);
|
||||
let result = match plugin.process(&mut output_buffer, &mut context) {
|
||||
ProcessStatus::Error(err) => {
|
||||
nih_debug_assert_failure!("Process error: {}", err);
|
||||
|
@ -1606,7 +1667,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
wrapper.plugin.write().initialize(
|
||||
&bus_config,
|
||||
&buffer_config,
|
||||
&mut wrapper.make_process_context(),
|
||||
&mut wrapper.make_process_context(Transport::new(buffer_config.sample_rate)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::sync::Arc;
|
|||
use vst3_sys::vst::IComponentHandler;
|
||||
|
||||
use super::inner::{Task, WrapperInner};
|
||||
use crate::context::{GuiContext, ProcessContext};
|
||||
use crate::context::{GuiContext, ProcessContext, Transport};
|
||||
use crate::event_loop::EventLoop;
|
||||
use crate::param::internals::ParamPtr;
|
||||
use crate::plugin::{NoteEvent, Vst3Plugin};
|
||||
|
@ -23,6 +23,7 @@ pub(crate) struct WrapperGuiContext<P: Vst3Plugin> {
|
|||
pub(crate) struct WrapperProcessContext<'a, P: Vst3Plugin> {
|
||||
pub(super) inner: &'a WrapperInner<P>,
|
||||
pub(super) input_events_guard: AtomicRefMut<'a, VecDeque<NoteEvent>>,
|
||||
pub(super) transport: Transport,
|
||||
}
|
||||
|
||||
impl<P: Vst3Plugin> GuiContext for WrapperGuiContext<P> {
|
||||
|
@ -91,6 +92,10 @@ impl<P: Vst3Plugin> GuiContext for WrapperGuiContext<P> {
|
|||
}
|
||||
|
||||
impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {
|
||||
fn transport(&self) -> &Transport {
|
||||
&self.transport
|
||||
}
|
||||
|
||||
fn next_midi_event(&mut self) -> Option<NoteEvent> {
|
||||
self.input_events_guard.pop_front()
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use super::context::{WrapperGuiContext, WrapperProcessContext};
|
|||
use super::util::{ObjectPtr, VstPtr, BYPASS_PARAM_HASH, BYPASS_PARAM_ID};
|
||||
use super::view::WrapperView;
|
||||
use crate::buffer::Buffer;
|
||||
use crate::context::Transport;
|
||||
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
|
||||
use crate::param::internals::ParamPtr;
|
||||
use crate::plugin::{BufferConfig, BusConfig, Editor, NoteEvent, ProcessStatus, Vst3Plugin};
|
||||
|
@ -193,10 +194,11 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
|||
Arc::new(WrapperGuiContext { inner: self })
|
||||
}
|
||||
|
||||
pub fn make_process_context(&self) -> WrapperProcessContext<'_, P> {
|
||||
pub fn make_process_context(&self, transport: Transport) -> WrapperProcessContext<'_, P> {
|
||||
WrapperProcessContext {
|
||||
inner: self,
|
||||
input_events_guard: self.input_events.borrow_mut(),
|
||||
transport,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ use widestring::U16CStr;
|
|||
use super::inner::WrapperInner;
|
||||
use super::util::{VstPtr, BYPASS_PARAM_HASH, BYPASS_PARAM_ID};
|
||||
use super::view::WrapperView;
|
||||
use crate::context::Transport;
|
||||
use crate::plugin::{BufferConfig, BusConfig, NoteEvent, ProcessStatus, Vst3Plugin};
|
||||
use crate::wrapper::state;
|
||||
use crate::wrapper::util::{process_wrapper, u16strlcpy};
|
||||
|
@ -236,7 +237,9 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
self.inner.plugin.write().initialize(
|
||||
&bus_config,
|
||||
&buffer_config,
|
||||
&mut self.inner.make_process_context(),
|
||||
&mut self
|
||||
.inner
|
||||
.make_process_context(Transport::new(buffer_config.sample_rate)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -593,7 +596,9 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
if self.inner.plugin.write().initialize(
|
||||
&bus_config,
|
||||
&buffer_config,
|
||||
&mut self.inner.make_process_context(),
|
||||
&mut self
|
||||
.inner
|
||||
.make_process_context(Transport::new(buffer_config.sample_rate)),
|
||||
) {
|
||||
// Preallocate enough room in the output slices vector so we can convert a `*mut *mut
|
||||
// f32` to a `&mut [&mut f32]` in the process call
|
||||
|
@ -634,7 +639,8 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
.inner
|
||||
.current_buffer_config
|
||||
.load()
|
||||
.map(|c| c.sample_rate);
|
||||
.expect("Process call without prior setup call")
|
||||
.sample_rate;
|
||||
if let Some(param_changes) = data.input_param_changes.upgrade() {
|
||||
let num_param_queues = param_changes.get_parameter_count();
|
||||
for change_queue_idx in 0..num_param_queues {
|
||||
|
@ -658,7 +664,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
self.inner.set_normalized_value_by_hash(
|
||||
param_hash,
|
||||
value as f32,
|
||||
sample_rate,
|
||||
Some(sample_rate),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -771,8 +777,43 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
|||
}
|
||||
}
|
||||
|
||||
// Some of the fields are left empty because VST3 does not provide this information, but
|
||||
// the methods on [`Transport`] can reconstruct these values from the other fields
|
||||
let mut transport = Transport::new(sample_rate);
|
||||
if !data.context.is_null() {
|
||||
let context = &*data.context;
|
||||
|
||||
// These constants are missing from vst3-sys, see:
|
||||
// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1ProcessContext.html
|
||||
transport.playing = context.state & (1 << 1) != 0; // kPlaying
|
||||
transport.recording = context.state & (1 << 3) != 0; // kRecording
|
||||
if context.state & (1 << 10) != 0 {
|
||||
// kTempoValid
|
||||
transport.tempo = Some(context.tempo);
|
||||
}
|
||||
if context.state & (1 << 13) != 0 {
|
||||
// kTimeSigValid
|
||||
transport.time_sig_numerator = Some(context.time_sig_num);
|
||||
transport.time_sig_denominator = Some(context.time_sig_den);
|
||||
}
|
||||
transport.pos_samples = Some(context.project_time_samples);
|
||||
if context.state & (1 << 9) != 0 {
|
||||
// kProjectTimeMusicValid
|
||||
transport.pos_beats = Some(context.project_time_music);
|
||||
}
|
||||
if context.state & (1 << 11) != 0 {
|
||||
// kBarPositionValid
|
||||
transport.bar_start_pos_beats = Some(context.bar_position_music);
|
||||
}
|
||||
if context.state & (1 << 2) != 0 && context.state & (1 << 12) != 0 {
|
||||
// kCycleActive && kCycleValid
|
||||
transport.loop_range_beats =
|
||||
Some((context.cycle_start_music, context.cycle_end_music));
|
||||
}
|
||||
}
|
||||
|
||||
let mut plugin = self.inner.plugin.write();
|
||||
let mut context = self.inner.make_process_context();
|
||||
let mut context = self.inner.make_process_context(transport);
|
||||
match plugin.process(&mut output_buffer, &mut context) {
|
||||
ProcessStatus::Error(err) => {
|
||||
nih_debug_assert_failure!("Process error: {}", err);
|
||||
|
|
Loading…
Add table
Reference in a new issue