From 4c6ec3a019822487bdd8681e676008600528fb98 Mon Sep 17 00:00:00 2001 From: Bliblank Date: Mon, 29 Dec 2025 00:44:43 -0600 Subject: [PATCH] prelim midi controller --- CMakeLists.txt | 32 +++++++++++++++-- scripts/build.bat | 5 ++- src/MidiController.cpp | 78 ++++++++++++++++++++++++++++++++++++++++++ src/MidiController.h | 28 +++++++++++++++ src/ui/MainWindow.cpp | 3 +- src/ui/MainWindow.h | 2 ++ 6 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 src/MidiController.cpp create mode 100644 src/MidiController.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cb3626f..4d656af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,9 +33,28 @@ if (WIN32) # windows 11 x86_64 # Public alias (this is where :: belongs) add_library(RtAudio::RtAudio ALIAS rtaudio) + add_library(rtmidi_headers INTERFACE) + target_include_directories(rtmidi_headers INTERFACE + "C:/rtmidi/include" + "C:/rtmidi/include/rtMidi" + ) + add_library(rtmidi_binary SHARED IMPORTED) + set_target_properties(rtmidi_binary PROPERTIES + IMPORTED_LOCATION "C:/rtmidi/bin/rtmidi.dll" + IMPORTED_IMPLIB "C:/rtmidi/lib/rtmidi.lib" + ) + add_library(rtmidi INTERFACE) + target_link_libraries(rtmidi INTERFACE + rtmidi_headers + rtmidi_binary + ) + add_library(RtMidi::RtMidi ALIAS rtmidi) + + else() # debian 12 x86_64 find_package(PkgConfig REQUIRED) pkg_check_modules(RTAUDIO REQUIRED rtaudio) + pkg_check_modules(RTMIDI REQUIRED rtmidi) endif() qt_standard_project_setup() @@ -49,6 +68,8 @@ qt_add_executable(metabolus src/ParameterStore.h src/KeyboardController.cpp src/KeyboardController.h + src/MidiController.cpp + src/MidiController.h src/NoteQueue.cpp src/NoteQueue.h src/synth/AudioEngine.cpp @@ -89,6 +110,7 @@ if (WIN32) PRIVATE Qt6::Widgets RtAudio::RtAudio + RtMidi::RtMidi ) add_custom_command(TARGET metabolus POST_BUILD @@ -97,8 +119,14 @@ if (WIN32) $ ) + add_custom_command(TARGET metabolus POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "C:/rtmidi/bin/rtmidi.dll" + $ + ) + else() - target_include_directories(metabolus PRIVATE ${RTAUDIO_INCLUDE_DIRS}) - target_link_libraries(metabolus PRIVATE Qt6::Widgets ${RTAUDIO_LIBRARIES}) + target_include_directories(metabolus PRIVATE ${RTAUDIO_INCLUDE_DIRS} ${RTMIDI_INCLUDE_DIRS}) + target_link_libraries(metabolus PRIVATE Qt6::Widgets ${RTAUDIO_LIBRARIES} ${RTMIDI_LIBARARIES}) target_compile_options(metabolus PRIVATE ${RTAUDIO_CFLAGS_OTHER}) endif() diff --git a/scripts/build.bat b/scripts/build.bat index 31080d8..15999a5 100644 --- a/scripts/build.bat +++ b/scripts/build.bat @@ -10,6 +10,7 @@ set CONFIG=Release set QT_ROOT=C:\Qt\6.10.1\msvc2022_64 set RTAUDIO_ROOT=C:\rtaudio +set RTMIDI_ROOT=C:\rtmidi REM ================================ REM Environment setup @@ -30,7 +31,8 @@ if not exist %BUILD_DIR% ( cmake -S . -B %BUILD_DIR% ^ -G Ninja ^ -DCMAKE_BUILD_TYPE=%CONFIG% ^ - -DRTAUDIO_ROOT=%RTAUDIO_ROOT% + -DRTAUDIO_ROOT=%RTAUDIO_ROOT% ^ + -DRTMIDI_ROOT=%RTMIDI_ROOT% if errorlevel 1 goto error @@ -51,6 +53,7 @@ cd %BUILD_DIR% windeployqt metabolus.exe copy "%RTAUDIO_ROOT%\bin\rtaudio.dll" . +copy "%RTMIDI_ROOT%\bin\rtmidi.dll" . echo. echo Build successful. diff --git a/src/MidiController.cpp b/src/MidiController.cpp new file mode 100644 index 0000000..982ab70 --- /dev/null +++ b/src/MidiController.cpp @@ -0,0 +1,78 @@ + +#include "MidiController.h" +#include + +MidiController::MidiController(NoteQueue& queue) : noteQueue_(queue) { + try { + midiIn_ = std::make_unique(); + midiIn_->ignoreTypes(false, false, false); + } catch (RtMidiError& e) { + std::cerr << "RtMidi init failed: " << e.getMessage() << std::endl; + } +} + +MidiController::~MidiController() { + close(); +} + +bool MidiController::openDefaultPort() { + if (!midiIn_) return false; + if (midiIn_->getPortCount() == 0) { + std::cerr << "No MIDI input ports available\n"; + return false; + } + return openPort(0); +} + +bool MidiController::openPort(unsigned int index) { + if (!midiIn_) return false; + + try { + midiIn_->openPort(index); + midiIn_->setCallback(&MidiController::midiCallback, this); + std::cout << "Opened MIDI port: " + << midiIn_->getPortName(index) << "\n"; + return true; + } catch (RtMidiError& e) { + std::cerr << e.getMessage() << std::endl; + return false; + } +} + +void MidiController::close() { + if (midiIn_ && midiIn_->isPortOpen()) { + midiIn_->closePort(); + } +} + +void MidiController::midiCallback(double /*deltaTime*/, std::vector* message, void* userData) { + auto* self = static_cast(userData); + if (!message || message->empty()) return; + self->handleMessage(*message); +} + +void MidiController::handleMessage(const std::vector& msg) { + unsigned char status = msg[0] & 0xF0; + unsigned char note = msg.size() > 1 ? msg[1] : 0; + unsigned char vel = msg.size() > 2 ? msg[2] : 0; + + // Note On (velocity > 0) + if (status == 0x90 && vel > 0) { + noteQueue_.push({ + NoteEventType::NoteOn, + static_cast(note), + vel / 127.0f + }); + } + // Note Off (or Note On with velocity 0) + else if (status == 0x80 || (status == 0x90 && vel == 0)) { + noteQueue_.push({ + NoteEventType::NoteOff, + static_cast(note), + 0.0f + }); + } + + // to have a peak :) + std::cout << msg[0] << std::endl; +} \ No newline at end of file diff --git a/src/MidiController.h b/src/MidiController.h new file mode 100644 index 0000000..3e2e1dd --- /dev/null +++ b/src/MidiController.h @@ -0,0 +1,28 @@ + +#pragma once + +#include +#include +#include "NoteQueue.h" + +class MidiController { +public: + explicit MidiController(NoteQueue& queue); + ~MidiController(); + + bool openDefaultPort(); + bool openPort(unsigned int index); + void close(); + +private: + static void midiCallback( + double deltaTime, + std::vector* message, + void* userData + ); + + void handleMessage(const std::vector& msg); + + std::unique_ptr midiIn_; + NoteQueue& noteQueue_; +}; diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index b382e0b..b1db327 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -9,7 +9,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui_(new Ui::MainWindow), audio_(new AudioEngine()), - keyboard_(audio_->noteQueue()) { + keyboard_(audio_->noteQueue()), + midi_(audio_->noteQueue()) { // initialize ui ui_->setupUi(this); diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h index c90629c..fe17f42 100644 --- a/src/ui/MainWindow.h +++ b/src/ui/MainWindow.h @@ -5,6 +5,7 @@ #include #include "../synth/AudioEngine.h" +#include "../MidiController.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } @@ -30,5 +31,6 @@ private: AudioEngine* audio_ = nullptr; KeyboardController keyboard_; + MidiController midi_; };