Skip to main content

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}