soundchip 0.4.61

Software sinth with configurable channels for authentic sounding virtual sound chips.
Documentation
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
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
use crate::{math::*, prelude::*, presets::*, rng::*, Vec};
use core::f32::consts::TAU;
use libm::powf;

const FREQ_C4: f32 = 261.63;

/// A single sound channel with configurable properties. The easiest way to create a Channel
/// is using Channel::from(spec), and provide one of the Specs from the "presets" module,
/// or create your own spec from scratch.
#[derive(Debug)]
pub struct Channel {
    /// All public sound properties
    sound: Sound,
    // Wavetable
    wavetable: Vec<f32>,
    wave_out: f32,
    // Timing
    phase: f32,
    time: f32,
    time_env: f32,
    time_tone: f32,
    time_noise: f32,
    // Volume
    // volume: f32,
    volume_attn: f32,
    // Pitch
    period: f32,
    midi_note: f32,
    // Noise
    rng: Rng,
    noise_on: bool,
    noise_period: f32,
    noise_output: f32,
    // State
    specs: SpecsChip,
    pan: NormalSigned,
    playing: bool,
    left_mult: f32,
    right_mult: f32,
    last_sample_index: usize,
    last_sample_value: f32,
    last_cycle_index: usize,
    // Envelope processing
    env_period: f32,
    last_env: EnvelopeValues,
    last_env_time: f32,
}

impl From<SpecsChip> for Channel {
    fn from(specs: SpecsChip) -> Self {
        let wave_env: Envelope<NormalSigned> = if let Some(knots) = specs.wavetable.default_waveform
        {
            Envelope::from(knots)
        } else {
            Envelope::from(KNOTS_WAVE_TRIANGLE)
        };
        let mut result = Self {
            // Timing
            phase: 0.0,
            time: 0.0,
            time_env: 0.0,
            time_tone: 0.0,
            time_noise: 0.0,
            // Wavetable
            wavetable: Self::get_wavetable_from_specs(&specs),
            wave_out: 0.0,
            // Volume
            volume_attn: 0.0,
            // Pitch
            midi_note: 60.0,
            period: 1.0 / FREQ_C4,
            // Noise
            rng: Self::get_rng(&specs),
            noise_on: false,
            noise_period: 0.0,
            noise_output: 0.0,
            // State
            sound: Sound {
                waveform: Some(wave_env),
                ..Default::default()
            },
            specs,
            pan: NormalSigned::from(0.0),
            playing: false,
            left_mult: 0.5,
            right_mult: 0.5,
            last_sample_index: 0,
            last_sample_value: 0.0,
            last_cycle_index: 0,
            // Envelope processing
            env_period: 0.0,
            last_env_time: 0.0,
            last_env: EnvelopeValues::default(),
        };
        result.set_note(4, Note::C);
        result.set_volume(1.0);
        result.reset();
        result
    }
}

impl Default for Channel {
    fn default() -> Self {
        Self::from(SpecsChip::default())
    }
}

impl Channel {
    /// Allows sound generation on this channel.
    pub fn play(&mut self) {
        self.playing = true;
        self.set_pitch(self.sound.pitch);
        self.calculate_multipliers();
    }

    /// Plays and immediately releases the channel, preventing any envelope with loop points
    /// from looping until released.
    pub fn play_and_release(&mut self) {
        self.set_pitch(self.sound.pitch);
        self.play();
        self.release();
    }

    /// Resets the channel, sets the sound, plays it and releases envelopes.
    pub fn play_sound(&mut self, sound: &Sound, release: bool) {
        self.reset();
        self.set_sound(sound);
        self.play();
        if release {
            self.release();
        }
    }

    /// Stops sound generation on this channel.
    pub fn stop(&mut self) {
        self.playing = false;
        self.reset();
    }

    /// "Releases" all envelopes, allowing them to exit their looping state and reach their end.
    pub fn release(&mut self) {
        if let Some(env) = &mut self.sound.volume_env {
            env.release();
        }
        if let Some(env) = &mut self.sound.pitch_env {
            env.release();
        }
    }

