core,desktop: Add and implement AudioBackend::get_sample_history()
This commit is contained in:
parent
fb8caad783
commit
2b4d8d9c6e
|
@ -3340,6 +3340,7 @@ name = "ruffle_desktop"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytemuck",
|
||||
"clap",
|
||||
"clipboard",
|
||||
"cpal",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<RwLock<CircBuf>>,
|
||||
}
|
||||
|
||||
/// 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<i16>,
|
||||
T::Float: dasp::sample::conv::FromSample<f32>,
|
||||
T: 'a
|
||||
+ Default
|
||||
+ dasp::Sample<Signed = T>
|
||||
+ dasp::sample::ToSample<f32>
|
||||
+ dasp::sample::FromSample<i16>,
|
||||
{
|
||||
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::<T>(
|
||||
&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<i16>,
|
||||
T::Float: dasp::sample::conv::FromSample<f32>,
|
||||
T: 'a
|
||||
+ Default
|
||||
+ dasp::Sample<Signed = T>
|
||||
+ dasp::sample::ToSample<f32>
|
||||
+ dasp::sample::FromSample<i16>,
|
||||
{
|
||||
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<T::Signed> = [
|
||||
let mut sound_frame: Stereo<T> = [
|
||||
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<SoundHandle, RegisterError> {
|
||||
// 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<RwLock<CircBuf>>,
|
||||
}
|
||||
|
||||
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<i16>,
|
||||
T::Float: dasp::sample::conv::FromSample<f32>,
|
||||
T: 'a
|
||||
+ Default
|
||||
+ dasp::Sample<Signed = T>
|
||||
+ dasp::sample::ToSample<f32>
|
||||
+ dasp::sample::FromSample<i16>,
|
||||
{
|
||||
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::<T>(
|
||||
&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()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -49,7 +49,15 @@ impl CpalAudioBackend {
|
|||
),
|
||||
cpal::SampleFormat::U16 => device.build_output_stream(
|
||||
&config,
|
||||
move |buffer, _| mixer.mix::<u16>(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::<i16>(bytemuck::cast_slice_mut(buffer));
|
||||
for s in buffer.iter_mut() {
|
||||
*s = (*s).wrapping_add(32768);
|
||||
}
|
||||
},
|
||||
error_handler,
|
||||
),
|
||||
}?
|
||||
|
|
Loading…
Reference in New Issue