ifendAbsoluteTempoFromTheNextItem==startTempo:#there is no interpolation. It is just one value, the one of the current TempoItem. Basically the same as graphType == "standalone"
m=(tickEnd-tickStart)/(endAbsoluteTempoFromTheNextItem-startTempo)#we need to calculate this after making sure that endAbsoluteTempoFromTheNextItem and start are not the same. Else we get /0
#From here on: We actually need interpolation. Let the math begin.
iteratorList=list(range(startTempo,endAbsoluteTempoFromTheNextItem))#20 to 70 results in 20....69
else:#downward slope
iteratorList=list(range(endAbsoluteTempoFromTheNextItem+1,startTempo+1))#70 to 20 results ins 70...21
iteratorList.reverse()
#Calculate at which tick a given tempoValue will happen. value = m * (i - y1)
fortempoValueiniteratorList:
result.append((tempoValue,tickStart+int(m*(tempoValue-startTempo))))# int(m * (tempoValue - startTempo)) results in 0 for the first item (set by the user). so the first item is just tickStart. that does not mean tickStart for the first item is 0. We just normalize to tickStart as virtual 0 here.
assertresult
assertresult[0][1]==tickStart
result[0]=((result[0][0],self.unitsPerMinute,self.referenceTicks),result[0][1])#this will be used later in the track static generation to inform the receiver what the original tempo data was.
#TODO: this is nearly the same as the GraphBlock method with the same name. During development it made sense to write to different functions. But this can be simplified by calling super().instanceFromSerializedData
assertcls.__name__==serializedObject["class"]
self=cls.__new__(cls)
ifserializedObject["data"]isNone:
firstBlock=TempoBlock.firstBlockWithNewContentDuringDeserializeToObject[serializedObject["contentLinkGroup"]]#first block with the same contentGroup. This is the one with the real data.
self.data=firstBlock.data
self._duration=firstBlock._duration
self.linkedContentBlocks=firstBlock.linkedContentBlocks#add self to this is in _secondInit
else:#Standalone or First occurence of a content linked block
self._duration=[int(serializedObject["duration"])]#this is just an int, minimumBlockInTicks or so. Not Item.Duration(). For save and load we drop the list as mutable value.
self.linkedContentBlocks=WeakSet()
#No super() deserialize call here so we need to create all objects directly:
self.name=serializedObject["name"]
self._secondInit(parentGraphTrack)
returnself
defserialize(self):
result=super().serialize()
returnresult
defgetDataAsDict(self):
return{"name":self.name,
"duration":self.duration,#For save and load we drop the list as mutable value.
}
defputDataFromDict(self,dataDict):
"""modify inplace. Useful for a gui function. Compatible with
thedatafromgetDataAsDict"""
forparameter,valueindataDict.items():
ifparameter=="name":
self.name=value
elifparameter=="duration":
self.duration=value
else:
raiseValueError("Block does not have this property",parameter,value)
#Since every tempo block comes with a default D4=120 value at position 0 we need to check if this was already replaced or if we need to adjust it to the real tempo at this point in time.
ifnothasPointAtZero:
realStartTempoPoint=block.data[block.getMaxContentPosition()]#only the remaining items.
modelNewBlock.data[0]=realStartTempoPoint.copy()
#Now the original block and all its content links have fewer items than before
#For every of the content-linked blocks in the tempo track we need to create a new block and insert it right next to it.
forblinblock.linkedContentBlocksInScore():
index=self.blocks.index(bl)
link=modelNewBlock.contentLink()
self.blocks.insert(index+1,link)
#duration is mutable. All content links change duration as well.
iflen(block.linkedContentBlocksInScore())==1andlen(nextBlock.linkedContentBlocksInScore())==1:#both blocks are standalone. no content-links. This also prevents that both blocks are content links of each other.
eliflen(block.linkedContentBlocksInScore())==len(nextBlock.linkedContentBlocksInScore()):#It is maybe possible to build pairs from the current block and all its content links with the follow up block and all its content links.
iffirstBlock.dataissecondBlock.data:#content link of itself in succession. Very common usecase, but not compatible with merging.
#print ("content link follows itself")
returnFalse
elifnotself.blocks.index(firstBlock)+1==self.blocks.index(secondBlock):#all first blocks must be followed directly by a content link of the second block. linkedContentBlocksInScore() returns a blocklist in order so we can compare.
#print ("not all blocks-pairs are next to each other")
returnFalse
#Test complete without exit. All blocks can be paired up.
ifnotdictExportItem["position"]<=tickPositionAbsolute<=dictExportItem["position"]+dictExportItem["duration"]:#make sure the point is really in this block. It is guaranteed that the tempo track is always at least as long as the music tracks.
raiseValueError("position {} is not within the tempo tracks boundaries".format(tickPositionAbsolute))
l=block.staticRepresentation()#this takes care that only user items which fit in the block.duration are exported.
foritemIndexinrange(len(l)):#range starts from 0 so we can use this as index.
#l has tuples (tickPositionFromBlockStart, GraphItem)
thisPosition=l[itemIndex][0]
thisGraphItem=l[itemIndex][1]
#Check if we reached the last item in this block.
ifitemIndexislen(l)-1:#len counts from 1, itemIndex from 0.
assertthisGraphItemisl[-1][1]#l[-1] is a tuple (tickPositionFromBlockStart, GraphItem)
#Is there another block after this one?
ifblockisself.blocks[-1]:#this was the last block?
#there is no nextGraphItem and subsequently no interpolation.
typeString="lastInTrack"# also a switch for later.
nextPosition=-1#doesn't matter
#this is checked later in the loop, before we create the exportDict
else:#there is still a block left after the current
#The nextGraphItem can be found in the next block.
nextBlock=self.blocks[blockIndex+1]
nextBlockL=nextBlock.staticRepresentation()
nextPosition=nextBlockL[0][0]+block.duration#Instead of itemIndex we just need [0] for the first in the next block.
nextGraphItem=nextBlockL[0][1]#Instead of itemIndex we just need [0] for the first
else:#default case. Next item is still in the same block.
nextPosition=l[itemIndex+1][0]
nextGraphItem=l[itemIndex+1][1]
#We now generate a chain of items from the current position to the next,
#at least one, depending on the interpolation type (like linear, none etc.)
iftypeString=="lastInTrack":
userItemAndInterpolatedItemsPositions=thisGraphItem.staticRepresentation(-1,thisPosition,-1)#-1 are magic markers that indicate a forced standalone mode. no interpolation, we just get one item back.
forccValue,generatedTickPositioninuserItemAndInterpolatedItemsPositions:#generatedTickPosition is relative to the block beginning.
additionalData={}
iftypeString=="user"ortypeString=="lastInTrack":#interpolated items can be anywhere. We don't care much about them.
assertgeneratedTickPosition<=block.duration
ccValue,additionalData["unitsPerMinute"],additionalData["referenceTicks"]=ccValue#additional data is not for interpolated items
assertgeneratedTickPosition>=0
exportDictItem={
"type":typeString,
"value":-1*ccValue,#minus goes up because it reduces the line position, which starts from top of the screen. For a tempo this is a bpm value for quarter notes. This is meant for a gui which needs only one combined value to order all items graphically on an axis.
"position":sumOfBlocksDurationsWithoutCurrent+generatedTickPosition,#generatedTickPosition is relative to the block beginning.
"positionInBlock":generatedTickPosition,
"id":id(thisGraphItem),
"blockId":id(block),
"minPossibleAbsolutePosition":sumOfBlocksDurationsWithoutCurrent,#If you want to move this item to the left or right, this is only possible within the current block.
"maxPossibleAbsolutePosition":sumOfBlocksDurationsWithoutCurrent+block.duration,#If you want to move this item to the left or right, this is only possible within the current block.
typeString="interpolated"#The next items in userItemAndInterpolatedItemsPositions are interpolated items. Reset once we leave the local forLoop.
#Prepare data for the next block.
sumOfBlocksDurationsWithoutCurrent+=block.duration#the naming is only true until the last iteration. Then all blocks, even the current one, are in the sum and can be used below.
#We duplicate the last exportItem and set it as interpolated tempo point on the last tick.