nooise 2.5.4

Ambient music generator for the terminal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
//! The live control values themselves: `FluidControls` and the per-voice
//! structs it aggregates, each a plain field bag with a default. Meaning,
//! range, and presentation all live in the registry, never here.

use super::module::LayerModules;
use super::voice::LeadPattern;

pub(crate) const MASTER_BPM_MIN: f32 = 30.0;
pub(crate) const MASTER_BPM_MAX: f32 = 200.0;
// Short enough to feel instant under a moving slider, long enough to stay
// click-free on gain changes.
pub(crate) const LEVEL_RAMP_MS: f32 = 30.0;

#[derive(Clone)]
pub(crate) struct MasterControls {
    pub(crate) bpm: f32,
    pub(crate) level: f32,
    pub(crate) tone: f32, // -1 (bass) to +1 (treble)
    pub(crate) tune: f32, // semitones, -12 (1 octave down) to +12 (1 octave up)
}

impl Default for MasterControls {
    fn default() -> Self {
        Self {
            bpm: 82.0,
            level: 0.8,
            tone: 0.0,
            tune: 0.0,
        }
    }
}

#[derive(Clone)]
pub(crate) struct PercControls {
    pub(crate) level: f32,
    pub(crate) decay_ms: f32,
    pub(crate) interval_beats: f32,
    pub(crate) offset_beats: f32,
    pub(crate) swing: f32, // 0 (straight) to 1 (max shuffle) on this voice's grid
}

impl Default for PercControls {
    fn default() -> Self {
        Self {
            level: 0.0,
            decay_ms: 200.0,
            interval_beats: 0.25,
            offset_beats: 0.0,
            swing: 0.0,
        }
    }
}

/// One slot of a custom chord progression: `degree` is the chord root as a
/// tonic-relative scale degree (diatonic steps, -7..7, spanning one octave in
/// each direction); `accidental` nudges that root a semitone flat/sharp;
/// `quality` overrides the chord's third for modal interchange (-1=force
/// minor, 0=whatever the scale gives at this degree, +1=force major);
/// `extension` picks how high the chord's top voice reaches above the triad
/// (0=triad, 1/2/3=progressively richer diatonic extensions); `inversion`
/// moves the lowest voice(s) up an octave. Only read when `PadControls`'s
/// `progression` selects the custom slot (`voice::CUSTOM_PROGRESSION_INDEX`);
/// otherwise inert.
#[derive(Clone, Default)]
pub(crate) struct ChordSlotControls {
    pub(crate) degree: f32,
    pub(crate) accidental: f32,
    pub(crate) quality: f32,
    pub(crate) extension: f32,
    pub(crate) inversion: f32,
}

/// Default per-slot root degrees for a fresh custom progression: a stepwise
/// shape around the tonic so switching into Custom mode is immediately
/// musical rather than eight identical tonic chords.
pub(crate) const DEFAULT_CHORD_SLOT_DEGREES: [f32; 8] = [0.0, -1.0, 0.0, 1.0, 0.0, -1.0, 2.0, 4.0];

/// Number of custom chord slots (`PadControls::chord_slots`), and the max of
/// `pad.chord_count`. Matches the built-ins' fixed 8-chord length.
pub(crate) const CHORD_SLOT_COUNT: usize = 8;
pub(crate) const PAD_RHYTHM_ROWS: u8 = 0b0000_0111;

