Remove option to manage your own interrupts in the mixer (#361)

Simplify the audio mixer to always use the interrupt system which fixes
a soundness issue (pun intended) if you forget the mixer but not the
interrupt handler.

- [x] Changelog updated / no changelog update needed
This commit is contained in:
Gwilym Kuiper 2023-01-12 23:00:14 +00:00 committed by GitHub
commit 01e4c6c147
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 104 additions and 174 deletions

View file

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Text renderer can now be re-used which is useful for rpg style character/word at a time text boxes. - Text renderer can now be re-used which is useful for rpg style character/word at a time text boxes.
- Audio now automatically uses interrupts, so you can remove the `setup_interrupt_handler` or `after_vblank` calls to the mixer.
- If a vblank happens outside of `wait_for_vblank`, then next call will immediately return. - If a vblank happens outside of `wait_for_vblank`, then next call will immediately return.
### Fixed ### Fixed

View file

@ -12,7 +12,8 @@ opt-level = 3
debug = true debug = true
[profile.release] [profile.release]
lto = true opt-level = "s"
lto = "thin"
debug = true debug = true
[features] [features]

View file

@ -39,11 +39,12 @@ fn main(mut gba: Gba) -> ! {
let timer_controller = gba.timers.timers(); let timer_controller = gba.timers.timers();
let mut timer = timer_controller.timer2; let mut timer = timer_controller.timer2;
let mut timer2 = timer_controller.timer3;
timer.set_enabled(true); timer.set_enabled(true);
timer2.set_cascade(true).set_enabled(true);
let mut mixer = gba.mixer.mixer(Frequency::Hz32768); let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
mixer.enable(); mixer.enable();
let _interrupt = mixer.setup_interrupt_handler();
let mut channel = SoundChannel::new(CRAZY_GLUE); let mut channel = SoundChannel::new(CRAZY_GLUE);
channel.stereo(); channel.stereo();
@ -57,14 +58,22 @@ fn main(mut gba: Gba) -> ! {
vblank_provider.wait_for_vblank(); vblank_provider.wait_for_vblank();
bg.commit(&mut vram); bg.commit(&mut vram);
let before_mixing_cycles = timer.value(); let before_mixing_cycles_high = timer2.value();
let before_mixing_cycles_low = timer.value();
mixer.frame(); mixer.frame();
let after_mixing_cycles = timer.value();
let after_mixing_cycles_low = timer.value();
let after_mixing_cycles_high = timer2.value();
frame_counter = frame_counter.wrapping_add(1); frame_counter = frame_counter.wrapping_add(1);
if frame_counter % 128 == 0 && !has_written_frame_time { if frame_counter % 128 == 0 && !has_written_frame_time {
let total_cycles = after_mixing_cycles.wrapping_sub(before_mixing_cycles) as u32; let before_mixing_cycles =
((before_mixing_cycles_high as u32) << 16) + before_mixing_cycles_low as u32;
let after_mixing_cycles =
((after_mixing_cycles_high as u32) << 16) + after_mixing_cycles_low as u32;
let total_cycles = after_mixing_cycles.wrapping_sub(before_mixing_cycles);
let percent = (total_cycles * 100) / 280896; let percent = (total_cycles * 100) / 280896;

View file

@ -51,6 +51,5 @@ fn main(mut gba: Gba) -> ! {
mixer.frame(); mixer.frame();
vblank_provider.wait_for_vblank(); vblank_provider.wait_for_vblank();
mixer.after_vblank();
} }
} }

View file

@ -32,7 +32,6 @@ fn main(mut gba: Gba) -> ! {
writeln!(&mut writer, "Let it in by Josh Woodward").unwrap(); writeln!(&mut writer, "Let it in by Josh Woodward").unwrap();
writer.commit(); writer.commit();
bg.commit(&mut vram); bg.commit(&mut vram);
@ -58,7 +57,6 @@ fn main(mut gba: Gba) -> ! {
bg.commit(&mut vram); bg.commit(&mut vram);
let before_mixing_cycles = timer.value(); let before_mixing_cycles = timer.value();
mixer.after_vblank();
mixer.frame(); mixer.frame();
let after_mixing_cycles = timer.value(); let after_mixing_cycles = timer.value();

View file

@ -46,23 +46,23 @@ pub(super) enum LeftOrRight {
Right, Right,
} }
pub(super) fn enable_dma_for_sound(sound_memory: &[i8], lr: LeftOrRight) { pub(super) fn enable_dma_for_sound(sound_memory: *const i8, lr: LeftOrRight) {
match lr { match lr {
LeftOrRight::Left => enable_dma1_for_sound(sound_memory), LeftOrRight::Left => enable_dma1_for_sound(sound_memory),
LeftOrRight::Right => enable_dma2_for_sound(sound_memory), LeftOrRight::Right => enable_dma2_for_sound(sound_memory),
} }
} }
fn enable_dma1_for_sound(sound_memory: &[i8]) { fn enable_dma1_for_sound(sound_memory: *const i8) {
DMA1_CONTROL.set(0); DMA1_CONTROL.set(0);
DMA1_SOURCE_ADDR.set(sound_memory.as_ptr() as u32); DMA1_SOURCE_ADDR.set(sound_memory as u32);
DMA1_DEST_ADDR.set(FIFO_A_DEST_ADDR); DMA1_DEST_ADDR.set(FIFO_A_DEST_ADDR);
DMA1_CONTROL.set(DMA_CONTROL_SETTING_FOR_SOUND); DMA1_CONTROL.set(DMA_CONTROL_SETTING_FOR_SOUND);
} }
fn enable_dma2_for_sound(sound_memory: &[i8]) { fn enable_dma2_for_sound(sound_memory: *const i8) {
DMA2_CONTROL.set(0); DMA2_CONTROL.set(0);
DMA2_SOURCE_ADDR.set(sound_memory.as_ptr() as u32); DMA2_SOURCE_ADDR.set(sound_memory as u32);
DMA2_DEST_ADDR.set(FIFO_B_DEST_ADDR); DMA2_DEST_ADDR.set(FIFO_B_DEST_ADDR);
DMA2_CONTROL.set(DMA_CONTROL_SETTING_FOR_SOUND); DMA2_CONTROL.set(DMA_CONTROL_SETTING_FOR_SOUND);
} }

View file

@ -45,11 +45,13 @@
//! //!
//! ## Doing the per-frame work //! ## Doing the per-frame work
//! //!
//! Then, you have a choice of whether you want to use interrupts or do the buffer swapping //! Despite being high performance, the mixer still takes a sizable portion of CPU time (6-10%
//! yourself after a vblank interrupt. If you are using 32768Hz as the frequency of your //! depending on number of channels and frequency) to do the per-frame tasks, so should be done
//! files, you _must_ use the interrupt version. //! 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.
//! //!
//! Without interrupts: //! In order to avoid skipping audio, call the [`Mixer::frame()`] function at least once per frame
//! as shown below:
//! //!
//! ```rust,no_run //! ```rust,no_run
//! # #![no_std] //! # #![no_std]
@ -61,35 +63,9 @@
//! // Somewhere in your main loop: //! // Somewhere in your main loop:
//! mixer.frame(); //! mixer.frame();
//! vblank.wait_for_vblank(); //! vblank.wait_for_vblank();
//! mixer.after_vblank();
//! # } //! # }
//! ``` //! ```
//! //!
//! Or with interrupts:
//!
//! ```rust,no_run
//! # #![no_std]
//! # #![no_main]
//! use agb::sound::mixer::Frequency;
//! # fn foo(gba: &mut agb::Gba) {
//! let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz32768);
//! let vblank = agb::interrupt::VBlank::get();
//! // outside your main loop, close to initialisation
//! // you must assign this to a variable (not to _ or ignored) or rust will immediately drop it
//! // and prevent the interrupt handler from firing.
//! 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 //! ## Loading a sample
//! //!
//! To load a sample, you must have it in `wav` format (both stereo and mono work) at exactly the //! To load a sample, you must have it in `wav` format (both stereo and mono work) at exactly the
@ -159,7 +135,7 @@ pub enum Frequency {
Hz10512, Hz10512,
/// 18157Hz /// 18157Hz
Hz18157, Hz18157,
/// 32768Hz - note that this option requires interrupts for buffer swapping /// 32768Hz
Hz32768, Hz32768,
} }

View file

@ -1,4 +1,5 @@
use core::cell::RefCell; use core::cell::RefCell;
use core::pin::Pin;
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::vec::Vec; use alloc::vec::Vec;
@ -74,17 +75,22 @@ extern "C" {
/// loop { /// loop {
/// mixer.frame(); /// mixer.frame();
/// vblank.wait_for_vblank(); /// vblank.wait_for_vblank();
/// mixer.after_vblank();
/// } /// }
/// # } /// # }
/// ``` /// ```
pub struct Mixer { pub struct Mixer {
buffer: MixerBuffer, interrupt_timer: Timer,
// SAFETY: Has to go before buffer because it holds a reference to it
_interrupt_handler: InterruptHandler<'static>,
buffer: Pin<Box<MixerBuffer, InternalAllocator>>,
channels: [Option<SoundChannel>; 8], channels: [Option<SoundChannel>; 8],
indices: [i32; 8], indices: [i32; 8],
frequency: Frequency, frequency: Frequency,
timer: Timer, working_buffer: Box<[Num<i16, 4>], InternalAllocator>,
fifo_timer: Timer,
} }
/// A pointer to a currently playing channel. /// A pointer to a currently playing channel.
@ -112,13 +118,46 @@ pub struct ChannelId(usize, i32);
impl Mixer { impl Mixer {
pub(super) fn new(frequency: Frequency) -> Self { pub(super) fn new(frequency: Frequency) -> Self {
let buffer = Box::pin_in(MixerBuffer::new(frequency), InternalAllocator);
// SAFETY: you can only ever have 1 Mixer at a time
let fifo_timer = unsafe { Timer::new(0) };
// SAFETY: you can only ever have 1 Mixer at a time
let mut interrupt_timer = unsafe { Timer::new(1) };
interrupt_timer
.set_cascade(true)
.set_divider(Divider::Divider1)
.set_interrupt(true)
.set_overflow_amount(frequency.buffer_size() as u16);
let buffer_pointer_for_interrupt_handler: &MixerBuffer = &buffer;
// SAFETY: dropping the lifetime, sound because interrupt handler dropped before the buffer is
// In the case of the mixer being forgotten, both stay alive so okay
let buffer_pointer_for_interrupt_handler: &MixerBuffer =
unsafe { core::mem::transmute(buffer_pointer_for_interrupt_handler) };
let interrupt_handler = add_interrupt_handler(interrupt_timer.interrupt(), |cs| {
buffer_pointer_for_interrupt_handler.swap(cs);
});
set_asm_buffer_size(frequency);
let mut working_buffer =
Vec::with_capacity_in(frequency.buffer_size() * 2, InternalAllocator);
working_buffer.resize(frequency.buffer_size() * 2, 0.into());
Self { Self {
frequency, frequency,
buffer: MixerBuffer::new(frequency), buffer,
channels: Default::default(), channels: Default::default(),
indices: Default::default(), indices: Default::default(),
timer: unsafe { Timer::new(0) }, interrupt_timer,
_interrupt_handler: interrupt_handler,
working_buffer: working_buffer.into_boxed_slice(),
fifo_timer,
} }
} }
@ -127,71 +166,13 @@ impl Mixer {
/// You must call this method in order to start playing sound. You can do as much set up before /// 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. /// 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, self.frequency.frequency()); hw::set_timer_counter_for_frequency_and_enable(
&mut self.fifo_timer,
self.frequency.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), self.interrupt_timer.set_enabled(true);
/// but not both. Note that this is not available if using 32768Hz sounds since those require more irregular timings.
///
/// # Example
///
/// ```rust,no_run
/// # #![no_std]
/// # #![no_main]
/// # use agb::sound::mixer::*;
/// # use agb::*;
/// # fn foo(gba: &mut Gba) {
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
/// # let vblank = agb::interrupt::VBlank::get();
/// loop {
/// mixer.frame();
/// vblank.wait_for_vblank();
/// mixer.after_vblank();
/// }
/// # }
/// ```
#[cfg(not(feature = "freq32768"))]
pub fn after_vblank(&mut self) {
free(|cs| self.buffer.swap(cs));
}
/// Use timer interrupts to do the timing required for ensuring the music runs smoothly.
///
/// 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
///
/// ```rust,no_run
/// # #![no_std]
/// # #![no_main]
/// # use agb::sound::mixer::*;
/// # use agb::*;
/// # fn foo(gba: &mut Gba) {
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
/// # let vblank = agb::interrupt::VBlank::get();
/// // 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<'_> {
let mut timer1 = unsafe { Timer::new(1) };
timer1
.set_cascade(true)
.set_divider(Divider::Divider1)
.set_interrupt(true)
.set_overflow_amount(self.frequency.buffer_size() as u16)
.set_enabled(true);
add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs))
} }
/// Do the CPU intensive mixing for the next frame's worth of data. /// Do the CPU intensive mixing for the next frame's worth of data.
@ -214,7 +195,6 @@ impl Mixer {
/// loop { /// loop {
/// mixer.frame(); /// mixer.frame();
/// vblank.wait_for_vblank(); /// vblank.wait_for_vblank();
/// mixer.after_vblank(); // optional, only if not using interrupts
/// } /// }
/// # } /// # }
/// ``` /// ```
@ -224,7 +204,7 @@ impl Mixer {
} }
self.buffer self.buffer
.write_channels(self.channels.iter_mut().flatten()); .write_channels(&mut self.working_buffer, self.channels.iter_mut().flatten());
} }
/// Start playing a given [`SoundChannel`]. /// Start playing a given [`SoundChannel`].
@ -340,8 +320,6 @@ impl SoundBuffer {
} }
struct MixerBuffer { struct MixerBuffer {
buffers: [SoundBuffer; 3],
working_buffer: Box<[Num<i16, 4>], InternalAllocator>,
frequency: Frequency, frequency: Frequency,
state: Mutex<RefCell<MixerBufferState>>, state: Mutex<RefCell<MixerBufferState>>,
@ -350,6 +328,7 @@ struct MixerBuffer {
struct MixerBufferState { struct MixerBufferState {
active_buffer: usize, active_buffer: usize,
playing_buffer: usize, playing_buffer: usize,
buffers: [SoundBuffer; 3],
} }
/// Only returns a valid result if 0 <= x <= 3 /// Only returns a valid result if 0 <= x <= 3
@ -365,38 +344,31 @@ const fn mod3_estimate(x: usize) -> usize {
impl MixerBufferState { impl MixerBufferState {
fn should_calculate(&self) -> bool { fn should_calculate(&self) -> bool {
mod3_estimate(self.active_buffer + 1) != mod3_estimate(self.playing_buffer) mod3_estimate(self.active_buffer + 1) != self.playing_buffer
} }
fn playing_advanced(&mut self) -> usize { fn playing_advanced(&mut self) -> *const i8 {
self.playing_buffer = mod3_estimate(self.playing_buffer + 1); self.playing_buffer = mod3_estimate(self.playing_buffer + 1);
self.playing_buffer self.buffers[self.playing_buffer].0.as_ptr()
} }
fn active_advanced(&mut self) -> usize { fn active_advanced(&mut self) -> *mut i8 {
self.active_buffer = mod3_estimate(self.active_buffer + 1); self.active_buffer = mod3_estimate(self.active_buffer + 1);
self.active_buffer self.buffers[self.active_buffer].0.as_mut_ptr()
} }
} }
impl MixerBuffer { impl MixerBuffer {
fn new(frequency: Frequency) -> Self { fn new(frequency: Frequency) -> Self {
let mut working_buffer =
Vec::with_capacity_in(frequency.buffer_size() * 2, InternalAllocator);
working_buffer.resize(frequency.buffer_size() * 2, 0.into());
MixerBuffer { MixerBuffer {
state: Mutex::new(RefCell::new(MixerBufferState {
active_buffer: 0,
playing_buffer: 0,
buffers: [ buffers: [
SoundBuffer::new(frequency), SoundBuffer::new(frequency),
SoundBuffer::new(frequency), SoundBuffer::new(frequency),
SoundBuffer::new(frequency), SoundBuffer::new(frequency),
], ],
working_buffer: working_buffer.into_boxed_slice(),
state: Mutex::new(RefCell::new(MixerBufferState {
active_buffer: 0,
playing_buffer: 0,
})), })),
frequency, frequency,
@ -410,18 +382,20 @@ impl MixerBuffer {
fn swap(&self, cs: CriticalSection) { fn swap(&self, cs: CriticalSection) {
let buffer = self.state.borrow(cs).borrow_mut().playing_advanced(); let buffer = self.state.borrow(cs).borrow_mut().playing_advanced();
let (left_buffer, right_buffer) = self.buffers[buffer] let left_buffer = buffer;
.0 // SAFETY: starting pointer is fine, resulting pointer also fine because buffer has length buffer_size() * 2 by construction
.split_at(self.frequency.buffer_size()); let right_buffer = unsafe { buffer.add(self.frequency.buffer_size()) };
hw::enable_dma_for_sound(left_buffer, LeftOrRight::Left); hw::enable_dma_for_sound(left_buffer, LeftOrRight::Left);
hw::enable_dma_for_sound(right_buffer, LeftOrRight::Right); hw::enable_dma_for_sound(right_buffer, LeftOrRight::Right);
} }
fn write_channels<'a>(&mut self, channels: impl Iterator<Item = &'a mut SoundChannel>) { fn write_channels<'a>(
set_asm_buffer_size(self.frequency); &self,
working_buffer: &mut [Num<i16, 4>],
self.working_buffer.fill(0.into()); channels: impl Iterator<Item = &'a mut SoundChannel>,
) {
working_buffer.fill(0.into());
for channel in channels { for channel in channels {
if channel.is_done { if channel.is_done {
@ -451,7 +425,7 @@ impl MixerBuffer {
unsafe { unsafe {
agb_rs__mixer_add_stereo( agb_rs__mixer_add_stereo(
channel.data.as_ptr().add(channel.pos.floor()), channel.data.as_ptr().add(channel.pos.floor()),
self.working_buffer.as_mut_ptr(), working_buffer.as_mut_ptr(),
channel.volume, channel.volume,
); );
} }
@ -462,7 +436,7 @@ impl MixerBuffer {
unsafe { unsafe {
agb_rs__mixer_add( agb_rs__mixer_add(
channel.data.as_ptr().add(channel.pos.floor()), channel.data.as_ptr().add(channel.pos.floor()),
self.working_buffer.as_mut_ptr(), working_buffer.as_mut_ptr(),
playback_speed, playback_speed,
left_amount, left_amount,
right_amount, right_amount,
@ -474,12 +448,10 @@ impl MixerBuffer {
channel.pos += playback_speed * self.frequency.buffer_size(); channel.pos += playback_speed * self.frequency.buffer_size();
} }
let write_buffer_index = free(|cs| self.state.borrow(cs).borrow_mut().active_advanced()); let write_buffer = free(|cs| self.state.borrow(cs).borrow_mut().active_advanced());
let write_buffer = &mut self.buffers[write_buffer_index].0;
unsafe { unsafe {
agb_rs__mixer_collapse(write_buffer.as_mut_ptr(), self.working_buffer.as_ptr()); agb_rs__mixer_collapse(write_buffer, working_buffer.as_ptr());
} }
} }
} }

View file

@ -142,7 +142,6 @@ pub fn main(mut gba: agb::Gba) -> ! {
let mut mixer = gba.mixer.mixer(Frequency::Hz32768); let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
mixer.enable(); mixer.enable();
let _interrupt_handler = mixer.setup_interrupt_handler();
let sfx = Sfx::new(&mut mixer); let sfx = Sfx::new(&mut mixer);

View file

@ -832,7 +832,6 @@ pub fn main(mut agb: agb::Gba) -> ! {
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
mixer.frame(); mixer.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank();
level_display::write_level( level_display::write_level(
&mut world_display, &mut world_display,
@ -848,7 +847,6 @@ pub fn main(mut agb: agb::Gba) -> ! {
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
mixer.frame(); mixer.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank();
let map_current_level = current_level; let map_current_level = current_level;
let mut background = InfiniteScrolledMap::new( let mut background = InfiniteScrolledMap::new(
@ -894,21 +892,18 @@ pub fn main(mut agb: agb::Gba) -> ! {
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
mixer.frame(); mixer.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank();
} }
while level.background.init_foreground(&mut vram) != PartialUpdateStatus::Done { while level.background.init_foreground(&mut vram) != PartialUpdateStatus::Done {
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
mixer.frame(); mixer.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank();
} }
for _ in 0..20 { for _ in 0..20 {
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
mixer.frame(); mixer.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank();
} }
object.commit(); object.commit();
@ -930,7 +925,6 @@ pub fn main(mut agb: agb::Gba) -> ! {
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
mixer.frame(); mixer.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank();
object.commit(); object.commit();
} }
break; break;
@ -944,7 +938,6 @@ pub fn main(mut agb: agb::Gba) -> ! {
music_box.before_frame(&mut mixer); music_box.before_frame(&mut mixer);
mixer.frame(); mixer.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
mixer.after_vblank();
object.commit(); object.commit();
} }

View file

@ -41,10 +41,6 @@ pub fn show_splash_screen(
vblank.wait_for_vblank(); vblank.wait_for_vblank();
if let Some(ref mut mixer) = mixer {
mixer.after_vblank();
}
for y in 0..20u16 { for y in 0..20u16 {
for x in 0..30u16 { for x in 0..30u16 {
map.set_tile( map.set_tile(
@ -63,10 +59,6 @@ pub fn show_splash_screen(
} }
vblank.wait_for_vblank(); vblank.wait_for_vblank();
if let Some(ref mut mixer) = mixer {
mixer.after_vblank();
}
} }
map.commit(vram); map.commit(vram);
@ -83,17 +75,13 @@ pub fn show_splash_screen(
) { ) {
break; break;
} }
if let Some(ref mut mixer) = mixer { if let Some(mixer) = &mut mixer {
if let Some(ref mut music_box) = music_box { if let Some(music_box) = &mut music_box {
music_box.before_frame(mixer); music_box.before_frame(mixer);
} }
mixer.frame(); mixer.frame();
} }
vblank.wait_for_vblank(); vblank.wait_for_vblank();
if let Some(ref mut mixer) = mixer {
mixer.after_vblank();
}
} }
map.hide(); map.hide();

View file

@ -85,7 +85,6 @@ impl<'a> Level<'a> {
let mut between_updates = || { let mut between_updates = || {
sfx.frame(); sfx.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
sfx.after_vblank();
}; };
backdrop.init(vram, start_pos, &mut between_updates); backdrop.init(vram, start_pos, &mut between_updates);
@ -2283,7 +2282,6 @@ fn game_with_level(gba: &mut agb::Gba) {
start_at_boss = loop { start_at_boss = loop {
sfx.frame(); sfx.frame();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
sfx.after_vblank();
object.commit(); object.commit();
match game.advance_frame(&object, &mut vram, &mut sfx) { match game.advance_frame(&object, &mut vram, &mut sfx) {
GameStatus::Continue => {} GameStatus::Continue => {}

View file

@ -39,10 +39,6 @@ impl<'a> Sfx<'a> {
self.mixer.frame(); self.mixer.frame();
} }
pub fn after_vblank(&mut self) {
self.mixer.after_vblank();
}
pub fn stop_music(&mut self) { pub fn stop_music(&mut self) {
if let Some(bgm) = &self.bgm { if let Some(bgm) = &self.bgm {
let channel = self.mixer.channel(bgm).unwrap(); let channel = self.mixer.channel(bgm).unwrap();