#![forbid(unsafe_code)]
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DwTrackEvent {
Note(u8),
LongWait(u8),
SetSample(u8),
SetPitchArpeggio(u8),
SetVolumeEnvelope(u8),
EndOfTrack,
Slide {
speed: i8,
counter: u8,
},
Mute,
WaitUntilNextRow,
StopSong,
GlobalTranspose(i8),
StartVibrato {
speed: u8,
max: u8,
},
StopVibrato,
Effect8(u8),
Effect9(u8),
SetSpeed(u8),
GlobalVolumeFade(u8),
SetGlobalVolume(u8),
StartOrStopSoundFx(u8),
StopSoundFx,
Retrigger,
SeqPtr(u16),
}
impl DwTrackEvent {
#[inline]
pub fn is_terminator(&self) -> bool {
matches!(self, DwTrackEvent::EndOfTrack)
}
#[inline]
pub fn is_note(&self) -> bool {
matches!(self, DwTrackEvent::Note(_))
}
}
pub fn decode_track(
bytes: &[u8],
dispatcher: &super::detect::DwDispatcher,
features: &super::detect::DwFeatures,
command_map: Option<&super::command_map::DwCommandMap>,
) -> Vec<DwTrackEvent> {
decode_track_counted(bytes, dispatcher, features, command_map).0
}
pub fn decode_track_counted(
bytes: &[u8],
dispatcher: &super::detect::DwDispatcher,
features: &super::detect::DwFeatures,
command_map: Option<&super::command_map::DwCommandMap>,
) -> (Vec<DwTrackEvent>, usize) {
let mut out: Vec<DwTrackEvent> = Vec::new();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
i += 1;
if b < 0x80 {
out.push(DwTrackEvent::Note(b));
continue;
}
if b >= 0xE0 {
out.push(DwTrackEvent::LongWait(b));
continue;
}
let (sample_t, vol_t, arp_t) = dispatch_thresholds(dispatcher);
if b >= sample_t {
out.push(DwTrackEvent::SetSample(b));
continue;
}
if let Some(t) = vol_t {
if b >= t {
out.push(DwTrackEvent::SetVolumeEnvelope(b));
continue;
}
}
if let Some(t) = arp_t {
if b >= t {
out.push(DwTrackEvent::SetPitchArpeggio(b));
continue;
}
}
if let Some(cmd) = command_map.and_then(|m| m.get(b)) {
use super::command_map::DwCmdKind as K;
let ev: Option<DwTrackEvent> = match cmd.kind {
K::SeqAdvance => Some(DwTrackEvent::EndOfTrack),
K::Slide => {
let speed = take_byte(bytes, &mut i) as i8;
let counter = take_byte(bytes, &mut i);
Some(DwTrackEvent::Slide { speed, counter })
}
K::Vibrato => {
let speed = take_byte(bytes, &mut i);
let max = take_byte(bytes, &mut i);
Some(DwTrackEvent::StartVibrato { speed, max })
}
K::NoteStop => Some(DwTrackEvent::Mute),
K::GlobalTranspose => {
Some(DwTrackEvent::GlobalTranspose(take_byte(bytes, &mut i) as i8))
}
K::SlideOn => {
let speed = take_byte(bytes, &mut i);
let max = take_byte(bytes, &mut i);
Some(DwTrackEvent::StartVibrato { speed, max })
}
K::SlideOff => Some(DwTrackEvent::StopVibrato),
K::ChannelTranspose => Some(DwTrackEvent::Effect8(take_byte(bytes, &mut i))),
K::ResetAll => Some(DwTrackEvent::StopSong),
K::Retrigger => Some(if features.note_repeat_is_tie {
DwTrackEvent::WaitUntilNextRow
} else {
DwTrackEvent::Retrigger
}),
K::SeqPtr => {
let hi = take_byte(bytes, &mut i);
let lo = take_byte(bytes, &mut i);
Some(DwTrackEvent::SeqPtr(u16::from_be_bytes([hi, lo])))
}
K::GlobalParam | K::Unknown => {
for _ in 0..cmd.args {
let _ = take_byte(bytes, &mut i);
}
None
}
};
if let Some(e) = ev {
let is_term = matches!(e, DwTrackEvent::EndOfTrack);
out.push(e);
if is_term {
break;
}
}
continue;
}
let event = match b & 0x7F {
0 => DwTrackEvent::EndOfTrack,
1 => {
let speed = take_byte(bytes, &mut i) as i8;
let counter = take_byte(bytes, &mut i);
DwTrackEvent::Slide { speed, counter }
}
2 => DwTrackEvent::Mute,
3 => DwTrackEvent::WaitUntilNextRow,
4 => DwTrackEvent::StopSong,
5 => DwTrackEvent::GlobalTranspose(take_byte(bytes, &mut i) as i8),
6 => {
let speed = take_byte(bytes, &mut i);
let max = take_byte(bytes, &mut i);
DwTrackEvent::StartVibrato { speed, max }
}
7 => DwTrackEvent::StopVibrato,
8 => DwTrackEvent::Effect8(take_byte(bytes, &mut i)),
9 => {
if features.enable_half_volume {
DwTrackEvent::Effect9(0)
} else {
let a = take_byte(bytes, &mut i);
let _b = take_byte(bytes, &mut i);
DwTrackEvent::Effect9(a)
}
}
10 => DwTrackEvent::SetSpeed(take_byte(bytes, &mut i)),
11 => DwTrackEvent::GlobalVolumeFade(take_byte(bytes, &mut i)),
12 => DwTrackEvent::SetGlobalVolume(take_byte(bytes, &mut i)),
13 => DwTrackEvent::StartOrStopSoundFx(take_byte(bytes, &mut i)),
14 => DwTrackEvent::StopSoundFx,
_ => DwTrackEvent::EndOfTrack,
};
let is_term = matches!(event, DwTrackEvent::EndOfTrack);
out.push(event);
if is_term {
break;
}
}
(out, i)
}
fn dispatch_thresholds(d: &super::detect::DwDispatcher) -> (u8, Option<u8>, Option<u8>) {
if d.thresholds.is_empty() {
(0xB0, Some(0xA0), Some(0x90))
} else {
(
d.sample_threshold().unwrap_or(0xB0),
d.volume_envelope_threshold(),
d.pitch_arpeggio_threshold(),
)
}
}
#[inline]
fn take_byte(bytes: &[u8], cursor: &mut usize) -> u8 {
if *cursor < bytes.len() {
let v = bytes[*cursor];
*cursor += 1;
v
} else {
0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decodes_notes_and_end() {
let bytes = [0x10, 0x20, 0x30, 0x80];
let events = decode_track(&bytes, &Default::default(), &Default::default(), None);
assert_eq!(
events,
alloc::vec![
DwTrackEvent::Note(0x10),
DwTrackEvent::Note(0x20),
DwTrackEvent::Note(0x30),
DwTrackEvent::EndOfTrack,
]
);
}
#[test]
fn decodes_slide_with_two_params() {
let bytes = [0x81, 0xFD, 0x05, 0x80];
let events = decode_track(&bytes, &Default::default(), &Default::default(), None);
assert_eq!(
events,
alloc::vec![
DwTrackEvent::Slide {
speed: -3,
counter: 5
},
DwTrackEvent::EndOfTrack,
]
);
}
#[test]
fn decodes_long_wait_and_set_sample() {
let bytes = [0xB2, 0xE3, 0x80];
let events = decode_track(&bytes, &Default::default(), &Default::default(), None);
assert_eq!(
events,
alloc::vec![
DwTrackEvent::SetSample(0xB2),
DwTrackEvent::LongWait(0xE3),
DwTrackEvent::EndOfTrack,
]
);
}
#[test]
fn decodes_table_dispatch_ranges() {
let bytes = [0x95, 0xA3, 0xC1, 0x80];
let events = decode_track(&bytes, &Default::default(), &Default::default(), None);
assert_eq!(
events,
alloc::vec![
DwTrackEvent::SetPitchArpeggio(0x95),
DwTrackEvent::SetVolumeEnvelope(0xA3),
DwTrackEvent::SetSample(0xC1),
DwTrackEvent::EndOfTrack,
]
);
}
#[test]
fn terminator_truncates_trailing_garbage() {
let bytes = [0x80, 0x10, 0x20];
let events = decode_track(&bytes, &Default::default(), &Default::default(), None);
assert_eq!(events, alloc::vec![DwTrackEvent::EndOfTrack]);
}
}