From 665108721a88a1dedca5ad1cb9e378d4e3444583 Mon Sep 17 00:00:00 2001
From: Robbert van der Helm <mail@robbertvanderhelm.nl>
Date: Sun, 22 May 2022 00:58:53 +0200
Subject: [PATCH] Add a way to fetch the current processing mode

---
 src/context.rs                    | 19 ++++++++++++++
 src/prelude.rs                    |  2 +-
 src/wrapper/clap/context.rs       |  6 ++++-
 src/wrapper/clap/wrapper.rs       | 42 ++++++++++++++++++++++++++++++-
 src/wrapper/standalone/context.rs |  7 +++++-
 src/wrapper/vst3/context.rs       |  4 +++
 src/wrapper/vst3/inner.rs         |  5 +++-
 src/wrapper/vst3/wrapper.rs       | 15 +++++++++--
 8 files changed, 93 insertions(+), 7 deletions(-)

diff --git a/src/context.rs b/src/context.rs
index 51cbfad2..555658e1 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -24,6 +24,9 @@ pub trait ProcessContext {
     /// Get information about the current transport position and status.
     fn transport(&self) -> &Transport;
 
+    /// The current processing mode. The host will reinitialize the plugin any time this changes.
+    fn process_mode(&self) -> ProcessMode;
+
     /// 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.
@@ -204,6 +207,22 @@ pub enum PluginApi {
     Vst3,
 }
 
+/// The plugin's current processing mode. Can be queried through [`ProcessContext::process_mode()`].
+/// The host will reinitialize the plugin whenever this changes.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ProcessMode {
+    /// The plugin is processing audio in real time at a fixed rate.
+    Realtime,
+    /// The plugin is processing audio at a real time-like pace, but at irregular intervals. The
+    /// host may do this to process audio ahead of time to loosen realtime constraints and to reduce
+    /// the chance of xruns happening. This is only used by VST3.
+    Buffered,
+    /// The plugin is rendering audio offline, potentially faster than realtime ('freewheeling').
+    /// The host will continuously call the process function back to back until all audio has been
+    /// processed.
+    Offline,
+}
+
 impl Display for PluginApi {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
diff --git a/src/prelude.rs b/src/prelude.rs
index e84c10fd..38c52260 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -11,7 +11,7 @@ pub use crate::formatters;
 pub use crate::util;
 
 pub use crate::buffer::Buffer;
-pub use crate::context::{GuiContext, ParamSetter, PluginApi, ProcessContext};
+pub use crate::context::{GuiContext, ParamSetter, PluginApi, ProcessContext, ProcessMode};
 // This also includes the derive macro
 pub use crate::midi::{control_change, MidiConfig, NoteEvent};
 pub use crate::param::enums::{Enum, EnumParam};
diff --git a/src/wrapper/clap/context.rs b/src/wrapper/clap/context.rs
index 3947f70a..5b383705 100644
--- a/src/wrapper/clap/context.rs
+++ b/src/wrapper/clap/context.rs
@@ -4,7 +4,7 @@ use std::sync::atomic::Ordering;
 use std::sync::Arc;
 
 use super::wrapper::{OutputParamEvent, Task, Wrapper};
-use crate::context::{GuiContext, PluginApi, ProcessContext, Transport};
+use crate::context::{GuiContext, PluginApi, ProcessContext, ProcessMode, Transport};
 use crate::event_loop::EventLoop;
 use crate::midi::NoteEvent;
 use crate::param::internals::ParamPtr;
@@ -104,6 +104,10 @@ impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {
         &self.transport
     }
 
+    fn process_mode(&self) -> ProcessMode {
+        self.wrapper.current_process_mode.load()
+    }
+
     fn next_event(&mut self) -> Option<NoteEvent> {
         self.input_events_guard.pop_front()
     }
diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs
index e90ef638..13c819ab 100644
--- a/src/wrapper/clap/wrapper.rs
+++ b/src/wrapper/clap/wrapper.rs
@@ -41,6 +41,9 @@ use clap_sys::ext::params::{
     CLAP_PARAM_IS_MODULATABLE, CLAP_PARAM_IS_READONLY, CLAP_PARAM_IS_STEPPED,
     CLAP_PARAM_RESCAN_VALUES,
 };
+use clap_sys::ext::render::{
+    clap_plugin_render, clap_plugin_render_mode, CLAP_RENDER_OFFLINE, CLAP_RENDER_REALTIME,
+};
 use clap_sys::ext::state::{clap_plugin_state, CLAP_EXT_STATE};
 use clap_sys::ext::tail::{clap_plugin_tail, CLAP_EXT_TAIL};
 use clap_sys::ext::thread_check::{clap_host_thread_check, CLAP_EXT_THREAD_CHECK};
@@ -75,7 +78,7 @@ use super::context::{WrapperGuiContext, WrapperProcessContext};
 use super::descriptor::PluginDescriptor;
 use super::util::ClapPtr;
 use crate::buffer::Buffer;
-use crate::context::Transport;
+use crate::context::{ProcessMode, Transport};
 use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY};
 use crate::midi::{MidiConfig, NoteEvent};
 use crate::param::internals::{ParamPtr, Params};
