m8_file_parser/instruments/
mod.rs

1use crate::eq::Equ;
2use crate::reader::*;
3use crate::version::*;
4use crate::writer::Writer;
5use crate::ReferenceTemplating;
6use crate::Table;
7use crate::TableView;
8use crate::V4_1_OFFSETS;
9
10mod common;
11mod external_inst;
12mod fmsynth;
13mod hypersynth;
14mod macrosynth;
15mod midi;
16mod modulator;
17mod sampler;
18mod wavsynth;
19
20pub use common::{LimitType, SynthParams};
21
22pub use external_inst::*;
23pub use fmsynth::*;
24pub use hypersynth::*;
25pub use macrosynth::*;
26pub use midi::*;
27pub use modulator::*;
28pub use sampler::*;
29pub use wavsynth::*;
30
31#[derive(PartialEq, Debug, Clone, Default)]
32pub enum Instrument {
33    WavSynth(WavSynth),
34    MacroSynth(MacroSynth),
35    Sampler(Sampler),
36    MIDIOut(MIDIOut),
37    FMSynth(FMSynth),
38    HyperSynth(HyperSynth),
39    External(ExternalInst),
40    #[default]
41    None,
42}
43
44/// Various constants for common parameters, to avoid nasty typos everywhere
45pub mod params {
46    pub const NAME: &'static str = "NAME";
47    pub const TRANSPOSE: &'static str = "TRANSPOSE";
48    pub const TBLTIC: &'static str = "TBL. TIC";
49    pub const EQ: &'static str = "EQ";
50    pub const SCALE: &'static str = "SCALE";
51
52    pub const CCA: &'static str = "CCA";
53    pub const CCB: &'static str = "CCB";
54    pub const CCC: &'static str = "CCC";
55    pub const CCD: &'static str = "CCD";
56
57    pub const DEST: &'static str = "DEST";
58    pub const AMOUNT: &'static str = "AMT";
59    pub const ATTACK: &'static str = "ATK";
60    pub const DECAY: &'static str = "DEC";
61    pub const HOLD: &'static str = "HOLD";
62    pub const SUSTAIN: &'static str = "SUS";
63    pub const RELEASE: &'static str = "REL";
64    pub const PEAK: &'static str = "PEAK";
65    pub const BODY: &'static str = "BODY";
66    pub const FREQ: &'static str = "FREQ";
67    pub const TRIGGER: &'static str = "TRIG";
68    pub const LFOSHAPE: &'static str = "OSC";
69    pub const SOURCE: &'static str = "SRC";
70}
71
72/// Various constants for modulation destinations, to avoid nasty typos everywhere
73pub mod dests {
74    pub const OFF: &'static str = "OFF";
75    pub const VOLUME: &'static str = "VOLUME";
76    pub const PITCH: &'static str = "PITCH";
77    pub const CUTOFF: &'static str = "CUTOFF";
78    pub const RES: &'static str = "RES";
79    pub const AMP: &'static str = "AMP";
80    pub const PAN: &'static str = "PAN";
81    pub const DEGRADE: &'static str = "DEGRADE";
82    pub const MOD_AMT: &'static str = "MOD AMT";
83    pub const MOD_RATE: &'static str = "MOD RATE";
84    pub const MOD_BOTH: &'static str = "MOD BOTH";
85    pub const MOD_BINV: &'static str = "MOD BINV";
86}
87
88#[rustfmt::skip] // Keep constants with important order vertical for maintenance
89pub(crate) const SEND_COMMAND_NAMES : [&'static str; 3] = [
90    "SCH",
91    "SDL",
92    "SRV"
93];
94
95#[rustfmt::skip] // Keep constants with important order vertical for maintenance
96pub(crate) const SEND_COMMAND_NAMES_6_2 : [&'static str; 3] = [
97    "SMX",
98    "SDL",
99    "SRV"
100];
101
102/// This structure will aggregate for every instrument and its
103/// modulator the name of the commands associated to it.
104#[derive(Clone, Copy)]
105pub struct CommandPack {
106    /// Instruments command
107    pub instr: &'static [&'static str],
108
109    /// For all the modulators, their respective
110    /// command names
111    pub mod_commands: [&'static [&'static str]; SynthParams::MODULATOR_COUNT],
112}
113
114impl Default for CommandPack {
115    fn default() -> Self {
116        Self {
117            instr: Default::default(),
118            mod_commands: Default::default(),
119        }
120    }
121}
122
123impl CommandPack {
124    /// Instrument specific command start at 0x80
125    pub const INSTRUMENT_COMMAND_OFFSET: usize = 0x80;
126
127    /// If we are below INSTRUMENT_COMMAND_OFFSET + this number, we will access to
128    /// CommandPack::instr array, for instrument specific command.
129    pub const BASE_INSTRUMENT_COMMAND_COUNT: usize = 18;
130
131    /// Last base instrument command index.
132    pub const BASE_INSTRUMENT_COMMAND_END: usize = CommandPack::INSTRUMENT_COMMAND_OFFSET
133        + Mod::COMMAND_PER_MOD * SynthParams::MODULATOR_COUNT;
134
135    /// Does this command pack can render properly a given command.
136    pub fn accepts(self, cmd: u8) -> bool {
137        let cmd = cmd as usize;
138        CommandPack::INSTRUMENT_COMMAND_OFFSET <= cmd
139            && cmd <= (CommandPack::BASE_INSTRUMENT_COMMAND_END + self.instr.len())
140    }
141
142    pub fn try_render(self, cmd: u8) -> Option<&'static str> {
143        if self.instr.len() == 0 {
144            return None;
145        }
146        if (cmd as usize) < CommandPack::INSTRUMENT_COMMAND_OFFSET {
147            return None;
148        }
149
150        let cmd = cmd as usize - CommandPack::INSTRUMENT_COMMAND_OFFSET;
151
152        if cmd < CommandPack::BASE_INSTRUMENT_COMMAND_COUNT {
153            if cmd < self.instr.len() {
154                return Some(self.instr[cmd]);
155            } else {
156                return None;
157            }
158        }
159
160        let mod_cmd = cmd - CommandPack::BASE_INSTRUMENT_COMMAND_COUNT;
161        let mod_ix = mod_cmd / Mod::COMMAND_PER_MOD;
162
163        if mod_ix < self.mod_commands.len() {
164            let ix = mod_cmd - Mod::COMMAND_PER_MOD * mod_ix;
165            return Some(self.mod_commands[mod_ix][ix]);
166        }
167
168        let extra_cmd = cmd - (Mod::COMMAND_PER_MOD * SynthParams::MODULATOR_COUNT);
169        if extra_cmd < self.instr.len() {
170            return Some(self.instr[extra_cmd]);
171        }
172
173        None
174    }
175}
176
177/// Firmware 4.1 introduce files with an instrument definition and an
178/// EQ. This structure represent the result of parsing such insturment
179/// with an optional EQ.
180pub struct InstrumentWithEq {
181    /// The parsed instrument
182    pub instrument: Instrument,
183
184    /// Table associated with the instrument.
185    pub table: Table,
186
187    /// If the instrument was referencing an EQ, the effectively
188    /// parsed EQ.
189    pub eq: Option<Equ>,
190
191    /// Instrument file version
192    pub version: Version
193}
194
195impl InstrumentWithEq {
196    pub fn table_view(&self) -> TableView<'_> {
197        TableView {
198            table: &self.table,
199            table_index: 0,
200            instrument: self.instrument.instr_command_text(self.version),
201            templates: ReferenceTemplating::default()
202        }
203    }
204
205    pub fn write(&self, w: &mut Writer) {
206        self.version.write(w);
207        let start_pos = w.pos();
208        self.instrument.write(self.version, w);
209        w.seek(start_pos + Instrument::INSTRUMENT_MEMORY_SIZE);
210        self.table.write(w);
211
212        if let Some(eq) = &self.eq {
213            eq.write(w);
214        }
215    }
216}
217
218impl Instrument {
219    pub const INSTRUMENT_MEMORY_SIZE: usize = 215;
220    pub const V4_SIZE: usize = Self::INSTRUMENT_MEMORY_SIZE;
221
222    pub fn is_empty(&self) -> bool {
223        match self {
224            Instrument::None => true,
225            _ => false,
226        }
227    }
228
229    pub fn instr_command_text(&self, ver: Version) -> CommandPack {
230        let (commands, mods) = match self {
231            Instrument::WavSynth(ws) => (ws.command_name(ver), &ws.synth_params.mods),
232            Instrument::MacroSynth(ms) => (ms.command_name(ver), &ms.synth_params.mods),
233            Instrument::Sampler(s) => (s.command_name(ver), &s.synth_params.mods),
234            Instrument::MIDIOut(mo) => (mo.command_name(ver), &mo.mods.mods),
235            Instrument::FMSynth(fs) => (fs.command_name(ver), &fs.synth_params.mods),
236            Instrument::HyperSynth(hs) => (hs.command_name(ver), &hs.synth_params.mods),
237            Instrument::External(ex) => (ex.command_name(ver), &ex.synth_params.mods),
238            Instrument::None => return CommandPack::default(),
239        };
240
241        CommandPack {
242            instr: commands,
243            mod_commands: [
244                mods[0].command_name(ver, 0),
245                mods[1].command_name(ver, 1),
246                mods[2].command_name(ver, 2),
247                mods[3].command_name(ver, 3),
248            ],
249        }
250    }
251
252    pub fn write(&self, ver: Version, w: &mut Writer) {
253        match self {
254            Instrument::WavSynth(ws) => {
255                w.write(0);
256                ws.write(ver, w);
257            }
258            Instrument::MacroSynth(ms) => {
259                w.write(1);
260                ms.write(ver, w);
261            }
262            Instrument::Sampler(s) => {
263                w.write(2);
264                s.write(ver, w);
265            }
266            Instrument::MIDIOut(mo) => {
267                w.write(3);
268                mo.write(ver, w);
269            }
270            Instrument::FMSynth(fs) => {
271                w.write(4);
272                fs.write(ver, w);
273            }
274            Instrument::HyperSynth(hs) => {
275                w.write(5);
276                hs.write(ver, w);
277            }
278            Instrument::External(ex) => {
279                w.write(6);
280                ex.write(ver, w);
281            }
282            Instrument::None => w.write(0xFF),
283        }
284    }
285
286    pub fn name(&self) -> Option<&str> {
287        match self {
288            Instrument::WavSynth(ws) => Some(&ws.name),
289            Instrument::MacroSynth(ms) => Some(&ms.name),
290            Instrument::Sampler(s) => Some(&s.name),
291            Instrument::MIDIOut(_) => None,
292            Instrument::FMSynth(fs) => Some(&fs.name),
293            Instrument::HyperSynth(hs) => Some(&hs.name),
294            Instrument::External(ex) => Some(&ex.name),
295            Instrument::None => None,
296        }
297    }
298
299    pub fn set_name(&mut self, name: String) {
300        match self {
301            Instrument::WavSynth(ws) => ws.name = name,
302            Instrument::MacroSynth(ms) => ms.name = name,
303            Instrument::Sampler(s) => s.name = name,
304            Instrument::MIDIOut(mo) => mo.name = name,
305            Instrument::FMSynth(fs) => fs.name = name,
306            Instrument::HyperSynth(hs) => hs.name = name,
307            Instrument::External(ex) => ex.name = name,
308            Instrument::None => {}
309        }
310    }
311
312    pub fn equ(&self) -> Option<u8> {
313        match self {
314            Instrument::WavSynth(ws) => Some(ws.synth_params.associated_eq),
315            Instrument::MacroSynth(ms) => Some(ms.synth_params.associated_eq),
316            Instrument::Sampler(s) => Some(s.synth_params.associated_eq),
317            Instrument::MIDIOut(_) => None,
318            Instrument::FMSynth(fs) => Some(fs.synth_params.associated_eq),
319            Instrument::HyperSynth(hs) => Some(hs.synth_params.associated_eq),
320            Instrument::External(ex) => Some(ex.synth_params.associated_eq),
321            Instrument::None => None,
322        }
323    }
324
325    pub fn set_eq(&mut self, eq_ix: u8) {
326        match self {
327            Instrument::WavSynth(ws) => ws.synth_params.set_eq(eq_ix),
328            Instrument::MacroSynth(ms) => ms.synth_params.set_eq(eq_ix),
329            Instrument::Sampler(s) => s.synth_params.set_eq(eq_ix),
330            Instrument::MIDIOut(_) => {}
331            Instrument::FMSynth(fs) => fs.synth_params.set_eq(eq_ix),
332            Instrument::HyperSynth(hs) => hs.synth_params.set_eq(eq_ix),
333            Instrument::External(ex) => ex.synth_params.set_eq(eq_ix),
334            Instrument::None => {}
335        }
336    }
337
338    /// Read an in-memory instrument file along with its optional eq
339    pub fn read_from_reader(reader: &mut Reader) -> M8Result<InstrumentWithEq> {
340        let instrument_end_offset = Instrument::INSTRUMENT_MEMORY_SIZE + Version::SIZE;
341        if reader.len() < instrument_end_offset {
342            return Err(ParseError(
343                "File is not long enough to be a M8 Instrument".to_string(),
344            ));
345        }
346
347        let version = Version::from_reader(reader)?;
348        let instrument = Self::from_reader(reader, 0, version)?;
349        let table = Table::from_reader(reader, version)?;
350
351        let eq = match V4_1_OFFSETS.instrument_file_eq_offset {
352            None => None,
353            Some(ofs) if version.after(&FIRMWARE_4_0_SONG_VERSION) => {
354                if reader.len() >= ofs + Equ::V4_SIZE {
355                    reader.set_pos(ofs);
356                    Some(Equ::from_reader(reader))
357                } else {
358                    None
359                }
360            }
361            Some(_) => None,
362        };
363
364        Ok(InstrumentWithEq { instrument, eq, table, version })
365    }
366
367    /// Read a M8 instrument file along with its optional Eq definition.
368    pub fn read(reader: &mut impl std::io::Read) -> M8Result<InstrumentWithEq> {
369        let mut buf: Vec<u8> = vec![];
370        reader.read_to_end(&mut buf).unwrap();
371        let mut reader = Reader::new(buf);
372
373        Self::read_from_reader(&mut reader)
374    }
375
376    pub fn from_reader(reader: &mut Reader, number: u8, version: Version) -> M8Result<Self> {
377        let start_pos = reader.pos();
378        let kind = reader.read();
379
380        let instr = match kind {
381            0x00 => Self::WavSynth(WavSynth::from_reader(version, reader, number, version)?),
382            0x01 => Self::MacroSynth(MacroSynth::from_reader(version, reader, number, version)?),
383            0x02 => Self::Sampler(Sampler::from_reader(
384                version, reader, start_pos, number, version,
385            )?),
386            0x03 => Self::MIDIOut(MIDIOut::from_reader(version, reader, number, version)?),
387            0x04 => Self::FMSynth(FMSynth::from_reader(version, reader, number, version)?),
388            0x05 if version.after(&FIRMWARE_3_0_SONG_VERSION) => {
389                Self::HyperSynth(HyperSynth::from_reader(version, reader, number)?)
390            }
391            0x06 if version.after(&FIRMWARE_3_0_SONG_VERSION) => {
392                Self::External(ExternalInst::from_reader(version, reader, number)?)
393            }
394            0xFF => Self::None,
395            _ => {
396                return Err(ParseError(format!(
397                    "Instrument type {} not supported",
398                    kind
399                )))
400            }
401        };
402
403        reader.set_pos(start_pos + Instrument::INSTRUMENT_MEMORY_SIZE);
404
405        Ok(instr)
406    }
407}