From 77f8dbb2dc31cc9028493f206f23093500459830 Mon Sep 17 00:00:00 2001 From: Nils <> Date: Sun, 30 Jun 2019 11:59:36 +0200 Subject: [PATCH] Create sub pattern for rows with context menu --- engine/api.py | 17 +++++++++++++++-- engine/pattern.py | 8 ++++---- qtgui/mainwindow.py | 18 +++++++++++------- qtgui/pattern_grid.py | 36 +++++++++++++++++++++++++++++++----- 4 files changed, 61 insertions(+), 18 deletions(-) diff --git a/engine/api.py b/engine/api.py index 3d4a3a8..c0d819c 100644 --- a/engine/api.py +++ b/engine/api.py @@ -506,7 +506,7 @@ def removeStep(trackId, index, pitch): updatePlayback() callbacks._removeStep(track, index, pitch) -def setScale(trackId, scale): +def setScale(trackId, scale, callback = True): """Expects a scale list or tuple from lowest index to highest. Actual pitches don't matter.""" track = session.data.trackById(trackId) @@ -514,7 +514,8 @@ def setScale(trackId, scale): track.pattern.buildExportCache() track.buildTrack() updatePlayback() - callbacks._trackMetaDataChanged(track) + if callback: + callbacks._trackMetaDataChanged(track) def setSimpleNoteNames(trackId, simpleNoteNames): """note names is a list of strings with length 128. One name for each midi note. @@ -584,6 +585,18 @@ def patternRowRepeatFromStep(trackId, pitchindex, index): updatePlayback() callbacks._patternChanged(track) +def patternRowChangeVelocity(trackId, pitchindex, delta): + track = session.data.trackById(trackId) + for note in track.pattern.getRow(pitchindex): + new = note["velocity"] + delta + note["velocity"] = min(max(new,0), 127) + + track.pattern.buildExportCache() + track.buildTrack() + updatePlayback() + callbacks._patternChanged(track) + + major = [0, 2, 4, 5, 7, 9, 11, 12] #this if sorted by pitch, lowest to highest. Patroneo works in reverse order to accomodate the row/column approach of a grid. We reverse in setScaleToKeyword schemesDict = { #this if sorted by pitch, lowest to highest. Patroneo works in reverse order to accomodate the row/column approach of a grid. We reverse in setScaleToKeyword diff --git a/engine/pattern.py b/engine/pattern.py index 0aa14b1..ac30fe7 100644 --- a/engine/pattern.py +++ b/engine/pattern.py @@ -118,7 +118,7 @@ class Pattern(object): lst.append({"index":index, "factor": 1, "pitch": pitchindex, "velocity":vel}) self.data = lst - def _getRow(self, pitchindex): + def getRow(self, pitchindex): """Returns a row of steps, sorted by index/column. Includes the original mutable dictionaries, which can be changed """ @@ -132,13 +132,13 @@ class Pattern(object): def clearRow(self, pitchindex): """pure convenience. This could be done with repeatFromStep on the first empty step""" - existingSteps = self._getRow(pitchindex) + existingSteps = self.getRow(pitchindex) for step in existingSteps: self.data.remove(step) def _rowAsBooleans(self, pitchindex): """Existence or not""" - existingSteps = self._getRow(pitchindex) + existingSteps = self.getRow(pitchindex) existingIndices = set(s["index"] for s in existingSteps) result = [False] * self.parentTrack.parentData.howManyUnits @@ -168,7 +168,7 @@ class Pattern(object): def invertRow(self, pitchindex): vel = self.averageVelocity - existingSteps = self._getRow(pitchindex) + existingSteps = self.getRow(pitchindex) existingIndices = set(s["index"] for s in existingSteps) for step in existingSteps: diff --git a/qtgui/mainwindow.py b/qtgui/mainwindow.py index 45bfbfa..ff6a6ad 100644 --- a/qtgui/mainwindow.py +++ b/qtgui/mainwindow.py @@ -111,18 +111,21 @@ class MainWindow(TemplateMainWindow): self.ui.centralwidget.addAction(self.ui.actionToStart) #no action without connection to a widget. self.ui.actionToStart.triggered.connect(self.ui.toStartButton.click) - ##Song Editor - self.ui.songEditorView.parentMainWindow = self - self.songEditor = SongEditor(parentView=self.ui.songEditorView) + ##Song Editor + self.ui.songEditorView.parentMainWindow = self + self.songEditor = SongEditor(parentView=self.ui.songEditorView) self.ui.songEditorView.setScene(self.songEditor) - - self.ui.trackEditorView.parentMainWindow = self + #self.ui.songEditorView.setViewport(QtWidgets.QOpenGLWidget()) + + self.ui.trackEditorView.parentMainWindow = self self.trackLabelEditor = TrackLabelEditor(parentView=self.ui.trackEditorView) self.ui.trackEditorView.setScene(self.trackLabelEditor) + #self.ui.trackEditorView.setViewport(QtWidgets.QOpenGLWidget()) - self.ui.timelineView.parentMainWindow = self + self.ui.timelineView.parentMainWindow = self self.timeline = Timeline(parentView=self.ui.timelineView) self.ui.timelineView.setScene(self.timeline) + #self.ui.timelineView.setViewport(QtWidgets.QOpenGLWidget()) #Sync the vertical trackEditorView scrollbar (which is never shown) with the songEditorView scrollbar. self.ui.songEditorView.setVerticalScrollBar(self.ui.trackEditorView.verticalScrollBar()) #this seems backwards, but it is correct :) @@ -134,7 +137,8 @@ class MainWindow(TemplateMainWindow): self.ui.gridView.parentMainWindow = self self.patternGrid = PatternGrid(parentView=self.ui.gridView) self.ui.gridView.setScene(self.patternGrid) - + self.ui.gridView.setRenderHints(QtGui.QPainter.TextAntialiasing) + #self.ui.gridView.setViewport(QtWidgets.QOpenGLWidget()) #Toolbar, which needs the widgets above already established self._populateToolbar() diff --git a/qtgui/pattern_grid.py b/qtgui/pattern_grid.py index 077c6c5..fd75c1f 100644 --- a/qtgui/pattern_grid.py +++ b/qtgui/pattern_grid.py @@ -21,7 +21,7 @@ along with this program. If not, see . from time import time import engine.api as api #Session is already loaded and created, no duplication. from template.engine import pitch -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets, QtOpenGL SIZE_UNIT = 40 SIZE_TOP_OFFSET = 75 @@ -349,10 +349,32 @@ class PatternGrid(QtWidgets.QGraphicsScene): self._zoomFactor = max(0.1, round(self._zoomFactor - 0.25, 2)) self._zoom(event) event.accept() + elif QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.AltModifier: + potentialStep = self.itemAt(event.scenePos().x(), event.scenePos().y(), self.parentView.transform()) + if type(potentialStep) is Step: + trackId = self.parentView.parentMainWindow.currentTrackId + if event.delta() > 0: + delta = 2 + else: + delta = -2 + api.patternRowChangeVelocity(trackId, potentialStep.row, delta) + self.showVelocities() #removed in self.keyReleaseEvent + event.accept() + else: + event.ignore() + super().wheelEvent(event) else: event.ignore() super().wheelEvent(event) + def keyReleaseEvent(self, event): + """Complementary for wheelEvent with Alt to change row velocity. + It is hard to detect the Alt key. We just brute force because there are not many + keyPresses in Patroneo at all.""" + self.hideVelocities() + event.ignore() + super().keyReleaseEvent(event) + def _zoom(self, event): if 0.1 < self._zoomFactor < 5: self.parentView.resetTransform() @@ -623,10 +645,12 @@ class Scale(QtWidgets.QGraphicsRectItem): for pitchWidget in self.pitchWidgets: pitchWidget.spinBoxValueChanged() #change all current pitchWidgets - def sendToEngine(self): + def sendToEngine(self, callback=True): result = [widget.spinBox.value() for widget in self.pitchWidgets] #result.reverse() - api.setScale(trackId=self.parentScene.parentView.parentMainWindow.currentTrackId, scale=result) + trackId = self.parentScene.parentView.parentMainWindow.currentTrackId + if trackId: #startup check + api.setScale(trackId, scale=result, callback=callback) class TransposeControls(QtWidgets.QWidget): """Communication with the scale spinBoxes is done via api callbacks. We just fire and forget""" @@ -738,12 +762,13 @@ class PitchWidget(QtWidgets.QGraphicsProxyWidget): def __init__(self, parentItem): super().__init__() self.parentItem = parentItem - self.spinBox = QtWidgets.QSpinBox() + self.spinBox = QtWidgets.QSpinBox() #self.spinBox.setFrame(True) self.spinBox.setMinimum(0) self.spinBox.setMaximum(127) self.spinBox.stepBy = self.stepBy #self.spinBox.setValue(0) #No init value. This is changed on active track callback + widget = QtWidgets.QWidget() layout = QtWidgets.QHBoxLayout() @@ -792,7 +817,8 @@ class PitchWidget(QtWidgets.QGraphicsProxyWidget): exit() def spinBoxValueChanged(self): - self.label.setText(self.midiToNotename(self.spinBox.value())) + self.label.setText(self.midiToNotename(self.spinBox.value())) + self.parentItem.sendToEngine(callback=False) def spinBoxEditingFinished(self): if not self.rememberLastValue == self.spinBox.value():