diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 248b5bd6..1bea31fb 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -32,3 +32,7 @@ rustc-hash = { version = "1", default-features = false } [package.metadata.docs.rs] default-target = "thumbv6m-none-eabi" targets = [] + +[[example]] +name = "mixer_32768" +required-features = ["freq32768"] \ No newline at end of file diff --git a/agb/examples/JoshWoodward-CrazyGlue.wav b/agb/examples/JoshWoodward-CrazyGlue.wav new file mode 100644 index 00000000..ca45d3cd Binary files /dev/null and b/agb/examples/JoshWoodward-CrazyGlue.wav differ diff --git a/agb/examples/mixer_32768.rs b/agb/examples/mixer_32768.rs new file mode 100644 index 00000000..4ae5365e --- /dev/null +++ b/agb/examples/mixer_32768.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use agb::sound::mixer::SoundChannel; +use agb::{include_wav, Gba}; + +// Music - "Let it in" by Josh Woodward, free download at http://joshwoodward.com +const LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-CrazyGlue.wav"); + +#[agb::entry] +fn main(mut gba: Gba) -> ! { + let vblank_provider = agb::interrupt::VBlank::get(); + + let timer_controller = gba.timers.timers(); + let mut timer = timer_controller.timer2; + timer.set_enabled(true); + + let mut mixer = gba.mixer.mixer(); + mixer.enable(); + + let mut channel = SoundChannel::new(LET_IT_IN); + channel.stereo(); + mixer.play_sound(channel).unwrap(); + + let _interrupt = mixer.setup_interrupt_handler(); + + let mut frame_counter = 0i32; + loop { + vblank_provider.wait_for_vblank(); + let before_mixing_cycles = timer.value(); + mixer.frame(); + let after_mixing_cycles = timer.value(); + + frame_counter = frame_counter.wrapping_add(1); + + if frame_counter % 128 == 0 { + let total_cycles = after_mixing_cycles.wrapping_sub(before_mixing_cycles) as u32; + + let percent = (total_cycles * 100) / 280896; + agb::println!( + "Took {} cycles to calculate mixer ~= {}% of total frame", + total_cycles, + percent + ); + } + } +} diff --git a/agb/src/sound/mixer/sw_mixer.rs b/agb/src/sound/mixer/sw_mixer.rs index a6967afc..be8ae736 100644 --- a/agb/src/sound/mixer/sw_mixer.rs +++ b/agb/src/sound/mixer/sw_mixer.rs @@ -1,8 +1,13 @@ +use core::cell::RefCell; + +use bare_metal::{CriticalSection, Mutex}; + use super::hw; use super::hw::LeftOrRight; use super::{SoundChannel, SoundPriority}; use crate::fixnum::Num; -use crate::timer::Timer; +use crate::interrupt::{add_interrupt_handler, free, Interrupt, InterruptHandler}; +use crate::timer::{Divider, Timer}; // Defined in mixer.s extern "C" { @@ -31,7 +36,7 @@ pub struct ChannelId(usize, i32); impl Mixer { pub(super) fn new() -> Self { - Mixer { + Self { buffer: MixerBuffer::new(), channels: Default::default(), indices: Default::default(), @@ -45,21 +50,25 @@ impl Mixer { hw::set_sound_control_register_for_mixer(); } - #[cfg(not(feature = "freq32768"))] - pub fn frame(&mut self) { - self.buffer.clear(); - self.buffer - .write_channels(self.channels.iter_mut().flatten()); - } - #[cfg(not(feature = "freq32768"))] pub fn after_vblank(&mut self) { - self.buffer.swap(); + free(|cs| self.buffer.swap(cs)); } #[cfg(feature = "freq32768")] + pub fn setup_interrupt_handler(&self) -> InterruptHandler<'_> { + let mut timer1 = unsafe { Timer::new(1) }; + timer1.set_cascade(true); + timer1.set_divider(Divider::Divider1); + timer1.set_interrupt(true); + timer1.set_overflow_amount(constants::SOUND_BUFFER_SIZE as u16); + timer1.set_enabled(true); + + add_interrupt_handler(Interrupt::Timer1, move |cs| self.buffer.swap(cs)) + } + pub fn frame(&mut self) { - if self.buffer.has_swapped() { + if !self.buffer.has_swapped() { return; } @@ -126,7 +135,7 @@ mod constants { #[cfg(feature = "freq32768")] mod constants { pub const SOUND_FREQUENCY: i32 = 32768; - pub const SOUND_BUFFER_SIZE: usize = 549; + pub const SOUND_BUFFER_SIZE: usize = 560; } fn set_asm_buffer_size() { @@ -146,6 +155,10 @@ struct MixerBuffer { buffer1: SoundBuffer, // alternating bytes left and right channels buffer2: SoundBuffer, + state: Mutex>, +} + +struct MixerBufferState { buffer_1_active: bool, swapped: bool, } @@ -158,28 +171,34 @@ impl MixerBuffer { buffer1: SoundBuffer([0; constants::SOUND_BUFFER_SIZE * 2]), buffer2: SoundBuffer([0; constants::SOUND_BUFFER_SIZE * 2]), - buffer_1_active: true, - swapped: false, + state: Mutex::new(RefCell::new(MixerBufferState { + buffer_1_active: true, + swapped: false, + })), } } fn has_swapped(&self) -> bool { - self.swapped + free(|cs| self.state.borrow(*cs).borrow().swapped) } - fn swap(&mut self) { + fn swap(&self, cs: &CriticalSection) { let (left_buffer, right_buffer) = - self.write_buffer().split_at(constants::SOUND_BUFFER_SIZE); + self.write_buffer(cs).split_at(constants::SOUND_BUFFER_SIZE); hw::enable_dma_for_sound(left_buffer, LeftOrRight::Left); hw::enable_dma_for_sound(right_buffer, LeftOrRight::Right); - self.buffer_1_active = !self.buffer_1_active; - self.swapped = true; + { + let mut state = self.state.borrow(*cs).borrow_mut(); + + state.buffer_1_active = !state.buffer_1_active; + state.swapped = true; + } } fn clear(&mut self) { - self.write_buffer().fill(0); + self.write_buffer_mut().fill(0); } fn write_channels<'a>(&mut self, channels: impl Iterator) { @@ -234,19 +253,33 @@ impl MixerBuffer { channel.pos += playback_speed * constants::SOUND_BUFFER_SIZE; } - let write_buffer = self.write_buffer(); + let write_buffer = self.write_buffer_mut(); unsafe { agb_rs__mixer_collapse(write_buffer.as_mut_ptr(), buffer.as_ptr()); } - self.swapped = false; + free(|cs| { + self.state.borrow(*cs).borrow_mut().swapped = false; + }); } - fn write_buffer(&mut self) -> &mut [i8; constants::SOUND_BUFFER_SIZE * 2] { - if self.buffer_1_active { + fn write_buffer_mut(&mut self) -> &mut [i8; constants::SOUND_BUFFER_SIZE * 2] { + let buffer_1_active = free(|cs| self.state.borrow(*cs).borrow().buffer_1_active); + + if buffer_1_active { &mut self.buffer2.0 } else { &mut self.buffer1.0 } } + + fn write_buffer(&self, cs: &CriticalSection) -> &[i8; constants::SOUND_BUFFER_SIZE * 2] { + let buffer_1_active = self.state.borrow(*cs).borrow().buffer_1_active; + + if buffer_1_active { + &self.buffer2.0 + } else { + &self.buffer1.0 + } + } }