#[derive(Clone)]
pub(crate) struct PadControls {
    pub(crate) level: f32,
    pub(crate) voice_type: f32, // Character selector; labels and range live in PAD_TYPES.
    pub(crate) chord_bars: f32, // 1,2,4,8,16,32,64
    pub(crate) chord_count: f32,
    /// The table slot the loop starts from, 0..8; `ChordWindow` wraps.
    pub(crate) chord_offset: f32,
    pub(crate) progression: f32,
    pub(crate) chord_notes: f32,
    pub(crate) midi_in: f32,
    pub(crate) midi_out: f32,
    /// In Stabs mode, 0 reads the step lane and 1 fires on MIDI Note On.
    pub(crate) midi_trigger: f32,
    pub(crate) trigger: f32,
    pub(crate) swing: f32,
    pub(crate) gate_beats: f32,
    pub(crate) steps: [f32; 16],
    pub(crate) chord_slots: [ChordSlotControls; CHORD_SLOT_COUNT],
    pub(crate) stereo_width: f32,
    pub(crate) detune: f32,
    pub(crate) octave_mix: f32,
    pub(crate) attack_time: f32,
    pub(crate) release_time: f32,
}

impl Default for PadControls {
    fn default() -> Self {
        Self {
            level: 0.7,
            voice_type: 0.0,
            chord_bars: 4.0,
            chord_count: 8.0,
            chord_offset: 0.0,
            progression: 0.0,
            chord_notes: 4.0,
            midi_in: 0.0,
            midi_out: 1.0,
            midi_trigger: 0.0,
            trigger: 0.0,
            swing: 0.0,
            gate_beats: 0.5,
            steps: [
                1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0,
            ],
            chord_slots: std::array::from_fn(|slot| ChordSlotControls {
                degree: DEFAULT_CHORD_SLOT_DEGREES[slot],
                ..ChordSlotControls::default()
            }),
            stereo_width: 0.8,
            detune: 0.5,
            octave_mix: 0.5,
            attack_time: 6.0,
            release_time: 8.0,
        }
    }
}

impl FluidControls {
    /// A morph or modulation can cross two saved direction switches at once.
    /// In takes priority for that transient; direct edits turn the other side
    /// off in the registry setter instead.
    pub(crate) fn keep_midi_directions_exclusive(&mut self) {
        if self.pad.midi_in >= 0.5 {
            self.pad.midi_out = 0.0;
        }
        if self.arp.midi_in >= 0.5 {
            self.arp.midi_out = 0.0;
        }
        if self.lead.midi_in >= 0.5 {
            self.lead.midi_out = 0.0;
        }
    }
}

#[derive(Clone)]
pub(crate) struct KickControls {
    pub(crate) level: f32,
    pub(crate) voice_type: f32, // 0=Sub (legacy), 1=Punch, 2=Membrane, 3=Driven character selector
    pub(crate) start_freq: f32,
    pub(crate) pitch_decay_ms: f32,
    pub(crate) amp_decay_ms: f32,
    pub(crate) click: f32, // 0–0.2 UI range
    pub(crate) interval_beats: f32,
    pub(crate) offset_beats: f32,
    pub(crate) swing: f32, // 0 (straight) to 1 (max shuffle) on this voice's grid
}

impl Default for KickControls {
    fn default() -> Self {
        Self {
            level: 0.0,
            voice_type: 0.0,
            start_freq: 160.0,
            pitch_decay_ms: 55.0,
            amp_decay_ms: 150.0,
            click: 0.02,
            interval_beats: 1.0,
            offset_beats: 0.0,
            swing: 0.0,
        }
    }
}

#[derive(Clone)]
pub(crate) struct TonalControls {
    pub(crate) level: f32,
    pub(crate) synth_type: f32,
    pub(crate) octave: f32,
    pub(crate) phrase: f32,
    pub(crate) randomness: f32,
    pub(crate) evolve_rate: f32,
    pub(crate) rate_beats: f32,
    pub(crate) step_interval_beats: f32,
    pub(crate) offset_beats: f32,
    pub(crate) attack: f32,
    pub(crate) decay: f32,
    pub(crate) swing: f32, // 0 (straight) to 1 (max shuffle) on this voice's grid
}

