sim_lib_audio_graph_core/processor.rs
1use sim_kernel::Result;
2use sim_lib_stream_core::{ClockDomain, LatencyClass};
3
4use crate::{PortDecl, PortDir, PortMedia, ProcessBlock};
5
6/// Configuration handed to each processor at [`prepare`](Processor::prepare)
7/// time: the sample rate, maximum block size, and channel counts.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct PrepareConfig {
10 /// Sample rate in hertz.
11 pub sample_rate_hz: u32,
12 /// Maximum number of frames in any single process block.
13 pub max_block_frames: u32,
14 /// Number of input channels.
15 pub in_channels: u16,
16 /// Number of output channels.
17 pub out_channels: u16,
18}
19
20impl PrepareConfig {
21 /// Creates a prepare configuration from its parts.
22 pub fn new(
23 sample_rate_hz: u32,
24 max_block_frames: u32,
25 in_channels: u16,
26 out_channels: u16,
27 ) -> Self {
28 Self {
29 sample_rate_hz,
30 max_block_frames,
31 in_channels,
32 out_channels,
33 }
34 }
35}
36
37/// Transport state delivered to processors per block.
38#[derive(Clone, Copy, Debug, PartialEq)]
39pub struct Transport {
40 /// Whether transport is playing.
41 pub playing: bool,
42 /// Playhead position in samples.
43 pub sample_pos: u64,
44 /// Tempo in beats per minute.
45 pub tempo_bpm: f64,
46 /// Playhead position in quarter notes (pulses per quarter position).
47 pub ppq_pos: f64,
48}
49
50impl Default for Transport {
51 fn default() -> Self {
52 Self {
53 playing: false,
54 sample_pos: 0,
55 tempo_bpm: 120.0,
56 ppq_pos: 0.0,
57 }
58 }
59}
60
61/// An event delivered to or emitted by a processor within a block, timestamped
62/// by a sample `offset` into the block.
63#[derive(Clone, Copy, Debug, PartialEq)]
64pub enum BlockEvent<'a> {
65 /// A short MIDI message of up to three bytes.
66 Midi {
67 /// Sample offset into the block.
68 offset: u32,
69 /// Message bytes (only the first `len` are valid).
70 bytes: [u8; 3],
71 /// Number of valid bytes in `bytes`.
72 len: u8,
73 },
74 /// A long MIDI message (for example sysex) borrowing its bytes.
75 MidiLong {
76 /// Sample offset into the block.
77 offset: u32,
78 /// Borrowed message bytes.
79 bytes: &'a [u8],
80 },
81 /// A parameter-set event.
82 ParamSet {
83 /// Sample offset into the block.
84 offset: u32,
85 /// Parameter index.
86 param: u32,
87 /// New parameter value.
88 value: f64,
89 },
90 /// A note-on event.
91 NoteOn {
92 /// Sample offset into the block.
93 offset: u32,
94 /// MIDI channel.
95 channel: u8,
96 /// MIDI key number.
97 key: u8,
98 /// Normalized velocity.
99 velocity: f32,
100 },
101 /// A note-off event.
102 NoteOff {
103 /// Sample offset into the block.
104 offset: u32,
105 /// MIDI channel.
106 channel: u8,
107 /// MIDI key number.
108 key: u8,
109 /// Normalized release velocity.
110 velocity: f32,
111 },
112}
113
114/// Sink that accepts events emitted by a processor during a block.
115pub trait EventSink {
116 /// Pushes one event into the sink.
117 fn push(&mut self, event: BlockEvent<'_>) -> Result<()>;
118}
119
120/// An [`EventSink`] that discards every event.
121#[derive(Clone, Copy, Debug, Default)]
122pub struct NullEventSink;
123
124impl EventSink for NullEventSink {
125 fn push(&mut self, _event: BlockEvent<'_>) -> Result<()> {
126 Ok(())
127 }
128}
129
130/// A node behavior in the audio graph: prepared once, then processes blocks.
131///
132/// Implementors must supply [`prepare`](Processor::prepare),
133/// [`reset`](Processor::reset), and [`process`](Processor::process); the
134/// remaining methods describe clocking, latency, and ports and have sensible
135/// defaults (sample-rate audio, block-local latency, realtime-pinned).
136pub trait Processor: Send {
137 /// Prepares the processor for a sample rate, block size, and channel layout.
138 fn prepare(&mut self, cfg: PrepareConfig);
139
140 /// Clears any internal state to a fresh start.
141 fn reset(&mut self);
142
143 /// Processes one block of audio and events in place.
144 fn process(&mut self, block: &mut ProcessBlock<'_>);
145
146 /// Returns the processor's clock domain (defaults to sample rate).
147 fn clock_domain(&self) -> ClockDomain {
148 ClockDomain::Sample
149 }
150
151 /// Returns the processor's latency class (defaults to block-local).
152 fn latency_class(&self) -> LatencyClass {
153 LatencyClass::BlockLocal
154 }
155
156 /// Returns whether the processor must run in the realtime thread (defaults
157 /// to `true`).
158 fn realtime_pin(&self) -> bool {
159 true
160 }
161
162 /// Returns the processor's port declarations for the given channel counts
163 /// (defaults to a single audio in/out pair).
164 fn ports(&self, in_channels: u16, out_channels: u16) -> Vec<PortDecl> {
165 default_processor_ports(in_channels, out_channels)
166 }
167
168 /// Returns the full [`ProcessorDescriptor`] assembled from this processor's
169 /// clock, latency, pinning, and ports.
170 fn descriptor(&self, in_channels: u16, out_channels: u16) -> ProcessorDescriptor {
171 ProcessorDescriptor::new(
172 self.clock_domain(),
173 self.latency_class(),
174 self.realtime_pin(),
175 self.ports(in_channels, out_channels),
176 )
177 }
178
179 /// Returns the processor's release tail length in frames (defaults to `0`).
180 fn tail_frames(&self) -> u64 {
181 0
182 }
183}
184
185/// Static description of a processor's clocking, latency, pinning, and ports.
186#[derive(Clone, Debug, PartialEq, Eq)]
187pub struct ProcessorDescriptor {
188 clock_domain: ClockDomain,
189 latency_class: LatencyClass,
190 realtime_pin: bool,
191 ports: Vec<PortDecl>,
192}
193
194impl ProcessorDescriptor {
195 /// Creates a descriptor from its parts.
196 pub fn new(
197 clock_domain: ClockDomain,
198 latency_class: LatencyClass,
199 realtime_pin: bool,
200 ports: Vec<PortDecl>,
201 ) -> Self {
202 Self {
203 clock_domain,
204 latency_class,
205 realtime_pin,
206 ports,
207 }
208 }
209
210 /// Returns the processor's clock domain.
211 pub fn clock_domain(&self) -> ClockDomain {
212 self.clock_domain
213 }
214
215 /// Returns the processor's latency class.
216 pub fn latency_class(&self) -> LatencyClass {
217 self.latency_class
218 }
219
220 /// Returns whether the processor is realtime-pinned.
221 pub fn realtime_pin(&self) -> bool {
222 self.realtime_pin
223 }
224
225 /// Returns the processor's port declarations.
226 pub fn ports(&self) -> &[PortDecl] {
227 &self.ports
228 }
229}
230
231fn default_processor_ports(in_channels: u16, out_channels: u16) -> Vec<PortDecl> {
232 let mut ports = Vec::new();
233 if in_channels > 0 {
234 ports.push(PortDecl::new(
235 "in",
236 PortMedia::Audio,
237 PortDir::In,
238 in_channels,
239 ));
240 }
241 if out_channels > 0 {
242 ports.push(PortDecl::new(
243 "out",
244 PortMedia::Audio,
245 PortDir::Out,
246 out_channels,
247 ));
248 }
249 ports
250}