@@ -125,6 +128,8 @@ pub struct Wrapper<P: ClapPlugin> {
     /// The current buffer configuration, containing the sample rate and the maximum block size.
     /// Will be set in `clap_plugin::activate()`.
     current_buffer_config: AtomicCell<Option<BufferConfig>>,
+    /// The current audio processing mode. Set through the render extension. Defaults to realtime.
+    pub current_process_mode: AtomicCell<ProcessMode>,
     /// The incoming events for the plugin, if `P::MIDI_INPUT` is set to `MidiConfig::Basic` or
     /// higher.
     ///
@@ -217,6 +222,8 @@ pub struct Wrapper<P: ClapPlugin> {
 
     host_thread_check: AtomicRefCell<Option<ClapPtr<clap_host_thread_check>>>,
 
+    clap_plugin_render: clap_plugin_render,
+
     clap_plugin_state: clap_plugin_state,
 
     clap_plugin_tail: clap_plugin_tail,
@@ -481,6 +488,7 @@ impl<P: ClapPlugin> Wrapper<P> {
                 num_output_channels: P::DEFAULT_NUM_OUTPUTS,
             }),
             current_buffer_config: AtomicCell::new(None),
+            current_process_mode: AtomicCell::new(ProcessMode::Realtime),
             input_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
             output_events: AtomicRefCell::new(VecDeque::with_capacity(512)),
             last_process_status: AtomicCell::new(ProcessStatus::Normal),
@@ -556,6 +564,11 @@ impl<P: ClapPlugin> Wrapper<P> {
 
             host_thread_check: AtomicRefCell::new(None),
 
+            clap_plugin_render: clap_plugin_render {
+                has_hard_realtime_requirement: Self::ext_render_has_hard_realtime_requirement,
+                set: Self::ext_render_set,
+            },
+
             clap_plugin_state: clap_plugin_state {
                 save: Self::ext_state_save,
                 load: Self::ext_state_load,
@@ -2620,6 +2633,33 @@ impl<P: ClapPlugin> Wrapper<P> {
         }
     }
 
+    unsafe extern "C" fn ext_render_has_hard_realtime_requirement(
+        _plugin: *const clap_plugin,
+    ) -> bool {
+        // TODO: Add a constant on the CLapPlugin trait
+        false
+    }
+
+    unsafe extern "C" fn ext_render_set(
+        plugin: *const clap_plugin,
+        mode: clap_plugin_render_mode,
+    ) -> bool {
+        check_null_ptr!(false, plugin);
+        let wrapper = &*(plugin as *const Self);
+
+        let mode = match mode {
+            CLAP_RENDER_REALTIME => ProcessMode::Realtime,
+            CLAP_RENDER_OFFLINE => ProcessMode::Offline,
+            n => {
+                nih_debug_assert_failure!("Unknown rendering mode '{}', defaulting to realtime", n);
+                ProcessMode::Realtime
+            }
+        };
+        wrapper.current_process_mode.store(mode);
+
+        true
+    }
+
     unsafe extern "C" fn ext_state_save(
         plugin: *const clap_plugin,
         stream: *const clap_ostream,
diff --git a/src/wrapper/standalone/context.rs b/src/wrapper/standalone/context.rs
index 46fc0032..73cdb453 100644
--- a/src/wrapper/standalone/context.rs
+++ b/src/wrapper/standalone/context.rs
@@ -3,7 +3,7 @@ use std::sync::Arc;
 
 use super::backend::Backend;
 use super::wrapper::{GuiTask, Wrapper};
-use crate::context::{GuiContext, PluginApi, ProcessContext, Transport};
+use crate::context::{GuiContext, PluginApi, ProcessContext, ProcessMode, Transport};
 use crate::midi::NoteEvent;
 use crate::param::internals::ParamPtr;
 use crate::plugin::Plugin;
@@ -81,6 +81,11 @@ impl<P: Plugin, B: Backend> ProcessContext for WrapperProcessContext<'_, P, B> {
         &self.transport
     }
 
+    fn process_mode(&self) -> ProcessMode {
+        // TODO: Detect JACK freewheeling and report it here
+        ProcessMode::Realtime
+    }
+
     fn next_event(&mut self) -> Option<NoteEvent> {
         nih_debug_assert_failure!("TODO: WrapperProcessContext::next_event()");
 
diff --git a/src/wrapper/vst3/context.rs b/src/wrapper/vst3/context.rs
index 36a05f30..224eec13 100644
--- a/src/wrapper/vst3/context.rs
+++ b/src/wrapper/vst3/context.rs
@@ -117,6 +117,10 @@ impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {
         &self.transport
     }
 
+    fn process_mode(&self) -> crate::context::ProcessMode {
+        self.inner.current_process_mode.load()
+    }
+
     fn next_event(&mut self) -> Option<NoteEvent> {
         self.input_events_guard.pop_front()
     }
diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs
index 8442fd5c..20d1b62a 100644
--- a/src/wrapper/vst3/inner.rs
+++ b/src/wrapper/vst3/inner.rs
@@ -16,7 +16,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::{ProcessMode, Transport};
 use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
 use crate::midi::{MidiConfig, NoteEvent};
 use crate::param::internals::{ParamPtr, Params};
@@ -67,6 +67,8 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
     /// The current buffer configuration, containing the sample rate and the maximum block size.
     /// Will be set in `IAudioProcessor::setupProcessing()`.
     pub current_buffer_config: AtomicCell<Option<BufferConfig>>,
+    /// The current audio processing mode. Set in `IAudioProcessor::setup_processing()`.
+    pub current_process_mode: AtomicCell<ProcessMode>,
     /// The last process status returned by the plugin. This is used for tail handling.
     pub last_process_status: AtomicCell<ProcessStatus>,
     /// The current latency in samples, as set by the plugin through the [`ProcessContext`].
@@ -277,6 +279,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
                 num_output_channels: P::DEFAULT_NUM_OUTPUTS,
             }),
             current_buffer_config: AtomicCell::new(None),
+            current_process_mode: AtomicCell::new(ProcessMode::Realtime),
             last_process_status: AtomicCell::new(ProcessStatus::Normal),
             current_latency: AtomicU32::new(0),
             output_buffer: AtomicRefCell::new(Buffer::default()),
diff --git a/src/wrapper/vst3/wrapper.rs b/src/wrapper/vst3/wrapper.rs
index 28722dfa..8afc8dd6 100644
--- a/src/wrapper/vst3/wrapper.rs
+++ b/src/wrapper/vst3/wrapper.rs
@@ -4,7 +4,7 @@ use std::mem::{self, MaybeUninit};
 use std::ptr;
 use std::sync::atomic::Ordering;
 use std::sync::Arc;
-use vst3_com::vst::IProcessContextRequirementsFlags;
+use vst3_com::vst::{IProcessContextRequirementsFlags, ProcessModes};
 use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, TBool};
 use vst3_sys::base::{IBStream, IPluginBase};
 use vst3_sys::utils::SharedVstPtr;
