configurable wavetable files

This commit is contained in:
2026-02-07 18:08:38 -06:00
parent 8b4a09fc39
commit 18ff8dfc9f
6 changed files with 62 additions and 37 deletions

View File

@@ -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.

Binary file not shown.

View File

@@ -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)

View File

@@ -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):

View File

@@ -16,45 +16,25 @@ WavetableController::WavetableController() {
void WavetableController::init() {
// find number of wavetable files
// find wavetable files
std::vector<std::filesystem::path> 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);
// 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<char*>(wavetables_[0].data()), SYNTH_WAVETABLE_SIZE * sizeof(float));
float phase = 0.0f;
float phaseInc = 2.0f * M_PI / static_cast<float>(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;
inputFile.read(reinterpret_cast<char*>(wavetables_[i].data()), SYNTH_WAVETABLE_SIZE * sizeof(float));
}
wavetables_[3][i] = tri / 0.577f;
phase += phaseInc;
}
// wavetable data structure is best explained in scripts/generate_wavetable.py
}

View File

@@ -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,