session.data.tempoMap.isTransportMaster=True#always true for Laborejo.
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.
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.
fortrIdinsession.data.listOfTrackIds():
fortrIdinsession.data.listOfTrackIds():
callbacks._updateTrack(trId)#create content: music items
callbacks._updateTrack(trId)#create content: music items
@ -302,6 +293,8 @@ def startEngine(nsmClient):
fortrackinsession.data.hiddenTracks:#After loading a file some tracks could be hidden. We want midi for them as well.
fortrackinsession.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.
track.staticRepresentation()#that generates the calfbox data as a side effect. discard the other data.
useCurrentTrackAsMetronome()
callbacks._setCursor()
callbacks._setCursor()
globallaborejoEngineStarted#makes for a convenient check. stepMidiInput uses it, which needs to know that the gui already started the api.
globallaborejoEngineStarted#makes for a convenient check. stepMidiInput uses it, which needs to know that the gui already started the api.
#startPos = session.data.where() #we cannot use where() since we don't know if a content linked block before our current position gets new items in the duplication process
#TODO: when duplicating with content links or at least inside a content link the selection gets lost completely. Maybe this cannot be avoided, but review that in the future.
copyItem.parentBlocks.add(new)#parentBlock was empty until now
assertnotcopyItem.parentBlocks#parentBlock was empty until now
copyItem.parentBlocks.add(new)
ifselfincopyItem.parentBlocks:#TODO: investigate
ifselfincopyItem.parentBlocks:#TODO: investigate
copyItem.parentBlocks.remove(self)
copyItem.parentBlocks.remove(self)
@ -301,7 +302,7 @@ class Block(object):
definsert(self,item):
definsert(self,item):
self.data.insert(self.localCursorIndex,item)#we do not need to check if appending or not. list.insert appends if the index is higher then len()
self.data.insert(self.localCursorIndex,item)#we do not need to check if appending or not. list.insert appends if the index is higher then len()
#self.localCursorIndex += 1 #we don't need to go right here because track.insert() is calling its own right() directly after insert, which triggers block.right()
#self.localCursorIndex += 1 #we don't need to go right here because track.insert() is calling its own right() directly after insert, which triggers block.right()
item.parentBlocks.add(self)
item.parentBlocks.add(self)
defdelete(self):
defdelete(self):
"""The commented out is the immediate garbage collector which
"""The commented out is the immediate garbage collector which
"""Move the oldItems parentBlocks to the new item"""
"""Move the oldItems parentBlocks to the new item"""
self.parentBlocks=oldItem.parentBlocks
#self.parentBlocks = oldItem.parentBlocks #TODO. We took that out when pasting after deleting a track and recreating failed. Why do we to copy the parentBlocks when a parentBlock is added during block.insert anyway? Wild guess: we don't.
@ -56,11 +56,12 @@ class Data(template.engine.sequencer.Score):
self.cursorWhenSelectionStarted=None#A cursor dict, from self.cursorExport(). Is None when there is no selection. Can be used for "if selection:" questions. Gets changed quite often.
self.cursorWhenSelectionStarted=None#A cursor dict, from self.cursorExport(). Is None when there is no selection. Can be used for "if selection:" questions. Gets changed quite often.
self.copyObjectsBuffer=[]#for copy and paste. obviously empty after load file. Also not saved.
self.copyObjectsBuffer=[]#for copy and paste. obviously empty after load file. Also not saved.
self.cachedTrackDurations={}#updated after every track export
self.cachedTrackDurations={}#updated after every track export
self.metronome=Metronome(parentData=self)#Purely dynamic structure. No save/load. No undo/redo
self.currentMetronomeTrack=None#A Laborejo Track, indepedent of currentTrack. The metronome is in self.metronome, set by the template Score.
defduration(self):
defduration(self):
"""Return the duration of the whole score, in ticks"""
"""Return the duration of the whole score, in ticks"""
#TODO: use cached duration? How often is this used?
#TODO: use cached duration? How often is this used? Pretty often. 3 Times for a single track note update.
#TODO: Measure before trying to improve performance.
result=[]
result=[]
fortrackinself.tracks:
fortrackinself.tracks:
result.append(track.duration())
result.append(track.duration())
@ -506,7 +507,7 @@ class Data(template.engine.sequencer.Score):
#TODO: check if the cursor is still in the correct position after delete selection
#TODO: check if the cursor is still in the correct position after delete selection
listOfChangedTrackIds=set()
listOfChangedTrackIds=set()
fornumber,trackinenumerate(workBuffer):
fornumber,trackinenumerate(workBuffer):
curTrack=self.currentTrack()
curTrack=self.currentTrack()
#Here comes an additional condition. Most of the time pastes are single-track. If so, or we simply are in the starting track, we want to paste at the actual position, not the tickindex. This circumvents problems with block boundaries and zero-duration items.
#Here comes an additional condition. Most of the time pastes are single-track. If so, or we simply are in the starting track, we want to paste at the actual position, not the tickindex. This circumvents problems with block boundaries and zero-duration items.
@ -597,8 +599,8 @@ class Data(template.engine.sequencer.Score):
foritemintrack:
foritemintrack:
newItem=item.copy()
newItem=item.copy()
curTrack.insert(newItem)#eventhough copyObjectsBuffer is already a copy of the original notes we don't want every paste to be the same instances.
curTrack.insert(newItem)#eventhough copyObjectsBuffer is already a copy of the original notes we don't want every paste to be the same instances.
forparentBlockinnewItem.parentBlocks:#TODO: just be naive and add them all for now. Performance can be improved later.
#newItem has now a parentBlock and a parentTrack. We add this parent track to the list of changed Tracks
listOfChangedTrackIds.update(newItem.parentTrackIds)#TODO: profiling. All items in a block have the same parentTrack. This is highly redundant BUT can be different for items which originated from block boundaries
ifnotnumber+1==len(workBuffer):#enumerate from 0, len from 1.
ifnotnumber+1==len(workBuffer):#enumerate from 0, len from 1.
#we have to prevent the track going down one too far in the last step and messing with the tick index though.
#we have to prevent the track going down one too far in the last step and messing with the tick index though.
self.trackDown()
self.trackDown()
@ -609,10 +611,13 @@ class Data(template.engine.sequencer.Score):
#Return to the item where pasting starting. Pasting counts as insert, and insert sticks to the item right of cursor.
#Therefore we go to the starting track and block, but not to the starting localCursorIndexInBlock. Instead we search for the specific item in a second step
self.goToItemInCurrentBlock(startItem)#we actually want to be at the same item where we started, not just the tick index which gets confused with block boundaries and zero duration items
self.goToItemInCurrentBlock(startItem)#we actually want to be at the same item where we started, not just the tick index which gets confused with block boundaries and zero duration items
try:#overwrite?
try:#overwrite? #TODO: What is that? assert with AssertionError pass?
allTracks=WeakValueDictionary()#key is the trackId, value is the weak reference to the Track. Deleted tracks (from the current session) and hidden tracks are in here as well.
allTracks=WeakValueDictionary()#key is the trackId, value is the weak reference to the Track. Deleted tracks (from the current session) and hidden tracks are in here as well.
#The instrument names are also handled by the trackState so that the cursor knows about instrument changes
#The instrument names are also handled by the trackState so that the cursor knows about instrument changes
self.initialInstrumentName=""#different than the track name. e.g. "Violin"
self.initialInstrumentName=""#different than the track name. e.g. "Violin"
self.initialShortInstrumentName=""# e.g. "vl"
self.initialShortInstrumentName=""# e.g. "vl"
self.asMetronomeData=None#This track as metronome version. Is always up to date through export.
self._processAfterInit()
self._processAfterInit()
@ -1060,10 +1062,10 @@ class Track(object):
#########
#########
#Metronome start
#Metronome start
#The metronome cannot be calculated by simply looking at metricalInstructions. We need to look at the barlines. Two instructions in a row at the same tick are wrong, but technically possible. These are not two full measures of metronome
#The metronome cannot be calculated by simply looking at metricalInstructions.
#TODO: metronome activate
#We need to look at the barlines. Two instructions in a row at the same tick are wrong,
#if self is self.parentData.currentMetronomeTrack:
#but technically possible. These are not two full measures of metronome
# self.parentData.metronome.generate(((pos, m.isMetrical, m.treeOfInstructions) for pos, m in barlines.items()))
self.liveMidiWaitingForNoteOff={}#all unfinished midi notes (key is currently held down). We use one for all tracks because we have a callback in the cursor update function that iterates over all live notes. We don't want that to iterate over all tracks just to find the current live notes.
self.duringTrackDragAndDrop=None#switched to a QGraphicsItem (e.g. GuiTrack) while a track is moved around by the mouse
self.duringTrackDragAndDrop=None#switched to a QGraphicsItem (e.g. GuiTrack) while a track is moved around by the mouse
self.duringBlockDragAndDrop=None#switched to a QGraphicsItem (e.g. GuiTrack) while a block is moved around by the mouse
self.duringBlockDragAndDrop=None#switched to a QGraphicsItem (e.g. GuiTrack) while a block is moved around by the mouse
@ -584,11 +572,6 @@ class GuiScore(QtWidgets.QGraphicsScene):