first pass at implementing runtime playback
This commit is contained in:
parent
28fd640d28
commit
4657e649b8
374
src/main.rs
374
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<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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue