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