From ca9e6acbe8b1b0afb1b786032a2c0aeb5034e6fa Mon Sep 17 00:00:00 2001 From: yaw-man Date: Thu, 22 Sep 2022 08:32:17 -0400 Subject: [PATCH] Revert to earlier timbre. --- src/yaw-shepard/dsp.cpp | 2 +- src/yaw-shepard/synth.cpp | 51 ++++++++++++++++++--------------------- src/yaw-shepard/synth.h | 17 ++++++------- src/yaw-shepard/ui.cpp | 2 +- 4 files changed, 33 insertions(+), 39 deletions(-) diff --git a/src/yaw-shepard/dsp.cpp b/src/yaw-shepard/dsp.cpp index ded043e..c3629d1 100644 --- a/src/yaw-shepard/dsp.cpp +++ b/src/yaw-shepard/dsp.cpp @@ -93,7 +93,7 @@ protected: synth.setPhaseVelocity(val); break; case ktpay: - synth.setTimbre(val); + synth.setPitchOffset(val); break; case ktpaz: break; diff --git a/src/yaw-shepard/synth.cpp b/src/yaw-shepard/synth.cpp index 4ef87ac..523f9f4 100644 --- a/src/yaw-shepard/synth.cpp +++ b/src/yaw-shepard/synth.cpp @@ -11,7 +11,7 @@ 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 double getAmplitude( double hz ) +static constexpr inline float getAmplitude( double hz ) { if( hz < amin ) return 0.0; if( hz < apiq ) { @@ -24,8 +24,13 @@ static constexpr double getAmplitude( double hz ) 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. -//Voice index code will loop infinitely if these fail. static_assert( MIN_VOLUME > getAmplitude(10.0) ); static_assert( MIN_VOLUME > getAmplitude(20000.0)); @@ -33,54 +38,45 @@ static_assert( MIN_VOLUME > getAmplitude(20000.0)); //Even overtones become plain overtones. void Synth::shiftUp() { + tablePhase *= 2.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. //Overtones become even overtones. void Synth::shiftDown() { + tablePhase /= 2.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) { - double hz; - //Render. for(uint32_t i = 0; i < frames; i++){ //Set pitch. - hz = hzFund = exp2(spectrumPhase) * fMin; + hzFund = exp2(spectrumPhase) * fMin; + + tablePhase += hzFund * sampleInterval; + tablePhase = frac(tablePhase); bool isOddHarmonic = true; 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. - if( hz > hzNyq ) break; + if( hzFund * (voice + 1.0) > hzNyq ) break; - output[i] += (!isOddHarmonic + isOddHarmonic * spectrumPhase * spectrumPhase) //Fade in odd harmonics. - * getAmplitude(hz) //Frequency response. - * sin(2.0 * M_PI * phase); //Additives. + output[i] += (!isOddHarmonic + isOddHarmonic * smooth(spectrumPhase)) //Fade in odd harmonics. + * getAmplitude(hzFund * (voice + 1.0)) //Frequency response. + * sinf(static_cast(voicePhase)); //Additives. - //Remember phase, move to higher overtone. - phases[voice] = phase; - hz *= (voice + 2.0) / (voice + 1.0); isOddHarmonic = !isOddHarmonic; } - output[i] *= 32 * volume / static_cast(NUM_VOICES); + output[i] *= fMin * volume / static_cast(8 * NUM_VOICES); //Wrapping. spectrumPhase += spectrumPhaseVelocity * sampleInterval; @@ -95,7 +91,7 @@ void Synth::setSampleRate(double oldRate, double newRate){ 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 //1 : raise one octave per second void Synth::setPhaseVelocity(double in){ @@ -103,11 +99,12 @@ void Synth::setPhaseVelocity(double in){ } //Vary pitch of fundamental in a non-wrapping kinda way. -//Current range is six octaves, current bottom is 110.0 Hz (A3) -void Synth::setTimbre(double in){ - fMin = exp2(in * 2.0) * 110.0; +//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; } \ No newline at end of file diff --git a/src/yaw-shepard/synth.h b/src/yaw-shepard/synth.h index 9c2131a..90250b3 100644 --- a/src/yaw-shepard/synth.h +++ b/src/yaw-shepard/synth.h @@ -1,12 +1,6 @@ #include "DistrhoPlugin.hpp" #include -struct Voice -{ - double hz; - double phase; -}; - constexpr unsigned int NUM_VOICES = 512; constexpr unsigned int VOICE_MASK = NUM_VOICES - 1; @@ -16,7 +10,7 @@ public: explicit Synth(double sampleRate); void process(float *output, uint32_t frames); void setPhaseVelocity(double in); - void setTimbre(double in); + void setPitchOffset(double in); void setSampleRate(double oldRate, double newRate); void setVolume(double in); void shiftUp(); @@ -26,9 +20,9 @@ public: double hzFund = fMin; private: - //Phase must persist between run invocation. - //Phase of each voice, considered a function on the unit circle. - std::array phases = {0}; + //Phase of wavetable. + double tablePhase = 0.0; + double fmPhase = 0.0; float volume = 0.0f; //Parameter in unit circle controlling pitch (varies by one octave). double spectrumPhase = 1.0f; @@ -37,6 +31,9 @@ private: //Lowest fundamental frequency of blit. double fMin = 55.0; + //Parameter stretching the overtone series. + double inharmonicity = 1.001; + double hzNyq = 24000.0; double sampleInterval = 1.0 / 48000.0; }; \ No newline at end of file diff --git a/src/yaw-shepard/ui.cpp b/src/yaw-shepard/ui.cpp index 45160c9..2e37120 100644 --- a/src/yaw-shepard/ui.cpp +++ b/src/yaw-shepard/ui.cpp @@ -177,7 +177,7 @@ void TabUI::onNanoDisplay() beginPath(); fillColor(200, 200, 200); textBox(0.f, 15.f, 250.f, - std::format("Frequency: {:.3f}\nNearest: {}\n", + std::format("Frequency: {:.3f}\nNearest: {:.3f}\n", hz, scale.getNearestNoteNumber(hz)) .c_str(), nullptr);