@ -39,7 +39,7 @@ from template.helper import flatList, EndlessGenerator
from . import items
from . import lilypond
from . tempotrack import TempoItem , TempoTrack
from . ccsubtrack import GraphItem
from . ccsubtrack import GraphItem , GraphTrackCC
from . track import Track
apiModuleSelfReference = sys . modules [ __name__ ]
@ -143,22 +143,24 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
for extra block view or background colors . """
#For simplistic reasons we just update all of this CC in all tracks.
#Makes things much simple with content links across tracks.
#Makes things much more simple with content links across tracks.
#We will see how that turns out performance wise.
#TODO: This reeks of O[n^n]!!!
#We keep the trId parameter but don't use it.
for track in session . data . tracks :
if cc in track . ccGraphTracks :
graphTrackCC = track . ccGraphTracks [ cc ]
trId = id ( track )
ex = graphTrackCC . staticRepresentation ( )
for func in self . updateGraphTrackCC :
func ( trId , cc , ex )
#for track in session.data.tracks:
track = session . data . trackById ( trId )
if cc in track . ccGraphTracks :
graphTrackCC = track . ccGraphTracks [ cc ]
trId = id ( track )
ex = graphTrackCC . staticRepresentation ( )
for func in self . updateGraphTrackCC :
func ( trId , cc , ex )
ex = graphTrackCC . staticGraphBlocksRepresentation ( )
for func in self . updateGraphBlockTrack :
func ( trId , cc , ex )
ex = graphTrackCC . staticGraphBlocksRepresentation ( )
for func in self . updateGraphBlockTrack :
func ( trId , cc , ex )
def _updateSingleTrackAllCC ( self , trId ) :
""" Used on CC-Channels change. No own callback list. """
@ -167,7 +169,6 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
for func in self . updateGraphTrackCC :
func ( trId , cc , ex )
def _graphCCTracksChanged ( self , trId ) :
""" CC graphs of a track deleted or added """
#TODO: moved?
@ -286,16 +287,22 @@ def startEngine(nsmClient):
#Send initial Data etc.
session . data . tempoMap . isTransportMaster = True #always true for Laborejo.
callbacks . _tracksChanged ( ) # This creates the frontend/GUI tracks with access through track ids. From now on we can send the GUI a trId and it knows which track needs change.
for track in session . data . hiddenTracks : #After loading a file some tracks could be hidden. We want midi for them as well.
track . staticRepresentation ( ) #that generates the calfbox data as a side effect. discard the other data.
#because we do a simplicifaction in _updateGraphTrackCC that calls all tracks for one CC (for content links) we need to build the structure first, only during file load.
for trId in session . data . listOfTrackIds ( ) :
callbacks . _graphCCTracksChanged ( trId ) #create structure: all CC graphs, accessed by CC number (0-127)
for trId in session . data . listOfTrackIds ( ) :
callbacks . _updateTrack ( trId ) #create content: music items
callbacks . _updateTempoTrack ( ) # does everything at once
callbacks . _graphCCTracksChanged ( trId ) #create structure: all CC graphs, accessed by CC number (0-127)
#done above. Special situation at file load. callbacks._graphCCTracksChanged(trId) #create structure: all CC graphs, accessed by CC number (0-127)
for cc in session . data . trackById ( trId ) . listOfCCsInThisTrack ( ) :
callbacks . _updateGraphTrackCC ( trId , cc ) #create content: CC points. user points and interpolated points.
for track in session . data . hiddenTracks : #After loading a file some tracks could be hidden. We want midi for them as well.
track . staticRepresentation ( ) #that generates the calfbox data as a side effect. discard the other data.
setMetronome ( session . data . currentMetronomeTrack . asMetronomeData , label = session . data . currentMetronomeTrack . name ) #track.asMetronomeData is generated in staticRepresentation #template api. has callbacks
callbacks . _setCursor ( )
@ -1805,14 +1812,35 @@ def getListOfGraphInterpolationTypesAsStrings():
return [ " linear " , " standalone " ]
#CC user points
#TODO: no undo here for various reasons. Many functions auto-delete hidden or overlapping cc-graph items with no way of currently remembering them.
#history.py say "Better no history than a wrong history." so we clear it.
#Control Change Subtracks
def _lazyCCUndoRedo ( trId : int , cc : int , data , description : str ) : #For items and blocks, not for tracks.
"""
Lazy Undo for CCs deals with one CC in one Track at a time . No blocks are involved .
The CC tracks are not full of data . We can afford it to use the save / load system to
store complete states instead of incremental changes .
"""
track = session . data . trackById ( trId )
oldData = track . ccGraphTracks [ cc ] . serialize ( )
track . ccGraphTracks [ cc ] = GraphTrackCC . instanceFromSerializedData ( data , parentTrack = track )
session . history . register ( lambda : _lazyCCUndoRedo ( trId , cc , oldData , description ) , descriptionString = description )
callbacks . _historyChanged ( )
callbacks . _updateGraphTrackCC ( trId , cc )
def _CCUndoCreater ( trId : int , cc : int , description : str = " " ) : #For items and blocks, not for tracks.
""" Can be used whenever saving the old state and actually registering undo does not depend
on the success of a function . In other words : if you could write all undo related lines
in a row , use this instead . """
track = session . data . trackById ( trId )
oldData = track . ccGraphTracks [ cc ] . serialize ( )
session . history . register ( lambda : _lazyCCUndoRedo ( trId , cc , oldData , description ) , descriptionString = description )
callbacks . _historyChanged ( )
##CC Tracks
def newGraphTrackCC ( trId , cc ) :
""" add a new CC Path for CC to the given Track.
Do nothing if already existent . """
Do nothing if already existent . """
track = session . data . trackById ( trId )
track . newGraphTrackCC ( cc )
session . history . clear ( )
@ -1833,76 +1861,47 @@ def deleteGraphTrackCC(trId, cc):
session . history . clear ( )
callbacks . _graphCCTracksChanged ( trId ) #this should delete any GUI cc track
def addGraphItem ( blockId , positionInTicksRelativeToBlock , newCCValue ) :
""" blockId includes the track as well as the CC """
graphItem = GraphItem ( newCCValue )
_addExistingGraphItem ( blockId , positionInTicksRelativeToBlock , graphItem )
def _addExistingGraphItem ( blockId , positionInTicksRelativeToBlock , graphItem ) :
""" blockId includes the track as well as the CC """
trId , cc , graphBlock = session . data . graphBlockById ( blockId )
session . history . clear ( )
graphBlock . insert ( graphItem , positionInTicksRelativeToBlock )
callbacks . _updateGraphTrackCC ( trId , cc )
def removeGraphItem ( graphItemId ) :
trId , cc , graphBlock , graphItem = session . data . graphItemById ( graphItemId )
tickPositionRelativeToBlockStart = graphBlock . find ( graphItem )
graphBlock . remove ( tickPositionRelativeToBlockStart )
session . history . clear ( )
callbacks . _updateGraphTrackCC ( trId , cc )
def changeGraphItem ( graphItemId , moveInTicks , newCCValue ) :
trId , cc , graphBlock , graphItem = session . data . graphItemById ( graphItemId )
session . history . clear ( )
currentTickPositionRelativeToBlock = graphBlock . find ( graphItem )
graphBlock . move ( currentTickPositionRelativeToBlock , currentTickPositionRelativeToBlock + moveInTicks )
graphItem . ccStart = newCCValue
callbacks . _updateGraphTrackCC ( trId , cc )
def changeGraphItemInterpolation ( graphItemId , graphType ) :
""" graphType is " linear " or " standalone " """
trId , cc , graphBlock , graphItem = session . data . graphItemById ( graphItemId )
session . history . clear ( )
graphItem . graphType = graphType
callbacks . _updateGraphTrackCC ( trId , cc )
## CC Blocks
def changeGraphBlockDuration ( graphBlockId , newDurationInTicks ) :
trId , cc , graphBlock = session . data . graphBlockById ( graphBlockId )
session . history . clear ( )
trId , cc , graphBlock = session . data . graphBlockById ( graphBlockId )
_CCUndoCreater ( trId , cc , " CC Block Duration " )
graphBlock . duration = newDurationInTicks
callbacks . _updateGraphTrackCC ( trId , cc )
def appendGraphBlock ( trId , cc ) :
""" Append a small duration block to the current cc track """
ccTrack = session . data . trackById ( trId ) . ccGraphTracks [ cc ]
newBlock = ccTrack . appendGraphBlock ( )
session . history . clear ( )
_CCUndoCreater ( trId , cc , " Append CC Block " )
newBlock = ccTrack . appendGraphBlock ( )
callbacks . _updateGraphTrackCC ( trId , cc )
def rearrangeCCBlocks ( trId , cc , listOfBlockIds ) :
ccTrack = session . data . trackById ( trId ) . ccGraphTracks [ cc ]
session . history . clear ( )
_CCUndoCreater ( trId , cc , " Move CC Block " )
ccTrack . rearrangeBlocks ( listOfBlockIds )
callbacks . _updateGraphTrackCC ( trId , cc )
def moveCCBlockToOtherTrack ( graphBlockId , newTrackId , listOfBlockIdsForNewTrack ) :
""" Modified copy of api.moveBlockToOtherTrack.
This only moves to the same CC value . not from volume to modwheel . """
trId , cc , graphBlock = session . data . graphBlockById ( graphBlockId )
oldccTrack = session . data . trackById ( trId ) . ccGraphTracks [ cc ]
newGraphTrack = session . data . trackById ( newTrackId ) . ccGraphTracks [ cc ] #we assume this track has this CC already activated.
if len ( oldccTrack . blocks ) == 1 :
return False #it is not possible to move the last block.
return False #it is not possible to move the only block.
with session . history . sequence ( " Move CC to other track " ) :
_CCUndoCreater ( trId , cc , " MoveToOther Old " )
_CCUndoCreater ( newTrackId , cc , " MoveToOther New " )
callbacks . _historyChanged ( )
newGraphTrack . appendExistingGraphBlock ( graphBlock )
newGraphTrack . rearrangeBlocks ( listOfBlockIdsForNewTrack )
oldccTrack . deleteBlock ( graphBlock ) #It is important that we delete the block at exactly this point in time, not ealier. Otherwise the reference for undo will go away.
graphBlock . parentGraphTrack = newGraphTrack
session . history . clear ( )
callbacks . _updateGraphTrackCC ( trId , cc )
callbacks . _updateGraphTrackCC ( newTrackId , cc )
@ -1978,15 +1977,63 @@ def mergeWithNextGraphBlock(graphBlockId):
session . history . clear ( ) #TODO: merging deletes hidden items in the first block without remembering them for undo
callbacks . _updateGraphTrackCC ( trId , cc )
##CC Blocks and User Points
def addGraphItem ( blockId , positionInTicksRelativeToBlock , newCCValue ) :
""" blockId includes the track as well as the CC """
trId , cc , graphBlock = session . data . graphBlockById ( blockId )
graphItem = GraphItem ( newCCValue )
_addExistingGraphItem ( blockId , positionInTicksRelativeToBlock , graphItem )
def _addExistingGraphItem ( blockId , positionInTicksRelativeToBlock , graphItem ) :
""" blockId includes the track as well as the CC """
trId , cc , graphBlock = session . data . graphBlockById ( blockId )
_CCUndoCreater ( trId , cc , " Add CC Point " )
graphBlock . insert ( graphItem , positionInTicksRelativeToBlock )
callbacks . _updateGraphTrackCC ( trId , cc )
def removeGraphItem ( graphItemId ) :
trId , cc , graphBlock , graphItem = session . data . graphItemById ( graphItemId )
_CCUndoCreater ( trId , cc , " Remove CC Point " )
tickPositionRelativeToBlockStart = graphBlock . find ( graphItem )
graphBlock . remove ( tickPositionRelativeToBlockStart )
callbacks . _updateGraphTrackCC ( trId , cc )
def changeGraphItem ( graphItemId , moveInTicks , newCCValue ) :
trId , cc , graphBlock , graphItem = session . data . graphItemById ( graphItemId )
_CCUndoCreater ( trId , cc , " Change CC Point " )
currentTickPositionRelativeToBlock = graphBlock . find ( graphItem )
graphBlock . move ( currentTickPositionRelativeToBlock , currentTickPositionRelativeToBlock + moveInTicks )
graphItem . ccStart = newCCValue
callbacks . _updateGraphTrackCC ( trId , cc )
def changeGraphItemInterpolation ( graphItemId , graphType ) :
""" graphType is " linear " or " standalone " """
trId , cc , graphBlock , graphItem = session . data . graphItemById ( graphItemId )
_CCUndoCreater ( trId , cc , " CC Point Interpolation " )
graphItem . graphType = graphType
callbacks . _updateGraphTrackCC ( trId , cc )
#Tempo Track
def _changeTempoTrackBlockAndItemOrder ( listWithBlocksData ) :
""" A helper function for that makes it possible to undo/redo properly. It registers
itself with complementary data as undo / redo . """
orderBeforeInsert = session . data . tempoTrack . putBlockAndItemOrder ( listWithBlocksData )
session . history . register ( lambda o = orderBeforeInsert : _changeTempoTrackBlockAndItemOrder ( o ) , descriptionString = " Tempo Change " )
def _lazyTempoTrackUndoRedo ( new , description : str ) :
""" The tempo track is not full of data. We can afford it to use the save/load system to
store complete states instead of incremental changes . """
old = session . data . tempoTrack . serialize ( )
session . data . tempoTrack = TempoTrack . instanceFromSerializedData ( new , parentData = session . data )
session . history . register ( lambda d = old : _lazyTempoTrackUndoRedo ( d , description ) , descriptionString = description )
callbacks . _historyChanged ( )
callbacks . _updateTempoTrack ( )
callbacks . _historyChanged ( )
def _tempoTrackUndoCreater ( description : str ) :
""" Can be used whenever saving the old state and actually registering undo does not depend
on the success of a function . In other words : if you could write all undo related lines
in a row , use this instead . """
old = session . data . tempoTrack . serialize ( )
session . history . register ( lambda d = old : _lazyTempoTrackUndoRedo ( d , description ) , descriptionString = description )
callbacks . _historyChanged ( )
def addTempoItem ( blockId , positionInTicksRelativeToBlock , unitsPerMinute , referenceTicks , graphType = " standalone " ) :
""" blockId includes the track as well as the CC """
@ -1996,23 +2043,18 @@ def addTempoItem(blockId, positionInTicksRelativeToBlock, unitsPerMinute, refere
return tempoItem
def _addExistingTempoItem ( blockId , positionInTicksRelativeToBlock , tempoItem ) :
orderBeforeInsert = session . data . tempoTrack . getBlockAndItemOrder ( )
_tempoTrackUndoCreater ( " Add Tempo Change " )
tempoBlock = session . data . tempoTrack . tempoBlockById ( blockId )
tempoBlock . insert ( tempoItem , positionInTicksRelativeToBlock )
session . history . register ( lambda o = orderBeforeInsert : _changeTempoTrackBlockAndItemOrder ( o ) , descriptionString = " Tempo Change " )
callbacks . _updateTempoTrack ( )
callbacks . _historyChanged ( )
tempoBlock . insert ( tempoItem , positionInTicksRelativeToBlock )
callbacks . _updateTempoTrack ( )
def removeTempoItem ( tempoItemId ) :
orderBeforeInsert = session . data . tempoTrack . getBlockAndItemOrder ( )
_tempoTrackUndoCreater ( " Delete Tempo Change " )
tempoBlock , tempoItem = session . data . tempoTrack . tempoItemById ( tempoItemId )
tickPositionRelativeToBlockStart = tempoBlock . find ( tempoItem )
tempoBlock . remove ( tickPositionRelativeToBlockStart )
session . history . register ( lambda o = orderBeforeInsert : _changeTempoTrackBlockAndItemOrder ( o ) , descriptionString = " Delete Tempo Change " )
tempoBlock . remove ( tickPositionRelativeToBlockStart )
callbacks . _updateTempoTrack ( )
callbacks . _historyChanged ( )
def moveTempoItem ( tempoItemId , tickPositionAbsolute ) :
""" Figures out the target block automatically """
@ -2028,122 +2070,80 @@ def removeCurrentTempoItem():
removeTempoItem ( id ( tempoItem ) ) #undo and callback
def changeTempoBlockDuration ( tempoBlockId , newDurationInTicks ) :
""" Can do circular undo/redo """
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
oldDuration = tempoBlock . duration
session . history . register ( lambda i = tempoBlockId , o = oldDuration : changeTempoBlockDuration ( i , o ) , descriptionString = " Tempo Block Duration " )
callbacks . _historyChanged ( )
_tempoTrackUndoCreater ( " Tempo Block Duration " )
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
tempoBlock . duration = newDurationInTicks
callbacks . _updateTempoTrack ( )
def rearrangeTempoBlocks ( listOfBlockIds ) :
""" Can do circular undo/redo. Is used by appendTempoBlock and others """
oldOrder = session . data . tempoTrack . asListOfBlockIds ( )
session . history . register ( lambda o = oldOrder : rearrangeTempoBlocks ( o ) , descriptionString = " Move Tempo Block " )
callbacks . _historyChanged ( )
_tempoTrackUndoCreater ( " Move Tempo Block " )
session . data . tempoTrack . rearrangeBlocks ( listOfBlockIds )
callbacks . _updateTempoTrack ( )
def appendTempoBlock ( ) :
oldOrder = session . data . tempoTrack . asListOfBlockIds ( )
session . history . register ( lambda o = oldOrder : rearrangeTempoBlocks ( o ) , descriptionString = " Append Tempo Block " )
callbacks . _historyChanged ( )
_tempoTrackUndoCreater ( " Append Tempo Block " )
newBlock = session . data . tempoTrack . appendTempoBlock ( )
callbacks . _updateTempoTrack ( )
def duplicateTempoBlock ( tempoBlockId , times = 1 ) :
oldOrder = session . data . tempoTrack . asListOfBlockIds ( )
session . history . register ( lambda o = oldOrder : rearrangeTempoBlocks ( o ) , descriptionString = " Duplicate Tempo Block " )
callbacks . _historyChanged ( )
_tempoTrackUndoCreater ( " Duplicate Tempo Block " )
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
for i in range ( times ) :
session . data . tempoTrack . duplicateBlock ( tempoBlock )
callbacks . _updateTempoTrack ( )
def duplicateContentLinkTempoBlock ( tempoBlockId , times = 1 ) :
""" This is also create content link """
oldOrder = session . data . tempoTrack . asListOfBlockIds ( )
session . history . register ( lambda o = oldOrder : rearrangeTempoBlocks ( o ) , descriptionString = " Duplicate Content Link Tempo Block " )
callbacks . _historyChanged ( )
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
""" This is also create content link """
_tempoTrackUndoCreater ( " Content Link Tempo Block " )
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
for i in range ( times ) :
session . data . tempoTrack . duplicateContentLinkBlock ( tempoBlock )
callbacks . _updateTempoTrack ( )
def changeTempoBlock ( tempoBlockId , newParametersDict ) :
""" Can do circular undo/redo """
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
oldParameters = tempoBlock . getDataAsDict ( )
session . history . register ( lambda i = tempoBlockId , o = oldParameters : changeTempoBlock ( i , o ) , descriptionString = " Change Tempo Block " )
callbacks . _historyChanged ( )
_tempoTrackUndoCreater ( " Change Tempo Block " )
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
tempoBlock . putDataFromDict ( newParametersDict )
callbacks . _updateTempoTrack ( )
def unlinkTempoBlock ( tempoBlockId ) :
def unlinkTempoBlock ( tempoBlockId ) :
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
if len ( tempoBlock . linkedContentBlocks ) == 1 :
return #This is not a content link block
newData , newLinkedContentBlocks , newDuration = tempoBlock . getUnlinkedData ( ) #does not set itself, just returns
_exchangeTempoBlockData ( tempoBlock , newData , newLinkedContentBlocks , newDuration ) #handles undo and callbacks
def _exchangeTempoBlockData ( tempoBlock , newData , newLinkedContentBlocks , newDuration ) :
oldData = tempoBlock . data . copy ( ) #shallow copy. otherwise we can't mix unlink with putBlockAndItemOrder
oldLinkedContentBlocks = tempoBlock . linkedContentBlocks
oldDuration = tempoBlock . _duration [ : ] #shallow copy.
_tempoTrackUndoCreater ( " Unlink Tempo Block " )
undofunction = lambda : _exchangeTempoBlockData ( tempoBlock , oldData , oldLinkedContentBlocks , oldDuration ) #flipped around
session . history . register ( undofunction , descriptionString = " Unlink Tempo Block " )
newData , newLinkedContentBlocks , newDuration = tempoBlock . getUnlinkedData ( ) #does not set itself, just returns
tempoBlock . data = newData
tempoBlock . linkedContentBlocks . remove ( tempoBlock ) #the block still exists, so WeakRef will not remove it.
tempoBlock . linkedContentBlocks = newLinkedContentBlocks
tempoBlock . _duration = newDuration #mutable list of length 1
for bl in tempoBlock . linkedContentBlocks :
bl . data = newData
bl . linkedContentBlocks = newLinkedContentBlocks
bl . _duration = newDuration #mutable list of length 1
callbacks . _historyChanged ( )
callbacks . _updateTempoTrack ( )
def _lazyTempoTrackUndoRedo ( new ) :
old = session . data . tempoTrack . serialize ( )
session . data . tempoTrack = TempoTrack . instanceFromSerializedData ( new , parentData = session . data )
session . history . register ( lambda d = old : _lazyTempoTrackUndoRedo ( d ) , descriptionString = " Change Tempo Track " )
callbacks . _historyChanged ( )
callbacks . _updateTempoTrack ( )
def splitTempoBlock ( tempoBlockId , positionInTicksRelativeToBlock : int ) :
""" tick position is relative to block start """
old = session . data . tempoTrack . serialize ( )
success = session . data . tempoTrack . splitTempoBlock ( tempoBlockId , positionInTicksRelativeToBlock )
if success :
session . history . register ( lambda d = old : _lazyTempoTrackUndoRedo ( d ) , descriptionString = " Split Tempo Block " )
session . history . register ( lambda d = old , desc = " Split Tempo Block " : _lazyTempoTrackUndoRedo ( d , desc ) , descriptionString = " Split Tempo Block " )
callbacks . _updateTempoTrack ( )
def mergeWithNextTempoBlock ( tempoBlockId ) :
old = session . data . tempoTrack . serialize ( )
positionForSplit = session . data . tempoTrack . mergeWithNextTempoBlock ( tempoBlockId )
if positionForSplit :
session . history . register ( lambda d = old : _lazyTempoTrackUndoRedo ( d ) , descriptionString = " Join Tempo Block " )
session . history . register ( lambda d = old , desc = " Join Tempo Block " : _lazyTempoTrackUndoRedo ( d , desc ) , descriptionString = " Join Tempo Block " )
callbacks . _historyChanged ( )
callbacks . _updateTempoTrack ( )
def deleteTempoBlock ( tempoBlockId ) :
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
oldLayout = session . data . tempoTrack . asListOfBlockIds ( )
old = session . data . tempoTrack . serialize ( )
tempoBlock = session . data . tempoTrack . tempoBlockById ( tempoBlockId )
deletedBlock = session . data . tempoTrack . deleteBlock ( tempoBlock )
if deletedBlock :
session . history . cl ea r( )
session . history . register ( lambda d = old , d esc = " Delete Tempo Block " : _lazyTempoT rackUndoRedo ( d , desc ) , descriptionString = " Delete Tempo Block " )
callbacks . _updateTempoTrack ( )
def insertTempoItemAtAbsolutePosition ( tickPositionAbsolute , unitsPerMinute , referenceTicks , graphType ) :
@ -2177,9 +2177,7 @@ def insertTempoChangeDuringDuration(percentageUnitsPerMinuteAsFloat):
#originalUnitsPerMinute, originalReferenceTicks = session.data.tempoTrack.tempoAtTickPosition(startTick)
newUnitsPerMinute = tempoItem . unitsPerMinute * percentageUnitsPerMinuteAsFloat
insertTempoItemAtAbsolutePosition ( startTick , newUnitsPerMinute , tempoItem . referenceTicks , graphType = " standalone " )
insertTempoItemAtAbsolutePosition ( endTick , tempoItem . unitsPerMinute , tempoItem . referenceTicks , graphType = " standalone " )
session . history . clear ( ) #TODO: This registers two times undo
insertTempoItemAtAbsolutePosition ( endTick , tempoItem . unitsPerMinute , tempoItem . referenceTicks , graphType = " standalone " )
def currentTempoScalingFactor ( ) :
return session . data . tempoTrack . factor