71 assert(sampleRate > 0.0);
72 if (!(sampleRate > 0.0))
return;
74 sampleRate_ = sampleRate;
75 phasor_.prepare(sampleRate);
97 return (harmonic % 2 == 0) ? T(-1.0 / harmonic) : T(1.0 / harmonic);
108 return (harmonic % 2 == 0) ? T(0) : T(1.0 / harmonic);
119 if (harmonic % 2 == 0)
return T(0);
120 T sign = ((harmonic / 2) % 2 == 0) ? T(1) : T(-1);
121 return sign /
static_cast<T
>(harmonic * harmonic);
137 T phase = twoPi<T> *
static_cast<T
>(i) /
static_cast<T
>(
kTableSize);
138 mipData_[
static_cast<size_t>(i)] = std::sin(phase);
151 template <
typename HarmonicFunc>
155 mipData_.assign(
static_cast<size_t>(numMipLevels_ *
kTableSize), T(0));
159 for (
int level = 0; level < numMipLevels_; ++level)
164 int maxHarmonics = 1 << (numMipLevels_ - 1 - level);
165 maxHarmonics = std::clamp(maxHarmonics, 1, maxTableHarmonics);
167 size_t offset =
static_cast<size_t>(level *
kTableSize);
169 for (
int h = 1; h <= maxHarmonics; ++h)
171 T amplitude = harmonicFunc(h);
172 if (amplitude == T(0))
continue;
176 T phase = twoPi<T> *
static_cast<T
>(h) *
static_cast<T
>(i) /
static_cast<T
>(
kTableSize);
177 mipData_[offset +
static_cast<size_t>(i)] += amplitude * std::sin(phase);
188 normalizeAllLevelsGlobally();
203 if (!data || size <= 0)
return;
210 int rawHarmonicLimit = (size - 1) / 2;
212 int maxHarmonics = std::min(rawHarmonicLimit, targetNyquistLimit);
213 if (maxHarmonics < 1)
return;
215 std::vector<T> cosCoeffs(
static_cast<size_t>(maxHarmonics + 1), T(0));
216 std::vector<T> sinCoeffs(
static_cast<size_t>(maxHarmonics + 1), T(0));
218 for (
int h = 1; h <= maxHarmonics; ++h)
220 T sumCos = T(0), sumSin = T(0);
221 for (
int i = 0; i < size; ++i)
223 T phase = twoPi<T> *
static_cast<T
>(h) *
static_cast<T
>(i) /
static_cast<T
>(size);
224 sumCos += data[i] * std::cos(phase);
225 sumSin += data[i] * std::sin(phase);
227 cosCoeffs[
static_cast<size_t>(h)] = sumCos * T(2) /
static_cast<T
>(size);
228 sinCoeffs[
static_cast<size_t>(h)] = sumSin * T(2) /
static_cast<T
>(size);
232 mipData_.assign(
static_cast<size_t>(numMipLevels_ *
kTableSize), T(0));
234 for (
int level = 0; level < numMipLevels_; ++level)
238 int maxH = 1 << (numMipLevels_ - 1 - level);
239 maxH = std::clamp(maxH, 1, maxHarmonics);
241 size_t offset =
static_cast<size_t>(level *
kTableSize);
243 for (
int h = 1; h <= maxH; ++h)
245 T a = cosCoeffs[
static_cast<size_t>(h)];
246 T b = sinCoeffs[
static_cast<size_t>(h)];
247 if (a == T(0) && b == T(0))
continue;
251 T phase = twoPi<T> *
static_cast<T
>(h) *
static_cast<T
>(i) /
static_cast<T
>(
kTableSize);
252 mipData_[offset +
static_cast<size_t>(i)] += a * std::cos(phase) + b * std::sin(phase);
261 normalizeAllLevelsGlobally();
276 if (frequencyHz != frequencyHz)
return;
277 frequency_ = frequencyHz;
278 phasor_.setFrequency(frequencyHz);
294 T phase = phasor_.advance();
295 return readTable(phase);
305 for (
int i = 0; i < numSamples; ++i)
315 const int nCh = buffer.getNumChannels();
316 const int nS = buffer.getNumSamples();
317 for (
int i = 0; i < nS; ++i)
320 for (
int ch = 0; ch < nCh; ++ch)
321 buffer.getChannel(ch)[i] = s;
331 phasor_.reset(phase);
345 [[nodiscard]]
inline T readFromLevel(T phase,
int level)
const noexcept
348 int i1 =
static_cast<int>(pos);
349 T frac = pos -
static_cast<T
>(i1);
357 size_t offset =
static_cast<size_t>(level *
kTableSize);
358 const T* table = &mipData_[offset];
366 [[nodiscard]]
inline T readTable(T phase)
const noexcept
368 if (mipData_.empty())
return T(0);
370 T levelF = selectMipLevelFloat();
371 int level0 =
static_cast<int>(levelF);
374 if (levelF ==
static_cast<T
>(level0))
376 return readFromLevel(phase, level0);
379 int level1 = std::min(level0 + 1, numMipLevels_ - 1);
380 T frac = levelF -
static_cast<T
>(level0);
382 T s0 = readFromLevel(phase, level0);
383 T s1 = readFromLevel(phase, level1);
390 const T w0 = (T(1) - frac) * (T(1) - frac);
391 return s1 + w0 * (s0 - s1);
397 [[nodiscard]]
inline T selectMipLevelFloat() const noexcept
399 T absFreq = std::abs(frequency_);
401 if (numMipLevels_ <= 1 || absFreq <= mipMaxFreq_[0])
404 for (
int i = 1; i < numMipLevels_; ++i)
406 if (absFreq <= mipMaxFreq_[
static_cast<size_t>(i)])
408 T freqLow = mipMaxFreq_[
static_cast<size_t>(i - 1)];
409 T freqHigh = mipMaxFreq_[
static_cast<size_t>(i)];
410 T t = (absFreq - freqLow) / (freqHigh - freqLow + T(1e-9));
411 return static_cast<T
>(i - 1) + t;
415 return static_cast<T
>(numMipLevels_ - 1);
423 void updateMipCutoffs()
425 if (numMipLevels_ <= 0)
return;
426 mipMaxFreq_.resize(
static_cast<size_t>(numMipLevels_));
428 const T nyquist =
static_cast<T
>(sampleRate_ / 2.0);
429 for (
int level = 0; level < numMipLevels_; ++level)
430 mipMaxFreq_[
static_cast<size_t>(level)] =
431 nyquist /
static_cast<T
>(1 << (numMipLevels_ - 1 - level));
442 void normalizeAllLevelsGlobally()
445 for (
const T v : mipData_)
446 maxVal = std::max(maxVal, std::abs(v));
450 const T invMax = T(1) / maxVal;
451 for (T& v : mipData_)
456 double sampleRate_ = 48000.0;
457 T frequency_ = T(440);
461 int numMipLevels_ = 0;
463 std::vector<T> mipData_;
464 std::vector<T> mipMaxFreq_;
Owning audio buffer and non-owning view for real-time DSP processing.
Describes the audio processing environment (sample rate, block size, channels).
Core mathematical utilities for digital signal processing.
Fractional-position interpolation for delay lines, tables and buffers.
Phase accumulator producing a [0, 1) ramp for oscillators and LFOs.
Non-owning view over audio channel data.
Professional mipmapped wavetable oscillator for bandlimited synthesis.
void prepare(double sampleRate)
Prepares the oscillator for a given sample rate.
void buildTriangle()
Builds a bandlimited triangle wavetable with mipmaps.
static constexpr int kTableSize
void prepare(const AudioSpec &spec)
Prepares from AudioSpec (unified API).
T getFrequency() const noexcept
Returns the current internal frequency.
void buildFromHarmonics(HarmonicFunc harmonicFunc)
Builds contiguous mipmapped tables from a harmonic amplitude function.
static constexpr int kMaxMipLevels
void buildSquare()
Builds a bandlimited square wavetable with mipmaps.
void buildSine()
Builds a pure sine wavetable. Requires only 1 level since it contains only the fundamental.
void generateBlock(AudioBufferView< T > buffer) noexcept
Fills every channel of the view with the generated waveform. Satisfies the GeneratorProcessor concept...
void loadWavetable(const T *data, int size)
Loads and analyzes a custom single-cycle wavetable using DFT.
T getSample() noexcept
Generates the next sample using Hermite interpolation.
void buildSaw()
Builds a bandlimited sawtooth wavetable with mipmaps.
void reset(T phase=T(0)) noexcept
Hard resets the oscillator phase.
void processBlock(T *output, int numSamples) noexcept
Generates a block of samples.
WavetableOscillator()=default
static constexpr int kTableMask
void setFrequency(T frequencyHz) noexcept
Sets the fundamental oscillation frequency.
Main namespace for the DSPark framework.
T interpolateHermite(T y0, T y1, T y2, T y3, T frac) noexcept
4-point, 3rd-order Hermite interpolation (optimized x-form).
Describes the audio environment for a DSP processor.
double sampleRate
Sample rate in Hz.