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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bytemuck",
|
||||||
"clap",
|
"clap",
|
||||||
"clipboard",
|
"clipboard",
|
||||||
"cpal",
|
"cpal",
|
||||||
|
|
|
@ -125,6 +125,9 @@ pub trait AudioBackend: Downcast {
|
||||||
|
|
||||||
/// Sets the master volume of the audio backend.
|
/// Sets the master volume of the audio backend.
|
||||||
fn set_volume(&mut self, volume: f32);
|
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);
|
impl_downcast!(AudioBackend);
|
||||||
|
@ -244,6 +247,10 @@ impl AudioBackend for NullAudioBackend {
|
||||||
fn set_volume(&mut self, volume: f32) {
|
fn set_volume(&mut self, volume: f32) {
|
||||||
self.volume = volume;
|
self.volume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sample_history(&self) -> [[f32; 2]; 1024] {
|
||||||
|
[[0.0f32; 2]; 1024]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NullAudioBackend {
|
impl Default for NullAudioBackend {
|
||||||
|
|
|
@ -7,6 +7,39 @@ use std::io::Cursor;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use swf::AudioCompression;
|
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.
|
/// An audio mixer for a Flash movie.
|
||||||
///
|
///
|
||||||
/// `AudioMixer` manages the audio state for a Flash movie. This can be used by any backend that
|
/// `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.
|
/// The sample rate of the output stream in Hz.
|
||||||
output_sample_rate: u32,
|
output_sample_rate: u32,
|
||||||
|
|
||||||
|
/// The last two windows of output samples.
|
||||||
|
output_memory: Arc<RwLock<CircBuf>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An audio stream.
|
/// An audio stream.
|
||||||
|
@ -155,6 +191,7 @@ impl AudioMixer {
|
||||||
volume: Arc::new(RwLock::new(1.0)),
|
volume: Arc::new(RwLock::new(1.0)),
|
||||||
num_output_channels,
|
num_output_channels,
|
||||||
output_sample_rate,
|
output_sample_rate,
|
||||||
|
output_memory: Arc::new(RwLock::new(CircBuf::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +201,7 @@ impl AudioMixer {
|
||||||
sound_instances: Arc::clone(&self.sound_instances),
|
sound_instances: Arc::clone(&self.sound_instances),
|
||||||
volume: Arc::clone(&self.volume),
|
volume: Arc::clone(&self.volume),
|
||||||
num_output_channels: self.num_output_channels,
|
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.
|
/// `output_buffer` is expected to be in 2-channel interleaved format.
|
||||||
pub fn mix<'a, T>(&mut self, output_buffer: &mut [T])
|
pub fn mix<'a, T>(&mut self, output_buffer: &mut [T])
|
||||||
where
|
where
|
||||||
T: 'a + dasp::Sample + Default,
|
T: 'a
|
||||||
T::Signed: dasp::sample::conv::FromSample<i16>,
|
+ Default
|
||||||
T::Float: dasp::sample::conv::FromSample<f32>,
|
+ dasp::Sample<Signed = T>
|
||||||
|
+ dasp::sample::ToSample<f32>
|
||||||
|
+ dasp::sample::FromSample<i16>,
|
||||||
{
|
{
|
||||||
let mut sound_instances = self.sound_instances.lock().unwrap();
|
let mut sound_instances = self.sound_instances.lock().unwrap();
|
||||||
let volume = *self.volume.read().unwrap();
|
let volume = *self.volume.read().unwrap();
|
||||||
|
let mut output_memory = self.output_memory.write().unwrap();
|
||||||
Self::mix_audio::<T>(
|
Self::mix_audio::<T>(
|
||||||
&mut sound_instances,
|
&mut sound_instances,
|
||||||
volume,
|
volume,
|
||||||
self.num_output_channels,
|
self.num_output_channels,
|
||||||
output_buffer,
|
output_buffer,
|
||||||
)
|
&mut output_memory,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instantiate a seekable decoder for audio data with the given format.
|
/// Instantiate a seekable decoder for audio data with the given format.
|
||||||
|
@ -319,10 +361,13 @@ impl AudioMixer {
|
||||||
volume: f32,
|
volume: f32,
|
||||||
num_channels: u8,
|
num_channels: u8,
|
||||||
mut output_buffer: &mut [T],
|
mut output_buffer: &mut [T],
|
||||||
|
output_memory: &mut CircBuf,
|
||||||
) where
|
) where
|
||||||
T: 'a + Default + dasp::Sample,
|
T: 'a
|
||||||
T::Signed: dasp::sample::conv::FromSample<i16>,
|
+ Default
|
||||||
T::Float: dasp::sample::conv::FromSample<f32>,
|
+ dasp::Sample<Signed = T>
|
||||||
|
+ dasp::sample::ToSample<f32>
|
||||||
|
+ dasp::sample::FromSample<i16>,
|
||||||
{
|
{
|
||||||
use dasp::{
|
use dasp::{
|
||||||
frame::{Frame, Stereo},
|
frame::{Frame, Stereo},
|
||||||
|
@ -343,7 +388,7 @@ impl AudioMixer {
|
||||||
let sound_frame = sound.stream.next();
|
let sound_frame = sound.stream.next();
|
||||||
let [left_0, left_1] = sound_frame.mul_amp(sound.left_transform);
|
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 [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(left_0, left_1).to_sample(),
|
||||||
Sample::add_amp(right_0, right_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()) {
|
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);
|
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.
|
/// Registers an embedded SWF sound with the audio mixer.
|
||||||
pub fn register_sound(&mut self, swf_sound: &swf::Sound) -> Result<SoundHandle, RegisterError> {
|
pub fn register_sound(&mut self, swf_sound: &swf::Sound) -> Result<SoundHandle, RegisterError> {
|
||||||
// Slice off latency seek for MP3 data.
|
// 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.
|
/// The number of channels in the output stream. Must be 1 or 2.
|
||||||
num_output_channels: u8,
|
num_output_channels: u8,
|
||||||
|
|
||||||
|
output_memory: Arc<RwLock<CircBuf>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioMixerProxy {
|
impl AudioMixerProxy {
|
||||||
|
@ -568,17 +623,21 @@ impl AudioMixerProxy {
|
||||||
/// `output_buffer` is expected to be in 2-channel interleaved format.
|
/// `output_buffer` is expected to be in 2-channel interleaved format.
|
||||||
pub fn mix<'a, T>(&self, output_buffer: &mut [T])
|
pub fn mix<'a, T>(&self, output_buffer: &mut [T])
|
||||||
where
|
where
|
||||||
T: 'a + dasp::Sample + Default,
|
T: 'a
|
||||||
T::Signed: dasp::sample::conv::FromSample<i16>,
|
+ Default
|
||||||
T::Float: dasp::sample::conv::FromSample<f32>,
|
+ dasp::Sample<Signed = T>
|
||||||
|
+ dasp::sample::ToSample<f32>
|
||||||
|
+ dasp::sample::FromSample<i16>,
|
||||||
{
|
{
|
||||||
let mut sound_instances = self.sound_instances.lock().unwrap();
|
let mut sound_instances = self.sound_instances.lock().unwrap();
|
||||||
let volume = *self.volume.read().unwrap();
|
let volume = *self.volume.read().unwrap();
|
||||||
|
let mut output_memory = self.output_memory.write().unwrap();
|
||||||
AudioMixer::mix_audio::<T>(
|
AudioMixer::mix_audio::<T>(
|
||||||
&mut sound_instances,
|
&mut sound_instances,
|
||||||
volume,
|
volume,
|
||||||
self.num_output_channels,
|
self.num_output_channels,
|
||||||
output_buffer,
|
output_buffer,
|
||||||
|
&mut output_memory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -970,5 +1029,9 @@ macro_rules! impl_audio_mixer_backend {
|
||||||
fn set_volume(&mut self, volume: f32) {
|
fn set_volume(&mut self, volume: f32) {
|
||||||
self.$mixer.set_volume(volume)
|
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"
|
isahc = "1.7.2"
|
||||||
rfd = "0.10.0"
|
rfd = "0.10.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
bytemuck = "1.7.2"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = "0.3.9"
|
winapi = "0.3.9"
|
||||||
|
|
|
@ -49,7 +49,15 @@ impl CpalAudioBackend {
|
||||||
),
|
),
|
||||||
cpal::SampleFormat::U16 => device.build_output_stream(
|
cpal::SampleFormat::U16 => device.build_output_stream(
|
||||||
&config,
|
&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,
|
error_handler,
|
||||||
),
|
),
|
||||||
}?
|
}?
|
||||||
|
|
Loading…
Reference in New Issue