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