110 lines
3.1 KiB
C++
110 lines
3.1 KiB
C++
#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<float>(voicePhase)); //Additives.
|
|
|
|
isOddHarmonic = !isOddHarmonic;
|
|
}
|
|
|
|
output[i] *= fMin * volume / static_cast<float>(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;
|
|
} |