Browse Source

fix crash

master
Nils 5 years ago
parent
commit
1759c5c014
  1. 9
      engine/api.py
  2. 37
      engine/items.py
  3. 35
      engine/main.py
  4. 2
      engine/track.py
  5. 11
      qtgui/items.py
  6. 9
      qtgui/submenus.py

9
engine/api.py

@ -124,7 +124,7 @@ class ClientCallbacks(Callbacks): #inherits from the templates api callbacks
can be used by a GUI to draw notes. can be used by a GUI to draw notes.
track.staticRepresentation also creates the internal midi representation for cbox. """ track.staticRepresentation also creates the internal midi representation for cbox. """
if self.updateTrack: if self.updateTrack:
ex = session.data.trackById(trId).staticRepresentation() #Includes cbox.updatePlayback ex = session.data.trackById(trId).staticRepresentation()
for func in self.updateTrack: for func in self.updateTrack:
func(trId, ex) func(trId, ex)
self._updateBlockTrack(trId) self._updateBlockTrack(trId)
@ -447,6 +447,7 @@ def _deleteSelection(backspaceParamForCompatibilityIgnoreThis = None): #this is
moveFunction = _createLambdaMoveToForCurrentPosition() moveFunction = _createLambdaMoveToForCurrentPosition()
listOfChangedTrackIDs = session.data.deleteSelection() listOfChangedTrackIDs = session.data.deleteSelection()
if listOfChangedTrackIDs: #delete succeeded. if listOfChangedTrackIDs: #delete succeeded.
def registeredUndoFunction(): def registeredUndoFunction():
moveFunction() moveFunction()
@ -1699,7 +1700,7 @@ def insertCommonMetricalInstrucions(scheme):
"1/1" : (D1,), "1/1" : (D1,),
} }
lilypond = { lilypond = {
"off" : "\\mark \"No meter\" \\cadenzaOn ", "off" : "\\mark \"X\" \\cadenzaOn ",
"2/4" : "\\cadenzaOff \\time 2/4", "2/4" : "\\cadenzaOff \\time 2/4",
"3/4" : "\\cadenzaOff \\time 3/4", "3/4" : "\\cadenzaOff \\time 3/4",
"4/4" : "\\cadenzaOff \\time 4/4", "4/4" : "\\cadenzaOff \\time 4/4",
@ -2303,8 +2304,8 @@ def insertRandomFromClipboard():
#Midi only #Midi only
def instrumentChange(program, msb, lsb, instrumentName, shortInstrumentName): def instrumentChange(program, msb, lsb, shortInstrumentName):
change = items.InstrumentChange(program, msb, lsb, instrumentName, shortInstrumentName) change = items.InstrumentChange(program, msb, lsb, shortInstrumentName)
insertItem(change) insertItem(change)
def channelChange(value, text): def channelChange(value, text):

37
engine/items.py

@ -2614,8 +2614,9 @@ class LegatoSlur(Item):
} }
class InstrumentChange(Item): class InstrumentChange(Item):
"""Includes program change, both bank changes and the lilypond instrument name and short name change""" """Includes program change, both bank changes and the lilypond short name change.
def __init__(self, program, msb, lsb, instrumentName, shortInstrumentName): Lilypond does not support a full name change, so we don't offer that here."""
def __init__(self, program, msb, lsb, shortInstrumentName):
"""Either init gets called, by creation during runtime, or instanceFromSerializedData. """Either init gets called, by creation during runtime, or instanceFromSerializedData.
That means everything in init must be matched by a loading call in instanceFromSerializedData.""" That means everything in init must be matched by a loading call in instanceFromSerializedData."""
super().__init__() super().__init__()
@ -2624,8 +2625,7 @@ class InstrumentChange(Item):
assert 0 <= lsb <= 127, lsb #a CC value assert 0 <= lsb <= 127, lsb #a CC value
self.program = program self.program = program
self.msb = msb self.msb = msb
self.lsb = lsb self.lsb = lsb
self.instrumentName = instrumentName
self.shortInstrumentName = shortInstrumentName self.shortInstrumentName = shortInstrumentName
self._secondInit(parentBlock = None) #see Item._secondInit. self._secondInit(parentBlock = None) #see Item._secondInit.
@ -2642,8 +2642,7 @@ class InstrumentChange(Item):
self = cls.__new__(cls) self = cls.__new__(cls)
self.program = serializedObject["program"] self.program = serializedObject["program"]
self.msb = serializedObject["msb"] self.msb = serializedObject["msb"]
self.lsb = serializedObject["lsb"] self.lsb = serializedObject["lsb"]
self.instrumentName = serializedObject["instrumentName"]
self.shortInstrumentName = serializedObject["shortInstrumentName"] self.shortInstrumentName = serializedObject["shortInstrumentName"]
self.deserializeDurationAndNotelistInPlace(serializedObject) self.deserializeDurationAndNotelistInPlace(serializedObject)
self._secondInit(parentBlock = parentObject) self._secondInit(parentBlock = parentObject)
@ -2653,14 +2652,13 @@ class InstrumentChange(Item):
result = super().serialize() #call this in child classes result = super().serialize() #call this in child classes
result["program"] = self.program result["program"] = self.program
result["msb"] = self.msb result["msb"] = self.msb
result["lsb"] = self.lsb result["lsb"] = self.lsb
result["instrumentName"] = self.instrumentName
result["shortInstrumentName"] = self.shortInstrumentName result["shortInstrumentName"] = self.shortInstrumentName
return result return result
def _copy(self): def _copy(self):
"""return an independent copy of self""" """return an independent copy of self"""
new = type(self)(self.program, self.msb, self.lsb, self.instrumentName, self.shortInstrumentName) #all init parameters are immutable types and copied implicitly new = type(self)(self.program, self.msb, self.lsb, self.shortInstrumentName) #all init parameters are immutable types and copied implicitly
return new return new
def _exportObject(self, trackState): def _exportObject(self, trackState):
@ -2671,8 +2669,8 @@ class InstrumentChange(Item):
_msbStr = " msb" + str(self.msb) if self.msb > 0 else "" _msbStr = " msb" + str(self.msb) if self.msb > 0 else ""
_lsbStr = " lsb" + str(self.lsb) if self.lsb > 0 else "" _lsbStr = " lsb" + str(self.lsb) if self.lsb > 0 else ""
_nameStr = self.shortInstrumentName if self.shortInstrumentName else self.instrumentName _nameStr = self.shortInstrumentName
_nameStr = _nameStr + " " if (self.shortInstrumentName or self.instrumentName) else "" _nameStr = _nameStr + " " if self.shortInstrumentName else ""
return { return {
"type" : "InstrumentChange", "type" : "InstrumentChange",
"completeDuration" : duration, "completeDuration" : duration,
@ -2680,26 +2678,17 @@ class InstrumentChange(Item):
"midiBytes" : [cbox.Pattern.serialize_event(tickPosition, 0xC0 + trackState.midiChannel(), self.program, 0), "midiBytes" : [cbox.Pattern.serialize_event(tickPosition, 0xC0 + trackState.midiChannel(), self.program, 0),
cbox.Pattern.serialize_event(tickPosition, 0xB0 + trackState.midiChannel(), 0, self.msb), #position, status byte+channel, controller number, controller value cbox.Pattern.serialize_event(tickPosition, 0xB0 + trackState.midiChannel(), 0, self.msb), #position, status byte+channel, controller number, controller value
cbox.Pattern.serialize_event(tickPosition, 0xB0 + trackState.midiChannel(), 32, self.lsb), #position, status byte+channel, controller number, controller value cbox.Pattern.serialize_event(tickPosition, 0xB0 + trackState.midiChannel(), 32, self.lsb), #position, status byte+channel, controller number, controller value
], ],
"instrumentName" : self.instrumentName,
"shortInstrumentName" : self.shortInstrumentName, "shortInstrumentName" : self.shortInstrumentName,
"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. "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.
} }
#hier weiter machen. die midiBytes können so exportiert werden, wahrscheinlich auch auf diesem channel.
#Aber die müssen im Track dann am Ende rausgefiltert werden und auf einen eigenen cbox midi track.
#Die Alternative wäre das aufzuteilen. Eins auf CC0, eins auf CC32 und der Program Change auf einen eigenen Kanal?
#Was ist mit sowas wie initial program? Wo wird das geändert? In den Track Properties. Vielleichst ist das selbst ein InstrumentChange Objekt. Könnte eins sein zumindest.
def _lilypond(self): def _lilypond(self):
"""called by block.lilypond(), returns a string. """called by block.lilypond(), returns a string.
Don't create white-spaces yourself, this is done by the structures. Don't create white-spaces yourself, this is done by the structures.
When in doubt prefer functionality and robustness over 'beautiful' lilypond syntax.""" When in doubt prefer functionality and robustness over 'beautiful' lilypond syntax."""
if self.instrumentName or self.shortInstrumentName: if self.shortInstrumentName:
raise NotImplementedError("Implement _lilypond() with correct instrument change syntax") return f'\\mark \\markup {{\\small "{self.shortInstrumentName}"}}' + "\n" + f'\\set Staff.shortInstrumentName = #"{self.shortInstrumentName}"'
return str
else: else:
return "" return ""

