From 66bea462e7d66a48e610efa61288bb5d42799c31 Mon Sep 17 00:00:00 2001 From: Annika Date: Sun, 26 Jul 2026 04:55:39 -0700 Subject: [PATCH] wavetable oscillator with lanczos2 resampling --- femtosynth | Bin 45688 -> 45664 bytes main.cpp | 1 + oscillator.cpp | 114 +++++++++++++++++++++++++++++++++++++++-- scripts/lanczos-gen.py | 23 +++++++++ 4 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 scripts/lanczos-gen.py diff --git a/femtosynth b/femtosynth index 45dc9cbc94e45ca2dd7737f2b18b606670768507..0bafa9bc87f6464b438120275c1ec6b5eadea99d 100755 GIT binary patch delta 110 zcmV-!0FnRr=qVC#cFS3jhEB delta 118 zcmV-+0Ez$L> 21; + uint32_t sampleError = (pitchAcc >> 13) & 0x000000ff; + int32_t filteredSample = waveSample[sampleIndex] * LANCZOS2[255 - sampleError]; + filteredSample += (waveSample[(sampleIndex + 1) & 0x000007ff] * LANCZOS2[511 - sampleError]); + filteredSample += (waveSample[(sampleIndex + 2) & 0x000007ff] * LANCZOS2[256 + sampleError]); + filteredSample += (waveSample[(sampleIndex + 3) & 0x000007ff] * LANCZOS2[sampleError]); + filteredSample /= 32768; // should actually be /= 32767 but who's counting + return (int16_t)filteredSample; + } + + uint32_t noteFrequency(uint8_t note) { + // 2048 samples / cycle ~ 48kHz / 20Hz + // uint32_t pitch accumulator coefficient + // top 11-bits = sample # + // bottom 21-bits = subsample resolution + // use a discontinuous "fast" note notation + // oooonnnn + // o = octave (0:15 = -6:9) + // n = note (0:11 = C:B) + + + uint32_t notePitch = NOTELUT[note & 0x0f]; + uint8_t octaveShift = note >> 4; + octaveShift = (0xf - octaveShift) & 0x0f; + notePitch = notePitch >> octaveShift; + return notePitch; + } + + // linear interpolation of two samples + int16_t lerp(int16_t a, int16_t b, uint16_t t) { + int32_t lerpSum = (int32_t)(0x2000 - t) * (int32_t)a; + lerpSum += (int32_t)t * (int32_t)b; + return (int16_t)(lerpSum / 0x2000); } public: - void Oscillator() { + Oscillator() { + this->pitchAcc = 0; + } + uint32_t pitchAcc; + void reset() { + pitchAcc = 0; } - + int16_t gen(uint8_t note, uint16_t wavePos, int16_t** waveTable) { + pitchAcc += noteFrequency(note); + + // interpolate between two waves in the wavetable + uint8_t waveIndex = wavePos >> 13; + int16_t* waveA = waveTable[waveIndex]; + int16_t* waveB = waveTable[(waveIndex + 1) & 0x07]; + int16_t sampleA = lanczos(pitchAcc, waveA); + int16_t sampleB = lanczos(pitchAcc, waveB); + return lerp (sampleA, sampleB, wavePos & 0x1fff); + } }; } \ No newline at end of file diff --git a/scripts/lanczos-gen.py b/scripts/lanczos-gen.py new file mode 100644 index 0000000..e1edfe9 --- /dev/null +++ b/scripts/lanczos-gen.py @@ -0,0 +1,23 @@ +# lanczos2 kernel interval -2:0 +# generating 512 point LUT +# 511 is centerpoint + +# lanczos defined as sinc(x) * sinc(x/a) +# a is order (2) +# sinc(x) = sin(x) / x +# output format: +# 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, 0xNNNN, +# x32 lines + +import numpy as np + +for j in range(0, 512, 16): + lineString = "" + for k in range(16): + i = j + k + x = (i - 511) / 256 + y = np.sinc(x) * np.sinc(x / 2) + y *= 32767 + lineString += hex(int(y)) + lineString += ", " + print(lineString) \ No newline at end of file