diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cb23b8..88ecb47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ qt_add_executable(metabolus src/main.cpp src/ui/MainWindow.cpp src/ui/MainWindow.h + src/ui/MainWindow.ui src/ParameterStore.h src/KeyboardController.cpp src/KeyboardController.h @@ -56,8 +57,16 @@ qt_add_executable(metabolus src/synth/Synth.cpp src/synth/Synth.h resources/resources.qrc - src/ui/MainWindow.ui - src/ui/SmartSlider.ui + src/ui/widgets/SmartSlider.cpp + src/ui/widgets/SmartSlider.h + src/ui/widgets/SmartSlider.ui +) + +set_target_properties(metabolus PROPERTIES AUTOUIC ON) + +target_include_directories(metabolus PRIVATE + ${CMAKE_SOURCE_DIR}/src/ui + ${CMAKE_SOURCE_DIR}/src/ui/widgets ) if (WIN32) diff --git a/src/ParameterStore.h b/src/ParameterStore.h index b55f0d0..48aa4a7 100644 --- a/src/ParameterStore.h +++ b/src/ParameterStore.h @@ -39,7 +39,7 @@ constexpr std::array(ParamId::Count)> PARAM_DEFS { { 10.0f, 0.0f, 1000.0f}, // Osc1VolumeEnvA, { 10.0f, 0.0f, 1000.0f}, // Osc1VolumeEnvD, { 10.0f, 0.0f, 1000.0f}, // Osc1VolumeEnvS, - { 10.0f, 0.0f, 1000.0f}, // Osc1VolumeEnvR, + { 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvR, { 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvA, { 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvD, { 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvS, diff --git a/src/synth/AudioEngine.h b/src/synth/AudioEngine.h index 5bbd987..22cc184 100644 --- a/src/synth/AudioEngine.h +++ b/src/synth/AudioEngine.h @@ -29,15 +29,15 @@ private: // RtAudio binding for passing samples static int32_t audioCallback(void* outputBuffer, void* inputBuffer, uint32_t nFrames, double streamTime, RtAudioStreamStatus status, void* userData); - // calls the synth.process to generate a buffer audio samples + // calls the synth.process to generate a buffer of audio samples int32_t process(float* out, uint32_t nFrames); - ParameterStore params_; - NoteQueue noteQueue_; - Synth synth_; + ParameterStore params_; // stores the control parameters + NoteQueue noteQueue_; // stores note events for passing between threads + Synth synth_; // generates audio + RtAudio audio_; // audio device // TODO: id like a yml config file or something for these - RtAudio audio_; uint32_t sampleRate_ = 44100; uint32_t bufferFrames_ = 256; // time per buffer = BF/SR (256/44100 = 5.8ms) uint32_t channels_ = 2; // stereo diff --git a/src/synth/Envelope.h b/src/synth/Envelope.h index 02b64ce..3d9a858 100644 --- a/src/synth/Envelope.h +++ b/src/synth/Envelope.h @@ -21,6 +21,7 @@ public: // setters void setSampleRate(float sampleRate) { sampleRate_ = sampleRate; } + void set(float a, float d, float s, float r) { setAttack(a); setDecay(a); setSustain(a); setRelease(a); } void setAttack(float seconds) { attack_ = std::max(seconds, 0.0001f); } void setDecay(float seconds) { decay_ = std::max(seconds, 0.0001f); } void setSustain(float level) { sustain_ = level; } diff --git a/src/synth/Synth.cpp b/src/synth/Synth.cpp index 347e626..262351d 100644 --- a/src/synth/Synth.cpp +++ b/src/synth/Synth.cpp @@ -73,6 +73,8 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) { for(auto& p : params_) p.update(); // TODO: profile this // process all envelopes + //gainEnvelope_.set(0.05f, 0.2f, 0.7f, getParam(ParamId::Osc1VolumeEnvR)); + gainEnvelope_.setRelease(getParam(ParamId::Osc1VolumeEnvR)); float gain = gainEnvelope_.process(); // skip if no active notes @@ -85,10 +87,13 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) { float phaseInc = 2.0f * M_PI * frequency_ / static_cast(sampleRate); // sample generation + // TODO: wavetables + // TODO: wavetables should be scaled by their RMS for equal loudness (prelim standard = 0.707) float sineSample = std::sin(phase_); float squareSample = -0.707f; if(phase_ >= M_PI) squareSample = 0.707f; - sampleOut = squareSample * gain; + float sawSample = phase_ * 4.0f / M_PI * frequency_ / static_cast(sampleRate); + sampleOut = sawSample * gain; // write to buffer out[2*i] = sampleOut; // left diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index b5afbc9..ed34a43 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -1,9 +1,8 @@ #include "MainWindow.h" - -#include #include "ui_MainWindow.h" +#include "SmartSlider.h" #include "../ParameterStore.h" MainWindow::MainWindow(QWidget *parent) : @@ -22,25 +21,18 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui_->buttonIncrement, &QPushButton::clicked, this, &MainWindow::onIncrementClicked); connect(ui_->buttonReset, &QPushButton::clicked, this, &MainWindow::onResetClicked); - // slider business - // TODO: smart slider widget - connect(ui_->slider, &QSlider::valueChanged, this, &MainWindow::onSliderValueChanged); - connect(ui_->inputMin, &QLineEdit::editingFinished, this, &MainWindow::onMinChanged); - connect(ui_->inputMax, &QLineEdit::editingFinished, this, &MainWindow::onMaxChanged); - connect(ui_->inputStep, &QLineEdit::editingFinished, this, &MainWindow::onStepChanged); - connect(ui_->inputValue, &QLineEdit::editingFinished, this, &MainWindow::onValueChanged); - // synth business audio_->start(); - // init defaults - // TODO:: there's gotta be a better way - ui_->slider->setValue(PARAM_DEFS[static_cast(ParamId::Osc1Frequency)].def); - ui_->slider->setMinimum(PARAM_DEFS[static_cast(ParamId::Osc1Frequency)].min); - ui_->slider->setMaximum(PARAM_DEFS[static_cast(ParamId::Osc1Frequency)].max); - ui_->inputValue->setText(QString::number(PARAM_DEFS[static_cast(ParamId::Osc1Frequency)].def)); - ui_->inputMin->setText(QString::number(PARAM_DEFS[static_cast(ParamId::Osc1Frequency)].min)); - ui_->inputMax->setText(QString::number(PARAM_DEFS[static_cast(ParamId::Osc1Frequency)].max)); + // slider business + connect(ui_->sliderGainR, &SmartSlider::valueChanged, this, [this](float v) { + audio_->parameters()->set(ParamId::Osc1VolumeEnvR, v); // bind valueChanged signal to the ParameterStore + }); + // initialize to defaults + ui_->sliderGainR->setRange(PARAM_DEFS[static_cast(ParamId::Osc1VolumeEnvR)].min, PARAM_DEFS[static_cast(ParamId::Osc1VolumeEnvR)].max); + ui_->sliderGainR->setValue(PARAM_DEFS[static_cast(ParamId::Osc1VolumeEnvR)].def); + // TODO: package a smartSlider into an envelope controller widget + // then intialization and parameter bindings can be done from the envelope controller } MainWindow::~MainWindow() { @@ -68,51 +60,3 @@ void MainWindow::onResetClicked() { void MainWindow::updateCounterLabel() { ui_->labelCounter->setText(QString::number(counter_)); } - -// allows only numbers to be set -void MainWindow::onSliderValueChanged(int value) { - QSignalBlocker blocker(ui_->inputValue); - ui_->inputValue->setText(QString::number(value)); - - // forward value so synthesizer can read - audio_->parameters()->set(ParamId::Osc1Frequency, static_cast(value)); -} - -// allows only values within the min, max to be set by the text field -void MainWindow::onValueChanged() { - bool ok = false; - int value = ui_->inputValue->text().toInt(&ok); - if(!ok) return; - - value = qBound(ui_->slider->minimum(), value, ui_->slider->maximum()); - ui_->slider->setValue(value); -} - -void MainWindow::applySliderRange() { - bool minOk, maxOk; - int min = ui_->inputMin->text().toInt(&minOk); - int max = ui_->inputMax->text().toInt(&maxOk); - if(!minOk || !maxOk || min > max) return; - - ui_->slider->setRange(min, max); - - syncValueToUi(ui_->slider->value()); -} - -void MainWindow::applySliderStep() { - bool ok; - int step = ui_->inputStep->text().toInt(&ok); - if(!ok || step <= 0) return; - - ui_->slider->setSingleStep(step); - ui_->slider->setPageStep(step); -} - -void MainWindow::syncValueToUi(int value) { - QSignalBlocker block1(ui_->slider); - QSignalBlocker block2(ui_->inputValue); - - value = qBound(ui_->slider->minimum(), value, ui_->slider->maximum()); - ui_->slider->setValue(value); - ui_->inputValue->setText(QString::number(value)); -} diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h index 78ee455..f8066e7 100644 --- a/src/ui/MainWindow.h +++ b/src/ui/MainWindow.h @@ -7,13 +7,11 @@ #include "../synth/AudioEngine.h" QT_BEGIN_NAMESPACE -namespace Ui { -class MainWindow; -} +namespace Ui { class MainWindow; } QT_END_NAMESPACE -class MainWindow : public QMainWindow -{ +class MainWindow : public QMainWindow { + Q_OBJECT public: @@ -28,12 +26,6 @@ private slots: void onIncrementClicked(); void onResetClicked(); - void onSliderValueChanged(int value); - void onMinChanged() { applySliderRange(); } - void onMaxChanged() { applySliderRange(); } - void onStepChanged() { applySliderStep(); } - void onValueChanged(); - private: Ui::MainWindow *ui_; int counter_ = 0; @@ -42,10 +34,6 @@ private: void updateCounterLabel(); - void applySliderRange(); - void applySliderStep(); - void syncValueToUi(int value); - AudioEngine* audio_ = nullptr; KeyboardController keyboard_; diff --git a/src/ui/MainWindow.ui b/src/ui/MainWindow.ui index 06bc7ce..7c22374 100644 --- a/src/ui/MainWindow.ui +++ b/src/ui/MainWindow.ui @@ -7,7 +7,7 @@ 0 0 800 - 600 + 605 @@ -71,124 +71,26 @@ Qt::AlignmentFlag::AlignCenter - + - 390 - 210 - 16 - 160 + 340 + 120 + 171 + 321 - - 50 - - - Qt::Orientation::Vertical - - - - - - 370 - 190 - 61 - 21 - - - - - 12 - PreferDefault - - - - Qt::LayoutDirection::LeftToRight - - - 100 - - - Qt::AlignmentFlag::AlignCenter - - - - - - 370 - 170 - 61 - 21 - - - - - 12 - PreferDefault - - - - Qt::LayoutDirection::LeftToRight - - - 100 - - - Qt::AlignmentFlag::AlignCenter - - - - - - 360 - 140 - 81 - 31 - - - - - 16 - PreferDefault - - - - Qt::LayoutDirection::LeftToRight - - - 100 - - - Qt::AlignmentFlag::AlignCenter - - - - - - 370 - 370 - 61 - 21 - - - - - 12 - PreferDefault - - - - Qt::LayoutDirection::LeftToRight - - - 100 - - - Qt::AlignmentFlag::AlignCenter - + + + SmartSlider + QWidget +
SmartSlider.h
+ 1 +
+
diff --git a/src/ui/SmartSlider.ui b/src/ui/SmartSlider.ui deleted file mode 100644 index e69de29..0000000 diff --git a/src/ui/widgets/SmartSlider.cpp b/src/ui/widgets/SmartSlider.cpp new file mode 100644 index 0000000..7fd0e33 --- /dev/null +++ b/src/ui/widgets/SmartSlider.cpp @@ -0,0 +1,88 @@ + +#include "SmartSlider.h" +#include "ui_SmartSlider.h" + +#include + +SmartSlider::SmartSlider(QWidget* parent) : QWidget(parent), ui_(new Ui::SmartSlider) { + + ui_->setupUi(this); + + ui_->slider->setMinimum(0); + ui_->slider->setMaximum(sliderResolution_); + + // connect widgets to slots + connect(ui_->slider, &QSlider::valueChanged, this, &SmartSlider::onSliderChanged); + connect(ui_->spinValue, QOverload::of(&QDoubleSpinBox::valueChanged), this, &SmartSlider::onSpinValueChanged); + connect(ui_->spinMin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &SmartSlider::onRangeChanged); + connect(ui_->spinMax, QOverload::of(&QDoubleSpinBox::valueChanged), this, &SmartSlider::onRangeChanged); +} + +SmartSlider::~SmartSlider() { + delete ui_; +} + +// sets range of the slider, updates the spinBoxes, and clamps output, called by other classes +// probably overengineered +void SmartSlider::setRange(float min, float max) { + if (min >= max) return; + + min_ = min; max_ = max; + + ui_->slider->setRange(0, sliderResolution_); + ui_->spinMin->setValue(min_); + ui_->spinMax->setValue(max_); + //ui_->spinValue->setRange(min_, max_); + + float value = ui_->spinValue->value(); + value = std::clamp(value, min_, max_); + + float t = (value - min_) / (max_ - min_); + int32_t sliderValue = static_cast(t) * sliderResolution_; + + ui_->slider->setValue(sliderValue); + ui_->spinValue->setValue(value); +} + +// sets value of the slider and the spinBox, called by other classes +void SmartSlider::setValue(float value) { + value = std::clamp(value, min_, max_); + + float t = (value - min_) / (max_ - min_); + int32_t sliderValue = static_cast(t) * sliderResolution_; + + ui_->slider->setValue(sliderValue); + ui_->spinValue->setValue(value); + + emit valueChanged(value); +} + +void SmartSlider::onSliderChanged(int32_t v) { + float min = ui_->spinMin->value(); + float max = ui_->spinMax->value(); + float value = min + (max - min) * static_cast(v) / sliderResolution_; + + ui_->spinValue->setValue(value); + + emit valueChanged(value); +} + +void SmartSlider::onSpinValueChanged(float v) { + float min = ui_->spinMin->value(); + float max = ui_->spinMax->value(); + + float norm = (v - min) / (max - min); + int32_t sliderValue = qRound(norm * sliderResolution_); + + ui_->slider->setValue(sliderValue); + + // intentionally skips clamping by min/max + emit valueChanged(v); +} + +void SmartSlider::onRangeChanged() { + float min = ui_->spinMin->value(); + float max = ui_->spinMax->value(); + + onSpinValueChanged(ui_->spinValue->value()); +} diff --git a/src/widgets/SmartSlider.h b/src/ui/widgets/SmartSlider.h similarity index 59% rename from src/widgets/SmartSlider.h rename to src/ui/widgets/SmartSlider.h index cb025a8..cf49f87 100644 --- a/src/widgets/SmartSlider.h +++ b/src/ui/widgets/SmartSlider.h @@ -7,19 +7,18 @@ QT_BEGIN_NAMESPACE namespace Ui { class SmartSlider; } QT_END_NAMESPACE +// SmartSlider is the widget including a slider, min/max settings, and a value setting parameter class SmartSlider : public QWidget { Q_OBJECT public: explicit SmartSlider(QWidget* parent = nullptr); - ~SmartSlider() = default; + ~SmartSlider(); + // setters void setRange(float min, float max); - void setStep(float step); void setValue(float value); - void value() const; - signals: void valueChanged(float value); @@ -32,6 +31,12 @@ private: Ui::SmartSlider* ui_; + // slider just does ints from 0 to this value + // all the floating point business happens in here int sliderResolution_ = 10000; + float min_; + float max_; + // I got rid of step because it just didn't work right and I really didn't need it + }; \ No newline at end of file diff --git a/src/ui/widgets/SmartSlider.ui b/src/ui/widgets/SmartSlider.ui new file mode 100644 index 0000000..4ff7028 --- /dev/null +++ b/src/ui/widgets/SmartSlider.ui @@ -0,0 +1,96 @@ + + + SmartSlider + + + + 0 + 0 + 168 + 298 + + + + Form + + + + + 70 + 90 + 16 + 160 + + + + Qt::Orientation::Vertical + + + false + + + + + + 40 + 260 + 82 + 24 + + + + + + + 40 + 60 + 82 + 24 + + + + + + + 40 + 10 + 82 + 31 + + + + + 12 + + + + + + + 20 + 10 + 20 + 271 + + + + Qt::Orientation::Vertical + + + + + + 120 + 10 + 20 + 271 + + + + Qt::Orientation::Vertical + + + + + + diff --git a/src/widgets/SmartSlider.cpp b/src/widgets/SmartSlider.cpp deleted file mode 100644 index 1af64eb..0000000 --- a/src/widgets/SmartSlider.cpp +++ /dev/null @@ -1,35 +0,0 @@ - -#include "SmartSlider.h" - -SmartSlider(QWidget* parent = nullptr) { - -} - -void SmartSlider::setRange(float min, float max) { - -} - -void SmartSlider::setStep(float step) { - -} - -void SmartSlider::setValue(float value) { - -} - -void SmartSlider::value() const; -void SmartSlider::valueChanged(float value) { - -} - -void SmartSlider::onSliderChanged(int v) { - -} - -void SmartSlider::onSpinValueChanged(float v) { - -} - -void SmartSlider::onRangeChanged() { - -}