Skip to main content

moont/
param.rs

1// Copyright (C) 2021-2026 Geoff Hill <geoff@geoffhill.org>
2// Copyright (C) 2003-2026 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
3//
4// This program is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Lesser General Public License as published by
6// the Free Software Foundation, either version 2.1 of the License, or (at
7// your option) any later version. Read COPYING.LESSER.txt for details.
8
9//! Shared parameter types for instrument timbres, patches, and rhythm keys.
10
11use core::fmt;
12
13/// Byte size of a serialized timbre parameter block (common header + 4 partials).
14pub const TIMBRE_PARAM_SIZE: usize = 246;
15
16/// Byte size of the common timbre header (name, structure, mute, sustain).
17pub const COMMON_PARAM_SIZE: usize = 14;
18
19/// Byte size of one partial's parameters (WG + TVP + LFO + TVF + TVA).
20pub const PARTIAL_PARAM_SIZE: usize = 58;
21
22/// Number of melodic parts (MIDI channels 1-8).
23pub const MELODIC_PARTS_COUNT: usize = 8;
24
25/// Number of rhythm keys (MIDI notes 24-108).
26pub const RHYTHM_KEYS_COUNT: usize = 85;
27
28/// Waveform source type for a partial.
29#[derive(Default, Copy, Clone, PartialEq, Eq)]
30pub enum PartialType {
31    /// LA synthesis (square or sawtooth oscillator).
32    #[default]
33    Oscillator,
34    /// PCM sample playback.
35    Pcm,
36}
37
38// Match literal format for ROM literal serialization.
39impl fmt::Debug for PartialType {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            PartialType::Oscillator => write!(f, "PartialType::Oscillator"),
43            PartialType::Pcm => write!(f, "PartialType::Pcm"),
44        }
45    }
46}
47
48/// Mix mode for a pair of partials.
49#[derive(Default, Copy, Clone, PartialEq, Eq)]
50pub enum PairMode {
51    /// Additive mix of both partials.
52    #[default]
53    Mix,
54    /// Ring modulation plus the top (master) partial.
55    RingPlusTop,
56    /// Ring modulation only (slave can terminate master).
57    RingOnly,
58    /// Each partial output to a separate stereo channel.
59    Stereo,
60}
61
62// Match literal format for ROM literal serialization.
63impl fmt::Debug for PairMode {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            PairMode::Mix => write!(f, "PairMode::Mix"),
67            PairMode::RingPlusTop => write!(f, "PairMode::RingPlusTop"),
68            PairMode::RingOnly => write!(f, "PairMode::RingOnly"),
69            PairMode::Stereo => write!(f, "PairMode::Stereo"),
70        }
71    }
72}
73
74use PairMode::{Mix, RingOnly, RingPlusTop, Stereo};
75use PartialType::{Oscillator, Pcm};
76
77/// Lookup table mapping structure byte (0-12) to details.
78pub const PAIR_STRUCTURES: [(PartialType, PartialType, PairMode); 13] = [
79    (Oscillator, Oscillator, Mix),         // 0
80    (Oscillator, Oscillator, RingPlusTop), // 1
81    (Pcm, Oscillator, Mix),                // 2
82    (Pcm, Oscillator, RingPlusTop),        // 3
83    (Oscillator, Pcm, RingPlusTop),        // 4
84    (Pcm, Pcm, Mix),                       // 5
85    (Pcm, Pcm, RingPlusTop),               // 6
86    (Oscillator, Oscillator, Stereo),      // 7
87    (Pcm, Pcm, Stereo),                    // 8
88    (Oscillator, Oscillator, RingOnly),    // 9
89    (Pcm, Oscillator, RingOnly),           // 10
90    (Oscillator, Pcm, RingOnly),           // 11
91    (Pcm, Pcm, RingOnly),                  // 12
92];
93
94#[derive(Debug, Copy, Clone)]
95#[repr(C, packed)]
96pub(crate) struct RawTvpParam {
97    pub depth: u8,
98    pub velo_sensitivity: u8,
99    pub time_keyfollow: u8,
100    pub time: [u8; 4],
101    pub level: [u8; 5],
102}
103
104/// Decoded pitch envelope (TVP) parameters for one partial.
105#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
106pub struct TvpParam {
107    pub depth: i32,
108    pub velo_sensitivity: i32,
109    pub time_keyfollow: i32,
110    pub time: [i32; 4],
111    pub level: [i32; 5],
112}
113
114impl From<&RawTvpParam> for TvpParam {
115    fn from(raw: &RawTvpParam) -> Self {
116        Self {
117            depth: raw.depth.min(10) as i32,
118            velo_sensitivity: raw.velo_sensitivity.min(3) as i32,
119            time_keyfollow: raw.time_keyfollow.min(4) as i32,
120            time: [
121                raw.time[0].min(100) as i32,
122                raw.time[1].min(100) as i32,
123                raw.time[2].min(100) as i32,
124                raw.time[3].min(100) as i32,
125            ],
126            level: [
127                raw.level[0].min(100) as i32 - 50,
128                raw.level[1].min(100) as i32 - 50,
129                raw.level[2].min(100) as i32 - 50,
130                raw.level[3].min(100) as i32 - 50,
131                raw.level[4].min(100) as i32 - 50,
132            ],
133        }
134    }
135}
136
137#[derive(Debug, Copy, Clone)]
138#[repr(C, packed)]
139pub(crate) struct RawTvfParam {
140    pub cutoff: u8,
141    pub resonance: u8,
142    pub keyfollow: u8,
143    pub bias_point: u8,
144    pub bias_level: u8,
145    pub env_depth: u8,
146    pub env_velo_sensitivity: u8,
147    pub env_depth_keyfollow: u8,
148    pub env_time_keyfollow: u8,
149    pub env_time: [u8; 5],
150    pub env_level: [u8; 4],
151}
152
153/// Decoded filter cutoff envelope (TVF) parameters for one partial.
154#[derive(Debug, Copy, Clone, PartialEq, Eq)]
155pub struct TvfParam {
156    pub cutoff: i32,
157    pub resonance: i32,
158    pub keyfollow: usize,
159    pub bias_point: i32,
160    pub bias_level: usize,
161    pub env_depth: i32,
162    pub env_velo_sensitivity: i32,
163    pub env_depth_keyfollow: i32,
164    pub env_time_keyfollow: i32,
165    pub env_time: [i32; 5],
166    pub env_level: [u8; 4],
167}
168
169impl Default for TvfParam {
170    fn default() -> Self {
171        Self {
172            cutoff: 0,
173            resonance: 0,
174            keyfollow: 0,
175            bias_point: 0,
176            bias_level: 0,
177            env_depth: 0,
178            env_velo_sensitivity: 0,
179            env_depth_keyfollow: 0,
180            env_time_keyfollow: 0,
181            env_time: [0; 5],
182            env_level: [100; 4],
183        }
184    }
185}
186
187impl From<&RawTvfParam> for TvfParam {
188    fn from(raw: &RawTvfParam) -> Self {
189        Self {
190            cutoff: raw.cutoff.min(100) as i32,
191            resonance: raw.resonance.min(30) as i32,
192            keyfollow: raw.keyfollow.min(16) as usize,
193            bias_point: (raw.bias_point & 0x7F) as i32,
194            bias_level: raw.bias_level.min(14) as usize,
195            env_depth: raw.env_depth.min(100) as i32,
196            env_velo_sensitivity: raw.env_velo_sensitivity.min(100) as i32,
197            env_depth_keyfollow: raw.env_depth_keyfollow.min(4) as i32,
198            env_time_keyfollow: raw.env_time_keyfollow.min(4) as i32,
199            env_time: [
200                raw.env_time[0].min(100) as i32,
201                raw.env_time[1].min(100) as i32,
202                raw.env_time[2].min(100) as i32,
203                raw.env_time[3].min(100) as i32,
204                raw.env_time[4].min(100) as i32,
205            ],
206            env_level: [
207                raw.env_level[0].min(100),
208                raw.env_level[1].min(100),
209                raw.env_level[2].min(100),
210                raw.env_level[3].min(100),
211            ],
212        }
213    }
214}
215
216#[derive(Debug, Copy, Clone)]
217#[repr(C, packed)]
218pub(crate) struct RawTvaParam {
219    pub level: u8,
220    pub velo_sensitivity: u8,
221    pub bias_point_1: u8,
222    pub bias_level_1: u8,
223    pub bias_point_2: u8,
224    pub bias_level_2: u8,
225    pub env_time_keyfollow: u8,
226    pub env_time_velo_sensitivity: u8,
227    pub env_time: [u8; 5],
228    pub env_level: [u8; 4],
229}
230
231/// Decoded amplitude envelope (TVA) parameters for one partial.
232#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
233pub struct TvaParam {
234    pub level: usize,
235    pub velo_sensitivity: i32,
236    pub bias_point_1: i32,
237    pub bias_level_1: usize,
238    pub bias_point_2: i32,
239    pub bias_level_2: usize,
240    pub env_time_keyfollow: i32,
241    pub env_time_velo_sensitivity: i32,
242    pub env_time: [i32; 5],
243    pub env_level: [u8; 4],
244}
245
246impl From<&RawTvaParam> for TvaParam {
247    fn from(raw: &RawTvaParam) -> Self {
248        Self {
249            level: raw.level.min(100) as usize,
250            velo_sensitivity: raw.velo_sensitivity.min(100) as i32,
251            bias_point_1: (raw.bias_point_1 & 0x7F) as i32,
252            bias_level_1: raw.bias_level_1.min(12) as usize,
253            bias_point_2: (raw.bias_point_2 & 0x7F) as i32,
254            bias_level_2: raw.bias_level_2.min(12) as usize,
255            env_time_keyfollow: raw.env_time_keyfollow.min(4) as i32,
256            env_time_velo_sensitivity: raw.env_time_velo_sensitivity.min(4)
257                as i32,
258            env_time: [
259                raw.env_time[0].min(100) as i32,
260                raw.env_time[1].min(100) as i32,
261                raw.env_time[2].min(100) as i32,
262                raw.env_time[3].min(100) as i32,
263                raw.env_time[4].min(100) as i32,
264            ],
265            env_level: [
266                raw.env_level[0].min(100),
267                raw.env_level[1].min(100),
268                raw.env_level[2].min(100),
269                raw.env_level[3].min(100),
270            ],
271        }
272    }
273}
274
275#[derive(Debug, Copy, Clone)]
276#[repr(C, packed)]
277pub(crate) struct RawPartialParam {
278    pub wg_pitch_coarse: u8,
279    pub wg_pitch_fine: u8,
280    pub wg_pitch_keyfollow: u8,
281    pub wg_pitch_bender_enabled: u8,
282    pub wg_waveform: u8,
283    pub wg_pcm_wave: u8,
284    pub wg_pulse_width: u8,
285    pub wg_pw_velo_sensitivity: u8,
286    pub tvp: RawTvpParam,
287    pub pitch_lfo_rate: u8,
288    pub pitch_lfo_depth: u8,
289    pub pitch_lfo_mod_sensitivity: u8,
290    pub tvf: RawTvfParam,
291    pub tva: RawTvaParam,
292}
293
294/// Decoded parameters for one partial (wave generator + envelopes + LFO).
295#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
296pub struct PartialParam {
297    pub wg_pitch_coarse_offset: i32,
298    pub wg_pitch_fine_offset: i32,
299    pub wg_pitch_keyfollow: usize,
300    pub wg_pitch_bender_enabled: bool,
301    pub wg_waveform: usize,
302    pub wg_pcm_wave: usize,
303    pub wg_pulse_width: usize,
304    pub wg_pw_velo_sensitivity: i32,
305    pub tvp: TvpParam,
306    pub pitch_lfo_rate: u8,
307    pub pitch_lfo_depth: u8,
308    pub pitch_lfo_mod_sensitivity: u8,
309    pub tvf: TvfParam,
310    pub tva: TvaParam,
311}
312
313impl From<&RawPartialParam> for PartialParam {
314    fn from(raw: &RawPartialParam) -> Self {
315        let mut pcm_wave = (raw.wg_pcm_wave & 0x7F) as usize;
316        let waveform = raw.wg_waveform.min(2) as usize;
317        if waveform > 1 {
318            pcm_wave += 128;
319        }
320
321        let coarse = raw.wg_pitch_coarse.min(108) as i32;
322        let coarse_offset = (coarse - 36) * 4096 / 12;
323
324        let fine = raw.wg_pitch_fine.min(100) as i32;
325        let fine_offset = (fine - 50) * 4096 / 1200;
326
327        let keyfollow = raw.wg_pitch_keyfollow.min(16) as usize;
328        let pulse_width = raw.wg_pulse_width.min(100) as usize;
329        let pw_velo_sensitivity = raw.wg_pw_velo_sensitivity.min(14) as i32;
330
331        Self {
332            wg_pitch_coarse_offset: coarse_offset,
333            wg_pitch_fine_offset: fine_offset,
334            wg_pitch_keyfollow: keyfollow,
335            wg_pitch_bender_enabled: (raw.wg_pitch_bender_enabled & 1) != 0,
336            wg_waveform: waveform,
337            wg_pcm_wave: pcm_wave,
338            wg_pulse_width: pulse_width,
339            wg_pw_velo_sensitivity: pw_velo_sensitivity,
340            tvp: TvpParam::from(&raw.tvp),
341            pitch_lfo_rate: raw.pitch_lfo_rate.min(100),
342            pitch_lfo_depth: raw.pitch_lfo_depth.min(100),
343            pitch_lfo_mod_sensitivity: raw.pitch_lfo_mod_sensitivity.min(100),
344            tvf: TvfParam::from(&raw.tvf),
345            tva: TvaParam::from(&raw.tva),
346        }
347    }
348}
349
350#[derive(Debug, Copy, Clone)]
351#[repr(C, packed)]
352pub(crate) struct RawTimbreParam {
353    pub name: [u8; 10],
354    pub partial_structure_12: u8,
355    pub partial_structure_34: u8,
356    pub partial_mute: u8,
357    pub no_sustain: u8,
358    pub partials: [RawPartialParam; 4],
359}
360
361/// Decoded timbre: pair structure, mute flags, and four partial parameter sets.
362#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
363pub struct TimbreParam {
364    pub partial_types: [PartialType; 4],
365    pub pair_modes: [PairMode; 2],
366    pub partial_mute: u8,
367    pub no_sustain: bool,
368    pub partials: [PartialParam; 4],
369}
370
371impl From<&RawTimbreParam> for TimbreParam {
372    fn from(raw: &RawTimbreParam) -> Self {
373        let idx12 = raw.partial_structure_12.min(12) as usize;
374        let idx34 = raw.partial_structure_34.min(12) as usize;
375        let (type0, type1, mode12) = PAIR_STRUCTURES[idx12];
376        let (type2, type3, mode34) = PAIR_STRUCTURES[idx34];
377
378        TimbreParam {
379            partial_types: [type0, type1, type2, type3],
380            pair_modes: [mode12, mode34],
381            partial_mute: raw.partial_mute,
382            no_sustain: raw.no_sustain != 0,
383            partials: [
384                PartialParam::from(&raw.partials[0]),
385                PartialParam::from(&raw.partials[1]),
386                PartialParam::from(&raw.partials[2]),
387                PartialParam::from(&raw.partials[3]),
388            ],
389        }
390    }
391}
392
393#[derive(Debug, Copy, Clone)]
394#[repr(C, packed)]
395pub(crate) struct RawRhythmKey {
396    pub timbre: u8,
397    pub level: u8,
398    pub panpot: u8,
399    pub reverb: u8,
400}
401
402/// Decoded rhythm settings for one MIDI key.
403#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
404pub struct RhythmKey {
405    pub timbre: usize,
406    pub level: usize,
407    pub panpot: i32,
408    pub reverb: bool,
409}
410
411impl From<&RawRhythmKey> for RhythmKey {
412    /// Decode a rhythm key setting from control ROM format.
413    fn from(raw: &RawRhythmKey) -> Self {
414        let timbre = (raw.timbre & 0x7F) as usize;
415        let level = raw.level.min(100) as usize;
416        let panpot = raw.panpot.min(14) as i32;
417        let reverb = (raw.reverb & 1) != 0;
418
419        RhythmKey {
420            timbre,
421            level,
422            panpot,
423            reverb,
424        }
425    }
426}
427
428/// Decoded patch settings (timbre reference, tuning, bender range, assign mode).
429#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
430pub struct PatchParam {
431    pub timbre_group: u8,
432    pub timbre_num: u8,
433    pub key_shift: i32,
434    pub fine_tune: i32,
435    pub bender_range: u8,
436    pub assign_mode: u8,
437    pub reverb_switch: bool,
438}
439
440/// Active per-part settings: patch parameters plus output level and pan.
441#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
442pub struct PatchTemp {
443    pub patch: PatchParam,
444    pub output_level: usize,
445    pub panpot: i32,
446}
447
448#[derive(Debug, Copy, Clone)]
449#[repr(C, packed)]
450pub(crate) struct RawPatchParam {
451    pub timbre_group: u8,
452    pub timbre_num: u8,
453    pub key_shift: u8,
454    pub fine_tune: u8,
455    pub bender_range: u8,
456    pub assign_mode: u8,
457    pub reverb_switch: u8,
458    pub dummy: u8,
459}
460
461impl From<&RawPatchParam> for PatchParam {
462    fn from(raw: &RawPatchParam) -> Self {
463        PatchParam {
464            timbre_group: raw.timbre_group.min(3),
465            timbre_num: raw.timbre_num.min(63),
466            key_shift: raw.key_shift.min(48) as i32,
467            fine_tune: raw.fine_tune.min(100) as i32,
468            bender_range: raw.bender_range.min(24),
469            assign_mode: raw.assign_mode.min(3),
470            reverb_switch: raw.reverb_switch != 0,
471        }
472    }
473}
474
475#[derive(Debug, Copy, Clone)]
476#[repr(C, packed)]
477pub(crate) struct RawPatchTemp {
478    pub patch: RawPatchParam,
479    pub output_level: u8,
480    pub panpot: u8,
481    pub dummy: [u8; 6],
482}
483
484impl From<&RawPatchTemp> for PatchTemp {
485    fn from(raw: &RawPatchTemp) -> Self {
486        PatchTemp {
487            patch: PatchParam::from(&raw.patch),
488            output_level: raw.output_level.min(100) as usize,
489            panpot: raw.panpot.min(14) as i32,
490        }
491    }
492}
493
494#[derive(Debug, Copy, Clone)]
495#[repr(C, packed)]
496pub(crate) struct RawRhythmTemp {
497    pub timbre: u8,
498    pub level: u8,
499    pub panpot: u8,
500    pub reverb_switch: u8,
501}
502
503impl From<&RawRhythmTemp> for RhythmKey {
504    fn from(raw: &RawRhythmTemp) -> Self {
505        RhythmKey {
506            timbre: (raw.timbre & 0x7F) as usize,
507            level: raw.level.min(100) as usize,
508            panpot: raw.panpot.min(14) as i32,
509            reverb: raw.reverb_switch != 0,
510        }
511    }
512}
513
514pub(crate) fn cast_raw_timbre(
515    raw: &[u8; TIMBRE_PARAM_SIZE],
516) -> &RawTimbreParam {
517    unsafe { &*(raw.as_ptr() as *const RawTimbreParam) }
518}