    /// The current internal time
    pub fn time(&self) -> f32 {
        self.time
    }

    /// Current playing state.
    pub fn is_playing(&self) -> bool {
        self.playing
    }

    /// The virtual Chip specs used in this channel.
    pub fn specs(&self) -> &SpecsChip {
        &self.specs
    }

    /// The current sound settings.
    pub fn sound(&self) -> &Sound {
        &self.sound
    }

    /// True if channel is set to noise, false if set to tone.
    pub fn is_noise(&self) -> bool {
        self.noise_on
    }

    /// Current octave.
    pub fn octave(&self) -> i32 {
        libm::floorf(self.midi_note / 12.0) as i32 - 1
    }

    /// Current midi note (C4 = 60). Does not account for pitch envelope.
    pub fn note(&self) -> i32 {
        libm::floorf(self.midi_note) as i32 % 12
    }

    /// Current frequency. Does not account for pitch envelope.
    pub fn pitch(&self) -> f32 {
        1.0 / self.period
    }

    /// The main volume level. Values above 1.0 may cause clipping. Does not account for volume envelope.
    pub fn volume(&self) -> f32 {
        self.sound.volume
    }

    /// Current stereo panning. Zero means centered (mono).
    pub fn pan(&self) -> f32 {
        self.pan.into()
    }

    /// Mutable access to the wavetable. Be careful to not set values beyond -1.0 to 1.0.
    pub fn wavetable(&mut self) -> &mut Vec<f32> {
        &mut self.wavetable
    }

    /// Resets al internal timers (tone, noise, envelopes)
    pub fn reset(&mut self) {
        self.time = 0.0;
        self.time_tone = 0.0;
        self.time_noise = 0.0;
        self.last_cycle_index = 0;
        self.calculate_multipliers();
        self.reset_envelopes();
    }

    /// Resets just the envelope timer.
    pub fn reset_envelopes(&mut self) {
        self.time_env = 0.0;
        self.last_env_time = 0.0;
        if let Some(env) = &mut self.sound.volume_env {
            env.reset();
        }
        if let Some(env) = &mut self.sound.pitch_env {
            env.reset();
        }
        if let Some(env) = &mut self.sound.noise_env {
            env.reset();
        }
        self.process_envelopes();
    }

    /// Reconfigures all internals according to new specs
    pub fn set_specs(&mut self, specs: SpecsChip) {
        self.rng = Self::get_rng(&specs);
        self.wavetable = Self::get_wavetable_from_specs(&specs);
        self.specs = specs;
    }

    /// Sets all of the channel's relevant properties to match the sound's properties, but
    /// does not change the specs (the only exception is the wavetable envelope,
    /// which can be set by the sound).
    pub fn set_sound(&mut self, sound: &Sound) {
        self.sound = sound.clone();
        if let Some(env) = &sound.waveform {
            self.wavetable = Self::get_wavetable(&self.specs, env);
        }
        self.reset();
    }

    /// Generates wavetable samples from an envelope
    /// TODO: Normalize time range.
    pub fn set_wavetable(&mut self, wave: &Envelope<NormalSigned>) -> Result<(), ChipError> {
        self.sound.waveform = Some(wave.clone());
        if let Some(env) = &self.sound.waveform {
            self.wavetable = Self::get_wavetable(&self.specs, env);
        }
        Ok(())
    }

    /// Directly sets the wavetable from f32 values, ensuring -1.0 to 1.0 range.
    /// Will return an error if values are invalid.
    pub fn set_wavetable_raw(&mut self, table: &[f32]) -> Result<(), ChipError> {
        self.wavetable.clear();
        for item in table {
            if *item >= -1.0 && *item <= 1.0 {
                self.wavetable.push(*item);
            } else {
                return Err(ChipError::InvalidWavetable);
            }
        }
        Ok(())
    }

    /// A value between 0.0 and 1.0. It will be quantized, receive a fixed gain and
    /// mapped to an exponential curve, according to the SpecsChip.
    pub fn set_volume(&mut self, volume: f32) {
        self.sound.volume = volume.clamp(0.0, 16.0);
        self.calculate_multipliers();
    }

