use crate::file::err; use crate::project::event_command::NoteCommand; use crate::project::note_event::{Note, NoteEvent, VolumeEffect}; use crate::project::pattern::{InPatternPosition, Pattern}; use core::error::Error; use core::fmt::Display; use std::io::Read; /// reader should be buffered in some way and not do a syscall on every read call. /// /// This function does a lot of read calls pub fn parse_pattern(reader: &mut R) -> Result { const PATTERN_HEADER_SIZE: usize = 8; 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]]), ) }; // a guarantee given by the impulse tracker "specs" 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 { let channel_variable = scratch[0]; if channel_variable == 0 { row_num += 1; continue; } let channel = (channel_variable - 1) & 63; // 64 channels, 0 based 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(); // Note 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() // } // }; let note = Note::new(scratch[0]).unwrap_or_default(); event.note = note; last_event[channel_id].note = note; } // Instrument / Sample if (maskvar & 0b00000010) != 0 { reader.read_exact(&mut scratch)?; let instrument = scratch[0]; event.sample_instr = instrument; last_event[channel_id].sample_instr = instrument; } // Volume if (maskvar & 0b00000100) != 0 { reader.read_exact(&mut scratch)?; // let vol_pan = match vol_pan_raw.try_into() { // Ok(v) => v, // Err(_) => { // // defect_handler(LoadDefect::OutOfBoundsValue); // VolumeEffect::default() // } // }; let vol_pan = volumeeffect_try_from(scratch[0]).unwrap_or_default(); last_event[channel_id].vol = vol_pan; event.vol = vol_pan; } // Effect 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() // } // }; let cmd = note_command_try_from((command, cmd_val)).unwrap_or_default(); last_event[channel_id].command = cmd; event.command = cmd; } // Same note if (maskvar & 0b00010000) != 0 { event.note = last_event[channel_id].note; } // Same Instr / Sample if (maskvar & 0b00100000) != 0 { event.sample_instr = last_event[channel_id].sample_instr; } // Same volume if (maskvar & 0b01000000) != 0 { event.vol = last_event[channel_id].vol; } // Same Command if (maskvar & 0b10000000) != 0 { event.command = last_event[channel_id].command; } pattern.set_event( InPatternPosition { row: row_num, channel, }, event, ); } Ok(pattern) } #[derive(Debug, Clone, Copy)] pub struct InvalidVolumeEffect; impl Display for InvalidVolumeEffect { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Invalid Volume Effect") } } impl Error for InvalidVolumeEffect {} /// IT Tracker Format Conversion /// no way to get None, as then it just doesn't get set fn volumeeffect_try_from(value: u8) -> Result { match value { 0..=64 => Ok(VolumeEffect::Volume(value)), 65..=74 => Ok(VolumeEffect::FineVolSlideUp(value - 65)), 75..=84 => Ok(VolumeEffect::FineVolSlideDown(value - 75)), 85..=94 => Ok(VolumeEffect::VolSlideUp(value - 85)), 95..=104 => Ok(VolumeEffect::VolSlideDown(value - 95)), 105..=114 => Ok(VolumeEffect::PitchSlideDown(value - 105)), 115..=124 => Ok(VolumeEffect::PitchSlideUp(value - 115)), 128..=192 => Ok(VolumeEffect::Panning(value - 128)), 193..=202 => Ok(VolumeEffect::SlideToNoteWithSpeed(value - 193)), 203..=212 => Ok(VolumeEffect::VibratoWithSpeed(value - 203)), _ => Err(InvalidVolumeEffect), } } #[derive(Debug, Clone, Copy)] pub struct UnknownNoteCommand; impl Display for UnknownNoteCommand { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Unknown Note Command") } } impl Error for UnknownNoteCommand {} fn note_command_try_from( (command_type, command_value): (u8, u8), ) -> Result { match command_type { 0 => Ok(NoteCommand::None), 1 => Ok(NoteCommand::SetTempo(command_value)), 2 => Ok(NoteCommand::JumpToOrder(command_value)), 3 => Ok(NoteCommand::BreakToRow(command_value)), 4 => Ok(NoteCommand::VolumeSlideDown(command_value)), 5 => Ok(NoteCommand::PitchSlideDown(command_value)), 6 => Ok(NoteCommand::PitchSlideUp(command_value)), 7 => Ok(NoteCommand::SlideToNote(command_value)), 8 => Ok(NoteCommand::Vibrato(command_value)), 9 => Ok(NoteCommand::Tremor(command_value)), 10 => Ok(NoteCommand::Arpeggio(command_value)), 11 => Ok(NoteCommand::VibratoAndVolSlideDown(command_value)), 12 => Ok(NoteCommand::SlideToNoteAndVolSlideDown(command_value)), 13 => Ok(NoteCommand::SetChannelVol(command_value)), 14 => Ok(NoteCommand::ChannelVolumeSlideDown(command_value)), 15 => Ok(NoteCommand::SetSampleOffset(command_value)), 16 => Ok(NoteCommand::PanningSlide(command_value)), 17 => Ok(NoteCommand::RetriggerNote(command_value)), 18 => Ok(NoteCommand::Tremolo(command_value)), 19 => Ok(NoteCommand::AlmostEverything(command_value)), 20 => Ok(NoteCommand::TempoChange(command_value)), 21 => Ok(NoteCommand::FineVibrato(command_value)), 22 => Ok(NoteCommand::SetGlobalVolume(command_value)), 23 => Ok(NoteCommand::GlobalVolumeSlide(command_value)), 24 => Ok(NoteCommand::SetPanning(command_value)), 25 => Ok(NoteCommand::Panbrello(command_value)), 26 => Ok(NoteCommand::MIDIMacros(command_value)), _ => Err(UnknownNoteCommand), } }