diff --git a/femtosynth b/femtosynth index 45dc9cb..0bafa9b 100755 Binary files a/femtosynth and b/femtosynth differ diff --git a/main.cpp b/main.cpp index 48ef0d8..af966cb 100644 --- a/main.cpp +++ b/main.cpp @@ -6,6 +6,7 @@ #include "oscillator.cpp" int main() { + const int sample_rate = 48000; const float freq = 600.0f; float phase = 0.0f; diff --git a/oscillator.cpp b/oscillator.cpp index 5391b8f..aee436d 100644 --- a/oscillator.cpp +++ b/oscillator.cpp @@ -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); + } }; } \ 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