From 3f335343e388ed23b1c2a020c06303286786c350 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Sat, 7 Feb 2026 15:13:47 -0600 Subject: [PATCH] wavetable loading checkpoint --- .gitignore | 3 +- README.md | 2 +- config/wavetables/sine.wt | Bin 0 -> 8192 bytes scripts/example_wavetable.py | 4 ++- scripts/generate_wavetable.py | 44 +++++++++++++++++++++++++----- src/synth/WavetableController.cpp | 13 +++++---- 6 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 config/wavetables/sine.wt diff --git a/.gitignore b/.gitignore index 367f77e..b29571d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/* -.vscode/* \ No newline at end of file +.vscode/* +scripts/_pycache_/* \ No newline at end of file diff --git a/README.md b/README.md index c303988..c502925 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This synthesizer isn't very good, but it's neat :3 oscillators increase the sound complexity considerably - [x] Create a UI scope to visualize the synthesized composite waveform - [x] Create wavetables for more complex tone generation. Needs to be selectable from ui -- [ ] Wavetable file loading +- [x] Wavetable file loading - [x] Create digital filters, prob biquad. Controllable from ui obv (cutoff + resonance) - [x] Add polyphony somewhere. Probably involves a voice class. If processing power allows it, tie a voice to each midi note diff --git a/config/wavetables/sine.wt b/config/wavetables/sine.wt new file mode 100644 index 0000000000000000000000000000000000000000..27b3b3c084e80d686e1a2ca757d5112bef0b765d GIT binary patch literal 8192 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 im writing metadata") def generateWavetable(file): - print("im generating the wavetable") - example_wavetable.process() + print(">> im generating the wavetable") -def closeFile(file): - print("finishing up") + # init variables + data_list = [None] * wavetableLength + phaseInc = 2*math.pi / wavetableLength + x = 0 + accumulator = 0 + + # generate each discrete sample + for i in range(wavetableLength): + sample = example_wavetable.process(x) + accumulator += sample * sample + x += phaseInc + data_list[i] = sample + + # normalize by rms + rms = math.sqrt(accumulator/wavetableLength) + print(">> wavetable RMS: ", rms) + for i in range(wavetableLength): + data_list[i] /= rms + + # write to file + binary_data = array("f", data_list) + file.write(binary_data) + +def closeFile(file): + print(">> finishing up") + file.close() def main(): print("Hello main") diff --git a/src/synth/WavetableController.cpp b/src/synth/WavetableController.cpp index 0758c21..3aeb3f8 100644 --- a/src/synth/WavetableController.cpp +++ b/src/synth/WavetableController.cpp @@ -3,6 +3,7 @@ #include #include +#include WavetableController::WavetableController() { // load from files @@ -17,17 +18,19 @@ void WavetableController::init() { wavetables_.resize(4); // resize for however many files we find - // don't really know how the files are gonna work - // but I'd like two files- a yaml that contains metadata like name, length, range, datatype, etc. - // and the main data be just a big array of that data type in a binary file - // although having it all in a single bin makes the most sense with the metadata being in the header + // wavetable file structure is best explained in scripts/generate_wavetable.py + + // read the wavetable file + std::ifstream inputFile("config/wavetables/sine.wt", std::ios::in | std::ios::binary); + if(!inputFile) std::cout << "error opening file" << std::endl; + inputFile.read(reinterpret_cast(wavetables_[0].data()), SYNTH_WAVETABLE_SIZE * sizeof(float)); float phase = 0.0f; float phaseInc = 2.0f * M_PI / static_cast(SYNTH_WAVETABLE_SIZE); for(int i = 0; i < SYNTH_WAVETABLE_SIZE; i++) { - wavetables_[0][i] = std::sin(phase) / 0.707f; // sine + //wavetables_[0][i] = std::sin(phase) / 0.707f; // sine wavetables_[1][i] = (phase >= M_PI) ? 1.0f : -1.0f; // square wavetables_[2][i] = ((phase / M_PI) - 1.0f) / 0.577f; // saw