Skip to main content

nord_format/formats/ns3/
program.rs

1//! The Stage 3 program body (`.ns3f`, `.ns3l`): 548 bytes, every documented
2//! parameter placed.
3//!
4//! The program-wide globals were decoded first and by hand; everything else — the
5//! organ's two presets and their drawbars, the piano, synth, extern and the whole
6//! effects chain — comes from the byte maps. Where the two disagree the hand
7//! decode wins: the map has one 22-bit `split` run where the companion doc, and
8//! this module, break it into ten fields.
9//!
10//! The body is 22 bytes of globals and then two [`Panel`]s — the program's two
11//! complete setups — so the panel is declared once and placed twice rather than
12//! spelled out either side. Registry paths follow: `panel_a.organ_type`.
13//!
14//! Values are raw except where the documentation enumerates them; see [the module
15//! docs](super) for what that ceiling is and why.
16
17use super::panel::Panel;
18use crate::cbin::{self, Cbin};
19use crate::components::{
20    sparse_enum, Level, MasterTempo, RotorSpeed, Selector, SplitNote, SplitWidth, StageTranspose,
21    SwitchMorph,
22};
23use crate::error::Error;
24use std::io::{Read, Seek};
25
26pub const FORMAT: &str = "ns3f";
27/// Schema versions this build's field offsets have been validated against:
28/// program v3.00 (OS v0.92) through v3.04 (OS v2.10 and later), stored ×100.
29pub const KNOWN_VERSIONS: &[u32] = &[300, 301, 302, 303, 304];
30pub const BODY_LEN: usize = 548;
31
32/// The Stage 3's octave shift: a nibble biased by 6.
33///
34/// **Corpus:** over the factory banks the slot holds 3..=9 with a decisive mode at 6,
35/// which is where an untransposed program has to sit. The bias differs per model — the
36/// Stage 2 centres on 7 and the Stage 4 stores two's complement — so each names its own.
37/// Inferred from specimens; not confirmed on hardware.
38///
39/// Total over the nibble: the widest encoding is `9 + 6 = 15`, so no stored pattern is
40/// refused.
41pub type OctaveShift = crate::components::OctaveShift<6, -6, 9>;
42
43sparse_enum!(
44    /// Which panels the program enables.
45    PanelEnable, 2, {
46        0 => AOnly, "A only";
47        1 => BOnly, "B only";
48        2 => Both, "A & B";
49    }
50);
51
52sparse_enum!(
53    /// What the second keyboard plays when Dual Keyboard is on.
54    DualKeyboardStyle, 2, {
55        0 => Panel, "Panel";
56        1 => Organ, "Organ";
57        2 => Piano, "Piano";
58        3 => Synth, "Synth";
59    }
60);
61
62/// The program-wide globals at the head of the body. Bits are MSB-first from body
63/// byte 0 (`0x2c` in a type-1 file), so byte 0x05 bit 7 is bit 40.
64///
65/// Reads and writes byte-exactly. A read verifies the container checksum, gates
66/// on [`KNOWN_VERSIONS`], and range-checks every field; unclaimed bits survive a
67/// re-encode verbatim. Placements from the community byte maps; values raw except
68/// where those maps enumerate them. Inferred from specimens; not confirmed on
69/// hardware.
70///
71/// ⚠️ The three split notes can be stored out of order — the panel reorders them on
72/// display (documented with specimens in the ns3-program-viewer sources). The
73/// decode reports what is stored.
74#[nord_bits_derive::bitbody(548)]
75pub struct Program {
76    #[bits(40..=40)]
77    pub panel_b_selected: bool,
78    #[bits(41..=42)]
79    pub panel_enable: PanelEnable,
80    #[bits(43..=43)]
81    pub split_enabled: bool,
82    #[bits(44..=44)]
83    pub split_low_enabled: bool,
84    #[bits(45..=45)]
85    pub split_mid_enabled: bool,
86    #[bits(46..=46)]
87    pub split_high_enabled: bool,
88    #[bits(47..=50)]
89    pub split_low_note: SplitNote,
90    #[bits(51..=54)]
91    pub split_mid_note: SplitNote,
92    #[bits(55..=58)]
93    pub split_high_note: SplitNote,
94    #[bits(59..=60)]
95    pub split_low_width: SplitWidth,
96    #[bits(61..=62)]
97    pub split_mid_width: SplitWidth,
98    #[bits(63..=64)]
99    pub split_high_width: SplitWidth,
100    #[bits(65..=66)]
101    pub piano_layer_detune: PianoLayerDetune,
102    #[bits(67..=67)]
103    pub organ_pitch_stick: bool,
104    #[bits(68..=70)]
105    pub organ_vibrato_mode: OrganVibratoMode,
106    #[bits(71..=71)]
107    pub rotary_speaker_speed: RotorSpeed,
108    #[bits(72..=72)]
109    pub rotary_speaker_stop_mode: bool,
110    #[bits(73..=75)]
111    pub rotary_speaker_speed_wheel: SwitchMorph,
112    #[bits(76..=78)]
113    pub rotary_speaker_speed_aftertouch: SwitchMorph,
114    #[bits(79..=81)]
115    pub rotary_speaker_speed_ctrl_pedal: SwitchMorph,
116    #[bits(96..=96)]
117    pub transpose_enabled: bool,
118    #[bits(97..=100)]
119    pub transpose: StageTranspose,
120    #[bits(101..=108)]
121    pub master_clock: MasterTempo,
122    #[bits(109..=115)]
123    pub rotary_speaker_drive: Level,
124    #[bits(116..=116)]
125    pub dual_keyboard: bool,
126    #[bits(118..=119)]
127    pub dual_keyboard_style: DualKeyboardStyle,
128    #[bits(120..=123)]
129    pub synth_pitch_stick_range: Selector<4>,
130
131    /// Panel A — the first of the program's two complete setups.
132    #[at(22..285)]
133    pub panel_a: Panel,
134
135    /// Panel B. Same type: the two are the same layout, and neither is
136    /// a copy of the other — `panel_enable` says which sound.
137    #[at(285..548)]
138    pub panel_b: Panel,
139}
140
141/// The `(bank, location)` pair from the header, uninterpreted.
142///
143/// Not validated: current exports hold bank 0..=15 and location 0..=24, but v3.00
144/// files in the wild hold out-of-range locations (norduserforum.com t=14414), so
145/// gating on them would refuse real files.
146pub fn location(file: &Cbin<Program>) -> (u16, u16) {
147    file.header.slot()
148}
149
150pub fn read_from(reader: &mut (impl Read + Seek)) -> Result<Cbin<Program>, Error> {
151    let file: Cbin<Program> = cbin::read(reader, FORMAT)?;
152    crate::formats::known_version(FORMAT, file.header.version, KNOWN_VERSIONS)?;
153    Ok(file)
154}
155
156sparse_enum!(
157    /// From the `ns3-amp-sim-eq-amp-type` table in the Stage byte-map docs. Inferred from
158    /// specimens; not confirmed on hardware.
159    AmpSimEqAmpType, 3, {
160        0 => Clean, "Clean";
161        1 => Twin, "Twin";
162        2 => Jc, "JC";
163        3 => Small, "Small";
164        4 => Lp24, "LP24";
165        5 => Hp24, "HP24";
166    }
167);
168
169sparse_enum!(
170    /// From the `ns3-clavinet-model` table in the Stage byte-map docs. Inferred from
171    /// specimens; not confirmed on hardware.
172    ClavinetModel, 2, {
173        0 => Ca, "CA";
174        1 => Cb, "CB";
175        2 => Da, "DA";
176        3 => Db, "DB";
177    }
178);
179
180sparse_enum!(
181    /// From the `ns3-organ-type` table in the Stage byte-map docs. Inferred from
182    /// specimens; not confirmed on hardware.
183    OrganType, 3, {
184        0 => B3, "B3";
185        1 => Vox, "Vox";
186        2 => Farfisa, "Farfisa";
187        3 => Pipe1, "Pipe1";
188        4 => Pipe2, "Pipe2";
189    }
190);
191
192sparse_enum!(
193    /// The organ's vibrato/chorus selection — the six [`VibChorus`] modes, in the order
194    /// the `ns3-organ-vibrato-mode` table in the Stage byte-map docs stores them.
195    /// Inferred from specimens; not confirmed on hardware.
196    ///
197    /// [`VibChorus`]: crate::components::VibChorus
198    OrganVibratoMode, 3, {
199        0 => V1, "V1";
200        1 => C1, "C1";
201        2 => V2, "V2";
202        3 => C2, "C2";
203        4 => V3, "V3";
204        5 => C3, "C3";
205    }
206);
207
208sparse_enum!(
209    /// From the `ns3-piano-kb-touch` table in the Stage byte-map docs. Inferred from
210    /// specimens; not confirmed on hardware.
211    PianoKbTouch, 2, {
212        0 => Normal, "Normal";
213        1 => KbTouch1, "KB Touch 1";
214        2 => Touch2, "Touch 2";
215        3 => Touch3, "Touch 3";
216    }
217);
218
219sparse_enum!(
220    /// From the `ns3-piano-layer-detune` table in the Stage byte-map docs. Inferred from
221    /// specimens; not confirmed on hardware.
222    PianoLayerDetune, 2, {
223        0 => V0, "Off";
224        1 => V1, "1";
225        2 => V2, "2";
226        3 => V3, "3";
227    }
228);
229
230sparse_enum!(
231    /// From the `ns3-piano-timbre` table in the Stage byte-map docs. Inferred from
232    /// specimens; not confirmed on hardware.
233    PianoTimbre, 3, {
234        0 => None, "None";
235        1 => Soft, "Soft";
236        2 => Treble, "Treble";
237        3 => SoftTreble, "Soft+Treble";
238        4 => Brilliant, "Brilliant";
239        5 => SoftBrill, "Soft+Brill";
240        6 => TrebleBrill, "Treble+Brill";
241        7 => SoftTrbBrill, "Soft+Trb+Brill";
242    }
243);
244
245sparse_enum!(
246    /// From the `ns3-piano-type` table in the Stage byte-map docs. Inferred from
247    /// specimens; not confirmed on hardware.
248    PianoType, 3, {
249        0 => Grand, "Grand";
250        1 => Upright, "Upright";
251        2 => Electric, "Electric";
252        3 => Clav, "Clav";
253        4 => Digital, "Digital";
254        5 => Misc, "Misc";
255    }
256);
257
258sparse_enum!(
259    /// From the `ns3-synth-amp-env-velocity` table in the Stage byte-map docs. Inferred from
260    /// specimens; not confirmed on hardware.
261    SynthAmpEnvVelocity, 2, {
262        0 => V0, "Off";
263        1 => V1, "1";
264        2 => V2, "2";
265        3 => V3, "3";
266    }
267);
268
269sparse_enum!(
270    /// From the `ns3-synth-arp-pattern` table in the Stage byte-map docs. Inferred from
271    /// specimens; not confirmed on hardware.
272    SynthArpPattern, 2, {
273        0 => Up, "Up";
274        1 => Down, "Down";
275        2 => UpDown, "Up/Down";
276        3 => Random, "Random";
277    }
278);
279
280sparse_enum!(
281    /// From the `ns3-synth-arp-range` table in the Stage byte-map docs. Inferred from
282    /// specimens; not confirmed on hardware.
283    SynthArpRange, 2, {
284        0 => V1Octave, "1 Octave";
285        1 => V2Octaves, "2 Octaves";
286        2 => V3Octaves, "3 Octaves";
287        3 => V4Octaves, "4 Octaves";
288    }
289);
290
291sparse_enum!(
292    /// From the `ns3-synth-filter-drive` table in the Stage byte-map docs. Inferred from
293    /// specimens; not confirmed on hardware.
294    SynthFilterDrive, 2, {
295        0 => V0, "Off";
296        1 => V1, "1";
297        2 => V2, "2";
298        3 => V3, "3";
299    }
300);
301
302sparse_enum!(
303    /// From the `ns3-synth-filter-kb-track` table in the Stage byte-map docs. Inferred from
304    /// specimens; not confirmed on hardware.
305    SynthFilterKbTrack, 2, {
306        0 => V0, "Off";
307        1 => V1, "1/3";
308        2 => V2, "2/3";
309        3 => V3, "1";
310    }
311);
312
313sparse_enum!(
314    /// From the `ns3-synth-filter-type` table in the Stage byte-map docs. Inferred from
315    /// specimens; not confirmed on hardware.
316    SynthFilterType, 3, {
317        0 => Lp12, "LP12";
318        1 => Lp24, "LP24";
319        2 => MiniMoog, "Mini Moog";
320        3 => LpHp, "LP+HP";
321        4 => Bp24, "BP24";
322        5 => Hp24, "HP24";
323    }
324);
325
326sparse_enum!(
327    /// From the `ns3-synth-lfo-wave` table in the Stage byte-map docs. Inferred from
328    /// specimens; not confirmed on hardware.
329    SynthLfoWave, 3, {
330        0 => Triangle, "Triangle";
331        1 => Saw, "Saw";
332        2 => NegSaw, "Neg Saw";
333        3 => Square, "Square";
334        4 => SH, "S/H";
335    }
336);
337
338sparse_enum!(
339    /// From the `ns3-synth-oscillator-config` table in the Stage byte-map docs. Inferred from
340    /// specimens; not confirmed on hardware.
341    SynthOscillatorConfig, 4, {
342        0 => None, "None";
343        1 => Pitch, "Pitch";
344        2 => Shape, "Shape";
345        3 => Sync, "Sync";
346        4 => Detune, "Detune";
347        5 => Mixsin, "MixSin";
348        6 => Mixtri, "MixTri";
349        7 => Mixsaw, "MixSaw";
350        8 => Mixsqr, "MixSqr";
351        9 => Mixbell, "MixBell";
352        10 => Mixns1, "MixNs1";
353        11 => Mixns2, "MixNs2";
354        12 => Fm1, "FM1";
355        13 => Fm2, "FM2";
356        14 => Rm, "RM";
357    }
358);
359
360sparse_enum!(
361    /// From the `ns3-synth-oscillator-type` table in the Stage byte-map docs. Inferred from
362    /// specimens; not confirmed on hardware.
363    SynthOscillatorType, 3, {
364        0 => Classic, "Classic";
365        1 => Wave, "Wave";
366        2 => Formant, "Formant";
367        3 => Super, "Super";
368        4 => Sample, "Sample";
369    }
370);
371
372sparse_enum!(
373    /// From the `ns3-synth-unison` table in the Stage byte-map docs. Inferred from
374    /// specimens; not confirmed on hardware.
375    SynthUnison, 2, {
376        0 => V0, "Off";
377        1 => V1, "1";
378        2 => V2, "2";
379        3 => V3, "3";
380    }
381);
382
383sparse_enum!(
384    /// From the `ns3-synth-vibrato` table in the Stage byte-map docs. Inferred from
385    /// specimens; not confirmed on hardware.
386    SynthVibrato, 3, {
387        0 => Off, "Off";
388        1 => Delay1, "Delay 1";
389        2 => Delay2, "Delay 2";
390        3 => Delay3, "Delay 3";
391        4 => Wheel, "Wheel";
392        5 => AfterTouch, "After Touch";
393    }
394);
395
396sparse_enum!(
397    /// From the `ns3-synth-voice` table in the Stage byte-map docs. Inferred from
398    /// specimens; not confirmed on hardware.
399    SynthVoice, 2, {
400        0 => Poly, "Poly";
401        1 => Legato, "Legato";
402        2 => Mono, "Mono";
403    }
404);