diff --git a/agb/src/number.rs b/agb/src/number.rs index 5325caa..250b0a5 100644 --- a/agb/src/number.rs +++ b/agb/src/number.rs @@ -86,6 +86,7 @@ fixed_width_unsigned_integer_impl!(i16); fixed_width_unsigned_integer_impl!(u16); fixed_width_unsigned_integer_impl!(i32); fixed_width_unsigned_integer_impl!(u32); +fixed_width_unsigned_integer_impl!(usize); fixed_width_signed_integer_impl!(i16); fixed_width_signed_integer_impl!(i32); @@ -102,6 +103,15 @@ impl From for Num { } } +impl Default for Num +where + I: FixedWidthUnsignedInteger, +{ + fn default() -> Self { + Num(I::zero()) + } +} + impl Add for Num where I: FixedWidthUnsignedInteger, diff --git a/agb/src/sound/mixer.rs b/agb/src/sound/mixer.rs deleted file mode 100644 index 3c0fbfd..0000000 --- a/agb/src/sound/mixer.rs +++ /dev/null @@ -1,195 +0,0 @@ -use crate::memory_mapped::MemoryMapped; - -#[non_exhaustive] -pub struct MixerController {} - -impl MixerController { - pub(crate) const fn new() -> Self { - MixerController {} - } - - pub fn mixer(&mut self) -> Mixer { - Mixer::new() - } -} - -pub struct Mixer { - buffer: MixerBuffer, - channels: [Option; 16], -} - -impl Mixer { - fn new() -> Self { - Mixer { - buffer: MixerBuffer::new(), - channels: Default::default(), - } - } - - pub fn enable(&self) { - set_timer_counter_for_frequency_and_enable(SOUND_FREQUENCY); - set_sound_control_register_for_mixer(); - } - - pub fn vblank(&mut self) { - self.buffer.swap(); - self.buffer.clear(); - - for channel in self.channels.iter_mut() { - let mut has_finished = false; - - if let Some(some_channel) = channel { - self.buffer.write_channel(some_channel); - some_channel.pos += SOUND_BUFFER_SIZE; - - if some_channel.pos >= some_channel.data.len() { - if some_channel.should_loop { - some_channel.pos = 0; - } else { - has_finished = true; - } - } - } - - if has_finished { - channel.take(); - } - } - } - - pub fn play_sound(&mut self, new_channel: SoundChannel) { - for channel in self.channels.iter_mut() { - if channel.is_some() { - continue; - } - - channel.replace(new_channel); - return; - } - - panic!("Cannot play more than 16 sounds at once"); - } -} - -pub struct SoundChannel { - data: &'static [u8], - pos: usize, - should_loop: bool, -} - -impl SoundChannel { - pub fn new(data: &'static [u8]) -> Self { - SoundChannel { - data, - pos: 0, - should_loop: false, - } - } - - pub fn should_loop(mut self) -> Self { - self.should_loop = true; - self - } -} - -// I've picked one frequency that works nicely. But there are others that work nicely -// which we may want to consider in the future: https://web.archive.org/web/20070608011909/http://deku.gbadev.org/program/sound1.html -const SOUND_FREQUENCY: i32 = 10512; -const SOUND_BUFFER_SIZE: usize = 176; - -struct MixerBuffer { - buffer1: [i8; SOUND_BUFFER_SIZE], - buffer2: [i8; SOUND_BUFFER_SIZE], - - buffer_1_active: bool, -} - -impl MixerBuffer { - fn new() -> Self { - MixerBuffer { - buffer1: [0; SOUND_BUFFER_SIZE], - buffer2: [0; SOUND_BUFFER_SIZE], - - buffer_1_active: true, - } - } - - fn swap(&mut self) { - self.buffer_1_active = !self.buffer_1_active; - - if self.buffer_1_active { - enable_dma1_for_sound(&self.buffer1); - } else { - enable_dma1_for_sound(&self.buffer2); - } - } - - fn clear(&mut self) { - self.get_write_buffer().fill(0); - } - - fn write_channel(&mut self, channel: &SoundChannel) { - let data_to_copy = &channel.data[channel.pos..]; - let place_to_write_to = self.get_write_buffer(); - - for (i, v) in data_to_copy.iter().take(SOUND_BUFFER_SIZE).enumerate() { - let v = *v as i8; - place_to_write_to[i] = place_to_write_to[i].saturating_add(v); - } - } - - fn get_write_buffer(&mut self) -> &mut [i8; SOUND_BUFFER_SIZE] { - if self.buffer_1_active { - &mut self.buffer2 - } else { - &mut self.buffer1 - } - } -} - -// Once we have proper DMA support, we should use that rather than hard coding these here too -const DMA1_SOURCE_ADDR: MemoryMapped = unsafe { MemoryMapped::new(0x0400_00bc) }; -const DMA1_DEST_ADDR: MemoryMapped = unsafe { MemoryMapped::new(0x0400_00c0) }; -const _DMA1_WORD_COUNT: MemoryMapped = unsafe { MemoryMapped::new(0x0400_00c4) }; // sound ignores this for some reason -const DMA1_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_00c6) }; - -const FIFOA_DEST_ADDR: u32 = 0x0400_00a0; - -// Similarly for proper timer support -const TIMER0_COUNTER: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0100) }; -const TIMER0_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0102) }; - -const SOUND_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0082) }; -const SOUND_CONTROL_X: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0084) }; - -fn enable_dma1_for_sound(sound_memory: &[i8]) { - let dest_fixed: u16 = 2 << 5; // dest addr control = fixed - let repeat: u16 = 1 << 9; - let transfer_type: u16 = 1 << 10; // transfer in words - let dma_start_timing: u16 = 3 << 12; // sound fifo timing - let enable: u16 = 1 << 15; // enable - - DMA1_CONTROL.set(0); - DMA1_SOURCE_ADDR.set(sound_memory.as_ptr() as u32); - DMA1_DEST_ADDR.set(FIFOA_DEST_ADDR); - DMA1_CONTROL.set(dest_fixed | repeat | transfer_type | dma_start_timing | enable); -} - -fn set_sound_control_register_for_mixer() { - let sound_a_volume_100: u16 = 1 << 2; - let sound_a_rout: u16 = 1 << 8; - let sound_a_lout: u16 = 1 << 9; - let sound_a_fifo_reset: u16 = 1 << 11; - - SOUND_CONTROL.set(sound_a_volume_100 | sound_a_rout | sound_a_lout | sound_a_fifo_reset); - - // master sound enable - SOUND_CONTROL_X.set(1 << 7); -} - -fn set_timer_counter_for_frequency_and_enable(frequency: i32) { - let counter = 65536 - (16777216 / frequency); - TIMER0_COUNTER.set(counter as u16); - - TIMER0_CONTROL.set(1 << 7); // enable the timer -} diff --git a/agb/src/sound/mixer/hw.rs b/agb/src/sound/mixer/hw.rs new file mode 100644 index 0000000..c055638 --- /dev/null +++ b/agb/src/sound/mixer/hw.rs @@ -0,0 +1,102 @@ +use crate::memory_mapped::MemoryMapped; + +const fn dma_source_addr(dma: usize) -> usize { + 0x0400_00b0 + 0x0c * dma +} + +const fn dma_dest_addr(dma: usize) -> usize { + 0x0400_00b4 + 0x0c * dma +} + +const fn dma_control_addr(dma: usize) -> usize { + 0x0400_00ba + 0x0c * dma +} + +// Once we have proper DMA support, we should use that rather than hard coding these here too +const DMA1_SOURCE_ADDR: MemoryMapped = unsafe { MemoryMapped::new(dma_source_addr(1)) }; +const DMA1_DEST_ADDR: MemoryMapped = unsafe { MemoryMapped::new(dma_dest_addr(1)) }; +const DMA1_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(dma_control_addr(1)) }; + +const DMA2_SOURCE_ADDR: MemoryMapped = unsafe { MemoryMapped::new(dma_source_addr(2)) }; +const DMA2_DEST_ADDR: MemoryMapped = unsafe { MemoryMapped::new(dma_dest_addr(2)) }; +const DMA2_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(dma_control_addr(2)) }; + +const FIFOA_DEST_ADDR: u32 = 0x0400_00a0; +const FIFOB_DEST_ADDR: u32 = 0x0400_00a4; + +// Similarly for proper timer support +const TIMER0_COUNTER: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0100) }; +const TIMER0_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0102) }; + +const SOUND_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0082) }; +const SOUND_CONTROL_X: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0084) }; + +const DMA_CONTROL_SETTING_FOR_SOUND: u16 = { + let dest_fixed: u16 = 2 << 5; // dest addr control = fixed + let repeat: u16 = 1 << 9; + let transfer_type: u16 = 1 << 10; // transfer in words + let dma_start_timing: u16 = 3 << 12; // sound fifo timing + let enable: u16 = 1 << 15; // enable + + dest_fixed | repeat | transfer_type | dma_start_timing | enable +}; + +#[derive(Copy, Clone)] +pub(super) enum LeftOrRight { + Left, + Right, +} + +pub(super) fn enable_dma_for_sound(sound_memory: &[i8], lr: LeftOrRight) { + match lr { + LeftOrRight::Left => enable_dma1_for_sound(sound_memory), + LeftOrRight::Right => enable_dma2_for_sound(sound_memory), + } +} + +fn enable_dma1_for_sound(sound_memory: &[i8]) { + DMA1_CONTROL.set(0); + DMA1_SOURCE_ADDR.set(sound_memory.as_ptr() as u32); + DMA1_DEST_ADDR.set(FIFOA_DEST_ADDR); + DMA1_CONTROL.set(DMA_CONTROL_SETTING_FOR_SOUND); +} + +fn enable_dma2_for_sound(sound_memory: &[i8]) { + DMA2_CONTROL.set(0); + DMA2_SOURCE_ADDR.set(sound_memory.as_ptr() as u32); + DMA2_DEST_ADDR.set(FIFOB_DEST_ADDR); + DMA2_CONTROL.set(DMA_CONTROL_SETTING_FOR_SOUND); +} + +pub(super) fn set_sound_control_register_for_mixer() { + let sound_a_volume_100: u16 = 1 << 2; + let sound_a_rout: u16 = 0 << 8; // sound A is for left channel only + let sound_a_lout: u16 = 1 << 9; + let sound_a_fifo_reset: u16 = 1 << 11; + + let sound_b_volume_100: u16 = 1 << 3; + let sound_b_rout: u16 = 1 << 12; + let sound_b_lout: u16 = 0 << 13; + let sound_b_fifo_reset: u16 = 1 << 15; + + SOUND_CONTROL.set( + sound_a_volume_100 + | sound_a_rout + | sound_a_lout + | sound_a_fifo_reset + | sound_b_volume_100 + | sound_b_rout + | sound_b_lout + | sound_b_fifo_reset, + ); + + // master sound enable + SOUND_CONTROL_X.set(1 << 7); +} + +pub(super) fn set_timer_counter_for_frequency_and_enable(frequency: i32) { + let counter = 65536 - (16777216 / frequency); + TIMER0_COUNTER.set(counter as u16); + + TIMER0_CONTROL.set(1 << 7); // enable the timer +} diff --git a/agb/src/sound/mixer/mod.rs b/agb/src/sound/mixer/mod.rs new file mode 100644 index 0000000..06875cd --- /dev/null +++ b/agb/src/sound/mixer/mod.rs @@ -0,0 +1,61 @@ +mod hw; +mod sw_mixer; + +pub use sw_mixer::Mixer; + +use crate::number::Num; + +#[non_exhaustive] +pub struct MixerController {} + +impl MixerController { + pub(crate) const fn new() -> Self { + MixerController {} + } + + pub fn mixer(&mut self) -> Mixer { + Mixer::new() + } +} + +pub struct SoundChannel { + data: &'static [u8], + pos: Num, + should_loop: bool, + + playback_speed: Num, + + panning: Num, // between -1 and 1 + is_done: bool, +} + +impl SoundChannel { + pub fn new(data: &'static [u8]) -> Self { + SoundChannel { + data, + pos: 0.into(), + should_loop: false, + playback_speed: 1.into(), + panning: 0.into(), + is_done: false, + } + } + + pub fn should_loop(mut self) -> Self { + self.should_loop = true; + self + } + + pub fn playback(mut self, playback_speed: Num) -> Self { + self.playback_speed = playback_speed; + self + } + + pub fn panning(mut self, panning: Num) -> Self { + debug_assert!(panning >= Num::new(-1), "panning value must be >= -1"); + debug_assert!(panning <= Num::new(1), "panning value must be <= 1"); + + self.panning = panning; + self + } +} diff --git a/agb/src/sound/mixer/sw_mixer.rs b/agb/src/sound/mixer/sw_mixer.rs new file mode 100644 index 0000000..69484d4 --- /dev/null +++ b/agb/src/sound/mixer/sw_mixer.rs @@ -0,0 +1,127 @@ +use super::hw; +use super::hw::LeftOrRight; +use super::SoundChannel; +use crate::number::Num; + +pub struct Mixer { + buffer: MixerBuffer, + channels: [Option; 16], +} + +impl Mixer { + pub(super) fn new() -> Self { + Mixer { + buffer: MixerBuffer::new(), + channels: Default::default(), + } + } + + pub fn enable(&self) { + hw::set_timer_counter_for_frequency_and_enable(SOUND_FREQUENCY); + hw::set_sound_control_register_for_mixer(); + } + + pub fn vblank(&mut self) { + self.buffer.swap(); + self.buffer.clear(); + + self.buffer + .write_channels(self.channels.iter_mut().flatten()); + } + + pub fn play_sound(&mut self, new_channel: SoundChannel) { + for channel in self.channels.iter_mut() { + if let Some(some_channel) = channel { + if !some_channel.is_done { + continue; + } + } + + channel.replace(new_channel); + return; + } + + panic!("Cannot play more than 16 sounds at once"); + } +} + +// I've picked one frequency that works nicely. But there are others that work nicely +// which we may want to consider in the future: http://deku.gbadev.org/program/sound1.html +const SOUND_FREQUENCY: i32 = 10512; +const SOUND_BUFFER_SIZE: usize = 176; + +struct MixerBuffer { + buffer1: [i8; SOUND_BUFFER_SIZE * 2], // first half is left, second is right + buffer2: [i8; SOUND_BUFFER_SIZE * 2], + + buffer_1_active: bool, +} + +impl MixerBuffer { + fn new() -> Self { + MixerBuffer { + buffer1: [0; SOUND_BUFFER_SIZE * 2], + buffer2: [0; SOUND_BUFFER_SIZE * 2], + + buffer_1_active: true, + } + } + + fn swap(&mut self) { + let (left_buffer, right_buffer) = self.get_write_buffer().split_at(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; + } + + fn clear(&mut self) { + self.get_write_buffer().fill(0); + } + + fn write_channels<'a>(&mut self, channels: impl Iterator) { + let mut buffer: [Num; SOUND_BUFFER_SIZE * 2] = [Num::new(0); SOUND_BUFFER_SIZE * 2]; + + for channel in channels { + if channel.is_done { + continue; + } + + let right_amount = (channel.panning + 1) / 2; + let left_amount = -right_amount + 1; + + if channel.pos + channel.playback_speed * SOUND_BUFFER_SIZE >= channel.data.len().into() + { + // TODO: This should probably play what's left rather than skip the last bit + if channel.should_loop { + channel.pos -= channel.data.len(); + } else { + channel.is_done = true; + continue; + } + } + + for i in 0..SOUND_BUFFER_SIZE { + let v = (channel.data[channel.pos.floor()] as i8) as i16; + channel.pos += channel.playback_speed; + + buffer[i] += left_amount * v; + buffer[i + SOUND_BUFFER_SIZE] += right_amount * v; + } + } + + let write_buffer = self.get_write_buffer(); + for i in 0..SOUND_BUFFER_SIZE * 2 { + write_buffer[i] = buffer[i].floor().clamp(i8::MIN as i16, i8::MAX as i16) as i8 + } + } + + fn get_write_buffer(&mut self) -> &mut [i8; SOUND_BUFFER_SIZE * 2] { + if self.buffer_1_active { + &mut self.buffer2 + } else { + &mut self.buffer1 + } + } +}