@@ -23,7 +23,7 @@ use super::util::{
     u16strlcpy, VstPtr, VST3_MIDI_CCS, VST3_MIDI_NUM_PARAMS, VST3_MIDI_PARAMS_START,
 };
 use super::view::WrapperView;
-use crate::context::Transport;
+use crate::context::{ProcessMode, Transport};
 use crate::midi::{MidiConfig, NoteEvent};
 use crate::param::ParamFlags;
 use crate::plugin::{BufferConfig, BusConfig, ProcessStatus, Vst3Plugin};
@@ -690,6 +690,17 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
             max_buffer_size: setup.max_samples_per_block as u32,
         }));
 
+        let mode = match setup.process_mode {
+            n if n == ProcessModes::kRealtime as i32 => ProcessMode::Realtime,
+            n if n == ProcessModes::kPrefetch as i32 => ProcessMode::Buffered,
+            n if n == ProcessModes::kOffline as i32 => ProcessMode::Offline,
+            n => {
+                nih_debug_assert_failure!("Unknown rendering mode '{}', defaulting to realtime", n);
+                ProcessMode::Realtime
+            }
+        };
+        self.inner.current_process_mode.store(mode);
+
         // Initializing the plugin happens in `IAudioProcessor::set_active()` because the host may
         // still change the channel layouts at this point