1 Commits

Author SHA1 Message Date
a6e15c4853 init 2026-04-18 17:05:49 -05:00
15 changed files with 88 additions and 55 deletions

View File

@@ -90,9 +90,7 @@ 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://git.vxbard.net/homeburger/metabalus.git --recursive git clone https://github.com/Blitblank/metabalus.git --recursive
``` ```
or if you forgot to --recursive: or if you forgot to --recursive:
```PowerShell ```PowerShell
git clone https://git.vxbard.net/homeburger/metabalus.git git clone https://github.com/Blitblank/metabalus.git
git submodule update --init --recursive git submodule update --init --recursive
``` ```
\ \

View File

@@ -15,10 +15,6 @@ 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
return status; return status;

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,8 +11,6 @@
#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,9 +11,13 @@ 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) * 0.2f; //float noise = -1.0f + 2.0f * scale;
// 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,10 +6,12 @@
#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() {
@@ -21,11 +23,11 @@ void WavetableController::init() {
wavetableFiles.push_back(entry.path()); wavetableFiles.push_back(entry.path());
} }
} }
wavetableCount_ = wavetableFiles.size(); uint32_t 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;
@@ -54,8 +56,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 >= wavetableCount_) { if(wavetableIndex >= 4) {
wavetableIndex = wavetableCount_ - 1; wavetableIndex = 3;
} else if(wavetableIndex < 0) { } else if(wavetableIndex < 0) {
wavetableIndex = 0; wavetableIndex = 0;
} }

View File

@@ -30,7 +30,6 @@ 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,7 +2,8 @@
#include "MainWindow.h" #include "MainWindow.h"
#include "ui_MainWindow.h" #include "ui_MainWindow.h"
#include "ParameterStore.h" #include "SmartSlider/SmartSlider.h"
#include "../ParameterStore.h"
MainWindow::MainWindow(QWidget *parent) : MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent), QMainWindow(parent),
@@ -58,19 +59,67 @@ MainWindow::MainWindow(QWidget *parent) :
} }
} }
// rogue sliders, TODO: clean these up in a package // rogue sliders, TODO: clean these up in a package
bindSlider(ui_->sliderMasterOctave, ParamId::MasterOctaveOffset, true); connect(ui_->sliderMasterOctave, &SmartSlider::valueChanged,
bindSlider(ui_->sliderMasterSemitone, ParamId::MasterSemitoneOffset, true); this, [this](float value) {
bindSlider(ui_->sliderMasterPitch, ParamId::MasterPitchOffset); audio_->parameters()->set(ParamId::MasterOctaveOffset, value);
bindSlider(ui_->sliderOsc1Octave, ParamId::Osc1OctaveOffset, true); ui_->sliderMasterOctave->setResolution();
bindSlider(ui_->sliderOsc1Semitone, ParamId::Osc1SemitoneOffset, true); });
bindSlider(ui_->sliderOsc1Pitch, ParamId::Osc1PitchOffset); connect(ui_->sliderMasterSemitone, &SmartSlider::valueChanged,
bindSlider(ui_->sliderOsc2Octave, ParamId::Osc2OctaveOffset, true); this, [this](float value) {
bindSlider(ui_->sliderOsc2Semitone, ParamId::Osc2SemitoneOffset, true); audio_->parameters()->set(ParamId::MasterSemitoneOffset, value);
bindSlider(ui_->sliderOsc2Pitch, ParamId::Osc2PitchOffset); ui_->sliderMasterSemitone->setResolution();
bindSlider(ui_->sliderOsc3Octave, ParamId::Osc3OctaveOffset, true); });
bindSlider(ui_->sliderOsc3Semitone, ParamId::Osc3SemitoneOffset, true); connect(ui_->sliderMasterPitch, &SmartSlider::valueChanged,
bindSlider(ui_->sliderOsc3Pitch, ParamId::Osc3PitchOffset); 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);
});
// synth business // synth business
audio_->start(); audio_->start();
@@ -108,7 +157,6 @@ 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"];
@@ -161,12 +209,3 @@ 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,10 +4,9 @@
#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; }
@@ -30,8 +29,6 @@ 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>