use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use bincode::error::DecodeError;
use serde::Deserialize;
use crate::import::patternslot::PatternSlot;
use crate::pitch::Pitch;
pub(crate) const NO_VOL_COL: u8 = 0xFF;
#[derive(Deserialize, Debug, Default)]
#[repr(C)]
pub struct ItPattern {
pattern_length: u16,
row_count: i16,
reserved: u32,
packed_data: Vec<u8>,
}
impl ItPattern {
pub fn load(source: &[u8]) -> Result<Self, DecodeError> {
let mut data = source;
if data.len() < 8 {
return Err(DecodeError::LimitExceeded);
}
let pattern_length: u16 = u16::from_le_bytes(data[0..2].try_into().unwrap());
let row_count: i16 = i16::from_le_bytes(data[2..4].try_into().unwrap());
let reserved: u32 = u32::from_le_bytes(data[4..8].try_into().unwrap());
data = &data[8..];
if data.len() < pattern_length as usize {
return Err(DecodeError::LimitExceeded);
}
if pattern_length == 0 {
return Ok(Self {
pattern_length,
row_count,
reserved,
packed_data: vec![],
});
}
Ok(Self {
pattern_length,
row_count,
reserved,
packed_data: data[..pattern_length as usize].to_vec(),
})
}
pub fn unpack(&self) -> Result<Vec<Vec<PatternSlot>>, DecodeError> {
if self.row_count <= 0 {
return Ok(vec![]);
}
let no_vol_default = PatternSlot {
volume: NO_VOL_COL,
..PatternSlot::default()
};
let mut result = vec![vec![no_vol_default; 64]; self.row_count as usize];
let mut last_mask_vars = [0u8; 64];
let mut last_note = [Pitch::None; 64];
let mut last_instrument: [Option<usize>; 64] = [None; 64];
let mut last_volume = [0u8; 64];
let mut last_effect_type = [0u8; 64];
let mut last_effect_parameter = [0u8; 64];
let mut data_iter = self.packed_data.iter();
for row in 0..self.row_count as usize {
let mut channel_mask = match data_iter.next() {
Some(&mask) => mask,
None => break,
};
while channel_mask > 0 {
let channel = (channel_mask - 1) & 63;
let ch = channel as usize;
let mask_variable = if channel_mask & 0x80 != 0 {
let var = *data_iter.next().ok_or(DecodeError::LimitExceeded)?;
last_mask_vars[ch] = var;
var
} else {
last_mask_vars[ch]
};
let mut slot = no_vol_default;
if mask_variable & 0x01 != 0 {
let mut n = *data_iter.next().ok_or(DecodeError::LimitExceeded)?;
if n > 119 && n < 253 {
n = Pitch::Fade as u8;
}
let note = n.try_into().map_err(|_| {
DecodeError::OtherString("Failed to convert note".to_string())
})?;
slot.note = note;
last_note[ch] = note;
} else if mask_variable & 0x10 != 0 {
slot.note = last_note[ch];
}
if mask_variable & 0x02 != 0 {
let instr = *data_iter.next().ok_or(DecodeError::LimitExceeded)?;
let instr_idx = if instr != 0 {
Some(instr as usize - 1)
} else {
None
};
slot.instrument = instr_idx;
last_instrument[ch] = instr_idx;
} else if mask_variable & 0x20 != 0 {
slot.instrument = last_instrument[ch];
}
if mask_variable & 0x04 != 0 {
let vol = *data_iter.next().ok_or(DecodeError::LimitExceeded)?;
slot.volume = vol;
last_volume[ch] = vol;
} else if mask_variable & 0x40 != 0 {
slot.volume = last_volume[ch];
}
if mask_variable & 0x08 != 0 {
let et = *data_iter.next().ok_or(DecodeError::LimitExceeded)?;
let ep = *data_iter.next().ok_or(DecodeError::LimitExceeded)?;
slot.effect_type = et;
slot.effect_parameter = ep;
last_effect_type[ch] = et;
last_effect_parameter[ch] = ep;
} else if mask_variable & 0x80 != 0 {
slot.effect_type = last_effect_type[ch];
slot.effect_parameter = last_effect_parameter[ch];
}
result[row][ch] = slot;
channel_mask = match data_iter.next() {
Some(&mask) => mask,
None => break,
};
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn unpack_one_row(packed: &[u8], row_count: i16) -> Vec<Vec<PatternSlot>> {
let pat = ItPattern {
pattern_length: packed.len() as u16,
row_count,
reserved: 0,
packed_data: packed.to_vec(),
};
pat.unpack().expect("unpack")
}
#[test]
fn empty_row_marks_volume_with_no_vol_col_sentinel() {
let rows = unpack_one_row(&[0x00], 1);
for ch in 0..64 {
assert_eq!(
rows[0][ch].volume, NO_VOL_COL,
"channel {ch} should keep NO_VOL_COL sentinel on an empty row"
);
}
}
#[test]
fn explicit_zero_volume_is_preserved() {
let packed = &[
0x80 | 1, 0x04, 0x00, 0x00, ];
let rows = unpack_one_row(packed, 1);
assert_eq!(rows[0][0].volume, 0, "explicit vol=0 must round-trip as 0");
assert_eq!(rows[0][1].volume, NO_VOL_COL);
}
#[test]
fn explicit_nonzero_volume_is_preserved() {
let packed = &[
0x80 | 1, 0x04, 40, 0x00, ];
let rows = unpack_one_row(packed, 1);
assert_eq!(rows[0][0].volume, 40);
}
#[test]
fn cached_volume_recall_uses_last_explicit_value() {
let packed = vec![
0x80 | 1,
0x04,
32,
0x00, 0x80 | 1,
0x40,
0x00, ];
let rows = unpack_one_row(&packed, 2);
assert_eq!(rows[0][0].volume, 32);
assert_eq!(rows[1][0].volume, 32, "0x40 must recall last explicit vol");
}
}