Skip to main content

aether_ndk/
lib.rs

1//! Aether Node Development Kit
2//!
3//! Everything a third-party developer needs to build a DSP node:
4//!
5//! ```rust
6//! use aether_ndk::prelude::*;
7//!
8//! #[aether_node]
9//! pub struct Tremolo {
10//!     #[param(name = "Rate", min = 0.1, max = 20.0, default = 4.0)]
11//!     rate: f32,
12//!     #[param(name = "Depth", min = 0.0, max = 1.0, default = 0.5)]
13//!     depth: f32,
14//!     phase: f32,
15//! }
16//!
17//! impl DspProcess for Tremolo {
18//!     fn process(
19//!         &mut self,
20//!         inputs: &NodeInputs,
21//!         output: &mut NodeOutput,
22//!         params: &mut ParamBlock,
23//!         sample_rate: f32,
24//!     ) {
25//!         let input = inputs.get(0);
26//!         for (i, out) in output.iter_mut().enumerate() {
27//!             let rate = params.get(0).current;
28//!             let depth = params.get(1).current;
29//!             let lfo = 1.0 - depth * 0.5 * (1.0 - (self.phase * std::f32::consts::TAU).cos());
30//!             *out = input[i] * lfo;
31//!             self.phase = (self.phase + rate / sample_rate).fract();
32//!             params.tick_all();
33//!         }
34//!     }
35//! }
36//! ```
37
38pub mod node;
39pub mod param;
40pub mod registry;
41pub mod schema;
42
43pub use aether_ndk_macro::aether_node;
44
45// Re-export core types so node authors don't need to depend on aether-core directly
46pub use aether_core::{
47    node::DspNode,
48    param::ParamBlock,
49    state::StateBlob,
50    BUFFER_SIZE, MAX_INPUTS,
51};
52
53/// A parameter definition — name, range, and default value.
54/// Generated statically by `#[aether_node]`.
55#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
56pub struct ParamDef {
57    pub name: &'static str,
58    pub min: f32,
59    pub max: f32,
60    pub default: f32,
61}
62
63/// Metadata trait auto-implemented by `#[aether_node]`.
64pub trait AetherNodeMeta {
65    fn type_name() -> &'static str;
66    fn param_defs() -> &'static [ParamDef];
67}
68
69/// The processing trait node authors implement.
70/// Simpler than `DspNode` — no raw pointer handling required.
71pub trait DspProcess: Send {
72    fn process(
73        &mut self,
74        inputs: &NodeInputs,
75        output: &mut NodeOutput,
76        params: &mut ParamBlock,
77        sample_rate: f32,
78    );
79
80    fn capture_state(&self) -> StateBlob { StateBlob::EMPTY }
81    fn restore_state(&mut self, _state: StateBlob) {}
82}
83
84/// Ergonomic wrapper around the raw input buffer array.
85pub struct NodeInputs<'a> {
86    pub raw: &'a [Option<&'a [f32; BUFFER_SIZE]>; MAX_INPUTS],
87}
88
89impl<'a> NodeInputs<'a> {
90    pub fn get(&self, slot: usize) -> &[f32; BUFFER_SIZE] {
91        static SILENCE: [f32; BUFFER_SIZE] = [0.0f32; BUFFER_SIZE];
92        self.raw.get(slot).and_then(|s| *s).unwrap_or(&SILENCE)
93    }
94}
95
96/// Ergonomic wrapper around the output buffer.
97pub type NodeOutput = [f32; BUFFER_SIZE];
98
99/// Adapter that bridges `DspProcess` → `DspNode` so NDK nodes
100/// work transparently inside the AetherDSP engine.
101pub struct NodeAdapter<T: DspProcess + AetherNodeMeta> {
102    pub inner: T,
103}
104
105impl<T: DspProcess + AetherNodeMeta + 'static> DspNode for NodeAdapter<T> {
106    fn process(
107        &mut self,
108        inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
109        output: &mut [f32; BUFFER_SIZE],
110        params: &mut ParamBlock,
111        sample_rate: f32,
112    ) {
113        let wrapped = NodeInputs { raw: inputs };
114        self.inner.process(&wrapped, output, params, sample_rate);
115    }
116
117    fn capture_state(&self) -> StateBlob { self.inner.capture_state() }
118    fn restore_state(&mut self, state: StateBlob) { self.inner.restore_state(state); }
119    fn type_name(&self) -> &'static str { T::type_name() }
120}
121
122/// Convenience function: wrap any `DspProcess + AetherNodeMeta` into a
123/// boxed `DspNode` ready for the graph.
124pub fn into_node<T>(node: T) -> Box<dyn DspNode>
125where
126    T: DspProcess + AetherNodeMeta + 'static,
127{
128    Box::new(NodeAdapter { inner: node })
129}
130
131/// Prelude — import everything needed to write a node.
132pub mod prelude {
133    pub use super::{
134        aether_node, into_node, AetherNodeMeta, DspProcess, NodeInputs,
135        NodeOutput, ParamDef,
136    };
137    pub use aether_core::param::ParamBlock;
138    pub use aether_core::state::StateBlob;
139    pub use aether_core::{BUFFER_SIZE, MAX_INPUTS};
140}