add AudioQueue
for queuing audio "commands" which is a much nicer way to interact with the audio system in general in a more complex application, as this lets you play audio in more of a "fire and forget" kind of way. Rc<AudioBuffer> play methods provided as an alternative because otherwise you end up with a somewhat gross double-copy of the audio buffer being played. but i didn't want to necessarily force use of Rc<AudioBuffer> just yet ... thus both versions of these methods are provided.
This commit is contained in:
parent
1a82436661
commit
e276eb950d
|
@ -58,6 +58,7 @@ fn main() -> Result<()> {
|
||||||
let mut system = SystemBuilder::new().window_title("Audio Playback").vsync(true).build()?;
|
let mut system = SystemBuilder::new().window_title("Audio Playback").vsync(true).build()?;
|
||||||
|
|
||||||
let mut is_running = true;
|
let mut is_running = true;
|
||||||
|
let mut using_queue_commands = false;
|
||||||
let mut volume = 1.0;
|
let mut volume = 1.0;
|
||||||
|
|
||||||
let sounds = [
|
let sounds = [
|
||||||
|
@ -77,48 +78,91 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut audio_device = system.audio.lock();
|
|
||||||
audio_device.volume = volume;
|
|
||||||
|
|
||||||
if system.keyboard.is_key_pressed(Scancode::Escape) {
|
if system.keyboard.is_key_pressed(Scancode::Escape) {
|
||||||
is_running = false;
|
is_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut audio_device = system.audio.lock();
|
||||||
|
audio_device.volume = volume;
|
||||||
|
|
||||||
if system.keyboard.is_key_pressed(Scancode::Num1) {
|
if system.keyboard.is_key_pressed(Scancode::Num1) {
|
||||||
audio_device.play_buffer(&sounds[0], false)?;
|
if using_queue_commands {
|
||||||
|
system.audio_queue.play_buffer(&sounds[0], false);
|
||||||
|
} else {
|
||||||
|
audio_device.play_buffer(&sounds[0], false)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if system.keyboard.is_key_pressed(Scancode::Num2) {
|
if system.keyboard.is_key_pressed(Scancode::Num2) {
|
||||||
audio_device.play_buffer(&sounds[1], false)?;
|
if using_queue_commands {
|
||||||
|
system.audio_queue.play_buffer(&sounds[1], false);
|
||||||
|
} else {
|
||||||
|
audio_device.play_buffer(&sounds[1], false)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if system.keyboard.is_key_pressed(Scancode::Num3) {
|
if system.keyboard.is_key_pressed(Scancode::Num3) {
|
||||||
audio_device.play_buffer(&sounds[2], false)?;
|
if using_queue_commands {
|
||||||
|
system.audio_queue.play_buffer(&sounds[2], false);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
audio_device.play_buffer(&sounds[2], false)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if system.keyboard.is_key_pressed(Scancode::Num4) {
|
if system.keyboard.is_key_pressed(Scancode::Num4) {
|
||||||
audio_device.play_buffer(&sounds[3], false)?;
|
if using_queue_commands {
|
||||||
|
system.audio_queue.play_buffer(&sounds[3], false);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
audio_device.play_buffer(&sounds[3], false)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if system.keyboard.is_key_pressed(Scancode::Num5) {
|
if system.keyboard.is_key_pressed(Scancode::Num5) {
|
||||||
audio_device.play_buffer(&sounds[4], false)?;
|
if using_queue_commands {
|
||||||
|
system.audio_queue.play_buffer(&sounds[4], false);
|
||||||
|
} else {
|
||||||
|
audio_device.play_buffer(&sounds[4], false)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if system.keyboard.is_key_pressed(Scancode::Num6) {
|
if system.keyboard.is_key_pressed(Scancode::Num6) {
|
||||||
audio_device.play_generator(SineWaveGenerator::new(), false);
|
if using_queue_commands {
|
||||||
|
system.audio_queue.play_generator(Box::new(SineWaveGenerator::new()), false);
|
||||||
|
} else {
|
||||||
|
audio_device.play_generator(Box::new(SineWaveGenerator::new()), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if system.keyboard.is_key_pressed(Scancode::Num7) {
|
if system.keyboard.is_key_pressed(Scancode::Num7) {
|
||||||
let index = rnd_value(0, sounds.len() - 1);
|
let index = rnd_value(0, sounds.len() - 1);
|
||||||
audio_device.play_buffer_on_channel(7, &sounds[index], false)?;
|
if using_queue_commands {
|
||||||
|
system.audio_queue.play_buffer_on_channel(7, &sounds[index], false)?;
|
||||||
|
} else {
|
||||||
|
audio_device.play_buffer_on_channel(7, &sounds[index], false)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if system.keyboard.is_key_pressed(Scancode::S) {
|
||||||
|
if using_queue_commands {
|
||||||
|
system.audio_queue.stop_all();
|
||||||
|
} else {
|
||||||
|
audio_device.stop_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
system.audio_queue.apply(&mut audio_device)?;
|
||||||
|
|
||||||
if system.keyboard.is_key_pressed(Scancode::KpMinus) {
|
if system.keyboard.is_key_pressed(Scancode::KpMinus) {
|
||||||
volume -= 0.1;
|
volume -= 0.1;
|
||||||
}
|
}
|
||||||
if system.keyboard.is_key_pressed(Scancode::KpPlus) {
|
if system.keyboard.is_key_pressed(Scancode::KpPlus) {
|
||||||
volume += 0.1;
|
volume += 0.1;
|
||||||
}
|
}
|
||||||
|
if system.keyboard.is_key_pressed(Scancode::Q) {
|
||||||
|
using_queue_commands = !using_queue_commands;
|
||||||
|
}
|
||||||
|
|
||||||
for index in 0..NUM_CHANNELS {
|
for index in 0..NUM_CHANNELS {
|
||||||
let channel = &audio_device[index];
|
let channel = &audio_device[index];
|
||||||
|
@ -133,6 +177,14 @@ fn main() -> Result<()> {
|
||||||
system.video.clear(0);
|
system.video.clear(0);
|
||||||
|
|
||||||
system.video.print_string(&format!("Volume: {:2.2}", volume), 16, 16, FontRenderOpts::Color(10), &system.font);
|
system.video.print_string(&format!("Volume: {:2.2}", volume), 16, 16, FontRenderOpts::Color(10), &system.font);
|
||||||
|
system.video.print_string(
|
||||||
|
if using_queue_commands {
|
||||||
|
"Queueing Commands"
|
||||||
|
} else {
|
||||||
|
"Direct Commands"
|
||||||
|
},
|
||||||
|
160, 16, FontRenderOpts::Color(9), &system.font
|
||||||
|
);
|
||||||
|
|
||||||
system.video.print_string("Audio Channels", 16, 32, FontRenderOpts::Color(14), &system.font);
|
system.video.print_string("Audio Channels", 16, 32, FontRenderOpts::Color(14), &system.font);
|
||||||
|
|
||||||
|
|
|
@ -98,9 +98,9 @@ impl AudioChannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn play_generator(&mut self, generator: impl AudioGenerator + 'static, loops: bool) {
|
pub fn play_generator(&mut self, generator: Box<dyn AudioGenerator>, loops: bool) {
|
||||||
self.data.clear();
|
self.data.clear();
|
||||||
self.generator = Some(Box::new(generator));
|
self.generator = Some(generator);
|
||||||
self.position = 0;
|
self.position = 0;
|
||||||
self.playing = true;
|
self.playing = true;
|
||||||
self.loops = loops;
|
self.loops = loops;
|
||||||
|
@ -233,7 +233,7 @@ impl AudioDevice {
|
||||||
|
|
||||||
pub fn play_generator(
|
pub fn play_generator(
|
||||||
&mut self,
|
&mut self,
|
||||||
generator: impl AudioGenerator + 'static,
|
generator: Box<dyn AudioGenerator>,
|
||||||
loops: bool,
|
loops: bool,
|
||||||
) -> Result<Option<&mut AudioChannel>, AudioDeviceError> {
|
) -> Result<Option<&mut AudioChannel>, AudioDeviceError> {
|
||||||
if let Some(channel) = self.stopped_channels_iter_mut().next() {
|
if let Some(channel) = self.stopped_channels_iter_mut().next() {
|
||||||
|
@ -247,7 +247,7 @@ impl AudioDevice {
|
||||||
pub fn play_generator_on_channel(
|
pub fn play_generator_on_channel(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_index: usize,
|
channel_index: usize,
|
||||||
generator: impl AudioGenerator + 'static,
|
generator: Box<dyn AudioGenerator>,
|
||||||
loops: bool,
|
loops: bool,
|
||||||
) -> Result<(), AudioDeviceError> {
|
) -> Result<(), AudioDeviceError> {
|
||||||
if channel_index >= NUM_CHANNELS {
|
if channel_index >= NUM_CHANNELS {
|
||||||
|
|
|
@ -4,9 +4,11 @@ use thiserror::Error;
|
||||||
|
|
||||||
pub use self::buffer::*;
|
pub use self::buffer::*;
|
||||||
pub use self::device::*;
|
pub use self::device::*;
|
||||||
|
pub use self::queue::*;
|
||||||
|
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
|
pub mod queue;
|
||||||
|
|
||||||
pub const NUM_CHANNELS: usize = 8;
|
pub const NUM_CHANNELS: usize = 8;
|
||||||
|
|
||||||
|
|
199
libretrogd/src/audio/queue.rs
Normal file
199
libretrogd/src/audio/queue.rs
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::audio::*;
|
||||||
|
|
||||||
|
pub enum AudioCommand {
|
||||||
|
StopChannel(usize),
|
||||||
|
StopAllChannels,
|
||||||
|
PlayBuffer {
|
||||||
|
buffer: AudioBuffer,
|
||||||
|
loops: bool,
|
||||||
|
},
|
||||||
|
PlayRcBuffer {
|
||||||
|
buffer: Rc<AudioBuffer>,
|
||||||
|
loops: bool,
|
||||||
|
},
|
||||||
|
PlayBufferOnChannel {
|
||||||
|
channel: usize,
|
||||||
|
buffer: AudioBuffer,
|
||||||
|
loops: bool,
|
||||||
|
},
|
||||||
|
PlayRcBufferOnChannel {
|
||||||
|
channel: usize,
|
||||||
|
buffer: Rc<AudioBuffer>,
|
||||||
|
loops: bool,
|
||||||
|
},
|
||||||
|
PlayGenerator {
|
||||||
|
generator: Box<dyn AudioGenerator>,
|
||||||
|
loops: bool,
|
||||||
|
},
|
||||||
|
PlayGeneratorOnChannel {
|
||||||
|
channel: usize,
|
||||||
|
generator: Box<dyn AudioGenerator>,
|
||||||
|
loops: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AudioQueue {
|
||||||
|
spec: AudioSpec,
|
||||||
|
commands: VecDeque<AudioCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioQueue {
|
||||||
|
pub fn from(audio: &Audio) -> Self {
|
||||||
|
AudioQueue {
|
||||||
|
spec: audio.spec,
|
||||||
|
commands: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn spec(&self) -> &AudioSpec {
|
||||||
|
&self.spec
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop_channel(&mut self, channel_index: usize) -> Result<(), AudioDeviceError> {
|
||||||
|
if channel_index >= NUM_CHANNELS {
|
||||||
|
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
|
||||||
|
} else {
|
||||||
|
self.commands.push_back(AudioCommand::StopChannel(channel_index));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop_all(&mut self) {
|
||||||
|
self.commands.push_back(AudioCommand::StopAllChannels);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_buffer(
|
||||||
|
&mut self,
|
||||||
|
buffer: &AudioBuffer,
|
||||||
|
loops: bool,
|
||||||
|
) -> Result<(), AudioDeviceError> {
|
||||||
|
if *buffer.spec() != self.spec {
|
||||||
|
Err(AudioDeviceError::AudioSpecMismatch)
|
||||||
|
} else {
|
||||||
|
self.commands.push_back(AudioCommand::PlayBuffer {
|
||||||
|
buffer: buffer.clone(),
|
||||||
|
loops,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_buffer_rc(
|
||||||
|
&mut self,
|
||||||
|
buffer: Rc<AudioBuffer>,
|
||||||
|
loops: bool,
|
||||||
|
) -> Result<(), AudioDeviceError> {
|
||||||
|
if *buffer.spec() != self.spec {
|
||||||
|
Err(AudioDeviceError::AudioSpecMismatch)
|
||||||
|
} else {
|
||||||
|
self.commands.push_back(AudioCommand::PlayRcBuffer {
|
||||||
|
buffer: buffer.clone(),
|
||||||
|
loops,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_buffer_on_channel(
|
||||||
|
&mut self,
|
||||||
|
channel_index: usize,
|
||||||
|
buffer: &AudioBuffer,
|
||||||
|
loops: bool,
|
||||||
|
) -> Result<(), AudioDeviceError> {
|
||||||
|
if *buffer.spec() != self.spec {
|
||||||
|
Err(AudioDeviceError::AudioSpecMismatch)
|
||||||
|
} else if channel_index >= NUM_CHANNELS {
|
||||||
|
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
|
||||||
|
} else {
|
||||||
|
self.commands.push_back(AudioCommand::PlayBufferOnChannel {
|
||||||
|
channel: channel_index,
|
||||||
|
buffer: buffer.clone(),
|
||||||
|
loops,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_buffer_rc_on_channel(
|
||||||
|
&mut self,
|
||||||
|
channel_index: usize,
|
||||||
|
buffer: Rc<AudioBuffer>,
|
||||||
|
loops: bool,
|
||||||
|
) -> Result<(), AudioDeviceError> {
|
||||||
|
if *buffer.spec() != self.spec {
|
||||||
|
Err(AudioDeviceError::AudioSpecMismatch)
|
||||||
|
} else if channel_index >= NUM_CHANNELS {
|
||||||
|
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
|
||||||
|
} else {
|
||||||
|
self.commands.push_back(AudioCommand::PlayRcBufferOnChannel {
|
||||||
|
channel: channel_index,
|
||||||
|
buffer: buffer.clone(),
|
||||||
|
loops,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_generator(
|
||||||
|
&mut self,
|
||||||
|
generator: Box<dyn AudioGenerator>,
|
||||||
|
loops: bool,
|
||||||
|
) -> Result<(), AudioDeviceError> {
|
||||||
|
self.commands.push_back(AudioCommand::PlayGenerator { generator, loops });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_generator_on_channel(
|
||||||
|
&mut self,
|
||||||
|
channel_index: usize,
|
||||||
|
generator: Box<dyn AudioGenerator>,
|
||||||
|
loops: bool,
|
||||||
|
) -> Result<(), AudioDeviceError> {
|
||||||
|
self.commands.push_back(AudioCommand::PlayGeneratorOnChannel {
|
||||||
|
channel: channel_index,
|
||||||
|
generator,
|
||||||
|
loops,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(&mut self, device: &mut AudioDevice) -> Result<(), AudioDeviceError> {
|
||||||
|
loop {
|
||||||
|
if let Some(command) = self.commands.pop_front() {
|
||||||
|
use AudioCommand::*;
|
||||||
|
match command {
|
||||||
|
StopChannel(channel_index) => {
|
||||||
|
device.stop_channel(channel_index)?;
|
||||||
|
},
|
||||||
|
StopAllChannels => {
|
||||||
|
device.stop_all();
|
||||||
|
},
|
||||||
|
PlayBuffer { buffer, loops } => {
|
||||||
|
device.play_buffer(&buffer, loops)?;
|
||||||
|
}
|
||||||
|
PlayRcBuffer { buffer, loops } => {
|
||||||
|
device.play_buffer(&buffer, loops)?;
|
||||||
|
},
|
||||||
|
PlayBufferOnChannel { channel, buffer, loops } => {
|
||||||
|
device.play_buffer_on_channel(channel, &buffer, loops)?;
|
||||||
|
}
|
||||||
|
PlayRcBufferOnChannel { channel, buffer, loops } => {
|
||||||
|
device.play_buffer_on_channel(channel, &buffer, loops)?;
|
||||||
|
},
|
||||||
|
PlayGenerator { generator, loops } => {
|
||||||
|
device.play_generator(generator, loops);
|
||||||
|
},
|
||||||
|
PlayGeneratorOnChannel { channel, generator, loops } => {
|
||||||
|
device.play_generator_on_channel(channel, generator, loops);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -252,6 +252,7 @@ impl SystemBuilder {
|
||||||
};
|
};
|
||||||
let mut audio = Audio::new(audio_spec, &sdl_audio_subsystem)?;
|
let mut audio = Audio::new(audio_spec, &sdl_audio_subsystem)?;
|
||||||
audio.resume();
|
audio.resume();
|
||||||
|
let mut audio_queue = AudioQueue::from(&audio);
|
||||||
|
|
||||||
// create input device objects, exposed to the application
|
// create input device objects, exposed to the application
|
||||||
|
|
||||||
|
@ -269,6 +270,7 @@ impl SystemBuilder {
|
||||||
sdl_event_pump,
|
sdl_event_pump,
|
||||||
texture_pixels,
|
texture_pixels,
|
||||||
audio,
|
audio,
|
||||||
|
audio_queue,
|
||||||
video: framebuffer,
|
video: framebuffer,
|
||||||
palette,
|
palette,
|
||||||
font,
|
font,
|
||||||
|
@ -302,6 +304,7 @@ pub struct System {
|
||||||
next_tick: i64,
|
next_tick: i64,
|
||||||
|
|
||||||
pub audio: Audio,
|
pub audio: Audio,
|
||||||
|
pub audio_queue: AudioQueue,
|
||||||
|
|
||||||
/// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever
|
/// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever
|
||||||
/// [`System::display`] is called. Regardless of the actual window size, this bitmap is always
|
/// [`System::display`] is called. Regardless of the actual window size, this bitmap is always
|
||||||
|
|
Loading…
Reference in a new issue