torque_tracker_engine/file/impulse_format/
instrument.rs

1use std::array;
2
3use crate::file::err;
4use crate::file::err::LoadDefect;
5
6#[derive(Debug, Default)]
7pub enum NewNoteAction {
8    #[default]
9    Cut = 0,
10    Continue = 1,
11    NoteOff = 2,
12    NoteFade = 3,
13}
14
15impl TryFrom<u8> for NewNoteAction {
16    type Error = u8;
17
18    fn try_from(value: u8) -> Result<Self, Self::Error> {
19        match value {
20            0 => Ok(Self::Cut),
21            1 => Ok(Self::Continue),
22            2 => Ok(Self::NoteOff),
23            3 => Ok(Self::NoteFade),
24            _ => Err(value),
25        }
26    }
27}
28
29#[derive(Debug, Default)]
30pub enum DuplicateCheckType {
31    #[default]
32    Off = 0,
33    Note = 1,
34    Sample = 2,
35    Instrument = 3,
36}
37
38impl TryFrom<u8> for DuplicateCheckType {
39    type Error = ();
40
41    fn try_from(value: u8) -> Result<Self, Self::Error> {
42        match value {
43            0 => Ok(Self::Off),
44            1 => Ok(Self::Note),
45            2 => Ok(Self::Sample),
46            3 => Ok(Self::Instrument),
47            _ => Err(()),
48        }
49    }
50}
51
52#[derive(Debug, Default)]
53pub enum DuplicateCheckAction {
54    #[default]
55    Cut = 0,
56    NoteOff = 1,
57    NoteFade = 2,
58}
59
60impl TryFrom<u8> for DuplicateCheckAction {
61    type Error = ();
62
63    fn try_from(value: u8) -> Result<Self, Self::Error> {
64        match value {
65            0 => Ok(Self::Cut),
66            1 => Ok(Self::NoteOff),
67            2 => Ok(Self::NoteFade),
68            _ => Err(()),
69        }
70    }
71}
72
73#[derive(Debug)]
74pub struct ImpulseInstrument {
75    pub dos_file_name: [u8; 12],
76    pub new_note_action: NewNoteAction,
77    pub duplicate_check_type: DuplicateCheckType,
78    pub duplicate_check_action: DuplicateCheckAction,
79    pub fade_out: u16,
80    pub pitch_pan_seperation: i8,
81    pub pitch_pan_center: u8,
82    pub global_volume: u8,
83    pub default_pan: Option<u8>,
84    pub random_volume: u8,
85    pub random_pan: u8,
86    pub created_with: u16,
87    pub number_of_samples: u8,
88    pub name: String,
89    pub initial_filter_cutoff: u8,
90    pub initial_filter_resonance: u8,
91    pub midi_channel: u8,
92    pub midi_program: u8,
93    pub midi_bank: u16,
94    pub note_sample_table: [(u8, u8); 120],
95    pub volume_envelope: ImpulseEnvelope,
96    pub pan_envelope: ImpulseEnvelope,
97    pub pitch_envelope: ImpulseEnvelope,
98}
99
100impl ImpulseInstrument {
101    const SIZE: usize = 554;
102
103    pub fn parse<H: FnMut(LoadDefect)>(
104        buf: &[u8; Self::SIZE],
105        defect_handler: &mut H,
106    ) -> Result<Self, err::LoadErr> {
107        if !buf.starts_with(b"IMPI") {
108            return Err(err::LoadErr::Invalid);
109        }
110
111        // unwrap is okay as the slice length is const
112        let dos_file_name: [u8; 12] = buf[0x04..=0x0F].try_into().unwrap();
113
114        if buf[10] != 0 {
115            return Err(err::LoadErr::Invalid);
116        }
117        let new_note_action = match NewNoteAction::try_from(buf[0x11]) {
118            Ok(nna) => nna,
119            Err(_) => {
120                defect_handler(LoadDefect::OutOfBoundsValue);
121                NewNoteAction::default()
122            }
123        };
124        let duplicate_check_type = match DuplicateCheckType::try_from(buf[0x12]) {
125            Ok(dct) => dct,
126            Err(_) => {
127                defect_handler(LoadDefect::OutOfBoundsValue);
128                DuplicateCheckType::default()
129            }
130        };
131        let duplicate_check_action = match DuplicateCheckAction::try_from(buf[0x13]) {
132            Ok(dca) => dca,
133            Err(_) => {
134                defect_handler(LoadDefect::OutOfBoundsValue);
135                DuplicateCheckAction::default()
136            }
137        };
138        let fade_out = u16::from_le_bytes([buf[0x14], buf[0x15]]);
139        let pitch_pan_seperation = {
140            let tmp = i8::from_le_bytes([buf[0x16]]);
141            if !(-32..=32).contains(&tmp) {
142                defect_handler(LoadDefect::OutOfBoundsValue);
143                0
144            } else {
145                tmp
146            }
147        };
148        let pitch_pan_center = if buf[0x17] <= 119 {
149            buf[0x17]
150        } else {
151            defect_handler(LoadDefect::OutOfBoundsValue);
152            59
153        };
154        let global_volume = if buf[0x18] <= 128 {
155            buf[0x18]
156        } else {
157            defect_handler(LoadDefect::OutOfBoundsValue);
158            64
159        };
160
161        let default_pan = if buf[0x19] == 128 {
162            None
163        } else if buf[0x19] > 64 {
164            defect_handler(LoadDefect::OutOfBoundsValue);
165            Some(32)
166        } else {
167            Some(buf[0x19])
168        };
169
170        let random_volume = buf[0x1A];
171        assert!(random_volume <= 100);
172        let random_pan = buf[0x1B];
173        assert!(random_pan <= 100);
174        let created_with = u16::from_le_bytes([buf[0x1C], buf[0x1D]]);
175        let number_of_samples = buf[0x1E];
176
177        let name = String::from_utf8(
178            buf[0x20..=0x39]
179                .split(|b| *b == 0)
180                .next()
181                .unwrap()
182                .to_owned(),
183        )
184        .unwrap();
185
186        let initial_filter_cutoff = buf[0x3A];
187        let initial_filter_resonance = buf[0x3B];
188        let midi_channel = buf[0x3C];
189        let midi_program = buf[0x3D];
190        let midi_bank = u16::from_le_bytes([buf[0x3E], buf[0x3F]]);
191        let note_sample_table: [(u8, u8); 120] = buf[0x030..0x130]
192            .chunks_exact(2)
193            .map(|chunk| (chunk[0], chunk[1]))
194            .collect::<Vec<(u8, u8)>>()
195            .try_into()
196            .unwrap();
197
198        let volume_envelope = ImpulseEnvelope::load(
199            &buf[0x130..0x130 + ImpulseEnvelope::SIZE]
200                .try_into()
201                .unwrap(),
202        );
203        let pan_envelope = ImpulseEnvelope::load(
204            &buf[0x182..0x182 + ImpulseEnvelope::SIZE]
205                .try_into()
206                .unwrap(),
207        );
208        let pitch_envelope = ImpulseEnvelope::load(
209            &buf[0x1D4..0x1D4 + ImpulseEnvelope::SIZE]
210                .try_into()
211                .unwrap(),
212        );
213
214        Ok(Self {
215            dos_file_name,
216            new_note_action,
217            duplicate_check_type,
218            duplicate_check_action,
219            fade_out,
220            pitch_pan_seperation,
221            pitch_pan_center,
222            global_volume,
223            default_pan,
224            random_volume,
225            random_pan,
226            created_with,
227            number_of_samples,
228            name,
229            initial_filter_cutoff,
230            initial_filter_resonance,
231            midi_channel,
232            midi_program,
233            midi_bank,
234            note_sample_table,
235            volume_envelope,
236            pan_envelope,
237            pitch_envelope,
238        })
239    }
240}
241
242/// flags and node values are interpreted differently depending on the type of envelope.
243/// doesn't affect loading
244#[derive(Debug)]
245pub struct ImpulseEnvelope {
246    flags: u8,
247    num_node_points: u8,
248    loop_start: u8,
249    loop_end: u8,
250    sustain_loop_start: u8,
251    sustain_loop_end: u8,
252    nodes: [(u8, u16); 25],
253}
254
255impl ImpulseEnvelope {
256    const SIZE: usize = 81; // = 0x51
257
258    fn load(buf: &[u8; Self::SIZE]) -> Self {
259        let flags = buf[0];
260        let num_node_points = buf[1];
261        let loop_start = buf[2];
262        let loop_end = buf[3];
263        let sustain_loop_start = buf[4];
264        let sustain_loop_end = buf[5];
265
266        let nodes = array::from_fn(|idx| {
267            let chunk = 6 + idx * 3;
268            (
269                buf[chunk],
270                u16::from_le_bytes([buf[chunk + 1], buf[chunk + 2]]),
271            )
272        });
273
274        Self {
275            flags,
276            num_node_points,
277            loop_start,
278            loop_end,
279            sustain_loop_start,
280            sustain_loop_end,
281            nodes,
282        }
283    }
284}