This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
|
the_synthesizer [2017/08/30 21:22] shane [SynthVoice] |
the_synthesizer [2017/08/30 23:49] (current) shane [SynthOscillator] |
||
|---|---|---|---|
| Line 138: | Line 138: | ||
| ===== SynthOscillator ===== | ===== SynthOscillator ===== | ||
| + | VanillaJuce' | ||
| + | <code cpp> | ||
| + | class SynthOscillator | ||
| + | { | ||
| + | private: | ||
| + | SynthOscillatorWaveform waveForm; | ||
| + | double phase; | ||
| + | double phaseDelta; | ||
| + | |||
| + | public: | ||
| + | SynthOscillator(); | ||
| + | | ||
| + | void setWaveform(SynthOscillatorWaveform wf) { waveForm = wf; } | ||
| + | void setFrequency(double cyclesPerSample); | ||
| + | |||
| + | float getSample (); | ||
| + | }; | ||
| + | |||
| + | SynthOscillator:: | ||
| + | : waveForm(kSawtooth) | ||
| + | , phase(0) | ||
| + | , phaseDelta(0) | ||
| + | { | ||
| + | } | ||
| + | |||
| + | void SynthOscillator:: | ||
| + | { | ||
| + | phaseDelta = cyclesPerSample; | ||
| + | } | ||
| + | |||
| + | float SynthOscillator:: | ||
| + | { | ||
| + | float sample = 0.0f; | ||
| + | switch (waveForm) | ||
| + | { | ||
| + | case kSine: | ||
| + | sample = (float)(std:: | ||
| + | break; | ||
| + | case kSquare: | ||
| + | sample = (phase <= 0.5) ? 1.0f : -1.0f; | ||
| + | break; | ||
| + | case kTriangle: | ||
| + | sample = (float)(2.0 * (0.5 - std:: | ||
| + | break; | ||
| + | case kSawtooth: | ||
| + | sample = (float)(2.0 * phase - 1.0); | ||
| + | break; | ||
| + | } | ||
| + | |||
| + | phase += phaseDelta; | ||
| + | while (phase > 1.0) phase -= 1.0; | ||
| + | |||
| + | return sample; | ||
| + | } | ||
| + | </ | ||
| + | Every time // | ||
| + | |||
| + | This simplistic code is acceptable for low-frequency oscillators (LFOs), but it's not good enough for audio-frequency oscillators, | ||
| + | |||
| + | VanillaJuce is essentially an early iteration of a project which was eventually renamed [[sarah|SARAH]] (// | ||
| ===== SynthEnvelopeGenerator ===== | ===== SynthEnvelopeGenerator ===== | ||
| + | The // | ||
| + | * The Attack phase always ramps from 0.0 up to 1.0. | ||
| + | * The Sustain level is some fraction in the range 0.0 to 1.0. | ||
| + | * The Release phase ramps from the Sustain level back to 0.0. | ||
| + | |||
| + | Here is the class declaration for // | ||
| + | <code cpp> | ||
| + | typedef enum | ||
| + | { | ||
| + | kIdle, | ||
| + | kAttack, | ||
| + | kDecay, | ||
| + | kSustain, | ||
| + | kRelease | ||
| + | } EG_Segment; | ||
| + | |||
| + | class SynthEnvelopeGenerator | ||
| + | { | ||
| + | private: | ||
| + | double sampleRateHz; | ||
| + | LinearSmoothedValue< | ||
| + | EG_Segment segment; | ||
| + | |||
| + | public: | ||
| + | double attackSeconds, | ||
| + | double sustainLevel; | ||
| + | |||
| + | public: | ||
| + | SynthEnvelopeGenerator(); | ||
| + | |||
| + | void start(double _sampleRateHz); | ||
| + | void release(); | ||
| + | bool isRunning() { return segment != kIdle; } | ||
| + | float getSample (); | ||
| + | }; | ||
| + | </ | ||
| + | // | ||
| + | It has several member functions: the following four of which are used in // | ||
| + | * // | ||
| + | * //reset()// takes a sampling rate in Hz and a ramp time in seconds, and prepares the interpolator. | ||
| + | * // | ||
| + | * // | ||
| + | |||
| + | Unfortunately, | ||
| + | <code cpp> | ||
| + | SynthEnvelopeGenerator:: | ||
| + | : sampleRateHz(44100) | ||
| + | , attackSeconds(0.01) | ||
| + | , decaySeconds(0.1) | ||
| + | , releaseSeconds(0.5) | ||
| + | , sustainLevel(0.5) | ||
| + | , segment(kIdle) | ||
| + | { | ||
| + | interpolator.setValue(0.0); | ||
| + | interpolator.reset(sampleRateHz, | ||
| + | } | ||
| + | |||
| + | void SynthEnvelopeGenerator:: | ||
| + | { | ||
| + | sampleRateHz = _sampleRateHz; | ||
| + | |||
| + | if (segment == kIdle) | ||
| + | { | ||
| + | // start new attack segment from zero | ||
| + | interpolator.setValue(0.0); | ||
| + | interpolator.reset(sampleRateHz, | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | // note is still playing but has been retriggered or stolen | ||
| + | // start new attack from where we are | ||
| + | double currentValue = interpolator.getNextValue(); | ||
| + | interpolator.setValue(currentValue); | ||
| + | interpolator.reset(sampleRateHz, | ||
| + | } | ||
| + | |||
| + | segment = kAttack; | ||
| + | interpolator.setValue(1.0); | ||
| + | } | ||
| + | |||
| + | void SynthEnvelopeGenerator:: | ||
| + | { | ||
| + | segment = kRelease; | ||
| + | interpolator.setValue(interpolator.getNextValue()); | ||
| + | interpolator.reset(sampleRateHz, | ||
| + | interpolator.setValue(0.0); | ||
| + | } | ||
| + | |||
| + | float SynthEnvelopeGenerator:: | ||
| + | { | ||
| + | if (segment == kSustain) return float(sustainLevel); | ||
| + | |||
| + | if (interpolator.isSmoothing()) return float(interpolator.getNextValue()); | ||
| + | |||
| + | if (segment == kAttack) | ||
| + | { | ||
| + | if (decaySeconds > 0.0) | ||
| + | { | ||
| + | // there is a decay segment | ||
| + | segment = kDecay; | ||
| + | interpolator.reset(sampleRateHz, | ||
| + | interpolator.setValue(sustainLevel); | ||
| + | return 1.0; | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | // no decay segment; go straight to sustain | ||
| + | segment = kSustain; | ||
| + | return float(sustainLevel); | ||
| + | } | ||
| + | } | ||
| + | else if (segment == kDecay) | ||
| + | { | ||
| + | segment = kSustain; | ||
| + | return float(sustainLevel); | ||
| + | } | ||
| + | else if (segment == kRelease) | ||
| + | { | ||
| + | segment = kIdle; | ||
| + | } | ||
| + | |||
| + | // after end of release segment | ||
| + | return 0.0f; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Remember where I talked about how // | ||
| ===== Summary: What happens when you play a note ===== | ===== Summary: What happens when you play a note ===== | ||
| + | When the VanillaJuce plugin is compiled and instantiated in a DAW, and the user presses down a note on a MIDI keyboard (or the same sequence of MIDI-events occurs during the playback of a recorded MIDI sequence), the following things happen: | ||
| + | - The // | ||
| + | - The // | ||
| + | - The // | ||
| + | - Because the // | ||
| + | - With each successive sample, the envelope generator is advanced through the ADSR shape. | ||
| + | |||
| + | When the MIDI note-off event occurs in the MIDI input sequence: | ||
| + | - // | ||
| + | - The // | ||
| + | - The // | ||
| + | - Eventually, the test of // | ||
| + | |||
| + | There are two special voice-assignment scenarios you should be aware of. The first one, //note reassignment// | ||
| + | |||
| + | The second special case concerns what happens when the synthesizer runs out of voices. That case is actually almost identical to the first one; the only difference is how the // | ||