    /// Stereo panning, from left (-1.0) to right (1.0). Centered is zero. Will be quantized per SpecsChip.
    pub fn set_pan(&mut self, pan: f32) {
        self.pan = pan.into();
        self.calculate_multipliers();
    }

    /// Switches channel between tone and noise generation, if specs allow noise.
    /// Will be overriden if a noise envelope is used.
    pub fn set_noise(&mut self, state: bool) {
        if self.specs.noise != SpecsNoise::None {
            self.noise_on = state;
        }
    }

    /// Adjusts internal pitch values to correspond to octave and note ( where C = 0, C# = 1, etc.).
    /// "reset_time" forces the waveform to start from position 0, ignoring previous phase.
    pub fn set_note(&mut self, octave: impl Into<i32>, note: impl Into<i32>) {
        let midi_note = get_midi_note(octave, note);
        self.set_midi_note(midi_note as f32);
    }

    /// Same as set_note, but the notes are an f32 value which allows "in-between" notes, or pitch sliding,
    /// and uses MIDI codes instead of octave and note, i.e. C4 is MIDI code 60.
    pub fn set_midi_note(&mut self, note: impl Into<f32>) {
        let note: f32 = note.into();
        let frequency = note_to_frequency(note);
        self.set_pitch(frequency);
    }

    /// Directly set the channel's frequency.
    pub fn set_pitch(&mut self, frequency: f32) {
        self.sound.pitch = frequency;
        self.period = 1.0 / frequency;
        self.midi_note = frequency_to_note(frequency);
        // Auto reset time if needed
        if !self.specs.wavetable.use_loop || !self.playing {
            // If looping isn't required, ensure sample will be played from beginning.
            // Also, if channel is not playing it means we'll start playing a cycle
            // from 0.0 to avoid clicks.
            self.time = 0.0;
            self.time_tone = 0.0;
        } else {
            // Adjust time to ensure continuous change (instead of abrupt change)
            self.time = self.phase * self.period;
            self.time_tone = self.phase * self.period;
        };
        // SpecsNoise
        match &self.specs.noise {
            SpecsNoise::Random { pitch, .. } | SpecsNoise::Melodic { pitch, .. } => {
                if let Some(steps) = &pitch.steps {
                    let freq_range = if let Some(range) = &pitch.range {
                        *range.start()..=*range.end()
                    } else {
                        // C0 to C10 in "scientific pitch"", roughly the human hearing range
                        16.0..=16384.0
                    };
                    let tone_freq = 1.0 / self.period;
                    let noise_freq = quantize_range(tone_freq, *steps, freq_range.clone());
                    let noise_period = 1.0 / noise_freq;
                    self.noise_period = noise_period / pitch.multiplier;
                } else {
                    self.noise_period = self.period / pitch.multiplier;
                }
            }
            _ => {}
        }
        self.last_env = self.process_envelopes();
    }

