#include "synth.h" Synth::Synth(double sampleRate){ setSampleRate(48000.0, sampleRate); } #define frac(x) ((x) - ((long)x)) constexpr double MIN_VOLUME = 0.00001; static constexpr double amin = 50; static constexpr double apiq = 500; static constexpr double amax = 10000; //Volume of voice as a function of sample rate independent frequency. static constexpr inline float getAmplitude( double hz ) { if( hz < amin ) return 0.0; if( hz < apiq ) { double a = (hz - amin) / (apiq - amin); return a * a;} if( hz < amax ) { double a = 1.0 - (hz - apiq) / (amax - apiq); return a * a; } return 0.0; } //Interpolation function on unit interval with good regularity as a function on S^1. static constexpr inline float smooth( double x ) { return x * x * x;//(3.0 * x - 2.0); } //Sanity checks: voices should become silent outside audible frequencies. static_assert( MIN_VOLUME > getAmplitude(10.0) ); static_assert( MIN_VOLUME > getAmplitude(20000.0)); //New fundamental is twice as high as old fundamental. //Even overtones become plain overtones. void Synth::shiftUp() { tablePhase *= 2.0; spectrumPhase += 1.0; } //New fundamental is half as high as old fundamental. //Overtones become even overtones. void Synth::shiftDown() { tablePhase /= 2.0; spectrumPhase -= 1.0; } void Synth::process(float* output, const uint32_t frames) { //Render. for(uint32_t i = 0; i < frames; i++){ //Set pitch. hzFund = exp2(spectrumPhase) * fMin; tablePhase += hzFund * sampleInterval; tablePhase = frac(tablePhase); bool isOddHarmonic = true; for(uint voice = 0; voice < NUM_VOICES; ++voice){ double voicePhase = 2.0 * M_PI * tablePhase * (voice + 1.0); //Anti-aliasing: don't bother rendering anything over the Nyquist rate. if( hzFund * (voice + 1.0) > hzNyq ) break; output[i] += (!isOddHarmonic + isOddHarmonic * smooth(spectrumPhase)) //Fade in odd harmonics. * getAmplitude(hzFund * (voice + 1.0)) //Frequency response. * sinf(static_cast(voicePhase)); //Additives. isOddHarmonic = !isOddHarmonic; } output[i] *= fMin * volume / static_cast(8 * NUM_VOICES); //Wrapping. spectrumPhase += spectrumPhaseVelocity * sampleInterval; if( spectrumPhase > 1.0) shiftDown(); if( spectrumPhase < 0.0) shiftUp(); } } void Synth::setSampleRate(double oldRate, double newRate){ sampleInterval = 1.0 / newRate; hzNyq = newRate / 2; } //Takes value from 0 to 1 representing rate of wrapping pitch increase. //0 : lower one octave per second //1 : raise one octave per second void Synth::setPhaseVelocity(double in){ spectrumPhaseVelocity = 2.0 * (in - 0.5); } //Vary pitch of fundamental in a non-wrapping kinda way. //Current range is six octaves, current bottom is 13.75 Hz (A0) void Synth::setPitchOffset(double in){ fMin = exp2(in * 6.0) * 13.75; } void Synth::setVolume(double in){ volume = in; }