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
//! # aether-core
//!
//! Hard real-time modular DSP engine — lock-free graph scheduler,
//! generational arena, and zero-allocation buffer pool.
//!
//! ```
//! 64-sample buffer · 48 kHz · ≤1.33 ms deadline · Zero allocations · Lock-free
//! ```
//!
//! ## Architecture
//!
//! ```text
//! Control thread RT audio thread
//! ────────────── ───────────────
//! AudioGraph (add/remove nodes) Scheduler::process_block()
//! │ │
//! │ CommandRing (SPSC) ├─ drain CommandRing
//! └────────────────────────────────►│
//! ├─ iterate sorted NodeArena
//! ├─ borrow buffers from BufferPool
//! └─ call DspNode::process() per node
//! ```
//!
//! ## Real-time guarantees
//!
//! | Rule | Enforcement |
//! |---|---|
//! | No heap allocation | Pre-allocated arena + buffer pool |
//! | No locks | SPSC ring buffer (`ringbuf`) |
//! | No I/O | All I/O on control/tokio threads |
//! | Bounded execution | Flat topo-sorted array, ≤32 commands/tick |
//!
//! ## Quick start
//!
//! See [`node::DspNode`] to implement a processing node, [`graph::AudioGraph`]
//! to build a patch, and [`scheduler::Scheduler`] to drive the RT loop.
/// Audio buffer size in samples. Hard real-time constraint: 64 samples @ 48kHz = 1.33ms deadline.
pub const BUFFER_SIZE: usize = 64;
/// Maximum number of inputs per node. Kept small to fit in cache lines.
pub const MAX_INPUTS: usize = 8;
/// Maximum number of nodes in the arena. Pre-allocated at startup.
pub const MAX_NODES: usize = 10_240;
/// Maximum number of audio buffers in the pool.
pub const MAX_BUFFERS: usize = MAX_NODES * 2;
/// Maximum commands processed per audio callback. Bounds mutation cost.
pub const MAX_COMMANDS_PER_TICK: usize = 32;
/// Command ring buffer capacity.
pub const COMMAND_RING_CAPACITY: usize = 1024;