wavetable loading checkpoint

This commit is contained in:
2026-02-07 15:13:47 -06:00
parent 537f571f6a
commit 3f335343e3
6 changed files with 51 additions and 15 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
build/* build/*
.vscode/* .vscode/*
scripts/_pycache_/*

View File

@@ -21,7 +21,7 @@ This synthesizer isn't very good, but it's neat :3
oscillators increase the sound complexity considerably oscillators increase the sound complexity considerably
- [x] Create a UI scope to visualize the synthesized composite waveform - [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 - [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] Create digital filters, prob biquad. Controllable from ui obv (cutoff + resonance)
- [x] Add polyphony somewhere. Probably involves a voice class. If processing power - [x] Add polyphony somewhere. Probably involves a voice class. If processing power
allows it, tie a voice to each midi note allows it, tie a voice to each midi note

BIN
config/wavetables/sine.wt Normal file

Binary file not shown.

View File

@@ -1,3 +1,5 @@
import math
def process(phase): def process(phase):
print("im from process") return math.sin(phase)

View File

@@ -3,30 +3,60 @@
# a wavetable file consists of a one-dimensional array of samples representing one period of a waveform # a wavetable file consists of a one-dimensional array of samples representing one period of a waveform
# metadata includes: # metadata includes:
# - file version (for program compatibility) # - file version (for program compatibility)
# - binary format (float, double, int32, etc.) # - binary format (float, double, int32, etc.) (RIGHT NOW I ONLY USE FLOAT)
# - domain (normal is a phase from x=0 to x=2pi) # - domain (normal is a phase from x=0 to x=2pi)
# - range (depending on datatypes, e.g. float=[-1,1], int32=[-2^15, 2^15-1]) # - range (depending on datatypes, e.g. float=[-1,1], int32=[-2^15, 2^15-1])
# - waveform RMS (for loudness normalization) # - waveform RMS (for loudness normalization)
# - sample count
# the synth program uses the filename, not any metadata # the synth program uses the filename, not any metadata
# this script uses the function defined in example_wavetable.py to calculate samples # this script uses the function defined in example_wavetable.py to calculate samples
# if you want a custom wavetable, copy/edit/modify the example function (desmos is great for brainstorming) # if you want a custom wavetable, copy/edit/modify the example function (desmos is great for brainstorming)
from array import array
import math
import example_wavetable import example_wavetable
wavetableLength = 2048
def createFile(): def createFile():
print("creating file") print("creating file")
return 1 file = open("sine.wt", "wb")
return file
def writeMetadata(file): def writeMetadata(file):
print("im writing metadata") print(">> im writing metadata")
def generateWavetable(file): def generateWavetable(file):
print("im generating the wavetable") print(">> im generating the wavetable")
example_wavetable.process()
def closeFile(file): # init variables
print("finishing up") 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(): def main():
print("Hello main") print("Hello main")

View File

@@ -3,6 +3,7 @@
#include <cmath> #include <cmath>
#include <iostream> #include <iostream>
#include <fstream>
WavetableController::WavetableController() { WavetableController::WavetableController() {
// load from files // load from files
@@ -17,17 +18,19 @@ void WavetableController::init() {
wavetables_.resize(4); // resize for however many files we find wavetables_.resize(4); // resize for however many files we find
// don't really know how the files are gonna work // wavetable file structure is best explained in scripts/generate_wavetable.py
// 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 // read the wavetable file
// although having it all in a single bin makes the most sense with the metadata being in the header 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<char*>(wavetables_[0].data()), SYNTH_WAVETABLE_SIZE * sizeof(float));
float phase = 0.0f; float phase = 0.0f;
float phaseInc = 2.0f * M_PI / static_cast<float>(SYNTH_WAVETABLE_SIZE); float phaseInc = 2.0f * M_PI / static_cast<float>(SYNTH_WAVETABLE_SIZE);
for(int i = 0; i < SYNTH_WAVETABLE_SIZE; i++) { 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_[1][i] = (phase >= M_PI) ? 1.0f : -1.0f; // square
wavetables_[2][i] = ((phase / M_PI) - 1.0f) / 0.577f; // saw wavetables_[2][i] = ((phase / M_PI) - 1.0f) / 0.577f; // saw