mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-11 17:41:33 +11:00
commit
a01c0f9e4e
|
@ -4,7 +4,7 @@
|
||||||
use agb::fixnum::Num;
|
use agb::fixnum::Num;
|
||||||
use agb::input::{Button, ButtonController, Tri};
|
use agb::input::{Button, ButtonController, Tri};
|
||||||
use agb::sound::mixer::SoundChannel;
|
use agb::sound::mixer::SoundChannel;
|
||||||
use agb::{include_wav, Gba};
|
use agb::{include_wav, Gba, fixnum::num};
|
||||||
|
|
||||||
// Music - "Dead Code" by Josh Woodward, free download at http://joshwoodward.com
|
// Music - "Dead Code" by Josh Woodward, free download at http://joshwoodward.com
|
||||||
const DEAD_CODE: &[u8] = include_wav!("examples/JoshWoodward-DeadCode.wav");
|
const DEAD_CODE: &[u8] = include_wav!("examples/JoshWoodward-DeadCode.wav");
|
||||||
|
@ -25,26 +25,26 @@ fn main(mut gba: Gba) -> ! {
|
||||||
|
|
||||||
{
|
{
|
||||||
if let Some(channel) = mixer.channel(&channel_id) {
|
if let Some(channel) = mixer.channel(&channel_id) {
|
||||||
let half: Num<i16, 4> = Num::new(1) / 2;
|
let half: Num<i16, 4> = num!(0.5);
|
||||||
let half_usize: Num<usize, 8> = Num::new(1) / 2;
|
let half_usize: Num<usize, 8> = num!(0.5);
|
||||||
match input.x_tri() {
|
match input.x_tri() {
|
||||||
Tri::Negative => channel.panning(-half),
|
Tri::Negative => channel.panning(-half),
|
||||||
Tri::Zero => channel.panning(0.into()),
|
Tri::Zero => channel.panning(0),
|
||||||
Tri::Positive => channel.panning(half),
|
Tri::Positive => channel.panning(half),
|
||||||
};
|
};
|
||||||
|
|
||||||
match input.y_tri() {
|
match input.y_tri() {
|
||||||
Tri::Negative => channel.playback(half_usize.change_base() + 1),
|
Tri::Negative => channel.playback(half_usize.change_base() + 1),
|
||||||
Tri::Zero => channel.playback(1.into()),
|
Tri::Zero => channel.playback(1),
|
||||||
Tri::Positive => channel.playback(half_usize),
|
Tri::Positive => channel.playback(half_usize),
|
||||||
};
|
};
|
||||||
|
|
||||||
if input.is_pressed(Button::L) {
|
if input.is_pressed(Button::L) {
|
||||||
channel.volume(half);
|
channel.volume(half);
|
||||||
} else if input.is_pressed(Button::R) {
|
} else if input.is_pressed(Button::R) {
|
||||||
channel.volume(20.into()); // intentionally introduce clipping
|
channel.volume(20); // intentionally introduce clipping
|
||||||
} else {
|
} else {
|
||||||
channel.volume(1.into());
|
channel.volume(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,101 @@
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
//! # agb mixer
|
||||||
|
//!
|
||||||
|
//! The agb software mixer allows for high performance playing of background music
|
||||||
|
//! and sound effects.
|
||||||
|
//!
|
||||||
|
//! Most games will need some form of sound effects or background music. The GBA has
|
||||||
|
//! no hardware sound mixer, so in order to play more than one sound at once, you
|
||||||
|
//! have to use a software mixer.
|
||||||
|
//!
|
||||||
|
//! agb's software mixer allows for up to 8 simultaneous sounds played at once at
|
||||||
|
//! various speeds and volumes.
|
||||||
|
//!
|
||||||
|
//! # Concepts
|
||||||
|
//!
|
||||||
|
//! The mixer runs at a fixed frequency which is determined at compile time by enabling
|
||||||
|
//! certain features within the crate. The following features are currently available:
|
||||||
|
//!
|
||||||
|
//! | Feature | Frequency |
|
||||||
|
//! |---------|-----------|
|
||||||
|
//! | none | 10512Hz |
|
||||||
|
//! | freq18157 | 18157Hz |
|
||||||
|
//! | freq32768[^32768Hz] | 32768Hz |
|
||||||
|
//!
|
||||||
|
//! All wav files you use within your application / game must use this _exact_ frequency.
|
||||||
|
//! You will get a compile error if you use the incorrect frequency for your file.
|
||||||
|
//!
|
||||||
|
//! The mixer can play both mono and stereo sounds, but only mono sound effects can have
|
||||||
|
//! effects applied to them (such as changing the speed at which they play or the panning).
|
||||||
|
//! Since the sound mixer runs in software, you must do some sound mixing every frame.
|
||||||
|
//!
|
||||||
|
//! ## Creating the mixer
|
||||||
|
//!
|
||||||
|
//! To create a sound mixer, you will need to get it out of the [`Gba`](crate::Gba) struct
|
||||||
|
//! as follows:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! let mut mixer = gba.mixer.mixer();
|
||||||
|
//! mixer.enable();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Doing the per-frame work
|
||||||
|
//!
|
||||||
|
//! Then, you have a choice of whether you want to use interrupts or do the buffer swapping
|
||||||
|
//! yourself after a vblank interrupt. If you are using 32768Hz as the frequency of your
|
||||||
|
//! files, you _must_ use the interrupt version.
|
||||||
|
//!
|
||||||
|
//! Without interrupts:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! // Somewhere in your main loop:
|
||||||
|
//! mixer.frame();
|
||||||
|
//! vblank.wait_for_vblank();
|
||||||
|
//! mixer.after_vblank();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Or with interrupts:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! // outside your main loop, close to initialisation
|
||||||
|
//! let _mixer_interrupt = mixer.setup_interrupt_handler();
|
||||||
|
//!
|
||||||
|
//! // inside your main loop
|
||||||
|
//! mixer.frame();
|
||||||
|
//! vblank.wait_for_vblank();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Despite being high performance, the mixer still takes a sizable portion of CPU time (6-10%
|
||||||
|
//! depending on number of channels and frequency) to do the per-frame tasks, so should be done
|
||||||
|
//! towards the end of the frame time (just before waiting for vblank) in order to give as much
|
||||||
|
//! time during vblank as possible for rendering related tasks.
|
||||||
|
//!
|
||||||
|
//! ## Loading a sample
|
||||||
|
//!
|
||||||
|
//! To load a sample, you must have it in `wav` format (both stereo and mono work) at exactly the
|
||||||
|
//! selected frequency based on the features enabled in the agb crate.
|
||||||
|
//!
|
||||||
|
//! Use the [`include_wav!`](crate::include_wav) macro in order to load the sound. This will produce
|
||||||
|
//! an error if your wav file is of the wrong frequency.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! // Outside your main function in global scope:
|
||||||
|
//! const MY_CRAZY_SOUND: &[u8] = include_wav!("sfx/my_crazy_sound.wav");
|
||||||
|
//!
|
||||||
|
//! // Then to play the sound:
|
||||||
|
//! let mut channel = SoundChannel::new(MY_CRAZY_SOUND);
|
||||||
|
//! channel.stereo();
|
||||||
|
//! let _ = mixer.play_sound(channel); // we don't mind if this sound doesn't actually play
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! See the [`SoundChannel`] struct for more details on how you can configure the sounds to play.
|
||||||
|
//!
|
||||||
|
//! Once you have run [`play_sound`](Mixer::play_sound), the mixer will play that sound until
|
||||||
|
//! it has finished.
|
||||||
|
//!
|
||||||
|
//! [^32768Hz]: You must use interrupts when using 32768Hz
|
||||||
|
|
||||||
mod hw;
|
mod hw;
|
||||||
mod sw_mixer;
|
mod sw_mixer;
|
||||||
|
|
||||||
|
@ -6,6 +104,8 @@ pub use sw_mixer::Mixer;
|
||||||
|
|
||||||
use crate::fixnum::Num;
|
use crate::fixnum::Num;
|
||||||
|
|
||||||
|
/// Controls access to the mixer and the underlying hardware it uses. A zero sized type that
|
||||||
|
/// ensures that mixer access is exclusive.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct MixerController {}
|
pub struct MixerController {}
|
||||||
|
|
||||||
|
@ -14,6 +114,7 @@ impl MixerController {
|
||||||
MixerController {}
|
MixerController {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a [`Mixer`] in order to start producing sounds.
|
||||||
pub fn mixer(&mut self) -> Mixer {
|
pub fn mixer(&mut self) -> Mixer {
|
||||||
Mixer::new()
|
Mixer::new()
|
||||||
}
|
}
|
||||||
|
@ -25,6 +126,52 @@ enum SoundPriority {
|
||||||
Low,
|
Low,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes one sound which should be playing. This could be a sound effect or
|
||||||
|
/// the background music. Use the factory methods on this to modify how it is played.
|
||||||
|
///
|
||||||
|
/// You _must_ set stereo sounds with [`.stereo()`](SoundChannel::stereo) or it will play as mono and at
|
||||||
|
/// half the intended speed.
|
||||||
|
///
|
||||||
|
/// SoundChannels are very cheap to create, so don't worry about creating a brand new
|
||||||
|
/// one for every single sound you want to play.
|
||||||
|
///
|
||||||
|
/// SoundChannels can be either 'low priority' or 'high priority'. A high priority
|
||||||
|
/// sound channel will override 'low priority' sound channels which are already playing
|
||||||
|
/// to ensure that it is always running. A 'low priority' sound channel will not override
|
||||||
|
/// any other channel.
|
||||||
|
///
|
||||||
|
/// This is because you can only play up to 8 channels at once, and so high priority channels
|
||||||
|
/// are prioritised over low priority channels to ensure that sounds that you always want
|
||||||
|
/// playing will always play.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ## Playing background music (stereo)
|
||||||
|
///
|
||||||
|
/// Background music is generally considered 'high priority' because you likely want it to
|
||||||
|
/// play regardless of whether you have lots of sound effects playing. You create a high
|
||||||
|
/// priority sound channel using [`new_high_priority`](SoundChannel::new_high_priority).
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// // in global scope:
|
||||||
|
/// const MY_BGM: [u8] = include_wav!("sfx/my_bgm.wav");
|
||||||
|
///
|
||||||
|
/// // somewhere in code
|
||||||
|
/// let mut bgm = SoundChannel::new_high_priority(MY_BGM);
|
||||||
|
/// bgm.stereo().should_loop();
|
||||||
|
/// let _ = mixer.play_sound(bgm);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Playing a sound effect
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// // in global scope:
|
||||||
|
/// const JUMP_SOUND: [u8] = include_wav!("sfx/jump_sound.wav");
|
||||||
|
///
|
||||||
|
/// // somewhere in code
|
||||||
|
/// let jump_sound = SoundChannel::new(MY_JUMP_SOUND);
|
||||||
|
/// let _ = mixer.play_sound(jump_sound);
|
||||||
|
/// ```
|
||||||
pub struct SoundChannel {
|
pub struct SoundChannel {
|
||||||
data: &'static [u8],
|
data: &'static [u8],
|
||||||
pos: Num<usize, 8>,
|
pos: Num<usize, 8>,
|
||||||
|
@ -42,6 +189,23 @@ pub struct SoundChannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SoundChannel {
|
impl SoundChannel {
|
||||||
|
/// Creates a new low priority [`SoundChannel`].
|
||||||
|
///
|
||||||
|
/// A low priority sound channel will be overridden by a high priority one if
|
||||||
|
/// the mixer runs out of channels.
|
||||||
|
///
|
||||||
|
/// Low priority sound channels are intended for sound effects.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// // in global scope:
|
||||||
|
/// const JUMP_SOUND: [u8] = include_wav!("sfx/jump_sound.wav");
|
||||||
|
///
|
||||||
|
/// // somewhere in code
|
||||||
|
/// let jump_sound = SoundChannel::new(MY_JUMP_SOUND);
|
||||||
|
/// let _ = mixer.play_sound(jump_sound);
|
||||||
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(data: &'static [u8]) -> Self {
|
pub fn new(data: &'static [u8]) -> Self {
|
||||||
|
@ -58,6 +222,26 @@ impl SoundChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new high priority [`SoundChannel`].
|
||||||
|
///
|
||||||
|
/// A high priority sound channel will override low priority ones if
|
||||||
|
/// the mixer runs out of channels. They will also never be overriden
|
||||||
|
/// by other high priority channels.
|
||||||
|
///
|
||||||
|
/// High priority channels are intended for background music and for
|
||||||
|
/// important, game breaking sound effects if you have any.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// // in global scope:
|
||||||
|
/// const MY_BGM: [u8] = include_wav!("sfx/my_bgm.wav");
|
||||||
|
///
|
||||||
|
/// // somewhere in code
|
||||||
|
/// let mut bgm = SoundChannel::new_high_priority(MY_BGM);
|
||||||
|
/// bgm.stereo().should_loop();
|
||||||
|
/// let _ = mixer.play_sound(bgm);
|
||||||
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new_high_priority(data: &'static [u8]) -> Self {
|
pub fn new_high_priority(data: &'static [u8]) -> Self {
|
||||||
|
@ -74,20 +258,36 @@ impl SoundChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets that a sound channel should loop back to the start once it has
|
||||||
|
/// finished playing rather than stopping.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn should_loop(&mut self) -> &mut Self {
|
pub fn should_loop(&mut self) -> &mut Self {
|
||||||
self.should_loop = true;
|
self.should_loop = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the speed at which this should channel should be played. Defaults
|
||||||
|
/// to 1 with values between 0 and 1 being slower above 1 being faster.
|
||||||
|
///
|
||||||
|
/// Note that this only works for mono sounds. Stereo sounds will not change
|
||||||
|
/// how fast they play.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn playback(&mut self, playback_speed: Num<usize, 8>) -> &mut Self {
|
pub fn playback(&mut self, playback_speed: impl Into<Num<usize, 8>>) -> &mut Self {
|
||||||
self.playback_speed = playback_speed;
|
self.playback_speed = playback_speed.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets how far left or right the sound effect should be played.
|
||||||
|
/// Must be a value between -1 and 1 (inclusive). -1 means fully played
|
||||||
|
/// on the left, 1 fully on the right and values in between allowing for
|
||||||
|
/// partial levels.
|
||||||
|
///
|
||||||
|
/// Defaults to 0 (meaning equal on left and right) and doesn't affect stereo
|
||||||
|
/// sounds.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn panning(&mut self, panning: Num<i16, 4>) -> &mut Self {
|
pub fn panning(&mut self, panning: impl Into<Num<i16, 4>>) -> &mut Self {
|
||||||
|
let panning = panning.into();
|
||||||
|
|
||||||
debug_assert!(panning >= Num::new(-1), "panning value must be >= -1");
|
debug_assert!(panning >= Num::new(-1), "panning value must be >= -1");
|
||||||
debug_assert!(panning <= Num::new(1), "panning value must be <= 1");
|
debug_assert!(panning <= Num::new(1), "panning value must be <= 1");
|
||||||
|
|
||||||
|
@ -95,14 +295,23 @@ impl SoundChannel {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the volume for how loud the sound should be played. Note that if
|
||||||
|
/// you play it too loud, the sound will clip sounding pretty terrible.
|
||||||
|
///
|
||||||
|
/// Must be a value >= 0 and defaults to 1.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn volume(&mut self, volume: Num<i16, 4>) -> &mut Self {
|
pub fn volume(&mut self, volume: impl Into<Num<i16, 4>>) -> &mut Self {
|
||||||
|
let volume = volume.into();
|
||||||
|
|
||||||
assert!(volume >= Num::new(0), "volume must be >= 0");
|
assert!(volume >= Num::new(0), "volume must be >= 0");
|
||||||
|
|
||||||
self.volume = volume;
|
self.volume = volume;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets that the sound effect should be played in stereo. Not setting this
|
||||||
|
/// will result in the sound playing at half speed and mono. Setting this on
|
||||||
|
/// a mono sound will cause some interesting results (and play it at double speed).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn stereo(&mut self) -> &mut Self {
|
pub fn stereo(&mut self) -> &mut Self {
|
||||||
self.is_stereo = true;
|
self.is_stereo = true;
|
||||||
|
@ -110,6 +319,7 @@ impl SoundChannel {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stops the sound from playing.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn stop(&mut self) {
|
pub fn stop(&mut self) {
|
||||||
self.is_done = true;
|
self.is_done = true;
|
||||||
|
|
|
@ -30,6 +30,34 @@ extern "C" {
|
||||||
fn agb_rs__mixer_collapse(sound_buffer: *mut i8, input_buffer: *const Num<i16, 4>);
|
fn agb_rs__mixer_collapse(sound_buffer: *mut i8, input_buffer: *const Num<i16, 4>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The main software mixer struct.
|
||||||
|
///
|
||||||
|
/// Tracks which sound channels are currently playing and handles actually playing them.
|
||||||
|
/// You should not create this struct directly, instead creating it through the [`Gba`](crate::Gba)
|
||||||
|
/// struct as follows:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let mut mixer = gba.mixer.mixer();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// // Outside your main function in global scope:
|
||||||
|
/// const MY_CRAZY_SOUND: &[u8] = include_wav!("sfx/my_crazy_sound.wav");
|
||||||
|
///
|
||||||
|
/// // in your main function:
|
||||||
|
/// let mut mixer = gba.mixer.mixer();
|
||||||
|
/// let mut channel = SoundChannel::new(MY_CRAZY_SOUND);
|
||||||
|
/// channel.stereo();
|
||||||
|
/// let _ = mixer.play_sound(channel);
|
||||||
|
///
|
||||||
|
/// loop {
|
||||||
|
/// mixer.frame();
|
||||||
|
/// vblank.wait_for_vblank();
|
||||||
|
/// mixer.after_vblank();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub struct Mixer {
|
pub struct Mixer {
|
||||||
buffer: MixerBuffer,
|
buffer: MixerBuffer,
|
||||||
channels: [Option<SoundChannel>; 8],
|
channels: [Option<SoundChannel>; 8],
|
||||||
|
@ -38,6 +66,19 @@ pub struct Mixer {
|
||||||
timer: Timer,
|
timer: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A pointer to a currently playing channel.
|
||||||
|
///
|
||||||
|
/// This is used to modify a channel that is already playing.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
||||||
|
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
||||||
|
///
|
||||||
|
/// // Later, stop that particular channel
|
||||||
|
/// mixer.channel(bgm_channel_id).stop();
|
||||||
|
/// ```
|
||||||
pub struct ChannelId(usize, i32);
|
pub struct ChannelId(usize, i32);
|
||||||
|
|
||||||
impl Mixer {
|
impl Mixer {
|
||||||
|
@ -51,18 +92,50 @@ impl Mixer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enable sound output
|
||||||
|
///
|
||||||
|
/// You must call this method in order to start playing sound. You can do as much set up before
|
||||||
|
/// this as you like, but you will not get any sound out of the console until this method is called.
|
||||||
pub fn enable(&mut self) {
|
pub fn enable(&mut self) {
|
||||||
hw::set_timer_counter_for_frequency_and_enable(&mut self.timer, constants::SOUND_FREQUENCY);
|
hw::set_timer_counter_for_frequency_and_enable(&mut self.timer, constants::SOUND_FREQUENCY);
|
||||||
hw::set_sound_control_register_for_mixer();
|
hw::set_sound_control_register_for_mixer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Do post-vblank work. You can use either this or [`setup_interrupt_handler()`](Mixer::setup_interrupt_handler),
|
||||||
|
/// but not both. Note that this is not available if using 32768Hz sounds since those require more irregular timings.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// loop {
|
||||||
|
/// mixer.frame();
|
||||||
|
/// vblank.wait_for_vblank();
|
||||||
|
/// mixer.after_vblank();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[cfg(not(feature = "freq32768"))]
|
#[cfg(not(feature = "freq32768"))]
|
||||||
pub fn after_vblank(&mut self) {
|
pub fn after_vblank(&mut self) {
|
||||||
free(|cs| self.buffer.swap(cs));
|
free(|cs| self.buffer.swap(cs));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Note that if you set up an interrupt handler, you should not call `after_vblank` any more
|
/// Use timer interrupts to do the timing required for ensuring the music runs smoothly.
|
||||||
/// You are still required to call `frame`
|
///
|
||||||
|
/// Note that if you set up an interrupt handler, you should not call [`after_vblank`](Mixer::after_vblank) any more
|
||||||
|
/// You are still required to call [`frame`](Mixer::frame).
|
||||||
|
///
|
||||||
|
/// This is required if using 32768Hz music, but optional for other frequencies.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// // you must set this to a named variable to ensure that the scope is long enough
|
||||||
|
/// let _mixer_interrupt = mixer.setup_interrupt_handler();
|
||||||
|
///
|
||||||
|
/// loop {
|
||||||
|
/// mixer.frame();
|
||||||
|
/// vblank.wait_for_vblank();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn setup_interrupt_handler(&self) -> InterruptHandler<'_> {
|
pub fn setup_interrupt_handler(&self) -> InterruptHandler<'_> {
|
||||||
let mut timer1 = unsafe { Timer::new(1) };
|
let mut timer1 = unsafe { Timer::new(1) };
|
||||||
timer1
|
timer1
|
||||||
|
@ -75,6 +148,22 @@ impl Mixer {
|
||||||
add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs))
|
add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Do the CPU intensive mixing for the next frame's worth of data.
|
||||||
|
///
|
||||||
|
/// This is where almost all of the CPU time for the mixer is done, and must be done every frame
|
||||||
|
/// or you will get crackling sounds.
|
||||||
|
///
|
||||||
|
/// Normally you would run this during vdraw, just before the vblank interrupt.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// loop {
|
||||||
|
/// mixer.frame();
|
||||||
|
/// vblank.wait_for_vblank();
|
||||||
|
/// mixer.after_vblank(); // optional, only if not using interrupts
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn frame(&mut self) {
|
pub fn frame(&mut self) {
|
||||||
if !self.buffer.should_calculate() {
|
if !self.buffer.should_calculate() {
|
||||||
return;
|
return;
|
||||||
|
@ -84,6 +173,25 @@ impl Mixer {
|
||||||
.write_channels(self.channels.iter_mut().flatten());
|
.write_channels(self.channels.iter_mut().flatten());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start playing a given [`SoundChannel`].
|
||||||
|
///
|
||||||
|
/// Returns a [`ChannelId`] which you can later use to modify the playing sound.
|
||||||
|
///
|
||||||
|
/// Will first try to play the sound in an unused channel (of the 8 possible channels)
|
||||||
|
/// followed by overriding a low priority sound (if the sound channel being passed in
|
||||||
|
/// is high priority).
|
||||||
|
///
|
||||||
|
/// Returns Some if the channel is now playing (which is guaranteed if the channel is
|
||||||
|
/// high priority) or None if it failed to find a slot.
|
||||||
|
///
|
||||||
|
/// Panics if you try to play a high priority sound and there are no free channels.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
||||||
|
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
||||||
|
/// ```
|
||||||
pub fn play_sound(&mut self, new_channel: SoundChannel) -> Option<ChannelId> {
|
pub fn play_sound(&mut self, new_channel: SoundChannel) -> Option<ChannelId> {
|
||||||
for (i, channel) in self.channels.iter_mut().enumerate() {
|
for (i, channel) in self.channels.iter_mut().enumerate() {
|
||||||
if let Some(some_channel) = channel {
|
if let Some(some_channel) = channel {
|
||||||
|
@ -114,6 +222,20 @@ impl Mixer {
|
||||||
panic!("Cannot play more than 8 sounds at once");
|
panic!("Cannot play more than 8 sounds at once");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lets you modify an already playing channel.
|
||||||
|
///
|
||||||
|
/// Allows you to change the volume, panning or stop an already playing channel.
|
||||||
|
/// Will return Some if the channel is still playing, or None if it has already finished.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
||||||
|
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
||||||
|
///
|
||||||
|
/// // Later, stop that particular channel
|
||||||
|
/// mixer.channel(bgm_channel_id).stop();
|
||||||
|
/// ```
|
||||||
pub fn channel(&mut self, id: &ChannelId) -> Option<&'_ mut SoundChannel> {
|
pub fn channel(&mut self, id: &ChannelId) -> Option<&'_ mut SoundChannel> {
|
||||||
if let Some(channel) = &mut self.channels[id.0] {
|
if let Some(channel) = &mut self.channels[id.0] {
|
||||||
if self.indices[id.0] == id.1 && !channel.is_done {
|
if self.indices[id.0] == id.1 && !channel.is_done {
|
||||||
|
|
|
@ -1,2 +1,17 @@
|
||||||
|
//! # Game Boy Advance audio
|
||||||
|
//!
|
||||||
|
//! The GBA has 2 different ways of producing sound, which agb has support for.
|
||||||
|
//! You currently cannot use both at the same time, and currently there is no
|
||||||
|
//! compile time prevention of using both, but you should either use the DMG
|
||||||
|
//! which allows for Game Boy and Game Boy Color style sound effects, or the mixer
|
||||||
|
//! which allows for more advanced sounds.
|
||||||
|
//!
|
||||||
|
//! The [`dmg`](crate::sound::dmg) module is very rudimentry and doesn't support most of the possible
|
||||||
|
//! sounds possible. However, it may be expanded in the future.
|
||||||
|
//!
|
||||||
|
//! The [`mixer`](crate::sound::mixer) module is high performance, and allows for playing wav files at
|
||||||
|
//! various levels of quality. Check out the module documentation for more.
|
||||||
|
|
||||||
pub mod dmg;
|
pub mod dmg;
|
||||||
|
|
||||||
pub mod mixer;
|
pub mod mixer;
|
||||||
|
|
Loading…
Reference in a new issue