synthahol_dx7/
lib.rs

1//! The [DX7](https://www.vintagesynth.com/yamaha/dx7.php) is a frequency
2//! modulation (FM) hardware synth by Yamaha.
3//!
4//! A summary of the file format can be found in the
5//! [Dexed repository](https://github.com/asb2m10/dexed/blob/master/Documentation/sysex-format.txt)
6//!
7//! The series
8//! [Yamaha DX7 chip reverse-engineering](https://www.righto.com/2021/12/yamaha-dx7-chip-reverse-engineering.html)
9//! by Ken Sherriff is a useful reference on the hardware.
10
11use std::fmt::{Display, Formatter};
12
13pub use algorithms::*;
14pub use envelope::*;
15pub use format::Format;
16pub use read::*;
17
18mod algorithms;
19mod envelope;
20mod format;
21mod read;
22
23const SYSEX_HEADER: [u8; 6] = [0xF0, 0x43, 0x00, 0x09, 0x20, 0x00];
24
25pub type OperatorId = u8;
26
27pub struct Hardware;
28
29impl Hardware {
30    /// The DX7 had 16 voice polyphony.
31    pub const POLYPHONY: u32 = 16;
32}
33
34#[derive(Clone, Debug, Eq, PartialEq)]
35pub struct PresetName(String);
36
37impl PresetName {
38    /// A preset name has a fixed length
39    pub const MAX_LENGTH: usize = 10;
40
41    /// Normalize and trim the preset name. Unsupported characters are replaced
42    /// with a space.
43    ///
44    /// # Example
45    ///
46    /// ```
47    /// use synthahol_dx7::PresetName;
48    /// assert_eq!("", PresetName::from_lossy(&[]).to_string());
49    /// assert_eq!(" AbC", PresetName::from_lossy(" AbC ".as_bytes()).to_string());
50    /// assert_eq!("! 8X", PresetName::from_lossy("! 8X".as_bytes()).to_string());
51    /// assert_eq!("ABC def", PresetName::from_lossy("ABC\x07def".as_bytes()).to_string());
52    /// assert_eq!("abcdefghij", PresetName::from_lossy("abcdefghijklmnopqrstuvwxyz".as_bytes()).to_string());
53    /// ```
54    pub fn from_lossy(data: &[u8]) -> PresetName {
55        let ascii = data
56            .iter()
57            .map(|c| match c & 0x7F {
58                c if (0x20..0x7f).contains(&c) => c, // Printable ASCII range
59                _ => b' ',
60            })
61            .take(PresetName::MAX_LENGTH)
62            .collect::<Vec<u8>>();
63        PresetName(String::from_utf8_lossy(&ascii).trim_end().to_string())
64    }
65}
66
67impl Default for PresetName {
68    fn default() -> Self {
69        PresetName("INIT VOICE".to_owned())
70    }
71}
72
73impl Display for PresetName {
74    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
75        f.write_str(&self.0)
76    }
77}
78
79#[derive(Copy, Clone, Debug, Eq, PartialEq)]
80#[repr(u8)]
81pub enum Waveform {
82    Triangle = 0,
83    SawDown = 1,
84    SawUp = 2,
85    Square = 3,
86    Sine = 4,
87    SampleAndHold = 5,
88}
89
90impl TryFrom<u8> for Waveform {
91    type Error = &'static str;
92
93    fn try_from(value: u8) -> Result<Self, Self::Error> {
94        match value {
95            0 => Ok(Waveform::Triangle),
96            1 => Ok(Waveform::SawDown),
97            2 => Ok(Waveform::SawUp),
98            3 => Ok(Waveform::Square),
99            4 => Ok(Waveform::Sine),
100            5 => Ok(Waveform::SampleAndHold),
101            _ => Err("Unknown waveform {value}"),
102        }
103    }
104}
105
106#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
107pub enum OperatorMode {
108    Fixed = 0,
109    Ratio = 1,
110}
111
112impl Display for OperatorMode {
113    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114        use OperatorMode::*;
115        let txt = match self {
116            Fixed => "Fixed",
117            Ratio => "Ratio",
118        };
119        f.write_str(txt)
120    }
121}
122
123#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
124pub struct Operator {
125    // In the DX7 the operator ON/OFF state is not stored in the preset and
126    // is only used in parameter change sysex messages while editing a voice.
127    pub envelope: Envelope,
128    pub scaling_break_point: u8,
129    pub scaling_left_depth: u8,
130    pub scaling_right_depth: u8,
131    pub scaling_left_curve: u8,
132    pub scaling_right_curve: u8,
133
134    // -7 to 7. Stored as 0-14 in the preset.
135    pub detune: i8,
136
137    pub rate_scaling: u8,
138    pub velocity_sensitivity: u8,
139    pub modulation_sensitivity: u8,
140    pub output_level: u8,
141    pub mode: OperatorMode,
142    pub frequency_course: u8,
143    pub frequency_fine: u8,
144}
145
146impl Operator {
147    /// Clamp all parameters to valid ranges.
148    fn normalize(&self) -> Self {
149        Self {
150            scaling_break_point: self.scaling_break_point.clamp(0, 99),
151            scaling_left_depth: self.scaling_left_depth.clamp(0, 99),
152            scaling_right_depth: self.scaling_right_depth.clamp(0, 99),
153            scaling_left_curve: self.scaling_left_curve.clamp(0, 3),
154            scaling_right_curve: self.scaling_right_curve.clamp(0, 3),
155            detune: self.detune.clamp(0, 14),
156            rate_scaling: self.rate_scaling.clamp(0, 7),
157            velocity_sensitivity: self.velocity_sensitivity.clamp(0, 7),
158            modulation_sensitivity: self.modulation_sensitivity.clamp(0, 3),
159            output_level: self.output_level.clamp(0, 99),
160            frequency_course: self.frequency_course.clamp(0, 31),
161            frequency_fine: self.frequency_fine.clamp(0, 99),
162            ..*self
163        }
164    }
165}
166
167impl Default for Operator {
168    fn default() -> Self {
169        // The last envelope generator has a different default level according to
170        // the DX7 II manual.
171        let mut envelope = Envelope::default();
172        envelope.levels[envelope.levels.len() - 1] = 0;
173
174        Operator {
175            envelope,
176            scaling_break_point: 39,
177            scaling_left_depth: 0,
178            scaling_right_depth: 0,
179            scaling_left_curve: 0,
180            scaling_right_curve: 0,
181            detune: 0,
182            rate_scaling: 0,
183            velocity_sensitivity: 0,
184            modulation_sensitivity: 0,
185            output_level: 0,
186            mode: OperatorMode::Fixed,
187            frequency_course: 1,
188            frequency_fine: 0,
189        }
190    }
191}
192
193#[derive(Clone, Debug, Eq, PartialEq)]
194pub struct Preset {
195    pub name: PresetName,
196    pub operators: [Operator; Preset::OPERATOR_COUNT],
197    pub pitch_envelope: Envelope,
198    pub algorithm_id: AlgorithmId,
199
200    #[doc(alias = "osc phase init")]
201    pub oscillator_key_sync: bool,
202    pub feedback_level: u8,
203    pub lfo_speed: u8,
204    pub lfo_delay: u8,
205    pub lfo_pitch_mod_depth: u8,
206    pub lfo_pitch_mod_sensitivity: u8,
207    pub lfo_amplitude_mod_depth: u8,
208    pub lfo_waveform: Waveform,
209    pub lfo_key_sync: bool,
210    pub transpose: u8,
211}
212
213impl Preset {
214    const OPERATOR_COUNT: usize = 6;
215
216    /// Clamp all parameters to valid ranges.
217    fn normalize(&self) -> Self {
218        // Normalization is done outside of reading to enable reuse.
219        Preset {
220            name: self.name.clone(),
221            operators: self.operators.map(|operator| operator.normalize()),
222            pitch_envelope: self.pitch_envelope.normalize(),
223            algorithm_id: self.algorithm_id.clamp(0, 31),
224            oscillator_key_sync: self.oscillator_key_sync,
225            feedback_level: self.feedback_level.clamp(0, 7),
226            lfo_speed: self.lfo_speed.clamp(0, 99),
227            lfo_delay: self.lfo_delay.clamp(0, 99),
228            lfo_pitch_mod_depth: self.lfo_pitch_mod_depth.clamp(0, 99),
229            lfo_pitch_mod_sensitivity: self.lfo_pitch_mod_sensitivity.clamp(0, 99),
230            lfo_amplitude_mod_depth: self.lfo_amplitude_mod_depth.clamp(0, 99),
231            lfo_waveform: self.lfo_waveform,
232            lfo_key_sync: self.lfo_key_sync,
233            transpose: self.transpose.clamp(0, 48),
234        }
235    }
236}
237
238impl Default for Preset {
239    fn default() -> Self {
240        let mut operators = [Operator::default(); Preset::OPERATOR_COUNT];
241        operators[0] = Operator {
242            output_level: 99,
243            ..Default::default()
244        };
245
246        let pitch_envelope = Envelope::from_rate_and_level(99, 50);
247        Preset {
248            name: PresetName::default(),
249            operators,
250            pitch_envelope,
251            algorithm_id: 0,
252            oscillator_key_sync: true,
253            feedback_level: 0,
254            lfo_speed: 35,
255            lfo_delay: 0,
256            lfo_pitch_mod_depth: 0,
257            lfo_pitch_mod_sensitivity: 3,
258            lfo_amplitude_mod_depth: 0,
259            lfo_waveform: Waveform::Triangle,
260            lfo_key_sync: true,
261            transpose: 24,
262        }
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use std::path::PathBuf;
269
270    use super::*;
271
272    pub(crate) fn test_data_path(components: &[&str]) -> PathBuf {
273        let mut parts = vec!["tests"];
274        parts.extend_from_slice(components);
275        parts.iter().collect::<PathBuf>()
276    }
277
278    #[test]
279    fn default() {
280        let preset = Preset::default();
281        assert_eq!(preset, preset);
282
283        // The defaults must already be in range so normalizing shouldn't have
284        // an effect.
285        assert_eq!(preset, preset.normalize());
286
287        // Pitch envelope generators.
288        assert_eq!(99, preset.pitch_envelope.rates[0]);
289        assert_eq!(50, preset.pitch_envelope.levels[0]);
290
291        // General parameters.
292        assert_eq!(0, preset.algorithm_id);
293        assert!(preset.lfo_key_sync);
294        assert!(preset.oscillator_key_sync);
295        assert_eq!(35, preset.lfo_speed);
296        assert_eq!(3, preset.lfo_pitch_mod_sensitivity);
297        assert_eq!(24, preset.transpose);
298        assert_eq!("INIT VOICE", preset.name.to_string());
299
300        // Only the first operator has an output level
301        assert_eq!(99, preset.operators[0].output_level);
302        assert_eq!(0, preset.operators.last().unwrap().output_level);
303
304        // Only the last envelope generator has a level of zero.
305        assert_eq!(99, preset.operators[0].envelope.levels[0]);
306        assert_eq!(0, *preset.operators[0].envelope.levels.last().unwrap());
307
308        // General operator parameters.
309        assert_eq!(0, preset.operators[0].rate_scaling);
310        assert_eq!(39, preset.operators[0].scaling_break_point);
311        assert_eq!(0, preset.operators[0].detune);
312        assert_eq!(OperatorMode::Fixed, preset.operators[0].mode);
313        assert_eq!(1, preset.operators[0].frequency_course);
314        assert_eq!(0, preset.operators[0].frequency_fine);
315    }
316
317    #[test]
318    fn normalize() {
319        let preset = Preset {
320            feedback_level: 123,
321            lfo_delay: 100,
322            transpose: 200,
323            ..Default::default()
324        }
325        .normalize();
326        assert_eq!(7, preset.feedback_level);
327        assert_eq!(99, preset.lfo_delay);
328        assert_eq!(48, preset.transpose);
329    }
330}