m8_files/instruments/
fmsynth.rs

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