importtemplate.engine.api#we need direct access to the module to inject data in the provided structures. but we also need the functions directly. next line:
theParameter=[[(2,3)]]#This is a tuplet with only one fraction, triplet. It is for a notelist with only one note.
#theParameter = [ [(2,3), (4,5)], [(4,5), (1,2), (3,4)] ] #notelist of 2 with a double-nested tuplet for the first and a triple nested tuplet for the second note.
self._baseDuration=baseDuration#base value. Without dots, tuplets/times , overrides etc. this is the duration for all representations
self.tuplets=[]# a list of tuplets [(numerator, denominator), (,)...]. normal triplet is (2,3). Numerator is the level of the notehead. 2^n Each is one level of tuplets, arbitrary nesting depth. [2,3] for triplet # Tuplet multiplication is used for all representations and gets auto-merged on lilypond output to tuplet groups (if its in a chord). This is a list and not tuple() because json load will make it a list anyway.
#DEPRECTATED. WILL BREAK SAVE FILES. self.tuplets = [] # a list of tuplets [(numerator, denominator), (,)...]. normal triplet is (2,3). Numerator is the level of the notehead. 2^n Each is one level of tuplets, arbitrary nesting depth. [2,3] for triplet # Tuplet multiplication is used for all representations and gets auto-merged on lilypond output to tuplet groups (if its in a chord). This is a list and not tuple() because json load will make it a list anyway.
self.tuplet=None#a single tuple (numerator, denominator). There is no nesting in Laborejo. Numerator is the level of the notehead. 2^n Each is one level of tuplets, arbitrary nesting depth. [2,3] for triplet # Tuplet multiplication is used for all representations and gets auto-merged on lilypond output to tuplet groups (if its in a chord).
self.dots=0#number of dots.
self.durationKeyword=D_DEFAULT
#The offsets shift the start and ending to the left and right or rather: earlier and later. positive values mean later, negative values mean earlier.
@ -383,12 +384,12 @@ class Duration(object):
new.dots=2
elifcompleteDuration*3/2indurList:#triplets are so a common we take care of them instead of brute forcing with fractions in the else branch
new=cls(guessedBase)
new.tuplets=[(2,3)]
new.tuplet=(2,3)
else:#tuplet. That means the value is below a standard "notehead" duration. We need to find how much much lower.
new=cls(guessedBase)
#ratio = completeDuration / guessedBase #0.666~ for triplet
newRatio=Fraction(int(completeDuration),guessedBase).limit_denominator(100000)#protects 6 or 7 decimal positions
defbuild(step,alteration):#generate a Scheme pair for Lilyponds GUILE interpreter
ifalteration==-10:
return"("+str(step)+" . ,"+"FLAT)"
@ -2292,7 +2333,7 @@ class Clef(Item):
#Thats it. A GUI does not need to know anything about the clef except its look because we already deliever note pitches as dots on lines, calculated with the clef.
}
def_lilypond(self):
def_lilypond(self,carryLilypondRanges):
return"\\clef \"{}\"".format(self.clefString)
classTimeSignature(Item):#Deprecated since 1750
@ -2415,7 +2456,7 @@ class MetricalInstruction(Item):
raiseRuntimeError("This metrical instruction should have a lilypond-override")
@ -2706,8 +2747,8 @@ class InstrumentChange(Item):
"UIstring":"{}[pr{}{}{}]".format(_nameStr,self.program,_msbStr,_lsbStr),#this is for a UI, possibly a text UI, maybe for simple items of a GUI. Make it as short and unambigious as possible.
}
def_lilypond(self):
"""called by block.lilypond(), returns a string.
def_lilypond(self,carryLilypondRanges):
"""called by block.lilypond(carryLilypondRanges), returns a string.
Don't create white-spaces yourself, this is done by the structures.
"UIstring":f"{self.text}(ch{self.value+1})"ifself.textelsef"ch{self.value+1}",#this is for a UI, possibly a text UI, maybe for simple items of a GUI. Make it as short and unambigious as possible.
}
def_lilypond(self):
"""called by block.lilypond(), returns a string.
def_lilypond(self,carryLilypondRanges):
"""called by block.lilypond(carryLilypondRanges), returns a string.
Don't create white-spaces yourself, this is done by the structures.
@ -39,14 +39,14 @@ from .lilypond import fromTemplate
classData(template.engine.sequencer.Score):
def__init__(self,parentSession):
super().__init__(parentSession)
super().__init__(parentSession)
self.tracks=[Track(parentData=self)]#this is the track order and all visible tracks. For getting a specific track use Track.allTracks with a track id.
self.hiddenTracks={}#track-instance:original Position. The value can exist multiple times. These still create a playback representation but are read-only and do not react to editing or GUI requests because you can't access them except through Track.allTrack (which is exactly the same for deleted tracks). Hidden tracks are saved though, deleted ones not.
self.hiddenTracks={}#track-instance:original Position. The value can exist multiple times. These still create a playback representation but are read-only and do not react to editing or GUI requests because you can't access them except through Track.allTrack (which is exactly the same for deleted tracks). Hidden tracks are saved though, deleted ones not.
self.tempoTrack=TempoTrack(parentData=self)#The tempoTrack is a Laborejo class. TempoMap is a template.sequencer class that is used by TempoTrack internally
#Metadata has only strings as keys, even the numbers.
self.currentMetronomeTrack=self.tracks[0]#A Laborejo Track, indepedent of currentTrack. The metronome is in self.metronome, set by the template Score.
self._processAfterInit()
def_processAfterInit(self):
@ -56,12 +56,12 @@ class Data(template.engine.sequencer.Score):
self.copyObjectsBuffer=[]#for copy and paste. obviously empty after load file. Also not saved.
self.cachedTrackDurations={}#updated after every track export
#track.asMetronomeData is a generated value from staticExport. Not available yet. needs to be done in api.startEngine #self.metronome.generate(data=self.currentMetronomeTrack.asMetronomeData, label=self.currentMetronomeTrack.name)
defduration(self):
"""Return the duration of the whole score, in ticks"""
#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.
#TODO: Measure before trying to improve performance.
result=[]
fortrackinself.tracks:
result.append(track.duration())
@ -78,7 +78,7 @@ class Data(template.engine.sequencer.Score):
t=self.selectionExport()#is there a selection at all?
t=self.selectionExport()#is there a selection at all?
ifnott:
return[False,None,None,[],[]]#mimickes the true result. the api can use this to decide to do nothing instead of processing the selection
else:
@ -451,7 +451,7 @@ class Data(template.engine.sequencer.Score):
#listOfChangedTrackIds = set([id(track) for track in tracksWithSelectedItems]) #this is not the same as tracksWithSelectedItems. It gets more ids than the initial ones here. A content-linked note outside a selected track will still be affected. We start with the trackIds we already know.
listOfChangedTrackIds=set()
finalResult=[selectionValid,topLeft,bottomRight,listOfChangedTrackIds]#listOfChangedTrackIds is mutable. It will change in the loop below
firstRound=True
fortrackintracksWithSelectedItems:
asserttrackinself.tracks,track#selecting from hidden or deleted tracks is impossible
@ -469,7 +469,7 @@ class Data(template.engine.sequencer.Score):
ifr==1:
# the item we want is already left of the cursor. So we want the previous item.
iftrack.state.tickindex<=endTick:#yes, it is correct that the state in the parameter is ahead by one position. Why is it that way? Because everything that matters, like new dynamics will only be parsed afterwards. The trackState is always correct except for the tickindex when exporting after parsing. Thats why exportObject sometimes substracts its own duration for finding its starting tick.
assertself.currentTrack().state.tickindex==finalTickIndex#this is not enough to pinpoint the real location.
#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
try:#overwrite? #TODO: What is that? assert with AssertionError pass?
@ -637,7 +638,7 @@ class Data(template.engine.sequencer.Score):
"""Mortals, Hear The Prophecy: Delete Selection is notoriously tricky. It was hard in Laborejo 1 and it is still hard
@ -707,7 +708,7 @@ class Data(template.engine.sequencer.Score):
#assert curTrack.state.tickindex == topLeftCursor["tickindex"] We cannot be sure of this. The selection could have started at a content-linked item that got deleted and the tickindex has no item anymore
#return [id(track) for track in self.tracks] #TODO: only for debug reasons. the GUI will draw everything with this! Either this function or the api has to figure out the tracks that really changed. we could do a len check of the blocks data.
returnlistOfChangedTrackIds
defgetBlockAndItemOrder(self):
@ -852,7 +853,7 @@ class Data(template.engine.sequencer.Score):
#what = Track.allTracks.values() #this includes deleted tracks
else:
what=self.tracks
fortrackinwhat:
forblockintrack.blocks:
foriteminblock.data:
@ -968,7 +969,7 @@ class Data(template.engine.sequencer.Score):
startBlock=track.currentBlock()#this is the only unique thing we can rely on.
#startBlock will get deleted, but we don't remove the blocks as parent because we need them for undo
#They will be finally cleared on save.
#They will be finally cleared on save.
#NO. This works for splitting, but not for undo. for item in startBlock.data:
# item.parentBlocks.remove(startBlock)
@ -1019,17 +1020,17 @@ class Data(template.engine.sequencer.Score):
self.setFlag(QtWidgets.QGraphicsItem.ItemHasNoContents,True) #only child items. Without this we get notImplementedError: QGraphicsItem.paint() is abstract and must be overridden
#self.setFlag(QtWidgets.QGraphicsItem.ItemHasNoContents, True) #only child items. Without this we get notImplementedError: QGraphicsItem.paint() is abstract and must be overridden
@ -294,7 +280,7 @@ class GuiTrack(QtWidgets.QGraphicsItem):
bgItem.setZValue(-10)#This is the z value within GuiTrack
bgItem.setEnabled(False)
transparentBlockHandle=GuiBlockHandle(self,block,0,-2*constantsAndConfigs.stafflineGap,block["completeDuration"]/constantsAndConfigs.ticksToPixelRatio,h+4*constantsAndConfigs.stafflineGap)#x, y, w, h
transparentBlockHandle=GuiBlockHandle(self,block,0,-2*constantsAndConfigs.stafflineGap,block["completeDuration"]/constantsAndConfigs.ticksToPixelRatio,h-constantsAndConfigs.stafflineGap)#x, y, w, h
elifevent.button()==1:#a positional mouse left click in a note-track
elifself.parentView.mode()=="notation"and(not(tempBlockDragAndDroportempTrackDragAndDrop))andevent.button()==1:#a positional mouse left click in a note-track, but only it not drag-drop release.