Browse Source

undo/redo part 2

master
Nils 3 years ago
parent
commit
c0bef9a813
  1. 62
      engine/api.py
  2. 10
      engine/pattern.py
  3. 2
      qtgui/pattern_grid.py

62
engine/api.py

@ -564,12 +564,12 @@ def setTrackPatternLengthMultiplicator(trackId, newMultiplicator:int):
#Track Switches
#Aka measures
def _setTrackStructure(trackId, structure):
def _setTrackStructure(trackId, structure, undoMessage):
"""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")
session.history.register(lambda tr=trackId, v=track.structure.copy(), msg=undoMessage: _setTrackStructure(trackId, v, msg), descriptionString=undoMessage)
track.structure = structure #restore data
track.buildTrack()
@ -580,7 +580,7 @@ def _setTrackStructure(trackId, structure):
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")
session.history.register(lambda tr=trackId, v=track.structure.copy(): _setTrackStructure(trackId, v, "Set Measures"), descriptionString="Set Measures")
if newBool:
track.structure = track.structure.union(setOfPositions) #merge: add setOfPositions to the existing one
else:
@ -591,7 +591,7 @@ def setSwitches(trackId, setOfPositions, newBool):
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")
session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Set Measures"), descriptionString="Set Measures")
if newBool:
if position in track.structure: return
track.structure.add(position)
@ -605,7 +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")
session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Invert Measures"), descriptionString="Invert Measures")
"""
if track.structure:
new = set(i for i in range(max(track.structure)))
@ -621,7 +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")
session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Track Measures Off"), descriptionString="Track Measures Off")
track.structure = set()
track.buildTrack()
updatePlayback()
@ -629,7 +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")
session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Track Measures On"), descriptionString="Track Measures On")
track.structure = set(i for i in range(session.data.numberOfMeasures))
track.buildTrack()
updatePlayback()
@ -639,7 +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")
session.history.register(lambda trId=id(targetTrack), v=targetTrack.structure.copy(): _setTrackStructure(trId, v, "Copy Measures"), descriptionString="Copy Measures")
targetTrack.structure = targetTrack.structure.union(sourceTrack.structure)
targetTrack.whichPatternsAreScaleTransposed.update(sourceTrack.whichPatternsAreScaleTransposed)
targetTrack.whichPatternsAreHalftoneTransposed.update(sourceTrack.whichPatternsAreHalftoneTransposed)
@ -651,7 +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")
session.history.register(lambda trId=id(targetTrack), v=targetTrack.structure.copy(): _setTrackStructure(trId, v, "Replace Measures"), descriptionString="Replace Measures")
copyPattern = sourceTrack.pattern.copy(newParentTrack = targetTrack)
targetTrack.pattern = copyPattern
@ -715,7 +715,7 @@ def _registerHistoryWholeTrackSwitches(track):
"""
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.structure.copy(): _setTrackStructure(trId, v, "Change Group"), descriptionString="Change Group")
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")
@ -830,9 +830,17 @@ def deleteSwitches(howMany, fromMeasureNumber):
updatePlayback()
#Pattern Steps
def setPattern(trackId, patternList):
"""Change the whole pattern, send a callback with the whole pattern"""
def setPattern(trackId, patternList, undoMessage):
"""Change the whole pattern, send a callback with the whole pattern.
This is also the main undo/redo function.
It is rarely used directly by the GUI, if at all. Normal changes are atomic.
"""
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(), msg=undoMessage: setPattern(trId, v, msg), descriptionString=undoMessage)
track.pattern.data = patternList
track.pattern.buildExportCache()
track.buildTrack()
@ -847,8 +855,12 @@ def setStep(trackId, stepExportDict):
only sends that switch back via callback. A simple GUI
will most like not listen to that callback since they
already changed the step on their side. Only useful for parallel
views."""
views.
This is also for velocity!
"""
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Change Step"), descriptionString="Change Step")
oldNote = track.pattern.stepByIndexAndPitch(index=stepExportDict["index"], pitch=stepExportDict["pitch"])
if oldNote: #modify existing note
oldNoteIndex = track.pattern.data.index(oldNote)
@ -865,6 +877,7 @@ def setStep(trackId, stepExportDict):
def removeStep(trackId, index, pitch):
"""Reverse of setStep"""
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Remove Step"), descriptionString="Remove Step")
oldNote = track.pattern.stepByIndexAndPitch(index, pitch)
track.pattern.data.remove(oldNote)
track.pattern.buildExportCache()
@ -876,7 +889,8 @@ 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)
track.pattern.scale = scale
session.history.register(lambda trId=trackId, v=track.pattern.scale[:]: setScale(trId, v, callback=True), descriptionString="Set Scale")
track.pattern.scale = scale #tuple, or list if oversight in json loading :)
track.pattern.buildExportCache()
track.buildTrack()
updatePlayback()
@ -887,11 +901,13 @@ def setSimpleNoteNames(trackId, simpleNoteNames):
"""note names is a list of strings with length 128. One name for each midi note.
It is saved to file"""
track = session.data.trackById(trackId)
track.pattern.simpleNoteNames = simpleNoteNames
session.history.register(lambda trId=trackId, v=track.pattern.simpleNoteNames[:]: setSimpleNoteNames(trId, v), descriptionString="Note Names")
track.pattern.simpleNoteNames = simpleNoteNames #list of strings
callbacks._trackMetaDataChanged(track)
def transposeHalftoneSteps(trackId, steps):
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.scale[:]: setScale(trId, v, callback=True), descriptionString="Transpose Scale")
track.pattern.scale = [midipitch+steps for midipitch in track.pattern.scale]
track.pattern.buildExportCache()
track.buildTrack()
@ -900,6 +916,7 @@ def transposeHalftoneSteps(trackId, steps):
def patternInvertSteps(trackId):
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Invert Steps"), descriptionString="Invert Steps")
track.pattern.invert()
track.pattern.buildExportCache()
track.buildTrack()
@ -908,6 +925,7 @@ def patternInvertSteps(trackId):
def patternOnAllSteps(trackId):
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "All Steps On"), descriptionString="All Steps On")
track.pattern.fill()
track.pattern.buildExportCache()
track.buildTrack()
@ -916,6 +934,7 @@ def patternOnAllSteps(trackId):
def patternOffAllSteps(trackId):
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "All Steps Off"), descriptionString="All Steps Off")
track.pattern.empty()
track.pattern.buildExportCache()
track.buildTrack()
@ -925,6 +944,7 @@ def patternOffAllSteps(trackId):
def patternInvertRow(trackId, pitchindex):
"""Pitchindex is the row"""
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Invert Row"), descriptionString="Invert Row")
track.pattern.invertRow(pitchindex)
track.pattern.buildExportCache()
track.buildTrack()
@ -935,6 +955,7 @@ def patternClearRow(trackId, pitchindex):
"""Pitchindex is the row.
Index is the column"""
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Clear Row"), descriptionString="Clear Row")
track.pattern.clearRow(pitchindex)
track.pattern.buildExportCache()
track.buildTrack()
@ -945,6 +966,7 @@ def patternRowRepeatFromStep(trackId, pitchindex, index):
"""Pitchindex is the row.
Index is the column"""
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Fill Row with Repeat"), descriptionString="Fill Row with Repeat")
track.pattern.repeatFromStep(pitchindex, index)
track.pattern.buildExportCache()
track.buildTrack()
@ -953,6 +975,7 @@ def patternRowRepeatFromStep(trackId, pitchindex, index):
def patternRowChangeVelocity(trackId, pitchindex, delta):
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Change Row Velocity"), descriptionString="Change Row Velocity")
for note in track.pattern.getRow(pitchindex):
new = note["velocity"] + delta
note["velocity"] = min(max(new,0), 127)
@ -1004,6 +1027,7 @@ def setScaleToKeyword(trackId, keyword):
This function is called not often and does not need to be performant.
"""
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.scale[:]: setScale(trId, v, callback=True), descriptionString="Set Scale")
rememberRootNote = track.pattern.scale[-1] #The last note has a special role by convention. No matter if this is the lowest midi-pitch or not. Most of the time it is the lowest though.
@ -1047,6 +1071,7 @@ def setScaleToKeyword(trackId, keyword):
def changePatternVelocity(trackId, steps):
track = session.data.trackById(trackId)
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Change Pattern Velocity"), descriptionString="Change Pattern Velocity")
for note in track.pattern.data:
new = note["velocity"] + steps
note["velocity"] = min(max(new,0), 127)
@ -1070,7 +1095,12 @@ def resizePatternWithoutScale(trackId, steps):
return
track = session.data.trackById(trackId)
currentNr = track.pattern.numberOfSteps
#We could use setScale for undo. But this requires a different set of callbacks. We use our own function, eventhough some of the calculations are not needed for undo.
session.history.register(lambda trId=trackId, v=track.pattern.numberOfSteps: resizePatternWithoutScale(trId, v), descriptionString="Number of Notes in Pattern")
currentNr = track.pattern.numberOfSteps #int
oldid = id(track.pattern.scale)
s = track.pattern.scale #GUI view: from top to bottom. Usually from higher pitches to lower. (49, 53, 50, 45, 42, 39, 38, 36)
if steps == currentNr:

10
engine/pattern.py

@ -78,9 +78,15 @@ class Pattern(object):
self._builtPatternCache = {} #holds a ready cbox pattern for a clip as value. Key is a tuple of hashable parameters. see self.buildPattern
def copy(self, newParentTrack):
"""Return an independent copy of this pattern"""
def copyData(self):
"""Return only the data as copy.
Used by other functions and api-Undo"""
data = [note.copy() for note in self.data] #list of mutable dicts. Dicts have only primitve data types inside
return data
def copy(self, newParentTrack):
"""Return an independent copy of this pattern as Pattern() instance"""
data = self.copyData()
scale = self.scale #it is immutable so there is no risk of changing it in place for both patterns at once
simpleNoteNames = self.simpleNoteNames[:] #this mutable list always gets replaced completely by setting a new list, but we don't want to take any chances and create a slice copy.
result = Pattern(newParentTrack, data, scale, simpleNoteNames)

2
qtgui/pattern_grid.py

@ -206,11 +206,11 @@ class PatternGrid(QtWidgets.QGraphicsScene):
if redrawStepsNeeded:
self._redrawSteps(exportDict["patternBaseLength"], forceId=exportDict["id"]) #first parameter howMany is rhtyhm, not pitches. redraw fetches pitches itself.
self.guicallback_subdivisionsChanged(self._cacheSubdivisonValue)
self.parentView.parentMainWindow.transposeControls.guicallback_chooseCurrentTrack(exportDict, exportDict["id"]) #update steps widget. needed for at least undo/redo
for step in self._steps.values():
step.off()
for noteDict in exportDict["pattern"]:
x = noteDict["index"]
y = noteDict["pitch"]

Loading…
Cancel
Save