m8_file_parser/instruments/
macrosynth.rs

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 super::dests;
12use super::CommandPack;
13
14/// Macro synth oscilator modes.
15#[repr(u8)]
16#[allow(non_camel_case_types)]
17#[derive(IntoPrimitive, TryFromPrimitive, PartialEq, Copy, Clone, Default, Debug)]
18pub enum MacroSynthOsc {
19    #[default]
20    CSAW,
21    MORPH,
22    SAW_SQUARE,
23    SINE_TRIANGLE,
24    BUZZ,
25    SQUARE_SUB,
26    SAW_SUB,
27    SQUARE_SYNC,
28    SAW_SYNC,
29    TRIPLE_SAW,
30    TRIPLE_SQUARE,
31    TRIPLE_TRIANGLE,
32    TRIPLE_SIN,
33    TRIPLE_RNG,
34    SAW_SWARM,
35    SAW_COMB,
36    TOY,
37    DIGITAL_FILTER_LP,
38    DIGITAL_FILTER_PK,
39    DIGITAL_FILTER_BP,
40    DIGITAL_FILTER_HP,
41    VOSIM,
42    VOWEL,
43    VOWEL_FOF,
44    HARMONICS,
45    FM,
46    FEEDBACK_FM,
47    CHAOTIC_FEEDBACK_FM,
48    PLUCKED,
49    BOWED,
50    BLOWN,
51    FLUTED,
52    STRUCK_BELL,
53    STRUCK_DRUM,
54    KICK,
55    CYMBAL,
56    SNARE,
57    WAVETABLES,
58    WAVE_MAP,
59    WAV_LINE,
60    WAV_PARAPHONIC,
61    FILTERED_NOISE,
62    TWIN_PEAKS_NOISE,
63    CLOCKED_NOISE,
64    GRANULAR_CLOUD,
65    PARTICLE_NOISE,
66    DIGITAL_MOD,
67    MORSE_NOISE,
68}
69
70#[rustfmt::skip] // Keep constants with important order vertical for maintenance
71const MACRO_SYNTH_COMMANDS_BASE : [&'static str;  CommandPack::BASE_INSTRUMENT_COMMAND_COUNT - 3] = [
72    "VOL",
73    "PIT",
74    "FIN",
75    "OSC",
76    "TBR",
77    "COL",
78    "DEG",
79    "RED",
80    "FIL",
81    "CUT",
82    "RES",
83    "AMP",
84    "LIM",
85    "PAN",
86    "DRY"
87];
88
89#[rustfmt::skip] // Keep constants with important order vertical for maintenance
90const MACRO_SYNTH_COMMANDS_EXTRA : [&'static str; 2] = [
91    "TRG",
92    "ERR"
93];
94
95const MACRO_SYNTH_COMMANDS : [&'static str;  CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 2] =
96    concat_arrays!(MACRO_SYNTH_COMMANDS_BASE, SEND_COMMAND_NAMES, MACRO_SYNTH_COMMANDS_EXTRA);
97
98const MACRO_SYNTH_COMMANDS_6_2 : [&'static str;  CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 2] =
99    concat_arrays!(MACRO_SYNTH_COMMANDS_BASE, SEND_COMMAND_NAMES_6_2, MACRO_SYNTH_COMMANDS_EXTRA);
100
101#[rustfmt::skip] // Keep constants with important order vertical for maintenance
102const DESTINATIONS : [&'static str; 15] = [
103    dests::OFF,
104    dests::VOLUME,
105    dests::PITCH,
106
107    "TIMBRE",
108    "COLOR",
109    dests::DEGRADE,
110    "REDUX",
111    dests::CUTOFF,
112    dests::RES,
113    dests::AMP,
114    dests::PAN,
115    dests::MOD_AMT,
116    dests::MOD_RATE,
117    dests::MOD_BOTH,
118    dests::MOD_BINV,
119];
120
121#[derive(PartialEq, Debug, Clone)]
122pub struct MacroSynth {
123    pub number: u8,
124    pub name: String,              // 12
125    pub transpose: bool,           // 1
126    pub table_tick: u8,            // 1
127    pub synth_params: SynthParams, // 10
128
129    pub shape: MacroSynthOsc, // 1
130    pub timbre: u8,           // 1
131    pub color: u8,            // 1
132    pub degrade: u8,          // 1
133    pub redux: u8,            // 1
134}
135
136impl MacroSynth {
137    pub const MOD_OFFSET: usize = 30;
138
139    pub fn command_name(&self, ver: Version) -> &'static [&'static str] {
140        if ver.at_least(6, 1) {
141            &MACRO_SYNTH_COMMANDS_6_2 
142        } else {
143            &MACRO_SYNTH_COMMANDS
144        }
145    }
146
147    pub fn destination_names(&self, _ver: Version) -> &'static [&'static str] {
148        &DESTINATIONS
149    }
150
151    /// List of all the applyable filter types for the instrument
152    pub fn filter_types(&self, _ver: Version) -> &'static [&'static str] {
153        &super::common::COMMON_FILTER_TYPES
154    }
155
156    pub fn human_readable_filter(&self) -> &'static str {
157        COMMON_FILTER_TYPES[self.synth_params.filter_type as usize]
158    }
159
160    pub fn write(&self, ver: Version, w: &mut Writer) {
161        w.write_string(&self.name, 12);
162        w.write(TranspEq::from(ver, self.transpose, self.synth_params.associated_eq).into());
163        w.write(self.table_tick);
164        w.write(self.synth_params.volume);
165        w.write(self.synth_params.pitch);
166        w.write(self.synth_params.fine_tune);
167
168        w.write(self.shape.into());
169        w.write(self.timbre);
170        w.write(self.color);
171        w.write(self.degrade);
172        w.write(self.redux);
173
174        self.synth_params.write(ver, w, MacroSynth::MOD_OFFSET);
175    }
176
177    pub fn from_reader(
178        ver: Version,
179        reader: &mut Reader,
180        number: u8,
181        version: Version,
182    ) -> M8Result<Self> {
183        let ms_pos = reader.pos();
184        let name = reader.read_string(12);
185
186        let transp_eq = TranspEq::from_version(ver, reader.read());
187        let table_tick = reader.read();
188        let volume = reader.read();
189        let pitch = reader.read();
190        let fine_tune = reader.read();
191
192        let ofs_shape = reader.pos();
193        let shape = reader.read();
194        let timbre = reader.read();
195        let color = reader.read();
196        let degrade = reader.read();
197        let redux = reader.read();
198
199        let synth_params = if version.at_least(3, 0) {
200            SynthParams::from_reader3(
201                ver,
202                reader,
203                volume,
204                pitch,
205                fine_tune,
206                transp_eq.eq,
207                MacroSynth::MOD_OFFSET,
208            )?
209        } else {
210            SynthParams::from_reader2(reader, volume, pitch, fine_tune)?
211        };
212
213        let nc = name.clone();
214        Ok(MacroSynth {
215            number,
216            name,
217            transpose: transp_eq.transpose,
218            table_tick,
219            synth_params,
220
221            shape: shape.try_into().map_err(|_| {
222                ParseError(format!(
223                    "I{number:X} Wrong macrosynth@{ms_pos} ({nc}) shape {shape}@0x{ofs_shape}"
224                ))
225            })?,
226            timbre,
227            color,
228            degrade,
229            redux,
230        })
231    }
232}