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
//! A antialiasing oscillator.
//!
//! ## Waveforms
//!
//! The oscillator supports several classical waveforms. The square, triangle,
//! and saw waves support both aliased and antialiased variants.
//!
//! The aliased variants produce pure signals; for example, a square wave only
//! emits two values: `-1.0` and `+1.0`. This is useful when a control signal is
//! wanted, but produces aliasing in the frequency domain.
//!
//! The antialiased variants use PolyBLEP (polynomial bandlimited step) to
//! mitigate the aliasing in the pure signals. This results in a much cleaner
//! audible signal, and is more desirable for most musical purposes.
//!
//! ## Pitch Bend
//!
//! The oscillator supports pitch bending as an additional modifier of the base
//! frequency. This makes it easier to bend a single note when working with
//! MIDI, rather than manually tracking the base frequency and computing a new
//! frequency yourself.
//!
//! ## Low Frequency Oscillator
//!
//! The oscillator supports a low frequency oscillator (LFO) as optional input.
//! If provided, then the LFO is used to modulate the frequency of the
//! oscillator, producing a vibrato.
//!
//! ## Example
//!
//! The oscillator uses a builder pattern for initialization. The following will
//! set up an antialiased saw wave at 440 Hz, with a 0.1 step vibrato:
//!
//! ```
//! use oxcable::oscillator::*;
//! let osc = Oscillator::new(Saw(PolyBlep)).freq(440.0).lfo_intensity(0.1);
//! ```

use std::f32::consts::PI;
use num::traits::Float;
use rand::random;

use types::{SAMPLE_RATE, AudioDevice, MessageReceiver, Sample, Time};


/// Defines the messages that the Oscillator supports.
#[derive(Clone, Copy, Debug)]
pub enum Message {
    /// Sets the frequency in Hz.
    SetFreq(f32),
    /// Sets the waveform type.
    SetWaveform(Waveform),
    /// Sets the LFO vibrato depth, in steps.
    SetLFOIntensity(f32),
    /// Sets the pitch transposition, in steps.
    SetTranspose(f32),
    /// Sets the pitch bend, in steps.
    SetBend(f32),
}
pub use self::Message::*;


/// Antialiasing method for certain waveforms.
#[derive(Clone, Copy, Debug)]
pub enum AntialiasType {
    /// Naive, aliasing waveforms.
    Aliased,
    /// Antialiasing using PolyBLEP.
    PolyBlep
}
pub use self::AntialiasType::*;


/// Oscillator waveforms.
#[derive(Clone, Copy, Debug)]
pub enum Waveform {
    /// A sine wave.
    Sine,
    /// A saw wave.
    Saw(AntialiasType),
    /// A square wave.
    Square(AntialiasType),
    /// A triangle wave.
    Tri(AntialiasType),
    /// Pure white noise.
    WhiteNoise,
    /// A series of impulses.
    PulseTrain
}
pub use self::Waveform::*;


/// An oscillator that generates a periodic waveform.
pub struct Oscillator {
    waveform: Waveform,
    lfo_intensity: f32,
    transpose: f32,
    bend: f32,
    phase: f32,
    phase_delta: f32,
    last_sample: Sample,
}

impl Oscillator {
    /// Returns an oscillator with the specified waveform.
    pub fn new(waveform: Waveform) -> Self {
        Oscillator {
            waveform: waveform,
            lfo_intensity: 0.0,
            transpose: 1.0,
            bend: 1.0,
            phase: 0.0,
            phase_delta: 0.0,
            last_sample: 0.0
        }
    }

    /// Sets the frequency of the waveform, and return the same oscillator.
    pub fn freq(mut self, freq: f32) -> Self {
        self.handle_message(SetFreq(freq));
        self
    }

    /// Sets the frequency transposition (in steps), and return the same
    /// oscillator.
    pub fn transpose(mut self, steps: f32) -> Self {
        self.handle_message(SetTranspose(steps));
        self
    }

    /// Sets the intensity of the LFO vibrato, and return the same oscillator.
    ///
    /// The intensity is provided in half steps (1/2ths of an octave).
    pub fn lfo_intensity(mut self, lfo_intensity: f32) -> Self {
        self.handle_message(SetLFOIntensity(lfo_intensity));
        self
    }
}

impl MessageReceiver for Oscillator {
    type Msg = Message;
    fn handle_message(&mut self, msg: Message) {
        match msg {
            SetFreq(freq) => {
                self.phase_delta = freq*2.0*PI/(SAMPLE_RATE as f32);
            },
            SetWaveform(waveform) => {
                self.waveform = waveform;
            },
            SetLFOIntensity(steps) => {
                self.lfo_intensity = steps/12.0;
            },
            SetTranspose(steps) => {
                self.transpose = 2.0.powf(steps/12.0);
            },
            SetBend(steps) => {
                self.bend = 2.0.powf(steps/12.0);
            },
        }
    }
}

