Init
This commit is contained in:
141
src/engine/audio.rs
Normal file
141
src/engine/audio.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use cpal::Stream;
|
||||
use crossbeam_channel::Receiver;
|
||||
use doux::{Engine, EngineMetrics};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::AudioCommand;
|
||||
|
||||
pub struct ScopeBuffer {
|
||||
pub samples: [AtomicU32; 64],
|
||||
peak_left: AtomicU32,
|
||||
peak_right: AtomicU32,
|
||||
}
|
||||
|
||||
impl ScopeBuffer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
samples: std::array::from_fn(|_| AtomicU32::new(0)),
|
||||
peak_left: AtomicU32::new(0),
|
||||
peak_right: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, data: &[f32]) {
|
||||
let mut peak_l: f32 = 0.0;
|
||||
let mut peak_r: f32 = 0.0;
|
||||
|
||||
for (i, atom) in self.samples.iter().enumerate() {
|
||||
let idx = i * 2;
|
||||
let left = data.get(idx).copied().unwrap_or(0.0);
|
||||
let right = data.get(idx + 1).copied().unwrap_or(0.0);
|
||||
peak_l = peak_l.max(left.abs());
|
||||
peak_r = peak_r.max(right.abs());
|
||||
atom.store(left.to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
self.peak_left.store(peak_l.to_bits(), Ordering::Relaxed);
|
||||
self.peak_right.store(peak_r.to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn read(&self) -> [f32; 64] {
|
||||
std::array::from_fn(|i| f32::from_bits(self.samples[i].load(Ordering::Relaxed)))
|
||||
}
|
||||
|
||||
pub fn peaks(&self) -> (f32, f32) {
|
||||
let left = f32::from_bits(self.peak_left.load(Ordering::Relaxed));
|
||||
let right = f32::from_bits(self.peak_right.load(Ordering::Relaxed));
|
||||
(left, right)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AudioStreamConfig {
|
||||
pub output_device: Option<String>,
|
||||
pub channels: u16,
|
||||
pub buffer_size: u32,
|
||||
}
|
||||
|
||||
pub fn build_stream(
|
||||
config: &AudioStreamConfig,
|
||||
audio_rx: Receiver<AudioCommand>,
|
||||
scope_buffer: Arc<ScopeBuffer>,
|
||||
metrics: Arc<EngineMetrics>,
|
||||
initial_samples: Vec<doux::sample::SampleEntry>,
|
||||
) -> Result<(Stream, f32), String> {
|
||||
let host = cpal::default_host();
|
||||
|
||||
let device = match &config.output_device {
|
||||
Some(name) => doux::audio::find_output_device(name)
|
||||
.ok_or_else(|| format!("Device not found: {name}"))?,
|
||||
None => host
|
||||
.default_output_device()
|
||||
.ok_or("No default output device")?,
|
||||
};
|
||||
|
||||
let default_config = device.default_output_config().map_err(|e| e.to_string())?;
|
||||
let sample_rate = default_config.sample_rate().0 as f32;
|
||||
|
||||
let buffer_size = if config.buffer_size > 0 {
|
||||
cpal::BufferSize::Fixed(config.buffer_size)
|
||||
} else {
|
||||
cpal::BufferSize::Default
|
||||
};
|
||||
|
||||
let stream_config = cpal::StreamConfig {
|
||||
channels: config.channels,
|
||||
sample_rate: default_config.sample_rate(),
|
||||
buffer_size,
|
||||
};
|
||||
|
||||
let sr = sample_rate;
|
||||
let channels = config.channels as usize;
|
||||
let metrics_clone = Arc::clone(&metrics);
|
||||
|
||||
let mut engine = Engine::new_with_metrics(sample_rate, channels, Arc::clone(&metrics));
|
||||
engine.sample_index = initial_samples;
|
||||
|
||||
let stream = device
|
||||
.build_output_stream(
|
||||
&stream_config,
|
||||
move |data: &mut [f32], _| {
|
||||
let buffer_samples = data.len() / channels;
|
||||
let buffer_time_ns = (buffer_samples as f64 / sr as f64 * 1e9) as u64;
|
||||
|
||||
while let Ok(cmd) = audio_rx.try_recv() {
|
||||
match cmd {
|
||||
AudioCommand::Evaluate(s) => {
|
||||
engine.evaluate(&s);
|
||||
}
|
||||
AudioCommand::Hush => {
|
||||
engine.hush();
|
||||
}
|
||||
AudioCommand::Panic => {
|
||||
engine.panic();
|
||||
}
|
||||
AudioCommand::LoadSamples(samples) => {
|
||||
engine.sample_index.extend(samples);
|
||||
}
|
||||
AudioCommand::ResetEngine => {
|
||||
let old_samples = std::mem::take(&mut engine.sample_index);
|
||||
engine =
|
||||
Engine::new_with_metrics(sr, channels, Arc::clone(&metrics_clone));
|
||||
engine.sample_index = old_samples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
engine.metrics.load.set_buffer_time(buffer_time_ns);
|
||||
engine.process_block(data, &[], &[]);
|
||||
scope_buffer.write(&engine.output);
|
||||
},
|
||||
|err| eprintln!("stream error: {err}"),
|
||||
None,
|
||||
)
|
||||
.map_err(|e| format!("Failed to build stream: {e}"))?;
|
||||
|
||||
stream
|
||||
.play()
|
||||
.map_err(|e| format!("Failed to play stream: {e}"))?;
|
||||
Ok((stream, sample_rate))
|
||||
}
|
||||
Reference in New Issue
Block a user