truce_core/config.rs
1//! Activation-time processing configuration.
2//!
3//! [`AudioConfig`] is the config struct handed to `reset` when the host
4//! (re)prepares the plugin. It carries the sample rate, the maximum block
5//! size, and the [`ProcessMode`] the host is driving audio with, so a
6//! plugin can size its buffers for an offline render before the first
7//! block arrives - allocation has to happen here, off the audio thread.
8
9/// How the host is driving audio through the plugin this activation.
10///
11/// Delivered two ways that answer two different questions.
12/// [`AudioConfig::process_mode`] at `reset` answers "how big should my
13/// buffers be for this render?" - the allocation-relevant question, since
14/// buffer sizing has to happen off the audio thread. The per-block
15/// `ProcessContext::process_mode` answers "may I skip the realtime
16/// discipline right now?" - it tracks host toggles that don't warrant a
17/// re-prepare (VST3 `kRealtime` <-> `kPrefetch`, an LV2 freewheel port).
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
19pub enum ProcessMode {
20 /// Fixed-rate realtime playback. Honor the no-alloc / no-lock rule.
21 #[default]
22 Realtime,
23 /// Real-time-like but processed ahead at irregular intervals to
24 /// loosen realtime pressure. Only VST3 (`kPrefetch`) produces this.
25 /// Treat it like [`Realtime`](Self::Realtime) unless there is a
26 /// specific reason to relax discipline.
27 Buffered,
28 /// Freewheeling offline render. No wall-clock deadline: a plugin may
29 /// allocate, raise oversampling, lengthen lookahead, and trade CPU
30 /// for quality.
31 Offline,
32}
33
34impl ProcessMode {
35 /// Whether the host is freewheeling with no realtime deadline. True
36 /// only for [`Offline`](Self::Offline) - the one mode where relaxing
37 /// the no-alloc / no-lock rule and raising quality is safe.
38 #[must_use]
39 pub fn is_offline(self) -> bool {
40 matches!(self, ProcessMode::Offline)
41 }
42
43 /// Discriminant for atomic storage. Wrappers whose offline signal
44 /// arrives on one thread (CLAP `render::set`, an AU property) and is
45 /// read on the audio thread stash the mode in an `AtomicU8`.
46 #[must_use]
47 pub fn as_u8(self) -> u8 {
48 match self {
49 ProcessMode::Realtime => 0,
50 ProcessMode::Buffered => 1,
51 ProcessMode::Offline => 2,
52 }
53 }
54
55 /// Inverse of [`Self::as_u8`]. Any unknown value maps to
56 /// [`Realtime`](Self::Realtime), the safe default.
57 #[must_use]
58 pub fn from_u8(v: u8) -> Self {
59 match v {
60 1 => ProcessMode::Buffered,
61 2 => ProcessMode::Offline,
62 _ => ProcessMode::Realtime,
63 }
64 }
65}
66
67/// Activation-time configuration handed to `reset`.
68///
69/// `#[non_exhaustive]` so future prepare-time fields (a bus layout, host
70/// latency budget) can be added without breaking the `reset` signature.
71/// Construct with [`Self::new`] (defaults to [`ProcessMode::Realtime`])
72/// plus [`Self::with_process_mode`]; read the fields directly.
73#[derive(Clone, Copy, Debug, PartialEq)]
74#[non_exhaustive]
75pub struct AudioConfig {
76 /// Host sample rate in Hz.
77 pub sample_rate: f64,
78 /// Largest block the host will hand `process`. Size preallocations
79 /// against this - the host never sends a bigger block this activation.
80 pub max_block_size: usize,
81 /// How the host drives audio this activation. See [`ProcessMode`].
82 pub process_mode: ProcessMode,
83}
84
85impl AudioConfig {
86 /// New config at the default [`ProcessMode::Realtime`]. Chain
87 /// [`Self::with_process_mode`] for an offline / buffered activation.
88 #[must_use]
89 pub fn new(sample_rate: f64, max_block_size: usize) -> Self {
90 Self {
91 sample_rate,
92 max_block_size,
93 process_mode: ProcessMode::Realtime,
94 }
95 }
96
97 /// Set the processing mode for this activation.
98 #[must_use]
99 pub fn with_process_mode(mut self, mode: ProcessMode) -> Self {
100 self.process_mode = mode;
101 self
102 }
103}