use anyhow::{Context, Result};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{Device, Stream, StreamConfig};
use fundsp::hacker::*;
use parking_lot::Mutex;
use std::collections::VecDeque;
use std::sync::Arc;
use super::preset::{master_bus, GlobalParams, Preset, PresetKind};
use super::track::Track;
use crate::math::harmony::golden_freq;
use crate::recording::RecorderState;
pub const MAX_TRACKS: usize = 8;
pub const SCOPE_CAPACITY: usize = 512;
pub const SCOPE_DECIMATION: usize = 32;
pub type ScopeBuffer = Arc<Mutex<VecDeque<(f32, f32)>>>;
pub type SharedGraph = Arc<Mutex<Net>>;
pub struct EngineHandle {
pub tracks: Arc<Mutex<Vec<Track>>>,
pub global: GlobalParams,
pub peak_l: Shared,
pub peak_r: Shared,
pub sample_rate: f32,
pub scope: ScopeBuffer,
pub phase_clock: Shared,
pub recorder: Arc<RecorderState>,
graph: SharedGraph,
_stream: Stream,
}
pub struct AudioEngine;
impl AudioEngine {
pub fn start(initial_tracks: Vec<Track>) -> Result<EngineHandle> {
assert!(
initial_tracks.len() == MAX_TRACKS,
"expected exactly {MAX_TRACKS} pre-allocated tracks, got {}",
initial_tracks.len()
);
let host = cpal::default_host();
let device = host
.default_output_device()
.context("no default output audio device")?;
let config: StreamConfig = device.default_output_config()?.into();
let sample_rate = config.sample_rate.0 as f32;
let channels = config.channels as usize;
let global = GlobalParams::default();
let peak_l = shared(0.0);
let peak_r = shared(0.0);
let phase_clock = shared(0.0);
let scope: ScopeBuffer = Arc::new(Mutex::new(VecDeque::with_capacity(SCOPE_CAPACITY)));
let tracks = Arc::new(Mutex::new(initial_tracks));
let recorder = RecorderState::new(sample_rate as u32);
let mut graph = build_master(&tracks.lock(), &global);
graph.set_sample_rate(sample_rate as f64);
let graph: SharedGraph = Arc::new(Mutex::new(graph));
let stream = start_stream(
device,
config,
channels,
sample_rate,
graph.clone(),
global.master_gain.clone(),
peak_l.clone(),
peak_r.clone(),
scope.clone(),
phase_clock.clone(),
recorder.clone(),
)?;
Ok(EngineHandle {
tracks,
global,
peak_l,
peak_r,
sample_rate,
scope,
phase_clock,
recorder,
graph,
_stream: stream,
})
}
}
impl EngineHandle {
pub fn rebuild_graph(&self) {
let tracks = self.tracks.lock();
let mut new_graph = build_master(&tracks, &self.global);
drop(tracks);
new_graph.set_sample_rate(self.sample_rate as f64);
*self.graph.lock() = new_graph;
}
}
fn build_master(tracks: &[Track], g: &GlobalParams) -> Net {
let mut summed: Option<Net> = None;
for t in tracks {
let node = Preset::build(t.kind, &t.params, g);
summed = Some(match summed {
Some(acc) => acc + node,
None => node,
});
}
let summed = summed.unwrap_or_else(|| Net::wrap(Box::new(zero() | zero())));
summed >> master_bus(g.brightness.clone())
}
#[allow(clippy::too_many_arguments)]
fn start_stream(
device: Device,
config: StreamConfig,
channels: usize,
sample_rate: f32,
graph: SharedGraph,
master: Shared,
peak_l: Shared,
peak_r: Shared,
scope: ScopeBuffer,
phase_clock: Shared,
recorder: Arc<RecorderState>,
) -> Result<Stream> {
let err_fn = |err| tracing::error!("audio stream error: {err}");
let mut env_l = 0.0f32;
let mut env_r = 0.0f32;
let fall = 0.9995f32;
let dt: f64 = 1.0 / sample_rate as f64;
let mut t: f64 = 0.0;
let mut decim = 0usize;
let stream = device.build_output_stream(
&config,
move |data: &mut [f32], _| {
let m = master.value();
let mut pending: [(f32, f32); 32] = [(0.0, 0.0); 32];
let mut pending_n = 0usize;
let mut graph = graph.lock();
for frame in data.chunks_mut(channels) {
let (lo, ro) = graph.get_stereo();
let l = lo * m;
let r = ro * m;
env_l = (env_l * fall).max(l.abs());
env_r = (env_r * fall).max(r.abs());
for (ch, slot) in frame.iter_mut().enumerate() {
*slot = if ch & 1 == 0 { l } else { r };
}
recorder.push_frame(l, r);
decim = decim.wrapping_add(1);
if decim.is_multiple_of(SCOPE_DECIMATION) && pending_n < pending.len() {
pending[pending_n] = (l, r);
pending_n += 1;
}
t += dt;
}
if pending_n > 0 {
let mut scope = scope.lock();
for &s in &pending[..pending_n] {
if scope.len() == SCOPE_CAPACITY {
scope.pop_front();
}
scope.push_back(s);
}
}
peak_l.set_value(env_l);
peak_r.set_value(env_r);
phase_clock.set_value(t as f32);
},
err_fn,
None,
)?;
stream.play()?;
Ok(stream)
}
pub fn default_track_set() -> Vec<Track> {
let root = 55.0f32; let mut tracks = Vec::with_capacity(MAX_TRACKS);
tracks.push(Track::new(0, "Pad", PresetKind::PadZimmer, golden_freq(root, 0)));
tracks.push(Track::new(1, "Bass", PresetKind::BassPulse, golden_freq(root, 0)));
tracks.push(Track::new(2, "Heartbeat", PresetKind::Heartbeat, golden_freq(root, 0)));
tracks.push(Track::new(3, "Drone", PresetKind::DroneSub, golden_freq(root, -1)));
tracks[3].params.gain.set_value(0.32);
tracks[3].params.reverb_mix.set_value(0.7);
tracks.push(Track::dormant(4, "Shimmer", PresetKind::Shimmer, golden_freq(root, 1)));
tracks.push(Track::dormant(5, "Bell", PresetKind::Bell, golden_freq(root, 2)));
tracks.push(Track::dormant(6, "SuperSaw", PresetKind::SuperSaw, golden_freq(root, -2)));
tracks.push(Track::dormant(7, "Pluck", PresetKind::PluckSaw, golden_freq(root, 1)));
tracks
}