impl Default for TonalControls {
    fn default() -> Self {
        Self {
            level: 0.0,
            synth_type: 0.0,
            octave: 0.0,
            phrase: 0.0,
            randomness: 0.5,
            evolve_rate: 0.0,
            rate_beats: 0.5,
            step_interval_beats: 16.0,
            offset_beats: 0.0,
            // Attack + decay are the note's whole envelope: ramp in over
            // `attack`, then fall from the peak to silence over `decay`. The
            // note's sounding length is exactly `attack + decay`, decoupled
            // from the step grid, so `decay` alone sets how long each note
            // rings.
            attack: 0.03,
            decay: 1.2,
            swing: 0.0,
        }
    }
}

#[derive(Clone)]
pub(crate) struct ClapControls {
    pub(crate) level: f32,
    pub(crate) interval_beats: f32,
    pub(crate) offset_beats: f32,
    pub(crate) swing: f32, // 0 (straight) to 1 (max shuffle) on this voice's grid
    pub(crate) slap_count: f32, // 1-8
    pub(crate) slap_spread_ms: f32, // 0-100 ms
    pub(crate) decay_ms: f32, // 10-200 ms
    pub(crate) body: f32,  // 0-1 low-freq flesh mix
}

impl Default for ClapControls {
    fn default() -> Self {
        Self {
            level: 0.0,
            interval_beats: 2.0,
            offset_beats: 1.0,
            swing: 0.0,
            slap_count: 3.0,
            slap_spread_ms: 8.0,
            decay_ms: 40.0,
            body: 0.2,
        }
    }
}

#[derive(Clone)]
pub(crate) struct BassControls {
    pub(crate) level: f32,
    pub(crate) voice_type: f32, // 0=Sub (legacy), 1=Saw, 2=Pluck character selector
    pub(crate) interval_beats: f32, // crops the 16-step rhythm phrase to this many beats (step length is fixed)
    pub(crate) offset_beats: f32,
    pub(crate) rhythm: f32, // 0..=3, A/B/C/D pattern selector
    pub(crate) octave: f32, // octaves relative to the chord root, e.g. -1.0 = one octave down
    pub(crate) attack_time: f32,
    pub(crate) decay_time: f32, // also used as the cutoff curve when a hit retriggers mid-decay
    pub(crate) swing: f32,      // derived from the optional Swing module
}

impl Default for BassControls {
    fn default() -> Self {
        Self {
            level: 0.0,
            voice_type: 0.0,
            interval_beats: 4.0,
            offset_beats: 0.0,
            rhythm: 0.0,
            octave: -1.0,
            decay_time: 0.3,
            attack_time: 0.01,
            swing: 0.0,
        }
    }
}

#[derive(Clone)]
pub(crate) struct ArpControls {
    pub(crate) gain: f32,
    pub(crate) midi_in: f32,
    pub(crate) midi_out: f32,
    pub(crate) midi_gate_beats: f32,
    pub(crate) voice_type: f32, // same Sine/piano-profile set as tonal.synth_type
    pub(crate) rate_beats: f32,
    pub(crate) offset_beats: f32,
    pub(crate) pattern: f32, // 0=Up, 1=Down, 2=Up-Down, 3=Random
    pub(crate) octaves: f32, // 1-3, octave span of the cycled chord tones
    pub(crate) attack: f32,
    pub(crate) decay: f32,
    pub(crate) swing: f32, // 0 (straight) to 1 (max shuffle) on this voice's grid
}

impl Default for ArpControls {
    fn default() -> Self {
        Self {
            // Silent by default: a new voice must never change the sound of
            // existing songs or a fresh startup.
            gain: 0.0,
            midi_in: 0.0,
            midi_out: 0.0,
            midi_gate_beats: 0.5,
            // 6 => the "Pluck" piano profile, matching the arp's former
            // fixed synth character byte-for-byte.
            voice_type: 6.0,
            rate_beats: 0.5,
            offset_beats: 0.0,
            pattern: 0.0,
            octaves: 1.0,
            attack: 0.005,
            decay: 0.4,
            swing: 0.0,
        }
    }
}

/// Number of steps in the Lead's pattern lane, and the max of `lead.steps`.
pub(crate) const LEAD_STEP_COUNT: usize = 16;

