Browse Source

Undo part 1

master
Nils 3 years ago
parent
commit
48439acb05
  1. 254
      engine/api.py
  2. 4
      qtgui/mainwindow.py
  3. 5
      template/engine/api.py
  4. 2
      template/engine/sequencer.py

254
engine/api.py

@ -208,6 +208,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
for func in self.patternLengthMultiplicatorChanged: for func in self.patternLengthMultiplicatorChanged:
func(export) func(export)
self._patternChanged(track) #includes dataChanged self._patternChanged(track) #includes dataChanged
callbacks._dataChanged()
def _swingChanged(self): def _swingChanged(self):
export = session.data.swing export = session.data.swing
@ -216,6 +217,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
for func in self.swingPercentChanged: for func in self.swingPercentChanged:
func(_swingToPercent_Table[export]) func(_swingToPercent_Table[export])
callbacks._dataChanged()
#Inject our derived Callbacks into the parent module #Inject our derived Callbacks into the parent module
template.engine.api.callbacks = ClientCallbacks() template.engine.api.callbacks = ClientCallbacks()
@ -322,6 +324,17 @@ def seek(value):
##Score ##Score
def set_quarterNotesPerMinute(value): 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: if value is None:
session.data.tempoMap.isTransportMaster = False #triggers rebuild session.data.tempoMap.isTransportMaster = False #triggers rebuild
elif value == "on": elif value == "on":
@ -338,8 +351,10 @@ def set_quarterNotesPerMinute(value):
def set_whatTypeOfUnit(ticks): def set_whatTypeOfUnit(ticks):
"""Denominator of Time Signature""" """Denominator of Time Signature = Each Group produces a Quarter """
if session.data.whatTypeOfUnit == ticks: return 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.whatTypeOfUnit = ticks
session.data.buildAllTracks() session.data.buildAllTracks()
if session.inLoopMode: if session.inLoopMode:
@ -348,8 +363,10 @@ def set_whatTypeOfUnit(ticks):
callbacks._timeSignatureChanged() callbacks._timeSignatureChanged()
def set_howManyUnits(value): def set_howManyUnits(value):
"""Numerator of Time Signature""" """Numerator of Time Signature = Steps per Pattern"""
if session.data.howManyUnits == value: return 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.howManyUnits = value
session.data.buildAllTracks() session.data.buildAllTracks()
if session.inLoopMode: if session.inLoopMode:
@ -359,7 +376,9 @@ def set_howManyUnits(value):
def set_subdivisions(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.subdivisions = value
session.data.buildAllTracks() session.data.buildAllTracks()
if session.inLoopMode: if session.inLoopMode:
@ -369,9 +388,10 @@ def set_subdivisions(value):
def convert_subdivisions(value, errorHandling): def convert_subdivisions(value, errorHandling):
""""errorHandling can be fail, delete or merge""" """"errorHandling can be fail, delete or merge"""
if session.data.subdivisions == value: return oldValue = session.data.subdivisions
result = session.data.convertSubdivisions(value, errorHandling) 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() session.data.buildAllTracks()
updatePlayback() updatePlayback()
callbacks._timeSignatureChanged() #includes subdivisions 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}") logger.warning(f"Swing can only be between -0.5 and 0.5, not {value}")
return return
session.history.register(lambda v=session.data.swing: set_swing(v), descriptionString="Swing")
session.data.swing = value session.data.swing = value
session.data.buildAllTracks() session.data.buildAllTracks()
updatePlayback() updatePlayback()
@ -409,12 +430,14 @@ def setSwingPercent(value:int):
if value < -100 or value > 100: if value < -100 or value > 100:
logger.warning(f"Swing in percent can only be between -100 and +100, not {value}") logger.warning(f"Swing in percent can only be between -100 and +100, not {value}")
return return
set_swing(_percentToSwing_Table[value]) set_swing(_percentToSwing_Table[value]) #handles undo and callbacks
def set_numberOfMeasures(value): def set_numberOfMeasures(value):
if session.data.numberOfMeasures == value: if session.data.numberOfMeasures == value:
return return
session.history.register(lambda v=session.data.numberOfMeasures: set_numberOfMeasures(v), descriptionString="Measures per Track")
session.data.numberOfMeasures = value session.data.numberOfMeasures = value
session.data.buildSongDuration() session.data.buildSongDuration()
updatePlayback() updatePlayback()
@ -424,12 +447,14 @@ def set_numberOfMeasures(value):
def set_measuresPerGroup(value): def set_measuresPerGroup(value):
if session.data.measuresPerGroup == value: if session.data.measuresPerGroup == value:
return return
session.history.register(lambda v=session.data.measuresPerGroup: set_measuresPerGroup(v), descriptionString="Measures per Group")
session.data.measuresPerGroup = value session.data.measuresPerGroup = value
#No playback change #No playback change
callbacks._scoreChanged() callbacks._scoreChanged()
def changeTrackName(trackId, name): def changeTrackName(trackId, name):
track = session.data.trackById(trackId) 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()) track.sequencerInterface.name = " ".join(name.split())
callbacks._trackMetaDataChanged(track) callbacks._trackMetaDataChanged(track)
@ -437,6 +462,7 @@ def changeTrackColor(trackId, colorInHex):
"""Expects "#rrggbb""" """Expects "#rrggbb"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
assert len(colorInHex) == 7, colorInHex assert len(colorInHex) == 7, colorInHex
session.history.register(lambda trId=trackId, v=track.color: changeTrackColor(trId,v), descriptionString="Track Color")
track.color = colorInHex track.color = colorInHex
callbacks._trackMetaDataChanged(track) 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.") logger.warning(f"Midi Channel must be between 1-16 for this function, was: {newChannel}. Doing nothing.")
return return
track = session.data.trackById(trackId) 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.midiChannel = newChannel-1
track.pattern.buildExportCache() track.pattern.buildExportCache()
track.buildTrack() track.buildTrack()
@ -456,7 +483,10 @@ def changeTrackMidiChannel(trackId, newChannel:int):
def addTrack(scale=None): def addTrack(scale=None):
if scale: if scale:
assert type(scale) == tuple 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() callbacks._numberOfTracksChanged()
def createSiblingTrack(trackId): def createSiblingTrack(trackId):
@ -478,14 +508,30 @@ def createSiblingTrack(trackId):
newTrackAgain = session.data.tracks.pop(newIndex) newTrackAgain = session.data.tracks.pop(newIndex)
assert newTrackAgain is newTrack assert newTrackAgain is newTrack
session.data.tracks.insert(oldIndex+1, newTrackAgain) session.data.tracks.insert(oldIndex+1, newTrackAgain)
session.history.register(lambda trId=id(newTrackAgain): deleteTrack(trId), descriptionString="Clone Track")
callbacks._numberOfTracksChanged() callbacks._numberOfTracksChanged()
return newTrack.export() 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): def deleteTrack(trackId):
track = session.data.trackById(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 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() updatePlayback()
callbacks._numberOfTracksChanged() callbacks._numberOfTracksChanged()
@ -494,6 +540,7 @@ def moveTrack(trackId, newIndex):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
oldIndex = session.data.tracks.index(track) oldIndex = session.data.tracks.index(track)
if not oldIndex == newIndex: 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.pop(oldIndex)
session.data.tracks.insert(newIndex, track) session.data.tracks.insert(newIndex, track)
callbacks._numberOfTracksChanged() callbacks._numberOfTracksChanged()
@ -503,6 +550,7 @@ def setTrackPatternLengthMultiplicator(trackId, newMultiplicator:int):
return #Invalid input return #Invalid input
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
session.history.register(lambda tr=trackId, v=track.patternLengthMultiplicator: setTrackPatternLengthMultiplicator(trackId, v), descriptionString="Pattern Multiplier")
track.patternLengthMultiplicator = newMultiplicator track.patternLengthMultiplicator = newMultiplicator
track.pattern.buildExportCache() track.pattern.buildExportCache()
track.buildTrack() track.buildTrack()
@ -513,22 +561,37 @@ def setTrackPatternLengthMultiplicator(trackId, newMultiplicator:int):
callbacks._patternChanged(track) 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): 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) track = session.data.trackById(trackId)
session.history.register(lambda tr=trackId, v=track.structure.copy(): _setTrackStructure(trackId, v), descriptionString="Set Measures")
if newBool: 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: 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() track.buildTrack()
updatePlayback() updatePlayback()
callbacks._trackStructureChanged(track) callbacks._trackStructureChanged(track)
def setSwitch(trackId, position, newBool): def setSwitch(trackId, position, newBool):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v), descriptionString="Set Measures")
if newBool: if newBool:
if position in track.structure: return if position in track.structure: return
track.structure.add(position) track.structure.add(position)
@ -542,6 +605,7 @@ def setSwitch(trackId, position, newBool):
def trackInvertSwitches(trackId): def trackInvertSwitches(trackId):
track = session.data.trackById(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: if track.structure:
new = set(i for i in range(max(track.structure))) new = set(i for i in range(max(track.structure)))
@ -557,6 +621,7 @@ def trackInvertSwitches(trackId):
def trackOffAllSwitches(trackId): def trackOffAllSwitches(trackId):
track = session.data.trackById(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.structure = set()
track.buildTrack() track.buildTrack()
updatePlayback() updatePlayback()
@ -564,6 +629,7 @@ def trackOffAllSwitches(trackId):
def trackOnAllSwitches(trackId): def trackOnAllSwitches(trackId):
track = session.data.trackById(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.structure = set(i for i in range(session.data.numberOfMeasures))
track.buildTrack() track.buildTrack()
updatePlayback() updatePlayback()
@ -573,6 +639,7 @@ def trackMergeCopyFrom(sourceTrackId, targetTrackId):
if not sourceTrackId == targetTrackId: if not sourceTrackId == targetTrackId:
sourceTrack = session.data.trackById(sourceTrackId) sourceTrack = session.data.trackById(sourceTrackId)
targetTrack = session.data.trackById(targetTrackId) 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.structure = targetTrack.structure.union(sourceTrack.structure)
targetTrack.whichPatternsAreScaleTransposed.update(sourceTrack.whichPatternsAreScaleTransposed) targetTrack.whichPatternsAreScaleTransposed.update(sourceTrack.whichPatternsAreScaleTransposed)
targetTrack.whichPatternsAreHalftoneTransposed.update(sourceTrack.whichPatternsAreHalftoneTransposed) targetTrack.whichPatternsAreHalftoneTransposed.update(sourceTrack.whichPatternsAreHalftoneTransposed)
@ -584,6 +651,7 @@ def trackPatternReplaceFrom(sourceTrackId, targetTrackId):
if not sourceTrackId == targetTrackId: if not sourceTrackId == targetTrackId:
sourceTrack = session.data.trackById(sourceTrackId) sourceTrack = session.data.trackById(sourceTrackId)
targetTrack = session.data.trackById(targetTrackId) 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) copyPattern = sourceTrack.pattern.copy(newParentTrack = targetTrack)
targetTrack.pattern = copyPattern targetTrack.pattern = copyPattern
@ -592,24 +660,66 @@ def trackPatternReplaceFrom(sourceTrackId, targetTrackId):
updatePlayback() updatePlayback()
callbacks._patternChanged(targetTrack) 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): def setSwitchScaleTranspose(trackId, position, transpose):
"""Scale transposition is flipped. lower value means higher pitch""" """Scale transposition is flipped. lower value means higher pitch"""
track = session.data.trackById(trackId) 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.whichPatternsAreScaleTransposed[position] = transpose
track.buildTrack() track.buildTrack()
updatePlayback() updatePlayback()
callbacks._trackStructureChanged(track) callbacks._trackStructureChanged(track)
return True 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): def setSwitchHalftoneTranspose(trackId, position, transpose):
"""Halftone transposition is not flipped. Higher value means higher pitch""" """Halftone transposition is not flipped. Higher value means higher pitch"""
track = session.data.trackById(trackId) 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.whichPatternsAreHalftoneTransposed[position] = transpose
track.buildTrack() track.buildTrack()
updatePlayback() updatePlayback()
callbacks._trackStructureChanged(track) callbacks._trackStructureChanged(track)
return True 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): def insertSilence(howMany:int, beforeMeasureNumber:int):
"""Insert empty measures into all tracks. """Insert empty measures into all tracks.
Parameters are un-multiplied.""" 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 #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. #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. #If you insert 4 normal measures then a multiplicator-track of two gets only 2 new ones.
for track in session.data.tracks: with session.history.sequence("Insert/Duplicate Group"): #this actually handles duplicateSwitchGroup undo as well!!!
thisTrackWhere = beforeMeasureNumber // track.patternLengthMultiplicator #integer division for track in session.data.tracks:
thisTrackHowMany = howMany // track.patternLengthMultiplicator #integer division _registerHistoryWholeTrackSwitches(track)
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() } thisTrackWhere = beforeMeasureNumber // track.patternLengthMultiplicator #integer division
track.whichPatternsAreHalftoneTransposed = { (k+thisTrackHowMany if k >= thisTrackWhere else k):v for k,v in track.whichPatternsAreHalftoneTransposed.items() } thisTrackHowMany = howMany // track.patternLengthMultiplicator #integer division
callbacks._trackStructureChanged(track) 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() session.data.buildAllTracks()
updatePlayback() updatePlayback()
@ -632,8 +747,9 @@ def duplicateSwitchGroup(startMeasureForGroup:int, endMeasureExclusive:int):
format.""" format."""
groupSize = endMeasureExclusive-startMeasureForGroup 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: for track in session.data.tracks:
thisTrackStartMeasure = startMeasureForGroup // track.patternLengthMultiplicator #integer division thisTrackStartMeasure = startMeasureForGroup // track.patternLengthMultiplicator #integer division
thisTrackEndMeasure = endMeasureExclusive // track.patternLengthMultiplicator thisTrackEndMeasure = endMeasureExclusive // track.patternLengthMultiplicator
@ -647,59 +763,69 @@ def duplicateSwitchGroup(startMeasureForGroup:int, endMeasureExclusive:int):
if switch-thisGroupSize in track.whichPatternsAreHalftoneTransposed: if switch-thisGroupSize in track.whichPatternsAreHalftoneTransposed:
track.whichPatternsAreHalftoneTransposed[switch] = track.whichPatternsAreHalftoneTransposed[switch-thisGroupSize] track.whichPatternsAreHalftoneTransposed[switch] = track.whichPatternsAreHalftoneTransposed[switch-thisGroupSize]
callbacks._trackStructureChanged(track) callbacks._trackStructureChanged(track)
session.data.buildAllTracks() session.data.buildAllTracks()
updatePlayback() updatePlayback()
def clearSwitchGroupTranspositions(startMeasureForGroup:int, endMeasureExclusive:int): def clearSwitchGroupTranspositions(startMeasureForGroup:int, endMeasureExclusive:int):
"""startMeasureForGroup and endMeasureExclusive are in the global, un-multiplied measure counting """startMeasureForGroup and endMeasureExclusive are in the global, un-multiplied measure counting
format.""" format."""
for track in session.data.tracks: with session.history.sequence("Clear all Group Transpositions"):
thisTrackStartMeasure = startMeasureForGroup // track.patternLengthMultiplicator #integer division for track in session.data.tracks:
thisTrackEndMeasure = endMeasureExclusive // track.patternLengthMultiplicator _registerHistoryWholeTrackSwitches(track)
for switch in range(thisTrackStartMeasure, thisTrackEndMeasure): thisTrackStartMeasure = startMeasureForGroup // track.patternLengthMultiplicator #integer division
if switch in track.whichPatternsAreScaleTransposed: thisTrackEndMeasure = endMeasureExclusive // track.patternLengthMultiplicator
del track.whichPatternsAreScaleTransposed[switch] for switch in range(thisTrackStartMeasure, thisTrackEndMeasure):
if switch in track.whichPatternsAreHalftoneTransposed: if switch in track.whichPatternsAreScaleTransposed:
del track.whichPatternsAreHalftoneTransposed[switch] del track.whichPatternsAreScaleTransposed[switch]
callbacks._trackStructureChanged(track) if switch in track.whichPatternsAreHalftoneTransposed:
del track.whichPatternsAreHalftoneTransposed[switch]
callbacks._trackStructureChanged(track)
callbacks._dataChanged() #register undo
session.data.buildAllTracks() session.data.buildAllTracks()
updatePlayback() updatePlayback()
def deleteSwitches(howMany, fromMeasureNumber): def deleteSwitches(howMany, fromMeasureNumber):
"""Parameters are un-multiplied measures.""" """Parameters are un-multiplied measures."""
for track in session.data.tracks:
thisTrackHowMany = howMany // track.patternLengthMultiplicator #integer division with session.history.sequence("Delete whole Group"):
thisTrackWhere = fromMeasureNumber // track.patternLengthMultiplicator #integer division for track in session.data.tracks:
_registerHistoryWholeTrackSwitches(track)
new_structure = set()
thisTrackHowMany = howMany // track.patternLengthMultiplicator #integer division
for switch in track.structure: thisTrackWhere = fromMeasureNumber // track.patternLengthMultiplicator #integer division
if switch < thisTrackWhere:
new_structure.add(switch) new_structure = set()
elif switch >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range
new_structure.add(switch-thisTrackHowMany) for switch in track.structure:
#else: #discard all in range to delete if switch < thisTrackWhere:
track.structure = new_structure new_structure.add(switch)
elif switch >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range
new_scaleTransposed = dict() new_structure.add(switch-thisTrackHowMany)
for k,v in track.whichPatternsAreScaleTransposed.items(): #else: #discard all in range to delete
if k < thisTrackWhere: track.structure = new_structure
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 = dict()
new_scaleTransposed[k-thisTrackHowMany] = v for k,v in track.whichPatternsAreScaleTransposed.items():
#else: #discard all in range to delete if k < thisTrackWhere:
track.whichPatternsAreScaleTransposed = new_scaleTransposed new_scaleTransposed[k] = v
elif k >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range
new_halftoneTransposed = dict() new_scaleTransposed[k-thisTrackHowMany] = v
for k,v in track.whichPatternsAreHalftoneTransposed.items(): #else: #discard all in range to delete
if k < thisTrackWhere: track.whichPatternsAreScaleTransposed = new_scaleTransposed
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 = dict()
new_halftoneTransposed[k-thisTrackHowMany] = v for k,v in track.whichPatternsAreHalftoneTransposed.items():
#else: #discard all in range to delete if k < thisTrackWhere:
track.whichPatternsAreHalftoneTransposed = new_halftoneTransposed new_halftoneTransposed[k] = v
elif k >= thisTrackWhere+thisTrackHowMany: #like a text editor let gravitate left into the hole left by the deleted range
callbacks._trackStructureChanged(track) 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() session.data.buildAllTracks()
updatePlayback() updatePlayback()

