|
|
@ -88,6 +88,7 @@ class Score(Data): |
|
|
|
|
|
|
|
#Tracks |
|
|
|
def addTrack(self, name:str=""): |
|
|
|
"""Create and add a new track. Not an existing one""" |
|
|
|
track = Score.TrackClass(parentData=self, name=name) |
|
|
|
assert track.sequencerInterface |
|
|
|
self.tracks.append(track) |
|
|
@ -96,6 +97,7 @@ class Score(Data): |
|
|
|
def deleteTrack(self, track): |
|
|
|
track.sequencerInterface.prepareForDeletion() |
|
|
|
self.tracks.remove(track) |
|
|
|
return track #for undo |
|
|
|
|
|
|
|
def updateJackMetadataSorting(self): |
|
|
|
"""Add this to you "tracksChanged" or "numberOfTracksChanged" callback. |
|
|
@ -105,12 +107,16 @@ class Score(Data): |
|
|
|
self.tracks changed and subsequent sorting. Multiple track changes in a row are common, |
|
|
|
therefore the place to update jack order is in the API, where the new track order is also |
|
|
|
sent to the UI. |
|
|
|
|
|
|
|
We also check if the track is 'deactivated' by probing track.cboxMidiOutUuid. |
|
|
|
Patroneo uses prepareForDeletion to deactive the tracks standalone track but keeps the |
|
|
|
interface around for later use. |
|
|
|
""" |
|
|
|
order = {portName:index for index, portName in enumerate(track.sequencerInterface.cboxPortName() for track in self.tracks)} |
|
|
|
order = {portName:index for index, portName in enumerate(track.sequencerInterface.cboxPortName() for track in self.tracks if track.sequencerInterface.cboxMidiOutUuid)} |
|
|
|
|
|
|
|
try: |
|
|
|
cbox.JackIO.Metadata.set_all_port_order(order) |
|
|
|
except Exception as e: #No Jack Meta Data |
|
|
|
except Exception as e: #No Jack Meta Data or Error with ports. |
|
|
|
logger.error(e) |
|
|
|
|
|
|
|
def trackById(self, trackId:int): |
|
|
@ -195,6 +201,8 @@ class _Interface(object): |
|
|
|
|
|
|
|
def _isNameAvailable(self, name:str): |
|
|
|
"""Check if the name is free. If not increment""" |
|
|
|
name = ''.join(ch for ch in name if ch.isalnum() or ch in (" ", "_", "-")) #sanitize |
|
|
|
name = " ".join(name.split()) #remove double spaces |
|
|
|
while name in [tr.sequencerInterface.name for tr in self.parentData.tracks]: |
|
|
|
beforeLastChar = name[-2] |
|
|
|
lastChar = name[-1] |
|
|
@ -319,7 +327,7 @@ class SequencerInterface(_Interface): #Basically the midi part of a track. |
|
|
|
|
|
|
|
def _processAfterInit(self): |
|
|
|
#Create midi out and cbox track |
|
|
|
logger.info("Creating empty SequencerInterface instance") |
|
|
|
logger.info(f"Creating empty SequencerInterface instance for {self._name}") |
|
|
|
super()._processAfterInit() |
|
|
|
self.cboxMidiOutUuid = cbox.JackIO.create_midi_output(self._name) |
|
|
|
self.calfboxTrack.set_external_output(self.cboxMidiOutUuid) |
|
|
@ -346,7 +354,8 @@ class SequencerInterface(_Interface): #Basically the midi part of a track. |
|
|
|
def name(self, value): |
|
|
|
if not value in (track.sequencerInterface.name for track in self.parentData.tracks): |
|
|
|
self._name = self._isNameAvailable(value) |
|
|
|
cbox.JackIO.rename_midi_output(self.cboxMidiOutUuid, self._name) |
|
|
|
if self.cboxMidiOutUuid: #we could be deactivated |
|
|
|
cbox.JackIO.rename_midi_output(self.cboxMidiOutUuid, self._name) |
|
|
|
|
|
|
|
def cboxPortName(self)->str: |
|
|
|
"""Return the complete jack portname: OurName:PortName""" |
|
|
@ -360,7 +369,14 @@ class SequencerInterface(_Interface): #Basically the midi part of a track. |
|
|
|
undo. That is why we bother setting calfboxTrack to None |
|
|
|
again. |
|
|
|
""" |
|
|
|
portlist = cbox.JackIO.get_connected_ports(self.cboxPortName()) |
|
|
|
if not self.calfboxTrack: #maybe non-template part deactivated it, like Patroneo groups |
|
|
|
return |
|
|
|
|
|
|
|
try: |
|
|
|
portlist = cbox.JackIO.get_connected_ports(self.cboxPortName()) |
|
|
|
except: #port not found. |
|
|
|
portlist = [] |
|
|
|
|
|
|
|
self._beforeDeleteThisJackMidiWasConnectedTo = portlist |
|
|
|
|
|
|
|
self.calfboxTrack.set_external_output("") |
|
|
@ -411,6 +427,15 @@ class SequencerInterface(_Interface): #Basically the midi part of a track. |
|
|
|
|
|
|
|
self._subtracks[key].setSubtrack(blobs) |
|
|
|
|
|
|
|
def deleteSubtrack(self, key): |
|
|
|
"""Remove a subtrack. |
|
|
|
Return for a potential undo""" |
|
|
|
self._subtracks[key].prepareForDeletion() |
|
|
|
toDelete = self._subtracks[key] |
|
|
|
del self._subtracks[key] |
|
|
|
return toDelete |
|
|
|
|
|
|
|
|
|
|
|
#Save / Load / Export |
|
|
|
def serialize(self)->dict: |
|
|
|
"""Generate Data to save as json""" |
|
|
|