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) set_target_properties(metabolus PROPERTIES AUTOUIC ON)
target_include_directories(metabolus PRIVATE target_include_directories(metabolus PRIVATE
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/ui ${CMAKE_SOURCE_DIR}/src/ui
${CMAKE_SOURCE_DIR}/src/synth
${CMAKE_SOURCE_DIR}/src/ui/widgets ${CMAKE_SOURCE_DIR}/src/ui/widgets
) )

View File

@@ -48,11 +48,11 @@ Linux: GCC
Clone repository Clone repository
```PowerShell ```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: or if you forgot to --recursive:
```PowerShell ```PowerShell
git clone https://github.com/Blitblank/metabalus.git git clone https://git.vxbard.net/homeburger/metabalus.git
git submodule update --init --recursive 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() MainWindow window; // entry point goes to MainWindow::MainWindow()
window.show(); 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 int status = app.exec(); // app execution; blocks until window close

View File

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

View File

@@ -11,6 +11,8 @@
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
#endif #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 #define SYNTH_TWELFTH_ROOT_TWO 1.05946309435929526463
// TODO: you get it, also in a yml config // 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; buffer_[w % buffer_.size()] = sample;
} }
// TODO: needs a mutex/spinlock to prevent flickering
// outputs value from the scope buffer, called by the scope widget // outputs value from the scope buffer, called by the scope widget
void ScopeBuffer::read(std::vector<float>& out) const { 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); size_t w = writeIndex_.load(std::memory_order_relaxed);
for (size_t i = 0; i < out.size(); i++) { for (size_t i = 0; i < out.size(); i++) {
size_t idx = (w + trigger_ + i * wavelength_ / out.size()) % buffer_.size(); size_t idx = (w + trigger_ + i * wavelength_ / out.size()) % buffer_.size();

View File

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

View File

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

View File

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

View File

@@ -6,12 +6,10 @@
#include <fstream> #include <fstream>
WavetableController::WavetableController() { WavetableController::WavetableController() {
// load from files
// load from files
init(); init();
//std::cout << "wavetable init" << std::endl;
} }
void WavetableController::init() { void WavetableController::init() {
@@ -23,11 +21,11 @@ void WavetableController::init() {
wavetableFiles.push_back(entry.path()); wavetableFiles.push_back(entry.path());
} }
} }
uint32_t wavetableCount = wavetableFiles.size(); wavetableCount_ = wavetableFiles.size();
wavetables_.resize(wavetableCount); wavetables_.resize(wavetableCount_);
// load the wavetable files // 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::cout << "loading wavetable file [" << i << "]: " << wavetableFiles[i] << std::endl;
std::ifstream inputFile(wavetableFiles[i], std::ios::in | std::ios::binary); std::ifstream inputFile(wavetableFiles[i], std::ios::in | std::ios::binary);
if(!inputFile) std::cout << "error opening file" << std::endl; 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)); uint32_t index = static_cast<uint32_t>(round(scaledPhase));
if(index >= SYNTH_WAVETABLE_SIZE) index = SYNTH_WAVETABLE_SIZE - 1; if(index >= SYNTH_WAVETABLE_SIZE) index = SYNTH_WAVETABLE_SIZE - 1;
if(wavetableIndex >= 4) { if(wavetableIndex >= wavetableCount_) {
wavetableIndex = 3; wavetableIndex = wavetableCount_ - 1;
} else if(wavetableIndex < 0) { } else if(wavetableIndex < 0) {
wavetableIndex = 0; wavetableIndex = 0;
} }

View File

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

View File

@@ -2,8 +2,7 @@
#include "MainWindow.h" #include "MainWindow.h"
#include "ui_MainWindow.h" #include "ui_MainWindow.h"
#include "SmartSlider/SmartSlider.h" #include "ParameterStore.h"
#include "../ParameterStore.h"
MainWindow::MainWindow(QWidget *parent) : MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent), QMainWindow(parent),
@@ -59,67 +58,19 @@ MainWindow::MainWindow(QWidget *parent) :
} }
} }
// rogue sliders, TODO: clean these up in a package // rogue sliders, TODO: clean these up in a package
connect(ui_->sliderMasterOctave, &SmartSlider::valueChanged, bindSlider(ui_->sliderMasterOctave, ParamId::MasterOctaveOffset, true);
this, [this](float value) { bindSlider(ui_->sliderMasterSemitone, ParamId::MasterSemitoneOffset, true);
audio_->parameters()->set(ParamId::MasterOctaveOffset, value); bindSlider(ui_->sliderMasterPitch, ParamId::MasterPitchOffset);
ui_->sliderMasterOctave->setResolution(); bindSlider(ui_->sliderOsc1Octave, ParamId::Osc1OctaveOffset, true);
}); bindSlider(ui_->sliderOsc1Semitone, ParamId::Osc1SemitoneOffset, true);
connect(ui_->sliderMasterSemitone, &SmartSlider::valueChanged, bindSlider(ui_->sliderOsc1Pitch, ParamId::Osc1PitchOffset);
this, [this](float value) { bindSlider(ui_->sliderOsc2Octave, ParamId::Osc2OctaveOffset, true);
audio_->parameters()->set(ParamId::MasterSemitoneOffset, value); bindSlider(ui_->sliderOsc2Semitone, ParamId::Osc2SemitoneOffset, true);
ui_->sliderMasterSemitone->setResolution(); bindSlider(ui_->sliderOsc2Pitch, ParamId::Osc2PitchOffset);
}); bindSlider(ui_->sliderOsc3Octave, ParamId::Osc3OctaveOffset, true);
connect(ui_->sliderMasterPitch, &SmartSlider::valueChanged, bindSlider(ui_->sliderOsc3Semitone, ParamId::Osc3SemitoneOffset, true);
this, [this](float value) { bindSlider(ui_->sliderOsc3Pitch, ParamId::Osc3PitchOffset);
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);
});
// synth business // synth business
audio_->start(); 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) // 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 // 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 masterNode = configRoot["MasterPitchOffset"];
YAML::Node osc1Node = configRoot["Osc1PitchOffset"]; YAML::Node osc1Node = configRoot["Osc1PitchOffset"];
YAML::Node osc2Node = configRoot["Osc2PitchOffset"]; YAML::Node osc2Node = configRoot["Osc2PitchOffset"];
@@ -209,3 +161,12 @@ void MainWindow::onResetClicked() {
ui_->comboOsc1WaveSelector2->setCurrentIndex(configRoot["OscWaveSelector2"].as<int>()); 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 <QMainWindow>
#include <QKeyEvent> #include <QKeyEvent>
#include "../ConfigInterface.h" #include "ConfigInterface.h"
#include "../synth/AudioEngine.h" #include "synth/AudioEngine.h"
#include "../MidiController.h" #include "MidiController.h"
#include "SmartSlider/SmartSlider.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; } namespace Ui { class MainWindow; }
@@ -29,6 +30,8 @@ private slots:
private: private:
void bindSlider(SmartSlider* slider, ParamId param, bool updateResolution = false);
Ui::MainWindow *ui_; Ui::MainWindow *ui_;
ParameterStore params_; ParameterStore params_;

View File

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

View File

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