    fn process_envelopes(&mut self) -> EnvelopeValues {
        // Adjust volume with envelope
        let mut volume_env = if let Some(env) = &mut self.sound.volume_env {
            env.peek(self.time_env)
        } else {
            1.0
        };

        // Apply tremolo
        if let Some(tremolo) = &self.sound.tremolo {
            let sine = libm::sinf(self.time * TAU * tremolo.frequency);
            let quant = if let Some(steps) = tremolo.steps {
                quantize_range(sine, steps, -1.0..=1.0)
            } else {
                sine
            };
            let normalized = ((quant / 2.0) + 0.5) * tremolo.amplitude;
            volume_env = (volume_env - normalized).clamp(0.0, 1.0);
        };

        // Quantize volume (if needed) and apply log curve.
        let volume = libm::powf(
            if let Some(steps) = self.specs.volume.steps {
                quantize_range(self.sound.volume * volume_env, steps, 0.0..=1.0)
            } else {
                self.sound.volume * volume_env
            },
            self.specs.volume.exponent,
        );

        // Pitch envelope
        let mut pitch_change = if let Some(env) = &mut self.sound.pitch_env {
            env.peek(self.time_env)
        } else {
            0.0
        };

        // Apply vibratto
        if let Some(vibratto) = &self.sound.vibratto {
            let sine = libm::sinf(self.time * TAU * vibratto.frequency);
            let quant = if let Some(steps) = vibratto.steps {
                quantize_range(sine, steps, -1.0..=1.0)
            } else {
                sine
            };
            pitch_change = pitch_change + (quant * vibratto.amplitude);
        };

        // Acquire optionally quantized tone period and noise period with pitch change
        let base_period = (self.period / self.specs.pitch.multiplier) * powf(2.0, -pitch_change);
        let tone_period = if let Some(steps) = self.specs.pitch.steps {
            if let Some(range) = &self.specs.pitch.range {
                // TODO: This needs optimization...
                let freq = 1.0 / base_period;
                1.0 / quantize_range(freq, steps, (*range).clone())
            } else {
                base_period
            }
        } else {
            base_period
        };

        let noise_period = self.noise_period * powf(2.0, -pitch_change);
        let noise = if let Some(env) = &mut self.sound.noise_env {
            env.peek(self.time_env)
        } else {
            if self.noise_on {
                1.0
            } else {
                0.0
            }
        };

        // Timing adjust to preserve phase
        self.time_tone = self.phase * tone_period;
        self.last_env_time = self.time;

        // Return
        EnvelopeValues {
            volume,
            noise,
            tone_period,
            noise_period,
        }
    }

    #[inline(always)]
    /// Returns the current sample and peeks the internal timer.
    pub(crate) fn sample(&mut self, delta_time: f32) -> Sample<f32> {
        // Always apply attenuation, so that values always drift to zero
        self.wave_out *= self.volume_attn;

        // Early return if not playing
        if !self.playing {
            return Sample {
                left: self.wave_out * self.left_mult,
                right: self.wave_out * self.right_mult,
            };
        }

        // Envelope processing
        let process_envelopes_now = if self.specs.envelope_rate.is_some() {
            // If there's an envelope rate, calculate envelopes when needed.
            if (self.time - self.last_env_time >= self.env_period) || self.time == 0.0 {
                true
            } else {
                false
            }
        } else {
            // If no envelope rate, always calculate envelopes
            true
        };

        // Generate noise level, will be mixed later
        if self.last_env.noise > 0.0 {
            self.noise_output = match self.specs.noise {
                SpecsNoise::None => 0.0,
                SpecsNoise::Random { volume_steps, .. }
                | SpecsNoise::Melodic { volume_steps, .. } => {
                    if process_envelopes_now {
                        self.last_env = self.process_envelopes();
                    }
                    if self.time_noise >= self.last_env.noise_period {
                        self.time_noise = 0.0;
                        (quantize_range(self.rng.next_f32(), volume_steps as u16, 0.0..=1.0) * 2.0)
                            - 1.0
                    } else {
                        self.noise_output
                    }
                }
                SpecsNoise::WaveTable { .. } => 0.0,
            };
        }

        // Determine wavetable index
        let len = self.wavetable.len();
        let index = if self.specs.wavetable.use_loop {
            (self.phase * len as f32) as usize
        } else {
            ((self.phase * len as f32) as usize).clamp(0, len - 1) // TODO: Needs testing
        };

        // Obtain wavetable sample and set it to output
        if index != self.last_sample_index {
            self.last_sample_index = index;
            let wave = self.wavetable[index];
            let value = if let Some(steps) = self.specs.wavetable.steps {
                quantize_range(wave, steps, -1.0..=1.0)
            } else {
                wave
            };
            // Avoids resetting attenuation if value hasn't changed
            if value != self.last_sample_value {
                // Prevents sampling envelope in the middle of a wave cycle
                let cycle_index =
                    (self.time_tone as f64 / self.last_env.tone_period as f64) as usize;
                if cycle_index != self.last_cycle_index {
                    self.last_cycle_index = cycle_index;
                    if process_envelopes_now {
                        self.last_env = self.process_envelopes();
                    }
                }
                self.wave_out = value;
                self.last_sample_value = value;
            }
        }

        // adjust timers
        self.time += delta_time;
        self.time_noise += delta_time;
        self.time_env += delta_time;
        self.time_tone += delta_time;
        self.phase = if self.specs.wavetable.use_loop {
            (self.time_tone % self.last_env.tone_period) / self.last_env.tone_period
        } else {
            self.time_tone / self.last_env.tone_period // TODO: Needs testing
        };

        // Mix with noise (currently just overwrites). TODO: optional mix
        if self.last_env.noise > 0.0 {
            self.wave_out = self.noise_output;
        }

        // Apply volume and optionally clamp to positive values
        let output = if self.specs.volume.clip_negative_values {
            self.wave_out.clamp(0.0, 1.0) * self.last_env.volume
        } else {
            self.wave_out * self.last_env.volume
        };

        // Return sample with volume and pan applied
        Sample {
            left: output * self.left_mult,
            right: output * self.right_mult,
        }
    }

