From ef0c96f72ac515a99d7f6534a27931c1d7bebc7a Mon Sep 17 00:00:00 2001 From: yaw-man Date: Fri, 16 Sep 2022 11:41:12 -0400 Subject: [PATCH] Shepard tone timbre is now a harmonic series (buzz). Fixed exception from custom tablet destructor. May need to check for a memory leak now. --- src/yaw-shepard/dsp.cpp | 4 +- src/yaw-shepard/synth.cpp | 95 ++++++++++++++++++++------------------ src/yaw-shepard/synth.h | 25 +++++----- src/yaw-shepard/tablet.h | 1 - src/yaw-shepard/wintab.cpp | 8 ++-- 5 files changed, 70 insertions(+), 63 deletions(-) diff --git a/src/yaw-shepard/dsp.cpp b/src/yaw-shepard/dsp.cpp index 25ffa1f..e03493a 100644 --- a/src/yaw-shepard/dsp.cpp +++ b/src/yaw-shepard/dsp.cpp @@ -83,10 +83,10 @@ protected: fParameters[idx] = val; switch (idx) { case ktpax: - synth.setFrequencyShift(val); + synth.setPhaseVelocity(val); break; case ktpay: - synth.setTargetRatio(val); + synth.setTimbre(val); break; case ktpaz: break; diff --git a/src/yaw-shepard/synth.cpp b/src/yaw-shepard/synth.cpp index 1dbeff3..2235572 100644 --- a/src/yaw-shepard/synth.cpp +++ b/src/yaw-shepard/synth.cpp @@ -7,12 +7,20 @@ Synth::Synth(double 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 double getAmplitude( double hz ) { - if( hz < 20.0 ) return 0.0; - if( hz < 440.0 ) return hz / 440.0; - if( hz < 10000.0 ) return (10000.0 - hz) / 9560.0; + 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; } @@ -21,6 +29,26 @@ static constexpr double getAmplitude( double hz ) 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() +{ + 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() +{ + 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; @@ -29,76 +57,53 @@ void Synth::process(float* output, const uint32_t frames) for(uint32_t i = 0; i < frames; i++){ //Set pitch. - hz = hzFund *= hzShift; + hz = hzFund = spectrumPhase * fMin + fMin; - //Set timbre. - ratio = ratioSlewRate * ratio + (1.0 - ratioSlewRate) * targetRatio; + bool isOddHarmonic = true; for(uint voice = 0; voice < NUM_VOICES; ++voice){ //Get new phase. - double phase = phases[(voice + idxFund) & VOICE_MASK]; + double phase = phases[voice]; phase += hz * sampleInterval; phase = frac(phase); //Anti-aliasing: don't bother rendering anything over the Nyquist rate. if( hz > hzNyq ) break; - output[i] += getAmplitude(hz) * sin(2.0 * M_PI * phase); + + output[i] += (!isOddHarmonic + isOddHarmonic * spectrumPhase * spectrumPhase) //Fade in odd harmonics. + * getAmplitude(hz) //Frequency response. + * sin(2.0 * M_PI * phase); //Additives. //Remember phase, move to higher overtone. - phases[(voice + idxFund) & VOICE_MASK] = phase; - hz *= ratio * (voice + 1.0) / (voice + 2.0); + phases[voice] = phase; + hz *= (voice + 2.0) / (voice + 1.0); + isOddHarmonic = !isOddHarmonic; } output[i] *= volume / static_cast(NUM_VOICES); + + //Wrapping. + spectrumPhase += spectrumPhaseVelocity * sampleInterval; + if( spectrumPhase > 1.0) shiftDown(); + if( spectrumPhase < 0.0) shiftUp(); } - //Make timbre cyclical. - if( hzShift < 1.0 ) - { - //Pitch low and decreasing, shift bottom voices to top. - while( getAmplitude(hz) > MIN_VOLUME - && getAmplitude(hzFund) < MIN_VOLUME) - { - ++idxFund; - hz *= 2.0 * ratio; - hzFund *= 2.0 * ratio; - } - } - - if( (hzShift > 1.0) || (targetRatio > ratio)) - { - //Pitch high and increasing. Shift top voices to bottom. - while( getAmplitude(hz) < MIN_VOLUME - && getAmplitude(hzFund) > MIN_VOLUME) - { - --idxFund; - hz /= 2.0 * ratio; - hzFund /= 2.0 * ratio; - } - } } void Synth::setSampleRate(double oldRate, double newRate){ sampleInterval = 1.0 / newRate; hzNyq = newRate / 2; - ratioSlewRate = pow(ratioSlewRate, oldRate / newRate); - hzShift = pow(hzShift, newRate / oldRate); } //Takes value from 0 to 1 representing frequency shift factor. //0 : lower one octave per second //1 : raise one octave per second -void Synth::setFrequencyShift(double in){ - hzShift = exp2(2.0 * (in - 0.5) * sampleInterval); +void Synth::setPhaseVelocity(double in){ + spectrumPhaseVelocity = 2.0 * (in - 0.5); } -static constexpr double minRatio = 1.0; -static constexpr double maxRatio = 1.05; -//Slew to given inharmonicity ratio. -//0 : next voice is exactly harmonic. -//1 : next voice is 5% inharmonic. -void Synth::setTargetRatio(double in){ - targetRatio = minRatio + in * (maxRatio - minRatio); +void Synth::setTimbre(double in){ + } void Synth::setVolume(double in){ diff --git a/src/yaw-shepard/synth.h b/src/yaw-shepard/synth.h index 15da8c3..a44d6ef 100644 --- a/src/yaw-shepard/synth.h +++ b/src/yaw-shepard/synth.h @@ -7,7 +7,7 @@ struct Voice double phase; }; -constexpr unsigned int NUM_VOICES = 64; +constexpr unsigned int NUM_VOICES = 256; constexpr unsigned int VOICE_MASK = NUM_VOICES - 1; class Synth @@ -15,23 +15,26 @@ class Synth public: explicit Synth(double sampleRate); void process(float *output, uint32_t frames); - void setFrequencyShift(double in); - void setTargetRatio(double in); + void setPhaseVelocity(double in); + void setTimbre(double in); void setSampleRate(double oldRate, double newRate); void setVolume(double in); + void shiftUp(); + void shiftDown(); private: //Phase must persist between run invocation. - //Phase of each voice, considered as a period function on the unit interval. + //Phase of each voice, considered a function on the unit circle. std::array phases = {0}; - //Index of lowest voice in phase array. - unsigned int idxFund = 0; float volume = 0.0f; - double hzFund = 32.7; - double hzShift = 1.0; + //Parameter in unit circle controlling pitch (varies by one octave). + double spectrumPhase = 1.0f; + //Revolutions per second of spectrum phase. + double spectrumPhaseVelocity = 0.0; + //Lowest fundamental frequency of blit. + static inline constexpr double fMin = 55.0; + //Current fundamental frequency of blit. + double hzFund = fMin; double hzNyq = 24000.0; - double ratio = 2.0; - double targetRatio = 2.0; - double ratioSlewRate = 0.99999; double sampleInterval = 1.0 / 48000.0; }; \ No newline at end of file diff --git a/src/yaw-shepard/tablet.h b/src/yaw-shepard/tablet.h index 3258e1e..46348ad 100644 --- a/src/yaw-shepard/tablet.h +++ b/src/yaw-shepard/tablet.h @@ -30,7 +30,6 @@ struct Packet { class Tablet { public: Tablet(uintptr_t handle); - ~Tablet(); bool GetPacket( Packet &pkt ); bool initialized; std::string errormsg = ""; diff --git a/src/yaw-shepard/wintab.cpp b/src/yaw-shepard/wintab.cpp index d1ff0b2..d0b443c 100644 --- a/src/yaw-shepard/wintab.cpp +++ b/src/yaw-shepard/wintab.cpp @@ -18,10 +18,10 @@ Tablet::Tablet(uintptr_t handle) NewContext(hwnd); } -Tablet::~Tablet() { - if (hctx) { gpWTClose(hctx); } +/*Tablet::~Tablet() { + // if (hctx) { gpWTClose(hctx); } UnloadWintab(); -} +}*/ bool Tablet::GetPacket( Packet& packet ) { //Serial number of newest packet. @@ -54,7 +54,7 @@ void Tablet::NewContext(HWND hwnd) { AXIS TabletZ = { 0 }; AXIS TabletPressure = { 0 }; gpWTInfoA(WTI_DEFCONTEXT, 0, &ctx); - ctx.lcOptions |= CXO_MESSAGES; //TODO: checker çela + ctx.lcOptions |= CXO_MESSAGES; //TODO: checker �ela ctx.lcPktData = PACKETDATA; ctx.lcPktMode = 0;