@ -475,6 +475,7 @@ def set_measuresPerGroup(value):
def changeTrackName ( trackId , name ) :
""" The template gurantees a unique, sanitized name across tracks and groups """
track = session . data . trackById ( trackId )
if not track : return
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 " )
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 ) :
""" Expects " #rrggbb """
track = session . data . trackById ( trackId )
if not track : return
assert len ( colorInHex ) == 7 , colorInHex
session . history . register ( lambda trId = trackId , v = track . color : changeTrackColor ( trId , v ) , descriptionString = " Track Color " )
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. " )
return
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 " )
track . midiChannel = newChannel - 1
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
( if any ) """
track = session . data . trackById ( trackId )
if not track : return
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 . pattern . averageVelocity = track . pattern . averageVelocity
@ -551,6 +555,7 @@ def _reinsertDeletedTrack(track, trackIndex):
def deleteTrack ( trackId ) :
track = session . data . trackById ( trackId )
if not track : return
oldIndex = session . data . tracks . index ( track )
with session . history . sequence ( " Delete Track " ) :
setTrackGroup ( trackId , " " ) #has it's own undo
@ -562,8 +567,6 @@ def deleteTrack(trackId):
else :
session . history . register ( lambda tr = deletedTrack , pos = oldIndex : _reinsertDeletedTrack ( tr , pos ) , descriptionString = " Delete Track " )
print ( session . data . groups )
updatePlayback ( )
callbacks . _numberOfTracksChanged ( )
@ -574,6 +577,7 @@ def moveTrack(trackId, newIndex):
in session . data . tracks , so that jack metadata port order works , groups or not .
"""
track = session . data . trackById ( trackId )
if not track : return
oldIndex = session . data . tracks . index ( track )
if not oldIndex == newIndex :
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
track = session . data . trackById ( trackId )
if not track : return
session . history . register ( lambda tr = trackId , v = track . patternLengthMultiplicator : setTrackPatternLengthMultiplicator ( trackId , v ) , descriptionString = " Pattern Multiplier " )
track . patternLengthMultiplicator = newMultiplicator
track . pattern . buildExportCache ( )
@ -610,6 +615,7 @@ def setTrackGroup(trackId, groupName:str):
""" A not yet existing groupName will create that.
Set to empty string to create a standalone track """
track = session . data . trackById ( trackId )
if not track : return
groupName = ' ' . join ( ch for ch in groupName if ch . isalnum ( ) ) #sanitize
groupName = " " . join ( groupName . split ( ) ) #remove double spaces
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.
structure is a set of integers which we can copy with . copy ( ) """
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 )
@ -687,6 +694,7 @@ def _setTrackStructure(trackId, structure, undoMessage):
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 )
if not track : return
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
@ -698,6 +706,7 @@ def setSwitches(trackId, setOfPositions, newBool):
def setSwitch ( trackId , position , newBool ) :
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 " )
if newBool :
if position in track . structure : return
@ -712,6 +721,7 @@ def setSwitch(trackId, position, newBool):
def trackInvertSwitches ( 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 " )
"""
if track . structure :
@ -728,6 +738,7 @@ def trackInvertSwitches(trackId):
def trackOffAllSwitches ( 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 " )
track . structure = set ( )
track . buildTrack ( )
@ -736,6 +747,7 @@ def trackOffAllSwitches(trackId):
def trackOnAllSwitches ( 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 " )
track . structure = set ( i for i in range ( session . data . numberOfMeasures ) )
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.
whichPatternsAreScaleTransposed is a dicts of int : int which we can copy with . copy ( ) """
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 " )
@ -785,6 +798,7 @@ def _setSwitchesScaleTranspose(trackId, whichPatternsAreScaleTransposed):
def setSwitchScaleTranspose ( trackId , position , transpose ) :
""" Scale transposition is flipped. lower value means higher pitch """
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 " )
track . whichPatternsAreScaleTransposed [ position ] = transpose
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.
whichPatternsAreScaleTransposed is a dicts of int : int which we can copy with . copy ( ) """
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 " )
@ -808,6 +823,7 @@ def _setSwitchHalftoneTranspose(trackId, whichPatternsAreHalftoneTransposed):
def setSwitchHalftoneTranspose ( trackId , position , transpose ) :
""" Halftone transposition is not flipped. Higher value means higher pitch """
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 " )
track . whichPatternsAreHalftoneTransposed [ position ] = transpose
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 .
"""
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 )
@ -1037,6 +1054,7 @@ def setStep(trackId, stepExportDict):
This is also for velocity !
"""
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 " )
oldNote = track . pattern . stepByIndexAndPitch ( index = stepExportDict [ " index " ] , pitch = stepExportDict [ " pitch " ] )
if oldNote : #modify existing note
@ -1054,6 +1072,7 @@ def setStep(trackId, stepExportDict):
def removeStep ( trackId , index , pitch ) :
""" Reverse of setStep """
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 " )
oldNote = track . pattern . stepByIndexAndPitch ( index , pitch )
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.
Actual pitches don ' t matter. " " "
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 " )
track . pattern . scale = scale #tuple, or list if oversight in json loading :)
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.
It is saved to file """
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 " )
track . pattern . simpleNoteNames = simpleNoteNames #list of strings
callbacks . _trackMetaDataChanged ( track )
def transposeHalftoneSteps ( trackId , steps ) :
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 " )
track . pattern . scale = [ midipitch + steps for midipitch in track . pattern . scale ]
track . pattern . buildExportCache ( )
@ -1093,6 +1115,7 @@ def transposeHalftoneSteps(trackId, steps):
def patternInvertSteps ( 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 " )
track . pattern . invert ( )
track . pattern . buildExportCache ( )
@ -1102,6 +1125,7 @@ def patternInvertSteps(trackId):
def patternOnAllSteps ( 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 " )
track . pattern . fill ( )
track . pattern . buildExportCache ( )
@ -1111,6 +1135,7 @@ def patternOnAllSteps(trackId):
def patternOffAllSteps ( 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 " )
track . pattern . empty ( )
track . pattern . buildExportCache ( )
@ -1121,6 +1146,7 @@ def patternOffAllSteps(trackId):
def patternInvertRow ( trackId , pitchindex ) :
""" Pitchindex is the row """
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 " )
track . pattern . invertRow ( pitchindex )
track . pattern . buildExportCache ( )
@ -1132,6 +1158,7 @@ def patternClearRow(trackId, pitchindex):
""" Pitchindex is the row.
Index is the column """
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 " )
track . pattern . clearRow ( pitchindex )
track . pattern . buildExportCache ( )
@ -1143,6 +1170,7 @@ def patternRowRepeatFromStep(trackId, pitchindex, index):
""" Pitchindex is the row.
Index is the column """
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 " )
track . pattern . repeatFromStep ( pitchindex , index )
track . pattern . buildExportCache ( )
@ -1152,6 +1180,7 @@ def patternRowRepeatFromStep(trackId, pitchindex, index):
def patternRowChangeVelocity ( trackId , pitchindex , delta ) :
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 " )
for note in track . pattern . getRow ( pitchindex ) :
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 .
"""
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 " )
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 ) :
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 " )
for note in track . pattern . data :
new = note [ " velocity " ] + steps
@ -1272,6 +1303,7 @@ def resizePatternWithoutScale(trackId, steps):
return
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.
@ -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.
def noteOn ( trackId , row ) :
track = session . data . trackById ( trackId )
if not track : return
midipitch = track . pattern . scale [ row ]
cbox . send_midi_event ( 0x90 + track . midiChannel , midipitch , track . pattern . averageVelocity , output = track . cboxMidiOutAbstraction )
def noteOff ( trackId , row ) :
track = session . data . trackById ( trackId )
if not track : return
midipitch = track . pattern . scale [ row ]
cbox . send_midi_event ( 0x80 + track . midiChannel , midipitch , track . pattern . averageVelocity , output = track . cboxMidiOutAbstraction )