Browse Source

guard against a rare crash that happens when a qt widget tries to access a just deleted track, because the widget itself got deleted which triggered an out-of-focus-send-to-engine event

master
Nils 3 years ago
parent
commit
b24394e681
  1. 38
      engine/api.py
  2. 13
      template/engine/sequencer.py

38
engine/api.py

@ -475,6 +475,7 @@ def set_measuresPerGroup(value):
def changeTrackName(trackId, name): def changeTrackName(trackId, name):
"""The template gurantees a unique, sanitized name across tracks and groups""" """The template gurantees a unique, sanitized name across tracks and groups"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
if not name.lower() in (gr.lower() for gr in getGroups()): if not name.lower() in (gr.lower() for gr in getGroups()):
session.history.register(lambda trId=trackId, v=track.sequencerInterface.name: changeTrackName(trId,v), descriptionString="Track Name") session.history.register(lambda trId=trackId, v=track.sequencerInterface.name: changeTrackName(trId,v), descriptionString="Track Name")
track.sequencerInterface.name = name #sanitizes on its own. Checks for duplicate tracks but not groups track.sequencerInterface.name = name #sanitizes on its own. Checks for duplicate tracks but not groups
@ -483,6 +484,7 @@ def changeTrackName(trackId, name):
def changeTrackColor(trackId, colorInHex): def changeTrackColor(trackId, colorInHex):
"""Expects "#rrggbb""" """Expects "#rrggbb"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
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") session.history.register(lambda trId=trackId, v=track.color: changeTrackColor(trId,v), descriptionString="Track Color")
track.color = colorInHex track.color = colorInHex
@ -495,6 +497,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)
if not track: return
session.history.register(lambda trId=trackId, v=track.midiChannel: changeTrackMidiChannel(trId,v), descriptionString="Track Midi Channel") 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()
@ -517,6 +520,7 @@ def createSiblingTrack(trackId): #aka clone track
The jack midi out will be independent after creation, but connected to the same instrument The jack midi out will be independent after creation, but connected to the same instrument
(if any)""" (if any)"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
assert type(track.pattern.scale) == tuple assert type(track.pattern.scale) == tuple
newTrack = session.data.addTrack(name=track.sequencerInterface.name, scale=track.pattern.scale, color=track.color, simpleNoteNames=track.pattern.simpleNoteNames) #track name increments itself from "Track A" to "Track B" or "Track 1" -> "Track 2" newTrack = session.data.addTrack(name=track.sequencerInterface.name, scale=track.pattern.scale, color=track.color, simpleNoteNames=track.pattern.simpleNoteNames) #track name increments itself from "Track A" to "Track B" or "Track 1" -> "Track 2"
newTrack.pattern.averageVelocity = track.pattern.averageVelocity newTrack.pattern.averageVelocity = track.pattern.averageVelocity
@ -551,6 +555,7 @@ def _reinsertDeletedTrack(track, trackIndex):
def deleteTrack(trackId): def deleteTrack(trackId):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
oldIndex = session.data.tracks.index(track) oldIndex = session.data.tracks.index(track)
with session.history.sequence("Delete Track"): with session.history.sequence("Delete Track"):
setTrackGroup(trackId, "") #has it's own undo setTrackGroup(trackId, "") #has it's own undo
@ -562,8 +567,6 @@ def deleteTrack(trackId):
else: else:
session.history.register(lambda tr=deletedTrack, pos=oldIndex: _reinsertDeletedTrack(tr, pos), descriptionString="Delete Track") session.history.register(lambda tr=deletedTrack, pos=oldIndex: _reinsertDeletedTrack(tr, pos), descriptionString="Delete Track")
print (session.data.groups)
updatePlayback() updatePlayback()
callbacks._numberOfTracksChanged() callbacks._numberOfTracksChanged()
@ -574,6 +577,7 @@ def moveTrack(trackId, newIndex):
in session.data.tracks , so that jack metadata port order works, groups or not. in session.data.tracks , so that jack metadata port order works, groups or not.
""" """
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
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.history.register(lambda tr=trackId, pos=oldIndex: moveTrack(trackId, pos), descriptionString="Move Track")
@ -587,6 +591,7 @@ def setTrackPatternLengthMultiplicator(trackId, newMultiplicator:int):
return #Invalid input return #Invalid input
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda tr=trackId, v=track.patternLengthMultiplicator: setTrackPatternLengthMultiplicator(trackId, v), descriptionString="Pattern Multiplier") 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()
@ -610,6 +615,7 @@ def setTrackGroup(trackId, groupName:str):
"""A not yet existing groupName will create that. """A not yet existing groupName will create that.
Set to empty string to create a standalone track""" Set to empty string to create a standalone track"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
groupName = ''.join(ch for ch in groupName if ch.isalnum()) #sanitize groupName = ''.join(ch for ch in groupName if ch.isalnum()) #sanitize
groupName = " ".join(groupName.split()) #remove double spaces groupName = " ".join(groupName.split()) #remove double spaces
if not track.group == groupName: if not track.group == groupName:
@ -675,6 +681,7 @@ def _setTrackStructure(trackId, structure, undoMessage):
"""For undo. Used by all functions as entry point, then calls itself for undo/redo. """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()""" structure is a set of integers which we can copy with .copy()"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda tr=trackId, v=track.structure.copy(), msg=undoMessage: _setTrackStructure(trackId, v, msg), descriptionString=undoMessage) session.history.register(lambda tr=trackId, v=track.structure.copy(), msg=undoMessage: _setTrackStructure(trackId, v, msg), descriptionString=undoMessage)
@ -687,6 +694,7 @@ def _setTrackStructure(trackId, structure, undoMessage):
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""" """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)
if not track: return
session.history.register(lambda tr=trackId, v=track.structure.copy(): _setTrackStructure(trackId, v, "Set Measures"), descriptionString="Set Measures") session.history.register(lambda tr=trackId, v=track.structure.copy(): _setTrackStructure(trackId, v, "Set Measures"), descriptionString="Set Measures")
if newBool: if newBool:
track.structure = track.structure.union(setOfPositions) #merge: add setOfPositions to the existing one track.structure = track.structure.union(setOfPositions) #merge: add setOfPositions to the existing one
@ -698,6 +706,7 @@ def setSwitches(trackId, setOfPositions, newBool):
def setSwitch(trackId, position, newBool): def setSwitch(trackId, position, newBool):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Set Measures"), descriptionString="Set Measures") session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Set Measures"), descriptionString="Set Measures")
if newBool: if newBool:
if position in track.structure: return if position in track.structure: return
@ -712,6 +721,7 @@ def setSwitch(trackId, position, newBool):
def trackInvertSwitches(trackId): def trackInvertSwitches(trackId):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Invert Measures"), descriptionString="Invert Measures") session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Invert Measures"), descriptionString="Invert Measures")
""" """
if track.structure: if track.structure:
@ -728,6 +738,7 @@ def trackInvertSwitches(trackId):
def trackOffAllSwitches(trackId): def trackOffAllSwitches(trackId):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Track Measures Off"), descriptionString="Track Measures Off") session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Track Measures Off"), descriptionString="Track Measures Off")
track.structure = set() track.structure = set()
track.buildTrack() track.buildTrack()
@ -736,6 +747,7 @@ def trackOffAllSwitches(trackId):
def trackOnAllSwitches(trackId): def trackOnAllSwitches(trackId):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.structure.copy(): _setTrackStructure(trId, v, "Track Measures On"), descriptionString="Track Measures On") 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.structure = set(i for i in range(session.data.numberOfMeasures))
track.buildTrack() track.buildTrack()
@ -773,6 +785,7 @@ def _setSwitchesScaleTranspose(trackId, whichPatternsAreScaleTransposed):
"""For undo. Used by all functions as entry point, then calls itself for undo/redo. """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()""" whichPatternsAreScaleTransposed is a dicts of int:int which we can copy with .copy()"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda tr=trackId, v=track.whichPatternsAreScaleTransposed.copy(): _setSwitchesScaleTranspose(trackId, v), descriptionString="Set Modal Shift") session.history.register(lambda tr=trackId, v=track.whichPatternsAreScaleTransposed.copy(): _setSwitchesScaleTranspose(trackId, v), descriptionString="Set Modal Shift")
@ -785,6 +798,7 @@ def _setSwitchesScaleTranspose(trackId, whichPatternsAreScaleTransposed):
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)
if not track: return
session.history.register(lambda tr=trackId, v=track.whichPatternsAreScaleTransposed.copy(): _setSwitchesScaleTranspose(trackId, v), descriptionString="Set Modal Shift") 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()
@ -797,6 +811,7 @@ def _setSwitchHalftoneTranspose(trackId, whichPatternsAreHalftoneTransposed):
"""For undo. Used by all functions as entry point, then calls itself for undo/redo. """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()""" whichPatternsAreScaleTransposed is a dicts of int:int which we can copy with .copy()"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda tr=trackId, v=track.whichPatternsAreHalftoneTransposed.copy(): _setSwitchHalftoneTranspose(trackId, v), descriptionString="Set Half Tone Shift") session.history.register(lambda tr=trackId, v=track.whichPatternsAreHalftoneTransposed.copy(): _setSwitchHalftoneTranspose(trackId, v), descriptionString="Set Half Tone Shift")
@ -808,6 +823,7 @@ def _setSwitchHalftoneTranspose(trackId, whichPatternsAreHalftoneTransposed):
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)
if not track: return
session.history.register(lambda tr=trackId, v=track.whichPatternsAreHalftoneTransposed.copy(): _setSwitchHalftoneTranspose(trackId, v), descriptionString="Set Half Tone Shift") 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()
@ -1015,6 +1031,7 @@ def setPattern(trackId, patternList, undoMessage):
It is rarely used directly by the GUI, if at all. Normal changes are atomic. It is rarely used directly by the GUI, if at all. Normal changes are atomic.
""" """
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(), msg=undoMessage: setPattern(trId, v, msg), descriptionString=undoMessage) session.history.register(lambda trId=trackId, v=track.pattern.copyData(), msg=undoMessage: setPattern(trId, v, msg), descriptionString=undoMessage)
@ -1037,6 +1054,7 @@ def setStep(trackId, stepExportDict):
This is also for velocity! This is also for velocity!
""" """
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Change Step"), descriptionString="Change Step") 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"]) oldNote = track.pattern.stepByIndexAndPitch(index=stepExportDict["index"], pitch=stepExportDict["pitch"])
if oldNote: #modify existing note if oldNote: #modify existing note
@ -1054,6 +1072,7 @@ def setStep(trackId, stepExportDict):
def removeStep(trackId, index, pitch): def removeStep(trackId, index, pitch):
"""Reverse of setStep""" """Reverse of setStep"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Remove Step"), descriptionString="Remove Step") session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Remove Step"), descriptionString="Remove Step")
oldNote = track.pattern.stepByIndexAndPitch(index, pitch) oldNote = track.pattern.stepByIndexAndPitch(index, pitch)
track.pattern.data.remove(oldNote) track.pattern.data.remove(oldNote)
@ -1066,6 +1085,7 @@ def setScale(trackId, scale, callback = True):
"""Expects a scale list or tuple from lowest index to highest. """Expects a scale list or tuple from lowest index to highest.
Actual pitches don't matter.""" Actual pitches don't matter."""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.scale[:]: setScale(trId, v, callback=True), descriptionString="Set 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.scale = scale #tuple, or list if oversight in json loading :)
track.pattern.buildExportCache() track.pattern.buildExportCache()
@ -1078,12 +1098,14 @@ def setSimpleNoteNames(trackId, simpleNoteNames):
"""note names is a list of strings with length 128. One name for each midi note. """note names is a list of strings with length 128. One name for each midi note.
It is saved to file""" It is saved to file"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.simpleNoteNames[:]: setSimpleNoteNames(trId, v), descriptionString="Note Names") session.history.register(lambda trId=trackId, v=track.pattern.simpleNoteNames[:]: setSimpleNoteNames(trId, v), descriptionString="Note Names")
track.pattern.simpleNoteNames = simpleNoteNames #list of strings track.pattern.simpleNoteNames = simpleNoteNames #list of strings
callbacks._trackMetaDataChanged(track) callbacks._trackMetaDataChanged(track)
def transposeHalftoneSteps(trackId, steps): def transposeHalftoneSteps(trackId, steps):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.scale[:]: setScale(trId, v, callback=True), descriptionString="Transpose Scale") 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.scale = [midipitch+steps for midipitch in track.pattern.scale]
track.pattern.buildExportCache() track.pattern.buildExportCache()
@ -1093,6 +1115,7 @@ def transposeHalftoneSteps(trackId, steps):
def patternInvertSteps(trackId): def patternInvertSteps(trackId):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Invert Steps"), descriptionString="Invert Steps") session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Invert Steps"), descriptionString="Invert Steps")
track.pattern.invert() track.pattern.invert()
track.pattern.buildExportCache() track.pattern.buildExportCache()
@ -1102,6 +1125,7 @@ def patternInvertSteps(trackId):
def patternOnAllSteps(trackId): def patternOnAllSteps(trackId):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "All Steps On"), descriptionString="All Steps On") 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.fill()
track.pattern.buildExportCache() track.pattern.buildExportCache()
@ -1111,6 +1135,7 @@ def patternOnAllSteps(trackId):
def patternOffAllSteps(trackId): def patternOffAllSteps(trackId):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "All Steps Off"), descriptionString="All Steps Off") 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.empty()
track.pattern.buildExportCache() track.pattern.buildExportCache()
@ -1121,6 +1146,7 @@ def patternOffAllSteps(trackId):
def patternInvertRow(trackId, pitchindex): def patternInvertRow(trackId, pitchindex):
"""Pitchindex is the row""" """Pitchindex is the row"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Invert Row"), descriptionString="Invert Row") session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Invert Row"), descriptionString="Invert Row")
track.pattern.invertRow(pitchindex) track.pattern.invertRow(pitchindex)
track.pattern.buildExportCache() track.pattern.buildExportCache()
@ -1132,6 +1158,7 @@ def patternClearRow(trackId, pitchindex):
"""Pitchindex is the row. """Pitchindex is the row.
Index is the column""" Index is the column"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Clear Row"), descriptionString="Clear Row") session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Clear Row"), descriptionString="Clear Row")
track.pattern.clearRow(pitchindex) track.pattern.clearRow(pitchindex)
track.pattern.buildExportCache() track.pattern.buildExportCache()
@ -1143,6 +1170,7 @@ def patternRowRepeatFromStep(trackId, pitchindex, index):
"""Pitchindex is the row. """Pitchindex is the row.
Index is the column""" Index is the column"""
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Fill Row with Repeat"), descriptionString="Fill Row with Repeat") 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.repeatFromStep(pitchindex, index)
track.pattern.buildExportCache() track.pattern.buildExportCache()
@ -1152,6 +1180,7 @@ def patternRowRepeatFromStep(trackId, pitchindex, index):
def patternRowChangeVelocity(trackId, pitchindex, delta): def patternRowChangeVelocity(trackId, pitchindex, delta):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Change Row Velocity"), descriptionString="Change Row Velocity") 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): for note in track.pattern.getRow(pitchindex):
new = note["velocity"] + delta new = note["velocity"] + delta
@ -1204,6 +1233,7 @@ def setScaleToKeyword(trackId, keyword):
This function is called not often and does not need to be performant. This function is called not often and does not need to be performant.
""" """
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.scale[:]: setScale(trId, v, callback=True), descriptionString="Set Scale") 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. 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.
@ -1248,6 +1278,7 @@ def setScaleToKeyword(trackId, keyword):
def changePatternVelocity(trackId, steps): def changePatternVelocity(trackId, steps):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
session.history.register(lambda trId=trackId, v=track.pattern.copyData(): setPattern(trId, v, "Change Pattern Velocity"), descriptionString="Change Pattern Velocity") 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: for note in track.pattern.data:
new = note["velocity"] + steps new = note["velocity"] + steps
@ -1272,6 +1303,7 @@ def resizePatternWithoutScale(trackId, steps):
return return
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
#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. #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.
@ -1305,11 +1337,13 @@ def resizePatternWithoutScale(trackId, steps):
#Other functions. These can't be template functions because they use a specific track and Patroneos row and scale system. #Other functions. These can't be template functions because they use a specific track and Patroneos row and scale system.
def noteOn(trackId, row): def noteOn(trackId, row):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
midipitch = track.pattern.scale[row] midipitch = track.pattern.scale[row]
cbox.send_midi_event(0x90+track.midiChannel, midipitch, track.pattern.averageVelocity, output=track.cboxMidiOutAbstraction) cbox.send_midi_event(0x90+track.midiChannel, midipitch, track.pattern.averageVelocity, output=track.cboxMidiOutAbstraction)
def noteOff(trackId, row): def noteOff(trackId, row):
track = session.data.trackById(trackId) track = session.data.trackById(trackId)
if not track: return
midipitch = track.pattern.scale[row] midipitch = track.pattern.scale[row]
cbox.send_midi_event(0x80+track.midiChannel, midipitch, track.pattern.averageVelocity, output=track.cboxMidiOutAbstraction) cbox.send_midi_event(0x80+track.midiChannel, midipitch, track.pattern.averageVelocity, output=track.cboxMidiOutAbstraction)

13
template/engine/sequencer.py

@ -61,6 +61,7 @@ class Score(Data):
self.tracks = [] #see docstring self.tracks = [] #see docstring
self.tempoMap = TempoMap(parentData = self) self.tempoMap = TempoMap(parentData = self)
self._template_processAfterInit() self._template_processAfterInit()
self._tracksFailedLookup = []
def _template_processAfterInit(self): #needs a different name because there is an inherited class with the same method. def _template_processAfterInit(self): #needs a different name because there is an inherited class with the same method.
"""Call this after either init or instanceFromSerializedData""" """Call this after either init or instanceFromSerializedData"""
@ -119,11 +120,21 @@ class Score(Data):
except Exception as e: #No Jack Meta Data or Error with ports. except Exception as e: #No Jack Meta Data or Error with ports.
logger.error(e) logger.error(e)
def trackById(self, trackId:int): def trackById(self, trackId:int):
"""Returns a track or None, if not found"""
for track in self.tracks: for track in self.tracks:
if trackId == id(track): if trackId == id(track):
return track return track
raise ValueError(f"Track {trackId} not found. Current Tracks: {[id(tr) for tr in self.tracks]}") else:
#Previously this crashed with a ValueError. However, after a rare bug that a gui widget focussed out because a track was deleted and then tried to send its value to the engine we realize that this lookup can gracefully return None.
#Nothing will break: Functions that are not aware yet, that None is an option will crash when they try to access None as a track object. For this case we present the following logger error:
if not trackId in self._tracksFailedLookup:
logger.error(f"Track {trackId} not found. Current Tracks: {[id(tr) for tr in self.tracks]}")
self._tracksFailedLookup.append(trackId) #prevent multiple error messages for the same track in a row.
return None
#raise ValueError(f"Track {trackId} not found. Current Tracks: {[id(tr) for tr in self.tracks]}")
#Save / Load / Export #Save / Load / Export

Loading…
Cancel
Save