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
//! Core node trait and context types.
use ;
/// Information available during audio processing.
///
/// Passed to every [`AudioNode::process`] call. Contains the graph's sample rate
/// and the buffer size (always 64 samples in the current implementation).
/// Unique identifier for a node within a graph.
///
/// You typically don't interact with this directly - use [`Handle`](crate::Handle) instead.
u32);
/// The core trait for audio processing nodes.
///
/// Implement this trait to create custom audio nodes. Nodes can be:
/// - **Sources**: Generate audio (0 inputs, 1+ outputs) - oscillators, sample players
/// - **Effects**: Process audio (1+ inputs, 1+ outputs) - gain, filters, delays
/// - **Sinks**: Consume audio (1+ inputs, 0 outputs) - device outputs, recorders
///
/// # Message-Based Parameters
///
/// Instead of shared mutable state, nodes receive parameter updates via messages.
/// Define your message type and handle it at the start of `process()`:
///
/// ```
/// use klingt::{AudioNode, ProcessContext};
/// use dasp_graph::{Buffer, Input};
///
/// enum MyMessage {
/// SetFrequency(f32),
/// SetVolume(f32),
/// }
///
/// struct MyOscillator {
/// frequency: f32,
/// volume: f32,
/// phase: f32,
/// }
///
/// impl AudioNode for MyOscillator {
/// type Message = MyMessage;
///
/// fn process(
/// &mut self,
/// ctx: &ProcessContext,
/// messages: impl Iterator<Item = MyMessage>,
/// _inputs: &[Input],
/// outputs: &mut [Buffer],
/// ) {
/// // Handle parameter updates first
/// for msg in messages {
/// match msg {
/// MyMessage::SetFrequency(f) => self.frequency = f,
/// MyMessage::SetVolume(v) => self.volume = v,
/// }
/// }
///
/// // Generate audio
/// for sample in outputs[0].iter_mut() {
/// *sample = (self.phase * std::f32::consts::TAU).sin() * self.volume;
/// self.phase = (self.phase + self.frequency / ctx.sample_rate as f32) % 1.0;
/// }
/// }
///
/// fn num_outputs(&self) -> usize { 1 }
/// }
/// ```
///
/// # No Messages Needed?
///
/// If your node doesn't need runtime parameter updates, use `()` as the message type:
///
/// ```
/// # use klingt::{AudioNode, ProcessContext};
/// # use dasp_graph::{Buffer, Input};
/// struct FixedTone { /* ... */ }
///
/// impl AudioNode for FixedTone {
/// type Message = (); // No messages
///
/// fn process(
/// &mut self,
/// ctx: &ProcessContext,
/// _messages: impl Iterator<Item = ()>,
/// _inputs: &[Input],
/// outputs: &mut [Buffer],
/// ) {
/// // Just generate audio, ignore messages
/// # let _ = (ctx, outputs);
/// }
/// }
/// ```