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(pub 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 constants 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 constants 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    /// List of all the applyable filter types for the instrument
224    pub fn filter_types(&self, _ver: Version) -> &'static [&'static str] {
225        &COMMON_FILTER_TYPES
226    }
227
228    pub fn human_readable_filter(&self) -> &'static str {
229        COMMON_FILTER_TYPES[self.synth_params.filter_type as usize]
230    }
231
232    pub fn write(&self, ver: Version, w: &mut Writer) {
233        w.write_string(&self.name, 12);
234        w.write(TranspEq::from(ver, self.transpose, self.synth_params.associated_eq).into());
235        w.write(self.table_tick);
236        w.write(self.synth_params.volume);
237        w.write(self.synth_params.pitch);
238        w.write(self.synth_params.fine_tune);
239
240        w.write(self.algo.0);
241
242        for op in &self.operators {
243            w.write(op.shape.into());
244        }
245
246        for op in &self.operators {
247            w.write(op.ratio);
248            w.write(op.ratio_fine);
249        }
250
251        for op in &self.operators {
252            w.write(op.level);
253            w.write(op.feedback);
254        }
255
256        for op in &self.operators {
257            w.write(op.mod_a);
258        }
259
260        for op in &self.operators {
261            w.write(op.mod_b);
262        }
263
264        w.write(self.mod1);
265        w.write(self.mod2);
266        w.write(self.mod3);
267        w.write(self.mod4);
268
269        self.synth_params.write(ver, w, FMSynth::MOD_OFFSET);
270    }
271
272    pub fn from_reader(
273        ver: Version,
274        reader: &mut Reader,
275        number: u8,
276        version: Version,
277    ) -> M8Result<Self> {
278        let name = reader.read_string(12);
279        let transp_eq = TranspEq::from_version(ver, reader.read());
280        let table_tick = reader.read();
281        let volume = reader.read();
282        let pitch = reader.read();
283        let fine_tune = reader.read();
284
285        let algo = reader.read();
286        let mut operators: [Operator; 4] = arr![Operator::default(); 4];
287        if version.at_least(1, 4) {
288            for i in 0..4 {
289                let wav_code = reader.read();
290                operators[i].shape = FMWave::try_from(wav_code)
291                    .map_err(|_| ParseError(format!("Invalid fm wave {}", wav_code)))?;
292            }
293        }
294        for i in 0..4 {
295            operators[i].ratio = reader.read();
296            operators[i].ratio_fine = reader.read();
297        }
298        for i in 0..4 {
299            operators[i].level = reader.read();
300            operators[i].feedback = reader.read();
301        }
302        for i in 0..4 {
303            operators[i].mod_a = reader.read();
304        }
305        for i in 0..4 {
306            operators[i].mod_b = reader.read();
307        }
308        let mod1 = reader.read();
309        let mod2 = reader.read();
310        let mod3 = reader.read();
311        let mod4 = reader.read();
312
313        let synth_params = if version.at_least(3, 0) {
314            SynthParams::from_reader3(
315                ver,
316                reader,
317                volume,
318                pitch,
319                fine_tune,
320                transp_eq.eq,
321                FMSynth::MOD_OFFSET,
322            )?
323        } else {
324            SynthParams::from_reader2(reader, volume, pitch, fine_tune)?
325        };
326
327        Ok(FMSynth {
328            number,
329            name,
330            transpose: transp_eq.transpose,
331            table_tick,
332            synth_params,
333
334            algo: FmAlgo(algo),
335            operators,
336            mod1,
337            mod2,
338            mod3,
339            mod4,
340        })
341    }
342}