torque_tracker_engine/file/impulse_format/
pattern.rs

1use crate::file::err;
2use crate::file::err::LoadDefect;
3use crate::project::event_command::NoteCommand;
4use crate::project::note_event::{Note, NoteEvent, VolumeEffect};
5use crate::project::pattern::{InPatternPosition, Pattern};
6
7/// reader should be buffered in some way and not do a syscall on every read call.
8///
9/// This function does a lot of read calls
10pub fn parse_pattern<R: std::io::Read + std::io::Seek, H: FnMut(LoadDefect)>(
11    reader: &mut R,
12    defect_handler: &mut H,
13) -> Result<Pattern, err::LoadErr> {
14    const PATTERN_HEADER_SIZE: usize = 8;
15
16    let read_start = reader.stream_position()?;
17
18    let (length, num_rows) = {
19        let mut header = [0; PATTERN_HEADER_SIZE];
20        reader.read_exact(&mut header)?;
21        (
22            u64::from(u16::from_le_bytes([header[0], header[1]])) + PATTERN_HEADER_SIZE as u64,
23            u16::from_le_bytes([header[2], header[3]]),
24        )
25    };
26
27    // a guarantee given by the impulse tracker "specs"
28    if length >= 64_000 {
29        return Err(err::LoadErr::Invalid);
30    }
31
32    if !(32..=200).contains(&num_rows) {
33        return Err(err::LoadErr::Invalid);
34    }
35
36    let mut pattern = Pattern::new(num_rows);
37
38    let mut row_num: u16 = 0;
39
40    let mut last_mask = [0; 64];
41    let mut last_event = [NoteEvent::default(); 64];
42
43    let mut scratch = [0; 1];
44
45    while row_num < num_rows && reader.stream_position()? - read_start < length {
46        let channel_variable = scratch[0];
47
48        if channel_variable == 0 {
49            row_num += 1;
50            continue;
51        }
52
53        let channel = (channel_variable - 1) & 63; // 64 channels, 0 based
54        let channel_id = usize::from(channel);
55
56        let maskvar = if (channel_variable & 0b10000000) != 0 {
57            reader.read_exact(&mut scratch)?;
58            let val = scratch[0];
59            last_mask[channel_id] = val;
60            val
61        } else {
62            last_mask[channel_id]
63        };
64
65        let mut event = NoteEvent::default();
66
67        // Note
68        if (maskvar & 0b00000001) != 0 {
69            reader.read_exact(&mut scratch)?;
70            let note = match Note::new(scratch[0]) {
71                Ok(n) => n,
72                Err(_) => {
73                    defect_handler(LoadDefect::OutOfBoundsValue);
74                    Note::default()
75                }
76            };
77
78            event.note = note;
79            last_event[channel_id].note = note;
80        }
81
82        // Instrument / Sample
83        if (maskvar & 0b00000010) != 0 {
84            reader.read_exact(&mut scratch)?;
85            let instrument = scratch[0];
86
87            event.sample_instr = instrument;
88            last_event[channel_id].sample_instr = instrument;
89        }
90
91        // Volume
92        if (maskvar & 0b00000100) != 0 {
93            reader.read_exact(&mut scratch)?;
94            let vol_pan_raw = scratch[0];
95            let vol_pan = match vol_pan_raw.try_into() {
96                Ok(v) => v,
97                Err(_) => {
98                    defect_handler(LoadDefect::OutOfBoundsValue);
99                    VolumeEffect::default()
100                }
101            };
102
103            last_event[channel_id].vol = vol_pan;
104            event.vol = vol_pan;
105        }
106
107        // Effect
108        if (maskvar & 0b00001000) != 0 {
109            reader.read_exact(&mut scratch)?;
110            let command = scratch[0];
111            reader.read_exact(&mut scratch)?;
112            let cmd_val = scratch[0];
113
114            let cmd = match NoteCommand::try_from((command, cmd_val)) {
115                Ok(cmd) => cmd,
116                Err(_) => {
117                    defect_handler(LoadDefect::OutOfBoundsValue);
118                    NoteCommand::default()
119                }
120            };
121
122            last_event[channel_id].command = cmd;
123            event.command = cmd;
124        }
125
126        // Same note
127        if (maskvar & 0b00010000) != 0 {
128            event.note = last_event[channel_id].note;
129        }
130
131        // Same Instr / Sample
132        if (maskvar & 0b00100000) != 0 {
133            event.sample_instr = last_event[channel_id].sample_instr;
134        }
135
136        // Same volume
137        if (maskvar & 0b01000000) != 0 {
138            event.vol = last_event[channel_id].vol;
139        }
140
141        // Same Command
142        if (maskvar & 0b10000000) != 0 {
143            event.command = last_event[channel_id].command;
144        }
145
146        pattern.set_event(
147            InPatternPosition {
148                row: row_num,
149                channel,
150            },
151            event,
152        );
153    }
154
155    if pattern.row_count() == row_num {
156        Ok(pattern)
157    } else {
158        Err(err::LoadErr::BufferTooShort)
159    }
160}