m8_file_parser/instruments/
fmsynth.rs

1use crate::instruments::common::*;
2use crate::reader::*;
3use crate::version::*;
4use crate::writer::Writer;
5use crate::SEND_COMMAND_NAMES;
6use crate::SEND_COMMAND_NAMES_6_2;
7use array_concat::concat_arrays;
8use num_enum::IntoPrimitive;
9use num_enum::TryFromPrimitive;
10
11use arr_macro::arr;
12
13use super::dests;
14use super::CommandPack;
15
16#[derive(Copy, Clone, PartialEq, Debug)]
17pub struct FmAlgo(pub u8);
18
19const FM_ALGO_STRINGS: [&str; 0x0C] = [
20    "A>B>C>D",
21    "[A+B]>C>D",
22    "[A>B+C]>D",
23    "[A>B+A>C]>D",
24    "[A+B+C]>D",
25    "[A>B>C]+D",
26    "[A>B>C]+[A>B>D]",
27    "[A>B]+[C>D]",
28    "[A>B]+[A>C]+[A>D]",
29    "[A>B]+[A>C]+D",
30    "[A>B]+C+D",
31    "A+B+C+D",
32];
33
34impl TryFrom<u8> for FmAlgo {
35    type Error = ParseError;
36
37    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
38        if (value as usize) < FM_ALGO_STRINGS.len() {
39            Ok(FmAlgo(value))
40        } else {
41            Err(ParseError(format!("Invalid fm algo {}", value)))
42        }
43    }
44}
45
46impl FmAlgo {
47    pub fn id(self) -> u8 {
48        let FmAlgo(v) = self;
49        v
50    }
51
52    pub fn str(self) -> &'static str {
53        FM_ALGO_STRINGS[self.id() as usize]
54    }
55}
56
57#[repr(u8)]
58#[allow(non_camel_case_types)]
59#[derive(IntoPrimitive, TryFromPrimitive, PartialEq, Copy, Clone, Default, Debug)]
60pub enum FMWave {
61    #[default]
62    SIN,
63    SW2,
64    SW3,
65    SW4,
66    SW5,
67    SW6,
68    TRI,
69    SAW,
70    SQR,
71    PUL,
72    IMP,
73    NOI,
74    NLP,
75    NHP,
76    NBP,
77    CLK,
78    // v4.1 addition
79    W09,
80    W0A,
81    W0B,
82    W0C,
83    W0D,
84    W0E,
85    W0F,
86    W10,
87    W11,
88    W12,
89    W13,
90    W14,
91    W15,
92    W16,
93    W17,
94    W18,
95    W19,
96    W1A,
97    W1B,
98    W1C,
99    W1D,
100    W1E,
101    W1F,
102    W20,
103    W21,
104    W22,
105    W23,
106    W24,
107    W25,
108    W26,
109    W27,
110    W28,
111    W29,
112    W2A,
113    W2B,
114    W2C,
115    W2D,
116    W2E,
117    W2F,
118    W30,
119    W31,
120    W32,
121    W33,
122    W34,
123    W35,
124    W36,
125    W37,
126    W38,
127    W39,
128    W3A,
129    W3B,
130    W3C,
131    W3D,
132    W3E,
133    W3F,
134    W40,
135    W41,
136    W42,
137    W43,
138    W44,
139    W45,
140}
141
142#[rustfmt::skip] // Keep constants with important order vertical for maintenance
143const FM_FX_BASE_COMMANDS : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT - 3] = [
144    "VOL",
145    "PIT",
146    "FIN",
147    "ALG",
148    "FM1",
149    "FM2",
150    "FM3",
151    "FM4",
152    "FLT",
153    "CUT",
154    "RES",
155    "AMP",
156    "LIM",
157    "PAN",
158    "DRY",
159];
160
161#[rustfmt::skip] // Keep constants with important order vertical for maintenance
162const FM_FX_COMMANDS_UPTO_5 : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 1] =
163    concat_arrays!(FM_FX_BASE_COMMANDS, SEND_COMMAND_NAMES, ["FMP"]);
164
165#[rustfmt::skip] // Keep constants with important order vertical for maintenance
166const FM_FX_COMMANDS_FROM_6 : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 2] =
167    concat_arrays!(FM_FX_BASE_COMMANDS, SEND_COMMAND_NAMES, ["SNC", "ERR"]);
168
169#[rustfmt::skip] // Keep constants with important order vertical for maintenance
170const FM_FX_COMMANDS_FROM_6_2 : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 2] =
171    concat_arrays!(FM_FX_BASE_COMMANDS, SEND_COMMAND_NAMES_6_2, ["SNC", "ERR"]);
172
173#[rustfmt::skip] // Keep constants with important order vertical for maintenance
174const DESTINATIONS : [&'static str; 15] = [
175    dests::OFF,
176    dests::VOLUME,
177    dests::PITCH,
178
179    "MOD1",
180    "MOD2",
181    "MOD3",
182    "MOD4",
183    dests::CUTOFF,
184    dests::RES,
185    dests::AMP,
186    dests::PAN,
187    dests::MOD_AMT,
188    dests::MOD_RATE,
189    dests::MOD_BOTH,
190    dests::MOD_BINV,
191];
192
193#[derive(PartialEq, Debug, Default, Clone)]
194pub struct Operator {
195    pub shape: FMWave,
196    pub ratio: u8,
197    pub ratio_fine: u8,
198    pub level: u8,
199    pub feedback: u8,
200    pub retrigger: u8,
201    pub mod_a: u8,
202    pub mod_b: u8,
203}
204
205#[derive(PartialEq, Debug, Clone)]
206pub struct FMSynth {
207    pub number: u8,
208    pub name: String,
209    pub transpose: bool,
210    pub table_tick: u8,
211    pub synth_params: SynthParams,
212
213    pub algo: FmAlgo,
214    pub operators: [Operator; 4],
215    pub mod1: u8,
216    pub mod2: u8,
217    pub mod3: u8,
218    pub mod4: u8,
219}
220
221impl FMSynth {
222    const MOD_OFFSET: usize = 2;
223
224    pub fn command_name(&self, ver: Version) -> &'static [&'static str] {
225        if ver.at_least(6, 1) {
226            &FM_FX_COMMANDS_FROM_6_2
227        } else if ver.at_least(6, 0) {
228            &FM_FX_COMMANDS_FROM_6
229        } else {
230            &FM_FX_COMMANDS_UPTO_5
231        }
232    }
233
234    pub fn destination_names(&self, _ver: Version) -> &'static [&'static str] {
235        &DESTINATIONS
236    }
237
238    /// List of all the applyable filter types for the instrument
239    pub fn filter_types(&self, _ver: Version) -> &'static [&'static str] {
240        &COMMON_FILTER_TYPES
241    }
242
243    pub fn human_readable_filter(&self) -> &'static str {
244        COMMON_FILTER_TYPES[self.synth_params.filter_type as usize]
245    }
246
247    pub fn write(&self, ver: Version, w: &mut Writer) {
248        w.write_string(&self.name, 12);
249        w.write(TranspEq::from(ver, self.transpose, self.synth_params.associated_eq).into());
250        w.write(self.table_tick);
251        w.write(self.synth_params.volume);
252        w.write(self.synth_params.pitch);
253        w.write(self.synth_params.fine_tune);
254
255        w.write(self.algo.0);
256
257        for op in &self.operators {
258            w.write(op.shape.into());
259        }
260
261        for op in &self.operators {
262            w.write(op.ratio);
263            w.write(op.ratio_fine);
264        }
265
266        for op in &self.operators {
267            w.write(op.level);
268            w.write(op.feedback);
269        }
270
271        for op in &self.operators {
272            w.write(op.mod_a);
273        }
274
275        for op in &self.operators {
276            w.write(op.mod_b);
277        }
278
279        w.write(self.mod1);
280        w.write(self.mod2);
281        w.write(self.mod3);
282        w.write(self.mod4);
283
284        self.synth_params.write(ver, w, FMSynth::MOD_OFFSET);
285    }
286
287    pub fn from_reader(
288        ver: Version,
289        reader: &mut Reader,
290        number: u8,
291        version: Version,
292    ) -> M8Result<Self> {
293        let name = reader.read_string(12);
294        let transp_eq = TranspEq::from_version(ver, reader.read());
295        let table_tick = reader.read();
296        let volume = reader.read();
297        let pitch = reader.read();
298        let fine_tune = reader.read();
299
300        let algo = reader.read();
301        let mut operators: [Operator; 4] = arr![Operator::default(); 4];
302        if version.at_least(1, 4) {
303            for i in 0..4 {
304                let wav_code = reader.read();
305                operators[i].shape = FMWave::try_from(wav_code)
306                    .map_err(|_| ParseError(format!("Invalid fm wave {}", wav_code)))?;
307            }
308        }
309        for i in 0..4 {
310            operators[i].ratio = reader.read();
311            operators[i].ratio_fine = reader.read();
312        }
313        for i in 0..4 {
314            operators[i].level = reader.read();
315            operators[i].feedback = reader.read();
316        }
317        for i in 0..4 {
318            operators[i].mod_a = reader.read();
319        }
320        for i in 0..4 {
321            operators[i].mod_b = reader.read();
322        }
323        let mod1 = reader.read();
324        let mod2 = reader.read();
325        let mod3 = reader.read();
326        let mod4 = reader.read();
327
328        let synth_params = if version.at_least(3, 0) {
329            SynthParams::from_reader3(
330                ver,
331                reader,
332                volume,
333                pitch,
334                fine_tune,
335                transp_eq.eq,
336                FMSynth::MOD_OFFSET,
337            )?
338        } else {
339            SynthParams::from_reader2(reader, volume, pitch, fine_tune)?
340        };
341
342        Ok(FMSynth {
343            number,
344            name,
345            transpose: transp_eq.transpose,
346            table_tick,
347            synth_params,
348
349            algo: FmAlgo(algo),
350            operators,
351            mod1,
352            mod2,
353            mod3,
354            mod4,
355        })
356    }
357}