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
use libm::pow;

use crate::*;
use core::f32::consts::TAU;

const FREQ_C4: f64 = 261.63;

/// A single sound channel with configurable properties.
pub struct Channel {
    // Wavetable
    /// Enables and disables sample looping. TODO: Looping strategies, i.e. In and Out points.
    wavetable: Vec<f32>,
    wave_out: f32,
    // Timing. All timing values are f64, sample values are f32
    time: f64,
    time_env: f32,
    time_noise: f64,
    period: f64,
    // Wavetable
    // Volume
    /// Optional volume envelope, range is 0.0 ..= 1.0
    pub volume_env: Option<Envelope>,
    volume: f32,
    volume_attn: f32,
    // Pitch
    /// Optional pitch envelope. range is -1.0 ..= 1.0, multiplied by pitch_env_multiplier.
    /// Resulting value is added to the current note in MIDI note range (C4 = 60.0).
    pub pitch_env: Option<Envelope>,
    /// Multiplies the pitch envelope to obtain a pitch offset.
    pub pitch_env_multiplier:f32,
    // Noise
    rng: Rng,
    noise_on: bool,
    noise_period: f64,
    noise_output: f32,
    // State
    specs: ChipSpecs,
    pan: f32,
    midi_note: f32,
    playing: bool,
    left_mult: f32,
    right_mult: f32,
    last_sample_index: usize,
    last_sample_value: f32,

}

impl From<ChipSpecs> for Channel {
    fn from(specs: ChipSpecs) -> Self {
        let mut result = Self {
            // Timing
            time: 0.0,
            time_env: 0.0,
            time_noise: 0.0,
            period: 1.0 / FREQ_C4,
            // Wavetable
            wavetable: Self::get_wavetable(&specs),
            wave_out: 0.0,
            // Volume
            volume: 1.0,
            volume_env: None,
            volume_attn: 0.0,
            // Pitch
            pitch_env: None,
            pitch_env_multiplier: 1.0,
            // Noise
            rng: Self::get_rng(&specs),
            noise_on: false,
            noise_period: 0.0,
            noise_output: 0.0,
            // State
            specs,
            pan: 0.0,
            midi_note: 60.0,
            playing: false,
            left_mult: 0.5,
            right_mult: 0.5,
            last_sample_index: 0,
            last_sample_value: 0.0,
        };
        result.set_note(4, Note::C, true);
        result.set_volume(1.0);
        result
    }
}

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

impl Channel {
    /// Creates a new channel configured with a square wave.
    pub fn new_psg(allow_noise: bool) -> Self {
        let specs = ChipSpecs {
            wavetable: WavetableSpecs::psg(),
            pan: PanSpecs::psg(),
            pitch: PitchSpecs::psg(),
            volume: VolumeSpecs::psg(),
            noise: NoiseSpecs::psg(allow_noise),
        };
        Self::from(specs)
    }

    /// Creates a new channel configured with a 32 byte wavetable
    pub fn new_scc() -> Self {
        let specs = ChipSpecs {
            wavetable: WavetableSpecs::scc(),
            pan: PanSpecs::scc(),
            pitch: PitchSpecs::scc(),
            volume: VolumeSpecs::scc(),
            noise: NoiseSpecs::None,
        };
        Self::from(specs)
    }

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

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

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

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

    /// 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).
    pub fn note(&self) -> i32 {
        libm::floorf(self.midi_note) as i32 % 12
    }

    /// Current frequency.
    pub fn pitch(&self) -> f32 {
        1.0 / self.period as f32
    }

    /// The main volume level. Values above 1.0 may cause clipping.
    pub fn volume(&self) -> f32 {
        self.volume
    }

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

