Skip to main content

truce_core/
process.rs

1use crate::config::ProcessMode;
2use crate::events::{EventList, TransportInfo};
3use crate::tasks::{AnyTaskSpawner, TaskSpawner};
4
5/// Per-block context handed to `process()`. Construct via
6/// [`Self::new`] + the `with_*` builders. Marked `#[non_exhaustive]`
7/// so adding host-populated fields in future (e.g. `host_latency`,
8/// `bus_routing`) isn't a `SemVer` break for downstream pre-1.0 callers.
9#[non_exhaustive]
10pub struct ProcessContext<'a> {
11    pub transport: &'a TransportInfo,
12    /// How the host is driving audio this block. Tracks host toggles
13    /// that don't force a re-prepare (VST3 `kRealtime` <-> `kPrefetch`,
14    /// an LV2 freewheel port). A plugin that reallocates for offline
15    /// keys off `AudioConfig::process_mode` at `reset` instead; this
16    /// field is for "may I relax realtime discipline right now?".
17    pub process_mode: ProcessMode,
18    pub sample_rate: f64,
19    pub block_size: usize,
20    pub output_events: &'a mut EventList,
21    params_fn: Option<&'a dyn Fn(u32) -> f64>,
22    meters_fn: Option<&'a dyn Fn(u32, f32)>,
23    /// Type-erased handle to this instance's background-task spawner,
24    /// installed by the shell when the plugin wired `tasks:` on
25    /// `plugin!`. Recover a typed spawner with [`Self::tasks`].
26    tasks: Option<&'a AnyTaskSpawner>,
27}
28
29impl<'a> ProcessContext<'a> {
30    pub fn new(
31        transport: &'a TransportInfo,
32        sample_rate: f64,
33        block_size: usize,
34        output_events: &'a mut EventList,
35    ) -> Self {
36        Self {
37            transport,
38            process_mode: ProcessMode::Realtime,
39            sample_rate,
40            block_size,
41            output_events,
42            params_fn: None,
43            meters_fn: None,
44            tasks: None,
45        }
46    }
47
48    /// Set the processing mode for this block. Defaults to
49    /// [`ProcessMode::Realtime`]; wrappers stamp the live host mode.
50    #[must_use]
51    pub fn with_process_mode(mut self, mode: ProcessMode) -> Self {
52        self.process_mode = mode;
53        self
54    }
55
56    /// Set the parameter lookup callback.
57    #[must_use]
58    pub fn with_params(mut self, f: &'a dyn Fn(u32) -> f64) -> Self {
59        self.params_fn = Some(f);
60        self
61    }
62
63    /// Set the meter reporting callback.
64    #[must_use]
65    pub fn with_meters(mut self, f: &'a dyn Fn(u32, f32)) -> Self {
66        self.meters_fn = Some(f);
67        self
68    }
69
70    /// Install the background-task spawner for this block. The shell
71    /// stamps it when the plugin wired `tasks:` on `plugin!`.
72    #[must_use]
73    pub fn with_tasks(mut self, tasks: &'a AnyTaskSpawner) -> Self {
74        self.tasks = Some(tasks);
75        self
76    }
77
78    /// The background-task spawner for task type `T`, or `None` if the
79    /// plugin declared no `tasks:` lane of that type. Scheduling with the
80    /// returned spawner is wait-free (see
81    /// [`TaskSpawner::try_spawn`] / [`TaskSpawner::spawn_coalescing`]), so
82    /// it is safe to call from the audio thread.
83    #[must_use]
84    pub fn tasks<T: Send + 'static>(&self) -> Option<TaskSpawner<T>> {
85        self.tasks.and_then(AnyTaskSpawner::downcast::<T>)
86    }
87
88    /// Read a parameter's plain value by ID.
89    ///
90    /// Returns `None` when no params callback is wired up (e.g. when a
91    /// plugin runs under the bare test driver without a `with_params`
92    /// closure). Callers that always run inside a real format wrapper
93    /// can `.unwrap_or_default()`. Distinguishing "no callback" from
94    /// "value is zero" lets test harnesses notice when they forgot to
95    /// wire up params rather than masking the misconfiguration as
96    /// "host set the value to zero".
97    #[must_use]
98    pub fn param(&self, id: u32) -> Option<f64> {
99        self.params_fn.map(|f| f(id))
100    }
101
102    /// Report a meter value (0.0 to 1.0).
103    pub fn set_meter(&self, id: impl Into<u32>, value: f32) {
104        let id = id.into();
105        if let Some(f) = self.meters_fn {
106            f(id, value);
107        }
108    }
109}
110
111#[derive(Clone, Copy, Debug, PartialEq)]
112pub enum ProcessStatus {
113    /// Plugin produced meaningful output.
114    Normal,
115    /// Plugin is producing tail. Value = remaining tail samples.
116    Tail(u32),
117    /// Keep alive even if input is silent.
118    KeepAlive,
119}