m8_file_parser/instruments/
fmsynth.rs

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