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