first pass at implementing runtime playback

This commit is contained in:
lif 2024-01-13 22:34:34 -08:00
parent 28fd640d28
commit 4657e649b8

View file

@ -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<KeyInput> = 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<u8> 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>,
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<T>(&mut self, voladdr: VolAddress<T, Safe, Safe>) {
let voladdr = unsafe { voladdr.cast::<u16>() };
voladdr.write((voladdr.read() & 0xFF00) | (self.next_byte() as u16));
}
#[link_section = ".iwram"]
fn write_msb<T>(&mut self, voladdr: VolAddress<T, Safe, Safe>) {
let voladdr = unsafe { voladdr.cast::<u16>() };
voladdr.write((voladdr.read() & 0xFF) | ((self.next_byte() as u16) << 8));
}
#[link_section = ".iwram"]
fn write_u16le<T>(&mut self, voladdr: VolAddress<T, Safe, Safe>) {
let voladdr = unsafe { voladdr.cast::<u16>() };
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();