1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! The [`Plugin`] trait — the framework's main user-facing surface.
use crate::;
/// A LADSPA plugin written against this framework.
///
/// Implement `Plugin` on the type that holds your plugin's
/// per-instance state. The trait's associated constants and methods
/// map directly onto the LADSPA descriptor and callback table; the
/// framework wraps them in C-compatible shims and exposes a
/// `ladspa_descriptor` entry point via the
/// [`plugin_entry!`](crate::plugin_entry) macro.
///
/// # Example
///
/// ```rust,no_run
/// use tympan_ladspa::{
/// plugin_entry,
/// port::{PortDefault, PortDescriptor, Ports},
/// realtime::RealtimeContext,
/// InstantiateError, Plugin,
/// };
///
/// struct Gain;
///
/// impl Plugin for Gain {
/// const UNIQUE_ID: u32 = 0x0010_0001;
/// const LABEL: &'static str = "gain";
/// const NAME: &'static str = "Linear Gain";
/// const MAKER: &'static str = "Example";
/// const COPYRIGHT: &'static str = "MIT OR Apache-2.0";
///
/// fn ports() -> &'static [PortDescriptor] {
/// static PORTS: &[PortDescriptor] = &[
/// PortDescriptor::audio_input("In"),
/// PortDescriptor::audio_output("Out"),
/// PortDescriptor::control_input("Gain")
/// .with_default(PortDefault::One)
/// .with_bounds(0.0, 4.0),
/// ];
/// PORTS
/// }
///
/// fn instantiate(_sample_rate: u32) -> Result<Self, InstantiateError> {
/// Ok(Self)
/// }
///
/// fn run(&mut self, _rt: &RealtimeContext, _frames: usize, ports: &mut Ports<'_>) {
/// let gain = ports.control_input(2);
/// let (input, output) = ports.audio_in_out(0, 1);
/// for (i, o) in input.iter().zip(output.iter_mut()) {
/// *o = *i * gain;
/// }
/// }
/// }
///
/// plugin_entry!(Gain);
/// ```
///
/// # Identity invariants
///
/// Every plugin must declare a [`UNIQUE_ID`](Self::UNIQUE_ID) that is
/// globally unique among LADSPA plugins. Authors obtain one from the
/// [LADSPA central registry](https://ladspa.org/) or pick a
/// high-entropy value outside the reserved low range.
///
/// The [`LABEL`](Self::LABEL) is the short machine-readable name
/// hosts use in configuration files; both [`UNIQUE_ID`] and
/// [`LABEL`] are part of the plugin's stable ABI and must not change
/// across versions of the same plugin (see
/// [ADR 0004](https://github.com/penta2himajin/tympan-ladspa/blob/main/docs/decisions/0004-no-global-state-multi-instance.md)).
///
/// # Lifecycle
///
/// A LADSPA host calls the methods in this order:
///
/// 1. [`Plugin::instantiate`] once per instance.
/// 2. [`Plugin::activate`] zero or more times before processing
/// starts.
/// 3. [`Plugin::run`] many times. Each call processes `frames`
/// samples using the buffers bound via LADSPA's `connect_port`
/// callback (handled by the framework).
/// 4. [`Plugin::deactivate`] zero or more times, balancing each
/// `activate`.
/// 5. The instance is dropped when the host issues `cleanup`.
///
/// `run` executes on the host's realtime audio thread. Code reachable
/// from it must obey the realtime invariants documented in
/// [`CLAUDE.md`](https://github.com/penta2himajin/tympan-ladspa/blob/main/CLAUDE.md)
/// — no allocations, no `Mutex::lock`, no blocking syscalls. The
/// [`RealtimeContext`] passed to `run` is the type-level witness that
/// the caller is on the realtime thread.