wavetable oscillator with lanczos2 resampling

This commit is contained in:
2026-07-26 04:55:39 -07:00
parent 3e41930e7d
commit 66bea462e7
4 changed files with 134 additions and 4 deletions
BIN
View File
Binary file not shown.
+1
View File
@@ -6,6 +6,7 @@
#include "oscillator.cpp"
int main() {
const int sample_rate = 48000;
const float freq = 600.0f;
float phase = 0.0f;
+110 -4
View File
@@ -3,15 +3,121 @@
namespace Fem {
class Oscillator {
private:
int16_t lanczos() {
return 0;
// 11.21 fixed point amount to increment per 48kHz clock cycle
// 11.8 used, least significant 13 bits only used for accumulator precision
const uint32_t NOTELUT[16] = {
749114669, // C9
793659848, // C#9
840853486, // D9
890853486, // D#9
943826222, // E9
999948917, // F9
1059409160, // F#9
1122404698, // G9
1189146700, // G#9D
1259857073, // A9
1334772041, // A#9
1414141247, // B9
0, // invalid
0, // invalid
0, // invalid
0 // invalid
};
// lanczos2 kernel LUT
const int16_t LANCZOS2[512] = {
0x0, 0x0, -0x1, -0x2, -0x3, -0x4, -0x6, -0x8, -0xa, -0xc, -0xf, -0x12, -0x16, -0x19, -0x1d, -0x21,
-0x26, -0x2b, -0x30, -0x35, -0x3b, -0x41, -0x47, -0x4d, -0x54, -0x5b, -0x63, -0x6a, -0x73, -0x7b, -0x84, -0x8d,
-0x96, -0x9f, -0xa9, -0xb3, -0xbe, -0xc9, -0xd4, -0xdf, -0xeb, -0xf7, -0x103, -0x110, -0x11d, -0x12a, -0x138, -0x145,
-0x154, -0x162, -0x171, -0x180, -0x18f, -0x19f, -0x1af, -0x1bf, -0x1cf, -0x1e0, -0x1f1, -0x202, -0x214, -0x226, -0x238, -0x24a,
-0x25d, -0x270, -0x283, -0x296, -0x2aa, -0x2be, -0x2d2, -0x2e6, -0x2fb, -0x310, -0x325, -0x33a, -0x350, -0x365, -0x37b, -0x391,
-0x3a8, -0x3be, -0x3d5, -0x3ec, -0x403, -0x41a, -0x431, -0x449, -0x461, -0x478, -0x490, -0x4a9, -0x4c1, -0x4d9, -0x4f2, -0x50a,
-0x523, -0x53c, -0x554, -0x56d, -0x586, -0x59f, -0x5b9, -0x5d2, -0x5eb, -0x604, -0x61d, -0x637, -0x650, -0x669, -0x682, -0x69c,
-0x6b5, -0x6ce, -0x6e7, -0x700, -0x719, -0x732, -0x74b, -0x764, -0x77d, -0x795, -0x7ae, -0x7c6, -0x7de, -0x7f7, -0x80e, -0x826,
-0x83e, -0x855, -0x86c, -0x883, -0x89a, -0x8b1, -0x8c7, -0x8dd, -0x8f3, -0x909, -0x91e, -0x933, -0x948, -0x95c, -0x970, -0x984,
-0x997, -0x9aa, -0x9bd, -0x9cf, -0x9e1, -0x9f3, -0xa04, -0xa14, -0xa25, -0xa34, -0xa44, -0xa53, -0xa61, -0xa6f, -0xa7c, -0xa89,
-0xa96, -0xaa2, -0xaad, -0xab8, -0xac2, -0xacc, -0xad5, -0xadd, -0xae5, -0xaec, -0xaf3, -0xaf9, -0xafe, -0xb03, -0xb07, -0xb0a,
-0xb0d, -0xb0e, -0xb10, -0xb10, -0xb10, -0xb0e, -0xb0d, -0xb0a, -0xb07, -0xb02, -0xafd, -0xaf7, -0xaf1, -0xae9, -0xae1, -0xad8,
-0xace, -0xac3, -0xab7, -0xaaa, -0xa9c, -0xa8e, -0xa7e, -0xa6e, -0xa5c, -0xa4a, -0xa36, -0xa22, -0xa0d, -0x9f7, -0x9df, -0x9c7,
-0x9ae, -0x993, -0x978, -0x95b, -0x93e, -0x91f, -0x8ff, -0x8df, -0x8bd, -0x89a, -0x876, -0x851, -0x82a, -0x803, -0x7da, -0x7b1,
-0x786, -0x75a, -0x72d, -0x6fe, -0x6cf, -0x69e, -0x66c, -0x639, -0x605, -0x5d0, -0x599, -0x561, -0x528, -0x4ee, -0x4b2, -0x475,
-0x437, -0x3f8, -0x3b8, -0x376, -0x333, -0x2ef, -0x2a9, -0x263, -0x21b, -0x1d2, -0x187, -0x13b, -0xee, -0xa0, -0x50, 0x0,
0x52, 0xa5, 0xfa, 0x150, 0x1a7, 0x1ff, 0x259, 0x2b4, 0x310, 0x36e, 0x3cd, 0x42d, 0x48e, 0x4f1, 0x555, 0x5ba,
0x621, 0x688, 0x6f1, 0x75c, 0x7c7, 0x834, 0x8a2, 0x911, 0x981, 0x9f3, 0xa66, 0xada, 0xb4f, 0xbc6, 0xc3e, 0xcb7,
0xd31, 0xdac, 0xe29, 0xea6, 0xf25, 0xfa5, 0x1026, 0x10a8, 0x112c, 0x11b0, 0x1236, 0x12bd, 0x1345, 0x13ce, 0x1458, 0x14e3,
0x156f, 0x15fc, 0x168b, 0x171a, 0x17aa, 0x183c, 0x18ce, 0x1962, 0x19f6, 0x1a8b, 0x1b22, 0x1bb9, 0x1c51, 0x1cea, 0x1d84, 0x1e1f,
0x1ebb, 0x1f58, 0x1ff5, 0x2094, 0x2133, 0x21d3, 0x2274, 0x2315, 0x23b8, 0x245b, 0x24ff, 0x25a3, 0x2649, 0x26ef, 0x2796, 0x283d,
0x28e5, 0x298e, 0x2a37, 0x2ae1, 0x2b8c, 0x2c37, 0x2ce2, 0x2d8e, 0x2e3b, 0x2ee8, 0x2f96, 0x3044, 0x30f3, 0x31a2, 0x3251, 0x3301,
0x33b2, 0x3462, 0x3513, 0x35c4, 0x3676, 0x3728, 0x37da, 0x388c, 0x393f, 0x39f2, 0x3aa5, 0x3b58, 0x3c0b, 0x3cbf, 0x3d72, 0x3e26,
0x3eda, 0x3f8d, 0x4041, 0x40f5, 0x41a9, 0x425d, 0x4310, 0x43c4, 0x4478, 0x452b, 0x45de, 0x4692, 0x4745, 0x47f7, 0x48aa, 0x495c,
0x4a0e, 0x4ac0, 0x4b72, 0x4c23, 0x4cd4, 0x4d84, 0x4e34, 0x4ee4, 0x4f93, 0x5042, 0x50f0, 0x519e, 0x524b, 0x52f8, 0x53a4, 0x5450,
0x54fb, 0x55a6, 0x564f, 0x56f9, 0x57a1, 0x5849, 0x58f0, 0x5996, 0x5a3c, 0x5ae0, 0x5b84, 0x5c28, 0x5cca, 0x5d6b, 0x5e0c, 0x5eab,
0x5f4a, 0x5fe8, 0x6085, 0x6120, 0x61bb, 0x6255, 0x62ee, 0x6385, 0x641c, 0x64b1, 0x6546, 0x65d9, 0x666b, 0x66fc, 0x678b, 0x681a,
0x68a7, 0x6933, 0x69bd, 0x6a47, 0x6acf, 0x6b56, 0x6bdb, 0x6c5f, 0x6ce1, 0x6d63, 0x6de3, 0x6e61, 0x6ede, 0x6f59, 0x6fd3, 0x704c,
0x70c3, 0x7138, 0x71ac, 0x721e, 0x728f, 0x72fe, 0x736c, 0x73d8, 0x7442, 0x74ab, 0x7512, 0x7577, 0x75db, 0x763d, 0x769d, 0x76fb,
0x7758, 0x77b3, 0x780c, 0x7864, 0x78b9, 0x790d, 0x795f, 0x79af, 0x79fe, 0x7a4a, 0x7a95, 0x7ade, 0x7b25, 0x7b6a, 0x7bad, 0x7bee,
0x7c2d, 0x7c6b, 0x7ca6, 0x7ce0, 0x7d17, 0x7d4d, 0x7d81, 0x7db2, 0x7de2, 0x7e10, 0x7e3b, 0x7e65, 0x7e8d, 0x7eb3, 0x7ed6, 0x7ef8,
0x7f18, 0x7f35, 0x7f51, 0x7f6b, 0x7f82, 0x7f98, 0x7fab, 0x7fbd, 0x7fcc, 0x7fda, 0x7fe5, 0x7fee, 0x7ff5, 0x7ffa, 0x7ffd, 0x7fff
};
// 4-point lanczos2 resampling filter
int16_t lanczos(uint32_t pitchAcc, int16_t* waveSample) {
uint32_t sampleIndex = pitchAcc >> 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);
}
};
}
+23
View File
@@ -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)