Merge pull request #232 from gwilymk/add-32khz-frequency-option

Add 32khz frequency option
This commit is contained in:
Gwilym Kuiper 2022-06-11 17:32:05 +01:00 committed by GitHub
commit 46260c085a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 247 additions and 84 deletions

View file

@ -19,6 +19,7 @@ proc-macro = true
[features] [features]
freq18157 = [] freq18157 = []
freq32768 = []
[dependencies] [dependencies]
hound = "3.4" hound = "3.4"

View file

@ -12,10 +12,14 @@ use std::{
}; };
use syn::parse_macro_input; use syn::parse_macro_input;
#[cfg(not(feature = "freq18157"))] #[cfg(all(not(feature = "freq18157"), not(feature = "freq32768")))]
const FREQUENCY: u32 = 10512; const FREQUENCY: u32 = 10512;
#[cfg(feature = "freq18157")] #[cfg(feature = "freq18157")]
const FREQUENCY: u32 = 18157; const FREQUENCY: u32 = 18157;
#[cfg(feature = "freq32768")]
const FREQUENCY: u32 = 32768;
#[cfg(all(feature = "freq18157", feature = "freq32768"))]
compile_error!("Must have at most one of freq18157 or freq32768 features enabled");
#[proc_macro] #[proc_macro]
pub fn include_wav(input: TokenStream) -> TokenStream { pub fn include_wav(input: TokenStream) -> TokenStream {

View file

@ -17,6 +17,7 @@ debug = true
[features] [features]
default = [] default = []
freq18157 = ["agb_sound_converter/freq18157"] freq18157 = ["agb_sound_converter/freq18157"]
freq32768 = ["agb_sound_converter/freq32768"]
[dependencies] [dependencies]
bitflags = "1" bitflags = "1"
@ -31,3 +32,7 @@ rustc-hash = { version = "1", default-features = false }
[package.metadata.docs.rs] [package.metadata.docs.rs]
default-target = "thumbv6m-none-eabi" default-target = "thumbv6m-none-eabi"
targets = [] targets = []
[[example]]
name = "mixer_32768"
required-features = ["freq32768"]

Binary file not shown.

View file

@ -0,0 +1,47 @@
#![no_std]
#![no_main]
use agb::sound::mixer::SoundChannel;
use agb::{include_wav, Gba};
// Music - "Crazy glue" 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
);
}
}
}

View file

@ -14,8 +14,7 @@ fn main(mut gba: Gba) -> ! {
let mut input = ButtonController::new(); let mut input = ButtonController::new();
let vblank_provider = agb::interrupt::VBlank::get(); let vblank_provider = agb::interrupt::VBlank::get();
let mut timers = gba.timers.timers(); let mut mixer = gba.mixer.mixer();
let mut mixer = gba.mixer.mixer(&mut timers.timer0);
mixer.enable(); mixer.enable();
let channel = SoundChannel::new(DEAD_CODE); let channel = SoundChannel::new(DEAD_CODE);

View file

@ -11,11 +11,11 @@ const LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-LetItIn.wav");
fn main(mut gba: Gba) -> ! { fn main(mut gba: Gba) -> ! {
let vblank_provider = agb::interrupt::VBlank::get(); let vblank_provider = agb::interrupt::VBlank::get();
let mut timer_controller = gba.timers.timers(); let timer_controller = gba.timers.timers();
let mut timer = timer_controller.timer1; let mut timer = timer_controller.timer2;
timer.set_enabled(true); timer.set_enabled(true);
let mut mixer = gba.mixer.mixer(&mut timer_controller.timer0); let mut mixer = gba.mixer.mixer();
mixer.enable(); mixer.enable();
let mut channel = SoundChannel::new(LET_IT_IN); let mut channel = SoundChannel::new(LET_IT_IN);

View file

@ -28,6 +28,8 @@ const FIFOB_DEST_ADDR: u32 = 0x0400_00a4;
const SOUND_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0082) }; const SOUND_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0082) };
const SOUND_CONTROL_X: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0084) }; const SOUND_CONTROL_X: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0084) };
const SOUND_BIAS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0088) };
const DMA_CONTROL_SETTING_FOR_SOUND: u16 = { const DMA_CONTROL_SETTING_FOR_SOUND: u16 = {
let dest_fixed: u16 = 2 << 5; // dest addr control = fixed let dest_fixed: u16 = 2 << 5; // dest addr control = fixed
let repeat: u16 = 1 << 9; let repeat: u16 = 1 << 9;
@ -89,6 +91,9 @@ pub(super) fn set_sound_control_register_for_mixer() {
// master sound enable // master sound enable
SOUND_CONTROL_X.set(1 << 7); SOUND_CONTROL_X.set(1 << 7);
// Set the sound bias PWM resampling rate to 8bit at 65536Hz (default for most games)
SOUND_BIAS.set(SOUND_BIAS.get() | 1 << 14);
} }
pub(super) fn set_timer_counter_for_frequency_and_enable(timer: &mut Timer, frequency: i32) { pub(super) fn set_timer_counter_for_frequency_and_enable(timer: &mut Timer, frequency: i32) {

View file

@ -192,6 +192,7 @@ SWAP_SIGN .req r11
.endm .endm
1: 1:
.rept 4
load_sample r3, r12 load_sample r3, r12
load_sample r5, r6 load_sample r5, r6
@ -211,8 +212,9 @@ SWAP_SIGN .req r11
str r3, [r0, r4] @ *(r0 + (r4 = SOUND_BUFFER_SIZE)) = r3 str r3, [r0, r4] @ *(r0 + (r4 = SOUND_BUFFER_SIZE)) = r3
str r12, [r0], #4 @ *r0 = r12; r0 += 4 str r12, [r0], #4 @ *r0 = r12; r0 += 4
.endr
subs r2, r2, #4 @ r2 -= 4 subs r2, r2, #16 @ r2 -= 16
bne 1b @ loop if not 0 bne 1b @ loop if not 0
pop {r4-r11} pop {r4-r11}

View file

@ -5,7 +5,6 @@ pub use sw_mixer::ChannelId;
pub use sw_mixer::Mixer; pub use sw_mixer::Mixer;
use crate::fixnum::Num; use crate::fixnum::Num;
use crate::timer::Timer;
#[non_exhaustive] #[non_exhaustive]
pub struct MixerController {} pub struct MixerController {}
@ -15,8 +14,8 @@ impl MixerController {
MixerController {} MixerController {}
} }
pub fn mixer<'a>(&mut self, timer: &'a mut Timer) -> Mixer<'a> { pub fn mixer(&mut self) -> Mixer {
Mixer::new(timer) Mixer::new()
} }
} }

View file

@ -1,8 +1,18 @@
use core::cell::RefCell;
use bare_metal::{CriticalSection, Mutex};
use super::hw; use super::hw;
use super::hw::LeftOrRight; use super::hw::LeftOrRight;
use super::{SoundChannel, SoundPriority}; use super::{SoundChannel, SoundPriority};
use crate::fixnum::Num; use crate::syscall::cpu_fast_fill_i8;
use crate::timer::Timer; use crate::{
fixnum::Num,
interrupt::free,
interrupt::{add_interrupt_handler, InterruptHandler},
timer::Divider,
timer::Timer,
};
// Defined in mixer.s // Defined in mixer.s
extern "C" { extern "C" {
@ -19,40 +29,58 @@ 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>);
} }
pub struct Mixer<'a> { pub struct Mixer {
buffer: MixerBuffer, buffer: MixerBuffer,
channels: [Option<SoundChannel>; 8], channels: [Option<SoundChannel>; 8],
indices: [i32; 8], indices: [i32; 8],
timer: &'a mut Timer, timer: Timer,
} }
pub struct ChannelId(usize, i32); pub struct ChannelId(usize, i32);
impl<'a> Mixer<'a> { impl Mixer {
pub(super) fn new(timer: &'a mut Timer) -> Self { pub(super) fn new() -> Self {
Mixer { Self {
buffer: MixerBuffer::new(), buffer: MixerBuffer::new(),
channels: Default::default(), channels: Default::default(),
indices: Default::default(), indices: Default::default(),
timer, timer: unsafe { Timer::new(0) },
} }
} }
pub fn enable(&mut self) { pub fn enable(&mut self) {
hw::set_timer_counter_for_frequency_and_enable(self.timer, 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();
} }
pub fn frame(&mut self) { #[cfg(not(feature = "freq32768"))]
self.buffer.clear(); pub fn after_vblank(&mut self) {
self.buffer free(|cs| self.buffer.swap(cs));
.write_channels(self.channels.iter_mut().flatten());
} }
pub fn after_vblank(&mut self) { /// Note that if you set up an interrupt handler, you should not call `after_vblank` any more
self.buffer.swap(); /// You are still required to call `frame`
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(constants::SOUND_BUFFER_SIZE as u16)
.set_enabled(true);
add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs))
}
pub fn frame(&mut self) {
if !self.buffer.should_calculate() {
return;
}
self.buffer
.write_channels(self.channels.iter_mut().flatten());
} }
pub fn play_sound(&mut self, new_channel: SoundChannel) -> Option<ChannelId> { pub fn play_sound(&mut self, new_channel: SoundChannel) -> Option<ChannelId> {
@ -96,17 +124,25 @@ impl<'a> Mixer<'a> {
} }
} }
// I've picked one frequency that works nicely. But there are others that work nicely // These work perfectly with swapping the buffers every vblank
// which we may want to consider in the future: http://deku.gbadev.org/program/sound1.html // list here: http://deku.gbadev.org/program/sound1.html
#[cfg(not(feature = "freq18157"))] #[cfg(all(not(feature = "freq18157"), not(feature = "freq32768")))]
const SOUND_FREQUENCY: i32 = 10512; mod constants {
#[cfg(not(feature = "freq18157"))] pub const SOUND_FREQUENCY: i32 = 10512;
const SOUND_BUFFER_SIZE: usize = 176; pub const SOUND_BUFFER_SIZE: usize = 176;
}
#[cfg(feature = "freq18157")] #[cfg(feature = "freq18157")]
const SOUND_FREQUENCY: i32 = 18157; mod constants {
#[cfg(feature = "freq18157")] pub const SOUND_FREQUENCY: i32 = 18157;
const SOUND_BUFFER_SIZE: usize = 304; pub const SOUND_BUFFER_SIZE: usize = 304;
}
#[cfg(feature = "freq32768")]
mod constants {
pub const SOUND_FREQUENCY: i32 = 32768;
pub const SOUND_BUFFER_SIZE: usize = 560;
}
fn set_asm_buffer_size() { fn set_asm_buffer_size() {
extern "C" { extern "C" {
@ -114,18 +150,55 @@ fn set_asm_buffer_size() {
} }
unsafe { unsafe {
agb_rs__buffer_size = SOUND_BUFFER_SIZE; agb_rs__buffer_size = constants::SOUND_BUFFER_SIZE;
} }
} }
#[repr(C, align(4))] #[repr(C, align(4))]
struct SoundBuffer([i8; SOUND_BUFFER_SIZE * 2]); struct SoundBuffer([i8; constants::SOUND_BUFFER_SIZE * 2]);
impl Default for SoundBuffer {
fn default() -> Self {
Self([0; constants::SOUND_BUFFER_SIZE * 2])
}
}
struct MixerBuffer { struct MixerBuffer {
buffer1: SoundBuffer, // alternating bytes left and right channels buffers: [SoundBuffer; 3],
buffer2: SoundBuffer,
buffer_1_active: bool, state: Mutex<RefCell<MixerBufferState>>,
}
struct MixerBufferState {
active_buffer: usize,
playing_buffer: usize,
}
/// Only returns a valid result if 0 <= x <= 3
const fn mod3_estimate(x: usize) -> usize {
match x & 0b11 {
0 => 0,
1 => 1,
2 => 2,
3 => 0,
_ => unreachable!(),
}
}
impl MixerBufferState {
fn should_calculate(&self) -> bool {
mod3_estimate(self.active_buffer + 1) != mod3_estimate(self.playing_buffer)
}
fn playing_advanced(&mut self) -> usize {
self.playing_buffer = mod3_estimate(self.playing_buffer + 1);
self.playing_buffer
}
fn active_advanced(&mut self) -> usize {
self.active_buffer = mod3_estimate(self.active_buffer + 1);
self.active_buffer
}
} }
impl MixerBuffer { impl MixerBuffer {
@ -133,28 +206,33 @@ impl MixerBuffer {
set_asm_buffer_size(); set_asm_buffer_size();
MixerBuffer { MixerBuffer {
buffer1: SoundBuffer([0; SOUND_BUFFER_SIZE * 2]), buffers: Default::default(),
buffer2: SoundBuffer([0; SOUND_BUFFER_SIZE * 2]),
buffer_1_active: true, state: Mutex::new(RefCell::new(MixerBufferState {
active_buffer: 0,
playing_buffer: 0,
})),
} }
} }
fn swap(&mut self) { fn should_calculate(&self) -> bool {
let (left_buffer, right_buffer) = self.write_buffer().split_at(SOUND_BUFFER_SIZE); free(|cs| self.state.borrow(*cs).borrow().should_calculate())
}
fn swap(&self, cs: &CriticalSection) {
let buffer = self.state.borrow(*cs).borrow_mut().playing_advanced();
let (left_buffer, right_buffer) = self.buffers[buffer]
.0
.split_at(constants::SOUND_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);
self.buffer_1_active = !self.buffer_1_active;
}
fn clear(&mut self) {
self.write_buffer().fill(0);
} }
fn write_channels<'a>(&mut self, channels: impl Iterator<Item = &'a mut SoundChannel>) { fn write_channels<'a>(&mut self, channels: impl Iterator<Item = &'a mut SoundChannel>) {
let mut buffer: [Num<i16, 4>; SOUND_BUFFER_SIZE * 2] = [Num::new(0); SOUND_BUFFER_SIZE * 2]; let mut buffer: [Num<i16, 4>; constants::SOUND_BUFFER_SIZE * 2] =
[Num::new(0); constants::SOUND_BUFFER_SIZE * 2];
for channel in channels { for channel in channels {
if channel.is_done { if channel.is_done {
@ -170,7 +248,9 @@ impl MixerBuffer {
let right_amount = ((channel.panning + 1) / 2) * channel.volume; let right_amount = ((channel.panning + 1) / 2) * channel.volume;
let left_amount = ((-channel.panning + 1) / 2) * channel.volume; let left_amount = ((-channel.panning + 1) / 2) * channel.volume;
if (channel.pos + playback_speed * SOUND_BUFFER_SIZE).floor() >= channel.data.len() { if (channel.pos + playback_speed * constants::SOUND_BUFFER_SIZE).floor()
>= channel.data.len()
{
// TODO: This should probably play what's left rather than skip the last bit // TODO: This should probably play what's left rather than skip the last bit
if channel.should_loop { if channel.should_loop {
channel.pos = 0.into(); channel.pos = 0.into();
@ -199,20 +279,16 @@ impl MixerBuffer {
} }
} }
channel.pos += playback_speed * SOUND_BUFFER_SIZE; channel.pos += playback_speed * constants::SOUND_BUFFER_SIZE;
} }
let write_buffer = self.write_buffer(); let write_buffer_index = free(|cs| self.state.borrow(*cs).borrow_mut().active_advanced());
let write_buffer = &mut self.buffers[write_buffer_index].0;
cpu_fast_fill_i8(write_buffer, 0);
unsafe { unsafe {
agb_rs__mixer_collapse(write_buffer.as_mut_ptr(), buffer.as_ptr()); agb_rs__mixer_collapse(write_buffer.as_mut_ptr(), buffer.as_ptr());
} }
} }
fn write_buffer(&mut self) -> &mut [i8; SOUND_BUFFER_SIZE * 2] {
if self.buffer_1_active {
&mut self.buffer2.0
} else {
&mut self.buffer1.0
}
}
} }

View file

@ -2,7 +2,6 @@ use core::arch::asm;
// use crate::display::object::AffineMatrixAttributes; // use crate::display::object::AffineMatrixAttributes;
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn halt() { pub fn halt() {
@ -94,6 +93,9 @@ pub fn arc_tan(n: i16) -> i16 {
"swi 0x09", "swi 0x09",
in("r0") n, in("r0") n,
lateout("r0") result, lateout("r0") result,
lateout("r1") _,
lateout("r2") _,
lateout("r3") _
); );
} }
result result
@ -107,12 +109,36 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 {
in("r0") x, in("r0") x,
in("r1") y, in("r1") y,
lateout("r0") result, lateout("r0") result,
lateout("r2") _,
lateout("r3") _
); );
} }
result result
} }
pub(crate) fn cpu_fast_fill_i8(input: &mut [i8], new_content: i32) {
assert_eq!(
input.len() % (4 * 8),
0,
"Input length must be divisible by 32"
);
let input_ptr = [new_content].as_ptr();
let output_ptr = input.as_mut_ptr();
let length_mode = (1 << 24) | // copy
(input.len() / 4);
unsafe {
asm!(
"swi 0x0c",
in("r0") input_ptr,
in("r1") output_ptr,
in("r2") length_mode,
lateout("r3") _,
);
}
}
// pub fn affine_matrix( // pub fn affine_matrix(
// x_scale: Num<i16, 8>, // x_scale: Num<i16, 8>,
// y_scale: Num<i16, 8>, // y_scale: Num<i16, 8>,

View file

@ -40,8 +40,6 @@ pub struct Timer {
#[non_exhaustive] #[non_exhaustive]
pub struct Timers { pub struct Timers {
pub timer0: Timer,
pub timer1: Timer,
pub timer2: Timer, pub timer2: Timer,
pub timer3: Timer, pub timer3: Timer,
} }
@ -49,8 +47,6 @@ pub struct Timers {
impl Timers { impl Timers {
pub(crate) unsafe fn new() -> Self { pub(crate) unsafe fn new() -> Self {
Self { Self {
timer0: Timer::new(0),
timer1: Timer::new(1),
timer2: Timer::new(2), timer2: Timer::new(2),
timer3: Timer::new(3), timer3: Timer::new(3),
} }
@ -58,7 +54,7 @@ impl Timers {
} }
impl Timer { impl Timer {
unsafe fn new(timer_number: u16) -> Self { pub(crate) unsafe fn new(timer_number: u16) -> Self {
let new_timer = Self { timer_number }; let new_timer = Self { timer_number };
new_timer.data_register().set(0); new_timer.data_register().set(0);
new_timer.control_register().set(0); new_timer.control_register().set(0);
@ -66,32 +62,37 @@ impl Timer {
new_timer new_timer
} }
pub fn set_overflow_amount(&mut self, n: u16) { pub fn set_overflow_amount(&mut self, n: u16) -> &mut Self {
let count_up_value = 0u16.wrapping_sub(n); let count_up_value = 0u16.wrapping_sub(n);
self.data_register().set(count_up_value); self.data_register().set(count_up_value);
self
} }
pub fn value(&self) -> u16 { pub fn value(&self) -> u16 {
self.data_register().get() self.data_register().get()
} }
pub fn set_divider(&mut self, divider: Divider) { pub fn set_divider(&mut self, divider: Divider) -> &mut Self {
self.control_register().set_bits(divider.as_bits(), 2, 0); self.control_register().set_bits(divider.as_bits(), 2, 0);
self
} }
pub fn set_enabled(&mut self, enabled: bool) { pub fn set_enabled(&mut self, enabled: bool) -> &mut Self {
let bit = if enabled { 1 } else { 0 }; let bit = if enabled { 1 } else { 0 };
self.control_register().set_bits(bit, 1, 7); self.control_register().set_bits(bit, 1, 7);
self
} }
pub fn set_cascade(&mut self, cascade: bool) { pub fn set_cascade(&mut self, cascade: bool) -> &mut Self {
let bit = if cascade { 1 } else { 0 }; let bit = if cascade { 1 } else { 0 };
self.control_register().set_bits(bit, 1, 2); self.control_register().set_bits(bit, 1, 2);
self
} }
pub fn set_interrupt(&mut self, interrupt: bool) { pub fn set_interrupt(&mut self, interrupt: bool) -> &mut Self {
let bit = interrupt as u16; let bit = interrupt as u16;
self.control_register().set_bits(bit, 1, 6); self.control_register().set_bits(bit, 1, 6);
self
} }
fn data_register(&self) -> MemoryMapped<u16> { fn data_register(&self) -> MemoryMapped<u16> {

View file

@ -809,8 +809,7 @@ fn main(mut agb: agb::Gba) -> ! {
vram.set_background_palettes(tile_sheet::background.palettes); vram.set_background_palettes(tile_sheet::background.palettes);
let object = agb.display.object.get(); let object = agb.display.object.get();
let mut timer_controller = agb.timers.timers(); let mut mixer = agb.mixer.mixer();
let mut mixer = agb.mixer.mixer(&mut timer_controller.timer0);
mixer.enable(); mixer.enable();
let mut music_box = sfx::MusicBox::new(); let mut music_box = sfx::MusicBox::new();

View file

@ -60,13 +60,13 @@ impl MusicBox {
} }
} }
pub struct SfxPlayer<'a, 'b> { pub struct SfxPlayer<'a> {
mixer: &'a mut Mixer<'b>, mixer: &'a mut Mixer,
frame: i32, frame: i32,
} }
impl<'a, 'b> SfxPlayer<'a, 'b> { impl<'a> SfxPlayer<'a> {
pub fn new(mixer: &'a mut Mixer<'b>, music_box: &MusicBox) -> Self { pub fn new(mixer: &'a mut Mixer, music_box: &MusicBox) -> Self {
SfxPlayer { SfxPlayer {
mixer, mixer,
frame: music_box.frame, frame: music_box.frame,

View file

@ -2205,8 +2205,7 @@ fn game_with_level(gba: &mut agb::Gba) {
let vblank = agb::interrupt::VBlank::get(); let vblank = agb::interrupt::VBlank::get();
vblank.wait_for_vblank(); vblank.wait_for_vblank();
let mut timers = gba.timers.timers(); let mut mixer = gba.mixer.mixer();
let mut mixer = gba.mixer.mixer(&mut timers.timer0);
mixer.enable(); mixer.enable();
let mut sfx = sfx::Sfx::new(&mut mixer); let mut sfx = sfx::Sfx::new(&mut mixer);

View file

@ -27,11 +27,11 @@ const BLUE_SPIRIT: &[u8] = agb::include_wav!("sfx/03 - Blue Spirit (Main Loop).w
pub struct Sfx<'a> { pub struct Sfx<'a> {
bgm: Option<ChannelId>, bgm: Option<ChannelId>,
mixer: &'a mut Mixer<'a>, mixer: &'a mut Mixer,
} }
impl<'a> Sfx<'a> { impl<'a> Sfx<'a> {
pub fn new(mixer: &'a mut Mixer<'a>) -> Self { pub fn new(mixer: &'a mut Mixer) -> Self {
Self { mixer, bgm: None } Self { mixer, bgm: None }
} }