// from https://github.com/rust-console/gba/blob/main/examples/hello.rs #![no_std] #![no_main] #![feature(ascii_char)] #![feature(panic_info_message)] // rustc is simply wrong about core::fmt::Write being unused, // it's used in the panic handler. #![allow(unused_imports)] 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] fn panic_handler(info: &core::panic::PanicInfo) -> ! { let mut text_painter = halfwidth::TextPainter::new(); text_painter.setup_display(); text_painter.write_str("panic at "); if let Some(location) = info.location() { write!(&mut text_painter, "{}:{}", location.file(), location.line()); } else { text_painter.write_str("unknown location"); }; if let Some(msg) = info.message() { write!(&mut text_painter, ":\n"); core::fmt::write(&mut text_painter, *msg); } if let Some(s) = info.payload().downcast_ref::<&str>() { text_painter.write_str("\n"); text_painter.write_str(s); } #[cfg(debug_assertions)] if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Fatal) { for _ in 0..4 { VBlankIntrWait(); } if let Some(args) = info.message() { logger.write_fmt(*args); } else { writeln!(logger, "{info}"); } } loop { VBlankIntrWait(); } } #[link_section = ".ewram"] static FRAME_KEYS: GbaCell = GbaCell::new(KeyInput::new()); #[link_section = ".iwram"] 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<(LsdpackCmd, bool)>, 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, flagged_ongoing)) = self.repeat_cmd { if self.repeat_cmd_counter > 0 { self.repeat_cmd_counter -= 1; ongoing = flagged_ongoing; ongoing &= self.apply_cmd(cmd); continue; } else { self.repeat_cmd = None; } } let (cmd, flagged_ongoing) = self.load_new_cmd_from_stream(); ongoing = flagged_ongoing; ongoing &= self.apply_cmd(cmd); } } #[link_section = ".iwram"] fn load_new_cmd_from_stream(&mut self) -> (LsdpackCmd, bool) { const FLAG_REPEAT: u8 = 0x40; const FLAG_END_TICK: u8 = 0x80; let mut cmd = self.next_byte(); let mut ongoing = true; if cmd & FLAG_END_TICK != 0 { cmd &= !FLAG_END_TICK; ongoing = false; } if cmd & FLAG_REPEAT != 0 { cmd &= !FLAG_REPEAT; self.repeat_cmd = Some((cmd.into(), ongoing)); self.repeat_cmd_counter = self.next_byte() as usize; } (cmd.into(), ongoing) } #[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 => { // https://github.com/LIJI32/SameBoy/blob/27b5935b8d0e0af988bcac8e55e92703af24f335/Core/apu.c#L341 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.with_timer0(true)); IME.write(true); //TIMER0_RELOAD.write((((16777216 / 1) / 360) as u16).wrapping_neg()); TIMER0_RELOAD.write((((280896 / 1) / 6) as u16).wrapping_neg()); TIMER0_CONTROL.write( TimerControl::new() .with_overflow_irq(true) .with_cascade(false) .with_scale(TimerScale::_1) .with_enabled(true), ); SOUNDBIAS.write( SoundBias::new() .with_bias_level(0x100) .with_sample_cycle(SampleCycle::_6bit), ); unsafe { LSDJ_SONGS.play_song(0); } if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Debug) { writeln!(logger, "hello!").ok(); let fx_u: Fixed = Fixed::::wrapping_from(7) + Fixed::::from_raw(12); writeln!(logger, "fixed unsigned: {fx_u:?}").ok(); let fx_i1: Fixed = Fixed::::wrapping_from(8) + Fixed::::from_raw(15); writeln!(logger, "fixed signed positive: {fx_i1:?}").ok(); let fx_i2: Fixed = Fixed::::wrapping_from(0) - Fixed::::wrapping_from(3) - Fixed::::from_raw(17); writeln!(logger, "fixed signed negative: {fx_i2:?}").ok(); } { // get our tile data into memory. Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0); } { // set up the tilemap let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap(); for y in 0..16 { let row = tsb.get_row(y).unwrap(); for (x, addr) in row.iter().enumerate().take(16) { let te = TextEntry::new().with_tile((y * 16 + x) as u16); addr.write(te); } } } { // Set BG0 to use the tilemap we just made, and set it to be shown. BG0CNT.write(BackgroundControl::new().with_screenblock(31)); DISPCNT.write(DisplayControl::new().with_show_bg0(true)); } let mut x_off = 0_u32; let mut y_off = 0_u32; let mut backdrop_color = Color(0); loop { VBlankIntrWait(); // show current frame BACKDROP_COLOR.write(backdrop_color); BG0HOFS.write(x_off as u16); BG0VOFS.write(y_off as u16); // prep next frame let k = FRAME_KEYS.read(); backdrop_color = Color(k.to_u16()); if k.up() { y_off = y_off.wrapping_add(1); } if k.down() { y_off = y_off.wrapping_sub(1); } if k.left() { x_off = x_off.wrapping_add(1); } if k.right() { x_off = x_off.wrapping_sub(1); } if k.start() { panic!("pressed start") } } }