m8_files/instruments/
mod.rs

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