From e5a867fcb8014859983d03bb49f3bbcb411b8d88 Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 16 Aug 2012 17:50:18 +0100 Subject: [PATCH] Add initial code for c++ xycontroller --- .gitignore | 7 +- c++/jackmeter/Makefile | 15 +- c++/widgets/pixmapdial.cpp | 245 +++++ c++/widgets/pixmapdial.h | 87 ++ c++/widgets/pixmapkeyboard.cpp | 506 +++++++++++ c++/widgets/pixmapkeyboard.h | 84 ++ c++/xycontroller/Makefile | 72 ++ c++/xycontroller/qtcreator/xycontroller.pro | 33 + c++/xycontroller/xycontroller.cpp | 936 ++++++++++++++++++++ 9 files changed, 1973 insertions(+), 12 deletions(-) create mode 100644 c++/widgets/pixmapdial.cpp create mode 100644 c++/widgets/pixmapdial.h create mode 100644 c++/widgets/pixmapkeyboard.cpp create mode 100644 c++/widgets/pixmapkeyboard.h create mode 100644 c++/xycontroller/Makefile create mode 100644 c++/xycontroller/qtcreator/xycontroller.pro create mode 100644 c++/xycontroller/xycontroller.cpp diff --git a/.gitignore b/.gitignore index 19204c1..1db9615 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,11 @@ c++/carla-backend/doxygen/ c++/carla-includes/vst/ c++/jackmeter/cadence_jackmeter +c++/*/*-build-*-Debug/ +c++/*/*-build-*-Release/ + src/resources_rc.py +resources/qrc_resources.cpp # incoming @@ -46,7 +50,4 @@ c++/carla-backend/carla_backend_lv2.cpp c++/carla-backend/carla_backend_vst.cpp c++/carla-bridge/qt-plugin/ c++/flmixer/ -c++/xycontroller/ c++/widgets/ledbutton.* -c++/widgets/pixmapdial.* -c++/widgets/pixmapkeyboard.* diff --git a/c++/jackmeter/Makefile b/c++/jackmeter/Makefile index 2d2d980..a1b1e4e 100644 --- a/c++/jackmeter/Makefile +++ b/c++/jackmeter/Makefile @@ -6,26 +6,23 @@ CXX ?= g++ STRIP ?= strip -PKGCONFIG ?= pkg-config BASE_FLAGS = -O2 -ffast-math -fomit-frame-pointer -fPIC -mtune=generic -msse -mfpmath=sse -Wall BUILD_FLAGS = $(BASE_FLAGS) -std=c++0x $(CXXFLAGS) -BUILD_FLAGS += $(shell $(PKGCONFIG) --cflags QtCore QtGui jack) +BUILD_FLAGS += $(shell pkg-config --cflags QtCore QtGui jack) BUILD_FLAGS += -DNDEBUG -DQT_NO_DEBUG -DQT_NO_DEBUG_STREAM -DQT_NO_DEBUG_OUTPUT LINK_FLAGS = $(LDFLAGS) -LINK_FLAGS += $(shell $(PKGCONFIG) --libs QtCore QtGui jack) +LINK_FLAGS += $(shell pkg-config --libs QtCore QtGui jack) -OBJS = jackmeter.o \ - ../widgets/digitalpeakmeter.o - -HAVE_JACKSESSION = $(shell pkg-config --atleast-version=0.121.0 jack && echo true) - -ifeq ($(HAVE_JACKSESSION),true) +ifeq ($(shell pkg-config --atleast-version=0.121.0 jack && echo true),true) BUILD_FLAGS += -DHAVE_JACKSESSION endif +OBJS = jackmeter.o \ + ../widgets/digitalpeakmeter.o + # -------------------------------------------------------------- all: cadence_jackmeter diff --git a/c++/widgets/pixmapdial.cpp b/c++/widgets/pixmapdial.cpp new file mode 100644 index 0000000..0753663 --- /dev/null +++ b/c++/widgets/pixmapdial.cpp @@ -0,0 +1,245 @@ +/* + * Pixmap Dial, a custom Qt4 widget + * 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 "pixmapdial.h" + +#include +#include + +PixmapDial::PixmapDial(QWidget* parent): + QDial(parent) +{ + m_pixmap.load(":/bitmaps/dial_01d.png"); + m_pixmap_n_str = "01"; + m_custom_paint = CUSTOM_PAINT_NULL; + + m_hovered = false; + m_hover_step = HOVER_MIN; + + if (m_pixmap.width() > m_pixmap.height()) + m_orientation = HORIZONTAL; + else + m_orientation = VERTICAL; + + m_label = ""; + m_label_pos = QPointF(0.0, 0.0); + m_label_width = 0; + m_label_height = 0; + m_label_gradient = QLinearGradient(0, 0, 0, 1); + + if (palette().window().color().lightness() > 100) + { + // Light background + QColor c = palette().dark().color(); + m_color1 = c; + m_color2 = QColor(c.red(), c.green(), c.blue(), 0); + m_colorT[0] = palette().buttonText().color(); + m_colorT[1] = palette().mid().color(); + } + else + { + // Dark background + m_color1 = QColor(0, 0, 0, 255); + m_color2 = QColor(0, 0, 0, 0); + m_colorT[0] = Qt::white; + m_colorT[1] = Qt::darkGray; + } + + updateSizes(); +} + +int PixmapDial::getSize() const +{ + return p_size; +} + +void PixmapDial::setCustomPaint(CustomPaint paint) +{ + m_custom_paint = paint; + update(); +} + +void PixmapDial::setEnabled(bool enabled) +{ + if (isEnabled() != enabled) + { + m_pixmap.load(QString(":/dial_%1%2.png").arg(m_pixmap_n_str).arg(enabled ? "" : "d")); + updateSizes(); + update(); + } + QDial::setEnabled(enabled); +} + +void PixmapDial::setLabel(QString label) +{ + m_label = label; + + m_label_width = QFontMetrics(font()).width(label); + m_label_height = QFontMetrics(font()).height(); + + m_label_pos.setX((p_size/2)-(m_label_width/2)); + m_label_pos.setY(p_size+m_label_height); + + m_label_gradient.setColorAt(0.0, m_color1); + m_label_gradient.setColorAt(0.6, m_color1); + m_label_gradient.setColorAt(1.0, m_color2); + + m_label_gradient.setStart(0, p_size/2); + m_label_gradient.setFinalStop(0, p_size+m_label_height+5); + + m_label_gradient_rect = QRectF(p_size*1/8, p_size/2, p_size*6/8, p_size+m_label_height+5); + update(); +} + +void PixmapDial::setPixmap(int pixmap_id) +{ + if (pixmap_id > 10) + m_pixmap_n_str = QString::number(pixmap_id); + else + m_pixmap_n_str = QString("0%1").arg(pixmap_id); + + m_pixmap.load(QString(":/bitmaps/dial_%1%2.png").arg(m_pixmap_n_str).arg(isEnabled() ? "" : "d")); + + if (m_pixmap.width() > m_pixmap.height()) + m_orientation = HORIZONTAL; + else + m_orientation = VERTICAL; + + updateSizes(); + update(); +} + +QSize PixmapDial::minimumSizeHint() const +{ + return QSize(p_size, p_size); +} + +QSize PixmapDial::sizeHint() const +{ + return QSize(p_size, p_size); +} + +void PixmapDial::updateSizes() +{ + p_width = m_pixmap.width(); + p_height = m_pixmap.height(); + + if (p_width < 1) + p_width = 1; + + if (p_height < 1) + p_height = 1; + + if (m_orientation == HORIZONTAL) + { + p_size = p_height; + p_count = p_width/p_height; + } + else + { + p_size = p_width; + p_count = p_height/p_width; + } + + setMinimumSize(p_size, p_size + m_label_height + 5); + setMaximumSize(p_size, p_size + m_label_height + 5); +} + +void PixmapDial::enterEvent(QEvent* event) +{ + m_hovered = true; + if (m_hover_step == HOVER_MIN) + m_hover_step = HOVER_MIN + 1; + QDial::enterEvent(event); +} + +void PixmapDial::leaveEvent(QEvent* event) +{ + m_hovered = false; + if (m_hover_step == HOVER_MAX) + m_hover_step = HOVER_MAX - 1; + QDial::leaveEvent(event); +} + +void PixmapDial::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + + if (! m_label.isEmpty()) + { + painter.setPen(m_color2); + painter.setBrush(m_label_gradient); + painter.drawRect(m_label_gradient_rect); + + painter.setPen(isEnabled() ? m_colorT[0] : m_colorT[1]); + painter.drawText(m_label_pos, m_label); + } + + QRectF target, source; + + if (isEnabled()) + { + float current = value()-minimum(); + float divider = maximum()-minimum(); + + if (divider == 0.0f) + return; + + target = QRectF(0.0, 0.0, p_size, p_size); + float value = current/divider; + + int xpos, ypos, per = int((p_count-1) * value); + + if (m_orientation == HORIZONTAL) + { + xpos = p_size*per; + ypos = 0.0; + } + else + { + xpos = 0.0; + ypos = p_size*per; + } + + source = QRectF(xpos, ypos, p_size, p_size); + painter.drawPixmap(target, m_pixmap, source); + + // Custom knobs (Dry/Wet and Volume) + // TODO + + // Custom knobs (L and R) + // TODO + + if (HOVER_MIN > m_hover_step && m_hover_step < HOVER_MAX) + { + m_hover_step += m_hovered ? 1 : -1; + QTimer::singleShot(20, this, SLOT(update())); + } + } + else + { + target = QRectF(0.0, 0.0, p_size, p_size); + source = target; + painter.drawPixmap(target, m_pixmap, source); + } +} + +void PixmapDial::resizeEvent(QResizeEvent* event) +{ + updateSizes(); + QDial::resizeEvent(event); +} diff --git a/c++/widgets/pixmapdial.h b/c++/widgets/pixmapdial.h new file mode 100644 index 0000000..1cba105 --- /dev/null +++ b/c++/widgets/pixmapdial.h @@ -0,0 +1,87 @@ +/* + * Pixmap Dial, a custom Qt4 widget + * 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 + */ + +#ifndef PIXMAPDIAL_H +#define PIXMAPDIAL_H + +#include +#include + +class PixmapDial : public QDial +{ +public: + enum Orientation { + HORIZONTAL = 0, + VERTICAL = 1 + }; + + enum CustomPaint { + CUSTOM_PAINT_NULL = 0, + CUSTOM_PAINT_CARLA_WET = 1, + CUSTOM_PAINT_CARLA_VOL = 2, + CUSTOM_PAINT_CARLA_L = 3, + CUSTOM_PAINT_CARLA_R = 4 + }; + + PixmapDial(QWidget* parent); + + int getSize() const; + void setCustomPaint(CustomPaint paint); + void setEnabled(bool enabled); + void setLabel(QString label); + void setPixmap(int pixmap_id); + + QSize minimumSizeHint() const; + QSize sizeHint() const; + +protected: + void updateSizes(); + + void enterEvent(QEvent* event); + void leaveEvent(QEvent* event); + void paintEvent(QPaintEvent* event); + void resizeEvent(QResizeEvent* event); + +private: + QPixmap m_pixmap; + QString m_pixmap_n_str; + + CustomPaint m_custom_paint; + Orientation m_orientation; + + bool m_hovered; + int m_hover_step; + + QString m_label; + QPointF m_label_pos; + int m_label_width; + int m_label_height; + + QLinearGradient m_label_gradient; + QRectF m_label_gradient_rect; + + QColor m_color1; + QColor m_color2; + QColor m_colorT[2]; + + int p_width, p_height, p_size, p_count; + + static const int HOVER_MIN = 0; + static const int HOVER_MAX = 9; +}; + +#endif // PIXMAPDIAL_H diff --git a/c++/widgets/pixmapkeyboard.cpp b/c++/widgets/pixmapkeyboard.cpp new file mode 100644 index 0000000..eed96b9 --- /dev/null +++ b/c++/widgets/pixmapkeyboard.cpp @@ -0,0 +1,506 @@ +/* + * Pixmap Keyboard, a custom Qt4 widget + * 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 "pixmapkeyboard.h" + +#include +#include +#include +#include +#include + +static QMap midi_key2rect_map_horizontal; +static QMap midi_key2rect_map_vertical; +static QMap midi_keyboard2key_map; +static QVector blackNotes; + +void midi_map_init() +{ + static bool init = false; + if (init) return; + init = true; + + // midi_key2rect_map_horizontal ------ + midi_key2rect_map_horizontal[0] = QRectF(0, 0, 18, 64); // C + midi_key2rect_map_horizontal[1] = QRectF(13, 0, 11, 42); // C# + midi_key2rect_map_horizontal[2] = QRectF(18, 0, 25, 64); // D + midi_key2rect_map_horizontal[3] = QRectF(37, 0, 11, 42); // D# + midi_key2rect_map_horizontal[4] = QRectF(42, 0, 18, 64); // E + midi_key2rect_map_horizontal[5] = QRectF(60, 0, 18, 64); // F + midi_key2rect_map_horizontal[6] = QRectF(73, 0, 11, 42); // F# + midi_key2rect_map_horizontal[7] = QRectF(78, 0, 25, 64); // G + midi_key2rect_map_horizontal[8] = QRectF(97, 0, 11, 42); // G# + midi_key2rect_map_horizontal[9] = QRectF(102, 0, 25, 64); // A + midi_key2rect_map_horizontal[10] = QRectF(121, 0, 11, 42); // A# + midi_key2rect_map_horizontal[11] = QRectF(126, 0, 18, 64); // B + + // midi_key2rect_map_vertical -------- + midi_key2rect_map_vertical[11] = QRectF(0, 0, 64, 18); // B + midi_key2rect_map_vertical[10] = QRectF(0, 14, 42, 7); // A# + midi_key2rect_map_vertical[9] = QRectF(0, 18, 64, 24); // A + midi_key2rect_map_vertical[8] = QRectF(0, 38, 42, 7); // G# + midi_key2rect_map_vertical[7] = QRectF(0, 42, 64, 24); // G + midi_key2rect_map_vertical[6] = QRectF(0, 62, 42, 7); // F# + midi_key2rect_map_vertical[5] = QRectF(0, 66, 64, 18); // F + midi_key2rect_map_vertical[4] = QRectF(0, 84, 64, 18); // E + midi_key2rect_map_vertical[3] = QRectF(0, 98, 42, 7); // D# + midi_key2rect_map_vertical[2] = QRectF(0, 102, 64, 24); // D + midi_key2rect_map_vertical[1] = QRectF(0, 122, 42, 7); // C# + midi_key2rect_map_vertical[0] = QRectF(0, 126, 64, 18); // C + + // midi_keyboard2key_map ------------- + // 3th octave + midi_keyboard2key_map[Qt::Key_Z] = 48; + midi_keyboard2key_map[Qt::Key_S] = 49; + midi_keyboard2key_map[Qt::Key_X] = 50; + midi_keyboard2key_map[Qt::Key_D] = 51; + midi_keyboard2key_map[Qt::Key_C] = 52; + midi_keyboard2key_map[Qt::Key_V] = 53; + midi_keyboard2key_map[Qt::Key_G] = 54; + midi_keyboard2key_map[Qt::Key_B] = 55; + midi_keyboard2key_map[Qt::Key_H] = 56; + midi_keyboard2key_map[Qt::Key_N] = 57; + midi_keyboard2key_map[Qt::Key_J] = 58; + midi_keyboard2key_map[Qt::Key_M] = 59; + // 4th octave + midi_keyboard2key_map[Qt::Key_Q] = 60; + midi_keyboard2key_map[Qt::Key_2] = 61; + midi_keyboard2key_map[Qt::Key_W] = 62; + midi_keyboard2key_map[Qt::Key_3] = 63; + midi_keyboard2key_map[Qt::Key_E] = 64; + midi_keyboard2key_map[Qt::Key_R] = 65; + midi_keyboard2key_map[Qt::Key_5] = 66; + midi_keyboard2key_map[Qt::Key_T] = 67; + midi_keyboard2key_map[Qt::Key_6] = 68; + midi_keyboard2key_map[Qt::Key_Y] = 69; + midi_keyboard2key_map[Qt::Key_7] = 70; + midi_keyboard2key_map[Qt::Key_U] = 71; + + blackNotes << 1; + blackNotes << 3; + blackNotes << 6; + blackNotes << 8; + blackNotes << 10; +} + +PixmapKeyboard::PixmapKeyboard(QWidget* parent): + QWidget(parent), + m_font("Monospace", 8, QFont::Normal) +{ + midi_map_init(); + + m_octaves = 6; + m_lastMouseNote = -1; + m_needsUpdate = false; + + setCursor(Qt::PointingHandCursor); + setMode(HORIZONTAL); +} + +void PixmapKeyboard::sendNoteOn(int note, bool sendSignal) +{ + if (note >= 0 && note <= 127 && ! m_enabledKeys.contains(note)) + { + m_enabledKeys.append(note); + if (sendSignal) + emit noteOn(note); + + m_needsUpdate = true; + QTimer::singleShot(0, this, SLOT(updateOnce())); + } + + if (m_enabledKeys.count() == 1) + emit notesOn(); +} + +void PixmapKeyboard::sendNoteOff(int note, bool sendSignal) +{ + if (note >= 0 && note <= 127 && m_enabledKeys.contains(note)) + { + m_enabledKeys.removeOne(note); + if (sendSignal) + emit noteOff(note); + + m_needsUpdate = true; + QTimer::singleShot(0, this, SLOT(updateOnce())); + } + + if (m_enabledKeys.count() == 0) + emit notesOff(); +} + +void PixmapKeyboard::setMode(Orientation mode, Color color) +{ + if (color == COLOR_CLASSIC) + { + m_colorStr = "classic"; + } + else if (color == COLOR_ORANGE) + { + m_colorStr = "orange"; + } + else + { + qCritical("PixmapKeyboard::setMode(%i, %i) - invalid color", mode, color); + return setMode(mode); + } + + if (mode == HORIZONTAL) + { + m_midi_map = &midi_key2rect_map_horizontal; + m_pixmap.load(QString(":/bitmaps/kbd_h_%1.png").arg(m_colorStr)); + m_pixmap_mode = HORIZONTAL; + p_width = m_pixmap.width(); + p_height = m_pixmap.height() / 2; + } + else if (mode == VERTICAL) + { + m_midi_map = &midi_key2rect_map_vertical; + m_pixmap.load(QString(":/bitmaps/kbd_v_%1.png").arg(m_colorStr)); + m_pixmap_mode = VERTICAL; + p_width = m_pixmap.width() / 2; + p_height = m_pixmap.height(); + } + else + { + qCritical("PixmapKeyboard::setMode(%i, %i) - invalid mode", mode, color); + return setMode(HORIZONTAL); + } + + setOctaves(m_octaves); +} + +void PixmapKeyboard::setOctaves(int octaves) +{ + if (octaves < 1) + octaves = 1; + else if (octaves > 6) + octaves = 6; + m_octaves = octaves; + + if (m_pixmap_mode == HORIZONTAL) + { + setMinimumSize(p_width * m_octaves, p_height); + setMaximumSize(p_width * m_octaves, p_height); + } + else if (m_pixmap_mode == VERTICAL) + { + setMinimumSize(p_width, p_height * m_octaves); + setMaximumSize(p_width, p_height * m_octaves); + } + + update(); +} + +void PixmapKeyboard::keyPressEvent(QKeyEvent* event) +{ + int qKey = event->key(); + + if (midi_keyboard2key_map.keys().contains(qKey)) + sendNoteOn(midi_keyboard2key_map[qKey]); + + QWidget::keyPressEvent(event); +} + +void PixmapKeyboard::keyReleaseEvent(QKeyEvent* event) +{ + int qKey = event->key(); + + if (midi_keyboard2key_map.keys().contains(qKey)) + sendNoteOff(midi_keyboard2key_map[qKey]); + + QWidget::keyReleaseEvent(event); +} + +void PixmapKeyboard::mousePressEvent(QMouseEvent* event) +{ + m_lastMouseNote = -1; + handleMousePos(event->pos()); + setFocus(); + QWidget::mousePressEvent(event); +} + +void PixmapKeyboard::mouseMoveEvent(QMouseEvent* event) +{ + handleMousePos(event->pos()); + QWidget::mousePressEvent(event); +} + +void PixmapKeyboard::mouseReleaseEvent(QMouseEvent* event) +{ + if (m_lastMouseNote != -1) + { + sendNoteOff(m_lastMouseNote); + m_lastMouseNote = -1; + } + QWidget::mouseReleaseEvent(event); +} + +void PixmapKeyboard::handleMousePos(const QPoint& pos) +{ + int note, octave; + QPointF n_pos; + + if (m_pixmap_mode == HORIZONTAL) + { + if (pos.x() < 0 or pos.x() > m_octaves * 144) + return; + octave = pos.x() / p_width; + n_pos = QPointF(pos.x() % p_width, pos.y()); + } + else if (m_pixmap_mode == VERTICAL) + { + if (pos.y() < 0 or pos.y() > m_octaves * 144) + return; + octave = m_octaves - pos.y() / p_height; + n_pos = QPointF(pos.x(), pos.y() % p_height); + } + else + return; + + octave += 3; + + if ((*m_midi_map)[1].contains(n_pos)) // C# + note = 1; + else if ((*m_midi_map)[3].contains(n_pos)) // D# + note = 3; + else if ((*m_midi_map)[6].contains(n_pos)) // F# + note = 6; + else if ((*m_midi_map)[8].contains(n_pos)) // G# + note = 8; + else if ((*m_midi_map)[10].contains(n_pos))// A# + note = 10; + else if ((*m_midi_map)[0].contains(n_pos)) // C + note = 0; + else if ((*m_midi_map)[2].contains(n_pos)) // D + note = 2; + else if ((*m_midi_map)[4].contains(n_pos)) // E + note = 4; + else if ((*m_midi_map)[5].contains(n_pos)) // F + note = 5; + else if ((*m_midi_map)[7].contains(n_pos)) // G + note = 7; + else if ((*m_midi_map)[9].contains(n_pos)) // A + note = 9; + else if ((*m_midi_map)[11].contains(n_pos))// B + note = 11; + else + note = -1; + + if (note != -1) + { + note += octave * 12; + if (m_lastMouseNote != note) + { + sendNoteOff(m_lastMouseNote); + sendNoteOn(note); + } + } + else + sendNoteOff(m_lastMouseNote); + + m_lastMouseNote = note; +} + +void PixmapKeyboard::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + + // ------------------------------------------------------------- + // Paint clean keys (as background) + + for (int octave=0; octave < m_octaves; octave++) + { + QRectF target; + + if (m_pixmap_mode == HORIZONTAL) + target = QRectF(p_width * octave, 0, p_width, p_height); + else if (m_pixmap_mode == VERTICAL) + target = QRectF(0, p_height * octave, p_width, p_height); + else + return; + + QRectF source = QRectF(0, 0, p_width, p_height); + painter.drawPixmap(target, m_pixmap, source); + } + + // ------------------------------------------------------------- + // Paint (white) pressed keys + + bool paintedWhite = false; + + for (int i=0; i < m_enabledKeys.count(); i++) + { + int octave, note = m_enabledKeys[i]; + QRectF pos = _getRectFromMidiNote(note); + + if (_isNoteBlack(note)) + continue; + + if (note < 35) + // cannot paint this note + continue; + else if (note < 48) + octave = 0; + else if (note < 60) + octave = 1; + else if (note < 72) + octave = 2; + else if (note < 84) + octave = 3; + else if (note < 96) + octave = 4; + else if (note < 108) + octave = 5; + else + // cannot paint this note either + continue; + + if (m_pixmap_mode == VERTICAL) + octave = m_octaves - octave - 1; + + QRectF target, source; + + if (m_pixmap_mode == HORIZONTAL) + { + target = QRectF(pos.x() + (p_width * octave), 0, pos.width(), pos.height()); + source = QRectF(pos.x(), p_height, pos.width(), pos.height()); + } + else if (m_pixmap_mode == VERTICAL) + { + target = QRectF(pos.x(), pos.y() + (p_height * octave), pos.width(), pos.height()); + source = QRectF(p_width, pos.y(), pos.width(), pos.height()); + } + else + return; + + paintedWhite = true; + painter.drawPixmap(target, m_pixmap, source); + } + + // ------------------------------------------------------------- + // Clear white keys border + + if (paintedWhite) + { + for (int octave=0; octave < m_octaves; octave++) + { + foreach (int note, blackNotes) + { + QRectF target, source; + QRectF pos = _getRectFromMidiNote(note); + if (m_pixmap_mode == HORIZONTAL) + { + target = QRectF(pos.x() + (p_width * octave), 0, pos.width(), pos.height()); + source = QRectF(pos.x(), 0, pos.width(), pos.height()); + } + else if (m_pixmap_mode == VERTICAL) + { + target = QRectF(pos.x(), pos.y() + (p_height * octave), pos.width(), pos.height()); + source = QRectF(0, pos.y(), pos.width(), pos.height()); + } + else + return; + + painter.drawPixmap(target, m_pixmap, source); + } + } + } + + // ------------------------------------------------------------- + // Paint (black) pressed keys + + for (int i=0; i < m_enabledKeys.count(); i++) + { + int octave, note = m_enabledKeys[i]; + QRectF pos = _getRectFromMidiNote(note); + + if (! _isNoteBlack(note)) + continue; + + if (note < 35) + // cannot paint this note + continue; + else if (note < 48) + octave = 0; + else if (note < 60) + octave = 1; + else if (note < 72) + octave = 2; + else if (note < 84) + octave = 3; + else if (note < 96) + octave = 4; + else if (note < 108) + octave = 5; + else + // cannot paint this note either + continue; + + if (m_pixmap_mode == VERTICAL) + octave = m_octaves - octave - 1; + + QRectF target, source; + + if (m_pixmap_mode == HORIZONTAL) + { + target = QRectF(pos.x() + (p_width * octave), 0, pos.width(), pos.height()); + source = QRectF(pos.x(), p_height, pos.width(), pos.height()); + } + else if (m_pixmap_mode == VERTICAL) + { + target = QRectF(pos.x(), pos.y() + (p_height * octave), pos.width(), pos.height()); + source = QRectF(p_width, pos.y(), pos.width(), pos.height()); + } + else + return; + + painter.drawPixmap(target, m_pixmap, source); + } + + // Paint C-number note info + painter.setFont(m_font); + painter.setPen(Qt::black); + + for (int i=0; i < m_octaves; i++) + { + if (m_pixmap_mode == HORIZONTAL) + painter.drawText(i * 144, 48, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2)); + else if (m_pixmap_mode == VERTICAL) + painter.drawText(45, (m_octaves * 144) - (i * 144) - 16, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2)); + } +} + +void PixmapKeyboard::updateOnce() +{ + if (m_needsUpdate) + { + update(); + m_needsUpdate = false; + } +} + +bool PixmapKeyboard::_isNoteBlack(int note) +{ + int baseNote = note % 12; + return blackNotes.contains(baseNote); +} + +QRectF PixmapKeyboard::_getRectFromMidiNote(int note) +{ + return (*m_midi_map)[note % 12]; +} diff --git a/c++/widgets/pixmapkeyboard.h b/c++/widgets/pixmapkeyboard.h new file mode 100644 index 0000000..c743ef6 --- /dev/null +++ b/c++/widgets/pixmapkeyboard.h @@ -0,0 +1,84 @@ +/* + * Pixmap Keyboard, a custom Qt4 widget + * 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 + */ + +#ifndef PIXMAPKEYBOARD_H +#define PIXMAPKEYBOARD_H + +#include +#include + +class PixmapKeyboard : public QWidget +{ + Q_OBJECT + +public: + enum Color { + COLOR_CLASSIC = 0, + COLOR_ORANGE = 1 + }; + + enum Orientation { + HORIZONTAL = 0, + VERTICAL = 1 + }; + + PixmapKeyboard(QWidget* parent); + + void sendNoteOn(int note, bool sendSignal=true); + void sendNoteOff(int note, bool sendSignal=true); + + void setMode(Orientation mode, Color color=COLOR_ORANGE); + void setOctaves(int octaves); + +signals: + void noteOn(int); + void noteOff(int); + void notesOn(); + void notesOff(); + +protected: + void keyPressEvent(QKeyEvent*); + void keyReleaseEvent(QKeyEvent*); + void mousePressEvent(QMouseEvent*); + void mouseMoveEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void handleMousePos(const QPoint&); + void paintEvent(QPaintEvent*); + +private Q_SLOTS: + void updateOnce(); + +private: + QPixmap m_pixmap; + Orientation m_pixmap_mode; + + QString m_colorStr; + QFont m_font; + + int m_octaves; + int m_lastMouseNote; + int p_width, p_height; + + bool m_needsUpdate; + QList m_enabledKeys; + QMap *m_midi_map; + + bool _isNoteBlack(int note); + QRectF _getRectFromMidiNote(int note); +}; + +#endif // PIXMAPKEYBOARD_H diff --git a/c++/xycontroller/Makefile b/c++/xycontroller/Makefile new file mode 100644 index 0000000..197b704 --- /dev/null +++ b/c++/xycontroller/Makefile @@ -0,0 +1,72 @@ +#!/usr/bin/make -f +# Makefile for xycontroller # +# ------------------------------------ # +# Created by falkTX +# + +CXX ?= g++ +MOC ?= moc +RCC ?= rcc +UIC ?= uic +STRIP ?= strip +WINDRES ?= windres + +BASE_FLAGS = -O2 -ffast-math -fomit-frame-pointer -fPIC -mtune=generic -msse -mfpmath=sse -Wall -I../widgets + +BUILD_FLAGS = $(BASE_FLAGS) -std=c++0x $(CXXFLAGS) +BUILD_FLAGS += $(shell pkg-config --cflags QtCore QtGui jack) +BUILD_FLAGS += -DNDEBUG -DQT_NO_DEBUG -DQT_NO_DEBUG_STREAM -DQT_NO_DEBUG_OUTPUT + +LINK_FLAGS = $(LDFLAGS) +LINK_FLAGS += $(shell pkg-config --libs QtCore QtGui jack) + +ifeq ($(shell pkg-config --atleast-version=0.121.0 jack && echo true),true) +BUILD_FLAGS += -DHAVE_JACKSESSION +endif + +FILES = \ + xycontroller.moc \ + ui_xycontroller.h \ + ../widgets/moc_pixmapkeyboard.cpp \ + ../../resources/qrc_resources.cpp + +OBJS = xycontroller.o \ + ../widgets/pixmapdial.o \ + ../widgets/pixmapkeyboard.o \ + ../widgets/moc_pixmapkeyboard.o \ + ../../resources/qrc_resources.o + +# -------------------------------------------------------------- + +all: cadence_xycontroller + +cadence_xycontroller: $(FILES) $(OBJS) + $(CXX) $(OBJS) $(LINK_FLAGS) -o $@ && strip $@ + +cadence_xycontroller.exe: $(FILES) $(OBJS) icon.o + $(CXX) $(OBJS) icon.o -static -mwindows $(LINK_FLAGS) -o $@ && strip $@ + +# -------------------------------------------------------------- + +xycontroller.moc: xycontroller.cpp + $(MOC) $< -o $@ + +ui_xycontroller.h: ../../src/ui/xycontroller.ui + $(UIC) $< -o $@ + +icon.o: ../../resources/ico/cadence.rc + $(WINDRES) -i $< -o $@ -O coff + +../widgets/moc_pixmapkeyboard.cpp: ../widgets/pixmapkeyboard.h + $(MOC) $< -o $@ + +../../resources/qrc_resources.cpp: ../../resources/resources.qrc + $(RCC) -name resources $< -o $@ + +# -------------------------------------------------------------- + +.cpp.o: + $(CXX) -c $< $(BUILD_FLAGS) -o $@ + +clean: + rm -f $(FILES) $(OBJS) icon.o cadence_xycontroller* diff --git a/c++/xycontroller/qtcreator/xycontroller.pro b/c++/xycontroller/qtcreator/xycontroller.pro new file mode 100644 index 0000000..3891f0e --- /dev/null +++ b/c++/xycontroller/qtcreator/xycontroller.pro @@ -0,0 +1,33 @@ +# QtCreator project file + +QT = core gui + +CONFIG = debug link_pkgconfig qt resources uic warn_on +PKGCONFIG = jack + +TARGET = xycontroller +TEMPLATE = app +VERSION = 0.5.0 + +DEFINES = HAVE_JACKSESSION + +SOURCES = \ + ../xycontroller.cpp \ + ../../widgets/pixmapdial.cpp \ + ../../widgets/pixmapkeyboard.cpp + +HEADERS = \ + ../../jack_utils.h \ + ../../widgets/pixmapdial.h \ + ../../widgets/pixmapkeyboard.h + +FORMS = \ + ../../../src/ui/xycontroller.ui + +RESOURCES = \ + ../../../resources/resources.qrc + +INCLUDEPATH = \ + ../../widgets + +QMAKE_CXXFLAGS *= -std=c++0x diff --git a/c++/xycontroller/xycontroller.cpp b/c++/xycontroller/xycontroller.cpp new file mode 100644 index 0000000..0c13e06 --- /dev/null +++ b/c++/xycontroller/xycontroller.cpp @@ -0,0 +1,936 @@ +/* + * 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 "0.5.0" + +//#include "../jack_utils.h" +#include "ui_xycontroller.h" + +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ------------------------------- + +class Queue { +public: + Queue(int size_) : + size(size_) + { + } + + ~Queue() + { + } + + void put_nowait(int, int, int) + { + //const QMutexLocker m(&mutex); + } + +private: + const int size; + //QMutex mutex; +}; + +qreal abs_q(const qreal value) +{ + if (value < 1.0) + return -value; + return value; +} + +#if 0 +jack_client_t* jack_client = nullptr; +jack_port_t* jack_midi_in_port = nullptr; +jack_port_t* jack_midi_out_port = nullptr; +#endif +Queue jack_midi_in_data = Queue(512); +Queue jack_midi_out_data = Queue(512); + +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 8"; + MIDI_CC_LIST << "0x53 General Purpose 9"; + 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; + m_smooth_y = 0; + + 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); + } + + ~XYGraphicsScene() + { + } + + void setControlX(int x) + { + cc_x = x; + } + + void setControlY(int y) + { + cc_y = y; + } + + void setChannels(QList channels) + { + m_channels = channels; + } + + void setPosX(qreal x, bool forward=true) + { + if (! m_mouseLock) + { + qreal pos_x = x * (p_size.x() + p_size.width()); + m_cursor->setPos(pos_x, m_cursor->y()); + m_lineV->setX(pos_x); + + if (forward) + { + qreal value = pos_x / (p_size.x() + p_size.width()); + sendMIDI(&value, nullptr); + } + else + m_smooth_x = pos_x; + } + } + + void setPosY(qreal y, bool forward=true) + { + if (! m_mouseLock) + { + qreal pos_y = y * (p_size.y() + p_size.height()); + m_cursor->setPos(m_cursor->x(), pos_y); + m_lineH->setY(pos_y); + + if (forward) + { + qreal value = pos_y / (p_size.y() + p_size.height()); + sendMIDI(nullptr, &value); + } + else + m_smooth_y = pos_y; + } + } + + void setSmooth(bool smooth) + { + m_smooth = smooth; + } + + void setSmoothValues(int x, int 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; + qreal xp, yp; + xp = yp = 0.0; + + if (param == cc_x) + { + sendUpdate = true; + xp = (float(value) / 63) - 1.0; + yp = m_cursor->y() / (p_size.y() + p_size.height()); + + if (xp < -1.0) + xp = -1.0; + else if (xp > 1.0) + xp = 1.0; + + setPosX(xp, false); + } + + if (param == cc_y) + { + sendUpdate = true; + xp = m_cursor->x() / (p_size.x() + p_size.width()); + yp = (float(value) / 63) - 1.0; + + if (yp < -1.0) + yp = -1.0; + else if (yp > 1.0) + yp = 1.0; + + setPosY(yp, false); + } + + if (sendUpdate) + emit cursorMoved(xp, yp); + } + + void updateSize(QSize size) + { + p_size.setRect(-(size.width() / 2), -(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) + { + if (abs(m_cursor->x() - m_smooth_x) <= 0.001) + { + m_smooth_x = m_cursor->x(); + return; + } + else if (abs(m_cursor->y() - m_smooth_y) <= 0.001) + { + m_smooth_y = m_cursor->y(); + return; + } + + qreal new_x = (m_smooth_x + m_cursor->x() * 3) / 4; + qreal new_y = (m_smooth_y + m_cursor->y() * 3) / 4; + QPointF pos(new_x, new_y); + + m_cursor->setPos(pos); + m_lineH->setY(pos.y()); + m_lineV->setX(pos.x()); + + qreal xp = pos.x() / (p_size.x() + p_size.width()); + qreal 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()); + + qreal xp = pos.x() / (p_size.x() + p_size.width()); + qreal yp = pos.y() / (p_size.y() + p_size.height()); + + sendMIDI(&xp, &yp); + + emit cursorMoved(xp, yp); + } + } + + void sendMIDI(qreal* xp=nullptr, qreal* yp=nullptr) + { + qreal rate = qreal(0xff) / 4; + + if (xp != nullptr) + { + int value = *xp * rate + rate; + foreach (const int& channel, m_channels) + jack_midi_out_data.put_nowait(0xB0 + channel - 1, cc_x, value); + } + + if (yp != nullptr) + { + int value = *yp * rate + rate; + foreach (const int& channel, m_channels) + jack_midi_out_data.put_nowait(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); + } + + QWidget* parent() const + { + return m_parent; + } + +signals: + void cursorMoved(qreal, qreal); + +private: + int cc_x; + int cc_y; + QList m_channels; + + bool m_mouseLock; + bool m_smooth; + int m_smooth_x; + int m_smooth_y; + + QGraphicsEllipseItem* m_cursor; + QGraphicsLineItem* m_lineH; + QGraphicsLineItem* m_lineV; + + QRectF p_size; + + QWidget* const m_parent; +}; + +// ------------------------------- +// XY Controller Window + +namespace Ui { +class XYControllerW; +} + +class XYControllerW : public QMainWindow +{ + Q_OBJECT + +public: + XYControllerW() : + QMainWindow(nullptr), + scene(this), + settings("Cadence", "XY-Controller"), + 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(6); + + 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(noteOn(int))); + connect(ui->keyboard, SIGNAL(noteOff(int)), SLOT(noteOff(int))); + + connect(ui->cb_smooth, SIGNAL(clicked(bool)), SLOT(setSmooth(bool))); + + connect(ui->dial_x, SIGNAL(valueChanged(int)), SLOT(updateSceneX(int))); + connect(ui->dial_y, SIGNAL(valueChanged(int)), SLOT(updateSceneY(int))); + + connect(ui->cb_control_x, SIGNAL(currentIndexChanged(QString)), SLOT(checkCC_X(QString))); + connect(ui->cb_control_y, SIGNAL(currentIndexChanged(QString)), SLOT(checkCC_Y(QString))); + + connect(&scene, SIGNAL(cursorMoved(qreal, qreal)), SLOT(sceneCursorMoved(qreal, qreal))); + + connect(ui->act_ch_01, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_02, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_03, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_04, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_05, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_06, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_07, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_08, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_09, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_10, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_11, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_12, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_13, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_14, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_15, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_16, SIGNAL(triggered(bool)), SLOT(checkChannel(bool))); + connect(ui->act_ch_all, SIGNAL(triggered()), SLOT(checkChannel_all())); + connect(ui->act_ch_none, SIGNAL(triggered()), SLOT(checkChannel_none())); + + connect(ui->act_show_keyboard, SIGNAL(triggered(bool)), SLOT(showKeyboard(bool))); + connect(ui->act_about, SIGNAL(triggered()), SLOT(about())); + + // ------------------------------------------------------------- + // Final stuff + + m_midiInTimerId = startTimer(50); + QTimer::singleShot(0, this, SLOT(updateScreen())); + } + +protected slots: + void noteOn(int note) + { + foreach (const int& channel, m_channels) + jack_midi_out_data.put_nowait(0x90 + channel - 1, note, 100); + } + + void noteOff(int note) + { + foreach (const int& channel, m_channels) + jack_midi_out_data.put_nowait(0x80 + channel - 1, note, 0); + } + + void updateSceneX(int x) + { + scene.setPosX(float(x) / 100, bool(sender())); + } + + void updateSceneY(int y) + { + scene.setPosY(float(y) / 100, bool(sender())); + } + + void checkCC_X(QString text) + { + if (! text.isEmpty()) + { + 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 checkCC_Y(QString text) + { + if (! text.isEmpty()) + { + 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 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 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 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 setSmooth(bool yesno) + { + scene.setSmooth(yesno); + } + + void sceneCursorMoved(qreal xp, qreal 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 showKeyboard(bool yesno) + { + ui->scrollArea->setVisible(yesno); + QTimer::singleShot(0, this, SLOT(updateScreen())); + } + + void 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 falkTX").arg(VERSION)); + } + + 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(); + updateSceneX(dial_x); + updateSceneY(dial_y); + scene.setSmoothValues(float(dial_x) / 100, float(dial_y) / 100); + } + +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 not jack_midi_in_data.empty(): + //while True: + //try: + // data1, data2, data3 = jack_midi_in_data.get_nowait() + //except QuequeEmpty: + // break + + //channel = (data1 & 0x0F) + 1 + //mode = data1 & 0xF0 + + //if channel in self.m_channels: + // if mode == 0x80: + // self.keyboard.sendNoteOff(data2, False) + // elif mode == 0x90: + // self.keyboard.sendNoteOn(data2, False) + // elif mode == 0xB0: + // self.scene.handleCC(data2, data3) + + //jack_midi_in_data.task_done() + + 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; + + XYGraphicsScene scene; + QSettings settings; + + Ui::XYControllerW* ui; +}; + +#include "xycontroller.moc" + +// ------------------------------- + +// ------------------------------- + +int main(int argc, char* argv[]) +{ + MIDI_CC_LIST__init(); + + QApplication app(argc, argv); + app.setApplicationName("XY-Controller"); + app.setApplicationVersion(VERSION); + app.setOrganizationName("Cadence"); + //app.setWindowIcon(QIcon(":/48x48/xy-controller.png")); + +#if 0 + // JACK initialization + jack_status_t jStatus; + jack_options_t jOptions = static_cast(JackNoStartServer/*|JackSessionID*/); + jack_client = jack_client_open("XY-Controller", jOptions, &jStatus); + + if (! jack_client) + { + std::string errorString(jack_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; + } + + jack_midi_in_port = jack_port_register(jack_client, "midi_in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); + jack_midi_out_port = jack_port_register(jack_client, "midi_out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); + + //jack_set_process_callback(jClient, process_callback, nullptr); + //jack_set_port_connect_callback(jClient, port_callback, nullptr); + //jack_set_session_callback(jClient, session_callback, argv[0]); + jack_activate(jack_client); +#endif + + // Show GUI + XYControllerW gui; + gui.show(); + + // App-Loop + int ret = app.exec(); + +#if 0 + jack_deactivate(jack_client); + jack_client_close(jack_client); +#endif + + return ret; +}