From cc4a4bb4711a87ad195fe131c187cec017faf4b0 Mon Sep 17 00:00:00 2001
From: Nils <>
Date: Fri, 25 Feb 2022 16:16:21 +0100
Subject: [PATCH] Refactor selectCurrentInstrument chain to prepare favorites
widget
---
engine/instrument.py | 4 +-
qtgui/designer/mainwindow.py | 126 +++++-------
qtgui/designer/mainwindow.ui | 270 +++++++++++---------------
qtgui/favorites.py | 47 +++++
qtgui/horizontalpiano.py | 38 +++-
qtgui/instrument.py | 69 ++++---
qtgui/mainwindow.py | 100 ++++++++--
qtgui/selectedinstrumentcontroller.py | 43 ++--
qtgui/verticalpiano.py | 35 +++-
9 files changed, 431 insertions(+), 301 deletions(-)
create mode 100644 qtgui/favorites.py
diff --git a/engine/instrument.py b/engine/instrument.py
index aafebef..84d9a44 100644
--- a/engine/instrument.py
+++ b/engine/instrument.py
@@ -151,9 +151,6 @@ class Instrument(object):
def exportMetadata(self)->dict:
"""
This is the big update that sends everything to build a GUI database
-
- Please note that we don't add the default variant here. It is only important for the
- external world to know what the current variant is. Which is handled by self.exportStatus()
"""
parentMetadata = self.parentLibrary.config["library"]
@@ -188,6 +185,7 @@ class Instrument(object):
def loadSamples(self):
"""
Convenience starter. Use this.
+ Used for loading save files as well as manual loading
"""
if not self.enabled:
self.enable()
diff --git a/qtgui/designer/mainwindow.py b/qtgui/designer/mainwindow.py
index fdaf0a2..4c7593d 100644
--- a/qtgui/designer/mainwindow.py
+++ b/qtgui/designer/mainwindow.py
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
- MainWindow.resize(1364, 977)
+ MainWindow.resize(1175, 651)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.centralwidget)
@@ -47,35 +47,6 @@ class Ui_MainWindow(object):
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setSpacing(0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.auditionerWidget = QtWidgets.QWidget(self.Instruments)
- self.auditionerWidget.setObjectName("auditionerWidget")
- self.horizontalLayout = QtWidgets.QHBoxLayout(self.auditionerWidget)
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.auditionerVolumeDial = QtWidgets.QDial(self.auditionerWidget)
- self.auditionerVolumeDial.setMaximumSize(QtCore.QSize(32, 32))
- self.auditionerVolumeDial.setSizeIncrement(QtCore.QSize(3, 0))
- self.auditionerVolumeDial.setMinimum(-40)
- self.auditionerVolumeDial.setMaximum(0)
- self.auditionerVolumeDial.setPageStep(3)
- self.auditionerVolumeDial.setProperty("value", -3)
- self.auditionerVolumeDial.setWrapping(False)
- self.auditionerVolumeDial.setNotchTarget(3.0)
- self.auditionerVolumeDial.setNotchesVisible(True)
- self.auditionerVolumeDial.setObjectName("auditionerVolumeDial")
- self.horizontalLayout.addWidget(self.auditionerVolumeDial)
- self.label = QtWidgets.QLabel(self.auditionerWidget)
- self.label.setObjectName("label")
- self.horizontalLayout.addWidget(self.label)
- self.auditionerMidiInputComboBox = QtWidgets.QComboBox(self.auditionerWidget)
- self.auditionerMidiInputComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
- self.auditionerMidiInputComboBox.setObjectName("auditionerMidiInputComboBox")
- self.horizontalLayout.addWidget(self.auditionerMidiInputComboBox)
- self.auditionerCurrentInstrument_label = QtWidgets.QLabel(self.auditionerWidget)
- self.auditionerCurrentInstrument_label.setObjectName("auditionerCurrentInstrument_label")
- self.horizontalLayout.addWidget(self.auditionerCurrentInstrument_label)
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.horizontalLayout.addItem(spacerItem)
- self.verticalLayout_2.addWidget(self.auditionerWidget)
self.instruments_treeWidget = QtWidgets.QTreeWidget(self.Instruments)
self.instruments_treeWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.instruments_treeWidget.setProperty("showDropIndicator", False)
@@ -86,46 +57,16 @@ class Ui_MainWindow(object):
self.instruments_treeWidget.setObjectName("instruments_treeWidget")
self.verticalLayout_2.addWidget(self.instruments_treeWidget)
self.iinstruments_tabWidget.addTab(self.Instruments, "")
- self.Mixer = QtWidgets.QWidget()
- self.Mixer.setObjectName("Mixer")
- self.mixerVerticalLayout = QtWidgets.QVBoxLayout(self.Mixer)
- self.mixerVerticalLayout.setContentsMargins(0, 0, 0, 0)
- self.mixerVerticalLayout.setSpacing(0)
- self.mixerVerticalLayout.setObjectName("mixerVerticalLayout")
- self.mixerInstructionLabel = QtWidgets.QLabel(self.Mixer)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.mixerInstructionLabel.sizePolicy().hasHeightForWidth())
- self.mixerInstructionLabel.setSizePolicy(sizePolicy)
- self.mixerInstructionLabel.setWordWrap(True)
- self.mixerInstructionLabel.setObjectName("mixerInstructionLabel")
- self.mixerVerticalLayout.addWidget(self.mixerInstructionLabel)
- self.scrollArea = QtWidgets.QScrollArea(self.Mixer)
- self.scrollArea.setWidgetResizable(True)
- self.scrollArea.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
- self.scrollArea.setObjectName("scrollArea")
- self.mixerAreaWidget = QtWidgets.QWidget()
- self.mixerAreaWidget.setGeometry(QtCore.QRect(0, 0, 98, 113))
- self.mixerAreaWidget.setObjectName("mixerAreaWidget")
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.mixerAreaWidget)
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.progressBar = QtWidgets.QProgressBar(self.mixerAreaWidget)
- self.progressBar.setProperty("value", 24)
- self.progressBar.setOrientation(QtCore.Qt.Vertical)
- self.progressBar.setTextDirection(QtWidgets.QProgressBar.TopToBottom)
- self.progressBar.setObjectName("progressBar")
- self.horizontalLayout_2.addWidget(self.progressBar)
- self.progressBar_2 = QtWidgets.QProgressBar(self.mixerAreaWidget)
- self.progressBar_2.setProperty("value", 24)
- self.progressBar_2.setOrientation(QtCore.Qt.Vertical)
- self.progressBar_2.setObjectName("progressBar_2")
- self.horizontalLayout_2.addWidget(self.progressBar_2)
- spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.horizontalLayout_2.addItem(spacerItem1)
- self.scrollArea.setWidget(self.mixerAreaWidget)
- self.mixerVerticalLayout.addWidget(self.scrollArea)
- self.iinstruments_tabWidget.addTab(self.Mixer, "")
+ self.Favorites = QtWidgets.QWidget()
+ self.Favorites.setObjectName("Favorites")
+ self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.Favorites)
+ self.verticalLayout_5.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_5.setSpacing(0)
+ self.verticalLayout_5.setObjectName("verticalLayout_5")
+ self.favorites_treeWidget = QtWidgets.QTreeWidget(self.Favorites)
+ self.favorites_treeWidget.setObjectName("favorites_treeWidget")
+ self.verticalLayout_5.addWidget(self.favorites_treeWidget)
+ self.iinstruments_tabWidget.addTab(self.Favorites, "")
self.details_groupBox = QtWidgets.QGroupBox(self.splitter)
self.details_groupBox.setObjectName("details_groupBox")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.details_groupBox)
@@ -136,7 +77,7 @@ class Ui_MainWindow(object):
self.details_scrollArea.setWidgetResizable(True)
self.details_scrollArea.setObjectName("details_scrollArea")
self.scrollAreaWidgetContents = QtWidgets.QWidget()
- self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 1182, 225))
+ self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 993, 106))
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.formLayout = QtWidgets.QFormLayout(self.scrollAreaWidgetContents)
self.formLayout.setObjectName("formLayout")
@@ -160,6 +101,35 @@ class Ui_MainWindow(object):
self.details_scrollArea.setWidget(self.scrollAreaWidgetContents)
self.verticalLayout_3.addWidget(self.details_scrollArea)
self.verticalLayout.addWidget(self.splitter)
+ self.auditionerWidget = QtWidgets.QWidget(self.rightFrame)
+ self.auditionerWidget.setObjectName("auditionerWidget")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.auditionerWidget)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.auditionerVolumeDial = QtWidgets.QDial(self.auditionerWidget)
+ self.auditionerVolumeDial.setMaximumSize(QtCore.QSize(32, 32))
+ self.auditionerVolumeDial.setSizeIncrement(QtCore.QSize(3, 0))
+ self.auditionerVolumeDial.setMinimum(-40)
+ self.auditionerVolumeDial.setMaximum(0)
+ self.auditionerVolumeDial.setPageStep(3)
+ self.auditionerVolumeDial.setProperty("value", -3)
+ self.auditionerVolumeDial.setWrapping(False)
+ self.auditionerVolumeDial.setNotchTarget(3.0)
+ self.auditionerVolumeDial.setNotchesVisible(True)
+ self.auditionerVolumeDial.setObjectName("auditionerVolumeDial")
+ self.horizontalLayout.addWidget(self.auditionerVolumeDial)
+ self.label = QtWidgets.QLabel(self.auditionerWidget)
+ self.label.setObjectName("label")
+ self.horizontalLayout.addWidget(self.label)
+ self.auditionerMidiInputComboBox = QtWidgets.QComboBox(self.auditionerWidget)
+ self.auditionerMidiInputComboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
+ self.auditionerMidiInputComboBox.setObjectName("auditionerMidiInputComboBox")
+ self.horizontalLayout.addWidget(self.auditionerMidiInputComboBox)
+ self.auditionerCurrentInstrument_label = QtWidgets.QLabel(self.auditionerWidget)
+ self.auditionerCurrentInstrument_label.setObjectName("auditionerCurrentInstrument_label")
+ self.horizontalLayout.addWidget(self.auditionerCurrentInstrument_label)
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout.addItem(spacerItem)
+ self.verticalLayout.addWidget(self.auditionerWidget)
self.horizontalPianoFrame = QtWidgets.QFrame(self.rightFrame)
self.horizontalPianoFrame.setMinimumSize(QtCore.QSize(0, 100))
self.horizontalPianoFrame.setFrameShape(QtWidgets.QFrame.StyledPanel)
@@ -171,7 +141,7 @@ class Ui_MainWindow(object):
self.horizontalLayout_3.addWidget(self.rightFrame)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
- self.menubar.setGeometry(QtCore.QRect(0, 0, 1364, 20))
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 1175, 20))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
@@ -185,17 +155,21 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
- self.label.setText(_translate("MainWindow", "Auditioner MIDI Input"))
- self.auditionerCurrentInstrument_label.setText(_translate("MainWindow", "TextLabel"))
self.instruments_treeWidget.headerItem().setText(1, _translate("MainWindow", "ID"))
self.instruments_treeWidget.headerItem().setText(2, _translate("MainWindow", "Volume"))
self.instruments_treeWidget.headerItem().setText(3, _translate("MainWindow", "Name"))
self.instruments_treeWidget.headerItem().setText(4, _translate("MainWindow", "Variant"))
self.instruments_treeWidget.headerItem().setText(5, _translate("MainWindow", "Tags"))
self.iinstruments_tabWidget.setTabText(self.iinstruments_tabWidget.indexOf(self.Instruments), _translate("MainWindow", "Instruments"))
- self.mixerInstructionLabel.setText(_translate("MainWindow", "This mixer controls the amount of each loaded instrument in the optional mixer output-ports. Idividual instrument outputs are unaffected."))
- self.iinstruments_tabWidget.setTabText(self.iinstruments_tabWidget.indexOf(self.Mixer), _translate("MainWindow", "Mixer"))
+ self.favorites_treeWidget.headerItem().setText(1, _translate("MainWindow", "ID"))
+ self.favorites_treeWidget.headerItem().setText(2, _translate("MainWindow", "Volume"))
+ self.favorites_treeWidget.headerItem().setText(3, _translate("MainWindow", "Name"))
+ self.favorites_treeWidget.headerItem().setText(4, _translate("MainWindow", "Variant"))
+ self.favorites_treeWidget.headerItem().setText(5, _translate("MainWindow", "Tags"))
+ self.iinstruments_tabWidget.setTabText(self.iinstruments_tabWidget.indexOf(self.Favorites), _translate("MainWindow", "Favorites"))
self.details_groupBox.setTitle(_translate("MainWindow", "NamePlaceholder"))
self.variant_label.setText(_translate("MainWindow", "Variants"))
self.info_label.setText(_translate("MainWindow", "TextLabel"))
self.keySwitch_label.setText(_translate("MainWindow", "KeySwitch"))
+ self.label.setText(_translate("MainWindow", "Auditioner MIDI Input"))
+ self.auditionerCurrentInstrument_label.setText(_translate("MainWindow", "TextLabel"))
diff --git a/qtgui/designer/mainwindow.ui b/qtgui/designer/mainwindow.ui
index 1ba06bf..facc329 100644
--- a/qtgui/designer/mainwindow.ui
+++ b/qtgui/designer/mainwindow.ui
@@ -6,8 +6,8 @@
0
0
- 1364
- 977
+ 1175
+ 651
@@ -101,83 +101,6 @@
0
- -
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- 3
- 0
-
-
-
- -40
-
-
- 0
-
-
- 3
-
-
- -3
-
-
- false
-
-
- 3.000000000000000
-
-
- true
-
-
-
- -
-
-
- Auditioner MIDI Input
-
-
-
- -
-
-
- QComboBox::AdjustToContents
-
-
-
- -
-
-
- TextLabel
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
@@ -232,11 +155,11 @@
-
+
- Mixer
+ Favorites
-
+
0
@@ -253,77 +176,37 @@
0
-
-
-
-
- 0
- 0
-
-
-
- This mixer controls the amount of each loaded instrument in the optional mixer output-ports. Idividual instrument outputs are unaffected.
-
-
- true
-
-
-
- -
-
-
- true
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
-
-
-
-
- 0
- 0
- 98
- 113
-
+
+
+
+
-
-
-
-
-
- 24
-
-
- Qt::Vertical
-
-
- QProgressBar::TopToBottom
-
-
-
- -
-
-
- 24
-
-
- Qt::Vertical
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
+
+
+
+ ID
+
+
+
+
+ Volume
+
+
+
+
+ Name
+
+
+
+
+ Variant
+
+
+
+
+ Tags
+
+
@@ -359,8 +242,8 @@
0
0
- 1182
- 225
+ 993
+ 106
@@ -405,6 +288,83 @@
+ -
+
+
+
-
+
+
+
+ 32
+ 32
+
+
+
+
+ 3
+ 0
+
+
+
+ -40
+
+
+ 0
+
+
+ 3
+
+
+ -3
+
+
+ false
+
+
+ 3.000000000000000
+
+
+ true
+
+
+
+ -
+
+
+ Auditioner MIDI Input
+
+
+
+ -
+
+
+ QComboBox::AdjustToContents
+
+
+
+ -
+
+
+ TextLabel
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
-
@@ -432,7 +392,7 @@
0
0
- 1364
+ 1175
20
diff --git a/qtgui/favorites.py b/qtgui/favorites.py
new file mode 100644
index 0000000..5faa921
--- /dev/null
+++ b/qtgui/favorites.py
@@ -0,0 +1,47 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net )
+
+This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),
+
+This application 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 3 of the License, or
+(at your option) 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.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
+#Standard Library
+
+#Third Party
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+#Our Qt
+from .instrument import InstrumentTreeController
+
+
+#Engine
+import engine.api as api
+
+
+class FavoritesTreeController(InstrumentTreeController):
+ pass
+
+
+
+#Most Loaded
+#Most Recent
+#Manuelle Favoriten?
+
+
+#Selection an den anderen weitergeben.+++++
diff --git a/qtgui/horizontalpiano.py b/qtgui/horizontalpiano.py
index 154a306..d06aa78 100644
--- a/qtgui/horizontalpiano.py
+++ b/qtgui/horizontalpiano.py
@@ -30,6 +30,7 @@ from template.engine.duration import baseDurationToTraditionalNumber
#User modules
import engine.api as api
+from .instrument import GuiInstrument, GuiLibrary #for the types
from .verticalpiano import WIDTH as HEIGHT
from .verticalpiano import STAFFLINEGAP as WIDTH
WIDTH = WIDTH * 1.5
@@ -64,6 +65,27 @@ class HorizontalPiano(QtWidgets.QGraphicsView):
self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() + event.pixelDelta().y()) #y because it is the original vert. scroll
+ def currentTreeItemChanged(self, currentTreeItem:QtWidgets.QTreeWidgetItem):
+ """
+ Program wide GUI-only callback from
+ widget.currentItemChanged->mainWindow.currentTreeItemChanged. We set the currentItem
+ ourselves, so we need to block our signals to avoid recursion.
+
+ Only one item can be selected at a time.
+
+ The currentTreeItem we receive is not a global instance but from a widget different to ours.
+ We need to find our local version of the same instrument/library/idKey first.
+ """
+
+ isLibrary = type(currentTreeItem) is GuiLibrary
+ idKey = currentTreeItem.idKey
+
+ if isLibrary:
+ self.pianoScene.selectedInstrumentChanged(None)
+ else:
+ self.pianoScene.selectedInstrumentChanged(currentTreeItem.cachedInstrumentStatus)
+
+
class _HorizontalPianoScene(QtWidgets.QGraphicsScene):
"""Most of this is copy paste from piano grid"""
@@ -80,7 +102,7 @@ class _HorizontalPianoScene(QtWidgets.QGraphicsScene):
self.allKeys = {} # pitch/int : BlackKey or WhiteKey
self.numberLabels = [] #index is pitch
- self._selectedInstrument = None #tuple instrumentStatus, instrumentData
+ self._selectedInstrument = None #instrumentStatus dict
self._leftMouseDown = False #For note preview
self.gridPen = QtGui.QPen(QtCore.Qt.SolidLine)
@@ -168,7 +190,7 @@ class _HorizontalPianoScene(QtWidgets.QGraphicsScene):
"""GUI callback. Data is live"""
#Is this for us?
- if instrumentStatus and self._selectedInstrument and not instrumentStatus["idKey"] == self._selectedInstrument[0]["idKey"]:
+ if instrumentStatus and self._selectedInstrument and not instrumentStatus["idKey"] == self._selectedInstrument["idKey"]:
return
#else:
# print ("not for us", instrumentStatus["idKey"])
@@ -197,7 +219,7 @@ class _HorizontalPianoScene(QtWidgets.QGraphicsScene):
#self.numberLabels[keyPitch].hide()
- def selectedInstrumentChanged(self, instrumentStatus, instrumentData):
+ def selectedInstrumentChanged(self, instrumentStatus):
"""GUI click to different instrument. The arguments are cached GUI data
If a library is clicked, and not an instrument, both parameters will be None.
@@ -207,15 +229,15 @@ class _HorizontalPianoScene(QtWidgets.QGraphicsScene):
self.clearHorizontalPiano()
self.fakeDeactivationOverlay.show()
else:
- self._selectedInstrument = (instrumentStatus, instrumentData)
+ self._selectedInstrument = instrumentStatus
self.instrumentStatusChanged(instrumentStatus)
def highlightNoteOn(self, idKey:tuple, pitch:int, velocity:int):
- if self._selectedInstrument and self._selectedInstrument[0]["idKey"] == idKey:
+ if self._selectedInstrument and self._selectedInstrument["idKey"] == idKey:
self.allKeys[pitch].highlightOn()
def highlightNoteOff(self, idKey:tuple, pitch:int, velocity:int):
- if self._selectedInstrument and self._selectedInstrument[0]["idKey"] == idKey:
+ if self._selectedInstrument and self._selectedInstrument["idKey"] == idKey:
self.allKeys[pitch].highlightOff()
def allHighlightsOff(self):
@@ -236,7 +258,7 @@ class _HorizontalPianoScene(QtWidgets.QGraphicsScene):
def _off(self):
if self._selectedInstrument and not self._lastPlayPitch is None:
- status, data = self._selectedInstrument
+ status = self._selectedInstrument
libId, instrId = status["idKey"]
api.sendNoteOffToInstrument(status["idKey"], self._lastPlayPitch)
self._lastPlayPitch = None
@@ -259,7 +281,7 @@ class _HorizontalPianoScene(QtWidgets.QGraphicsScene):
if self._selectedInstrument and not pitch == self._lastPlayPitch:
#TODO: Play note on at a different instrument than note off? Possible?
- status, data = self._selectedInstrument
+ status = self._selectedInstrument
if not self._lastPlayPitch is None:
#Force a note off that is currently playing but not under the cursor anymore
diff --git a/qtgui/instrument.py b/qtgui/instrument.py
index 63e0b61..08c1600 100644
--- a/qtgui/instrument.py
+++ b/qtgui/instrument.py
@@ -47,15 +47,15 @@ class InstrumentTreeController(object):
to use. You need to add an Item to each cell. While in TreeWidget you just create one item.
"""
- def __init__(self, parentMainWindow):
+ def __init__(self, parentMainWindow, treeWidget):
self.parentMainWindow = parentMainWindow
- self.treeWidget = self.parentMainWindow.ui.instruments_treeWidget
+ self.treeWidget = treeWidget
self.reset()
#Includes:
#self._cachedData = None
#self._cachedLastInstrumentStatus = {} # instrument idKey : status Dict
- #self.guiLibraries = {} # idKey : GuiLibrary
+ #self.guiLibraries = {} # idKey : GuiLibrary. idKey is a tuple with second value -1, which would be the instrument.
#self.guiInstruments = {} # idKey : GuiInstrument
@@ -71,12 +71,16 @@ class InstrumentTreeController(object):
self.treeWidget.setColumnCount(len(self.headerLabels))
self.treeWidget.setHeaderLabels(self.headerLabels)
self.treeWidget.setSortingEnabled(True)
+ self.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
+ self.treeWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.treeWidget.setAlternatingRowColors(True)
self.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.treeWidget.header().setSortIndicator(0,0)
self.treeWidget.itemDoubleClicked.connect(self.itemDoubleClicked)
- self.treeWidget.itemSelectionChanged.connect(self.itemSelectionChanged)
+ self.treeWidget.currentItemChanged.connect(self.parentMainWindow.currentTreeItemChanged)
+ #self.treeWidget.itemSelectionChanged.connect(self.itemSelectionChanged) #also triggers on tab change between favorits and instruments
+ #self.treeWidget.itemClicked.connect(self.itemSelectionChanged) #This will not activate when using the arrow keys to select
self.treeWidget.itemExpanded.connect(self.itemExpandedOrCollapsed)
self.treeWidget.itemCollapsed.connect(self.itemExpandedOrCollapsed)
self.treeWidget.customContextMenuRequested.connect(self.contextMenu)
@@ -108,7 +112,7 @@ class InstrumentTreeController(object):
self._cachedData = None
self._cachedLastInstrumentStatus = {} # instrument idKey : status Dict
#The next two will delete all children through the garbage collector.
- self.guiLibraries = {} # idKey : GuiLibrary
+ self.guiLibraries = {} # idKey : GuiLibrary idKey is a tuple with second value -1, which would be the instrument.
self.guiInstruments = {} # idKey : GuiInstrument
@@ -116,20 +120,32 @@ class InstrumentTreeController(object):
if type(libraryItem) is GuiLibrary : #just in case
api.session.guiSharedDataToSave["libraryIsExpanded"][libraryItem.id] = libraryItem.isExpanded()
- def itemSelectionChanged(self):
- """Only one instrument can be selected at the same time.
- This function mostly informs other widgets that a different instrument was selected
+
+ def currentTreeItemChanged(self, currentTreeItem:QtWidgets.QTreeWidgetItem):
"""
- selItems = self.treeWidget.selectedItems()
- if not selItems:
- return
+ Program wide GUI-only callback from
+ widget.currentItemChanged->mainWindow.currentTreeItemChanged. We set the currentItem
+ ourselves, so we need to block our signals to avoid recursion.
- assert len(selItems) == 1, selItems #because our selection is set to single.
- item = selItems[0]
- if type(item) is GuiInstrument:
- self.parentMainWindow.selectedInstrumentController.instrumentChanged(item.idKey)
+ Only one item can be selected at a time.
+
+ The currentTreeItem we receive is not a global instance but from a widget different to ours.
+ We need to find our local version of the same instrument/library/idKey first.
+ """
+ self.treeWidget.blockSignals(True)
+
+ isLibrary = type(currentTreeItem) is GuiLibrary
+ idKey = currentTreeItem.idKey
+
+ if isLibrary:
+ assert idKey in self.guiLibraries, (idKey, self.guiLibraries)
+ item = self.guiLibraries[idKey]
else:
- self.parentMainWindow.selectedInstrumentController.directLibrary(item.idKey)
+ assert idKey in self.guiInstruments, (idKey, self.guiInstruments)
+ item = self.guiInstruments[idKey]
+
+ self.treeWidget.setCurrentItem(item) #This will work at first, but as soon as the tab instr/favorites is changed this will jump back to a wrong item (first of the group). We have a tabWidget.changed signal in the main window to reset this
+ self.treeWidget.blockSignals(False)
def itemDoubleClicked(self, item:QtWidgets.QTreeWidgetItem, column:int):
"""This chooses the auditioner. Callbacks are handled in the auditioner widget itself"""
@@ -206,7 +222,7 @@ class InstrumentTreeController(object):
"""
#Reset everything except our cached data.
self.treeWidget.clear() #will delete the C++ objects. We need to delete the PyQt objects ourselves, like so:
- self.guiLibraries = {} # idKey : GuiLibrary
+ self.guiLibraries = {} # idKey : GuiLibrary idKey is a tuple with second value -1, which would be the instrument.
self.guiInstruments = {} # idKey : GuiInstrument
if data:
@@ -223,7 +239,7 @@ class InstrumentTreeController(object):
if nested:
parentLibraryWidget = GuiLibrary(parentTreeController=self, libraryDict=libraryDict["library"])
- self.guiLibraries[libraryId] = parentLibraryWidget
+ self.guiLibraries[(libraryId, -1)] = parentLibraryWidget #-1 marks the library but keeps it a tuple.
self.treeWidget.addTopLevelItem(parentLibraryWidget)
if libraryId in api.session.guiSharedDataToSave["libraryIsExpanded"]:
parentLibraryWidget.setExpanded(api.session.guiSharedDataToSave["libraryIsExpanded"][libraryId]) #only possible after gi.init() was done and item inserted.
@@ -270,7 +286,7 @@ class InstrumentTreeController(object):
"""We do not use the qt function collapseAll and expandAll because they do not trigger
the signal"""
if self.isNested():
- for libid, guiLib in self.guiLibraries.items():
+ for idKey, guiLib in self.guiLibraries.items():
guiLib.setExpanded(state)
def _adjustColumnSize(self):
@@ -334,7 +350,7 @@ class GuiLibrary(QtWidgets.QTreeWidgetItem):
super().__init__([], type=1000) #type 0 is default qt type. 1000 is subclassed user type)
self.id = libraryDict["id"]
- self.idKey = (libraryDict["id"], 0) #fake it for compatibility
+ self.idKey = (libraryDict["id"], -1) #fake it for compatibility. -1 means library
self.name = libraryDict["name"]
@@ -474,15 +490,22 @@ class GuiInstrument(QtWidgets.QTreeWidgetItem):
def instrumentSwitchOnViaGui(self, state):
"""Only GUI clicks. Does not react to the engine callback that switches on instruments. For
- example one that arrives through "load group" or "load all" """
+ example one that arrives through "load group" or "load all"
+
+ We use this to count, across program runs, how many times this instrument was activated
+ and present it as favourites. The engine does not know about this.
+ Loading states from save files, "load all" and other algorithmic loaders do not contribute
+ to this counting.
+ """
if state:
api.loadInstrumentSamples(self.idKey)
+ self.parentTreeController.parentMainWindow.favoriteInstrument(self.idKey)
else:
api.unloadInstrumentSamples(self.idKey)
def updateStatus(self, instrumentStatus:dict):
#Before we set the state permanently we use the opportunity to see if this is program state (state == None) or unloading
- self._cachedInstrumentStatus = instrumentStatus
+ self.cachedInstrumentStatus = instrumentStatus
firstLoad = self.state is None
variantColumnIndex = self.columns.index("loaded")
self.currentVariant = instrumentStatus["currentVariant"]
@@ -521,7 +544,7 @@ class GuiInstrument(QtWidgets.QTreeWidgetItem):
def _mixSendDialContextMenuEvent(self, event):
- if self._cachedInstrumentStatus["mixerEnabled"]:
+ if self.cachedInstrumentStatus["mixerEnabled"]:
mixerMuteText = QtCore.QCoreApplication.translate("InstrumentMixerLevelContextMenu", "Mute/Disable Mixer-Send for {}".format(self.instrumentDict["name"]))
mixerMuteFunc = lambda: api.setInstrumentMixerEnabled(self.idKey, False)
else:
diff --git a/qtgui/mainwindow.py b/qtgui/mainwindow.py
index 58c16ec..9f919ac 100644
--- a/qtgui/mainwindow.py
+++ b/qtgui/mainwindow.py
@@ -23,6 +23,7 @@ import logging; logging.info("import {}".format(__file__))
#Standard Library Modules
import pathlib
import os
+from collections import Counter
#Third Party Modules
from PyQt5 import QtWidgets, QtCore, QtGui
@@ -37,6 +38,7 @@ import engine.api as api
from engine.config import * #imports METADATA
from .instrument import InstrumentTreeController
+from .favorites import FavoritesTreeController
from .auditioner import AuditionerMidiInputComboController
from .selectedinstrumentcontroller import SelectedInstrumentController
from .verticalpiano import VerticalPiano
@@ -68,18 +70,27 @@ class MainWindow(TemplateMainWindow):
super().__init__()
+ #To prevent double counting of instruments loads we remember what we already loaded this time. The permanent database is in the qt settings.
+ self.favoriteInstrumentsThisRun = set() #idKey Tuple
+
#Make the description field at least a bit visible
self.ui.details_groupBox.setMinimumSize(1, 50)
self.ui.splitter.setSizes([1,1]) #just a forced update
self.auditionerMidiInputComboController = AuditionerMidiInputComboController(parentMainWindow=self)
- self.instrumentTreeController = InstrumentTreeController(parentMainWindow=self)
+ self.instrumentTreeController = InstrumentTreeController(parentMainWindow=self, treeWidget=self.ui.instruments_treeWidget)
+ self.favoritesTreeController = FavoritesTreeController(parentMainWindow=self, treeWidget=self.ui.favorites_treeWidget)
self.selectedInstrumentController = SelectedInstrumentController(parentMainWindow=self)
- self.tabWidget = self.ui.iinstruments_tabWidget #with this ugly name it is only a matter of time that it gets changed
+ self.tabWidget = self.ui.iinstruments_tabWidget #with this ugly name it is only a matter of time that it gets changed. Better save in a permanent var.
self.tabWidget.setTabBarAutoHide(True)
- self.tabWidget.setTabVisible(1, False) #Hide Mixer until we decide if we need it. #TODO
-
+ #self.tabWidget.setTabVisible(2, False) #Hide Favorites until we decide if we need it.
+ #The tab widget / treewidget combination triggers a bug in Qt. The treewidget will actively switch to the wrongly selected item.
+ #We added a signal on tabChanged to set it back as a hack/workaround. Remember the real item here:
+ self.rememberCurrentItem = None #idKey
+ self.duringTabChange = False #our own "block signal"
+ self.tabWidget.tabBarClicked.connect(self.signalBlockTabAboutToChange)
+ self.tabWidget.currentChanged.connect(self.bugWorkaroundRestoreCurrentItem)
#Set up the two pianos
self.verticalPiano = VerticalPiano(self)
@@ -180,7 +191,6 @@ class MainWindow(TemplateMainWindow):
self.menu.addMenuEntry("menuView", "actionPianoRollVisible", QtCore.QCoreApplication.translate("Menu", "Piano Roll"), self.pianoRollToggleVisibleAndRemember, shortcut="Ctrl+R", checkable=True, startChecked=True) #function receives check state as automatic parameter
self.menu.addMenuEntry("menuView", "actionPianoVisible", QtCore.QCoreApplication.translate("Menu", "Piano"), self.pianoToggleVisibleAndRemember, shortcut="Ctrl+P", checkable=True, startChecked=True) #function receives check state as automatic parameter
-
self.menu.orderSubmenus(["menuFile", "menuEdit", "menuView", "menuHelp"])
def react_rescanSampleDir(self):
@@ -227,18 +237,82 @@ class MainWindow(TemplateMainWindow):
settings.setValue("pianoVisible", state)
- def selectedInstrumentChanged(self, instrumentStatus, instrumentData):
- """We receive this from selectedinstrumentcontroller.py, when the user clicks on a GUI
- entry for a different instrument. This is purely a GUI function. This functions
- relays the change to other widgets, except the above mentioned controller.
+ def currentTreeItemChanged(self, currentTreeItem:QtWidgets.QTreeWidgetItem, previousTreeItem:QtWidgets.QTreeWidgetItem):
+ """Somewhere in the program the user-selected tree item changed.
+ It can be an instrument or a library. Send this change to all widgets
+ that deal with instruments and libraries, which are most of them.
- If a library is clicked, and not an instrument, both parameters will be None.
- The pianos use this to switch off.
+ The widgets handle the type of item themselves. We attached all engine data
+ to the items themselves. The items are not universal for the whole program but children
+ of one specific widget. The receivers of this callback need to extract their information
+ from the treeItem.
+
+ Technically this is the signal QTreeWidget.currentTreeItemChanged so we receive their
+ parameters.
"""
- self.verticalPiano.pianoScene.selectedInstrumentChanged(instrumentStatus, instrumentData)
- self.horizontalPiano.pianoScene.selectedInstrumentChanged(instrumentStatus, instrumentData)
+ if self.duringTabChange:
+ currentTreeItem = self.rememberCurrentItem #There is a bug in tabChange signal. The TreeWidget will send an old item
+
+ self.rememberCurrentItem = currentTreeItem
+
+ if not currentTreeItem:
+ return
+
+ widgets = (
+ self.verticalPiano,
+ self.horizontalPiano,
+ self.selectedInstrumentController,
+ self.instrumentTreeController,
+ self.favoritesTreeController,
+ )
+
+ for widget in widgets:
+ widget.currentTreeItemChanged(currentTreeItem)
+
+ def signalBlockTabAboutToChange(self, tabIndex:int):
+ """Click on a tab.
+ Finalized through bugWorkaroundRestoreCurrentItem"""
+ self.duringTabChange = True
+
+ def bugWorkaroundRestoreCurrentItem(self):
+ """The tab already has changed.
+ Preceded by signalBlockTabAboutToChange"""
+ self.duringTabChange = False
def zoom(self, scaleFactor:float):
pass
def stretchXCoordinates(self, factor):
pass
+
+ def favoriteInstrument(self, idKey:tuple):
+ """Count the user-instructed, explicit loading of an instrument in the permanent database.
+ We work directly with the qt settings without local data. A crash or sigkill will not
+ prevent the counting."""
+ settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"])
+ if settings.contains("favoriteInstruments"):
+ database = settings.value("favoriteInstruments", type=dict)
+ else:
+ database = {}
+
+ if not idKey in self.favoriteInstrumentsThisRun:
+ if not idKey in database:
+ database[idKey] = 0
+ database[idKey] += 1
+ self.favoriteInstrumentsThisRun.add(idKey)
+
+ settings.setValue("favoriteInstruments", database)
+
+ def resetFavoriteInstrumentDatabase(self):
+ settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"])
+ if settings.contains("favoriteInstruments"):
+ settings.remove("favoriteInstruments")
+ self.favoriteInstrumentsThisRun = set()
+
+ def getTop10FavoriteInstruments(self)->list:
+ """Return an ordered list of double tuple ((libid, instid), counter) """
+ settings = QtCore.QSettings("LaborejoSoftwareSuite", METADATA["shortName"])
+ if settings.contains("favoriteInstruments"):
+ database = settings.value("favoriteInstruments", type=dict)
+ return Counter(database).most_common(10) #python already knows how to create that
+ else:
+ return []
diff --git a/qtgui/selectedinstrumentcontroller.py b/qtgui/selectedinstrumentcontroller.py
index b20ad9f..1fe32f7 100644
--- a/qtgui/selectedinstrumentcontroller.py
+++ b/qtgui/selectedinstrumentcontroller.py
@@ -27,6 +27,7 @@ import logging; logger = logging.getLogger(__name__); logger.info("import")
from PyQt5 import QtCore, QtGui, QtWidgets
#Our Qt
+from .instrument import GuiInstrument, GuiLibrary #for the types
#Engine
import engine.api as api
@@ -60,9 +61,9 @@ class SelectedInstrumentController(object):
api.callbacks.instrumentListMetadata.append(self.react_initialInstrumentList)
api.callbacks.instrumentStatusChanged.append(self.react_instrumentStatusChanged)
- def directLibrary(self, idkey:tuple):
+ def directLibrary(self, idKey:tuple):
"""User clicked on a library treeItem"""
- libraryId, instrumentId = idkey
+ libraryId, instrumentId = idKey
self.currentIdKey = None
self.ui.details_scrollArea.show()
self.ui.variants_comboBox.hide()
@@ -77,10 +78,6 @@ class SelectedInstrumentController(object):
self.ui.info_label.setText(self._metadataToDescriptionLabel(metadata))
- #Inform other widgets (but not recursively ourselves) that we clicked on no instrument.
- #e.g. to switch off the gui keyboards.
- self.parentMainWindow.selectedInstrumentChanged(None, None)
-
def _metadataToDescriptionLabel(self, metadata:dict)->str:
"""Can work with instruments and libraries alike"""
@@ -132,7 +129,26 @@ class SelectedInstrumentController(object):
self.ui.keySwitch_comboBox.setCurrentIndex(curIdx)
- def instrumentChanged(self, idkey:tuple):
+ def currentTreeItemChanged(self, currentTreeItem:QtWidgets.QTreeWidgetItem):
+ """
+ Program wide GUI-only callback from
+ widget.currentItemChanged->mainWindow.currentTreeItemChanged. We set the currentItem
+ ourselves, so we need to block our signals to avoid recursion.
+
+ Only one item can be selected at a time.
+
+ The currentTreeItem we receive is not a global instance but from a widget different to ours.
+ We need to find our local version of the same instrument/library/idKey first.
+ """
+ isLibrary = type(currentTreeItem) is GuiLibrary
+ idKey = currentTreeItem.idKey
+
+ if isLibrary:
+ self.directLibrary(idKey)
+ else:
+ self.instrumentChanged(idKey)
+
+ def instrumentChanged(self, idKey:tuple):
"""This is a GUI-internal function. The user selected a different instrument from
the list. Single click, arrow keys etc.
@@ -142,8 +158,8 @@ class SelectedInstrumentController(object):
We also relay this information to the main window which can send it to other widgets,
like the two keyboards. We will not receive this callback from the mainwindow.
"""
- libraryId, instrumentId = idkey
- self.currentIdKey = idkey
+ libraryId, instrumentId = idKey
+ self.currentIdKey = idKey
self.ui.details_scrollArea.show()
@@ -168,9 +184,6 @@ class SelectedInstrumentController(object):
#Dynamic
self.react_instrumentStatusChanged(self.statusUpdates[libraryId][instrumentId])
- #Relay to Main Window. We will not receive this ourselves.
- self.parentMainWindow.selectedInstrumentChanged(instrumentStatus, instrumentData)
-
def react_instrumentStatusChanged(self, instrumentStatus:dict):
"""Callback from the api. Has nothing to do with any GUI state or selection.
@@ -194,14 +207,14 @@ class SelectedInstrumentController(object):
This callback is called again by the GUI directly when switching the instrument with a
mouseclick in instrumentChanged and the GUI will use the cached data.
"""
- idkey = instrumentStatus["idKey"]
- libraryId, instrumentId = idkey
+ idKey = instrumentStatus["idKey"]
+ libraryId, instrumentId = idKey
if not libraryId in self.statusUpdates:
self.statusUpdates[libraryId] = {} #empty library. status dict
self.statusUpdates[libraryId][instrumentId] = instrumentStatus #create or overwrite / keep up to date
- if not self.currentIdKey == idkey:
+ if not self.currentIdKey == idKey:
#Callback for an instrument currently not selected
return
diff --git a/qtgui/verticalpiano.py b/qtgui/verticalpiano.py
index 29aa6ac..64f3f5a 100644
--- a/qtgui/verticalpiano.py
+++ b/qtgui/verticalpiano.py
@@ -30,6 +30,7 @@ from template.engine.duration import baseDurationToTraditionalNumber
#User modules
import engine.api as api
+from .instrument import GuiInstrument, GuiLibrary #for the types
WIDTH = 200
@@ -59,7 +60,25 @@ class VerticalPiano(QtWidgets.QGraphicsView):
self.centerOn(0, 64*STAFFLINEGAP)
+ def currentTreeItemChanged(self, currentTreeItem:QtWidgets.QTreeWidgetItem):
+ """
+ Program wide GUI-only callback from
+ widget.currentItemChanged->mainWindow.currentTreeItemChanged. We set the currentItem
+ ourselves, so we need to block our signals to avoid recursion.
+
+ Only one item can be selected at a time.
+
+ The currentTreeItem we receive is not a global instance but from a widget different to ours.
+ We need to find our local version of the same instrument/library/idKey first.
+ """
+
+ isLibrary = type(currentTreeItem) is GuiLibrary
+ idKey = currentTreeItem.idKey
+ if isLibrary:
+ self.pianoScene.selectedInstrumentChanged(None)
+ else:
+ self.pianoScene.selectedInstrumentChanged(currentTreeItem.cachedInstrumentStatus)
class _VerticalPianoScene(QtWidgets.QGraphicsScene):
"""Most of this is copy paste from piano grid"""
@@ -81,7 +100,7 @@ class _VerticalPianoScene(QtWidgets.QGraphicsScene):
self.numberLabels = [] #index is pitch
- self._selectedInstrument = None #tuple instrumentStatus, instrumentData
+ self._selectedInstrument = None #instrumentStatus dict
self._leftMouseDown = False #For note preview
self.gridPen = QtGui.QPen(QtCore.Qt.SolidLine)
@@ -177,7 +196,7 @@ class _VerticalPianoScene(QtWidgets.QGraphicsScene):
"""GUI callback. Data is live"""
#Is this for us?
- if instrumentStatus and self._selectedInstrument and not instrumentStatus["idKey"] == self._selectedInstrument[0]["idKey"]:
+ if instrumentStatus and self._selectedInstrument and not instrumentStatus["idKey"] == self._selectedInstrument["idKey"]:
return
#else:
# print ("not for us", instrumentStatus["idKey"])
@@ -205,7 +224,7 @@ class _VerticalPianoScene(QtWidgets.QGraphicsScene):
#keyObject.hide()
keyObject.setPlayable(keyPitch in instrumentStatus["playableKeys"])
- def selectedInstrumentChanged(self, instrumentStatus, instrumentData):
+ def selectedInstrumentChanged(self, instrumentStatus):
"""GUI click to different instrument. The arguments are cached GUI data
If a library is clicked, and not an instrument, both parameters will be None.
@@ -215,16 +234,16 @@ class _VerticalPianoScene(QtWidgets.QGraphicsScene):
self.clearVerticalPiano()
self.fakeDeactivationOverlay.show()
else:
- self._selectedInstrument = (instrumentStatus, instrumentData)
+ self._selectedInstrument = instrumentStatus
self.instrumentStatusChanged(instrumentStatus)
def highlightNoteOn(self, idKey:tuple, pitch:int, velocity:int):
- if self._selectedInstrument and self._selectedInstrument[0]["idKey"] == idKey:
+ if self._selectedInstrument and self._selectedInstrument["idKey"] == idKey:
highlight = self.highlights[pitch]
highlight.show()
def highlightNoteOff(self, idKey:tuple, pitch:int, velocity:int):
- if self._selectedInstrument and self._selectedInstrument[0]["idKey"] == idKey:
+ if self._selectedInstrument and self._selectedInstrument["idKey"] == idKey:
highlight = self.highlights[pitch]
highlight.hide()
@@ -245,7 +264,7 @@ class _VerticalPianoScene(QtWidgets.QGraphicsScene):
def _off(self):
if self._selectedInstrument and not self._lastPlayPitch is None:
- status, data = self._selectedInstrument
+ status = self._selectedInstrument
libId, instrId = status["idKey"]
api.sendNoteOffToInstrument(status["idKey"], self._lastPlayPitch)
self._lastPlayPitch = None
@@ -259,7 +278,7 @@ class _VerticalPianoScene(QtWidgets.QGraphicsScene):
if self._selectedInstrument and not pitch == self._lastPlayPitch:
#TODO: Play note on at a different instrument than note off? Possible?
- status, data = self._selectedInstrument
+ status = self._selectedInstrument
if not self._lastPlayPitch is None:
#Force a note off that is currently playing but not under the cursor anymore