diff --git a/README.md b/README.md index c502925..1eebec7 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,10 @@ Use the install_dependencies script to manually install dependencies. Build troubleshooting: On windows, `bcdedit /set IncreaseUserVa 3072` solved cc1plus.exe: out of memory errors while building qt for me -## Configurations (NOT YET IMPLEMENTED) +## Configurations Default config files are located in the config/ directory, and they are replicated into build/config/ if they dont already exist there. To edit the configurations, edit the config files in the build directory, not the defaults. Most config files are loaded/parsed at startup (TODO: investigate some reloading functions), so the program must be restarted, although not recompiled, for new configs to take effect. \ Voice profiles are saved into config files into a human-readable format (YAML) and can be edited manually or by saving within the app. \ -## Wavetables (NOT YET IMPLEMENTED) -Wavetables are this synthesizer's starting point for audio synthesis. A wavetable (as defined for this synthesizer, not elsewhere) contains a single period of a particular wave-shape with a discrete number of samples. Wavetables are loaded at runtime and sampled by oscillator objects to define and mix different wave shapes. Further specifications, as well as instructions for generating your own wavetable (including an example python script << TODO), are located within config/wavetables/README.md +## Wavetables +Wavetables are this synthesizer's starting point for audio synthesis. A wavetable (as defined for this synthesizer, not elsewhere) contains a single period of a particular wave-shape with a discrete number of samples. Wavetables are loaded at runtime and sampled by oscillator objects to define and mix different wave shapes. Further specifications, as well as instructions for generating your own wavetable (including an example python script), are located within the scripts directory. diff --git a/config/wavetables/sine.wt b/config/wavetables/sine.wt index 27b3b3c..c37a42b 100644 Binary files a/config/wavetables/sine.wt and b/config/wavetables/sine.wt differ diff --git a/scripts/example_wavetable.py b/scripts/example_wavetable.py index eb1dcf5..f6ebdad 100644 --- a/scripts/example_wavetable.py +++ b/scripts/example_wavetable.py @@ -1,5 +1,38 @@ import math -def process(phase): +WAVETABLE_FILE_NAME = "triangle" + +def sine(phase): return math.sin(phase) + +def square(phase): + sample = 1.0 + if(phase <= math.pi): + sample = -1 + return sample + +def saw(phase): + return (phase / math.pi) - 1.0 + +def triangle(phase): + sample = 0.0 + if(phase <= math.pi/2.0): + sample = phase * 2.0/math.pi + elif(phase <= 3.0*math.pi/2.0): + sample = phase * (-2.0/math.pi) + 2.0 + else: + sample = phase * 2.0/math.pi - 4.0 + return sample + +def sharkFin(phase): + return 1 + +def sphere(phase): + return 1 + +# process get called by generate_wavetable.py +# it calculates a single sample at a specified phase +# normalization is handled by generate_wavetable.py +def process(phase): + return triangle(phase) diff --git a/scripts/generate_wavetable.py b/scripts/generate_wavetable.py index a5fa074..28dc616 100644 --- a/scripts/generate_wavetable.py +++ b/scripts/generate_wavetable.py @@ -21,8 +21,9 @@ import example_wavetable wavetableLength = 2048 def createFile(): - print("creating file") - file = open("sine.wt", "wb") + filename = example_wavetable.WAVETABLE_FILE_NAME + ".wt" + print("creating file " + filename) + file = open(filename, "wb") return file def writeMetadata(file): diff --git a/src/synth/WavetableController.cpp b/src/synth/WavetableController.cpp index 8ef2949..196eabf 100644 --- a/src/synth/WavetableController.cpp +++ b/src/synth/WavetableController.cpp @@ -16,46 +16,26 @@ WavetableController::WavetableController() { void WavetableController::init() { - // find number of wavetable files + // find wavetable files std::vector wavetableFiles; for(std::filesystem::directory_entry entry : std::filesystem::directory_iterator(wavetablesRoot_)) { if(std::filesystem::is_regular_file(entry.status())) { wavetableFiles.push_back(entry.path()); } } + uint32_t wavetableCount = wavetableFiles.size(); + wavetables_.resize(wavetableCount); - wavetables_.resize(4); // resize for however many files we find - - // 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_[1][i] = (phase >= M_PI) ? 1.0f : -1.0f; // square - wavetables_[2][i] = ((phase / M_PI) - 1.0f) / 0.577f; // saw - - // triangle - float tri = 0.0f; - if(phase <= M_PI/2.0f) { - tri = phase * 2.0f/M_PI; - } else if(phase <= 3.0f*M_PI/2.0f) { - tri = phase * -2.0f/M_PI + 2.0f; - } else { - tri = phase * 2.0f/M_PI - 4.0f; - } - wavetables_[3][i] = tri / 0.577f; - - phase += phaseInc; + // load the wavetable files + for(int i = 0; i < wavetableCount; i++) { + std::cout << "loading wavetable file [" << i << "]: " << wavetableFiles[i] << std::endl; + std::ifstream inputFile(wavetableFiles[i], std::ios::in | std::ios::binary); + if(!inputFile) std::cout << "error opening file" << std::endl; + inputFile.read(reinterpret_cast(wavetables_[i].data()), SYNTH_WAVETABLE_SIZE * sizeof(float)); } + // wavetable data structure is best explained in scripts/generate_wavetable.py + } float WavetableController::sample(uint8_t wavetableIndex, float phase) { diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index 7fcb343..377fcae 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -48,6 +48,17 @@ MainWindow::MainWindow(QWidget *parent) : this, [this](int index) { audio_->parameters()->set(ParamId::Osc1WaveSelector2, index); }); + ui_->comboOsc1WaveSelector1->clear(); + ui_->comboOsc1WaveSelector2->clear(); + for(std::filesystem::directory_entry entry : std::filesystem::directory_iterator("config/wavetables")) { + if(std::filesystem::is_regular_file(entry.status())) { + std::string fileName = entry.path().string().substr(18); + fileName.erase(fileName.length() - 3); + ui_->comboOsc1WaveSelector1->addItem(QString::fromStdString(fileName)); + ui_->comboOsc1WaveSelector2->addItem(QString::fromStdString(fileName)); + } + } + // rogue sliders, TODO: clean these up in a package connect(ui_->sliderMasterOctave, &SmartSlider::valueChanged,