1use crate::graph::{AudioGraph, AudioNode, Edge, NodeId, NodeKind, GraphError};
8use crate::limiter::BrickwallLimiter;
9use arc_swap::ArcSwap;
10use std::sync::Arc;
11
12#[derive(Debug, Clone)]
18pub struct EngineConfig {
19 pub sample_rate: u32,
20 pub buffer_size: usize,
21 pub channels: usize,
22 pub limiter_ceiling_db: f64,
24}
25
26impl Default for EngineConfig {
27 fn default() -> Self {
28 Self {
29 sample_rate: 44100,
30 buffer_size: 512,
31 channels: 2,
32 limiter_ceiling_db: -1.0,
33 }
34 }
35}
36
37pub struct GraphSnapshot {
44 pub graph: AudioGraph,
45 pub master_id: NodeId,
47}
48
49unsafe impl Send for GraphSnapshot {}
52unsafe impl Sync for GraphSnapshot {}
53
54pub struct AudioEngine {
60 graph: Arc<ArcSwap<GraphSnapshot>>,
62 config: EngineConfig,
64 limiter: BrickwallLimiter,
66 output_buf: Vec<f64>,
68 output_f32: Vec<f32>,
70}
71
72impl AudioEngine {
73 pub fn new(config: EngineConfig) -> Self {
75 let limiter = BrickwallLimiter::new(
76 config.limiter_ceiling_db,
77 1.0, 50.0, config.sample_rate,
80 config.channels,
81 );
82
83 let buf_len = config.buffer_size * config.channels;
84
85 let empty_graph = AudioGraph::build(vec![], vec![], config.buffer_size)
87 .expect("Empty graph should always succeed");
88
89 let snapshot = GraphSnapshot {
90 graph: empty_graph,
91 master_id: NodeId(0),
92 };
93
94 Self {
95 graph: Arc::new(ArcSwap::new(Arc::new(snapshot))),
96 config,
97 limiter,
98 output_buf: vec![0.0f64; buf_len],
99 output_f32: vec![0.0f32; buf_len],
100 }
101 }
102
103 pub fn graph_handle(&self) -> Arc<ArcSwap<GraphSnapshot>> {
105 Arc::clone(&self.graph)
106 }
107
108 pub fn process(&mut self, frames: usize) -> &[f32] {
113 let frames = frames.min(self.config.buffer_size);
114 let ch = self.config.channels;
115 let len = frames * ch;
116
117 let snapshot = self.graph.load();
119
120 if snapshot.graph.node_count() == 0 {
122 for s in self.output_f32[..len].iter_mut() { *s = 0.0; }
124 return &self.output_f32[..len];
125 }
126
127 if let Some(master_out) = snapshot.graph.output(snapshot.master_id) {
139 let copy_len = len.min(master_out.len());
140 self.output_buf[..copy_len].copy_from_slice(&master_out[..copy_len]);
141 } else {
142 for s in self.output_buf[..len].iter_mut() { *s = 0.0; }
143 }
144
145 let mut limited = vec![0.0f64; len]; self.limiter.process_block(&self.output_buf[..len], &mut limited, frames);
148
149 for i in 0..len {
151 self.output_f32[i] = limited[i] as f32;
152 }
153
154 &self.output_f32[..len]
155 }
156
157 pub fn limiter_gr_db(&self) -> f64 {
159 self.limiter.gain_reduction_db()
160 }
161
162 pub fn config(&self) -> &EngineConfig {
164 &self.config
165 }
166}
167
168pub struct GraphBuilder {
174 nodes: Vec<Box<dyn AudioNode>>,
175 edges: Vec<Edge>,
176 master_id: Option<NodeId>,
177 max_frames: usize,
178}
179
180impl GraphBuilder {
181 pub fn new(max_frames: usize) -> Self {
182 Self {
183 nodes: Vec::new(),
184 edges: Vec::new(),
185 master_id: None,
186 max_frames,
187 }
188 }
189
190 pub fn add_node(mut self, node: Box<dyn AudioNode>) -> Self {
192 if node.kind() == NodeKind::Master {
193 self.master_id = Some(node.node_id());
194 }
195 self.nodes.push(node);
196 self
197 }
198
199 pub fn add_edge(mut self, from: NodeId, to: NodeId) -> Self {
201 self.edges.push(Edge { from, to });
202 self
203 }
204
205 pub fn set_master(mut self, id: NodeId) -> Self {
207 self.master_id = Some(id);
208 self
209 }
210
211 pub fn build(self) -> Result<Arc<GraphSnapshot>, GraphError> {
213 let graph = AudioGraph::build(self.nodes, self.edges, self.max_frames)?;
214 let master_id = self.master_id.unwrap_or(NodeId(0));
215 Ok(Arc::new(GraphSnapshot { graph, master_id }))
216 }
217}
218
219pub fn swap_graph(handle: &ArcSwap<GraphSnapshot>, new_graph: Arc<GraphSnapshot>) {
221 handle.store(new_graph);
222}
223
224#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::graph::{NodeKind};
232
233 struct TestSource { id: NodeId, value: f64 }
234 impl AudioNode for TestSource {
235 fn process(&mut self, _: &[&[f64]], output: &mut [f64], frames: usize) {
236 for i in 0..(frames * 2).min(output.len()) { output[i] = self.value; }
237 }
238 fn node_id(&self) -> NodeId { self.id }
239 fn kind(&self) -> NodeKind { NodeKind::Deck }
240 }
241
242 struct TestPassthrough { id: NodeId }
243 impl AudioNode for TestPassthrough {
244 fn process(&mut self, inputs: &[&[f64]], output: &mut [f64], frames: usize) {
245 let len = frames * 2;
246 for inp in inputs {
247 for i in 0..len.min(inp.len()).min(output.len()) {
248 output[i] += inp[i];
249 }
250 }
251 }
252 fn node_id(&self) -> NodeId { self.id }
253 fn kind(&self) -> NodeKind { NodeKind::Master }
254 }
255
256 #[test]
257 fn engine_creates_with_defaults() {
258 let engine = AudioEngine::new(EngineConfig::default());
259 assert_eq!(engine.config().sample_rate, 44100);
260 assert_eq!(engine.config().buffer_size, 512);
261 }
262
263 #[test]
264 fn engine_outputs_silence_with_empty_graph() {
265 let mut engine = AudioEngine::new(EngineConfig::default());
266 let out = engine.process(64);
267 for &s in out {
268 assert_eq!(s, 0.0);
269 }
270 }
271
272 #[test]
273 fn graph_builder_builds() {
274 let snapshot = GraphBuilder::new(64)
275 .add_node(Box::new(TestSource { id: NodeId(1), value: 0.5 }))
276 .add_node(Box::new(TestPassthrough { id: NodeId(2) }))
277 .add_edge(NodeId(1), NodeId(2))
278 .set_master(NodeId(2))
279 .build()
280 .unwrap();
281
282 assert_eq!(snapshot.master_id, NodeId(2));
283 assert_eq!(snapshot.graph.node_count(), 2);
284 }
285
286 #[test]
287 fn graph_swap_is_atomic() {
288 let engine = AudioEngine::new(EngineConfig::default());
289 let handle = engine.graph_handle();
290
291 let new_snapshot = GraphBuilder::new(512)
293 .add_node(Box::new(TestSource { id: NodeId(1), value: 1.0 }))
294 .add_node(Box::new(TestPassthrough { id: NodeId(2) }))
295 .add_edge(NodeId(1), NodeId(2))
296 .set_master(NodeId(2))
297 .build()
298 .unwrap();
299
300 swap_graph(&handle, new_snapshot);
302
303 let loaded = handle.load();
305 assert_eq!(loaded.graph.node_count(), 2);
306 }
307
308 #[test]
309 fn limiter_reports_no_reduction_on_silence() {
310 let engine = AudioEngine::new(EngineConfig::default());
311 assert!((engine.limiter_gr_db() - 0.0).abs() < 0.01);
312 }
313}