impl AudioDevice for Oscillator {
    fn num_inputs(&self) -> usize {
        1
    }

    fn num_outputs(&self) -> usize {
        1
    }

    fn tick(&mut self, _: Time, inputs: &[Sample], outputs: &mut[Sample]) {
        // Tick the phase
        let phase_delta = if inputs.len() > 0 {
            self.phase_delta*2.0.powf(inputs[0]*self.lfo_intensity)
        } else {
            self.phase_delta
        } * self.bend * self.transpose;
        self.phase += phase_delta;
        if self.phase >= 2.0*PI {
            self.phase -= 2.0*PI;
        }

        // Compute the next sample
        self.last_sample = match self.waveform {
            Sine => self.phase.sin(),
            Saw(_) => {
                self.phase/PI -1.0 +
                    poly_blep(self.waveform, self.phase, phase_delta)
            },
            Square(_) => {
                (if self.phase < PI { 1.0 } else { -1.0 }) +
                    poly_blep(self.waveform, self.phase, phase_delta)
            },
            Tri(_) => {
                // Compute a square wave signal
                let out = (if self.phase < PI { 1.0 } else { -1.0 }) +
                    poly_blep(self.waveform, self.phase, phase_delta);

                // Perform leaky integration
                phase_delta*out + (1.0-phase_delta)*self.last_sample
            },
            WhiteNoise => 2.0*random::<f32>() - 1.0,
            PulseTrain => {
                // If we wrapped around...
                if self.phase < self.phase_delta { 1.0 } else { 0.0 }
            }
        };
        outputs[0] = self.last_sample;
    }
}


/// Computes the PolyBLEP step for a given waveform type. This should be added
/// to the naive waveform.
///
/// `waveform` should be the waveform we are antialiasing.
/// `phase` should be the current phase, from 0 to 2pi.
/// `phase_delta` should be the change in phase for one tick.
fn poly_blep(waveform: Waveform, phase: f32, phase_delta: f32) -> Sample {
    match waveform {
        Saw(PolyBlep) => {
            -1.0*poly_blep_offset(phase/(2.0*PI), phase_delta/(2.0*PI))
        },
        Square(PolyBlep) | Tri(PolyBlep) => {
            let t = phase/(2.0*PI);
            let dt = phase_delta/(2.0*PI);
            poly_blep_offset(t, dt) - poly_blep_offset((t+0.5) % 1.0, dt)
        },
        _ => 0.0
    }
}

/// Computes a single offset for PolyBLEP antialiasing.
///
/// `t` should be the current waveform phase, normalized.
/// `dt` should be the change in phase for one sample time, normalized.
fn poly_blep_offset(t: f32, dt: f32) -> f32 {
    if t < dt { // t ~= 0
        let t = t / dt;
        -t*t + 2.0*t - 1.0
    } else if t > 1.0-dt { // t ~= 1
        let t = (t-1.0) / dt;
        t*t + 2.0*t + 1.0
    } else {
        0.0
    }
}


#[cfg(test)]
mod test {
    use testing::flt_eq;
    use super::{Aliased, Oscillator, Waveform};
    use types::{AudioDevice, Sample, Time};

    const FREQ: f32 = 4410.0;
    fn get_one_cycle(osc: &mut Oscillator) -> [Sample; 10] {
        let mut output = [0.0; 10];
        for t in 0..10 {
            osc.tick(t as Time, &[], &mut output[t..t+1]);
        }
        output
    }

    fn check(actual: &[Sample], expected: &[Sample]) {
        println!("actual:\n    {:?}", actual);
        println!("expected:\n    {:?}", expected);
        assert_eq!(actual.len(), expected.len());
        for (a, e) in actual.iter().zip(expected) {
            assert!(flt_eq(*a, *e));
        }
    }

    #[test]
    fn test_naive_square() {
        let mut osc = Oscillator::new(Waveform::Square(Aliased)).freq(FREQ);
        check(&get_one_cycle(&mut osc),
              &[1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0]);
    }

    #[test]
    fn test_naive_saw() {
        let mut osc = Oscillator::new(Waveform::Saw(Aliased)).freq(FREQ);
        check(&get_one_cycle(&mut osc),
              &[-0.8, -0.6, -0.4, -0.2, 0.0, 0.2, 0.4, 0.6, 0.8, -1.0]);
    }

    #[test]
    fn test_pulse() {
        let mut osc = Oscillator::new(Waveform::PulseTrain).freq(FREQ);
        check(&get_one_cycle(&mut osc),
              &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]);
    }
}