use crate::graph::{AudioGraph, AudioNode, Edge, NodeId, NodeKind, GraphError};
use crate::limiter::BrickwallLimiter;
use arc_swap::ArcSwap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct EngineConfig {
pub sample_rate: u32,
pub buffer_size: usize,
pub channels: usize,
pub limiter_ceiling_db: f64,
}
impl Default for EngineConfig {
fn default() -> Self {
Self {
sample_rate: 44100,
buffer_size: 512,
channels: 2,
limiter_ceiling_db: -1.0,
}
}
}
pub struct GraphSnapshot {
pub graph: AudioGraph,
pub master_id: NodeId,
}
unsafe impl Send for GraphSnapshot {}
unsafe impl Sync for GraphSnapshot {}
pub struct AudioEngine {
graph: Arc<ArcSwap<GraphSnapshot>>,
config: EngineConfig,
limiter: BrickwallLimiter,
output_buf: Vec<f64>,
output_f32: Vec<f32>,
}
impl AudioEngine {
pub fn new(config: EngineConfig) -> Self {
let limiter = BrickwallLimiter::new(
config.limiter_ceiling_db,
1.0, 50.0, config.sample_rate,
config.channels,
);
let buf_len = config.buffer_size * config.channels;
let empty_graph = AudioGraph::build(vec![], vec![], config.buffer_size)
.expect("Empty graph should always succeed");
let snapshot = GraphSnapshot {
graph: empty_graph,
master_id: NodeId(0),
};
Self {
graph: Arc::new(ArcSwap::new(Arc::new(snapshot))),
config,
limiter,
output_buf: vec![0.0f64; buf_len],
output_f32: vec![0.0f32; buf_len],
}
}
pub fn graph_handle(&self) -> Arc<ArcSwap<GraphSnapshot>> {
Arc::clone(&self.graph)
}
pub fn process(&mut self, frames: usize) -> &[f32] {
let frames = frames.min(self.config.buffer_size);
let ch = self.config.channels;
let len = frames * ch;
let snapshot = self.graph.load();
if snapshot.graph.node_count() == 0 {
for s in self.output_f32[..len].iter_mut() { *s = 0.0; }
return &self.output_f32[..len];
}
if let Some(master_out) = snapshot.graph.output(snapshot.master_id) {
let copy_len = len.min(master_out.len());
self.output_buf[..copy_len].copy_from_slice(&master_out[..copy_len]);
} else {
for s in self.output_buf[..len].iter_mut() { *s = 0.0; }
}
let mut limited = vec![0.0f64; len]; self.limiter.process_block(&self.output_buf[..len], &mut limited, frames);
for i in 0..len {
self.output_f32[i] = limited[i] as f32;
}
&self.output_f32[..len]
}
pub fn limiter_gr_db(&self) -> f64 {
self.limiter.gain_reduction_db()
}
pub fn config(&self) -> &EngineConfig {
&self.config
}
}
pub struct GraphBuilder {
nodes: Vec<Box<dyn AudioNode>>,
edges: Vec<Edge>,
master_id: Option<NodeId>,
max_frames: usize,
}
impl GraphBuilder {
pub fn new(max_frames: usize) -> Self {
Self {
nodes: Vec::new(),
edges: Vec::new(),
master_id: None,
max_frames,
}
}
pub fn add_node(mut self, node: Box<dyn AudioNode>) -> Self {
if node.kind() == NodeKind::Master {
self.master_id = Some(node.node_id());
}
self.nodes.push(node);
self
}
pub fn add_edge(mut self, from: NodeId, to: NodeId) -> Self {
self.edges.push(Edge { from, to });
self
}
pub fn set_master(mut self, id: NodeId) -> Self {
self.master_id = Some(id);
self
}
pub fn build(self) -> Result<Arc<GraphSnapshot>, GraphError> {
let graph = AudioGraph::build(self.nodes, self.edges, self.max_frames)?;
let master_id = self.master_id.unwrap_or(NodeId(0));
Ok(Arc::new(GraphSnapshot { graph, master_id }))
}
}
pub fn swap_graph(handle: &ArcSwap<GraphSnapshot>, new_graph: Arc<GraphSnapshot>) {
handle.store(new_graph);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::{NodeKind};
struct TestSource { id: NodeId, value: f64 }
impl AudioNode for TestSource {
fn process(&mut self, _: &[&[f64]], output: &mut [f64], frames: usize) {
for i in 0..(frames * 2).min(output.len()) { output[i] = self.value; }
}
fn node_id(&self) -> NodeId { self.id }
fn kind(&self) -> NodeKind { NodeKind::Deck }
}
struct TestPassthrough { id: NodeId }
impl AudioNode for TestPassthrough {
fn process(&mut self, inputs: &[&[f64]], output: &mut [f64], frames: usize) {
let len = frames * 2;
for inp in inputs {
for i in 0..len.min(inp.len()).min(output.len()) {
output[i] += inp[i];
}
}
}
fn node_id(&self) -> NodeId { self.id }
fn kind(&self) -> NodeKind { NodeKind::Master }
}
#[test]
fn engine_creates_with_defaults() {
let engine = AudioEngine::new(EngineConfig::default());
assert_eq!(engine.config().sample_rate, 44100);
assert_eq!(engine.config().buffer_size, 512);
}
#[test]
fn engine_outputs_silence_with_empty_graph() {
let mut engine = AudioEngine::new(EngineConfig::default());
let out = engine.process(64);
for &s in out {
assert_eq!(s, 0.0);
}
}
#[test]
fn graph_builder_builds() {
let snapshot = GraphBuilder::new(64)
.add_node(Box::new(TestSource { id: NodeId(1), value: 0.5 }))
.add_node(Box::new(TestPassthrough { id: NodeId(2) }))
.add_edge(NodeId(1), NodeId(2))
.set_master(NodeId(2))
.build()
.unwrap();
assert_eq!(snapshot.master_id, NodeId(2));
assert_eq!(snapshot.graph.node_count(), 2);
}
#[test]
fn graph_swap_is_atomic() {
let engine = AudioEngine::new(EngineConfig::default());
let handle = engine.graph_handle();
let new_snapshot = GraphBuilder::new(512)
.add_node(Box::new(TestSource { id: NodeId(1), value: 1.0 }))
.add_node(Box::new(TestPassthrough { id: NodeId(2) }))
.add_edge(NodeId(1), NodeId(2))
.set_master(NodeId(2))
.build()
.unwrap();
swap_graph(&handle, new_snapshot);
let loaded = handle.load();
assert_eq!(loaded.graph.node_count(), 2);
}
#[test]
fn limiter_reports_no_reduction_on_silence() {
let engine = AudioEngine::new(EngineConfig::default());
assert!((engine.limiter_gr_db() - 0.0).abs() < 0.01);
}
}