add doc comments for audio functionality

This commit is contained in:
Gered 2022-07-10 14:31:23 -04:00
parent 44984c716c
commit b856ab2fc6
6 changed files with 154 additions and 0 deletions

View file

@ -10,6 +10,7 @@ pub enum AudioBufferError {
ConversionError(String),
}
/// Holds audio sample data that can be played via [`AudioDevice`].
#[derive(Debug, Clone)]
pub struct AudioBuffer {
spec: AudioSpec,
@ -17,6 +18,8 @@ pub struct AudioBuffer {
}
impl AudioBuffer {
/// Creates and returns a new, empty, [`AudioBuffer`] that will hold audio sample data in the
/// spec/format given.
pub fn new(spec: AudioSpec) -> Self {
AudioBuffer {
spec,
@ -24,11 +27,14 @@ impl AudioBuffer {
}
}
/// Returns the spec of the audio sample data that this buffer contains.
#[inline]
pub fn spec(&self) -> &AudioSpec {
&self.spec
}
/// Converts the audio sample data in this buffer to the spec given, returning the newly
/// converted buffer.
pub fn convert(self, to_spec: &AudioSpec) -> Result<Self, AudioBufferError> {
if self.spec == *to_spec {
Ok(self)

View file

@ -192,6 +192,8 @@ impl DataChunk {
}
impl AudioBuffer {
/// Loads the bytes of a WAV file into an [`AudioBuffer`]. The returned buffer will be in its
/// original format and may need to be converted before it can be played.
pub fn load_wav_bytes<T: ReadBytesExt + Seek>(reader: &mut T) -> Result<AudioBuffer, WavError> {
let file_size = reader.stream_size()?;
@ -285,6 +287,8 @@ impl AudioBuffer {
Ok(audio_buffer)
}
/// Loads a WAV file into an [`AudioBuffer`]. The returned buffer will be in its original
/// format and may need to be converted before it can be played.
pub fn load_wav_file(path: &Path) -> Result<AudioBuffer, WavError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);

View file

@ -5,12 +5,27 @@ use thiserror::Error;
use crate::audio::*;
/// Represents a "channel" of audio playback that will be mixed together with all of the other
/// actively playing audio channels to get the final audio playback.
pub struct AudioChannel {
/// Whether the channel is currently playing or not.
pub playing: bool,
/// Whether this channel is playing on a loop or not. If not, once the end of the [`data`]
/// buffer is reached, or the [`AudioGenerator::gen_sample`] method returns `None`, playback
/// on this channel will automatically stop and [`playing`] will be changed to `false`.
pub loops: bool,
/// The audio data buffer (samples) that this channel will play from, **only** if [`generator`]
/// is `None`.
pub data: Vec<u8>,
/// An [`AudioGenerator`] instance that will be used to dynamically generate audio data to play
/// on this channel _instead of_ playing from [`data`]. Set this to `None` to play from audio
/// data in [`data`] instead.
pub generator: Option<Box<dyn AudioGenerator>>,
/// The volume level to play this channel at. 1.0 is "normal", 0.0 is completely silent.
pub volume: f32,
/// The current playback position (index). 0 is the start of playback. The end position is
/// either the (current) size of the [`data`] buffer or dependant on the implementation of this
/// channel's current [`generator`] if not `None`.
pub position: usize,
}
@ -26,6 +41,7 @@ impl AudioChannel {
}
}
/// Returns the audio sample for the given position, or `None` if that position is invalid.
#[inline]
fn data_at(&mut self, position: usize) -> Option<u8> {
if let Some(generator) = &mut self.generator {
@ -79,6 +95,8 @@ impl AudioChannel {
}
}
/// Resets the audio channel to a "blank slate", clearing the audio buffer, setting no current
/// audio generator, and turning playback off.
#[inline]
pub fn reset(&mut self) {
self.data.clear();
@ -87,6 +105,9 @@ impl AudioChannel {
self.playing = false;
}
/// Copies the data from the given audio buffer into this channel's buffer (clearing it first,
/// and extending the size of the buffer if necessary) and then begins playback from position 0.
/// This also sets the associated [`generator`] to `None`.
#[inline]
pub fn play_buffer(&mut self, buffer: &AudioBuffer, loops: bool) {
self.data.clear();
@ -97,6 +118,8 @@ impl AudioChannel {
self.loops = loops;
}
/// Begins playback on this channel from the given [`AudioGenerator`] instance from position 0.
/// This also clears the existing audio buffer contents.
#[inline]
pub fn play_generator(&mut self, generator: Box<dyn AudioGenerator>, loops: bool) {
self.data.clear();
@ -106,11 +129,15 @@ impl AudioChannel {
self.loops = loops;
}
/// Returns true if this channel has something that can be played back currently.
#[inline]
pub fn is_playable(&self) -> bool {
!self.data.is_empty() || self.generator.is_some()
}
/// Begins playback on this channel, only if playback is currently possible with its current
/// state (if it has some sample data in the buffer or if an [`AudioGenerator`] is set).
/// Resets the position to 0 if playback is started and returns true, otherwise returns false.
#[inline]
pub fn play(&mut self, loops: bool) -> bool {
if self.is_playable() {
@ -123,6 +150,7 @@ impl AudioChannel {
}
}
/// Stops playback on this channel.
#[inline]
pub fn stop(&mut self) {
self.playing = false;
@ -140,12 +168,17 @@ pub enum AudioDeviceError {
ChannelIndexOutOfRange(usize),
}
/// Represents the audio device and performs mixing of all of the [`AudioChannel`]s that are
/// currently playing. You should not be creating this manually, but obtaining it as needed via
/// [`Audio::lock`].
pub struct AudioDevice {
spec: AudioSpec,
channels: Vec<AudioChannel>,
pub volume: f32,
}
/// SDL audio callback implementation which performs audio mixing, generating the final sample data
/// that will be played by the system's audio device.
impl AudioCallback for AudioDevice {
type Channel = u8;
@ -164,6 +197,7 @@ impl AudioCallback for AudioDevice {
}
impl AudioDevice {
/// Creates a new [`AudioDevice`] instance, using the given spec as its playback format.
pub fn new(spec: AudioSpec) -> Self {
let mut channels = Vec::new();
for _ in 0..NUM_CHANNELS {
@ -176,16 +210,21 @@ impl AudioDevice {
}
}
/// Returns the spec that this device is currently set to play. All audio to be played via
/// this device must be pre-converted to match this spec!
#[inline]
pub fn spec(&self) -> &AudioSpec {
&self.spec
}
/// Returns true if any of the audio channels are currently playing, false otherwise.
#[inline]
pub fn is_playing(&self) -> bool {
self.channels.iter().any(|channel| channel.playing)
}
/// Stops the specified channel's playback, or does nothing if that channel was not currently
/// playing. This does not affect the channel's other state (data buffer, etc).
pub fn stop_channel(&mut self, channel_index: usize) -> Result<(), AudioDeviceError> {
if channel_index >= NUM_CHANNELS {
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
@ -195,12 +234,17 @@ impl AudioDevice {
}
}
/// Stops playback of all channels.
pub fn stop_all(&mut self) {
for channel in self.channels.iter_mut() {
channel.stop();
}
}
/// Tries to play the given [`AudioBuffer`] on the first channel found that is not already
/// playing. If a free channel is found, playback will be started by copying the buffer's
/// contents to the channel. The index of the channel is returned. If playback was not started
/// because no channel is free currently, then `None` is returned.
pub fn play_buffer(
&mut self,
buffer: &AudioBuffer,
@ -218,6 +262,8 @@ impl AudioDevice {
}
}
/// Plays the given [`AudioBuffer`] on the specified channel. Whatever that channel was playing
/// will be interrupted and replaced with a copy of the given buffer's data.
pub fn play_buffer_on_channel(
&mut self,
channel_index: usize,
@ -234,6 +280,10 @@ impl AudioDevice {
}
}
/// Tries to play the given [`AudioGenerator`] on the first channel found that is not already
/// playing. If a free channel is found, playback will be started and the index of the channel
/// will be returned. If playback was not started because no channel is free currently, then
/// `None` is returned.
pub fn play_generator(
&mut self,
generator: Box<dyn AudioGenerator>,
@ -247,6 +297,8 @@ impl AudioDevice {
}
}
/// Plays the given [`AudioGenerator`] on the specified channel. Whatever that channel was
/// playing will be interrupted and replaced.
pub fn play_generator_on_channel(
&mut self,
channel_index: usize,
@ -261,41 +313,51 @@ impl AudioDevice {
}
}
/// Returns an iterator of any [`AudioChannel`]s that are currently playing.
#[inline]
pub fn playing_channels_iter(&mut self) -> impl Iterator<Item = &AudioChannel> {
self.channels.iter().filter(|channel| channel.playing)
}
/// Returns an iterator of mutable [`AudioChannel`]s that are currently playing.
#[inline]
pub fn playing_channels_iter_mut(&mut self) -> impl Iterator<Item = &mut AudioChannel> {
self.channels.iter_mut().filter(|channel| channel.playing)
}
/// Returns an iterator of [`AudioChannel`]s that are not currently playing.
#[inline]
pub fn stopped_channels_iter(&mut self) -> impl Iterator<Item = &AudioChannel> {
self.channels.iter().filter(|channel| !channel.playing)
}
/// Returns an iterator of mutable [`AudioChannel`]s that are not currently playing.
#[inline]
pub fn stopped_channels_iter_mut(&mut self) -> impl Iterator<Item = &mut AudioChannel> {
self.channels.iter_mut().filter(|channel| !channel.playing)
}
/// Returns an iterator of all [`AudioChannel`]s.
#[inline]
pub fn channels_iter(&mut self) -> impl Iterator<Item = &AudioChannel> {
self.channels.iter()
}
/// Returns an iterator of all [`AudioChannel`]s as mutable references.
#[inline]
pub fn channels_iter_mut(&mut self) -> impl Iterator<Item = &mut AudioChannel> {
self.channels.iter_mut()
}
/// Returns a reference to the specified [`AudioChannel`] or `None` if the index specified
/// is not valid.
#[inline]
pub fn get(&self, index: usize) -> Option<&AudioChannel> {
self.channels.get(index)
}
/// Returns a mutable reference to the specified [`AudioChannel`] or `None` if the index
/// specified is not valid.
#[inline]
pub fn get_mut(&mut self, index: usize) -> Option<&mut AudioChannel> {
self.channels.get_mut(index)
@ -305,6 +367,8 @@ impl AudioDevice {
impl Index<usize> for AudioDevice {
type Output = AudioChannel;
/// Returns a reference to the specified [`AudioChannel`] or panics if the index specified is
/// not valid.
#[inline]
fn index(&self, index: usize) -> &Self::Output {
self.get(index).unwrap()
@ -312,6 +376,8 @@ impl Index<usize> for AudioDevice {
}
impl IndexMut<usize> for AudioDevice {
/// Returns a mutable reference to the specified [`AudioChannel`] or panics if the index
/// specified is not valid.
#[inline]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
self.get_mut(index).unwrap()

View file

@ -10,6 +10,7 @@ pub mod buffer;
pub mod device;
pub mod queue;
/// The number of simultaneously playing audio channels supported by this library currently.
pub const NUM_CHANNELS: usize = 8;
pub const AUDIO_FREQUENCY_44KHZ: u32 = 44100;
@ -18,11 +19,15 @@ pub const AUDIO_FREQUENCY_11KHZ: u32 = 11025;
pub const SILENCE: u8 = AudioFormatNum::SILENCE;
/// The target audio frequency supported by this library currently.
pub const TARGET_AUDIO_FREQUENCY: u32 = AUDIO_FREQUENCY_22KHZ;
/// The number of channels per audio buffer supported by this library currently.
pub const TARGET_AUDIO_CHANNELS: u8 = 1;
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Represents an "audio specification" for an audio buffer or the audio device itself. Useful
/// to know what format an audio buffer is in and to specify conversion formats, etc.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct AudioSpec {
frequency: u32,
@ -31,6 +36,13 @@ pub struct AudioSpec {
}
impl AudioSpec {
/// Creates a new `AudioSpec` with the properties specified.
///
/// # Arguments
///
/// * `frequency`: the frequency of the audio
/// * `channels`: the number of channels of the audio (e.g. 1 = mono, 2 = stereo, etc)
/// * `format`: indicates the format of the bytes making up the audio buffer.
pub fn new(frequency: u32, channels: u8, format: AudioFormat) -> Self {
AudioSpec {
frequency,
@ -49,6 +61,8 @@ impl AudioSpec {
self.channels
}
/// An SDL2 [`sdl2::audio::AudioFormat`] value indicating the audio format of the bytes making
/// up an audio buffer.
#[inline]
pub fn format(&self) -> AudioFormat {
self.format
@ -57,7 +71,12 @@ impl AudioSpec {
//////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE: this is currently hardcoded such that 8-bit samples must always be used!
/// Used to implement custom/dynamic audio generation.
pub trait AudioGenerator: Send {
/// Generates and returns the sample for the given playback position. `None` is returned if
/// there is no sample for that position (e.g. it might be past the "end").
fn gen_sample(&mut self, position: usize) -> Option<u8>;
}
@ -69,12 +88,20 @@ pub enum AudioError {
OpenDeviceFailed(String),
}
/// Top-level abstraction over the system's audio output device. To play audio or change other
/// playback properties, you will need to lock the audio device via [`Audio::lock`] to obtain an
/// [`AudioDevice`].
pub struct Audio {
spec: AudioSpec,
sdl_audio_device: sdl2::audio::AudioDevice<AudioDevice>,
}
impl Audio {
/// Creates a new [`Audio`] instance, wrapping the given SDL [`sdl2::audio::AudioSubsystem`].
/// The `desired_spec` given specifies the target audio playback format.
///
/// Ideally, you should not be creating an instance of this yourself and should just use the
/// one provided by [`crate::system::System`].
pub fn new(
desired_spec: AudioSpecDesired,
sdl_audio_subsystem: &AudioSubsystem,
@ -106,26 +133,36 @@ impl Audio {
}
}
/// Returns current audio device's audio specification/format for playback. All [`AudioBuffer`]s
/// that are to be used for playback must be converted to match this before they can be played.
#[inline]
pub fn spec(&self) -> &AudioSpec {
&self.spec
}
/// Returns the current status of the audio device (e.g. whether it is paused, stopped, etc).
#[inline]
pub fn status(&self) -> sdl2::audio::AudioStatus {
self.sdl_audio_device.status()
}
/// Pauses all audio playback.
#[inline]
pub fn pause(&mut self) {
self.sdl_audio_device.pause()
}
/// Resumes all audio playback.
#[inline]
pub fn resume(&mut self) {
self.sdl_audio_device.resume()
}
/// Locks the audio device so that new audio data can be provided or playback altered. A
/// [`AudioDevice`] instance is returned on successful lock which can be used to interact with
/// the actual system's audio playback. The audio device is unlocked once this instance is
/// dropped. Ideally, you will want to keep the audio device for **as _short_ a time as
/// possible!**
#[inline]
pub fn lock(&mut self) -> sdl2::audio::AudioDeviceLockGuard<AudioDevice> {
self.sdl_audio_device.lock()

View file

@ -35,12 +35,18 @@ pub enum AudioCommand {
},
}
/// A convenience abstraction that can be used to queue up commands to be issued to an
/// [`AudioDevice`]. This can be more useful to utilize in applications versus needing to directly
/// lock the [`AudioDevice`] and then determine what your application needs to do and issue those
/// commands that time. [`AudioQueue`] lets you play/stop audio in more of a "fire-and-forget"
/// manner.
pub struct AudioQueue {
spec: AudioSpec,
commands: VecDeque<AudioCommand>,
}
impl AudioQueue {
/// Creates and returns a new [`AudioQueue`] instance.
pub fn new(audio: &Audio) -> Self {
AudioQueue {
spec: audio.spec,
@ -48,11 +54,15 @@ impl AudioQueue {
}
}
/// Returns the spec that this queue is currently set to play. All audio to be played via
/// this queue must be pre-converted to match this spec! This spec is a copy of the one that
/// was obtained from the [`Audio`] instance used to create this [`AudioQueue`].
#[inline]
pub fn spec(&self) -> &AudioSpec {
&self.spec
}
/// Queues a stop command for the given channel.
pub fn stop_channel(&mut self, channel_index: usize) -> Result<(), AudioDeviceError> {
if channel_index >= NUM_CHANNELS {
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
@ -62,10 +72,14 @@ impl AudioQueue {
}
}
/// Queues a command that will stop playback on all channels.
pub fn stop_all(&mut self) {
self.commands.push_back(AudioCommand::StopAllChannels);
}
/// Queues a command to play a copy of the given [`AudioBuffer`]'s data. The buffer will be
/// played on the first channel found that is not already playing. If all channels are already
/// playing, then nothing will be done.
pub fn play_buffer(
&mut self,
buffer: &AudioBuffer,
@ -82,6 +96,10 @@ impl AudioQueue {
}
}
/// Queues a command to play the given [`AudioBuffer`]'s data. The buffer will be played on
/// the first channel found that is not already playing. If all channels are already playing,
/// then nothing will be done. This method is more performant than [`AudioQueue::play_buffer`],
/// as that method will always immediately copy the given buffer to create the queued command.
pub fn play_buffer_rc(
&mut self,
buffer: Rc<AudioBuffer>,
@ -98,6 +116,9 @@ impl AudioQueue {
}
}
/// Queues a command to play a copy of the given [`AudioBuffer`]'s data on the channel
/// specified. Whatever that channel was playing will be interrupted to begin playing this
/// buffer.
pub fn play_buffer_on_channel(
&mut self,
channel_index: usize,
@ -118,6 +139,10 @@ impl AudioQueue {
}
}
/// Queues a command to play the given [`AudioBuffer`]'s data on the channel specified. Whatever
/// that channel was playing will be interrupted to begin playing this buffer. This method is
/// more performant than [`AudioQueue::play_buffer_on_channel`], as that method will always
/// immediately copy the given buffer to create the queued command.
pub fn play_buffer_rc_on_channel(
&mut self,
channel_index: usize,
@ -138,6 +163,8 @@ impl AudioQueue {
}
}
/// Queues a command to play the given [`AudioGenerator`] on the first channel found that is
/// not already playing. If all channels are already playing, then nothing will be done.
pub fn play_generator(
&mut self,
generator: Box<dyn AudioGenerator>,
@ -147,6 +174,8 @@ impl AudioQueue {
Ok(())
}
/// Queues a command to play the given [`AudioGenerator`] on the channel specified. Whatever
/// that channel was playing will be interrupted to begin playing this generator.
pub fn play_generator_on_channel(
&mut self,
channel_index: usize,
@ -161,6 +190,8 @@ impl AudioQueue {
Ok(())
}
/// Flushes the queued commands, issuing them in the same order they were created, to the
/// given [`AudioDevice`].
pub fn apply_to_device(&mut self, device: &mut AudioDevice) -> Result<(), AudioDeviceError> {
loop {
if let Some(command) = self.commands.pop_front() {
@ -197,6 +228,10 @@ impl AudioQueue {
}
}
/// Flushes the queued commands, issuing them in the same order they were created, to the
/// given [`Audio`] instance. This method automatically handles obtaining a locked
/// [`AudioDevice`], and so is a bit more convenient to use if you don't actually need to
/// interact with the [`AudioDevice`] itself in your code.
pub fn apply(&mut self, audio: &mut Audio) -> Result<(), AudioDeviceError> {
let mut device = audio.lock();
self.apply_to_device(&mut device)

View file

@ -303,7 +303,13 @@ pub struct System {
target_framerate_delta: Option<i64>,
next_tick: i64,
/// An [`Audio`] instance that allows interacting with the system's audio output device.
pub audio: Audio,
/// An [`AudioQueue`] instance that can queue up playback/stop commands to be issued to the
/// system's [`Audio`] instance a bit more flexibly. If you use this, your application must
/// manually call [`AudioQueue::apply`] or [`AudioQueue::apply_to_device`] in your loop to
/// flush the queued commands, otherwise this queue will not do anything.
pub audio_queue: AudioQueue,
/// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever