Add support of various wavetable quality settings
This commit is contained in:
parent
4acb4cd7f7
commit
c9b4687661
2 changed files with 164 additions and 17 deletions
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "Wavetables.h"
|
||||
#include "FilePool.h"
|
||||
#include "Interpolators.h"
|
||||
#include "MathHelpers.h"
|
||||
#include "absl/meta/type_traits.h"
|
||||
#include <kiss_fftr.h>
|
||||
|
|
@ -35,7 +36,8 @@ void WavetableOscillator::setPhase(float phase)
|
|||
_phase = phase;
|
||||
}
|
||||
|
||||
void WavetableOscillator::process(float frequency, float detuneRatio, float* output, unsigned nframes)
|
||||
template <InterpolatorModel M>
|
||||
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<unsigned>(position);
|
||||
float frac = position - index;
|
||||
output[i] = interpolate(&table[index], frac);
|
||||
output[i] = interpolate<M>(&table[index], frac);
|
||||
|
||||
phase += phaseInc;
|
||||
phase -= static_cast<int>(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 <InterpolatorModel M>
|
||||
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<unsigned>(position);
|
||||
float frac = position - index;
|
||||
output[i] = interpolate(&table[index], frac);
|
||||
output[i] = interpolate<M>(&table[index], frac);
|
||||
|
||||
phase += phaseInc;
|
||||
phase -= static_cast<int>(phase);
|
||||
|
|
@ -82,9 +85,96 @@ void WavetableOscillator::processModulated(const float* frequencies, float detun
|
|||
_phase = phase;
|
||||
}
|
||||
|
||||
float WavetableOscillator::interpolate(const float* x, float delta)
|
||||
template <InterpolatorModel M>
|
||||
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<unsigned>(position);
|
||||
float frac = position - index;
|
||||
output[i] =
|
||||
(1 - dt.delta) * interpolate<M>(&dt.table1[index], frac) +
|
||||
dt.delta * interpolate<M>(&dt.table2[index], frac);
|
||||
|
||||
phase += phaseInc;
|
||||
phase -= static_cast<int>(phase);
|
||||
}
|
||||
|
||||
_phase = phase;
|
||||
}
|
||||
|
||||
template <InterpolatorModel M>
|
||||
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<unsigned>(position);
|
||||
float frac = position - index;
|
||||
output[i] =
|
||||
(1 - dt.delta) * interpolate<M>(&dt.table1[index], frac) +
|
||||
dt.delta * interpolate<M>(&dt.table2[index], frac);
|
||||
|
||||
phase += phaseInc;
|
||||
phase -= static_cast<int>(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<kInterpolatorLinear>(frequency, detuneRatio, output, nframes);
|
||||
break;
|
||||
case 2:
|
||||
processSingle<kInterpolatorBspline3>(frequency, detuneRatio, output, nframes);
|
||||
break;
|
||||
case 3:
|
||||
processDual<kInterpolatorBspline3>(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<kInterpolatorLinear>(frequencies, detuneRatio, output, nframes);
|
||||
break;
|
||||
case 2:
|
||||
processModulatedSingle<kInterpolatorBspline3>(frequencies, detuneRatio, output, nframes);
|
||||
break;
|
||||
case 3:
|
||||
processModulatedDual<kInterpolatorBspline3>(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<float*>(getTablePointer(m));
|
||||
for (unsigned i = 0; i < tableExtra; ++i)
|
||||
ptr[tableSize + i] = ptr[i % tableSize];
|
||||
float* beg = const_cast<float*>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "Config.h"
|
||||
#include "LeakDetector.h"
|
||||
#include "Buffer.h"
|
||||
#include "MathHelpers.h"
|
||||
#include <absl/types/span.h>
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
#include <memory>
|
||||
|
|
@ -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 <InterpolatorModel M>
|
||||
void processSingle(float frequency, float detuneRatio, float* output, unsigned nframes);
|
||||
template <InterpolatorModel M>
|
||||
void processModulatedSingle(const float* frequencies, float detuneRatio, float* output, unsigned nframes);
|
||||
|
||||
// dual-table interpolation
|
||||
template <InterpolatorModel M>
|
||||
void processDual(float frequency, float detuneRatio, float* output, unsigned nframes);
|
||||
template <InterpolatorModel M>
|
||||
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<int>(position);
|
||||
dt.delta = position - index;
|
||||
dt.table1 = getTablePointer(clamp<int>(index, 0, WavetableRange::countOctaves - 1));
|
||||
dt.table2 = getTablePointer(clamp<int>(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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue