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}