35
engine/main.py

@ -77,8 +77,9 @@ class Data(template.engine.sequencer.Score):
try: try:
ret = Track.allTracks[trackId] ret = Track.allTracks[trackId]
except: except:
print ("Error while trying to get track by id")
print (sys.exc_info()[0]) print (sys.exc_info()[0])
print (trackId, Track.allTracks, self.tracks, self.hiddenTracks, self.trackIndex) print (trackId, hex(trackId), Track.allTracks, self.tracks, self.hiddenTracks, self.trackIndex)
raise raise
return ret return ret
@ -436,7 +437,8 @@ class Data(template.engine.sequencer.Score):
tickindex and position. tickindex and position.
We take care of duration-changed content linked items left of the selection as well. We take care of duration-changed content linked items left of the selection as well.
""" """
t = self.selectionExport() #is there a selection at all?
t = self.selectionExport() #is there a selection at all?
if not t: if not t:
return [False, None, None, [], []] #mimickes the true result. the api can use this to decide to do nothing instead of processing the selection return [False, None, None, [], []] #mimickes the true result. the api can use this to decide to do nothing instead of processing the selection
else: else:
@ -450,9 +452,10 @@ 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([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() listOfChangedTrackIds = set()
finalResult = [selectionValid, topLeft, bottomRight, listOfChangedTrackIds] #listOfChangedTrackIds is mutable. It will change in the loop below finalResult = [selectionValid, topLeft, bottomRight, listOfChangedTrackIds] #listOfChangedTrackIds is mutable. It will change in the loop below
firstRound = True firstRound = True
for track in tracksWithSelectedItems: for track in tracksWithSelectedItems:
assert track in self.tracks, track #selecting from hidden or deleted tracks is impossible
originalPosition = track.state.position() originalPosition = track.state.position()
result = [] result = []
@ -467,12 +470,15 @@ class Data(template.engine.sequencer.Score):
if r == 1: if r == 1:
# the item we want is already left of the cursor. So we want the previous item. # the item we want is already left of the cursor. So we want the previous item.
if track.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. if track.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.
item = track.previousItem() item = track.previousItem()
if (not removeContentLinkedData) or (not item in seenItems): if (not removeContentLinkedData) or (not item in seenItems):
result.append((item, {"keySignature" : track.state.keySignature()})) #The keysig is needed for undo operations after apply to selection result.append((item, {"keySignature" : track.state.keySignature()})) #The keysig is needed for undo operations after apply to selection
seenItems.add(item) seenItems.add(item)
for parentBlock in item.parentBlocks: #TODO: just be naive and add them all for now. Performance can be improved later. for parentBlock in item.parentBlocks: #TODO: just be naive and add them all for now. Performance can be improved later.
listOfChangedTrackIds.add(id(parentBlock.parentTrack)) #assert parentBlock, item
#assert parentBlock.parentTrack, (parentBlock, parentBlock.parentTrack)
if parentBlock.parentTrack:
listOfChangedTrackIds.add(id(parentBlock.parentTrack)) #there are items without parent Tracks. They have None. We must filter them out otherwise our changedTrackIds get polluted with id(None)
else: else:
continue continue
elif r == 2: #block end. Again, this is already the previous state. right now we are in the next block already. elif r == 2: #block end. Again, this is already the previous state. right now we are in the next block already.
@ -482,7 +488,10 @@ class Data(template.engine.sequencer.Score):
track.toPosition(originalPosition) #has head() in it track.toPosition(originalPosition) #has head() in it
finalResult.append(result) finalResult.append(result)
#finalResult[4:] is only lists(tracks) with items
for changedTrId in listOfChangedTrackIds:
assert self.trackById(changedTrId) in self.tracks, changedTrId #selecting from hidden or deleted tracks is impossible
return finalResult return finalResult
#The next two functions have "Objects" in their name so copy gets not confused with block.copy or item.copy. This is Ctrl+C as in copy and paste. #The next two functions have "Objects" in their name so copy gets not confused with block.copy or item.copy. This is Ctrl+C as in copy and paste.
@ -628,7 +637,10 @@ class Data(template.engine.sequencer.Score):
def deleteSelection(self): def deleteSelection(self):
"""Mortals, Hear The Prophecy: Delete Selection is notoriously tricky. It was hard in Laborejo 1 and it is still hard """Mortals, Hear The Prophecy: Delete Selection is notoriously tricky. It was hard in Laborejo 1 and it is still hard
here. Please use as many asserts and tests as you possibly can. The next bug or regression here. Please use as many asserts and tests as you possibly can. The next bug or regression
WILL come. And it will be here.""" WILL come. And it will be here.
Will only delete from visible tracks. No hidden, no deleted.
"""
validSelection, topLeftCursor, bottomRightCursor, listOfChangedTrackIds, *selectedTracksAndItems = self.listOfSelectedItems(removeContentLinkedData = True) validSelection, topLeftCursor, bottomRightCursor, listOfChangedTrackIds, *selectedTracksAndItems = self.listOfSelectedItems(removeContentLinkedData = True)
#selectedTracksAndItems format: [track1[(item, itsKeysig), (item, itsKeysig), (item, itsKeysig)], track2[(item, itsKeysig), (item, itsKeysig), (item, itsKeysig)]] #selectedTracksAndItems format: [track1[(item, itsKeysig), (item, itsKeysig), (item, itsKeysig)], track2[(item, itsKeysig), (item, itsKeysig), (item, itsKeysig)]]
@ -674,7 +686,7 @@ class Data(template.engine.sequencer.Score):
assert curTrack.state.tickindex == topLeftCursor["tickindex"], (curTrack.state.tickindex, topLeftCursor["tickindex"]) assert curTrack.state.tickindex == topLeftCursor["tickindex"], (curTrack.state.tickindex, topLeftCursor["tickindex"])
#We delete always from the same cursor position. After one deletion the rest items gravitate left to our position. Except block boundaries, those will not be crossed so we have to check for them: #We delete always from the same cursor position. After one deletion the rest items gravitate left to our position. Except block boundaries, those will not be crossed so we have to check for them:
for item, cached6State in selectionTrack: for item, cachedState in selectionTrack:
if item is bottomRightCursor["item"]: if item is bottomRightCursor["item"]:
break #without this the program will delete the first non-selected item, if zero duration, as well break #without this the program will delete the first non-selected item, if zero duration, as well
@ -696,6 +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 #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. #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.
return listOfChangedTrackIds return listOfChangedTrackIds
def getBlockAndItemOrder(self): def getBlockAndItemOrder(self):
@ -955,6 +968,11 @@ class Data(template.engine.sequencer.Score):
track = self.currentTrack() track = self.currentTrack()
startBlock = track.currentBlock() #this is the only unique thing we can rely on. 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.
#NO. This works for splitting, but not for undo. for item in startBlock.data:
# item.parentBlocks.remove(startBlock)
dictOfTrackIdsWithListOfBlockIds = {} # [trackId] = [listOfBlockIds] dictOfTrackIdsWithListOfBlockIds = {} # [trackId] = [listOfBlockIds]
protoBlockLeft = startBlock.copy(track) protoBlockLeft = startBlock.copy(track)
@ -963,7 +981,6 @@ class Data(template.engine.sequencer.Score):
protoBlockRight = startBlock.copy(track) protoBlockRight = startBlock.copy(track)
protoBlockRight.data = startBlock.data[startBlock.localCursorIndex:] #everything right of the cursor. The local cursor index of the current block is guaranteed to be correct. The one from other blocks is not. protoBlockRight.data = startBlock.data[startBlock.localCursorIndex:] #everything right of the cursor. The local cursor index of the current block is guaranteed to be correct. The one from other blocks is not.
for block in startBlock.linkedContentBlocksInScore(): #all linked blocks in all tracks, including currentBlock. All blocks are unique. The content is not. for block in startBlock.linkedContentBlocksInScore(): #all linked blocks in all tracks, including currentBlock. All blocks are unique. The content is not.
parentTrackId = id(block.parentTrack) parentTrackId = id(block.parentTrack)
if parentTrackId in dictOfTrackIdsWithListOfBlockIds: if parentTrackId in dictOfTrackIdsWithListOfBlockIds:

2
engine/track.py

@ -297,7 +297,7 @@ class TrackState(object):
self.duringLegatoSlur = False #there are no nested legato slurs self.duringLegatoSlur = False #there are no nested legato slurs
self.duringBeamGroup = False #no nested beam groups. self.duringBeamGroup = False #no nested beam groups.
self.midiChannels = [track.initialMidiChannel] #these are just integers, not items. items.ChannelChange parsing changes this and items automatically get the new value. A stack of midi channels allows the cursor to know the current channel for immediate playback/feedback. self.midiChannels = [track.initialMidiChannel] #these are just integers, not items. items.ChannelChange parsing changes this and items automatically get the new value. A stack of midi channels allows the cursor to know the current channel for immediate playback/feedback.
self.instrumentChanges = [InstrumentChange(track.initialMidiProgram, track.initialMidiBankMsb, track.initialMidiBankLsb, track.initialInstrumentName, track.initialShortInstrumentName, )] self.instrumentChanges = [InstrumentChange(track.initialMidiProgram, track.initialMidiBankMsb, track.initialMidiBankLsb, track.initialShortInstrumentName, )]
self.midiTranspose = track.midiTranspose self.midiTranspose = track.midiTranspose
# TODO: When a change item for the following functions is introduced they need to get parsed in track.parseLeft and track.parseRight # TODO: When a change item for the following functions is introduced they need to get parsed in track.parseLeft and track.parseRight

11
qtgui/items.py

@ -738,7 +738,7 @@ class GuiMetricalInstruction(GuiItem):
if staticItem["oneMeasureInTicks"] == 0: if staticItem["oneMeasureInTicks"] == 0:
#Basically just a manual barline #Basically just a manual barline
displayString = "Metrical Off" displayString = "X"
markerPosition = -2 markerPosition = -2
markerHeight = 4 markerHeight = 4
@ -754,12 +754,15 @@ class GuiMetricalInstruction(GuiItem):
r = r.replace(",", "") r = r.replace(",", "")
displayString = "Metrical " + r displayString = "Metrical " + r
markerHeight = -7 markerPosition = -2
markerPosition = 1 markerHeight = 4
#A metrical instruction is by definition at the beginning of a measure
#We therefore need to place the text above the barnumber
self.text = QtWidgets.QGraphicsSimpleTextItem(displayString) self.text = QtWidgets.QGraphicsSimpleTextItem(displayString)
self.text.setParentItem(self) self.text.setParentItem(self)
self.text.setPos(0, -7 * constantsAndConfigs.stafflineGap) self.text.setPos(0, -7 * constantsAndConfigs.stafflineGap)
self.marker = GuiPositionMarker(markerHeight, markerPosition) self.marker = GuiPositionMarker(markerHeight, markerPosition)
self.marker.setParentItem(self) self.marker.setParentItem(self)

9
qtgui/submenus.py

@ -566,8 +566,7 @@ class SecondaryProgramChangeMenu(Submenu):
self.msb = QtWidgets.QSpinBox() self.msb = QtWidgets.QSpinBox()
self.msb.setValue(type(self).lastMsbValue) self.msb.setValue(type(self).lastMsbValue)
self.lsb = QtWidgets.QSpinBox() self.lsb = QtWidgets.QSpinBox()
self.lsb.setValue(type(self).lastLsbValue) self.lsb.setValue(type(self).lastLsbValue)
self.instrumentName = QtWidgets.QLineEdit()
self.shortInstrumentName = QtWidgets.QLineEdit() self.shortInstrumentName = QtWidgets.QLineEdit()
for label, spinbox in (("Program", self.program), ("Bank MSB", self.msb), ("Bank LSB", self.lsb)): for label, spinbox in (("Program", self.program), ("Bank MSB", self.msb), ("Bank LSB", self.lsb)):
@ -575,9 +574,7 @@ class SecondaryProgramChangeMenu(Submenu):
spinbox.setMaximum(127) spinbox.setMaximum(127)
spinbox.setSingleStep(1) spinbox.setSingleStep(1)
self.layout.addRow(label, spinbox) self.layout.addRow(label, spinbox)
self.layout.addRow("Instr.Name", self.instrumentName)
self.layout.addRow("Short Name", self.shortInstrumentName) self.layout.addRow("Short Name", self.shortInstrumentName)
self.insert = QtWidgets.QPushButton("Insert") self.insert = QtWidgets.QPushButton("Insert")
@ -593,7 +590,7 @@ class SecondaryProgramChangeMenu(Submenu):
lsb = self.lsb.value() lsb = self.lsb.value()
type(self).lastLsbValue = lsb type(self).lastLsbValue = lsb
api.instrumentChange(program, msb, lsb, self.instrumentName.text(), self.shortInstrumentName.text(), ) api.instrumentChange(program, msb, lsb, self.shortInstrumentName.text(), )
self.done(True) self.done(True)

Loading…
Cancel
Save