moa/emulator/frontends/common/src/audio.rs
transistor 527f65c69b Refactored audio to use ClockedQueue
It now actually checks the clock and tries to mix the audio in sync
relative to the clock, but the cpal output doesn't yet try to sync
to the StreamInstant time.  Sound seems a lot better on chrome in
wasm, but and kind of better on firefox despite frame skipping not
being supported yet, but it's way slower for some reason (12fps)
2023-05-07 10:03:25 -07:00

200 lines
5.3 KiB
Rust

use std::sync::{Arc, Mutex, MutexGuard};
use moa_core::{ClockTime, ClockDuration};
use moa_core::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: ClockTime, 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: ClockTime, 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::new(),
})))
}
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) -> ClockDuration {
ClockDuration::from_secs(1) / self.sample_rate as u64
}
fn assemble_frame(&mut self, frame_start: ClockTime, frame_duration: ClockDuration) {
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<ClockDuration, Error> {
let duration = ClockDuration::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 AudioOutput {
pub fn new() -> Self {
Self {
queue: ClockedQueue::new(5000),
}
}
pub fn add_frame(&self, clock: ClockTime, frame: AudioFrame) {
self.queue.push(clock, frame);
}
pub fn put_back(&self, clock: ClockTime, frame: AudioFrame) {
self.queue.put_back(clock, frame);
}
pub fn receive(&self) -> Option<(ClockTime, AudioFrame)> {
self.queue.pop_next()
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
}