From 397e1fe7dcc3063de5920477df3c0ca7576a178f Mon Sep 17 00:00:00 2001 From: homeburger Date: Sat, 13 Jun 2026 22:04:27 -0500 Subject: [PATCH] midi checkpoint --- CMakeLists.txt | 1 + README.md | 5 ++++- src/main.cpp | 5 +++-- src/synth/MidiController.cpp | 14 ++++++++++---- src/synth/MidiController.hpp | 11 ++++++++--- ui/Main.qml | 3 +++ 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dee1fba..17df385 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,7 @@ if (WIN32) TARGET sonobulus POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ + $ $ $ ) diff --git a/README.md b/README.md index f614f9d..da69852 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,12 @@ $ ./build/sonobulus ``` ## Configurations -There is a plan eventually to use config files so app behavior can be tweaked without needing to recompile. Will flesh out more once more of the app's structure takes shape. +Configuration files are located in the config directory. They offer options to change the program's settings without recompiling. Eventually I might make a user's guide for the configurations, but there's not too much there right now. ## Instrument Profiles Later into the app's development I have this vision: the state-space model of an instrument can be saved to a file. The app can allow parameter tweaking and re-saving of an instruments matrices. Far far into the future there may be a dynamic process for constructing your own instrument models and a way for the app to load and play those instruments. At the end of the day, a state-space model is just a set of 2 n-dimensional matrix equations and a profile would only need to be able to represent each element in the matrices. + +## Note for Windows MIDI +If you intend to hook this instrument to a DAW or some kind of application that outputs midi (I use musescore), Windows takes an extra step (Linux and Mac works fine AFAIK). You need a program that creates a virtual MIDI port, like loopMIDI in order to hook the interfaces between apps. Installation is easy and just requires having the program open with one virtual midi port available. Then, attach the midi output in your DAW to the loopMIDI port and the synthesizer should pick it up on startup. \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d83a4ba..c680fcc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include "synth/AudioEngine.hpp" #include "synth/KeyboardController.hpp" #include "synth/Scope.hpp" +#include "synth/MidiController.hpp" int main(int argc, char* argv[]) { @@ -24,6 +25,7 @@ int main(int argc, char* argv[]) { NoteQueue queue = NoteQueue(); ScopeBuffer scopeBuffer = ScopeBuffer(512); KeyboardController keyboard(&config, &logger, &queue); + MidiController midi(&config, &logger, &queue); Synth synth(&config, &logger, &scopeBuffer, &queue); // audio synthesizer doohickey @@ -33,12 +35,11 @@ int main(int argc, char* argv[]) { // attach backend gui components qmlRegisterType("AppDemo", 1, 0, "TimerComponent"); qmlRegisterType("AppDemo", 1, 0, "Scope"); + // attach backend services to qml engine.rootContext()->setContextProperty("keyboardController", &keyboard); engine.rootContext()->setContextProperty("scopeBuffer", &scopeBuffer); // load qml - //engine.loadFromModule("sonobulus", "Main"); - //engine.load(QUrl("qrc:/Main.qml")); engine.load(QUrl::fromLocalFile("ui/Main.qml")); // ugh if(engine.rootObjects().isEmpty()) { diff --git a/src/synth/MidiController.cpp b/src/synth/MidiController.cpp index 9a36575..b00ace8 100644 --- a/src/synth/MidiController.cpp +++ b/src/synth/MidiController.cpp @@ -4,14 +4,20 @@ #include #include -MidiController::MidiController(NoteQueue& queue) : noteQueue_(queue) { +MidiController::MidiController(ConfigService* config, LoggerService* logger, NoteQueue* queue) : + config_(config), logger_(logger), noteQueue_(queue) { try { +#ifdef WIN32 + midiIn_ = std::make_unique(RtMidi::WINDOWS_MM); +#else midiIn_ = std::make_unique(RtMidi::LINUX_ALSA); +#endif midiIn_->ignoreTypes(false, false, false); } catch (RtMidiError& e) { std::cout << "RtMidi init failed: " << e.getMessage() << std::endl; } - // TODO: this still doesnt work on windows + + openDefaultPort(); } MidiController::~MidiController() { @@ -100,7 +106,7 @@ void MidiController::handleMessage(const std::vector& msg) { void MidiController::noteOn(uint8_t note, uint8_t vel) { sustainedNotes_.erase(note); - noteQueue_.push({ + noteQueue_->push({ NoteEventType::NoteOn, static_cast(note), vel / 127.0f, @@ -114,7 +120,7 @@ void MidiController::noteOff(uint8_t note) { sustainedNotes_.insert(note); return; } - noteQueue_.push({ + noteQueue_->push({ NoteEventType::NoteOff, static_cast(note), 0.0f, diff --git a/src/synth/MidiController.hpp b/src/synth/MidiController.hpp index 646fdfb..ef1ee9e 100644 --- a/src/synth/MidiController.hpp +++ b/src/synth/MidiController.hpp @@ -4,13 +4,16 @@ #include #include -#include "NoteQueue.hpp" #include +#include "NoteQueue.hpp" +#include "ConfigService.hpp" +#include "LoggerService.hpp" + class MidiController { public: - MidiController(NoteQueue& queue); + MidiController(ConfigService* config, LoggerService* logger, NoteQueue* queue); ~MidiController(); bool openDefaultPort(); @@ -27,7 +30,9 @@ private: void noteOff(uint8_t note); std::unique_ptr midiIn_; - NoteQueue& noteQueue_; + ConfigService* config_; + LoggerService* logger_; + NoteQueue* noteQueue_; bool sustainDown_ = false; std::unordered_set sustainedNotes_; diff --git a/ui/Main.qml b/ui/Main.qml index 74dc1ba..1d518a3 100644 --- a/ui/Main.qml +++ b/ui/Main.qml @@ -54,6 +54,9 @@ ApplicationWindow { width: 600 height: 300 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -200 // this timer triggers a constant update on the scope's canvas, calls its draw() each time Timer {