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 0000000..27b3b3c Binary files /dev/null and b/config/wavetables/sine.wt differ diff --git a/scripts/example_wavetable.py b/scripts/example_wavetable.py index 9397d53..eb1dcf5 100644 --- a/scripts/example_wavetable.py +++ b/scripts/example_wavetable.py @@ -1,3 +1,5 @@ +import math + def process(phase): - print("im from process") + return math.sin(phase) diff --git a/scripts/generate_wavetable.py b/scripts/generate_wavetable.py index 46fda4b..a5fa074 100644 --- a/scripts/generate_wavetable.py +++ b/scripts/generate_wavetable.py @@ -3,30 +3,60 @@ # a wavetable file consists of a one-dimensional array of samples representing one period of a waveform # metadata includes: # - 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) # - range (depending on datatypes, e.g. float=[-1,1], int32=[-2^15, 2^15-1]) # - waveform RMS (for loudness normalization) +# - sample count # the synth program uses the filename, not any metadata # 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) +from array import array +import math + import example_wavetable +wavetableLength = 2048 + def createFile(): print("creating file") - return 1 + file = open("sine.wt", "wb") + return file def writeMetadata(file): - print("im writing metadata") + print(">> 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