/// Default Lead pattern: a stepwise line over the chord tones so a fresh
/// Lead is immediately musical once its level comes up. Step values index
/// the Lead's reach (0 = rest, 1..=4 chord tones, 5..=8 an octave up, 9 the
/// root two octaves up; in Scale follow the same nine walk the progression's
/// scale). Only the default live length is authored; every step past it
/// rests, so lengthening the lane adds silence to fill in, not notes to
/// remove.
pub(crate) const DEFAULT_LEAD_STEPS: [f32; LEAD_STEP_COUNT] = [
    1.0, 2.0, 3.0, 5.0, 4.0, 3.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
];

#[derive(Clone)]
pub(crate) struct LeadControls {
    pub(crate) level: f32,
    pub(crate) midi_in: f32,
    pub(crate) midi_out: f32,
    pub(crate) midi_gate_beats: f32,
    pub(crate) attack: f32,
    pub(crate) decay: f32,
    pub(crate) glide: f32,      // seconds for a pitch change to settle
    pub(crate) voice_type: f32, // character selector; labels and range live in LEAD_TYPES
    pub(crate) octave: f32,     // whole octaves shifting every played tone
    pub(crate) follow: f32,     // what the tones index: labels and range live in LEAD_FOLLOWS
    pub(crate) rate_beats: f32,
    pub(crate) offset_beats: f32,
    pub(crate) step_count: f32, // 1..=LEAD_STEP_COUNT live steps in the lane
    pub(crate) pattern: f32,    // lane transport: labels and range live in LEAD_PATTERNS
    pub(crate) steps: [f32; LEAD_STEP_COUNT],
    pub(crate) swing: f32, // derived from the optional Swing module
}

impl Default for LeadControls {
    fn default() -> Self {
        Self {
            // Silent by default: a new voice must never change the sound of
            // existing songs or a fresh startup.
            level: 0.0,
            midi_in: 0.0,
            midi_out: 0.0,
            midi_gate_beats: 0.5,
            attack: 0.02,
            decay: 0.5,
            // Settled within a 32nd at 82 BPM: audible as a slide between
            // held notes, invisible as lag under a played line.
            glide: 0.03,
            voice_type: 0.0,
            octave: 0.0,
            follow: 0.0,
            rate_beats: 0.5,
            offset_beats: 0.0,
            step_count: 8.0,
            // Play, so a code from before the transport existed still runs
            // its lane.
            pattern: LeadPattern::Play.value(),
            steps: DEFAULT_LEAD_STEPS,
            swing: 0.0,
        }
    }
}

#[derive(Clone)]
pub(crate) struct FluidControls {
    pub(crate) master: MasterControls,
    pub(crate) perc: PercControls,
    pub(crate) pad: PadControls,
    pub(crate) kick: KickControls,
    pub(crate) tonal: TonalControls,
    pub(crate) clap: ClapControls,
    pub(crate) bass: BassControls,
    pub(crate) arp: ArpControls,
    pub(crate) lead: LeadControls,
    /// Which optional MIDI rows have been added through the control palette.
    /// Bits are assigned by `midi_row_bit` and saved separately from values.
    pub(crate) midi_rows: u8,
    /// Pad Trigger, Swing, and Gate rows hidden until selected through `/`.
    pub(crate) hidden_pad_rhythm_rows: u8,
    /// Per-layer module chains, including the factory presets defined by
    /// `LayerModules::default`.
    pub(crate) modules: LayerModules,
}

impl Default for FluidControls {
    fn default() -> Self {
        Self {
            master: MasterControls::default(),
            perc: PercControls::default(),
            pad: PadControls::default(),
            kick: KickControls::default(),
            tonal: TonalControls::default(),
            clap: ClapControls::default(),
            bass: BassControls::default(),
            arp: ArpControls::default(),
            lead: LeadControls::default(),
            midi_rows: 0,
            hidden_pad_rhythm_rows: PAD_RHYTHM_ROWS,
            modules: LayerModules::default(),
        }
    }
}