diff --git a/engine/api.py b/engine/api.py index 305afa0..409dff1 100644 --- a/engine/api.py +++ b/engine/api.py @@ -58,7 +58,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks #self.playbackStart = [] #self.playbackStop = [] - self.playbackSpeedChanged = [] self._cachedTickIndex = -1 self.updateGraphTrackCC = [] @@ -68,9 +67,9 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks self.updateTempoTrack = [] self.updateTempoTrackBlocks = [] self.updateTempoTrackMeta = [] + self.tempoScalingChanged = [] - self.prevailingBaseDurationChanged = [] - self.metronomeChanged = [] + self.prevailingBaseDurationChanged = [] #self.recordingStreamNoteOn = [] #self.recordingStreamNoteOff = [] @@ -115,6 +114,8 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks changedTracks, currentItem = session.data.currentContentLinkAndItsBlocksInAllTracks() for track in changedTracks: self._updateTrack(id(track)) + if session.data.currentMetronomeTrack is track: + setMetronome(track.asMetronomeData, label=track.name) #template api def _updateTrack(self, trId): """The most important function. Create a static representation of the music data which @@ -178,7 +179,8 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks def _tracksChanged(self): """Track deleted, added or moved. Or toggled double/non-double. This callback is relatively cheap because it does not generate - any item data.""" + any item data.""" + #TODO: does NOT call template.api._numberOfTracksChanged session.data.updateJackMetadataSorting() if self.tracksChanged: @@ -226,6 +228,10 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks #TODO: but this also leads to centerOn cursor in the gui after every mouse click in the conductor. #self._setCursor(destroySelection = False) + def _tempoScalingChanged(self, newValue): + for func in self.tempoScalingChanged: + func(newValue) + def _playbackStart(self): for func in self.playbackStart: func() @@ -234,9 +240,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks for func in self.playbackStop: func() - def _playbackSpeedChanged(self, newValue): - for func in self.playbackSpeedChanged: - func(newValue) def _prevailingBaseDurationChanged(self, newPrevailingBaseDuration): for func in self.prevailingBaseDurationChanged: @@ -252,18 +255,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks for func in self.historyChanged: func(undoHistory, redoHistory) - def _metronomeChanged(self): - """returns a dictionary with meta data such as the mute-state and the track name""" - #Check if the actual track exists. This handles track delete of any kind. - #session.data.metronome.currentMetronomeTrack is not a calf box track but our Laborejo track that HOLDS the current cbox metronome track - return #TODO - if not session.data.metronome.currentMetronomeTrack or not (session.data.metronome.currentMetronomeTrack in session.data.tracks or session.data.metronome.currentMetronomeTrack in session.data.hiddenTracks): - session.data.metronome.currentMetronomeTrack = None - session.data.metronome.useTrack(session.data.currentTrack()) - - for func in self.metronomeChanged: - func(session.data.metronome.staticRepresentation()) - def _recordingStreamNoteOn(self, liveChord): """One dict at a time""" trId = id(session.data.currentTrack()) @@ -290,7 +281,7 @@ def startEngine(nsmClient): _templateStartEngine(nsmClient) #Send initial Data etc. - session.data.tempoMap.isTransportMaster = True #always true for Laborejo. + session.data.tempoMap.isTransportMaster = True #always true for Laborejo. callbacks._tracksChanged() # This creates the frontend/GUI tracks with access through track ids. From now on we can send the GUI a trId and it knows which track needs change. for trId in session.data.listOfTrackIds(): callbacks._updateTrack(trId) #create content: music items @@ -302,6 +293,8 @@ def startEngine(nsmClient): for track in session.data.hiddenTracks: #After loading a file some tracks could be hidden. We want midi for them as well. track.staticRepresentation() #that generates the calfbox data as a side effect. discard the other data. + useCurrentTrackAsMetronome() + callbacks._setCursor() global laborejoEngineStarted #makes for a convenient check. stepMidiInput uses it, which needs to know that the gui already started the api. @@ -414,21 +407,6 @@ def _updateCallbackAllTracks(): updateCallbackAllTracks = _updateCallbackAllTracks -def useCurrentTrackAsMetronome(): - track = session.data.currentTrack() - session.data.metronome.useTrack(track) - callbacks._metronomeChanged() #updates playback as well - -def enableMetronome(value): - session.data.metronome.enabled = value #has side effects - callbacks._metronomeChanged() #updates playback as well - -def isMetronomeEnabled(): - return session.data.metronome.enabled - -def toggleMetronome(): - enableMetronome(not session.data.metronome.enabled) #handles callback etc. - def _changeBlockAndItemOrder(dictWithTrackIDsAndDataLists): """A helper function for deleteSelection, paste and other... This makes it possible to undo/redo properly. It registers @@ -472,10 +450,10 @@ def copyObjects(): #ctrl+c #The score doesn't change at all. No callback. #no undo. -def pasteObjects(customBuffer = None, keepCursorState = False, overwriteSelection = True): #ctrl+v +def pasteObjects(customBuffer = None, updateCursor = True, overwriteSelection = True): #ctrl+v """api.duplicate overrides default paste behaviour by providing its own copyBuffer and not destroying the selection/keep the cursor at its origin position - """ + """ dataBefore = session.data.getBlockAndItemOrder() moveFunction = _createLambdaMoveToForCurrentPosition() listOfChangedTrackIDs = session.data.pasteObjects(customBuffer, overwriteSelection) @@ -488,31 +466,31 @@ def pasteObjects(customBuffer = None, keepCursorState = False, overwriteSelectio #Make changes visible in the GUI for trackId in listOfChangedTrackIDs: callbacks._updateTrack(trackId) - - if keepCursorState: - goTo() - callbacks._setCursor(destroySelection = False) - else: + + if updateCursor: callbacks._setCursor() def duplicate(): #ctrl+d - """Duplicate a single object and put it right of the original. Duplicate the entire selection - and put the copy right of the last selected note. - Basically a special case of copy and paste that does not touch the clipboard.""" + """Duplicate a single object and put it right of the original. The cursor moves with it + to enable follow up insertion. + + Duplicate the entire selection and put the copy right of the last selected note. + The cursor moves to selection start. deal with it. + + Basically a special case of copy and paste that does not touch the clipboard.""" if session.data.cursorWhenSelectionStarted: - #startPos = session.data.where() #we cannot use where() since we don't know if a content linked block before our current position gets new items in the duplication process - #TODO: when duplicating with content links or at least inside a content link the selection gets lost completely. Maybe this cannot be avoided, but review that in the future. - customBuffer = session.data.copyObjects(writeInSessionBuffer = False) - if customBuffer: - session.data.goToSelectionStart() - pasteObjects(customBuffer = customBuffer, keepCursorState = True, overwriteSelection = False) - else: + if customBuffer: #success + session.data.goToSelectionStart() + pos = session.data.where() #where even keeps the local position if a content linked block inserts items before our position + pasteObjects(customBuffer = customBuffer, updateCursor = False, overwriteSelection = False) + session.data.goTo(*pos) + callbacks._setCursor(destroySelection=False) + else: item = session.data.currentItem() if item: - insertItem(item.copy()) - - + insertItem(item.copy()) + callbacks._setCursor() #Score def transposeScore(rootPitch, targetPitch): """Based on automatic transpose. The interval is caculated from two pitches. @@ -521,6 +499,11 @@ def transposeScore(rootPitch, targetPitch): session.history.register(lambda r=rootPitch,t=targetPitch: transposeScore(t,r), descriptionString="transpose score") _updateCallbackAllTracks() +def useCurrentTrackAsMetronome(): + """This is called once after loading/creating a session in startEngine""" + session.data.currentMetronomeTrack = session.data.currentTrack() + setMetronome(session.data.currentMetronomeTrack.asMetronomeData, label=session.data.currentMetronomeTrack.name) #template api. has callbacks + #Tracks def insertTrack(atIndex, trackObject): moveFunction = _createLambdaMoveToForCurrentPosition() @@ -540,6 +523,7 @@ def newEmptyTrack(): newTrack = Track(session.data) insertTrack(newIndex, newTrack) #handles callbacks and undo return (id(newTrack)) + def deleteTrack(trId): """Can not delete hidden tracks because these don't implement undo. @@ -2135,6 +2119,15 @@ def insertTempoChangeDuringDuration(percentageUnitsPerMinuteAsFloat): session.history.clear() #TODO: This registers two times undo +def currentTempoScalingFactor(): + return session.data.tempoTrack.factor + +def changeTempoScaling(factor:float): + """The factor is always a factor from x1, not from the previous + value""" + session.data.tempoTrack.setFactor(float(factor)) + callbacks._tempoScalingChanged(session.data.tempoTrack.factor) + #Toolbox #High Level commands diff --git a/engine/block.py b/engine/block.py index 62a63d6..d2e5033 100644 --- a/engine/block.py +++ b/engine/block.py @@ -151,8 +151,9 @@ class Block(object): for item in self.data: copyItem = item.copy() new.data.append(copyItem) - - copyItem.parentBlocks.add(new) #parentBlock was empty until now + + assert not copyItem.parentBlocks #parentBlock was empty until now + copyItem.parentBlocks.add(new) if self in copyItem.parentBlocks: #TODO: investigate copyItem.parentBlocks.remove(self) @@ -301,7 +302,7 @@ class Block(object): def insert(self, item): self.data.insert(self.localCursorIndex, item) #we do not need to check if appending or not. list.insert appends if the index is higher then len() #self.localCursorIndex += 1 #we don't need to go right here because track.insert() is calling its own right() directly after insert, which triggers block.right() - item.parentBlocks.add(self) + item.parentBlocks.add(self) def delete(self): """The commented out is the immediate garbage collector which diff --git a/engine/config.py b/engine/config.py index ac9eba6..6715288 100644 --- a/engine/config.py +++ b/engine/config.py @@ -36,7 +36,7 @@ METADATA={ #How many audio outputs do you want? must be pairs. These are just unconnected jack outputs #that need to be connected internally to instrument outputs like fluidsynth - "cboxOutputs" : 2 * 0, + "cboxOutputs" : 2 * 1, #metronome #Various strings for the README #Extra whitespace will be stripped so we don't need to worry about docstring indentation diff --git a/engine/graphtracks.py b/engine/graphtracks.py index e906e42..71676b5 100644 --- a/engine/graphtracks.py +++ b/engine/graphtracks.py @@ -1313,6 +1313,13 @@ class TempoTrack(GraphTrackCC): assert self.blocks + @property + def factor(self): + return self.parentData.tempoMap.factor + + def setFactor(self, factor:float): + self.parentData.tempoMap.setFactor(factor) + def lilypond(self): """Based on the static export""" def _ly(tempoItem, nextTempoItem): diff --git a/engine/items.py b/engine/items.py index c5601c1..3246a55 100644 --- a/engine/items.py +++ b/engine/items.py @@ -744,8 +744,13 @@ class Item(object): @property - def parentTracks(self): - return [block.parentTrack for block in self.parentBlocks] + def parentTracks(self): + #return (block.parentTrack for block in self.parentBlocks[0]) + return (block.parentTrack for block in list(self.parentBlocks)[0].linkedContentBlocksInScore()) + + @property + def parentTrackIds(self): + return (id(tr) for tr in self.parentTracks) def _copy(self): """return an independent copy of self""" @@ -757,8 +762,8 @@ class Item(object): return new def copyParentBlocks(self, oldItem): - """Move the oldItems parentBlocks to the new item""" - self.parentBlocks = oldItem.parentBlocks + """Move the oldItems parentBlocks to the new item""" + #self.parentBlocks = oldItem.parentBlocks #TODO. We took that out when pasting after deleting a track and recreating failed. Why do we to copy the parentBlocks when a parentBlock is added during block.insert anyway? Wild guess: we don't. def logicalDuration(self): return 0 diff --git a/engine/main.py b/engine/main.py index ffe0e0d..6042b14 100644 --- a/engine/main.py +++ b/engine/main.py @@ -23,12 +23,12 @@ along with this program. If not, see . import logging; logging.info("import {}".format(__file__)) #Standar Library +import sys #3rd Party #Template import template.engine.sequencer -from template.engine.metronome import Metronome #Our modules from .block import Block @@ -56,11 +56,12 @@ class Data(template.engine.sequencer.Score): self.cursorWhenSelectionStarted = None #A cursor dict, from self.cursorExport(). Is None when there is no selection. Can be used for "if selection:" questions. Gets changed quite often. self.copyObjectsBuffer = [] #for copy and paste. obviously empty after load file. Also not saved. self.cachedTrackDurations = {} #updated after every track export - self.metronome = Metronome(parentData=self) #Purely dynamic structure. No save/load. No undo/redo + self.currentMetronomeTrack = None #A Laborejo Track, indepedent of currentTrack. The metronome is in self.metronome, set by the template Score. def duration(self): """Return the duration of the whole score, in ticks""" - #TODO: use cached duration? How often is this used? + #TODO: use cached duration? How often is this used? Pretty often. 3 Times for a single track note update. + #TODO: Measure before trying to improve performance. result = [] for track in self.tracks: result.append(track.duration()) @@ -506,7 +507,7 @@ class Data(template.engine.sequencer.Score): result = [] if validSelection: for track in selectedTracksAndItems: - result.append([item.copy() for item, cachedTrackState in track]) + result.append([item.copy() for item, cachedTrackState in track]) if writeInSessionBuffer: self.copyObjectsBuffer = result return result @@ -580,10 +581,11 @@ class Data(template.engine.sequencer.Score): if self.cursorWhenSelectionStarted and overwriteSelection: listOfChangedTrackIdsFromDelete = self.deleteSelection() + startPosition = self.where() + startItem = self.currentItem() #TODO: check if the cursor is still in the correct position after delete selection listOfChangedTrackIds = set() - for number, track in enumerate(workBuffer): curTrack = self.currentTrack() #Here comes an additional condition. Most of the time pastes are single-track. If so, or we simply are in the starting track, we want to paste at the actual position, not the tickindex. This circumvents problems with block boundaries and zero-duration items. @@ -597,8 +599,8 @@ class Data(template.engine.sequencer.Score): for item in track: newItem = item.copy() curTrack.insert(newItem) #eventhough copyObjectsBuffer is already a copy of the original notes we don't want every paste to be the same instances. - for parentBlock in newItem.parentBlocks: #TODO: just be naive and add them all for now. Performance can be improved later. - listOfChangedTrackIds.add(id(parentBlock.parentTrack)) + #newItem has now a parentBlock and a parentTrack. We add this parent track to the list of changed Tracks + listOfChangedTrackIds.update(newItem.parentTrackIds) #TODO: profiling. All items in a block have the same parentTrack. This is highly redundant BUT can be different for items which originated from block boundaries if not number+1 == len(workBuffer): #enumerate from 0, len from 1. #we have to prevent the track going down one too far in the last step and messing with the tick index though. self.trackDown() @@ -609,10 +611,13 @@ class Data(template.engine.sequencer.Score): self.trackUp() assert self.trackIndex == trackIndexStart == startPosition[0] assert self.currentTrack().state.tickindex == finalTickIndex #this is not enough to pinpoint the real location. - self.goTo(trackIndex=startPosition[0], blockindex=startPosition[1], localCursorIndexInBlock=0) + + #Return to the item where pasting starting. Pasting counts as insert, and insert sticks to the item right of cursor. + #Therefore we go to the starting track and block, but not to the starting localCursorIndexInBlock. Instead we search for the specific item in a second step + self.goTo(trackIndex=startPosition[0], blockindex=startPosition[1], localCursorIndexInBlock=0) self.goToItemInCurrentBlock(startItem) #we actually want to be at the same item where we started, not just the tick index which gets confused with block boundaries and zero duration items - try: #overwrite? + try: #overwrite? #TODO: What is that? assert with AssertionError pass? assert listOfChangedTrackIds == listOfChangedTrackIdsFromDelete except AssertionError: pass @@ -811,12 +816,14 @@ class Data(template.engine.sequencer.Score): raise ValueError("graphItem with this id not in any track") def goTo(self, trackIndex, blockindex, localCursorIndexInBlock): + """Handles even shifting positions in a content link situation""" self.trackIndex = trackIndex self.currentTrack().toBlockAndLocalCursorIndex(blockindex, localCursorIndexInBlock) assert self.where() == (trackIndex, blockindex, localCursorIndexInBlock) def where(self): - """return the data needed by self.goto""" + """return the data needed by self.goto + where even keeps the local position if a content linked block inserts items before our position """ return self.trackIndex, self.currentTrack().state.blockindex, self.currentTrack().currentBlock().localCursorIndex def goToItemInCurrentBlock(self, itemInstance): diff --git a/engine/resources/metronome.sfz b/engine/resources/metronome.sfz new file mode 100644 index 0000000..4a854a6 --- /dev/null +++ b/engine/resources/metronome.sfz @@ -0,0 +1,2 @@ + key=77 sample=samples/normal.wav count=1 //normal metronome tick + key=76 sample=samples/stressed.wav count=1 //stressed metronome tick diff --git a/engine/resources/normal.wav b/engine/resources/samples/normal.wav similarity index 100% rename from engine/resources/normal.wav rename to engine/resources/samples/normal.wav diff --git a/engine/resources/stressed.wav b/engine/resources/samples/stressed.wav similarity index 100% rename from engine/resources/stressed.wav rename to engine/resources/samples/stressed.wav diff --git a/engine/track.py b/engine/track.py index 7bc62e6..cbb0e40 100644 --- a/engine/track.py +++ b/engine/track.py @@ -369,8 +369,8 @@ class TrackState(object): class Track(object): allTracks = WeakValueDictionary() #key is the trackId, value is the weak reference to the Track. Deleted tracks (from the current session) and hidden tracks are in here as well. - def __repr__(self) -> str: - return f"Laborejo Track: {self.sequencerInterface.name}" + #def __repr__(self) -> str: + # return f"Laborejo Track: {self.sequencerInterface.name}" def __init__(self, parentData, name=None): self.parentData = parentData @@ -395,6 +395,8 @@ class Track(object): #The instrument names are also handled by the trackState so that the cursor knows about instrument changes self.initialInstrumentName = "" #different than the track name. e.g. "Violin" self.initialShortInstrumentName = "" # e.g. "vl" + + self.asMetronomeData = None #This track as metronome version. Is always up to date through export. self._processAfterInit() @@ -1060,10 +1062,10 @@ class Track(object): ######### #Metronome start - #The metronome cannot be calculated by simply looking at metricalInstructions. We need to look at the barlines. Two instructions in a row at the same tick are wrong, but technically possible. These are not two full measures of metronome - #TODO: metronome activate - #if self is self.parentData.currentMetronomeTrack: - # self.parentData.metronome.generate(((pos, m.isMetrical, m.treeOfInstructions) for pos, m in barlines.items())) + #The metronome cannot be calculated by simply looking at metricalInstructions. + #We need to look at the barlines. Two instructions in a row at the same tick are wrong, + #but technically possible. These are not two full measures of metronome + self.asMetronomeData = tuple((pos, m.isMetrical, m.treeOfInstructions) for pos, m in barlines.items()) #Metronome end. Nothing below is connected with the metronome subtrack. #Calculate the beam positions for the static groups. @@ -1152,10 +1154,5 @@ class Track(object): self.toPosition(originalPosition, strict = False) #has head() in it return result - - - - - #Dependency Injections. template.engine.sequencer.Score.TrackClass = Track #Score will look for Track in its module. diff --git a/qtgui/menu.py b/qtgui/menu.py index c380408..ba26262 100644 --- a/qtgui/menu.py +++ b/qtgui/menu.py @@ -539,10 +539,11 @@ class ToolBarMetronome(QtWidgets.QCheckBox): return False def updateFromCallback(self, exportDict): + """Gets the metronome dict""" self.blockSignals(True) self.setStatus() assert self.isChecked() == exportDict["enabled"], (self.isChecked(), exportDict["enabled"]) - self.setText("Metronome: " + exportDict["trackName"]) + self.setText("Metronome: " + exportDict["label"]) self.blockSignals(False) def changed(self, value): @@ -554,10 +555,10 @@ class ToolBarPlaybackSpeed(QtWidgets.QDoubleSpinBox): super().__init__() self.setRange(0.1,3.0) self.setDecimals(1) - self.setValue(1.0) + self.setValue(api.currentTempoScalingFactor()) self.setSingleStep(0.1) self.setPrefix("Tempo ×") #yes, this is the unicode multiplication sign × and not an x - api.callbacks.playbackSpeedChanged.append(self.updateFromCallback) + api.callbacks.tempoScalingChanged.append(self.updateFromCallback) self.valueChanged.connect(self.changed) def updateFromCallback(self, newValue): @@ -566,5 +567,4 @@ class ToolBarPlaybackSpeed(QtWidgets.QDoubleSpinBox): self.blockSignals(False) def changed(self): - api.playbackChangeSpeed(self.value()) - constantsAndConfigs.ccViewValue = self.value() #this sends a call back to all CC spinboxes in the whole program + api.changeTempoScaling(self.value()) diff --git a/qtgui/scoreview.py b/qtgui/scoreview.py index 1cc58e3..b2972c7 100644 --- a/qtgui/scoreview.py +++ b/qtgui/scoreview.py @@ -23,7 +23,7 @@ along with this program. If not, see . #Third Party -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets, QtOpenGL #from PyQt5 import QtOpenGL #Template Modules @@ -39,22 +39,21 @@ class ScoreView(QtWidgets.QGraphicsView): def __init__(self, mainWindow): super().__init__() self.mainWindow = mainWindow - - - #TODO: Measure OpenGL performance impact. Make sure fallback works - """ - viewport = QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers)) - viewport.format().setSwapInterval(0) #disable VSync. - viewport.setAutoFillBackground(False) - - viewport = QtWidgets.QOpenGLWidget() - viewportFormat = QtGui.QSurfaceFormat() - viewportFormat.setSwapInterval(0) #disable VSync - viewportFormat.setSamples(2**8) - viewportFormat.setDefaultFormat(viewportFormat) - viewport.setFormat(viewportFormat) - self.setViewport(viewport) - """ + + #OpenGL has a huge positive impact on performance + if True: #for testing + viewport = QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers)) + viewport.format().setSwapInterval(0) #disable VSync. + viewport.setAutoFillBackground(False) + + viewport = QtWidgets.QOpenGLWidget() + viewportFormat = QtGui.QSurfaceFormat() + viewportFormat.setSwapInterval(0) #disable VSync + #viewportFormat.setSamples(2**8) #By default, the highest number of samples available is used. + viewportFormat.setDefaultFormat(viewportFormat) + viewport.setFormat(viewportFormat) + self.setViewport(viewport) + self.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) #self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag) diff --git a/qtgui/structures.py b/qtgui/structures.py index 269f6b0..9a47bd7 100644 --- a/qtgui/structures.py +++ b/qtgui/structures.py @@ -22,7 +22,7 @@ along with this program. If not, see . from math import log from PyQt5 import QtCore, QtGui, QtWidgets -from .items import staticItem2Item, GuiTieCurveGraphicsItem, GuiLiveNote +from .items import staticItem2Item, GuiTieCurveGraphicsItem from .constantsAndConfigs import constantsAndConfigs from .cursor import Cursor, Playhead, Selection from .conductor import Conductor, ConductorTransparentBlock @@ -517,16 +517,6 @@ class GuiTrack(QtWidgets.QGraphicsItem): backgroundColor.setOpacity(1) for tbh in self.transparentBlockHandles: tbh.blockMode() - def recordingStreamNoteOn(self, liveNoteData): - guiLiveNote = GuiLiveNote(parent=self, liveNoteData=liveNoteData) - guiLiveNote.setX(liveNoteData["tickindex"] / constantsAndConfigs.ticksToPixelRatio) - self.parentScore.liveMidiWaitingForNoteOff[liveNoteData["midipitch"]] = guiLiveNote - - def recordingStreamNoteOff(self, liveNoteData): - guiLiveNote = self.parentScore.liveMidiWaitingForNoteOff[liveNoteData["midipitch"]] - guiLiveNote.update(liveNoteData["duration"]) - del self.parentScore.liveMidiWaitingForNoteOff[liveNoteData["midipitch"]] - class GuiScore(QtWidgets.QGraphicsScene): def __init__(self, parentView): @@ -541,8 +531,6 @@ class GuiScore(QtWidgets.QGraphicsScene): self._deleteOnIdleLoop.start(0) #0 means "if there is time" self._deleteOnIdleLoop.timeout.connect(self._deleteOnIdle) #processes deleteOnIdleStack - self.liveMidiWaitingForNoteOff = {} #all unfinished midi notes (key is currently held down). We use one for all tracks because we have a callback in the cursor update function that iterates over all live notes. We don't want that to iterate over all tracks just to find the current live notes. - self.duringTrackDragAndDrop = None #switched to a QGraphicsItem (e.g. GuiTrack) while a track is moved around by the mouse self.duringBlockDragAndDrop = None #switched to a QGraphicsItem (e.g. GuiTrack) while a block is moved around by the mouse @@ -584,11 +572,6 @@ class GuiScore(QtWidgets.QGraphicsScene): api.callbacks.updateGraphBlockTrack.append(self.updateGraphBlockTrack) api.callbacks.graphCCTracksChanged.append(self.syncCCsToBackend) - #api.callbacks.recordingStreamNoteOn.append(self.recordingStreamNoteOn) - #api.callbacks.recordingStreamNoteOff.append(self.recordingStreamNoteOff) - #api.callbacks.setPlaybackTicks.append(self._drawLiveMidiRecording) - #api.callbacks.recordingStreamClear.append(self.recordingStreamClear) - def updateMode(self, nameAsString): assert nameAsString in constantsAndConfigs.availableEditModes @@ -665,25 +648,7 @@ class GuiScore(QtWidgets.QGraphicsScene): self.removeWhenIdle(self.tracks[trackId].ccPaths[cc]) del self.tracks[trackId].ccPaths[cc] - def recordingStreamNoteOn(self, trackId, liveChord): - if trackId in self.tracks: - self.tracks[trackId].recordingStreamNoteOn(liveChord) - #else: - #hidden track. - #self.parentView.updateMode() - #self.updateSceneRect() - - def recordingStreamNoteOff(self, trackId, liveChord): - if trackId in self.tracks: - self.tracks[trackId].recordingStreamNoteOff(liveChord) - #else: - #hidden track. - #self.parentView.updateMode() - #self.updateSceneRect() - - def recordingStreamClear(self): - removeInstancesFromScene(GuiLiveNote) - + def redraw(self, listOfStaticTrackRepresentations): """The order of guiTracks depends on the backend index. This way it is a no-brainer, we don't need to maintain our own @@ -762,10 +727,6 @@ class GuiScore(QtWidgets.QGraphicsScene): self.removeItem(deleteMe) #This is the only line in the program that should call scene.removeItem del deleteMe - def _drawLiveMidiRecording(self, tickPosition): - for midiPitch, guiLiveNote in self.liveMidiWaitingForNoteOff.items(): - guiLiveNote.update(tickPosition - guiLiveNote.liveNoteData["tickindex"]) - def trackAt(self, qScenePosition): """trackAt always returns the full GuiTrack, even if in ccEdit mode."""