mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-11 09:31:34 +11:00
Document sw_mixer
This commit is contained in:
parent
d78d9155bf
commit
2c7db427f8
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue