From c9b46876611075d9e673fef6de78bf624b82bbb3 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 22 Jun 2020 06:24:39 +0200 Subject: [PATCH] Add support of various wavetable quality settings --- src/sfizz/Wavetables.cpp | 123 +++++++++++++++++++++++++++++++++++---- src/sfizz/Wavetables.h | 58 +++++++++++++++--- 2 files changed, 164 insertions(+), 17 deletions(-) diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index d13a8e7c..929735a1 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -6,6 +6,7 @@ #include "Wavetables.h" #include "FilePool.h" +#include "Interpolators.h" #include "MathHelpers.h" #include "absl/meta/type_traits.h" #include @@ -35,7 +36,8 @@ void WavetableOscillator::setPhase(float phase) _phase = phase; } -void WavetableOscillator::process(float frequency, float detuneRatio, float* output, unsigned nframes) +template +void WavetableOscillator::processSingle(float frequency, float detuneRatio, float* output, unsigned nframes) { float phase = _phase; float phaseInc = frequency * (detuneRatio * _sampleInterval); @@ -48,7 +50,7 @@ void WavetableOscillator::process(float frequency, float detuneRatio, float* out float position = phase * tableSize; unsigned index = static_cast(position); float frac = position - index; - output[i] = interpolate(&table[index], frac); + output[i] = interpolate(&table[index], frac); phase += phaseInc; phase -= static_cast(phase); @@ -57,7 +59,8 @@ void WavetableOscillator::process(float frequency, float detuneRatio, float* out _phase = phase; } -void WavetableOscillator::processModulated(const float* frequencies, float detuneRatio, float* output, unsigned nframes) +template +void WavetableOscillator::processModulatedSingle(const float* frequencies, float detuneRatio, float* output, unsigned nframes) { float phase = _phase; float sampleInterval = _sampleInterval; @@ -73,7 +76,7 @@ void WavetableOscillator::processModulated(const float* frequencies, float detun float position = phase * tableSize; unsigned index = static_cast(position); float frac = position - index; - output[i] = interpolate(&table[index], frac); + output[i] = interpolate(&table[index], frac); phase += phaseInc; phase -= static_cast(phase); @@ -82,9 +85,96 @@ void WavetableOscillator::processModulated(const float* frequencies, float detun _phase = phase; } -float WavetableOscillator::interpolate(const float* x, float delta) +template +void WavetableOscillator::processDual(float frequency, float detuneRatio, float* output, unsigned nframes) { - return x[0] + delta * (x[1] - x[0]); + float phase = _phase; + float phaseInc = frequency * (detuneRatio * _sampleInterval); + + const WavetableMulti& multi = *_multi; + unsigned tableSize = multi.tableSize(); + WavetableMulti::DualTable dt = multi.getInterpolationPairForFrequency(frequency); + + for (unsigned i = 0; i < nframes; ++i) { + float position = phase * tableSize; + unsigned index = static_cast(position); + float frac = position - index; + output[i] = + (1 - dt.delta) * interpolate(&dt.table1[index], frac) + + dt.delta * interpolate(&dt.table2[index], frac); + + phase += phaseInc; + phase -= static_cast(phase); + } + + _phase = phase; +} + +template +void WavetableOscillator::processModulatedDual(const float* frequencies, float detuneRatio, float* output, unsigned nframes) +{ + float phase = _phase; + float sampleInterval = _sampleInterval; + + const WavetableMulti& multi = *_multi; + unsigned tableSize = multi.tableSize(); + + for (unsigned i = 0; i < nframes; ++i) { + float frequency = frequencies[i]; + float phaseInc = frequency * (detuneRatio * sampleInterval); + + WavetableMulti::DualTable dt = multi.getInterpolationPairForFrequency(frequency); + + float position = phase * tableSize; + unsigned index = static_cast(position); + float frac = position - index; + output[i] = + (1 - dt.delta) * interpolate(&dt.table1[index], frac) + + dt.delta * interpolate(&dt.table2[index], frac); + + phase += phaseInc; + phase -= static_cast(phase); + } + + _phase = phase; +} + +void WavetableOscillator::process(float frequency, float detuneRatio, float* output, unsigned nframes) +{ + int quality = clamp(_quality, 0, 3); + + switch (quality) { + case 0: // supposed to be nearest according to book + // fall through + case 1: + processSingle(frequency, detuneRatio, output, nframes); + break; + case 2: + processSingle(frequency, detuneRatio, output, nframes); + break; + case 3: + processDual(frequency, detuneRatio, output, nframes); + break; + } +} + +void WavetableOscillator::processModulated(const float* frequencies, float detuneRatio, float* output, unsigned nframes) +{ + int quality = clamp(_quality, 0, 3); + + switch (quality) { + case 0: // supposed to be nearest according to book + // fall through + case 1: + processModulatedSingle(frequencies, detuneRatio, output, nframes); + break; + case 2: + processModulatedSingle(frequencies, detuneRatio, output, nframes); + break; + case 3: + processModulatedDual(frequencies, detuneRatio, output, nframes); + break; + } } //------------------------------------------------------------------------------ @@ -304,7 +394,7 @@ const WavetableMulti* WavetableMulti::getSilenceWavetable() void WavetableMulti::allocateStorage(unsigned tableSize) { - _multiData.resize((tableSize + _tableExtra) * numTables()); + _multiData.resize((tableSize + 2 * _tableExtra) * numTables()); _tableSize = tableSize; } @@ -315,9 +405,22 @@ void WavetableMulti::fillExtra() constexpr unsigned numTables = WavetableMulti::numTables(); for (unsigned m = 0; m < numTables; ++m) { - float* ptr = const_cast(getTablePointer(m)); - for (unsigned i = 0; i < tableExtra; ++i) - ptr[tableSize + i] = ptr[i % tableSize]; + float* beg = const_cast(getTablePointer(m)); + float* end = beg + tableSize; + // fill right + float* src = beg; + float* dst = end; + for (unsigned i = 0; i < tableExtra; ++i) { + *dst++ = *src; + src = (src + 1 != end) ? (src + 1) : beg; + } + // fill left + src = end - 1; + dst = beg - 1; + for (unsigned i = 0; i < tableExtra; ++i) { + *dst-- = *src; + src = (src != beg) ? (src - 1) : (end - 1); + } } } diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index e5a5df36..e2f26cfb 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -8,6 +8,7 @@ #include "Config.h" #include "LeakDetector.h" #include "Buffer.h" +#include "MathHelpers.h" #include #include #include @@ -18,6 +19,8 @@ class FilePool; class WavetableMulti; +enum InterpolatorModel : int; + /** An oscillator based on wavetables */ @@ -44,6 +47,16 @@ public: */ void setPhase(float phase); + /** + Set the quality of this oscillator. (cf. `oscillator_quality`) + + 0: nearest + 1: linear + 2: high + 3: dual-high + */ + void setQuality(int q) { _quality = q; } + /** Compute a cycle of the oscillator, with constant frequency. */ @@ -55,17 +68,23 @@ public: void processModulated(const float* frequencies, float detuneRatio, float* output, unsigned nframes); private: - /** - Interpolate a value from a part of table, with delta in 0 to 1 excluded. - There are `TableExtra` elements available for reading. - (cf. WavetableMulti) - */ - static float interpolate(const float* x, float delta); + // single-table interpolation + template + void processSingle(float frequency, float detuneRatio, float* output, unsigned nframes); + template + void processModulatedSingle(const float* frequencies, float detuneRatio, float* output, unsigned nframes); + + // dual-table interpolation + template + void processDual(float frequency, float detuneRatio, float* output, unsigned nframes); + template + void processModulatedDual(const float* frequencies, float detuneRatio, float* output, unsigned nframes); private: float _phase = 0.0f; float _sampleInterval = 0.0f; const WavetableMulti* _multi = nullptr; + int _quality = 1; LEAK_DETECTOR(WavetableOscillator); }; @@ -154,6 +173,31 @@ public: return getTable(WavetableRange::getOctaveForFrequency(freq)); } + // adjacent tables with interpolation factor between them + struct DualTable { + const float* table1; + const float* table2; + float delta; + }; + + // get the pair of tables at the fractional multisample position (range checked) + DualTable getInterpolationPair(float position) const + { + DualTable dt; + int index = static_cast(position); + dt.delta = position - index; + dt.table1 = getTablePointer(clamp(index, 0, WavetableRange::countOctaves - 1)); + dt.table2 = getTablePointer(clamp(index + 1, 0, WavetableRange::countOctaves - 1)); + return dt; + } + + // get the pair of tables for the given playback frequency (range checked) + DualTable getInterpolationPairForFrequency(float freq) const + { + float position = WavetableRange::getFractionalOctaveForFrequency(freq); + return getInterpolationPair(position); + } + // create a multisample according to a given harmonic profile // the reference sample rate is the minimum value accepted by the DSP // system (most defavorable wrt. aliasing) @@ -167,7 +211,7 @@ private: // get a pointer to the beginning of the N-th table const float* getTablePointer(unsigned index) const { - return _multiData.data() + index * (_tableSize + _tableExtra); + return _multiData.data() + index * (_tableSize + 2 * _tableExtra) + _tableExtra; } // allocate the internal data for tables of the given size