Revert to earlier timbre.

This commit is contained in:
yaw-man 2022-09-22 08:32:17 -04:00
parent bca9e0381d
commit ca9e6acbe8
4 changed files with 33 additions and 39 deletions

View File

@ -93,7 +93,7 @@ protected:
synth.setPhaseVelocity(val); synth.setPhaseVelocity(val);
break; break;
case ktpay: case ktpay:
synth.setTimbre(val); synth.setPitchOffset(val);
break; break;
case ktpaz: case ktpaz:
break; break;

View File

@ -11,7 +11,7 @@ static constexpr double amin = 50;
static constexpr double apiq = 500; static constexpr double apiq = 500;
static constexpr double amax = 10000; static constexpr double amax = 10000;
//Volume of voice as a function of sample rate independent frequency. //Volume of voice as a function of sample rate independent frequency.
static constexpr double getAmplitude( double hz ) static constexpr inline float getAmplitude( double hz )
{ {
if( hz < amin ) return 0.0; if( hz < amin ) return 0.0;
if( hz < apiq ) { if( hz < apiq ) {
@ -24,8 +24,13 @@ static constexpr double getAmplitude( double hz )
return 0.0; 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. //Sanity checks: voices should become silent outside audible frequencies.
//Voice index code will loop infinitely if these fail.
static_assert( MIN_VOLUME > getAmplitude(10.0) ); static_assert( MIN_VOLUME > getAmplitude(10.0) );
static_assert( MIN_VOLUME > getAmplitude(20000.0)); static_assert( MIN_VOLUME > getAmplitude(20000.0));
@ -33,54 +38,45 @@ static_assert( MIN_VOLUME > getAmplitude(20000.0));
//Even overtones become plain overtones. //Even overtones become plain overtones.
void Synth::shiftUp() void Synth::shiftUp()
{ {
tablePhase *= 2.0;
spectrumPhase += 1.0; spectrumPhase += 1.0;
for(uint voice = 0; voice < NUM_VOICES; ++voice){
phases[voice] = phases[(2 * voice + 1) & VOICE_MASK];
}
} }
//New fundamental is half as high as old fundamental. //New fundamental is half as high as old fundamental.
//Overtones become even overtones. //Overtones become even overtones.
void Synth::shiftDown() void Synth::shiftDown()
{ {
tablePhase /= 2.0;
spectrumPhase -= 1.0; spectrumPhase -= 1.0;
for(uint voice = NUM_VOICES / 2 - 1; voice < NUM_VOICES; --voice){
phases[voice * 2 + 1] = phases[voice];
}
} }
void Synth::process(float* output, const uint32_t frames) void Synth::process(float* output, const uint32_t frames)
{ {
double hz;
//Render. //Render.
for(uint32_t i = 0; i < frames; i++){ for(uint32_t i = 0; i < frames; i++){
//Set pitch. //Set pitch.
hz = hzFund = exp2(spectrumPhase) * fMin; hzFund = exp2(spectrumPhase) * fMin;
tablePhase += hzFund * sampleInterval;
tablePhase = frac(tablePhase);
bool isOddHarmonic = true; bool isOddHarmonic = true;
for(uint voice = 0; voice < NUM_VOICES; ++voice){ for(uint voice = 0; voice < NUM_VOICES; ++voice){
//Get new phase.
double phase = phases[voice];
phase += hz * sampleInterval;
phase = frac(phase);
double voicePhase = 2.0 * M_PI * tablePhase * (voice + 1.0);
//Anti-aliasing: don't bother rendering anything over the Nyquist rate. //Anti-aliasing: don't bother rendering anything over the Nyquist rate.
if( hz > hzNyq ) break; if( hzFund * (voice + 1.0) > hzNyq ) break;
output[i] += (!isOddHarmonic + isOddHarmonic * spectrumPhase * spectrumPhase) //Fade in odd harmonics. output[i] += (!isOddHarmonic + isOddHarmonic * smooth(spectrumPhase)) //Fade in odd harmonics.
* getAmplitude(hz) //Frequency response. * getAmplitude(hzFund * (voice + 1.0)) //Frequency response.
* sin(2.0 * M_PI * phase); //Additives. * sinf(static_cast<float>(voicePhase)); //Additives.
//Remember phase, move to higher overtone.
phases[voice] = phase;
hz *= (voice + 2.0) / (voice + 1.0);
isOddHarmonic = !isOddHarmonic; isOddHarmonic = !isOddHarmonic;
} }
output[i] *= 32 * volume / static_cast<double>(NUM_VOICES); output[i] *= fMin * volume / static_cast<float>(8 * NUM_VOICES);
//Wrapping. //Wrapping.
spectrumPhase += spectrumPhaseVelocity * sampleInterval; spectrumPhase += spectrumPhaseVelocity * sampleInterval;
@ -95,7 +91,7 @@ void Synth::setSampleRate(double oldRate, double newRate){
hzNyq = newRate / 2; hzNyq = newRate / 2;
} }
//Takes value from 0 to 1 representing frequency shift factor. //Takes value from 0 to 1 representing rate of wrapping pitch increase.
//0 : lower one octave per second //0 : lower one octave per second
//1 : raise one octave per second //1 : raise one octave per second
void Synth::setPhaseVelocity(double in){ void Synth::setPhaseVelocity(double in){
@ -103,11 +99,12 @@ void Synth::setPhaseVelocity(double in){
} }
//Vary pitch of fundamental in a non-wrapping kinda way. //Vary pitch of fundamental in a non-wrapping kinda way.
//Current range is six octaves, current bottom is 110.0 Hz (A3) //Current range is six octaves, current bottom is 13.75 Hz (A0)
void Synth::setTimbre(double in){ void Synth::setPitchOffset(double in){
fMin = exp2(in * 2.0) * 110.0; fMin = exp2(in * 6.0) * 13.75;
} }
void Synth::setVolume(double in){ void Synth::setVolume(double in){
volume = in; volume = in;
} }

View File

@ -1,12 +1,6 @@
#include "DistrhoPlugin.hpp" #include "DistrhoPlugin.hpp"
#include <array> #include <array>
struct Voice
{
double hz;
double phase;
};
constexpr unsigned int NUM_VOICES = 512; constexpr unsigned int NUM_VOICES = 512;
constexpr unsigned int VOICE_MASK = NUM_VOICES - 1; constexpr unsigned int VOICE_MASK = NUM_VOICES - 1;
@ -16,7 +10,7 @@ public:
explicit Synth(double sampleRate); explicit Synth(double sampleRate);
void process(float *output, uint32_t frames); void process(float *output, uint32_t frames);
void setPhaseVelocity(double in); void setPhaseVelocity(double in);
void setTimbre(double in); void setPitchOffset(double in);
void setSampleRate(double oldRate, double newRate); void setSampleRate(double oldRate, double newRate);
void setVolume(double in); void setVolume(double in);
void shiftUp(); void shiftUp();
@ -26,9 +20,9 @@ public:
double hzFund = fMin; double hzFund = fMin;
private: private:
//Phase must persist between run invocation. //Phase of wavetable.
//Phase of each voice, considered a function on the unit circle. double tablePhase = 0.0;
std::array<double, NUM_VOICES> phases = {0}; double fmPhase = 0.0;
float volume = 0.0f; float volume = 0.0f;
//Parameter in unit circle controlling pitch (varies by one octave). //Parameter in unit circle controlling pitch (varies by one octave).
double spectrumPhase = 1.0f; double spectrumPhase = 1.0f;
@ -37,6 +31,9 @@ private:
//Lowest fundamental frequency of blit. //Lowest fundamental frequency of blit.
double fMin = 55.0; double fMin = 55.0;
//Parameter stretching the overtone series.
double inharmonicity = 1.001;
double hzNyq = 24000.0; double hzNyq = 24000.0;
double sampleInterval = 1.0 / 48000.0; double sampleInterval = 1.0 / 48000.0;
}; };

View File

@ -177,7 +177,7 @@ void TabUI::onNanoDisplay()
beginPath(); beginPath();
fillColor(200, 200, 200); fillColor(200, 200, 200);
textBox(0.f, 15.f, 250.f, textBox(0.f, 15.f, 250.f,
std::format("Frequency: {:.3f}\nNearest: {}\n", std::format("Frequency: {:.3f}\nNearest: {:.3f}\n",
hz, scale.getNearestNoteNumber(hz)) hz, scale.getNearestNoteNumber(hz))
.c_str(), .c_str(),
nullptr); nullptr);