diff --git a/agb/examples/beep.rs b/agb/examples/beep.rs index 0e7ebf42..e6f6cefa 100644 --- a/agb/examples/beep.rs +++ b/agb/examples/beep.rs @@ -11,25 +11,29 @@ pub fn main() -> ! { gba.sound.enable(); - let sweep_settings = sound::SweepSettings::default(); + let sweep_settings = sound::dmg::SweepSettings::default(); gba.sound.channel1().play_sound( 1024, Some(0), &sweep_settings, - &sound::EnvelopeSettings::default(), - sound::DutyCycle::Half, + &sound::dmg::EnvelopeSettings::default(), + sound::dmg::DutyCycle::Half, ); gba.sound.channel2().play_sound( 1524, Some(0), - &sound::EnvelopeSettings::default(), - sound::DutyCycle::Half, + &sound::dmg::EnvelopeSettings::default(), + sound::dmg::DutyCycle::Half, ); - gba.sound - .noise() - .play_sound(Some(0), &sound::EnvelopeSettings::default(), 4, false, 1); + gba.sound.noise().play_sound( + Some(0), + &sound::dmg::EnvelopeSettings::default(), + 4, + false, + 1, + ); loop {} } diff --git a/agb/examples/i-will-not-let-you-let-me-down.raw b/agb/examples/i-will-not-let-you-let-me-down.raw new file mode 100644 index 00000000..cc272fad Binary files /dev/null and b/agb/examples/i-will-not-let-you-let-me-down.raw differ diff --git a/agb/examples/mixer_basic.rs b/agb/examples/mixer_basic.rs new file mode 100644 index 00000000..4d479687 --- /dev/null +++ b/agb/examples/mixer_basic.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] + +extern crate agb; + +use agb::sound::mixer::SoundChannel; +use agb::Gba; + +// Music - "I will not let you let me down" by Josh Woodward, free download at http://joshwoodward.com +const I_WILL_NOT_LET_YOU_LET_ME_DOWN: &[u8] = include_bytes!("i-will-not-let-you-let-me-down.raw"); + +#[no_mangle] +pub fn main() -> ! { + let mut gba = Gba::new(); + let vblank_provider = gba.display.vblank.get(); + + let mut mixer = gba.mixer.mixer(); + mixer.enable(); + + let channel = SoundChannel::new(I_WILL_NOT_LET_YOU_LET_ME_DOWN); + mixer.play_sound(channel); + + loop { + vblank_provider.wait_for_VBlank(); + mixer.vblank(); + } +} diff --git a/agb/src/lib.rs b/agb/src/lib.rs index b4a94802..edf1de2f 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -50,7 +50,8 @@ static mut GBASINGLE: single::Singleton = single::Singleton::new(unsafe { G pub struct Gba { pub display: display::Display, - pub sound: sound::Sound, + pub sound: sound::dmg::Sound, + pub mixer: sound::mixer::MixerController, } impl Gba { @@ -61,7 +62,8 @@ impl Gba { const unsafe fn single_new() -> Self { Self { display: display::Display::new(), - sound: sound::Sound::new(), + sound: sound::dmg::Sound::new(), + mixer: sound::mixer::MixerController::new(), } } } diff --git a/agb/src/sound/dmg.rs b/agb/src/sound/dmg.rs new file mode 100644 index 00000000..4a91aa65 --- /dev/null +++ b/agb/src/sound/dmg.rs @@ -0,0 +1,238 @@ +use crate::memory_mapped::MemoryMapped; + +const CHANNEL_1_SWEEP: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0060) }; +const CHANNEL_1_LENGTH_DUTY_ENVELOPE: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0062) }; +const CHANNEL_1_FREQUENCY_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0064) }; + +const CHANNEL_2_LENGTH_DUTY_ENVELOPE: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0068) }; +const CHANNEL_2_FREQUENCY_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_006c) }; + +const CHANNEL_4_LENGTH_ENVELOPE: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0078) }; +const CHANNEL_4_FREQUENCY_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_007c) }; + +const MASTER_SOUND_VOLUME_ENABLE: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0080) }; +const MASTER_SOUND_VOLUME_MIXING: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0082) }; +const MASTER_SOUND_STATUS: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0084) }; + +#[non_exhaustive] +pub struct Sound {} + +impl Sound { + pub(crate) const unsafe fn new() -> Self { + Sound {} + } + + pub fn channel1(&self) -> Channel1 { + Channel1 {} + } + + pub fn channel2(&self) -> Channel2 { + Channel2 {} + } + + pub fn noise(&self) -> Noise { + Noise {} + } + + pub fn enable(&self) { + MASTER_SOUND_STATUS.set_bits(1, 1, 7); + + #[allow(clippy::unusual_byte_groupings)] // I've split these like this for a reason + MASTER_SOUND_VOLUME_ENABLE.set(0b1111_1111_0_111_0_111); + MASTER_SOUND_VOLUME_MIXING.set(0b10); + } +} + +#[non_exhaustive] +pub struct Channel1 {} + +impl Channel1 { + pub fn play_sound( + &self, + frequency: u16, + length: Option, + sweep_settings: &SweepSettings, + envelope_settings: &EnvelopeSettings, + duty_cycle: DutyCycle, + ) { + CHANNEL_1_SWEEP.set(sweep_settings.as_bits()); + let length_bits = length.unwrap_or(0) as u16; + assert!(length_bits < 64, "Length must be less than 64"); + + let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0); + let initial: u16 = 1 << 15; + + assert!(frequency < 2048, "Frequency must be less than 2048"); + + CHANNEL_1_LENGTH_DUTY_ENVELOPE + .set(envelope_settings.as_bits() | duty_cycle.as_bits() | length_bits); + CHANNEL_1_FREQUENCY_CONTROL.set(frequency | length_flag | initial); + } +} + +#[non_exhaustive] +pub struct Channel2 {} + +impl Channel2 { + pub fn play_sound( + &self, + frequency: u16, + length: Option, + envelope_settings: &EnvelopeSettings, + duty_cycle: DutyCycle, + ) { + let length_bits = length.unwrap_or(0) as u16; + assert!(length_bits < 64, "Length must be less than 64"); + + let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0); + let initial: u16 = 1 << 15; + + assert!(frequency < 2048, "Frequency must be less than 2048"); + + CHANNEL_2_LENGTH_DUTY_ENVELOPE + .set(envelope_settings.as_bits() | duty_cycle.as_bits() | length_bits); + CHANNEL_2_FREQUENCY_CONTROL.set(frequency | length_flag | initial); + } +} + +#[non_exhaustive] +pub struct Noise {} + +impl Noise { + pub fn play_sound( + &self, + length: Option, + envelope_setting: &EnvelopeSettings, + frequency_divider: u8, + counter_step_width_15: bool, + shift_clock_frequency: u8, + ) { + let length_bits = length.unwrap_or(0) as u16; + assert!(length_bits < 64, "length must be less than 16"); + + assert!( + frequency_divider < 8, + "frequency divider must be less than 8" + ); + assert!( + shift_clock_frequency < 16, + "frequency clock divider must be less than 16" + ); + + let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0); + let initial: u16 = 1 << 15; + + let counter_step_bit = if counter_step_width_15 { 0 } else { 1 << 3 }; + + CHANNEL_4_LENGTH_ENVELOPE.set(length_bits | envelope_setting.as_bits()); + CHANNEL_4_FREQUENCY_CONTROL.set( + (frequency_divider as u16) + | counter_step_bit + | ((shift_clock_frequency as u16) << 4) + | length_flag + | initial, + ) + } +} + +pub enum SoundDirection { + Increase, + Decrease, +} + +impl SoundDirection { + fn as_bits(&self) -> u16 { + match &self { + SoundDirection::Increase => 1, + SoundDirection::Decrease => 0, + } + } +} + +pub struct SweepSettings { + number_of_sweep_shifts: u8, + sound_direction: SoundDirection, + sweep_time: u8, +} + +impl SweepSettings { + pub fn new( + number_of_sweep_shifts: u8, + sound_direction: SoundDirection, + sweep_time: u8, + ) -> Self { + assert!( + number_of_sweep_shifts < 8, + "Number of sweep shifts must be less than 8" + ); + assert!(sweep_time < 8, "Sweep time must be less than 8"); + + SweepSettings { + number_of_sweep_shifts, + sound_direction, + sweep_time, + } + } + + fn as_bits(&self) -> u16 { + ((self.number_of_sweep_shifts as u16) & 0b111) + | ((1 - self.sound_direction.as_bits()) << 3) // sweep works backwards + | ((self.sweep_time as u16) & 0b111) << 4 + } +} + +impl Default for SweepSettings { + fn default() -> Self { + SweepSettings::new(0, SoundDirection::Increase, 0) + } +} + +pub struct EnvelopeSettings { + step_time: u8, + direction: SoundDirection, + initial_volume: u8, +} + +impl EnvelopeSettings { + pub fn new(step_time: u8, direction: SoundDirection, initial_volume: u8) -> Self { + assert!(step_time < 8, "Step time must be less than 8"); + assert!(initial_volume < 16, "Initial volume must be less that 16"); + EnvelopeSettings { + step_time, + direction, + initial_volume, + } + } + + fn as_bits(&self) -> u16 { + (self.step_time as u16) << 8 + | (self.direction.as_bits() << 11) + | ((self.initial_volume as u16) << 12) + } +} + +impl Default for EnvelopeSettings { + fn default() -> Self { + EnvelopeSettings::new(0, SoundDirection::Increase, 15) + } +} + +pub enum DutyCycle { + OneEighth, + OneQuarter, + Half, + ThreeQuarters, +} + +impl DutyCycle { + fn as_bits(&self) -> u16 { + use DutyCycle::*; + + match &self { + OneEighth => 0, + OneQuarter => 1, + Half => 2, + ThreeQuarters => 3, + } + } +} diff --git a/agb/src/sound/mixer.rs b/agb/src/sound/mixer.rs new file mode 100644 index 00000000..a49c68bd --- /dev/null +++ b/agb/src/sound/mixer.rs @@ -0,0 +1,181 @@ +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() { + 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, +} + +impl SoundChannel { + pub fn new(data: &'static [u8]) -> Self { + SoundChannel { data, pos: 0 } + } +} + +// 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..(channel.pos + SOUND_BUFFER_SIZE)]; + let place_to_write_to = self.get_write_buffer(); + + for (i, v) in data_to_copy.iter().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/mod.rs b/agb/src/sound/mod.rs index 4a91aa65..1236802d 100644 --- a/agb/src/sound/mod.rs +++ b/agb/src/sound/mod.rs @@ -1,238 +1,2 @@ -use crate::memory_mapped::MemoryMapped; - -const CHANNEL_1_SWEEP: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0060) }; -const CHANNEL_1_LENGTH_DUTY_ENVELOPE: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0062) }; -const CHANNEL_1_FREQUENCY_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0064) }; - -const CHANNEL_2_LENGTH_DUTY_ENVELOPE: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0068) }; -const CHANNEL_2_FREQUENCY_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_006c) }; - -const CHANNEL_4_LENGTH_ENVELOPE: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0078) }; -const CHANNEL_4_FREQUENCY_CONTROL: MemoryMapped = unsafe { MemoryMapped::new(0x0400_007c) }; - -const MASTER_SOUND_VOLUME_ENABLE: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0080) }; -const MASTER_SOUND_VOLUME_MIXING: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0082) }; -const MASTER_SOUND_STATUS: MemoryMapped = unsafe { MemoryMapped::new(0x0400_0084) }; - -#[non_exhaustive] -pub struct Sound {} - -impl Sound { - pub(crate) const unsafe fn new() -> Self { - Sound {} - } - - pub fn channel1(&self) -> Channel1 { - Channel1 {} - } - - pub fn channel2(&self) -> Channel2 { - Channel2 {} - } - - pub fn noise(&self) -> Noise { - Noise {} - } - - pub fn enable(&self) { - MASTER_SOUND_STATUS.set_bits(1, 1, 7); - - #[allow(clippy::unusual_byte_groupings)] // I've split these like this for a reason - MASTER_SOUND_VOLUME_ENABLE.set(0b1111_1111_0_111_0_111); - MASTER_SOUND_VOLUME_MIXING.set(0b10); - } -} - -#[non_exhaustive] -pub struct Channel1 {} - -impl Channel1 { - pub fn play_sound( - &self, - frequency: u16, - length: Option, - sweep_settings: &SweepSettings, - envelope_settings: &EnvelopeSettings, - duty_cycle: DutyCycle, - ) { - CHANNEL_1_SWEEP.set(sweep_settings.as_bits()); - let length_bits = length.unwrap_or(0) as u16; - assert!(length_bits < 64, "Length must be less than 64"); - - let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0); - let initial: u16 = 1 << 15; - - assert!(frequency < 2048, "Frequency must be less than 2048"); - - CHANNEL_1_LENGTH_DUTY_ENVELOPE - .set(envelope_settings.as_bits() | duty_cycle.as_bits() | length_bits); - CHANNEL_1_FREQUENCY_CONTROL.set(frequency | length_flag | initial); - } -} - -#[non_exhaustive] -pub struct Channel2 {} - -impl Channel2 { - pub fn play_sound( - &self, - frequency: u16, - length: Option, - envelope_settings: &EnvelopeSettings, - duty_cycle: DutyCycle, - ) { - let length_bits = length.unwrap_or(0) as u16; - assert!(length_bits < 64, "Length must be less than 64"); - - let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0); - let initial: u16 = 1 << 15; - - assert!(frequency < 2048, "Frequency must be less than 2048"); - - CHANNEL_2_LENGTH_DUTY_ENVELOPE - .set(envelope_settings.as_bits() | duty_cycle.as_bits() | length_bits); - CHANNEL_2_FREQUENCY_CONTROL.set(frequency | length_flag | initial); - } -} - -#[non_exhaustive] -pub struct Noise {} - -impl Noise { - pub fn play_sound( - &self, - length: Option, - envelope_setting: &EnvelopeSettings, - frequency_divider: u8, - counter_step_width_15: bool, - shift_clock_frequency: u8, - ) { - let length_bits = length.unwrap_or(0) as u16; - assert!(length_bits < 64, "length must be less than 16"); - - assert!( - frequency_divider < 8, - "frequency divider must be less than 8" - ); - assert!( - shift_clock_frequency < 16, - "frequency clock divider must be less than 16" - ); - - let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0); - let initial: u16 = 1 << 15; - - let counter_step_bit = if counter_step_width_15 { 0 } else { 1 << 3 }; - - CHANNEL_4_LENGTH_ENVELOPE.set(length_bits | envelope_setting.as_bits()); - CHANNEL_4_FREQUENCY_CONTROL.set( - (frequency_divider as u16) - | counter_step_bit - | ((shift_clock_frequency as u16) << 4) - | length_flag - | initial, - ) - } -} - -pub enum SoundDirection { - Increase, - Decrease, -} - -impl SoundDirection { - fn as_bits(&self) -> u16 { - match &self { - SoundDirection::Increase => 1, - SoundDirection::Decrease => 0, - } - } -} - -pub struct SweepSettings { - number_of_sweep_shifts: u8, - sound_direction: SoundDirection, - sweep_time: u8, -} - -impl SweepSettings { - pub fn new( - number_of_sweep_shifts: u8, - sound_direction: SoundDirection, - sweep_time: u8, - ) -> Self { - assert!( - number_of_sweep_shifts < 8, - "Number of sweep shifts must be less than 8" - ); - assert!(sweep_time < 8, "Sweep time must be less than 8"); - - SweepSettings { - number_of_sweep_shifts, - sound_direction, - sweep_time, - } - } - - fn as_bits(&self) -> u16 { - ((self.number_of_sweep_shifts as u16) & 0b111) - | ((1 - self.sound_direction.as_bits()) << 3) // sweep works backwards - | ((self.sweep_time as u16) & 0b111) << 4 - } -} - -impl Default for SweepSettings { - fn default() -> Self { - SweepSettings::new(0, SoundDirection::Increase, 0) - } -} - -pub struct EnvelopeSettings { - step_time: u8, - direction: SoundDirection, - initial_volume: u8, -} - -impl EnvelopeSettings { - pub fn new(step_time: u8, direction: SoundDirection, initial_volume: u8) -> Self { - assert!(step_time < 8, "Step time must be less than 8"); - assert!(initial_volume < 16, "Initial volume must be less that 16"); - EnvelopeSettings { - step_time, - direction, - initial_volume, - } - } - - fn as_bits(&self) -> u16 { - (self.step_time as u16) << 8 - | (self.direction.as_bits() << 11) - | ((self.initial_volume as u16) << 12) - } -} - -impl Default for EnvelopeSettings { - fn default() -> Self { - EnvelopeSettings::new(0, SoundDirection::Increase, 15) - } -} - -pub enum DutyCycle { - OneEighth, - OneQuarter, - Half, - ThreeQuarters, -} - -impl DutyCycle { - fn as_bits(&self) -> u16 { - use DutyCycle::*; - - match &self { - OneEighth => 0, - OneQuarter => 1, - Half => 2, - ThreeQuarters => 3, - } - } -} +pub mod dmg; +pub mod mixer;