first pass at implementing runtime playback
This commit is contained in:
parent
28fd640d28
commit
4657e649b8
370
src/main.rs
370
src/main.rs
|
@ -12,6 +12,11 @@ mod halfwidth;
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
|
||||||
use gba::prelude::*;
|
use gba::prelude::*;
|
||||||
|
use voladdress::{Safe, VolAddress};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate build_const;
|
||||||
|
build_const!("lsdpack_bc");
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
|
@ -53,18 +58,379 @@ fn panic_handler(info: &core::panic::PanicInfo) -> ! {
|
||||||
static FRAME_KEYS: GbaCell<KeyInput> = GbaCell::new(KeyInput::new());
|
static FRAME_KEYS: GbaCell<KeyInput> = GbaCell::new(KeyInput::new());
|
||||||
|
|
||||||
#[link_section = ".iwram"]
|
#[link_section = ".iwram"]
|
||||||
extern "C" fn irq_handler(_: IrqBits) {
|
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.
|
// We'll read the keys during vblank and store it for later.
|
||||||
FRAME_KEYS.write(KEYINPUT.read());
|
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]
|
#[no_mangle]
|
||||||
extern "C" fn main() -> ! {
|
extern "C" fn main() -> ! {
|
||||||
RUST_IRQ_HANDLER.write(Some(irq_handler));
|
RUST_IRQ_HANDLER.write(Some(irq_handler));
|
||||||
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
|
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
|
||||||
IE.write(IrqBits::VBLANK);
|
IE.write(IrqBits::VBLANK.with_timer0(true));
|
||||||
IME.write(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) {
|
if let Ok(mut logger) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Debug) {
|
||||||
writeln!(logger, "hello!").ok();
|
writeln!(logger, "hello!").ok();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue