From 2b4d8d9c6e8d60f398841a29534c55c6fda36d7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 20 Sep 2022 18:52:50 +0200 Subject: [PATCH] core,desktop: Add and implement AudioBackend::get_sample_history() --- Cargo.lock | 1 + core/src/backend/audio.rs | 7 +++ core/src/backend/audio/mixer.rs | 87 ++++++++++++++++++++++++++++----- desktop/Cargo.toml | 1 + desktop/src/audio.rs | 10 +++- 5 files changed, 93 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b92cbc1c1..cee6d7e4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3340,6 +3340,7 @@ name = "ruffle_desktop" version = "0.1.0" dependencies = [ "anyhow", + "bytemuck", "clap", "clipboard", "cpal", diff --git a/core/src/backend/audio.rs b/core/src/backend/audio.rs index 830f9dd82..4fe8c5cd0 100644 --- a/core/src/backend/audio.rs +++ b/core/src/backend/audio.rs @@ -125,6 +125,9 @@ pub trait AudioBackend: Downcast { /// Sets the master volume of the audio backend. fn set_volume(&mut self, volume: f32); + + /// Returns the last whole window of output samples. + fn get_sample_history(&self) -> [[f32; 2]; 1024]; } impl_downcast!(AudioBackend); @@ -244,6 +247,10 @@ impl AudioBackend for NullAudioBackend { fn set_volume(&mut self, volume: f32) { self.volume = volume; } + + fn get_sample_history(&self) -> [[f32; 2]; 1024] { + [[0.0f32; 2]; 1024] + } } impl Default for NullAudioBackend { diff --git a/core/src/backend/audio/mixer.rs b/core/src/backend/audio/mixer.rs index 773d64e0e..420cf782d 100644 --- a/core/src/backend/audio/mixer.rs +++ b/core/src/backend/audio/mixer.rs @@ -7,6 +7,39 @@ use std::io::Cursor; use std::sync::{Arc, Mutex, RwLock}; use swf::AudioCompression; +/// Holds the last 2048 output audio frames. Frames can be written to it one by +/// one, and the last completely filled 1024-wide window can be read from it. +struct CircBuf { + pub samples: [[f32; 2]; 2048], + pub pos: usize, +} + +impl CircBuf { + /// Creates an empty circular buffer. + pub fn new() -> Self { + Self { + samples: [[0.0; 2]; 2048], + pos: 0, + } + } + + /// Writes a value into the buffer, pushing the write position forward. + pub fn push(&mut self, sample: [f32; 2]) { + self.samples[self.pos] = sample; + self.pos = (self.pos + 1) % 2048; + } + + /// Returns one half of the inner buffer, the one that is not currently + /// being written to. + pub fn get(&self) -> &[[f32; 2]; 1024] { + if self.pos < 1024 { + self.samples[1024..2048].try_into().unwrap() + } else { + self.samples[0..1024].try_into().unwrap() + } + } +} + /// An audio mixer for a Flash movie. /// /// `AudioMixer` manages the audio state for a Flash movie. This can be used by any backend that @@ -29,6 +62,9 @@ pub struct AudioMixer { /// The sample rate of the output stream in Hz. output_sample_rate: u32, + + /// The last two windows of output samples. + output_memory: Arc>, } /// An audio stream. @@ -155,6 +191,7 @@ impl AudioMixer { volume: Arc::new(RwLock::new(1.0)), num_output_channels, output_sample_rate, + output_memory: Arc::new(RwLock::new(CircBuf::new())), } } @@ -164,6 +201,7 @@ impl AudioMixer { sound_instances: Arc::clone(&self.sound_instances), volume: Arc::clone(&self.volume), num_output_channels: self.num_output_channels, + output_memory: Arc::clone(&self.output_memory), } } @@ -173,18 +211,22 @@ impl AudioMixer { /// `output_buffer` is expected to be in 2-channel interleaved format. pub fn mix<'a, T>(&mut self, output_buffer: &mut [T]) where - T: 'a + dasp::Sample + Default, - T::Signed: dasp::sample::conv::FromSample, - T::Float: dasp::sample::conv::FromSample, + T: 'a + + Default + + dasp::Sample + + dasp::sample::ToSample + + dasp::sample::FromSample, { let mut sound_instances = self.sound_instances.lock().unwrap(); let volume = *self.volume.read().unwrap(); + let mut output_memory = self.output_memory.write().unwrap(); Self::mix_audio::( &mut sound_instances, volume, self.num_output_channels, output_buffer, - ) + &mut output_memory, + ); } /// Instantiate a seekable decoder for audio data with the given format. @@ -319,10 +361,13 @@ impl AudioMixer { volume: f32, num_channels: u8, mut output_buffer: &mut [T], + output_memory: &mut CircBuf, ) where - T: 'a + Default + dasp::Sample, - T::Signed: dasp::sample::conv::FromSample, - T::Float: dasp::sample::conv::FromSample, + T: 'a + + Default + + dasp::Sample + + dasp::sample::ToSample + + dasp::sample::FromSample, { use dasp::{ frame::{Frame, Stereo}, @@ -343,7 +388,7 @@ impl AudioMixer { let sound_frame = sound.stream.next(); let [left_0, left_1] = sound_frame.mul_amp(sound.left_transform); let [right_0, right_1] = sound_frame.mul_amp(sound.right_transform); - let mut sound_frame: Stereo = [ + let mut sound_frame: Stereo = [ Sample::add_amp(left_0, left_1).to_sample(), Sample::add_amp(right_0, right_1).to_sample(), ]; @@ -354,8 +399,10 @@ impl AudioMixer { } } + output_memory.push([output_frame[0].to_sample(), output_frame[1].to_sample()]); + for (buf_sample, output_sample) in buf_frame.iter_mut().zip(output_frame.iter()) { - *buf_sample = output_sample.to_sample(); + *buf_sample = *output_sample; } } @@ -363,6 +410,12 @@ impl AudioMixer { sound_instances.retain(|_, sound| sound.active); } + pub fn get_sample_history(&self) -> [[f32; 2]; 1024] { + let output_memory = self.output_memory.read().unwrap(); + + *output_memory.get() + } + /// Registers an embedded SWF sound with the audio mixer. pub fn register_sound(&mut self, swf_sound: &swf::Sound) -> Result { // Slice off latency seek for MP3 data. @@ -559,6 +612,8 @@ pub struct AudioMixerProxy { /// The number of channels in the output stream. Must be 1 or 2. num_output_channels: u8, + + output_memory: Arc>, } impl AudioMixerProxy { @@ -568,17 +623,21 @@ impl AudioMixerProxy { /// `output_buffer` is expected to be in 2-channel interleaved format. pub fn mix<'a, T>(&self, output_buffer: &mut [T]) where - T: 'a + dasp::Sample + Default, - T::Signed: dasp::sample::conv::FromSample, - T::Float: dasp::sample::conv::FromSample, + T: 'a + + Default + + dasp::Sample + + dasp::sample::ToSample + + dasp::sample::FromSample, { let mut sound_instances = self.sound_instances.lock().unwrap(); let volume = *self.volume.read().unwrap(); + let mut output_memory = self.output_memory.write().unwrap(); AudioMixer::mix_audio::( &mut sound_instances, volume, self.num_output_channels, output_buffer, + &mut output_memory, ) } } @@ -970,5 +1029,9 @@ macro_rules! impl_audio_mixer_backend { fn set_volume(&mut self, volume: f32) { self.$mixer.set_volume(volume) } + + fn get_sample_history(&self) -> [[f32; 2]; 1024] { + self.$mixer.get_sample_history() + } }; } diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index e2fcb135a..d3d22f038 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -22,6 +22,7 @@ dirs = "4.0" isahc = "1.7.2" rfd = "0.10.0" anyhow = "1.0" +bytemuck = "1.7.2" [target.'cfg(windows)'.dependencies] winapi = "0.3.9" diff --git a/desktop/src/audio.rs b/desktop/src/audio.rs index d84a3230f..e3ccf34bb 100644 --- a/desktop/src/audio.rs +++ b/desktop/src/audio.rs @@ -49,7 +49,15 @@ impl CpalAudioBackend { ), cpal::SampleFormat::U16 => device.build_output_stream( &config, - move |buffer, _| mixer.mix::(buffer), + move |buffer: &mut [u16], _| { + // Since I couldn't easily make `mixer` work with `u16` samples, + // we fill the buffer as if it was `&[i16]`, and then rotate + // the sample values to make 32768 the equilibrium. + mixer.mix::(bytemuck::cast_slice_mut(buffer)); + for s in buffer.iter_mut() { + *s = (*s).wrapping_add(32768); + } + }, error_handler, ), }?