moa/emulator/frontends/common/src/audio.rs

199 lines
5.3 KiB
Rust

use std::sync::{Arc, Mutex, MutexGuard};
use femtos::{Instant, Duration};
use moa_host::{Audio, Sample, AudioFrame, ClockedQueue};
pub const SAMPLE_RATE: usize = 48000;
pub struct AudioSource {
id: usize,
sample_rate: usize,
queue: ClockedQueue<AudioFrame>,
}
impl AudioSource {
// TODO should you move this to AudioMixer to make the interface easier to use?
// ie. let source: AudioSource = mixer.new_source();
pub fn new(mixer: AudioMixer) -> Self {
let queue = ClockedQueue::new(5000);
let (id, sample_rate) = {
let mut mixer = mixer.borrow_mut();
let id = mixer.add_source(queue.clone());
(id, mixer.sample_rate())
};
Self {
id,
sample_rate,
queue,
}
}
pub fn id(&self) -> usize {
self.id
}
pub fn add_frame(&mut self, clock: Instant, buffer: &[Sample]) {
let mut data = Vec::with_capacity(buffer.len());
for sample in buffer.iter() {
data.push(*sample);
}
let frame = AudioFrame::new(self.sample_rate, data);
self.queue.push(clock, frame);
}
}
impl Audio for AudioSource {
fn samples_per_second(&self) -> usize {
self.sample_rate
}
fn write_samples(&mut self, clock: Instant, buffer: &[Sample]) {
self.add_frame(clock, buffer);
}
}
#[derive(Clone)]
pub struct AudioMixer(Arc<Mutex<AudioMixerInner>>);
pub struct AudioMixerInner {
sample_rate: usize,
sources: Vec<ClockedQueue<AudioFrame>>,
output: AudioOutput,
}
impl AudioMixer {
pub fn new(sample_rate: usize) -> AudioMixer {
AudioMixer(Arc::new(Mutex::new(AudioMixerInner {
sample_rate,
sources: vec![],
output: AudioOutput::default(),
})))
}
pub fn with_default_rate() -> AudioMixer {
AudioMixer::new(SAMPLE_RATE)
}
pub fn borrow_mut(&self) -> MutexGuard<'_, AudioMixerInner> {
self.0.lock().unwrap()
}
}
impl AudioMixerInner {
pub fn add_source(&mut self, source: ClockedQueue<AudioFrame>) -> usize {
self.sources.push(source);
self.sources.len() - 1
}
pub fn num_sources(&self) -> usize {
self.sources.len()
}
pub fn get_sink(&mut self) -> AudioOutput {
self.output.clone()
}
pub fn sample_rate(&self) -> usize {
self.sample_rate
}
pub fn sample_duration(&self) -> Duration {
Duration::from_secs(1) / self.sample_rate as u64
}
fn assemble_frame(&mut self, frame_start: Instant, frame_duration: Duration) {
let sample_duration = self.sample_duration();
let samples = (frame_duration / sample_duration) as usize;
let mut data = vec![Sample(0.0, 0.0); samples];
for source in &self.sources {
let mut index = 0;
while index < data.len() {
if let Some((clock, mut frame)) = source.pop_next() {
index = (clock.duration_since(frame_start) / sample_duration) as usize;
let size = frame.data.len().min(data.len() - index);
frame
.data
.iter()
.zip(&mut data[index..index + size])
.for_each(|(source, dest)| {
dest.0 += source.0;
dest.1 += source.1;
});
index += size;
if size < frame.data.len() {
frame.data.drain(0..size);
source.put_back(clock, frame);
}
}
}
}
// Average each sample, and clamp it to the 1 to -1 range
for sample in data.iter_mut() {
sample.0 = (sample.0 / self.sources.len() as f32).clamp(-1.0, 1.0);
sample.1 = (sample.1 / self.sources.len() as f32).clamp(-1.0, 1.0);
}
self.output.add_frame(frame_start, AudioFrame::new(self.sample_rate, data));
}
}
use moa_core::{Transmutable, Steppable, Error, System};
impl Steppable for AudioMixer {
fn step(&mut self, system: &System) -> Result<Duration, Error> {
let duration = Duration::from_millis(1);
// TODO should you make the clock be even further back to ensure the data is already written
if let Some(start) = system.clock.checked_sub(duration) {
self.borrow_mut().assemble_frame(start, duration);
}
Ok(duration)
}
}
impl Transmutable for AudioMixer {
fn as_steppable(&mut self) -> Option<&mut dyn Steppable> {
Some(self)
}
}
// TODO this should be split up into a sender/receiver
#[derive(Clone)]
pub struct AudioOutput {
queue: ClockedQueue<AudioFrame>,
}
impl Default for AudioOutput {
fn default() -> Self {
Self {
queue: ClockedQueue::new(5000),
}
}
}
impl AudioOutput {
pub fn add_frame(&self, clock: Instant, frame: AudioFrame) {
self.queue.push(clock, frame);
}
pub fn put_back(&self, clock: Instant, frame: AudioFrame) {
self.queue.put_back(clock, frame);
}
pub fn receive(&self) -> Option<(Instant, AudioFrame)> {
self.queue.pop_next()
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
}