diff --git a/engine/api.py b/engine/api.py index 1b1609b..c7d564d 100644 --- a/engine/api.py +++ b/engine/api.py @@ -208,6 +208,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks for func in self.patternLengthMultiplicatorChanged: func(export) self._patternChanged(track) #includes dataChanged + callbacks._dataChanged() def _swingChanged(self): export = session.data.swing @@ -216,6 +217,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks for func in self.swingPercentChanged: func(_swingToPercent_Table[export]) + callbacks._dataChanged() #Inject our derived Callbacks into the parent module template.engine.api.callbacks = ClientCallbacks() @@ -322,6 +324,17 @@ def seek(value): ##Score def set_quarterNotesPerMinute(value): + """Transport Master is set implicitly. If value == None Patroneo will switch into + JackTransport Slave mode""" + if session.data.tempoMap.isTransportMaster: + oldValue = session.data.tempoMap.getQuarterNotesPerMinute() + else: + oldValue = None + + if oldValue == value: return # no change + + session.history.register(lambda v=oldValue: set_quarterNotesPerMinute(v), descriptionString="Tempo") + if value is None: session.data.tempoMap.isTransportMaster = False #triggers rebuild elif value == "on": @@ -338,8 +351,10 @@ def set_quarterNotesPerMinute(value): def set_whatTypeOfUnit(ticks): - """Denominator of Time Signature""" - if session.data.whatTypeOfUnit == ticks: return + """Denominator of Time Signature = Each Group produces a Quarter """ + if session.data.whatTypeOfUnit == ticks: return #no change + + session.history.register(lambda v=session.data.whatTypeOfUnit: set_whatTypeOfUnit(v), descriptionString="Group Duration") session.data.whatTypeOfUnit = ticks session.data.buildAllTracks() if session.inLoopMode: @@ -348,8 +363,10 @@ def set_whatTypeOfUnit(ticks): callbacks._timeSignatureChanged() def set_howManyUnits(value): - """Numerator of Time Signature""" - if session.data.howManyUnits == value: return + """Numerator of Time Signature = Steps per Pattern""" + if session.data.howManyUnits == value: return #no change + + session.history.register(lambda v=session.data.howManyUnits: set_howManyUnits(v), descriptionString="Steps per Pattern") session.data.howManyUnits = value session.data.buildAllTracks() if session.inLoopMode: @@ -359,7 +376,9 @@ def set_howManyUnits(value): def set_subdivisions(value): - if session.data.subdivisions == value: return + """In groups of 1, 2, 4""" + if session.data.subdivisions == value: return #no change + session.history.register(lambda v=session.data.subdivisions: set_subdivisions(v), descriptionString="Group Size") session.data.subdivisions = value session.data.buildAllTracks() if session.inLoopMode: @@ -369,9 +388,10 @@ def set_subdivisions(value): def convert_subdivisions(value, errorHandling): """"errorHandling can be fail, delete or merge""" - if session.data.subdivisions == value: return + oldValue = session.data.subdivisions result = session.data.convertSubdivisions(value, errorHandling) - if result: + if result: #bool for success + session.history.register(lambda v=oldValue: convert_subdivisions(v, "delete"), descriptionString="Convert Grouping") #the error handling = delete should not matter at all. We are always in a position where this is possible because we just converted to the current state. session.data.buildAllTracks() updatePlayback() callbacks._timeSignatureChanged() #includes subdivisions @@ -392,6 +412,7 @@ def set_swing(value:float): logger.warning(f"Swing can only be between -0.5 and 0.5, not {value}") return + session.history.register(lambda v=session.data.swing: set_swing(v), descriptionString="Swing") session.data.swing = value session.data.buildAllTracks() updatePlayback() @@ -409,12 +430,14 @@ def setSwingPercent(value:int): if value < -100 or value > 100: logger.warning(f"Swing in percent can only be between -100 and +100, not {value}") return - set_swing(_percentToSwing_Table[value]) + set_swing(_percentToSwing_Table[value]) #handles undo and callbacks def set_numberOfMeasures(value): if session.data.numberOfMeasures == value: return + + session.history.register(lambda v=session.data.numberOfMeasures: set_numberOfMeasures(v), descriptionString="Measures per Track") session.data.numberOfMeasures = value session.data.buildSongDuration() updatePlayback() @@ -424,12 +447,14 @@ def set_numberOfMeasures(value): def set_measuresPerGroup(value): if session.data.measuresPerGroup == value: return + session.history.register(lambda v=session.data.measuresPerGroup: set_measuresPerGroup(v), descriptionString="Measures per Group") session.data.measuresPerGroup = value #No playback change callbacks._scoreChanged() def changeTrackName(trackId, name): track = session.data.trackById(trackId) + session.history.register(lambda trId=trackId, v=track.sequencerInterface.name: changeTrackName(trId,v), descriptionString="Track Name") track.sequencerInterface.name = " ".join(name.split()) callbacks._trackMetaDataChanged(track) @@ -437,6 +462,7 @@ def changeTrackColor(trackId, colorInHex): """Expects "#rrggbb""" track = session.data.trackById(trackId) assert len(colorInHex) == 7, colorInHex + session.history.register(lambda trId=trackId, v=track.color: changeTrackColor(trId,v), descriptionString="Track Color") track.color = colorInHex callbacks._trackMetaDataChanged(track) @@ -447,6 +473,7 @@ def changeTrackMidiChannel(trackId, newChannel:int): logger.warning(f"Midi Channel must be between 1-16 for this function, was: {newChannel}. Doing nothing.") return track = session.data.trackById(trackId) + session.history.register(lambda trId=trackId, v=track.midiChannel: changeTrackMidiChannel(trId,v), descriptionString="Track Midi Channel") track.midiChannel = newChannel-1 track.pattern.buildExportCache() track.buildTrack() @@ -456,7 +483,10 @@ def changeTrackMidiChannel(trackId, newChannel:int): def addTrack(scale=None): if scale: assert type(scale) == tuple - session.data.addTrack(scale=scale) + track = session.data.addTrack(scale=scale) + assert track + trackId = id(track) + session.history.register(lambda trId=trackId: deleteTrack(trId), descriptionString="Add Track") callbacks._numberOfTracksChanged() def createSiblingTrack(trackId): @@ -478,14 +508,30 @@ def createSiblingTrack(trackId): newTrackAgain = session.data.tracks.pop(newIndex) assert newTrackAgain is newTrack session.data.tracks.insert(oldIndex+1, newTrackAgain) + session.history.register(lambda trId=id(newTrackAgain): deleteTrack(trId), descriptionString="Clone Track") callbacks._numberOfTracksChanged() return newTrack.export() +def _reinsertDeletedTrack(track, trackIndex): + """For undo""" + track.sequencerInterface.recreateThroughUndo() + session.data.tracks.insert(trackIndex, track) + session.history.register(lambda trId=id(track): deleteTrack(trId), descriptionString="Add deleted Track again") + updatePlayback() + callbacks._numberOfTracksChanged() + def deleteTrack(trackId): track = session.data.trackById(trackId) - session.data.deleteTrack(track) + oldIndex = session.data.tracks.index(track) + deletedTrack = session.data.deleteTrack(track) + if not session.data.tracks: #always keep at least one track - session.data.addTrack() + with session.history.sequence("Delete Track and autocreated Track"): + addTrack() #has it's own undo + session.history.register(lambda tr=deletedTrack, pos=oldIndex: _reinsertDeletedTrack(tr, pos), descriptionString="Delete Track") + else: + session.history.register(lambda tr=deletedTrack, pos=oldIndex: _reinsertDeletedTrack(tr, pos), descriptionString="Delete Track") + updatePlayback() callbacks._numberOfTracksChanged() @@ -494,6 +540,7 @@ def moveTrack(trackId, newIndex): track = session.data.trackById(trackId) oldIndex = session.data.tracks.index(track) if not oldIndex == newIndex: + session.history.register(lambda tr=trackId, pos=oldIndex: moveTrack(trackId, pos), descriptionString="Move Track") session.data.tracks.pop(oldIndex) session.data.tracks.insert(newIndex, track) callbacks._numberOfTracksChanged() @@ -503,6 +550,7 @@ def setTrackPatternLengthMultiplicator(trackId, newMultiplicator:int): return #Invalid input track = session.data.trackById(trackId) + session.history.register(lambda tr=trackId, v=track.patternLengthMultiplicator: setTrackPatternLengthMultiplicator(trackId, v), descriptionString="Pattern Multiplier") track.patternLengthMultiplicator = newMultiplicator track.pattern.buildExportCache() track.buildTrack() @@ -513,22 +561,37 @@ def setTrackPatternLengthMultiplicator(trackId, newMultiplicator:int): callbacks._patternChanged(track) +#Track Switches +#Aka measures + +def _setTrackStructure(trackId, structure): + """For undo. Used by all functions as entry point, then calls itself for undo/redo. + structure is a set of integers which we can copy with .copy()""" + track = session.data.trackById(trackId) + session.history.register(lambda tr=trackId, v=track.structure.copy(): _setTrackStructure(trackId, v), descriptionString="Set Measures") + + track.structure = structure #restore data + track.buildTrack() + updatePlayback() + callbacks._trackStructureChanged(track) -#Track Switches def setSwitches(trackId, setOfPositions, newBool): + """Used in the GUI to select multiple switches in a row by dragging the mouse""" track = session.data.trackById(trackId) + session.history.register(lambda tr=trackId, v=track.structure.copy(): _setTrackStructure(trackId, v), descriptionString="Set Measures") if newBool: - track.structure = track.structure.union(setOfPositions) #add setOfPositions to the existing one + track.structure = track.structure.union(setOfPositions) #merge: add setOfPositions to the existing one else: - track.structure = track.structure.difference(setOfPositions) #remove everything from setOfPositions that is in the existing one, ignore entries from setOfPositions not in existing. + track.structure = track.structure.difference(setOfPositions) #replace: remove everything from setOfPositions that is in the existing one, ignore entries from setOfPositions not in existing. track.buildTrack() updatePlayback() callbacks._trackStructureChanged(track) def setSwitch(trackId, position, newBool): track = session.data.trackById(trackId) + session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v), descriptionString="Set Measures") if newBool: if position in track.structure: return track.structure.add(position) @@ -542,6 +605,7 @@ def setSwitch(trackId, position, newBool): def trackInvertSwitches(trackId): track = session.data.trackById(trackId) + session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v), descriptionString="Set Measures") """ if track.structure: new = set(i for i in range(max(track.structure))) @@ -557,6 +621,7 @@ def trackInvertSwitches(trackId): def trackOffAllSwitches(trackId): track = session.data.trackById(trackId) + session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v), descriptionString="Set Measures") track.structure = set() track.buildTrack() updatePlayback() @@ -564,6 +629,7 @@ def trackOffAllSwitches(trackId): def trackOnAllSwitches(trackId): track = session.data.trackById(trackId) + session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v), descriptionString="Set Measures") track.structure = set(i for i in range(session.data.numberOfMeasures)) track.buildTrack() updatePlayback() @@ -573,6 +639,7 @@ def trackMergeCopyFrom(sourceTrackId, targetTrackId): if not sourceTrackId == targetTrackId: sourceTrack = session.data.trackById(sourceTrackId) targetTrack = session.data.trackById(targetTrackId) + session.history.register(lambda trId=id(targetTrack), v=targetTrack.structure.copy(): _setTrackStructure(trId, v), descriptionString="Set Measures") targetTrack.structure = targetTrack.structure.union(sourceTrack.structure) targetTrack.whichPatternsAreScaleTransposed.update(sourceTrack.whichPatternsAreScaleTransposed) targetTrack.whichPatternsAreHalftoneTransposed.update(sourceTrack.whichPatternsAreHalftoneTransposed) @@ -584,6 +651,7 @@ def trackPatternReplaceFrom(sourceTrackId, targetTrackId): if not sourceTrackId == targetTrackId: sourceTrack = session.data.trackById(sourceTrackId) targetTrack = session.data.trackById(targetTrackId) + session.history.register(lambda trId=id(targetTrack), v=targetTrack.structure.copy(): _setTrackStructure(trId, v), descriptionString="Set Measures") copyPattern = sourceTrack.pattern.copy(newParentTrack = targetTrack) targetTrack.pattern = copyPattern @@ -592,24 +660,66 @@ def trackPatternReplaceFrom(sourceTrackId, targetTrackId): updatePlayback() callbacks._patternChanged(targetTrack) +#Transpositions and Modal Shifts + +def _setSwitchesScaleTranspose(trackId, whichPatternsAreScaleTransposed): + """For undo. Used by all functions as entry point, then calls itself for undo/redo. + whichPatternsAreScaleTransposed is a dicts of int:int which we can copy with .copy()""" + track = session.data.trackById(trackId) + + session.history.register(lambda tr=trackId, v=track.whichPatternsAreScaleTransposed.copy(): _setSwitchesScaleTranspose(trackId, v), descriptionString="Set Modal Shift") + + track.whichPatternsAreScaleTransposed = whichPatternsAreScaleTransposed #restore data + track.buildTrack() + updatePlayback() + callbacks._trackStructureChanged(track) + + def setSwitchScaleTranspose(trackId, position, transpose): """Scale transposition is flipped. lower value means higher pitch""" track = session.data.trackById(trackId) + session.history.register(lambda tr=trackId, v=track.whichPatternsAreScaleTransposed.copy(): _setSwitchesScaleTranspose(trackId, v), descriptionString="Set Modal Shift") track.whichPatternsAreScaleTransposed[position] = transpose track.buildTrack() updatePlayback() callbacks._trackStructureChanged(track) return True + +def _setSwitchHalftoneTranspose(trackId, whichPatternsAreHalftoneTransposed): + """For undo. Used by all functions as entry point, then calls itself for undo/redo. + whichPatternsAreScaleTransposed is a dicts of int:int which we can copy with .copy()""" + track = session.data.trackById(trackId) + + session.history.register(lambda tr=trackId, v=track.whichPatternsAreHalftoneTransposed.copy(): _setSwitchHalftoneTranspose(trackId, v), descriptionString="Set Half Tone Shift") + + track.whichPatternsAreHalftoneTransposed = whichPatternsAreHalftoneTransposed #restore data + track.buildTrack() + updatePlayback() + callbacks._trackStructureChanged(track) + def setSwitchHalftoneTranspose(trackId, position, transpose): """Halftone transposition is not flipped. Higher value means higher pitch""" track = session.data.trackById(trackId) + session.history.register(lambda tr=trackId, v=track.whichPatternsAreHalftoneTransposed.copy(): _setSwitchHalftoneTranspose(trackId, v), descriptionString="Set Half Tone Shift") track.whichPatternsAreHalftoneTransposed[position] = transpose track.buildTrack() updatePlayback() callbacks._trackStructureChanged(track) return True +def _registerHistoryWholeTrackSwitches(track): + """This is used by insertSilence, clearSwitchGroupTranspositions etc. + It assumes to run inside the context: + with session.history.sequence(""): + """ + trackId = id(track) + session.history.register(lambda trId=trackId, v=track.patternLengthMultiplicator: setTrackPatternLengthMultiplicator(trId, v), descriptionString="Pattern Multiplier") + session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v), descriptionString="Set Measures") + session.history.register(lambda trId=trackId, v=track.whichPatternsAreScaleTransposed.copy(): _setSwitchesScaleTranspose(trId, v), descriptionString="Set Modal Shift") + session.history.register(lambda trId=trackId, v=track.whichPatternsAreHalftoneTransposed.copy(): _setSwitchHalftoneTranspose(trId, v), descriptionString="Set Half Tone Shift") + + def insertSilence(howMany:int, beforeMeasureNumber:int): """Insert empty measures into all tracks. Parameters are un-multiplied.""" @@ -617,13 +727,18 @@ def insertSilence(howMany:int, beforeMeasureNumber:int): #In each track shift every switch to the right if it is before the dividing measure number #Keep the concept of a "group" even if there are multiplicators in the track. #If you insert 4 normal measures then a multiplicator-track of two gets only 2 new ones. - for track in session.data.tracks: - thisTrackWhere = beforeMeasureNumber // track.patternLengthMultiplicator #integer division - thisTrackHowMany = howMany // track.patternLengthMultiplicator #integer division - track.structure = set( (switch + thisTrackHowMany if switch >= thisTrackWhere else switch) for switch in track.structure ) - track.whichPatternsAreScaleTransposed = { (k+thisTrackHowMany if k >= thisTrackWhere else k):v for k,v in track.whichPatternsAreScaleTransposed.items() } - track.whichPatternsAreHalftoneTransposed = { (k+thisTrackHowMany if k >= thisTrackWhere else k):v for k,v in track.whichPatternsAreHalftoneTransposed.items() } - callbacks._trackStructureChanged(track) + with session.history.sequence("Insert/Duplicate Group"): #this actually handles duplicateSwitchGroup undo as well!!! + for track in session.data.tracks: + _registerHistoryWholeTrackSwitches(track) + + thisTrackWhere = beforeMeasureNumber // track.patternLengthMultiplicator #integer division + thisTrackHowMany = howMany // track.patternLengthMultiplicator #integer division + track.structure = set( (switch + thisTrackHowMany if switch >= thisTrackWhere else switch) for switch in track.structure ) + track.whichPatternsAreScaleTransposed = { (k+thisTrackHowMany if k >= thisTrackWhere else k):v for k,v in track.whichPatternsAreScaleTransposed.items() } + track.whichPatternsAreHalftoneTransposed = { (k+thisTrackHowMany if k >= thisTrackWhere else k):v for k,v in track.whichPatternsAreHalftoneTransposed.items() } + callbacks._trackStructureChanged(track) + + callbacks._dataChanged() #register undo session.data.buildAllTracks() updatePlayback() @@ -632,8 +747,9 @@ def duplicateSwitchGroup(startMeasureForGroup:int, endMeasureExclusive:int): format.""" groupSize = endMeasureExclusive-startMeasureForGroup - insertSilence(groupSize, endMeasureExclusive) #insert silence handles multiplicator-tracks on its own + #Undo: InsertSilence has a complete undo already registered. We chose a neutral undo description so it handles both duplicate and insert silence. + insertSilence(groupSize, endMeasureExclusive) #insert silence handles multiplicator-tracks on its own for track in session.data.tracks: thisTrackStartMeasure = startMeasureForGroup // track.patternLengthMultiplicator #integer division thisTrackEndMeasure = endMeasureExclusive // track.patternLengthMultiplicator @@ -647,59 +763,69 @@ def duplicateSwitchGroup(startMeasureForGroup:int, endMeasureExclusive:int): if switch-thisGroupSize in track.whichPatternsAreHalftoneTransposed: track.whichPatternsAreHalftoneTransposed[switch] = track.whichPatternsAreHalftoneTransposed[switch-thisGroupSize] callbacks._trackStructureChanged(track) + session.data.buildAllTracks() updatePlayback() def clearSwitchGroupTranspositions(startMeasureForGroup:int, endMeasureExclusive:int): """startMeasureForGroup and endMeasureExclusive are in the global, un-multiplied measure counting format.""" - for track in session.data.tracks: - thisTrackStartMeasure = startMeasureForGroup // track.patternLengthMultiplicator #integer division - thisTrackEndMeasure = endMeasureExclusive // track.patternLengthMultiplicator - for switch in range(thisTrackStartMeasure, thisTrackEndMeasure): - if switch in track.whichPatternsAreScaleTransposed: - del track.whichPatternsAreScaleTransposed[switch] - if switch in track.whichPatternsAreHalftoneTransposed: - del track.whichPatternsAreHalftoneTransposed[switch] - callbacks._trackStructureChanged(track) + with session.history.sequence("Clear all Group Transpositions"): + for track in session.data.tracks: + _registerHistoryWholeTrackSwitches(track) + thisTrackStartMeasure = startMeasureForGroup // track.patternLengthMultiplicator #integer division + thisTrackEndMeasure = endMeasureExclusive // track.patternLengthMultiplicator + for switch in range(thisTrackStartMeasure, thisTrackEndMeasure): + if switch in track.whichPatternsAreScaleTransposed: + del track.whichPatternsAreScaleTransposed[switch] + if switch in track.whichPatternsAreHalftoneTransposed: + del track.whichPatternsAreHalftoneTransposed[switch] + callbacks._trackStructureChanged(track) + callbacks._dataChanged() #register undo session.data.buildAllTracks() updatePlayback() def deleteSwitches(howMany, fromMeasureNumber): """Parameters are un-multiplied measures.""" - for track in session.data.tracks: - thisTrackHowMany = howMany // track.patternLengthMultiplicator #integer division - thisTrackWhere = fromMeasureNumber // track.patternLengthMultiplicator #integer division - - new_structure = set() - - for switch in track.structure: - if switch < thisTrackWhere: - new_structure.add(switch) - elif switch >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range - new_structure.add(switch-thisTrackHowMany) - #else: #discard all in range to delete - track.structure = new_structure - - new_scaleTransposed = dict() - for k,v in track.whichPatternsAreScaleTransposed.items(): - if k < thisTrackWhere: - new_scaleTransposed[k] = v - elif k >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range - new_scaleTransposed[k-thisTrackHowMany] = v - #else: #discard all in range to delete - track.whichPatternsAreScaleTransposed = new_scaleTransposed - - new_halftoneTransposed = dict() - for k,v in track.whichPatternsAreHalftoneTransposed.items(): - if k < thisTrackWhere: - new_halftoneTransposed[k] = v - elif k >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range - new_halftoneTransposed[k-thisTrackHowMany] = v - #else: #discard all in range to delete - track.whichPatternsAreHalftoneTransposed = new_halftoneTransposed - - callbacks._trackStructureChanged(track) + + with session.history.sequence("Delete whole Group"): + for track in session.data.tracks: + _registerHistoryWholeTrackSwitches(track) + + thisTrackHowMany = howMany // track.patternLengthMultiplicator #integer division + thisTrackWhere = fromMeasureNumber // track.patternLengthMultiplicator #integer division + + new_structure = set() + + for switch in track.structure: + if switch < thisTrackWhere: + new_structure.add(switch) + elif switch >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range + new_structure.add(switch-thisTrackHowMany) + #else: #discard all in range to delete + track.structure = new_structure + + new_scaleTransposed = dict() + for k,v in track.whichPatternsAreScaleTransposed.items(): + if k < thisTrackWhere: + new_scaleTransposed[k] = v + elif k >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range + new_scaleTransposed[k-thisTrackHowMany] = v + #else: #discard all in range to delete + track.whichPatternsAreScaleTransposed = new_scaleTransposed + + new_halftoneTransposed = dict() + for k,v in track.whichPatternsAreHalftoneTransposed.items(): + if k < thisTrackWhere: + new_halftoneTransposed[k] = v + elif k >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range + new_halftoneTransposed[k-thisTrackHowMany] = v + #else: #discard all in range to delete + track.whichPatternsAreHalftoneTransposed = new_halftoneTransposed + + callbacks._trackStructureChanged(track) + + callbacks._dataChanged() #register undo session.data.buildAllTracks() updatePlayback() diff --git a/qtgui/mainwindow.py b/qtgui/mainwindow.py index cde02e3..23aabb3 100644 --- a/qtgui/mainwindow.py +++ b/qtgui/mainwindow.py @@ -75,8 +75,8 @@ class MainWindow(TemplateMainWindow): tooltip=QtCore.QCoreApplication.translate("Menu", "Change step-grouping but keep your music the same"), ) - self.ui.actionUndo.setVisible(False) - self.ui.actionRedo.setVisible(False) + #self.ui.actionUndo.setVisible(False) + #self.ui.actionRedo.setVisible(False) #Playback Controls diff --git a/template/engine/api.py b/template/engine/api.py index 015eb76..f8186cc 100644 --- a/template/engine/api.py +++ b/template/engine/api.py @@ -78,7 +78,7 @@ class Callbacks(object): def _dataChanged(self): - """Only called from within the callbacks. + """Only called from within the callbacks or template api. This is about data the user cares about. In other words this is the indicator if you need to save again. @@ -136,6 +136,7 @@ class Callbacks(object): status = playbackStatus() for func in self.setPlaybackTicks: func(ppqn, status) + self._dataChanged() #includes _historyChanged def _playbackStatusChanged(self): """Returns a bool if the playback is running. @@ -243,6 +244,8 @@ class Callbacks(object): lst = [track.export() for track in session.data.tracks] for func in self.numberOfTracksChanged: func(lst) + self._dataChanged() #includes _historyChanged + def _metronomeChanged(self): """returns a dictionary with meta data such as the mute-state and the track name""" diff --git a/template/engine/sequencer.py b/template/engine/sequencer.py index b0a3c57..5ac5ad2 100644 --- a/template/engine/sequencer.py +++ b/template/engine/sequencer.py @@ -88,6 +88,7 @@ class Score(Data): #Tracks def addTrack(self, name:str=""): + """Create and add a new track. Not an existing one""" track = Score.TrackClass(parentData=self, name=name) assert track.sequencerInterface self.tracks.append(track) @@ -96,6 +97,7 @@ class Score(Data): def deleteTrack(self, track): track.sequencerInterface.prepareForDeletion() self.tracks.remove(track) + return track #for undo def updateJackMetadataSorting(self): """Add this to you "tracksChanged" or "numberOfTracksChanged" callback.