    // Must be called after setting volume or pan.
    // Used to pre-calculate as many values as possible instead of doing it per sample, since
    // this function is called much less frequently (by orders of magnitude)
    pub(crate) fn calculate_multipliers(&mut self) {
        // Pre calculate this so we don't do it on every sample
        self.volume_attn = 1.0 - self.specs.volume.attenuation.clamp(0.0, 1.0);
        // Pan quantization
        let pan = if let Some(pan_steps) = self.specs.pan.steps {
            quantize_range(self.pan.into(), pan_steps, -1.0..=1.0)
        } else {
            self.pan.into()
        };
        // Is applying gain to the pan OK? Needs testing
        self.left_mult = ((pan - 1.0) / -2.0) * self.specs.volume.gain;
        self.right_mult = ((pan + 1.0) / 2.0) * self.specs.volume.gain;
        // Envelope period
        if let Some(env_freq) = self.specs.envelope_rate {
            self.env_period = 1.0 / env_freq;
        }
    }

    // New Rng from specs
    fn get_rng(specs: &SpecsChip) -> Rng {
        match specs.noise {
            SpecsNoise::None | SpecsNoise::Random { .. } | SpecsNoise::WaveTable { .. } => {
                Rng::new(15, 1)
            }
            SpecsNoise::Melodic { lfsr_length, .. } => Rng::new(lfsr_length as u32, 1),
        }
    }

    // New Wavetable Vec from specs
    fn get_wavetable_from_specs(specs: &SpecsChip) -> Vec<f32> {
        let mut envelope: Envelope<NormalSigned> =
            if let Some(knots) = specs.wavetable.default_waveform {
                knots.into()
            } else {
                KNOTS_WAVE_TRIANGLE.into()
            };
        (0..specs.wavetable.sample_count)
            .map(|i| {
                // Default sine wave
                let t = i as f32 / specs.wavetable.sample_count as f32;
                // libm::sinf(t * TAU)
                envelope.peek(t).into()
            })
            .collect()
    }

    // New Wavetable Vec
    fn get_wavetable(specs: &SpecsChip, envelope: &Envelope<NormalSigned>) -> Vec<f32> {
        let mut envelope = envelope.clone();
        (0..specs.wavetable.sample_count)
            .map(|i| {
                // Default sine wave
                let t = i as f32 / specs.wavetable.sample_count as f32;
                // libm::sinf(t * TAU)
                envelope.peek(t).into()
            })
            .collect()
    }
}

#[derive(Debug)]
struct EnvelopeValues {
    volume: f32, // TODO: Normal
    noise: f32,  // TODO: Normal
    tone_period: f32,
    noise_period: f32,
}

impl Default for EnvelopeValues {
    fn default() -> Self {
        Self {
            volume: 1.0,
            noise: 0.0,
            tone_period: 1.0 / FREQ_C4,
            noise_period: 1.0 / FREQ_C4,
        }
    }
}