3337e5294b
fix #276217 Support MIDI-IN while playback engaged
546 lines
19 KiB
C++
546 lines
19 KiB
C++
//=============================================================================
|
|
// MusE Score
|
|
// Linux Music Score Editor
|
|
//
|
|
// Copyright (C) 2011-2016 Werner Schweer and others
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License version 2.
|
|
//
|
|
// 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.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
//=============================================================================
|
|
|
|
#include "pianotools.h"
|
|
#include "preferences.h"
|
|
#include "libmscore/chord.h"
|
|
|
|
namespace Ms {
|
|
|
|
static const qreal KEY_WIDTH = 13.0;
|
|
static const qreal BKEY_WIDTH = 8.0;
|
|
static const qreal KEY_HEIGHT = 40.0;
|
|
static const qreal BKEY_HEIGHT = 25.0;
|
|
|
|
//---------------------------------------------------------
|
|
// HPiano
|
|
//---------------------------------------------------------
|
|
|
|
HPiano::HPiano(QWidget* parent)
|
|
: QGraphicsView(parent)
|
|
{
|
|
scaleVal = 1.0;
|
|
setLineWidth(0);
|
|
setMidLineWidth(0);
|
|
|
|
setScene(new QGraphicsScene);
|
|
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
|
|
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
|
|
setMouseTracking(true);
|
|
setRubberBandSelectionMode(Qt::IntersectsItemBoundingRect);
|
|
setDragMode(QGraphicsView::RubberBandDrag);
|
|
setScale(2.5);
|
|
|
|
grabGesture(Qt::PinchGesture); // laptop pad (Mac) and touchscreen
|
|
|
|
scene()->setSceneRect(0.0, 0.0, KEY_WIDTH * 52, KEY_HEIGHT);
|
|
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
setSizePolicy(policy);
|
|
int margin = 16;
|
|
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, 1000));
|
|
|
|
_firstKey = 21;
|
|
_lastKey = 108; // 88 key piano
|
|
qreal x = 0.0;
|
|
for (int i = _firstKey; i <= _lastKey; ++i) {
|
|
PianoKeyItem* k = new PianoKeyItem(this, i);
|
|
switch(i % 12) {
|
|
case 0:
|
|
case 5:
|
|
k->setType(i == _lastKey ? 6 : 0);
|
|
k->setPos(x, 0);
|
|
x += KEY_WIDTH;
|
|
break;
|
|
|
|
case 2:
|
|
k->setType(1);
|
|
k->setPos(x, 0);
|
|
x += KEY_WIDTH;
|
|
break;
|
|
case 4:
|
|
case 11:
|
|
k->setType(2);
|
|
k->setPos(x, 0);
|
|
x += KEY_WIDTH;
|
|
break;
|
|
|
|
case 7:
|
|
k->setType(3);
|
|
k->setPos(x, 0);
|
|
x += KEY_WIDTH;
|
|
break;
|
|
|
|
case 9:
|
|
k->setType(i == _firstKey ? 5 : 4);
|
|
k->setPos(x, 0);
|
|
x += KEY_WIDTH;
|
|
break;
|
|
|
|
case 1:
|
|
case 6:
|
|
k->setType(7);
|
|
k->setPos(x - BKEY_WIDTH * 5/9, 0);
|
|
break;
|
|
case 3:
|
|
case 10:
|
|
k->setType(7);
|
|
k->setPos(x - BKEY_WIDTH * 4/9, 0);
|
|
break;
|
|
case 8:
|
|
k->setType(7);
|
|
k->setPos(x - BKEY_WIDTH / 2.0, 0);
|
|
break;
|
|
}
|
|
keys.append(k);
|
|
scene()->addItem(k);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setScale
|
|
//---------------------------------------------------------
|
|
|
|
void HPiano::setScale(qreal s)
|
|
{
|
|
if (s > 16.0)
|
|
s = 16.0;
|
|
else if (s < .5)
|
|
s = .5;
|
|
if (s != scaleVal) {
|
|
scaleVal = s;
|
|
int margin = 16;
|
|
QDockWidget* par = static_cast<QDockWidget*>(parent());
|
|
if (par) {
|
|
if (!par->isFloating())
|
|
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, 1000));
|
|
else
|
|
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, (KEY_HEIGHT + margin) * scaleVal));
|
|
}
|
|
else
|
|
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, (KEY_HEIGHT + margin) * scaleVal));
|
|
setMinimumSize(QSize(100 * scaleVal, (KEY_HEIGHT + margin) * scaleVal));
|
|
QTransform t;
|
|
t.scale(scaleVal, scaleVal);
|
|
setTransform(t, false);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// sizeHint
|
|
//---------------------------------------------------------
|
|
|
|
QSize HPiano::sizeHint() const
|
|
{
|
|
return QSize(KEY_WIDTH * 52 + 1, KEY_HEIGHT+1);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// pressKeys
|
|
//---------------------------------------------------------
|
|
|
|
void HPiano::setPressedPlaybackPitches(QSet<int> pitches)
|
|
{
|
|
_pressedPlaybackPitches = pitches;
|
|
updateAllKeys();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// pressPitch
|
|
//---------------------------------------------------------
|
|
|
|
void HPiano::pressPitch(int pitch)
|
|
{
|
|
_pressedPitches.insert(pitch);
|
|
updateAllKeys();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// releasePitch
|
|
//---------------------------------------------------------
|
|
|
|
void HPiano::releasePitch(int pitch)
|
|
{
|
|
_pressedPitches.remove(pitch);
|
|
updateAllKeys();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// changeSelection
|
|
//---------------------------------------------------------
|
|
|
|
void HPiano::changeSelection(const Selection& selection)
|
|
{
|
|
for (PianoKeyItem* key : keys) {
|
|
key->setHighlighted(false);
|
|
key->setSelected(false);
|
|
}
|
|
for (Note* n : selection.noteList()) {
|
|
if (n->epitch() >= _firstKey && n->epitch() <= _lastKey)
|
|
keys[n->epitch() - _firstKey]->setSelected(true);
|
|
for (Note* other : n->chord()->notes())
|
|
if (other->epitch() >= _firstKey && other->epitch() <= _lastKey)
|
|
keys[other->epitch() - _firstKey]->setHighlighted(true);
|
|
}
|
|
for (PianoKeyItem* key : keys)
|
|
key->update();
|
|
}
|
|
|
|
// used when currentScore() is NULL; same as above except the for loop
|
|
void HPiano::clearSelection()
|
|
{
|
|
for (PianoKeyItem* key : keys) {
|
|
key->setHighlighted(false);
|
|
key->setSelected(false);
|
|
key->update();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// updateAllKeys
|
|
//---------------------------------------------------------
|
|
|
|
void HPiano::updateAllKeys()
|
|
{
|
|
for (PianoKeyItem* key : keys) {
|
|
key->setPressed(_pressedPitches.contains(key->pitch())
|
|
|| _pressedPlaybackPitches.contains(key->pitch()));
|
|
key->update();
|
|
}
|
|
}
|
|
|
|
void HPiano::setMaximum(bool top_level) {
|
|
int margin = 16;
|
|
if (!top_level)
|
|
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, 1000));
|
|
else
|
|
setMaximumSize(QSize((KEY_WIDTH * 52 + margin/2) * scaleVal, (KEY_HEIGHT + margin) * scaleVal));
|
|
updateAllKeys();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// PianoKeyItem
|
|
//---------------------------------------------------------
|
|
|
|
PianoKeyItem::PianoKeyItem(HPiano* _piano, int p)
|
|
: QGraphicsPathItem()
|
|
{
|
|
piano = _piano;
|
|
_pitch = p;
|
|
_pressed = false;
|
|
_selected = false;
|
|
_highlighted = false;
|
|
type = -1;
|
|
|
|
QString pitchNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
|
QString text = pitchNames[_pitch % 12] + QString::number((_pitch / 12) - 1);
|
|
setToolTip(text);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setType
|
|
//---------------------------------------------------------
|
|
|
|
void PianoKeyItem::setType(int val)
|
|
{
|
|
type = val;
|
|
QPainterPath path;
|
|
qreal triangle = 1.0;
|
|
qreal htriangle = triangle/2;
|
|
switch(type) {
|
|
case 0:
|
|
path.moveTo(0,0);
|
|
path.lineTo(0, KEY_HEIGHT-triangle);
|
|
path.lineTo(triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
|
|
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 5/9, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 5/9, 0);
|
|
break;
|
|
case 1:
|
|
path.moveTo(BKEY_WIDTH * 4/9, 0);
|
|
path.lineTo(BKEY_WIDTH * 4/9, BKEY_HEIGHT);
|
|
path.lineTo(0, BKEY_HEIGHT);
|
|
path.lineTo(0, KEY_HEIGHT-triangle);
|
|
path.lineTo(triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
|
|
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, 0);
|
|
break;
|
|
case 2:
|
|
path.moveTo(BKEY_WIDTH * 5/9, 0);
|
|
path.lineTo(BKEY_WIDTH * 5/9, BKEY_HEIGHT);
|
|
path.lineTo(0, BKEY_HEIGHT);
|
|
path.lineTo(0, KEY_HEIGHT-triangle);
|
|
path.lineTo(triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
|
|
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH, 0);
|
|
break;
|
|
case 3:
|
|
path.moveTo(BKEY_WIDTH * 4/9, 0);
|
|
path.lineTo(BKEY_WIDTH * 4/9, BKEY_HEIGHT);
|
|
path.lineTo(0, BKEY_HEIGHT);
|
|
path.lineTo(0, KEY_HEIGHT-triangle);
|
|
path.lineTo(triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
|
|
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 5/9, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 5/9, 0);
|
|
break;
|
|
case 4:
|
|
path.moveTo(BKEY_WIDTH * 5/9, 0);
|
|
path.lineTo(BKEY_WIDTH * 5/9, BKEY_HEIGHT);
|
|
path.lineTo(0, BKEY_HEIGHT);
|
|
path.lineTo(0, KEY_HEIGHT-triangle);
|
|
path.lineTo(triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
|
|
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, 0);
|
|
break;
|
|
case 5:
|
|
path.moveTo(0,0);
|
|
path.lineTo(0, KEY_HEIGHT-triangle);
|
|
path.lineTo(triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
|
|
path.lineTo(KEY_WIDTH, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, BKEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH - BKEY_WIDTH * 4/9, 0);
|
|
break;
|
|
case 6:
|
|
path.moveTo(0,0);
|
|
path.lineTo(0, KEY_HEIGHT-triangle);
|
|
path.lineTo(triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH-triangle, KEY_HEIGHT);
|
|
path.lineTo(KEY_WIDTH, KEY_HEIGHT-triangle);
|
|
path.lineTo(KEY_WIDTH, 0);
|
|
break;
|
|
case 7:
|
|
path.moveTo(0,0);
|
|
path.lineTo(0, BKEY_HEIGHT-htriangle);
|
|
path.lineTo(htriangle, BKEY_HEIGHT);
|
|
path.lineTo(BKEY_WIDTH-htriangle, BKEY_HEIGHT);
|
|
path.lineTo(BKEY_WIDTH, BKEY_HEIGHT-htriangle);
|
|
path.lineTo(BKEY_WIDTH, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
path.closeSubpath();
|
|
setPath(path);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mousePressEvent
|
|
//---------------------------------------------------------
|
|
|
|
void PianoKeyItem::mousePressEvent(QGraphicsSceneMouseEvent*)
|
|
{
|
|
_pressed = true;
|
|
update();
|
|
bool chord = qApp->keyboardModifiers() & Qt::ShiftModifier;
|
|
emit piano->keyPressed(_pitch, chord, 80);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mouseReleaseEvent
|
|
//---------------------------------------------------------
|
|
|
|
void PianoKeyItem::mouseReleaseEvent(QGraphicsSceneMouseEvent*)
|
|
{
|
|
_pressed = false;
|
|
update();
|
|
emit piano->keyReleased(_pitch, false, 0);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// paint
|
|
//---------------------------------------------------------
|
|
|
|
void PianoKeyItem::paint(QPainter* p, const QStyleOptionGraphicsItem* /*o*/, QWidget*)
|
|
{
|
|
p->setRenderHint(QPainter::Antialiasing, true);
|
|
p->setPen(QPen(Qt::black, .8));
|
|
if (_pressed) {
|
|
QColor c(preferences.getColor(PREF_UI_PIANO_HIGHLIGHTCOLOR));
|
|
c.setAlpha(180);
|
|
p->setBrush(c);
|
|
}
|
|
else if (_selected) {
|
|
QColor c(preferences.getColor(PREF_UI_PIANO_HIGHLIGHTCOLOR));
|
|
c.setAlpha(100);
|
|
p->setBrush(c);
|
|
}
|
|
else if (_highlighted)
|
|
p->setBrush(type >= 7 ? QColor(125, 125, 125) : QColor(200, 200, 200));
|
|
else
|
|
p->setBrush(type >= 7 ? Qt::black : Qt::white);
|
|
p->drawPath(path());
|
|
if (_pitch % 12 == 0) {
|
|
QFont f("FreeSerif", 6);
|
|
p->setFont(f);
|
|
QString text = "C" + QString::number((_pitch / 12) - 1);
|
|
p->drawText(QRectF(KEY_WIDTH / 2, KEY_HEIGHT - 8, 0, 0),
|
|
Qt::AlignCenter | Qt::TextDontClip, text);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// PianoTools
|
|
//---------------------------------------------------------
|
|
|
|
PianoTools::PianoTools(QWidget* parent)
|
|
: QDockWidget(parent)
|
|
{
|
|
setObjectName("piano");
|
|
setAllowedAreas(Qt::DockWidgetAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea));
|
|
|
|
_piano = new HPiano;
|
|
_piano->setFocusPolicy(Qt::ClickFocus);
|
|
setWidget(_piano);
|
|
|
|
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
setSizePolicy(policy);
|
|
|
|
connect(_piano, SIGNAL(keyPressed(int, bool, int)), SIGNAL(keyPressed(int, bool, int)));
|
|
connect(_piano, SIGNAL(keyReleased(int, bool, int)), SIGNAL(keyReleased(int, bool, int)));
|
|
connect(this, SIGNAL(topLevelChanged(bool)), _piano, SLOT(setMaximum(bool)));
|
|
retranslate();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// retranslate
|
|
//---------------------------------------------------------
|
|
|
|
void PianoTools::retranslate()
|
|
{
|
|
setWindowTitle(tr("Piano Keyboard"));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// heartBeat
|
|
//---------------------------------------------------------
|
|
|
|
void PianoTools::setPlaybackNotes(QList<const Ms::Note *> notes)
|
|
{
|
|
QSet<int> pitches;
|
|
for (const Note* note : notes) {
|
|
pitches.insert(note->ppitch());
|
|
}
|
|
_piano->setPressedPlaybackPitches(pitches);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// changeEvent
|
|
//---------------------------------------------------------
|
|
|
|
void PianoTools::changeEvent(QEvent *event)
|
|
{
|
|
QDockWidget::changeEvent(event);
|
|
if (event->type() == QEvent::LanguageChange)
|
|
retranslate();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// wheelEvent
|
|
//---------------------------------------------------------
|
|
|
|
void HPiano::wheelEvent(QWheelEvent* event)
|
|
{
|
|
static int deltaSum = 0;
|
|
deltaSum += event->delta();
|
|
int step = deltaSum / 120;
|
|
deltaSum %= 120;
|
|
qreal mag = scaleVal;
|
|
if (event->modifiers() & Qt::ControlModifier) {
|
|
if (step > 0) {
|
|
for (int i = 0; i < step; ++i)
|
|
mag *= 1.1;
|
|
}
|
|
else {
|
|
for (int i = 0; i < -step; ++i)
|
|
mag /= 1.1;
|
|
}
|
|
setScale(mag);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// gestureEvent
|
|
// fired on touchscreen gestures as well as Mac touchpad gestures
|
|
//---------------------------------------------------------
|
|
|
|
bool HPiano::event(QEvent* event)
|
|
{
|
|
if (event->type() == QEvent::Gesture) {
|
|
return gestureEvent(static_cast<QGestureEvent*>(event));
|
|
}
|
|
return QGraphicsView::event(event);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// gestureEvent
|
|
// fired on touchscreen gestures as well as Mac touchpad gestures
|
|
//---------------------------------------------------------
|
|
|
|
bool HPiano::gestureEvent(QGestureEvent *event)
|
|
{
|
|
if (QGesture *gesture = event->gesture(Qt::PinchGesture)) {
|
|
// Zoom in/out when receiving a pinch gesture
|
|
QPinchGesture *pinch = static_cast<QPinchGesture *>(gesture);
|
|
|
|
static qreal magStart = 1.0;
|
|
if (pinch->state() == Qt::GestureStarted) {
|
|
magStart = scaleVal;
|
|
}
|
|
if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged) {
|
|
// On Windows, totalScaleFactor() contains the net magnification.
|
|
// On OS X, totalScaleFactor() is 1, and scaleFactor() contains the net magnification.
|
|
qreal value = pinch->totalScaleFactor();
|
|
if (value == 1) {
|
|
value = pinch->scaleFactor();
|
|
}
|
|
// Qt 5.4 doesn't report pinch->centerPoint() correctly
|
|
setScale(magStart*value);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// changeSelection
|
|
//---------------------------------------------------------
|
|
|
|
void PianoTools::changeSelection(const Selection& selection)
|
|
{
|
|
_piano->changeSelection(selection);
|
|
}
|
|
|
|
void PianoTools::clearSelection()
|
|
{
|
|
_piano->clearSelection();
|
|
}
|
|
}
|