m8_file_parser/instruments/
mod.rs

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