    /// 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
    }

    pub fn reset_envelopes(&mut self) {
        self.time_env = 0.0;
        if let Some(env) = &mut self.volume_env {
            env.reset();
        }
        if let Some(env) = &mut self.pitch_env {
            env.reset();
        }
    }

    /// TODO: Better noise Rng per noise settings
    pub fn set_specs(&mut self, specs: ChipSpecs) {
        self.rng = Self::get_rng(&specs);
        self.specs = specs;
    }

    /// Resets the wavetable and copies new f32 values to it, ensuring -1.0 to 1.0 range.
    /// Will return an error if values are invalid.
    pub fn set_wavetable(&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 ChipSpecs.
    pub fn set_volume(&mut self, volume: f32) {
        self.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 ChipSpecs.
    pub fn set_pan(&mut self, pan: f32) {
        self.pan = pan;
        self.calculate_multipliers();
    }

    /// Switches channel between tone and noise generation, if specs allow noise.
    pub fn set_noise(&mut self, state: bool) {
        if self.specs.noise != NoiseSpecs::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>, reset_time: bool) {
        let midi_note = get_midi_note(octave, note) as f64;
        self.set_midi_note(midi_note, reset_time);
    }

    /// 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<f64>, reset_time: bool) {
        let note: f64 = note.into();
        self.midi_note = note as f32;
        // cache current phase to re-apply at the end
        let previous_phase = (self.time % self.period as f64) / self.period as f64;
        // Calculate note frequency
        let frequency = note_to_frequency_f64(note);
        self.period = 1.0 / frequency;
        // Envelope timer
        if reset_time {
            self.time_env = 0.0;
        }
        self.time = if !self.specs.wavetable.use_loop || !self.playing || reset_time {
            // 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.
            0.0
        } else {
            // Adjust time to ensure continuous change (instead of abrupt change)
            previous_phase * self.period
        };
        // NoiseSpecs
        match &self.specs.noise {
            NoiseSpecs::Random { pitch, .. } | NoiseSpecs::Melodic { pitch, .. } => {
                if let Some(steps) = &pitch.steps {
                    let freq_range = if let Some(range) = &pitch.range {
                        *range.start() as f64..=*range.end() as f64
                    } 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_f64(tone_freq, *steps, freq_range.clone());
                    let noise_period = 1.0 / noise_freq;
                    self.noise_period = noise_period / pitch.multiplier as f64;
                } else {
                    self.noise_period = self.period / pitch.multiplier as f64;
                }
            }
            _ => {}
        }
    }

    #[inline(always)]
    /// Returns the current sample and advances the internal timer.
    pub(crate) fn sample(&mut self, delta_time: f64) -> 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,
            };
        }

        // Adjust volume with envelope
        let volume_env = if let Some(env) = &mut self.volume_env {
            env.process(self.time_env)
        } else {
            1.0
        };

        // Adjust periods with pitch envelope
        let (period, noise_period) = if let Some(env) = &mut self.pitch_env {
            let value = env.process(self.time_env) as f64;
            // let octave_mult = envelope_value * self.pitch_env_multiplier as f64;
            let tone_period = self.period * pow(self.pitch_env_multiplier as f64, -value);
            let noise_period = self.noise_period * pow(self.pitch_env_multiplier as f64, -value);
            // let note = self.note + offset;
            (tone_period, noise_period)
        } else {
            (self.period, self.noise_period)
        };

        // Generate noise level, will be mixed later
        self.noise_output = match &self.specs.noise {
            NoiseSpecs::None => 0.0,
            NoiseSpecs::Random { volume_steps, .. } | NoiseSpecs::Melodic { volume_steps, .. } => {
                if self.time_noise >= noise_period {
                    self.time_noise = 0.0;
                    (quantize_range_f32(self.rng.next_f32(), *volume_steps, 0.0..=1.0) * 2.0) - 1.0
                } else {
                    self.noise_output
                }
            }
            NoiseSpecs::WaveTable { .. } => 0.0,
        };

        // Determine wavetable index
        let len = self.wavetable.len();
        let index = if self.specs.wavetable.use_loop {
            let phase = (self.time % period) / period;
            (phase * len as f64) as usize
        } else {
            let phase = (self.time / period).clamp(0.0, 1.0);
            ((phase * len as f64) as usize).clamp(0, len - 1)
        };

        // Obtain wavetable sample and set it to output
        if index != self.last_sample_index {
            self.last_sample_index = index;
            // TODO: Optional quantization!
            let value = if let Some(steps) = self.specs.wavetable.steps {
                quantize_range_f32(self.wavetable[index] as f32, steps, -1.0..=1.0)
            } else {
                self.wavetable[index] as f32
            };

            // Avoids resetting attenuation if value hasn't changed
            if value != self.last_sample_value {
                self.wave_out = value;
                self.last_sample_value = value;
            }
        }

        // Advance timer
        self.time += delta_time;
        self.time_noise += delta_time;
        self.time_env += delta_time as f32;

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

        // Quantize volume (if needed) and apply log curve.
        // Any math using the envelope needs to be calculate per sample, unfortunately.
        let vol = libm::powf(
            if let Some(steps) = self.specs.volume.steps {
                quantize_range_f32(self.volume * volume_env, steps, 0.0..=1.0)
            } else {
                self.volume * volume_env
            },
            self.specs.volume.exponent,
        );

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

        // 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_f32(self.pan, pan_steps, -1.0..=1.0)
        } else {
            self.pan
        };
        // 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;
    }

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

    // New Wavetable Vec from specs
    fn get_wavetable(specs: &ChipSpecs) -> Vec<f32> {
        (0..specs.wavetable.sample_count)
            .map(|i| {
                // Default sine wave
                let a = (i as f32 / specs.wavetable.sample_count as f32) * TAU;
                libm::sinf(a)
            })
            .collect()
    }
}

#[inline(always)]
pub(crate) fn note_to_frequency_f64(note: f64) -> f64 {
    libm::pow(2.0, (note - 69.0) / 12.0) * 440.0
}

#[inline(always)]
pub(crate) fn note_to_frequency_f32(note: f32) -> f32 {
    libm::powf(2.0, (note - 69.0) / 12.0) * 440.0
}