4 Commits

Author SHA1 Message Date
35663df15c add helper function for binding smart-sliders 2026-04-18 21:55:58 -05:00
2590b03756 cleanup include paths 2026-04-18 13:48:00 -05:00
8e838c61e6 bugfix: loading more than 4 wavetables 2026-04-18 12:25:20 -05:00
5c9a37b8d4 init 2026-04-17 21:02:44 -05:00
15 changed files with 55 additions and 88 deletions

View File

@@ -90,7 +90,9 @@ qt_add_executable(metabolus
set_target_properties(metabolus PROPERTIES AUTOUIC ON)
target_include_directories(metabolus PRIVATE
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/ui
${CMAKE_SOURCE_DIR}/src/synth
${CMAKE_SOURCE_DIR}/src/ui/widgets
)

View File

@@ -48,11 +48,11 @@ Linux: GCC
Clone repository
```PowerShell
git clone https://github.com/Blitblank/metabalus.git --recursive
git clone https://git.vxbard.net/homeburger/metabalus.git --recursive
```
or if you forgot to --recursive:
```PowerShell
git clone https://github.com/Blitblank/metabalus.git
git clone https://git.vxbard.net/homeburger/metabalus.git
git submodule update --init --recursive
```
\

View File

@@ -14,6 +14,10 @@ int main(int argc, char *argv[]) {
MainWindow window; // entry point goes to MainWindow::MainWindow()
window.show();
// TODO: logging
// TODO: separate app from the window
// i made a good debug filtering setup in ouros so could probably improt that into this project
int status = app.exec(); // app execution; blocks until window close

View File

@@ -5,9 +5,9 @@
#include <stdint.h>
#include <atomic>
#include "../ConfigInterface.h"
#include "ConfigInterface.h"
#include "Synth.h"
#include "../KeyboardController.h"
#include "KeyboardController.h"
#if defined(_WIN32)
#define AUDIO_API RtAudio::WINDOWS_WASAPI

View File

@@ -11,6 +11,8 @@
#define M_PI 3.14159265358979323846
#endif
// TODO: if the amount of notes per octave is changed via config then this constant needs to be calculated at startup
// as SYNTH_Xth_ROOT_TWO
#define SYNTH_TWELFTH_ROOT_TWO 1.05946309435929526463
// TODO: you get it, also in a yml config

View File

@@ -11,13 +11,9 @@ void ScopeBuffer::push(float sample) {
buffer_[w % buffer_.size()] = sample;
}
// TODO: needs a mutex/spinlock to prevent flickering
// outputs value from the scope buffer, called by the scope widget
void ScopeBuffer::read(std::vector<float>& out) const {
// yeah this didn't work, maybe it needs to be atomic or something
//while(!spinLock_) { int x = 1 + 1; }
size_t w = writeIndex_.load(std::memory_order_relaxed);
for (size_t i = 0; i < out.size(); i++) {
size_t idx = (w + trigger_ + i * wavelength_ / out.size()) % buffer_.size();

View File

@@ -1,8 +1,8 @@
#pragma once
#include "../ParameterStore.h"
#include "../NoteQueue.h"
#include "ParameterStore.h"
#include "NoteQueue.h"
#include "Envelope.h"
#include "ScopeBuffer.h"
#include "Filter.h"

View File

@@ -101,7 +101,7 @@ float Voice::process(float* params, bool& scopeTrigger) {
// TODO: implement controls for noise
//float scale = static_cast<float>(rand()) / static_cast<float>(RAND_MAX); // Range [0.0, 1.0]
//float noise = -1.0f + 2.0f * scale;
//float noise = (-1.0f + 2.0f * scale) * 0.2f;
// these values didn't sound good so I commented them out before I get controls for them
// mix oscillators

View File

@@ -4,7 +4,7 @@
#include "Oscillator.h"
#include "Envelope.h"
#include "Filter.h"
#include "../ParameterStore.h"
#include "ParameterStore.h"
#ifndef M_PI // I hate my stupid chungus life
#define M_PI 3.14159265358979323846

View File

@@ -6,12 +6,10 @@
#include <fstream>
WavetableController::WavetableController() {
// load from files
// load from files
init();
//std::cout << "wavetable init" << std::endl;
}
void WavetableController::init() {
@@ -23,11 +21,11 @@ void WavetableController::init() {
wavetableFiles.push_back(entry.path());
}
}
uint32_t wavetableCount = wavetableFiles.size();
wavetables_.resize(wavetableCount);
wavetableCount_ = wavetableFiles.size();
wavetables_.resize(wavetableCount_);
// load the wavetable files
for(int i = 0; i < wavetableCount; i++) {
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;
@@ -56,8 +54,8 @@ float WavetableController::sample(uint8_t wavetableIndex, float phase) {
uint32_t index = static_cast<uint32_t>(round(scaledPhase));
if(index >= SYNTH_WAVETABLE_SIZE) index = SYNTH_WAVETABLE_SIZE - 1;
if(wavetableIndex >= 4) {
wavetableIndex = 3;
if(wavetableIndex >= wavetableCount_) {
wavetableIndex = wavetableCount_ - 1;
} else if(wavetableIndex < 0) {
wavetableIndex = 0;
}

View File

@@ -30,6 +30,7 @@ public:
private:
std::vector<Wavetable> wavetables_;
uint32_t wavetableCount_;
const std::filesystem::path wavetablesRoot_ = "./config/wavetables";

View File

@@ -2,8 +2,7 @@
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "SmartSlider/SmartSlider.h"
#include "../ParameterStore.h"
#include "ParameterStore.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
@@ -59,67 +58,19 @@ MainWindow::MainWindow(QWidget *parent) :
}
}
// rogue sliders, TODO: clean these up in a package
connect(ui_->sliderMasterOctave, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::MasterOctaveOffset, value);
ui_->sliderMasterOctave->setResolution();
});
connect(ui_->sliderMasterSemitone, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::MasterSemitoneOffset, value);
ui_->sliderMasterSemitone->setResolution();
});
connect(ui_->sliderMasterPitch, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::MasterPitchOffset, value);
});
connect(ui_->sliderOsc1Octave, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::Osc1OctaveOffset, value);
ui_->sliderOsc1Octave->setResolution();
});
connect(ui_->sliderOsc1Semitone, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::Osc1SemitoneOffset, value);
ui_->sliderOsc1Semitone->setResolution();
});
connect(ui_->sliderOsc1Pitch, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::Osc1PitchOffset, value);
});
connect(ui_->sliderOsc2Octave, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::Osc2OctaveOffset, value);
ui_->sliderOsc2Octave->setResolution();
});
connect(ui_->sliderOsc2Semitone, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::Osc2SemitoneOffset, value);
ui_->sliderOsc2Semitone->setResolution();
});
connect(ui_->sliderOsc2Pitch, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::Osc2PitchOffset, value);
});
connect(ui_->sliderOsc3Octave, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::Osc3OctaveOffset, value);
ui_->sliderOsc3Octave->setResolution();
});
connect(ui_->sliderOsc3Semitone, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::Osc3SemitoneOffset, value);
ui_->sliderOsc3Semitone->setResolution();
});
connect(ui_->sliderOsc3Pitch, &SmartSlider::valueChanged,
this, [this](float value) {
audio_->parameters()->set(ParamId::Osc3PitchOffset, value);
});
bindSlider(ui_->sliderMasterOctave, ParamId::MasterOctaveOffset, true);
bindSlider(ui_->sliderMasterSemitone, ParamId::MasterSemitoneOffset, true);
bindSlider(ui_->sliderMasterPitch, ParamId::MasterPitchOffset);
bindSlider(ui_->sliderOsc1Octave, ParamId::Osc1OctaveOffset, true);
bindSlider(ui_->sliderOsc1Semitone, ParamId::Osc1SemitoneOffset, true);
bindSlider(ui_->sliderOsc1Pitch, ParamId::Osc1PitchOffset);
bindSlider(ui_->sliderOsc2Octave, ParamId::Osc2OctaveOffset, true);
bindSlider(ui_->sliderOsc2Semitone, ParamId::Osc2SemitoneOffset, true);
bindSlider(ui_->sliderOsc2Pitch, ParamId::Osc2PitchOffset);
bindSlider(ui_->sliderOsc3Octave, ParamId::Osc3OctaveOffset, true);
bindSlider(ui_->sliderOsc3Semitone, ParamId::Osc3SemitoneOffset, true);
bindSlider(ui_->sliderOsc3Pitch, ParamId::Osc3PitchOffset);
// synth business
audio_->start();
@@ -157,6 +108,7 @@ void MainWindow::onResetClicked() {
// TODO: clean these up, maybe put them in a package like the envelope generators (it'll help encapsulate the int-snapping business)
// what I might do is make a variable-length slider-package object
// TODO: the oscillator things also need an amplitude parameter too
YAML::Node masterNode = configRoot["MasterPitchOffset"];
YAML::Node osc1Node = configRoot["Osc1PitchOffset"];
YAML::Node osc2Node = configRoot["Osc2PitchOffset"];
@@ -209,3 +161,12 @@ void MainWindow::onResetClicked() {
ui_->comboOsc1WaveSelector2->setCurrentIndex(configRoot["OscWaveSelector2"].as<int>());
}
void MainWindow::bindSlider(SmartSlider* slider, ParamId param, bool updateResolution)
{
connect(slider, &SmartSlider::valueChanged, this,
[this](float value, ParamId param, SmartSlider* slider, bool updateResolution) {
audio_->parameters()->set(param, value);
if (updateResolution) slider->setResolution();
});
}

View File

@@ -4,9 +4,10 @@
#include <QMainWindow>
#include <QKeyEvent>
#include "../ConfigInterface.h"
#include "../synth/AudioEngine.h"
#include "../MidiController.h"
#include "ConfigInterface.h"
#include "synth/AudioEngine.h"
#include "MidiController.h"
#include "SmartSlider/SmartSlider.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
@@ -29,6 +30,8 @@ private slots:
private:
void bindSlider(SmartSlider* slider, ParamId param, bool updateResolution = false);
Ui::MainWindow *ui_;
ParameterStore params_;

View File

@@ -3,7 +3,7 @@
#include <QWidget>
#include "../../ParameterStore.h"
#include "ParameterStore.h"
QT_BEGIN_NAMESPACE
namespace Ui { class EnvelopeGenerator; }

View File

@@ -3,7 +3,7 @@
#include "ui_Scope.h"
// TODO: fix include directories because what is this
#include "../../../synth/ScopeBuffer.h"
#include "synth/ScopeBuffer.h"
#include <QPainter>
#include <iostream>