@ -64,6 +65,7 @@ class Data(template.engine.sequencer.Score):
self.cachedOffsetInTicks=0
#Create three tracks with their first pattern activated, so 'play' after startup already produces sounding notes. This is less confusing for a new user.
#self.tracks is created in the template.
self.addTrack(name="Melody A",color="#ffff00")
self.addTrack(name="Chords A",color="#0055ff")
self.addTrack(name="Bass A",color="#00ff00")
@ -82,6 +84,19 @@ class Data(template.engine.sequencer.Score):
def_processAfterInit(self):
self._lastLoopStart=0#ticks. not saved
self.parentData=self# a trick to get SequencerInterface to play nicely with our groups
#After load each track has it's own groupless Sequencerinterface.
#self.groups itself is not saved.
#Restore groups:
logger.info("Restoring saved groups")
self.groups={}#NameString:SequencerInterface . Updated through function self.setGroup.
assertself.tracks#at least 1
self._duringFileLoadGroupIndicator=True
fortrackinself.tracks:
self.setGroup(track,track.group,buildAllTracksAfterwards=False)#buildAllTracks is handled by the API initial callback/load engineStart
#we never deleted the tracks standalone sequencerInterface. Reactivate.
logger.info(f"Re-activiating standalone SequencerInterface for track {track.sequencerInterface.name}")
track.sequencerInterface.recreateThroughUndo()
#Add track to new or existing group
else:
ifgroupNameinself.groups:
#We need to base track visibility on the other group members. But we cannot use the inner position in the group for that because the first one might already be the new track.
groupVisibility=self._isGroupVisible(groupName)#checking one track is enough. They are all the same, once the file is loaded.
assertnotgroupVisibilityisNone
else:
logger.info(f"Creating new group SequencerInterface for {groupName}")
ifnotself._duringFileLoadGroupIndicator:#During file load tracks have groups already.
assertnotgroupNamein[track.groupfortrackinself.tracksiftrack.group],([track.groupfortrackinself.tracks])#no track should have that group yet
self.groups[groupName]=SequencerInterface(parentTrack=self,name=groupName)#we are not really a parentTrack, but it works.
groupVisibility=True#First member of a new group, everything stays visible. It already is, but this makes it easier to set self.visible below.
iftrack.group==""orself._duringFileLoadGroupIndicator:#Change from standalone to group
logger.info(f"Deactiviating standalone SequencerInterface for track {track.sequencerInterface.name}")
track.sequencerInterface.prepareForDeletion()#we do NOT delete the sequencerInterface. That has more data, such as the name!
else:#track was already in another group. But could also be file load!
logger.info(f"Changing group for track {track.sequencerInterface.name}")
#order = {portName:index for index, portName in enumerate(track.sequencerInterface.cboxPortName() for track in self.tracks if track.sequencerInterface.cboxMidiOutUuid)}
@ -58,6 +58,10 @@ class Track(object): #injection at the bottom of this file!
self.patternLengthMultiplicator=1#int. >= 1 the multiplicator is added after all other calculations, like subdivions. We can't integrate this into howManyUnits because that is the global score value
self.midiChannel=0# 0-15 midi channel is always set.
#2.1
self.group=""# "" is a standalone track, the normal one which existed since version 1.0. Using a name here will group these tracks together. A GUI can use this information. Also all tracks in a group share a single jack out port.
self.visible=True#only used together with groups. the api and our Datas setGroup function take care that standalone tracks are never hidden.
self.structure=structureifstructureelseset()#see buildTrack(). This is the main track data structure besides the pattern. Just integers (starts at 0) as switches which are positions where to play the patterns. In between are automatic rests.
self.whichPatternsAreScaleTransposed=whichPatternsAreScaleTransposedifwhichPatternsAreScaleTransposedelse{}#position:integers between -7 and 7. Reversed pitch, row based: -7 is higher than 7!!
@ -73,6 +77,14 @@ class Track(object): #injection at the bottom of this file!
Thisiscalledaftereverysmallchange.
"""
ifself.group:
#We still have an inactive sequencerinterface but instead we use the group ones as subtrack.
self.setSceneRect(0,0,exportDict["numberOfMeasures"]*SIZE_UNIT,self.cachedCombinedTrackHeight+3*SIZE_TOP_OFFSET)#no empty space on top without a scene rect. Also a bit of leniance.
grouplabel.setY(index*SIZE_UNIT+SIZE_TOP_OFFSET+groupOffset-hiddenOffsetCounter)#group offset is still "above" the current group. If this is a group itself the offset will be extended just belowt to make room for ourselves.
delitem#yes, manual memory management in Python. We need to get rid of anything we want to delete later or Qt will crash because we have a python wrapper without a qt object
listOfLabelsAndFunctions=[
@ -773,6 +842,27 @@ class TrackLabelEditor(QtWidgets.QGraphicsScene):
mchAction.setEnabled(False)
mchAction.triggered.connect(midiChannelCommand)
#Add a submenu for track groups. Will call the api which will send us a callback to reorder the tracks.
#super().mouseMoveEvent(event) #with this the sync between cursor and item is off.
defmousePressEvent(self,event):
"""release gets only triggered when mousePressEvent was on the same item.
Wedon't need to worry about the user just releasing the mouse on this item"""
self._posBeforeMove=self.parentGroupLabel.pos()
self._cursorPosOnMoveStart=QtGui.QCursor.pos()
self.parentGroupLabel.mousePressEvent(event)
#super().mousePressEvent(event) #with this in mouseMoveEvent does not work. IIRC because we do not set the movableFlag
defmouseReleaseEvent(self,event):
newIndex=self.yPos2trackIndex(self.parentGroupLabel.y())#we need to save that first, right after this we reset the position
self.parentGroupLabel.setPos(self._posBeforeMove)#In case the block was moved to a position where no track is (below the tracks) we just reset the graphics before anything happens. The user will never see this really