/* * Simple JACK Audio Meter * Copyright (C) 2011-2012 Filipe Coelho * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the COPYING file */ #include #ifndef Q_COMPILER_LAMBDA # define nullptr (0) #endif #define VERSION "1.9.4" #include "../jack_utils.hpp" #include "../midi_queue.hpp" #include "ui_xycontroller.h" #include #include #include #include #include #include #include #include #include // ------------------------------- float abs_f(const float value) { return (value < 1.0f) ? -value : value; } jack_client_t* jClient = nullptr; jack_port_t* jMidiInPort = nullptr; jack_port_t* jMidiOutPort = nullptr; static Queue qMidiInData; static Queue qMidiOutData; QVector MIDI_CC_LIST; void MIDI_CC_LIST__init() { //MIDI_CC_LIST << "0x00 Bank Select"; MIDI_CC_LIST << "0x01 Modulation"; MIDI_CC_LIST << "0x02 Breath"; MIDI_CC_LIST << "0x03 (Undefined)"; MIDI_CC_LIST << "0x04 Foot"; MIDI_CC_LIST << "0x05 Portamento"; //MIDI_CC_LIST << "0x06 (Data Entry MSB)"; MIDI_CC_LIST << "0x07 Volume"; MIDI_CC_LIST << "0x08 Balance"; MIDI_CC_LIST << "0x09 (Undefined)"; MIDI_CC_LIST << "0x0A Pan"; MIDI_CC_LIST << "0x0B Expression"; MIDI_CC_LIST << "0x0C FX Control 1"; MIDI_CC_LIST << "0x0D FX Control 2"; MIDI_CC_LIST << "0x0E (Undefined)"; MIDI_CC_LIST << "0x0F (Undefined)"; MIDI_CC_LIST << "0x10 General Purpose 1"; MIDI_CC_LIST << "0x11 General Purpose 2"; MIDI_CC_LIST << "0x12 General Purpose 3"; MIDI_CC_LIST << "0x13 General Purpose 4"; MIDI_CC_LIST << "0x14 (Undefined)"; MIDI_CC_LIST << "0x15 (Undefined)"; MIDI_CC_LIST << "0x16 (Undefined)"; MIDI_CC_LIST << "0x17 (Undefined)"; MIDI_CC_LIST << "0x18 (Undefined)"; MIDI_CC_LIST << "0x19 (Undefined)"; MIDI_CC_LIST << "0x1A (Undefined)"; MIDI_CC_LIST << "0x1B (Undefined)"; MIDI_CC_LIST << "0x1C (Undefined)"; MIDI_CC_LIST << "0x1D (Undefined)"; MIDI_CC_LIST << "0x1E (Undefined)"; MIDI_CC_LIST << "0x1F (Undefined)"; //MIDI_CC_LIST << "0x20 *Bank Select"; //MIDI_CC_LIST << "0x21 *Modulation"; //MIDI_CC_LIST << "0x22 *Breath"; //MIDI_CC_LIST << "0x23 *(Undefined)"; //MIDI_CC_LIST << "0x24 *Foot"; //MIDI_CC_LIST << "0x25 *Portamento"; //MIDI_CC_LIST << "0x26 *(Data Entry MSB)"; //MIDI_CC_LIST << "0x27 *Volume"; //MIDI_CC_LIST << "0x28 *Balance"; //MIDI_CC_LIST << "0x29 *(Undefined)"; //MIDI_CC_LIST << "0x2A *Pan"; //MIDI_CC_LIST << "0x2B *Expression"; //MIDI_CC_LIST << "0x2C *FX *Control 1"; //MIDI_CC_LIST << "0x2D *FX *Control 2"; //MIDI_CC_LIST << "0x2E *(Undefined)"; //MIDI_CC_LIST << "0x2F *(Undefined)"; //MIDI_CC_LIST << "0x30 *General Purpose 1"; //MIDI_CC_LIST << "0x31 *General Purpose 2"; //MIDI_CC_LIST << "0x32 *General Purpose 3"; //MIDI_CC_LIST << "0x33 *General Purpose 4"; //MIDI_CC_LIST << "0x34 *(Undefined)"; //MIDI_CC_LIST << "0x35 *(Undefined)"; //MIDI_CC_LIST << "0x36 *(Undefined)"; //MIDI_CC_LIST << "0x37 *(Undefined)"; //MIDI_CC_LIST << "0x38 *(Undefined)"; //MIDI_CC_LIST << "0x39 *(Undefined)"; //MIDI_CC_LIST << "0x3A *(Undefined)"; //MIDI_CC_LIST << "0x3B *(Undefined)"; //MIDI_CC_LIST << "0x3C *(Undefined)"; //MIDI_CC_LIST << "0x3D *(Undefined)"; //MIDI_CC_LIST << "0x3E *(Undefined)"; //MIDI_CC_LIST << "0x3F *(Undefined)"; //MIDI_CC_LIST << "0x40 Damper On/Off"; // <63 off, >64 on //MIDI_CC_LIST << "0x41 Portamento On/Off"; // <63 off, >64 on //MIDI_CC_LIST << "0x42 Sostenuto On/Off"; // <63 off, >64 on //MIDI_CC_LIST << "0x43 Soft Pedal On/Off"; // <63 off, >64 on //MIDI_CC_LIST << "0x44 Legato Footswitch"; // <63 Normal, >64 Legato //MIDI_CC_LIST << "0x45 Hold 2"; // <63 off, >64 on MIDI_CC_LIST << "0x46 Control 1 [Variation]"; MIDI_CC_LIST << "0x47 Control 2 [Timbre]"; MIDI_CC_LIST << "0x48 Control 3 [Release]"; MIDI_CC_LIST << "0x49 Control 4 [Attack]"; MIDI_CC_LIST << "0x4A Control 5 [Brightness]"; MIDI_CC_LIST << "0x4B Control 6 [Decay]"; MIDI_CC_LIST << "0x4C Control 7 [Vib Rate]"; MIDI_CC_LIST << "0x4D Control 8 [Vib Depth]"; MIDI_CC_LIST << "0x4E Control 9 [Vib Delay]"; MIDI_CC_LIST << "0x4F Control 10 [Undefined]"; MIDI_CC_LIST << "0x50 General Purpose 5"; MIDI_CC_LIST << "0x51 General Purpose 6"; MIDI_CC_LIST << "0x52 General Purpose 7"; MIDI_CC_LIST << "0x53 General Purpose 8"; MIDI_CC_LIST << "0x54 Portamento Control"; MIDI_CC_LIST << "0x5B FX 1 Depth [Reverb]"; MIDI_CC_LIST << "0x5C FX 2 Depth [Tremolo]"; MIDI_CC_LIST << "0x5D FX 3 Depth [Chorus]"; MIDI_CC_LIST << "0x5E FX 4 Depth [Detune]"; MIDI_CC_LIST << "0x5F FX 5 Depth [Phaser]"; } // ------------------------------- // XY Controller Scene class XYGraphicsScene : public QGraphicsScene { Q_OBJECT public: XYGraphicsScene(QWidget* parent) : QGraphicsScene(parent), m_parent(parent) { cc_x = 1; cc_y = 2; m_mouseLock = false; m_smooth = false; m_smooth_x = 0.0f; m_smooth_y = 0.0f; setBackgroundBrush(Qt::black); QPen cursorPen(QColor(255, 255, 255), 2); QColor cursorBrush(255, 255, 255, 50); m_cursor = addEllipse(QRectF(-10, -10, 20, 20), cursorPen, cursorBrush); QPen linePen(QColor(200, 200, 200, 100), 1, Qt::DashLine); m_lineH = addLine(-9999, 0, 9999, 0, linePen); m_lineV = addLine(0, -9999, 0, 9999, linePen); p_size = QRectF(-100, -100, 100, 100); } void setControlX(int x) { cc_x = x; } void setControlY(int y) { cc_y = y; } void setChannels(QList channels) { m_channels = channels; } void setPosX(float x, bool forward=true) { if (m_mouseLock) return; float posX = x * (p_size.x() + p_size.width()); m_cursor->setPos(posX, m_cursor->y()); m_lineV->setX(posX); if (forward) { float value = posX / (p_size.x() + p_size.width()); sendMIDI(&value, nullptr); } else m_smooth_x = posX; } void setPosY(float y, bool forward=true) { if (m_mouseLock) return; float posY = y * (p_size.y() + p_size.height()); m_cursor->setPos(m_cursor->x(), posY); m_lineH->setY(posY); if (forward) { float value = posY / (p_size.y() + p_size.height()); sendMIDI(nullptr, &value); } else m_smooth_y = posY; } void setSmooth(bool smooth) { m_smooth = smooth; } void setSmoothValues(float x, float y) { m_smooth_x = x * (p_size.x() + p_size.width()); m_smooth_y = y * (p_size.y() + p_size.height()); } void handleCC(int param, int value) { bool sendUpdate = false; float xp, yp; xp = yp = 0.0f; if (param == cc_x) { sendUpdate = true; xp = float(value)/63 - 1.0f; yp = m_cursor->y() / (p_size.y() + p_size.height()); setPosX(xp, false); } if (param == cc_y) { sendUpdate = true; xp = m_cursor->x() / (p_size.x() + p_size.width()); yp = float(value)/63 - 1.0f; setPosY(yp, false); } if (xp < -1.0f) xp = -1.0f; else if (xp > 1.0f) xp = 1.0f; if (yp < -1.0f) yp = -1.0f; else if (yp > 1.0f) yp = 1.0f; if (sendUpdate) emit cursorMoved(xp, yp); } void updateSize(QSize size) { p_size.setRect(-(float(size.width())/2), -(float(size.height())/2), size.width(), size.height()); } void updateSmooth() { if (! m_smooth) return; if (m_cursor->x() == m_smooth_x && m_cursor->y() == m_smooth_y) return; if (abs_f(m_cursor->x() - m_smooth_x) <= 0.0005f) m_smooth_x = m_cursor->x(); if (abs_f(m_cursor->y() - m_smooth_y) <= 0.0005f) m_smooth_y = m_cursor->y(); if (m_cursor->x() == m_smooth_x && m_cursor->y() == m_smooth_y) return; float newX = float(m_smooth_x + m_cursor->x()*7) / 8; float newY = float(m_smooth_y + m_cursor->y()*7) / 8; QPointF pos(newX, newY); m_cursor->setPos(pos); m_lineH->setY(pos.y()); m_lineV->setX(pos.x()); float xp = pos.x() / (p_size.x() + p_size.width()); float yp = pos.y() / (p_size.y() + p_size.height()); sendMIDI(&xp, &yp); emit cursorMoved(xp, yp); } protected: void handleMousePos(QPointF pos) { if (! p_size.contains(pos)) { if (pos.x() < p_size.x()) pos.setX(p_size.x()); else if (pos.x() > p_size.x() + p_size.width()) pos.setX(p_size.x() + p_size.width()); if (pos.y() < p_size.y()) pos.setY(p_size.y()); else if (pos.y() > p_size.y() + p_size.height()) pos.setY(p_size.y() + p_size.height()); } m_smooth_x = pos.x(); m_smooth_y = pos.y(); if (! m_smooth) { m_cursor->setPos(pos); m_lineH->setY(pos.y()); m_lineV->setX(pos.x()); float xp = pos.x() / (p_size.x() + p_size.width()); float yp = pos.y() / (p_size.y() + p_size.height()); sendMIDI(&xp, &yp); emit cursorMoved(xp, yp); } } void sendMIDI(float* xp=nullptr, float* yp=nullptr) { float rate = float(0xff) / 4; if (xp != nullptr) { int value = *xp * rate + rate; foreach (const int& channel, m_channels) qMidiOutData.put(0xB0 + channel - 1, cc_x, value); } if (yp != nullptr) { int value = *yp * rate + rate; foreach (const int& channel, m_channels) qMidiOutData.put(0xB0 + channel - 1, cc_y, value); } } void keyPressEvent(QKeyEvent* event) { event->accept(); } void wheelEvent(QGraphicsSceneWheelEvent* event) { event->accept(); } void mousePressEvent(QGraphicsSceneMouseEvent* event) { m_mouseLock = true; handleMousePos(event->scenePos()); parent()->setCursor(Qt::CrossCursor); QGraphicsScene::mousePressEvent(event); } void mouseMoveEvent(QGraphicsSceneMouseEvent* event) { handleMousePos(event->scenePos()); QGraphicsScene::mouseMoveEvent(event); } void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { m_mouseLock = false; parent()->setCursor(Qt::ArrowCursor); QGraphicsScene::mouseReleaseEvent(event); } signals: void cursorMoved(float, float); private: int cc_x; int cc_y; QList m_channels; bool m_mouseLock; bool m_smooth; float m_smooth_x; float m_smooth_y; QGraphicsEllipseItem* m_cursor; QGraphicsLineItem* m_lineH; QGraphicsLineItem* m_lineV; QRectF p_size; // fake parent QWidget* const m_parent; QWidget* parent() const { return m_parent; } }; // ------------------------------- // XY Controller Window namespace Ui { class XYControllerW; } class XYControllerW : public QMainWindow { Q_OBJECT public: XYControllerW() : QMainWindow(nullptr), settings("Cadence", "XY-Controller"), scene(this), ui(new Ui::XYControllerW) { ui->setupUi(this); // ------------------------------------------------------------- // Internal stuff cc_x = 1; cc_y = 2; // ------------------------------------------------------------- // Set-up GUI stuff ui->dial_x->setPixmap(2); ui->dial_y->setPixmap(2); ui->dial_x->setLabel("X"); ui->dial_y->setLabel("Y"); ui->keyboard->setOctaves(10); ui->graphicsView->setScene(&scene); ui->graphicsView->setRenderHints(QPainter::Antialiasing); foreach (const QString& MIDI_CC, MIDI_CC_LIST) { ui->cb_control_x->addItem(MIDI_CC); ui->cb_control_y->addItem(MIDI_CC); } // ------------------------------------------------------------- // Load Settings loadSettings(); // ------------------------------------------------------------- // Connect actions to functions connect(ui->keyboard, SIGNAL(noteOn(int)), SLOT(slot_noteOn(int))); connect(ui->keyboard, SIGNAL(noteOff(int)), SLOT(slot_noteOff(int))); connect(ui->cb_smooth, SIGNAL(clicked(bool)), SLOT(slot_setSmooth(bool))); connect(ui->dial_x, SIGNAL(valueChanged(int)), SLOT(slot_updateSceneX(int))); connect(ui->dial_y, SIGNAL(valueChanged(int)), SLOT(slot_updateSceneY(int))); connect(ui->cb_control_x, SIGNAL(currentIndexChanged(QString)), SLOT(slot_checkCC_X(QString))); connect(ui->cb_control_y, SIGNAL(currentIndexChanged(QString)), SLOT(slot_checkCC_Y(QString))); connect(&scene, SIGNAL(cursorMoved(float,float)), SLOT(slot_sceneCursorMoved(float,float))); connect(ui->act_ch_01, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_02, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_03, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_04, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_05, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_06, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_07, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_08, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_09, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_10, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_11, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_12, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_13, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_14, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_15, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_16, SIGNAL(triggered(bool)), SLOT(slot_checkChannel(bool))); connect(ui->act_ch_all, SIGNAL(triggered()), SLOT(slot_checkChannel_all())); connect(ui->act_ch_none, SIGNAL(triggered()), SLOT(slot_checkChannel_none())); connect(ui->act_show_keyboard, SIGNAL(triggered(bool)), SLOT(slot_showKeyboard(bool))); connect(ui->act_about, SIGNAL(triggered()), SLOT(slot_about())); // ------------------------------------------------------------- // Final stuff m_midiInTimerId = startTimer(30); QTimer::singleShot(0, this, SLOT(slot_updateScreen())); } void updateScreen() { scene.updateSize(ui->graphicsView->size()); ui->graphicsView->centerOn(0, 0); int dial_x = ui->dial_x->value(); int dial_y = ui->dial_y->value(); slot_updateSceneX(dial_x); slot_updateSceneY(dial_y); scene.setSmoothValues(float(dial_x) / 100, float(dial_y) / 100); } protected slots: void slot_noteOn(int note) { foreach (const int& channel, m_channels) qMidiOutData.put(0x90 + channel - 1, note, 100); } void slot_noteOff(int note) { foreach (const int& channel, m_channels) qMidiOutData.put(0x80 + channel - 1, note, 0); } void slot_updateSceneX(int x) { scene.setSmoothValues(float(x) / 100, float(ui->dial_y->value()) / 100); scene.setPosX(float(x) / 100, bool(sender())); } void slot_updateSceneY(int y) { scene.setSmoothValues(float(ui->dial_x->value()) / 100, float(y) / 100); scene.setPosY(float(y) / 100, bool(sender())); } void slot_checkCC_X(QString text) { if (text.isEmpty()) return; bool ok; int tmp_cc_x = text.split(" ").at(0).toInt(&ok, 16); if (ok) { cc_x = tmp_cc_x; scene.setControlX(cc_x); } } void slot_checkCC_Y(QString text) { if (text.isEmpty()) return; bool ok; int tmp_cc_y = text.split(" ").at(0).toInt(&ok, 16); if (ok) { cc_y = tmp_cc_y; scene.setControlY(cc_y); } } void slot_checkChannel(bool clicked) { if (! sender()) return; bool ok; int channel = ((QAction*)sender())->text().toInt(&ok); if (ok) { if (clicked && ! m_channels.contains(channel)) m_channels.append(channel); else if ((! clicked) && m_channels.contains(channel)) m_channels.removeOne(channel); scene.setChannels(m_channels); } } void slot_checkChannel_all() { ui->act_ch_01->setChecked(true); ui->act_ch_02->setChecked(true); ui->act_ch_03->setChecked(true); ui->act_ch_04->setChecked(true); ui->act_ch_05->setChecked(true); ui->act_ch_06->setChecked(true); ui->act_ch_07->setChecked(true); ui->act_ch_08->setChecked(true); ui->act_ch_09->setChecked(true); ui->act_ch_10->setChecked(true); ui->act_ch_11->setChecked(true); ui->act_ch_12->setChecked(true); ui->act_ch_13->setChecked(true); ui->act_ch_14->setChecked(true); ui->act_ch_15->setChecked(true); ui->act_ch_16->setChecked(true); #ifdef Q_COMPILER_INITIALIZER_LISTS m_channels = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; #else m_channels.clear(); for (int i=1; i <= 16; i++) m_channels << i; #endif scene.setChannels(m_channels); } void slot_checkChannel_none() { ui->act_ch_01->setChecked(false); ui->act_ch_02->setChecked(false); ui->act_ch_03->setChecked(false); ui->act_ch_04->setChecked(false); ui->act_ch_05->setChecked(false); ui->act_ch_06->setChecked(false); ui->act_ch_07->setChecked(false); ui->act_ch_08->setChecked(false); ui->act_ch_09->setChecked(false); ui->act_ch_10->setChecked(false); ui->act_ch_11->setChecked(false); ui->act_ch_12->setChecked(false); ui->act_ch_13->setChecked(false); ui->act_ch_14->setChecked(false); ui->act_ch_15->setChecked(false); ui->act_ch_16->setChecked(false); m_channels.clear(); scene.setChannels(m_channels); } void slot_setSmooth(bool yesno) { scene.setSmooth(yesno); } void slot_sceneCursorMoved(float xp, float yp) { ui->dial_x->blockSignals(true); ui->dial_y->blockSignals(true); ui->dial_x->setValue(xp * 100); ui->dial_y->setValue(yp * 100); ui->dial_x->blockSignals(false); ui->dial_y->blockSignals(false); } void slot_showKeyboard(bool yesno) { ui->scrollArea->setVisible(yesno); QTimer::singleShot(0, this, SLOT(slot_updateScreen())); } void slot_about() { QMessageBox::about(this, tr("About XY Controller"), tr("