4
qtgui/mainwindow.py

@ -75,8 +75,8 @@ class MainWindow(TemplateMainWindow):
tooltip=QtCore.QCoreApplication.translate("Menu", "Change step-grouping but keep your music the same"), tooltip=QtCore.QCoreApplication.translate("Menu", "Change step-grouping but keep your music the same"),
) )
self.ui.actionUndo.setVisible(False) #self.ui.actionUndo.setVisible(False)
self.ui.actionRedo.setVisible(False) #self.ui.actionRedo.setVisible(False)
#Playback Controls #Playback Controls

5
template/engine/api.py

@ -78,7 +78,7 @@ class Callbacks(object):
def _dataChanged(self): 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 This is about data the user cares about. In other words this is the indicator if you need
to save again. to save again.
@ -136,6 +136,7 @@ class Callbacks(object):
status = playbackStatus() status = playbackStatus()
for func in self.setPlaybackTicks: for func in self.setPlaybackTicks:
func(ppqn, status) func(ppqn, status)
self._dataChanged() #includes _historyChanged
def _playbackStatusChanged(self): def _playbackStatusChanged(self):
"""Returns a bool if the playback is running. """Returns a bool if the playback is running.
@ -243,6 +244,8 @@ class Callbacks(object):
lst = [track.export() for track in session.data.tracks] lst = [track.export() for track in session.data.tracks]
for func in self.numberOfTracksChanged: for func in self.numberOfTracksChanged:
func(lst) func(lst)
self._dataChanged() #includes _historyChanged
def _metronomeChanged(self): def _metronomeChanged(self):
"""returns a dictionary with meta data such as the mute-state and the track name""" """returns a dictionary with meta data such as the mute-state and the track name"""

2
template/engine/sequencer.py

@ -88,6 +88,7 @@ class Score(Data):
#Tracks #Tracks
def addTrack(self, name:str=""): def addTrack(self, name:str=""):
"""Create and add a new track. Not an existing one"""
track = Score.TrackClass(parentData=self, name=name) track = Score.TrackClass(parentData=self, name=name)
assert track.sequencerInterface assert track.sequencerInterface
self.tracks.append(track) self.tracks.append(track)
@ -96,6 +97,7 @@ class Score(Data):
def deleteTrack(self, track): def deleteTrack(self, track):
track.sequencerInterface.prepareForDeletion() track.sequencerInterface.prepareForDeletion()
self.tracks.remove(track) self.tracks.remove(track)
return track #for undo
def updateJackMetadataSorting(self): def updateJackMetadataSorting(self):
"""Add this to you "tracksChanged" or "numberOfTracksChanged" callback. """Add this to you "tracksChanged" or "numberOfTracksChanged" callback.

Loading…
Cancel
Save