use crate::file::err;
use crate::file::err::LoadDefect;
use crate::project::event_command::NoteCommand;
use crate::project::note_event::{Note, NoteEvent, VolumeEffect};
use crate::project::pattern::{InPatternPosition, Pattern};
pub fn parse_pattern<R: std::io::Read + std::io::Seek, H: FnMut(LoadDefect)>(
reader: &mut R,
defect_handler: &mut H,
) -> Result<Pattern, err::LoadErr> {
const PATTERN_HEADER_SIZE: usize = 8;
let read_start = reader.stream_position()?;
let (length, num_rows) = {
let mut header = [0; PATTERN_HEADER_SIZE];
reader.read_exact(&mut header)?;
(
u64::from(u16::from_le_bytes([header[0], header[1]])) + PATTERN_HEADER_SIZE as u64,
u16::from_le_bytes([header[2], header[3]]),
)
};
if length >= 64_000 {
return Err(err::LoadErr::Invalid);
}
if !(32..=200).contains(&num_rows) {
return Err(err::LoadErr::Invalid);
}
let mut pattern = Pattern::new(num_rows);
let mut row_num: u16 = 0;
let mut last_mask = [0; 64];
let mut last_event = [NoteEvent::default(); 64];
let mut scratch = [0; 1];
while row_num < num_rows && reader.stream_position()? - read_start < length {
let channel_variable = scratch[0];
if channel_variable == 0 {
row_num += 1;
continue;
}
let channel = (channel_variable - 1) & 63; let channel_id = usize::from(channel);
let maskvar = if (channel_variable & 0b10000000) != 0 {
reader.read_exact(&mut scratch)?;
let val = scratch[0];
last_mask[channel_id] = val;
val
} else {
last_mask[channel_id]
};
let mut event = NoteEvent::default();
if (maskvar & 0b00000001) != 0 {
reader.read_exact(&mut scratch)?;
let note = match Note::new(scratch[0]) {
Ok(n) => n,
Err(_) => {
defect_handler(LoadDefect::OutOfBoundsValue);
Note::default()
}
};
event.note = note;
last_event[channel_id].note = note;
}
if (maskvar & 0b00000010) != 0 {
reader.read_exact(&mut scratch)?;
let instrument = scratch[0];
event.sample_instr = instrument;
last_event[channel_id].sample_instr = instrument;
}
if (maskvar & 0b00000100) != 0 {
reader.read_exact(&mut scratch)?;
let vol_pan_raw = scratch[0];
let vol_pan = match vol_pan_raw.try_into() {
Ok(v) => v,
Err(_) => {
defect_handler(LoadDefect::OutOfBoundsValue);
VolumeEffect::default()
}
};
last_event[channel_id].vol = vol_pan;
event.vol = vol_pan;
}
if (maskvar & 0b00001000) != 0 {
reader.read_exact(&mut scratch)?;
let command = scratch[0];
reader.read_exact(&mut scratch)?;
let cmd_val = scratch[0];
let cmd = match NoteCommand::try_from((command, cmd_val)) {
Ok(cmd) => cmd,
Err(_) => {
defect_handler(LoadDefect::OutOfBoundsValue);
NoteCommand::default()
}
};
last_event[channel_id].command = cmd;
event.command = cmd;
}
if (maskvar & 0b00010000) != 0 {
event.note = last_event[channel_id].note;
}
if (maskvar & 0b00100000) != 0 {
event.sample_instr = last_event[channel_id].sample_instr;
}
if (maskvar & 0b01000000) != 0 {
event.vol = last_event[channel_id].vol;
}
if (maskvar & 0b10000000) != 0 {
event.command = last_event[channel_id].command;
}
pattern.set_event(
InPatternPosition {
row: row_num,
channel,
},
event,
);
}
if pattern.row_count() == row_num {
Ok(pattern)
} else {
Err(err::LoadErr::BufferTooShort)
}
}