XY Controller

" "
Version %1" "
XY Controller is a simple XY widget that sends and receives data from Jack MIDI.
" "
Copyright (C) 2012-2022 falkTX").arg(VERSION)); } void slot_updateScreen() { updateScreen(); } protected: void saveSettings() { QVariantList varChannelList; foreach (const int& channel, m_channels) varChannelList << channel; settings.setValue("Geometry", saveGeometry()); settings.setValue("ShowKeyboard", ui->scrollArea->isVisible()); settings.setValue("Smooth", ui->cb_smooth->isChecked()); settings.setValue("DialX", ui->dial_x->value()); settings.setValue("DialY", ui->dial_y->value()); settings.setValue("ControlX", cc_x); settings.setValue("ControlY", cc_y); settings.setValue("Channels", varChannelList); } void loadSettings() { restoreGeometry(settings.value("Geometry").toByteArray()); bool showKeyboard = settings.value("ShowKeyboard", false).toBool(); ui->act_show_keyboard->setChecked(showKeyboard); ui->scrollArea->setVisible(showKeyboard); bool smooth = settings.value("Smooth", false).toBool(); ui->cb_smooth->setChecked(smooth); scene.setSmooth(smooth); ui->dial_x->setValue(settings.value("DialX", 50).toInt()); ui->dial_y->setValue(settings.value("DialY", 50).toInt()); cc_x = settings.value("ControlX", 1).toInt(); cc_y = settings.value("ControlY", 2).toInt(); scene.setControlX(cc_x); scene.setControlY(cc_y); m_channels.clear(); if (settings.contains("Channels")) { QVariantList channels = settings.value("Channels").toList(); foreach (const QVariant& var, channels) { bool ok; int channel = var.toInt(&ok); if (ok) m_channels.append(channel); } } else { #ifdef Q_COMPILER_INITIALIZER_LISTS m_channels = { 1 }; #else m_channels << 1; #endif } scene.setChannels(m_channels); for (int i=0; i < MIDI_CC_LIST.size(); i++) { bool ok; int cc = MIDI_CC_LIST[i].split(" ").at(0).toInt(&ok, 16); if (ok) { if (cc_x == cc) ui->cb_control_x->setCurrentIndex(i); if (cc_y == cc) ui->cb_control_y->setCurrentIndex(i); } } if (m_channels.contains(1)) ui->act_ch_01->setChecked(true); if (m_channels.contains(2)) ui->act_ch_02->setChecked(true); if (m_channels.contains(3)) ui->act_ch_03->setChecked(true); if (m_channels.contains(4)) ui->act_ch_04->setChecked(true); if (m_channels.contains(5)) ui->act_ch_05->setChecked(true); if (m_channels.contains(6)) ui->act_ch_06->setChecked(true); if (m_channels.contains(7)) ui->act_ch_07->setChecked(true); if (m_channels.contains(8)) ui->act_ch_08->setChecked(true); if (m_channels.contains(9)) ui->act_ch_09->setChecked(true); if (m_channels.contains(10)) ui->act_ch_10->setChecked(true); if (m_channels.contains(11)) ui->act_ch_11->setChecked(true); if (m_channels.contains(12)) ui->act_ch_12->setChecked(true); if (m_channels.contains(13)) ui->act_ch_13->setChecked(true); if (m_channels.contains(14)) ui->act_ch_14->setChecked(true); if (m_channels.contains(15)) ui->act_ch_15->setChecked(true); if (m_channels.contains(16)) ui->act_ch_16->setChecked(true); } void timerEvent(QTimerEvent* event) { if (event->timerId() == m_midiInTimerId) { if (! qMidiInData.isEmpty()) { unsigned char d1, d2, d3; qMidiInInternal.copyDataFrom(&qMidiInData); while (qMidiInInternal.get(&d1, &d2, &d3, false)) { int channel = (d1 & 0x0F) + 1; int mode = d1 & 0xF0; if (m_channels.contains(channel)) { if (mode == 0x80) ui->keyboard->sendNoteOff(d2, false); else if (mode == 0x90) ui->keyboard->sendNoteOn(d2, false); else if (mode == 0xB0) scene.handleCC(d2, d3); } } } scene.updateSmooth(); } QMainWindow::timerEvent(event); } void resizeEvent(QResizeEvent* event) { updateScreen(); QMainWindow::resizeEvent(event); } void closeEvent(QCloseEvent* event) { saveSettings(); QMainWindow::closeEvent(event); } private: int cc_x; int cc_y; QList m_channels; int m_midiInTimerId; QSettings settings; XYGraphicsScene scene; Ui::XYControllerW* const ui; Queue qMidiInInternal; }; #include "xycontroller.moc" // ------------------------------- int process_callback(const jack_nframes_t nframes, void*) { void* const midiInBuffer = jackbridge_port_get_buffer(jMidiInPort, nframes); void* const midiOutBuffer = jackbridge_port_get_buffer(jMidiOutPort, nframes); if (! (midiInBuffer && midiOutBuffer)) return 1; // MIDI In jack_midi_event_t midiEvent; uint32_t midiEventCount = jackbridge_midi_get_event_count(midiInBuffer); qMidiInData.lock(); for (uint32_t i=0; i < midiEventCount; i++) { if (! jackbridge_midi_event_get(&midiEvent, midiInBuffer, i)) break; if (midiEvent.size == 1) qMidiInData.put(midiEvent.buffer[0], 0, 0, false); else if (midiEvent.size == 2) qMidiInData.put(midiEvent.buffer[0], midiEvent.buffer[1], 0, false); else if (midiEvent.size >= 3) qMidiInData.put(midiEvent.buffer[0], midiEvent.buffer[1], midiEvent.buffer[2], false); if (qMidiInData.isFull()) break; } qMidiInData.unlock(); // MIDI Out jackbridge_midi_clear_buffer(midiOutBuffer); qMidiOutData.lock(); if (! qMidiOutData.isEmpty()) { unsigned char d1, d2, d3, data[3]; while (qMidiOutData.get(&d1, &d2, &d3, false)) { data[0] = d1; data[1] = d2; data[2] = d3; jackbridge_midi_event_write(midiOutBuffer, 0, data, 3); } } qMidiOutData.unlock(); return 0; } #ifdef HAVE_JACKSESSION void session_callback(jack_session_event_t* const event, void* const arg) { #ifdef Q_OS_LINUX QString filepath("cadence-xycontroller"); Q_UNUSED(arg); #else QString filepath((char*)arg); #endif event->command_line = strdup(filepath.toUtf8().constData()); jackbridge_session_reply(jClient, event); if (event->type == JackSessionSaveAndQuit) QApplication::instance()->quit(); jackbridge_session_event_free(event); } #endif // ------------------------------- int main(int argc, char* argv[]) { MIDI_CC_LIST__init(); #ifdef Q_OS_WIN QApplication::setGraphicsSystem("raster"); #endif QApplication app(argc, argv); app.setApplicationName("XY-Controller"); app.setApplicationVersion(VERSION); app.setOrganizationName("Cadence"); app.setWindowIcon(QIcon(":/scalable/cadence.svg")); // JACK initialization jack_status_t jStatus; #ifdef HAVE_JACKSESSION jack_options_t jOptions = static_cast(JackNoStartServer|JackSessionID); #else jack_options_t jOptions = static_cast(JackNoStartServer); #endif jClient = jackbridge_client_open("XY-Controller", jOptions, &jStatus); if (! jClient) { std::string errorString(jackbridge_status_get_error_string(jStatus)); QMessageBox::critical(nullptr, app.translate("XY-Controller", "Error"), app.translate("XY-Controller", "Could not connect to JACK, possible reasons:\n" "%1").arg(QString::fromStdString(errorString))); return 1; } jMidiInPort = jackbridge_port_register(jClient, "midi_in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); jMidiOutPort = jackbridge_port_register(jClient, "midi_out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); jackbridge_set_process_callback(jClient, process_callback, nullptr); #ifdef HAVE_JACKSESSION jackbridge_set_session_callback(jClient, session_callback, argv[0]); #endif jackbridge_activate(jClient); // Show GUI XYControllerW gui; gui.show(); // App-Loop int ret = app.exec(); jackbridge_deactivate(jClient); jackbridge_client_close(jClient); return ret; }