Skip to main content

m8_file_parser/instruments/
hypersynth.rs

1use num_enum::{IntoPrimitive, TryFromPrimitive};
2use super::common::SynthParams;
3use super::common::TranspEq;
4use super::common::COMMON_FILTER_TYPES;
5use super::dests;
6use super::CommandPack;
7use super::Version;
8use crate::reader::*;
9use crate::writer::Writer;
10use crate::FIRMWARE_6_0_SONG_VERSION;
11use crate::FIRMWARE_6_2_SONG_VERSION;
12use crate::SEND_COMMAND_NAMES;
13use crate::SEND_COMMAND_NAMES_6_2;
14
15use arr_macro::arr;
16use array_concat::concat_arrays;
17
18#[derive(PartialEq, Debug, Clone, Default)]
19pub struct Chord {
20    pub mask: u8,
21    pub offsets : [u8; 6]
22}
23
24impl Chord {
25    pub fn read(reader: &mut Reader) -> Self{
26        let mask = reader.read();
27
28        Self {
29            mask,
30            offsets: arr![reader.read(); 6]
31        }
32    }
33
34    pub fn write(&self, w: &mut Writer) {
35        w.write(self.mask);
36        for k in &self.offsets {
37            w.write(*k);
38        }
39    }
40
41    pub fn is_osc_on(&self, osc: usize) -> bool{
42        (self.mask & (1 << osc)) != 0
43    }
44
45    pub fn offset_str(&self, osc: usize) -> String {
46        if self.is_osc_on(osc) {
47            format!("{:02X}", self.offsets[osc])
48        } else {
49            String::from("--")
50        }
51    }
52}
53
54#[repr(u8)]
55#[derive(IntoPrimitive, TryFromPrimitive, PartialEq, Copy, Clone, Default, Debug)]
56pub enum HyperSynthShape {
57    #[default]
58    Saw = 0x0,
59    SoftSaw = 0x1,
60    DarkSaw = 0x2,
61    SquareSoft = 0x3,
62    Triangle = 0x4,
63    Sine = 0x5,
64    Sine3x = 0x6,
65    SineFb = 0x7,
66    SineFold = 0x8,
67    SineRing = 0x9,
68    SineHalf = 0xA,
69    SineOrgan = 0xB
70}
71
72
73#[derive(PartialEq, Debug, Clone)]
74pub struct HyperSynth {
75    pub number: u8,
76    pub name: String,
77    pub transpose: bool,
78    pub table_tick: u8,
79    pub synth_params: SynthParams,
80
81    pub scale: u8,
82    pub default_chord: [u8; 7],
83    pub shift: u8,
84    pub swarm: u8,
85    pub width: u8,
86    pub subosc: u8,
87
88    pub chords: [Chord; 0x10],
89    pub shape: Option<HyperSynthShape>
90}
91
92#[rustfmt::skip] // Keep constants with important order vertical for maintenance
93const HYPERSYNTH_COMMAND_NAMES_BASE : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT - 3] = [
94    "VOL",
95    "PIT",
96    "FIN",
97    "CRD",
98    "SHF",
99    "SWM",
100    "WID",
101    "SUB",
102    "FLT",
103    "CUT",
104    "RES",
105    "AMP",
106    "LIM",
107    "PAN",
108    "DRY"
109];
110    
111
112#[rustfmt::skip] // Keep constants with important order vertical for maintenance
113const HYPERSYNTH_COMMAND_NAMES : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 2] =
114    concat_arrays!(HYPERSYNTH_COMMAND_NAMES_BASE, SEND_COMMAND_NAMES, ["CVO", "SNC"]);
115
116#[rustfmt::skip] // Keep constants with important order vertical for maintenance
117const HYPERSYNTH_COMMAND_NAMES_BASE_6 : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT - 3] = [
118    "VOL",
119    "PIT",
120    "FIN",
121    "CRD",
122    "CVO",
123    "SWM",
124    "WID",
125    "SUB",
126    "FLT",
127    "CUT",
128    "RES",
129    "AMP",
130    "LIM",
131    "PAN",
132    "DRY"
133];
134    
135
136#[rustfmt::skip] // Keep constants with important order vertical for maintenance
137const HYPERSYNTH_COMMAND_NAMES_6 : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 2] =
138    concat_arrays!(HYPERSYNTH_COMMAND_NAMES_BASE_6, SEND_COMMAND_NAMES, ["SNC", "ERR"]);
139
140#[rustfmt::skip] // Keep constants with important order vertical for maintenance
141const HYPERSYNTH_COMMAND_NAMES_6_2 : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 2] =
142    concat_arrays!(HYPERSYNTH_COMMAND_NAMES_BASE_6, SEND_COMMAND_NAMES_6_2, ["SNC", "ERR"]);
143
144#[rustfmt::skip] // Keep constants with important order vertical for maintenance
145const DESTINATIONS : [&'static str; 15] = [
146    dests::OFF,
147    dests::VOLUME,
148    dests::PITCH,
149
150    "SHIFT",
151    "SWARM",
152    "WIDTH",
153    "SUBOSC",
154    dests::CUTOFF,
155    dests::RES,
156    dests::AMP,
157    dests::PAN,
158    dests::MOD_AMT,
159    dests::MOD_RATE,
160    dests::MOD_BOTH,
161    dests::MOD_BINV,
162];
163
164impl HyperSynth {
165    const MOD_OFFSET: usize = 23;
166
167    pub fn command_name(&self, ver: Version) -> &'static [&'static str] {
168        if ver.after(&FIRMWARE_6_2_SONG_VERSION) {
169            &HYPERSYNTH_COMMAND_NAMES_6_2
170        } else if ver.after(&FIRMWARE_6_0_SONG_VERSION) {
171            &HYPERSYNTH_COMMAND_NAMES_6
172        } else {
173            &HYPERSYNTH_COMMAND_NAMES
174        }
175    }
176
177    pub fn destination_names(&self, _ver: Version) -> &'static [&'static str] {
178        &DESTINATIONS
179    }
180
181    /// List of all the applyable filter types for the instrument
182    pub fn filter_types(&self, _ver: Version) -> &'static [&'static str] {
183        &super::common::COMMON_FILTER_TYPES
184    }
185
186    pub fn human_readable_filter(&self) -> &'static str {
187        COMMON_FILTER_TYPES[self.synth_params.filter_type as usize]
188    }
189
190    pub fn write(&self, ver: Version, w: &mut Writer) {
191        w.write_string(&self.name, 12);
192        w.write(TranspEq::from(ver, self.transpose, self.synth_params.associated_eq).into());
193        w.write(self.table_tick);
194        w.write(self.synth_params.volume);
195        w.write(self.synth_params.pitch);
196        w.write(self.synth_params.fine_tune);
197
198        for c in self.default_chord {
199            w.write(c);
200        }
201
202        w.write(self.scale);
203        w.write(self.shift);
204        w.write(self.swarm);
205        w.write(self.width);
206        w.write(self.subosc);
207
208        self.synth_params.write(ver, w, HyperSynth::MOD_OFFSET);
209
210        for chd in &self.chords {
211            chd.write(w);
212        }
213    }
214
215    pub fn from_reader(ver: Version, reader: &mut Reader, number: u8) -> M8Result<Self> {
216        let name = reader.read_string(12);
217        let transp_eq = TranspEq::from_version(ver, reader.read());
218        let table_tick = reader.read();
219        let volume = reader.read();
220        let pitch = reader.read();
221        let fine_tune = reader.read();
222
223        let default_chord = arr![reader.read(); 7];
224        let scale = reader.read();
225        let shift = reader.read();
226        let swarm = reader.read();
227        let width = reader.read();
228        let subosc = reader.read();
229        let synth_params = SynthParams::from_reader3(
230            ver,
231            reader,
232            volume,
233            pitch,
234            fine_tune,
235            transp_eq.eq,
236            HyperSynth::MOD_OFFSET,
237        )?;
238
239        let chords = arr![Chord::read(reader); 0x10];
240
241        let shape =
242            if ver.at_least(6, 6) {
243                let shape_byte = synth_params.shape;
244                let hsshape =
245                    HyperSynthShape::try_from(shape_byte)
246                        .map_err(|_| ParseError(String::from("Invalid hypersynth shape parameter")))?;
247
248                Some(hsshape)
249            } else {
250                None
251            };
252
253        Ok(HyperSynth {
254            number,
255            name,
256            transpose: transp_eq.transpose,
257            table_tick,
258            synth_params,
259
260            scale,
261            default_chord,
262            shift,
263            swarm,
264            width,
265            subosc,
266            chords,
267            shape
268        })
269    }
270}