From 68d420cf4129546e6ce765314bdfb2f79bc5ffd4 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Sat, 17 Dec 2022 21:56:23 +0100 Subject: [PATCH] Implemented midi output for feedback on color pads --- DrumMachine.pro | 2 + mainwindow.cpp | 91 ++++++++++++++++++++++++++++++++++++++++------ mainwindow.h | 7 +++- mainwindow.ui | 57 +++++++++++++++++++++++++---- midiinwrapper.cpp | 7 +++- midiinwrapper.h | 2 +- midioutwrapper.cpp | 58 +++++++++++++++++++++++++++++ midioutwrapper.h | 24 ++++++++++++ sampleswidget.cpp | 7 ++++ sampleswidget.h | 5 +++ samplewidget.cpp | 39 ++++++++++++++++++++ samplewidget.h | 5 +++ 12 files changed, 281 insertions(+), 23 deletions(-) create mode 100644 midioutwrapper.cpp create mode 100644 midioutwrapper.h diff --git a/DrumMachine.pro b/DrumMachine.pro index 8d3d194..4224bd3 100755 --- a/DrumMachine.pro +++ b/DrumMachine.pro @@ -23,6 +23,7 @@ SOURCES += \ mainwindow.cpp \ midicontainers.cpp \ midiinwrapper.cpp \ + midioutwrapper.cpp \ presetdetailwidget.cpp \ presets.cpp \ presetsmodel.cpp \ @@ -47,6 +48,7 @@ HEADERS += \ mainwindow.h \ midicontainers.h \ midiinwrapper.h \ + midioutwrapper.h \ presetdetailwidget.h \ presets.h \ presetsmodel.h \ diff --git a/mainwindow.cpp b/mainwindow.cpp index 86b3668..cf644f8 100755 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -14,7 +14,10 @@ #include "midicontainers.h" namespace { -void DummyDeleter(PaStream *stream) {} +void DummyDeleter(PaStream *stream) +{ + Q_UNUSED(stream); +} void PaStreamCloser(PaStream *stream) { if (PaError err = Pa_CloseStream(stream); err != paNoError) @@ -74,22 +77,46 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par connect(m_ui->sequencerWidget, &SequencerWidget::triggerSample, m_ui->samplesWidget, &SamplesWidget::sequencerTriggerSample); - updateMidiDevices(); + updateMidiInDevices(); - connect(m_ui->pushButtonRefreshMidiControllers, &QAbstractButton::pressed, this, &MainWindow::updateMidiDevices); + updateMidiOutDevices(); - connect(m_ui->pushButtonMidiController, &QAbstractButton::pressed, this, [this](){ + connect(m_ui->pushButtonRefreshMidiIn, &QAbstractButton::pressed, this, &MainWindow::updateMidiInDevices); + + connect(m_ui->pushButtonRefreshMidiOut, &QAbstractButton::pressed, this, &MainWindow::updateMidiOutDevices); + + connect(m_ui->pushButtonMidiIn, &QAbstractButton::pressed, this, [this](){ if (m_midiIn.isPortOpen()) m_midiIn.closePort(); else { - const auto index = m_ui->comboBoxMidiController->currentIndex(); + const auto index = m_ui->comboBoxMidiIn->currentIndex(); if (index != -1) - m_midiIn.openPort(index); + m_midiIn.openPort(index, "DrumMachine"); } - m_ui->comboBoxMidiController->setDisabled(m_midiIn.isPortOpen()); - m_ui->pushButtonMidiController->setText(m_midiIn.isPortOpen() ? tr("Close") : tr("Open")); + m_ui->comboBoxMidiIn->setDisabled(m_midiIn.isPortOpen()); + m_ui->pushButtonMidiIn->setText(m_midiIn.isPortOpen() ? tr("Close") : tr("Open")); + }); + + connect(m_ui->pushButtonMidiOut, &QAbstractButton::pressed, this, [this](){ + if (m_midiOut.isPortOpen()) + { + qDebug() << "closing port"; + m_midiOut.closePort(); + } + else + { + const auto index = m_ui->comboBoxMidiOut->currentIndex(); + if (index != -1) + { + m_midiOut.openPort(index, "DrumMachine"); + sendColors(); + } + } + + m_ui->comboBoxMidiOut->setDisabled(m_midiOut.isPortOpen()); + m_ui->pushButtonMidiOut->setText(m_midiOut.isPortOpen() ? tr("Close") : tr("Open")); }); updateAudioDevices(); @@ -114,6 +141,8 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par connect(m_ui->presetsView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MainWindow::currentRowChanged); loadSettings(); + + connect(m_ui->samplesWidget, &SamplesWidget::sendMidi, this, &MainWindow::sendMidi); } MainWindow::~MainWindow() @@ -232,14 +261,30 @@ void MainWindow::currentRowChanged(const QModelIndex ¤t) m_ui->samplesWidget->setPreset(preset); } -void MainWindow::updateMidiDevices() +void MainWindow::sendMidi(const midi::MidiMessage &midiMsg) { - m_ui->comboBoxMidiController->clear(); + if (m_midiOut.isPortOpen()) + m_midiOut.sendMessage(midiMsg); +} + +void MainWindow::updateMidiInDevices() +{ + m_ui->comboBoxMidiIn->clear(); for (const auto &name : m_midiIn.portNames()) - m_ui->comboBoxMidiController->addItem(name); + m_ui->comboBoxMidiIn->addItem(name); - m_ui->pushButtonMidiController->setEnabled(m_ui->comboBoxMidiController->count() > 0); + m_ui->pushButtonMidiIn->setEnabled(m_ui->comboBoxMidiIn->count() > 0); +} + +void MainWindow::updateMidiOutDevices() +{ + m_ui->comboBoxMidiOut->clear(); + + for (const auto &name : m_midiOut.portNames()) + m_ui->comboBoxMidiOut->addItem(name); + + m_ui->pushButtonMidiOut->setEnabled(m_ui->comboBoxMidiOut->count() > 0); } void MainWindow::updateAudioDevices() @@ -260,3 +305,25 @@ void MainWindow::loadSettings() m_synthisizer.loadSettings(m_settings); m_ui->samplesWidget->loadSettings(m_settings); } + +void MainWindow::sendColors() +{ + m_ui->samplesWidget->sendColors(); + return; + + int k{0}; + for (int j = 0; j < 128; j+= 16) + { + qDebug() << k; + for (auto i = 0; i < 8; i++) + { + midi::MidiMessage midiMsg; + midiMsg.channel = 0; + midiMsg.cmd = midi::Command::NoteOn; + midiMsg.flag = true; + midiMsg.note = i + j; + midiMsg.velocity = k++; + sendMidi(midiMsg); + } + } +} diff --git a/mainwindow.h b/mainwindow.h index e18713e..3d3c1da 100755 --- a/mainwindow.h +++ b/mainwindow.h @@ -12,6 +12,7 @@ #include "presetsmodel.h" #include "filesmodel.h" #include "midiinwrapper.h" +#include "midioutwrapper.h" #include "synthisizer.h" #include "drummachinesettings.h" @@ -35,11 +36,14 @@ private slots: void openAudioDevice(); void messageReceived(const midi::MidiMessage &message); void currentRowChanged(const QModelIndex ¤t); + void sendMidi(const midi::MidiMessage &midiMsg); private: - void updateMidiDevices(); + void updateMidiInDevices(); + void updateMidiOutDevices(); void updateAudioDevices(); void loadSettings(); + void sendColors(); const std::unique_ptr m_ui; @@ -48,6 +52,7 @@ private: std::unique_ptr m_paStream; MidiInWrapper m_midiIn; + MidiOutWrapper m_midiOut; QThread m_decoderThread; diff --git a/mainwindow.ui b/mainwindow.ui index 43173e1..3f90aee 100755 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 1017 - 712 + 1520 + 890 @@ -99,10 +99,10 @@ - + - + 32 @@ -115,7 +115,7 @@ - + Open @@ -135,6 +135,49 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Midi out: + + + + + + + + + + + 32 + 16777215 + + + + + + + + + + + Open + + + @@ -278,8 +321,8 @@ 0 0 - 1017 - 20 + 1520 + 23 diff --git a/midiinwrapper.cpp b/midiinwrapper.cpp index 969f98a..204c6e9 100755 --- a/midiinwrapper.cpp +++ b/midiinwrapper.cpp @@ -12,9 +12,12 @@ MidiInWrapper::MidiInWrapper(RtMidi::Api api, const QString& clientName, unsigne midiIn.setCallback(&mycallback, this); } -void MidiInWrapper::openPort(unsigned int portNumber) +void MidiInWrapper::openPort(unsigned int portNumber, const QString &portName) { - midiIn.openPort(portNumber); + qDebug() << "opening" << QString::fromStdString(midiIn.getPortName(portNumber)); + midiIn.openPort(portNumber, portName.toStdString()); + if (!midiIn.isPortOpen()) + qFatal("port did not open"); } void MidiInWrapper::openVirtualPort(const QString &portName) diff --git a/midiinwrapper.h b/midiinwrapper.h index d7423bc..9f1c117 100755 --- a/midiinwrapper.h +++ b/midiinwrapper.h @@ -17,7 +17,7 @@ public: unsigned int queueSizeLimit = 100, QObject *parent = nullptr); - void openPort(unsigned int portNumber); + void openPort(unsigned int portNumber, const QString &portName); void openVirtualPort(const QString &portName); void closePort(); bool isPortOpen() const; diff --git a/midioutwrapper.cpp b/midioutwrapper.cpp new file mode 100644 index 0000000..b3166d1 --- /dev/null +++ b/midioutwrapper.cpp @@ -0,0 +1,58 @@ +#include "midioutwrapper.h" + +#include + +MidiOutWrapper::MidiOutWrapper(RtMidi::Api api, const QString &clientName) : + midiOut{api, clientName.toStdString()} +{ +} + +void MidiOutWrapper::openPort(unsigned int portNumber, const QString &portName) +{ + qDebug() << "opening" << QString::fromStdString(midiOut.getPortName(portNumber)); + midiOut.openPort(portNumber, portName.toStdString()); + if (!midiOut.isPortOpen()) + qFatal("port did not open"); +} + +void MidiOutWrapper::openVirtualPort(const QString &portName) +{ + midiOut.openVirtualPort(portName.toStdString()); +} + +void MidiOutWrapper::closePort() +{ + midiOut.closePort(); +} + +bool MidiOutWrapper::isPortOpen() const +{ + return midiOut.isPortOpen(); +} + +QStringList MidiOutWrapper::portNames() +{ + QStringList names; + + const auto count = midiOut.getPortCount(); + + for (unsigned int i = 0; i < count; i++) + names.append(QString::fromStdString(midiOut.getPortName(i))); + + return names; +} + +void MidiOutWrapper::sendMessage(const midi::MidiMessage &midiMsg) +{ + union Helper { + Helper() {} + + midi::MidiMessage midiMsg; + unsigned char buf[sizeof(midiMsg)]; + }; + + Helper helper; + helper.midiMsg = midiMsg; + + midiOut.sendMessage(helper.buf, sizeof(helper.buf)); +} diff --git a/midioutwrapper.h b/midioutwrapper.h new file mode 100644 index 0000000..c43cafb --- /dev/null +++ b/midioutwrapper.h @@ -0,0 +1,24 @@ +#pragma once + +#include "rtmidi/RtMidi.h" + +#include "midicontainers.h" + +class MidiOutWrapper +{ +public: + MidiOutWrapper(RtMidi::Api api = RtMidi::UNSPECIFIED, + const QString &clientName = "RtMidi Input Client"); + + void openPort(unsigned int portNumber, const QString &portName); + void openVirtualPort(const QString &portName); + void closePort(); + bool isPortOpen() const; + + QStringList portNames(); + + void sendMessage(const midi::MidiMessage &midiMsg); + +private: + RtMidiOut midiOut; +}; diff --git a/sampleswidget.cpp b/sampleswidget.cpp index f9be185..646b52d 100755 --- a/sampleswidget.cpp +++ b/sampleswidget.cpp @@ -26,6 +26,7 @@ SamplesWidget::SamplesWidget(QWidget *parent) : widget.setPadNr(padNr++); widget.injectNetworkAccessManager(m_networkAccessManager); connect(&widget, &SampleWidget::chokeTriggered, this, &SamplesWidget::chokeTriggered); + connect(&widget, &SampleWidget::sendMidi, this, &SamplesWidget::sendMidi); } } @@ -83,6 +84,12 @@ void SamplesWidget::injectDecodingThread(QThread &thread) widget.injectDecodingThread(thread); } +void SamplesWidget::sendColors() +{ + for (SampleWidget &widget : getWidgets()) + widget.sendColor(); +} + void SamplesWidget::sequencerTriggerSample(int index) { const auto widgets = getWidgets(); diff --git a/sampleswidget.h b/sampleswidget.h index 3933f10..b700f78 100755 --- a/sampleswidget.h +++ b/sampleswidget.h @@ -34,6 +34,11 @@ public: void injectDecodingThread(QThread &thread); + void sendColors(); + +signals: + void sendMidi(const midi::MidiMessage &midiMsg); + public slots: void sequencerTriggerSample(int index); diff --git a/samplewidget.cpp b/samplewidget.cpp index 3d3b045..951b6f7 100755 --- a/samplewidget.cpp +++ b/samplewidget.cpp @@ -11,6 +11,7 @@ #include "audiodecoder.h" #include "drummachinesettings.h" +#include "midicontainers.h" namespace { QString toString(QString value) { return value; } @@ -182,9 +183,45 @@ void SampleWidget::learn(quint8 channel, quint8 note) learnPressed(); } +void SampleWidget::sendColor() +{ + midi::MidiMessage midiMsg; + + midiMsg.channel = m_ui->channelSpinBox->value(); + midiMsg.cmd = midi::Command::NoteOn; + midiMsg.flag = true; + midiMsg.note = m_ui->noteSpinBox->value(); + + if (m_file && m_file->color && m_player.buffer().isValid()) + { + const auto &color = *m_file->color; + if (color == "purple") + midiMsg.velocity = m_player.playing() ? 43 : 18; + else if (color == "red") + midiMsg.velocity = m_player.playing() ? 3 : 1; + else if (color == "yellow") + midiMsg.velocity = m_player.playing() ? 58 : 33; + else if (color == "green") + midiMsg.velocity = m_player.playing() ? 56 : 16; + else if (color == "blue") + midiMsg.velocity = m_player.playing() ? 49 : 51; + else + goto noColor; + } + else + { + noColor: + midiMsg.velocity = 0; + } + + emit sendMidi(midiMsg); +} + void SampleWidget::updateStatus() { QPalette pal; + + if (m_file && m_file->color && m_player.buffer().isValid()) { const auto bright = m_player.playing() ? 255 : 155; @@ -212,6 +249,8 @@ void SampleWidget::updateStatus() } setPalette(pal); + sendColor(); + if (m_reply) { if (!m_reply->isFinished()) diff --git a/samplewidget.h b/samplewidget.h index a738c99..495d46f 100755 --- a/samplewidget.h +++ b/samplewidget.h @@ -7,6 +7,7 @@ #include "audioformat.h" #include "presets.h" #include "audioplayer.h" +#include "midicontainers.h" namespace Ui { class SampleWidget; } class QNetworkAccessManager; @@ -60,6 +61,10 @@ public: signals: void chokeTriggered(int choke); void startDecoding(const std::shared_ptr &device); + void sendMidi(const midi::MidiMessage &midiMsg); + +public slots: + void sendColor(); private slots: void updateStatus();