diff --git a/src/main.rs b/src/main.rs index 91bc484..6ce0466 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,11 @@ mod halfwidth; use core::fmt::Write; use gba::prelude::*; +use voladdress::{Safe, VolAddress}; + +#[macro_use] +extern crate build_const; +build_const!("lsdpack_bc"); #[allow(unused_must_use)] #[panic_handler] @@ -53,18 +58,379 @@ fn panic_handler(info: &core::panic::PanicInfo) -> ! { static FRAME_KEYS: GbaCell = GbaCell::new(KeyInput::new()); #[link_section = ".iwram"] -extern "C" fn irq_handler(_: IrqBits) { - // We'll read the keys during vblank and store it for later. - FRAME_KEYS.write(KEYINPUT.read()); +static mut LSDJ_SONGS: Lsdpack = Lsdpack::new(&LSDPACK_DATA, &LSDPACK_SONG_POSITIONS); + +#[link_section = ".iwram"] +extern "C" fn irq_handler(bits: IrqBits) { + if bits.vblank() { + // We'll read the keys during vblank and store it for later. + FRAME_KEYS.write(KEYINPUT.read()); + } + if bits.timer0() { + unsafe { + LSDJ_SONGS.tick(); + } + } +} + +#[allow(dead_code)] +#[repr(u8)] +#[derive(Copy, Clone)] +enum LsdpackCmd { + // special commands (not just plain register writes) + EndTick = 0, + SampleStart = 1, + SongStop = 2, + NextBank = 3, + AmpDecPu0 = 4, + AmpDecPu1 = 5, + AmpDecNoi = 6, + PitchPu0 = 7, + PitchPu1 = 8, + PitchWav = 9, + SampleNext = 10, + + _Invalid0B = 0x0b, + _Invalid0C = 0x0c, + _Invalid0D = 0x0d, + _Invalid0E = 0x0e, + _Invalid0F = 0x0f, + + // ch1 pulse + Pu0Sweep = 0x10, + Pu0LengthWave = 0x11, + Pu0Env = 0x12, + Pu0PitchLsb = 0x13, + Pu0PitchMsb = 0x14, + + _Invalid15 = 0x15, + + // ch2 pulse + Pu1LengthWave = 0x16, + Pu1Env = 0x17, + Pu1PitchLsb = 0x18, + Pu1PitchMsb = 0x19, + + // ch3 wave + WavOnOff = 0x1a, + WavLength = 0x1b, + WavEnv = 0x1c, + WavPitchLsb = 0x1d, + WavPitchMsb = 0x1e, + + _Invalid1F = 0x1f, + + // ch4 noise + NoiLength = 0x20, + NoiEnv = 0x21, + NoiWave = 0x22, + NoiTrig = 0x23, + + // general + ChannelVolume = 0x24, + Pan = 0x25, + SoundOffOn = 0x26, + + _Invalid27 = 0x27, + _Invalid28 = 0x28, + _Invalid29 = 0x29, + _Invalid2A = 0x2a, + _Invalid2B = 0x2b, + _Invalid2C = 0x2c, + _Invalid2D = 0x2d, + _Invalid2E = 0x2e, + _Invalid2F = 0x2f, + + // wave pattern + WavePattern0 = 0x30, + WavePattern1 = 0x31, + WavePattern2 = 0x32, + WavePattern3 = 0x33, + WavePattern4 = 0x34, + WavePattern5 = 0x35, + WavePattern6 = 0x36, + WavePattern7 = 0x37, + WavePattern8 = 0x38, + WavePattern9 = 0x39, + WavePatternA = 0x3a, + WavePatternB = 0x3b, + WavePatternC = 0x3c, + WavePatternD = 0x3d, + WavePatternE = 0x3e, + WavePatternF = 0x3f, +} + +impl From for LsdpackCmd { + fn from(val: u8) -> Self { + assert!(val <= 0x3f); + unsafe { core::mem::transmute(val) } + } +} + +struct Lsdpack { + data: &'static [u8], + setlist: &'static [usize], + current_ptr: usize, + sample_ptr: usize, + repeat_cmd: Option, + repeat_cmd_counter: usize, + sample_pitch: u16, + stopped: bool, +} + +impl Lsdpack { + const fn new(data: &'static [u8], setlist: &'static [usize]) -> Self { + Self { + data, + setlist, + current_ptr: setlist[0], + sample_ptr: 0, + repeat_cmd: None, + repeat_cmd_counter: 0, + sample_pitch: 0, + stopped: false, + } + } +} + +impl Lsdpack { + fn play_song(&mut self, song_id: usize) { + self.current_ptr = self.setlist[song_id]; + self.sample_ptr = 0; + self.repeat_cmd = None; + self.repeat_cmd_counter = 0; + self.stopped = false; + } + + #[link_section = ".iwram"] + fn next_byte(&mut self) -> u8 { + let val = self.data[self.current_ptr]; + self.current_ptr += 1; + val + } + + #[link_section = ".iwram"] + fn write_next_samples(&mut self) { + let ptr = self.sample_ptr; + self.sample_ptr += 16; + //let sound_enabled = SOUND_ENABLED.read(); + //SOUND_ENABLED.write(sound_enabled.with_wave_playing(false)); + let lrvol = LEFT_RIGHT_VOLUME.read(); + LEFT_RIGHT_VOLUME.write(lrvol.with_wave_left(false).with_wave_right(false)); + WAVE_BANK.write(WaveBank::new().with_enabled(false)); + WAVE_RAM.index(0).write(u32::from_le_bytes( + TryFrom::try_from(&self.data[ptr..ptr + 4]).unwrap(), + )); + WAVE_RAM.index(1).write(u32::from_le_bytes( + TryFrom::try_from(&self.data[ptr + 4..ptr + 8]).unwrap(), + )); + WAVE_RAM.index(2).write(u32::from_le_bytes( + TryFrom::try_from(&self.data[ptr + 8..ptr + 12]).unwrap(), + )); + WAVE_RAM.index(3).write(u32::from_le_bytes( + TryFrom::try_from(&self.data[ptr + 12..ptr + 16]).unwrap(), + )); + WAVE_BANK.write(WaveBank::new().with_enabled(true)); + //SOUND_ENABLED.write(sound_enabled); + LEFT_RIGHT_VOLUME.write(lrvol); + WAVE_FREQ.write(WaveFrequency::new().with_length(self.sample_pitch)); + } + + #[link_section = ".iwram"] + fn tick(&mut self) { + if self.stopped { + return; + } + let mut ongoing = true; + while ongoing { + if let Some(cmd) = self.repeat_cmd { + if self.repeat_cmd_counter > 0 { + self.repeat_cmd_counter -= 1; + ongoing = self.apply_cmd(cmd); + continue; + } else { + self.repeat_cmd = None; + } + } + let cmd = self.load_new_cmd_from_stream(); + ongoing = self.apply_cmd(cmd); + } + } + + #[link_section = ".iwram"] + fn load_new_cmd_from_stream(&mut self) -> LsdpackCmd { + let cmd = self.next_byte(); + if cmd & 0x40 != 0 { + self.repeat_cmd = Some((cmd & 0x3f).into()); + self.repeat_cmd_counter = self.next_byte() as usize; + self.repeat_cmd.unwrap() + } else { + cmd.into() + } + } + + #[link_section = ".iwram"] + fn write_lsb(&mut self, voladdr: VolAddress) { + let voladdr = unsafe { voladdr.cast::() }; + voladdr.write((voladdr.read() & 0xFF00) | (self.next_byte() as u16)); + } + + #[link_section = ".iwram"] + fn write_msb(&mut self, voladdr: VolAddress) { + let voladdr = unsafe { voladdr.cast::() }; + voladdr.write((voladdr.read() & 0xFF) | ((self.next_byte() as u16) << 8)); + } + + #[link_section = ".iwram"] + fn write_u16le(&mut self, voladdr: VolAddress) { + let voladdr = unsafe { voladdr.cast::() }; + let halfword = u16::from_le_bytes([self.next_byte(), self.next_byte()]); + voladdr.write(halfword); + } + + #[link_section = ".iwram"] + fn apply_cmd(&mut self, cmd: LsdpackCmd) -> bool { + use LsdpackCmd::*; + match cmd { + // special commands: + EndTick => return false, + SampleStart => { + let _sample_bank = self.next_byte(); // TODO? + self.sample_ptr = u16::from_le_bytes([self.next_byte(), self.next_byte()]) as usize; + // big endian order for the pitch here? don't know why + self.sample_pitch = u16::from_be_bytes([self.next_byte(), self.next_byte()]); + self.write_next_samples(); // also writes pitch + } + SongStop => { + self.stopped = true; + return false; + } + NextBank => { /* lol */ } + AmpDecPu0 => { + let base = TONE1_PATTERN.read(); + TONE1_PATTERN.write( + base.with_volume(0) + .with_step_increasing(true) + .with_step_time(1), + ); // $FF12 <- $9 + TONE1_PATTERN.write( + base.with_volume(1) + .with_step_increasing(false) + .with_step_time(1), + ); // $FF12 <- $11 + TONE1_PATTERN.write( + base.with_volume(1) + .with_step_increasing(true) + .with_step_time(0), + ); // $FF12 <- $18 + } + AmpDecPu1 => { + let base = TONE2_PATTERN.read(); + TONE2_PATTERN.write( + base.with_volume(0) + .with_step_increasing(true) + .with_step_time(1), + ); // $FF17 <- $9 + TONE2_PATTERN.write( + base.with_volume(1) + .with_step_increasing(false) + .with_step_time(1), + ); // $FF17 <- $11 + TONE2_PATTERN.write( + base.with_volume(1) + .with_step_increasing(true) + .with_step_time(0), + ); // $FF17 <- $18 + } + AmpDecNoi => { + let base = NOISE_LEN_ENV.read(); + NOISE_LEN_ENV.write( + base.with_volume(0) + .with_step_increasing(true) + .with_step_time(1), + ); // $FF21 <- $9 + NOISE_LEN_ENV.write( + base.with_volume(1) + .with_step_increasing(false) + .with_step_time(1), + ); // $FF21 <- $11 + NOISE_LEN_ENV.write( + base.with_volume(1) + .with_step_increasing(true) + .with_step_time(0), + ); // $FF21 <- $18 + } + PitchPu0 => self.write_u16le(TONE1_FREQUENCY), + PitchPu1 => self.write_u16le(TONE2_FREQUENCY), + PitchWav => self.write_u16le(WAVE_FREQ), + SampleNext => self.write_next_samples(), + + // general register writes: + Pu0Sweep => unsafe { TONE1_SWEEP.cast() }.write(self.next_byte()), + Pu0LengthWave => self.write_lsb(TONE1_PATTERN), + Pu0Env => self.write_msb(TONE1_PATTERN), + Pu0PitchLsb => self.write_lsb(TONE1_FREQUENCY), + Pu0PitchMsb => self.write_msb(TONE1_FREQUENCY), + Pu1LengthWave => self.write_lsb(TONE2_PATTERN), + Pu1Env => self.write_msb(TONE2_PATTERN), + Pu1PitchLsb => self.write_lsb(TONE2_FREQUENCY), + Pu1PitchMsb => self.write_msb(TONE2_FREQUENCY), + WavOnOff => WAVE_BANK.write(WaveBank::new().with_enabled(self.next_byte() != 0)), + WavLength => self.write_lsb(WAVE_LEN_VOLUME), + WavEnv => self.write_msb(WAVE_LEN_VOLUME), + WavPitchLsb => self.write_lsb(WAVE_FREQ), + WavPitchMsb => self.write_msb(WAVE_FREQ), + NoiLength => self.write_lsb(NOISE_LEN_ENV), + NoiEnv => self.write_msb(NOISE_LEN_ENV), + NoiWave => self.write_lsb(NOISE_FREQ), + NoiTrig => self.write_msb(NOISE_FREQ), + ChannelVolume => self.write_lsb(LEFT_RIGHT_VOLUME), + Pan => self.write_msb(LEFT_RIGHT_VOLUME), + SoundOffOn => unsafe { SOUND_ENABLED.cast() }.write(self.next_byte()), + WavePattern0 => todo!(), + WavePattern1 => todo!(), + WavePattern2 => todo!(), + WavePattern3 => todo!(), + WavePattern4 => todo!(), + WavePattern5 => todo!(), + WavePattern6 => todo!(), + WavePattern7 => todo!(), + WavePattern8 => todo!(), + WavePattern9 => todo!(), + WavePatternA => todo!(), + WavePatternB => todo!(), + WavePatternC => todo!(), + WavePatternD => todo!(), + WavePatternE => todo!(), + WavePatternF => todo!(), + _Invalid0B | _Invalid0C | _Invalid0D | _Invalid0E | _Invalid0F | _Invalid15 + | _Invalid1F | _Invalid27 | _Invalid28 | _Invalid29 | _Invalid2A | _Invalid2B + | _Invalid2C | _Invalid2D | _Invalid2E | _Invalid2F => unimplemented!(), + } + true + } } #[no_mangle] extern "C" fn main() -> ! { RUST_IRQ_HANDLER.write(Some(irq_handler)); DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true)); - IE.write(IrqBits::VBLANK); + IE.write(IrqBits::VBLANK.with_timer0(true)); IME.write(true); + TIMER0_RELOAD.write((((16777216 / 256) / 6) as u16).wrapping_neg()); + TIMER0_CONTROL.write( + TimerControl::new() + .with_overflow_irq(true) + .with_cascade(false) + .with_scale(TimerScale::_256) + .with_enabled(true), + ); + + unsafe { + LSDJ_SONGS.play_song(0); + } + if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Debug) { writeln!(logger, "hello!").ok();