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 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] const 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] const 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] const 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] const 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] const 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 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}