Browse Source

Fix wrong beaming stems for chords

master
Nils 4 years ago
parent
commit
e655044f7e
  1. 100
      engine/items.py
  2. 91
      engine/track.py
  3. 11
      qtgui/items.py
  4. 6
      qtgui/musicstructures.py

100
engine/items.py

@ -370,7 +370,7 @@ class Duration(object):
completeDuration = int(completeDuration)
durList = [D1024, D512, D256, D128, D64, D32, D16, D8, D4, D2, D1, DB, DL, DM]
guessedBase = _closest(completeDuration, durList)
guessedBase = _closest(completeDuration, durList)
if guessedBase:
if completeDuration in durList: #no need for guessing
@ -386,7 +386,7 @@ class Duration(object):
new.tuplets = [(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
#ratio = completeDuration / guessedBase #0.666~ for triplet
newRatio = Fraction(int(completeDuration), guessedBase).limit_denominator(100000) #protects 6 or 7 decimal positions
new.tuplets = [(newRatio.numerator, newRatio.denominator)]
else:
@ -571,13 +571,13 @@ class Duration(object):
def lilypond(self):
"""Called by note.lilypond(), See Item.lilypond for the general docstring.
returns a number as string."""
if self.durationKeyword == D_TIE:
append = "~"
else:
append = ""
n = self.genericNumber
n = self.genericNumber
if n == 0:
return "\\breve" + append
elif n == -1:
@ -594,9 +594,9 @@ class DurationGroup(object):
Is compatible to Duration() methods and parameters.
Midi will send the actual note durations, per-note.
For the representation (GUI) and logical calculations always the minimum
For the representation (GUI) and logical calculations always the minimum
note duration counts for the whole chord.
"""
def __init__(self, chord):
self.chord = chord
@ -677,7 +677,7 @@ class Item(object):
}
def _secondInit(self, parentBlock):
"""see Score._secondInit"""
"""see Score._secondInit"""
def deserializeDurationAndNotelistInPlace(self, serializedObject):
"""Part of instanceFromSerializedData.
@ -758,7 +758,7 @@ class Item(object):
@property
def parentTracks(self):
def parentTracks(self):
#return (block.parentTrack for block in self.parentBlocks[0])
return (block.parentTrack for block in list(self.parentBlocks)[0].linkedContentBlocksInScore())
@ -776,8 +776,8 @@ class Item(object):
return new
def copyParentBlocks(self, oldItem):
"""Move the oldItems parentBlocks to the new item"""
#self.parentBlocks = oldItem.parentBlocks #TODO. We took that out when pasting after deleting a track and recreating failed. Why do we to copy the parentBlocks when a parentBlock is added during block.insert anyway? Wild guess: we don't.
"""Move the oldItems parentBlocks to the new item"""
#self.parentBlocks = oldItem.parentBlocks #TODO. We took that out when pasting after deleting a track and recreating failed. Why do we to copy the parentBlocks when a parentBlock is added during block.insert anyway? Wild guess: we don't.
def logicalDuration(self):
return 0
@ -1347,8 +1347,8 @@ class Chord(Item):
for note in self.notelist:
oldValues.append(note.duration.copy()) #we don't actually need a copy since we create a new one. But better safe than sorry. This matches also the behaviour of the other duration change functions.
complDur = note.duration.completeDuration()
#newTicks = complDur / newparts
#newTicks = complDur / newparts
note.duration = Duration.createByGuessing(Fraction(complDur, newparts))
#Now we have one note with the duration the user wants to have. Make n-1 copies-
@ -1502,7 +1502,7 @@ class Chord(Item):
self.notelist.sort()
self._cachedClefForLedgerLines = None
return lambda: self._setPitchlist(oldValues)
def moreDuration(self):
oldValues = []
@ -1654,7 +1654,7 @@ class Chord(Item):
for note in self.notelist:
velocity = note.dynamic.velocity(trackState)
midipitch = pitchmath.toMidi[note.pitch]
onOffset, offOffset = note.duration.noteOnAndOff(trackState, note.duration.completeDuration())
onOffset, offOffset = note.duration.noteOnAndOff(trackState, note.duration.completeDuration())
if note.pitch in trackState.EXPORTtiedNoteExportObjectsWaitingForClosing: #this is the last or the middle in a tied note sequence (but not the first).
exportNoteList.append(note.exportObject(trackState))
@ -1693,7 +1693,7 @@ class Chord(Item):
"beamGroup" : self.beamGroup, #bool
"midiBytes" : midiBytesList,
"midiChannelOffset" : self.midiChannelOffset,
"beam" : tuple(), #decided later in structures export. Has the same structure as a stem.
"beam" : tuple(), #decided later in track export. Has the same structure as a stem.
}
def _lilypond(self):
@ -1720,12 +1720,12 @@ class Chord(Item):
substrings = []
minimumTicksInChord = self.durationGroup.completeDuration()
for (ticks, lilydur), lst in table.items():
for (ticks, lilydur), lst in table.items():
#factor = Fraction(ticks, minimumDurInChord).limit_denominator(100000) #protects 6 or 7 decimal positions
#dur = lilydur + " * {}/{}".format(factor.denominator, factor.numerator) #lilypond requires the 1/x syntax
assert int(ticks) == ticks, ticks
assert int(minimumTicksInChord) == minimumTicksInChord, minimumTicksInChord
assert minimumTicksInChord <= ticks
assert minimumTicksInChord <= ticks
if lilydur.endswith("~"):
lilydur = lilydur[:-1] #without ~ #TODO: not supported yet.
dur = lilydur + " * {}/{}".format(int(minimumTicksInChord), int(ticks)) #lilypond requires the x/y syntax, even if it is 1/y #TODO: this is very ugly
@ -2169,23 +2169,23 @@ class KeySignature(Item):
return ""
#G Major ((3, 10), (0, 0), (4, 0), (1, 0), (5, 0), (2, 0), (6, 0))
#`((0 . ,NATURAL) (1 . ,NATURAL) (2 . ,NATURAL) (3 . ,SHARP) (4 . ,NATURAL) (5 . ,NATURAL) (6 . ,NATURAL))
schemepairs = " ".join(build(x[0], x[1]) for x in self.keysigList)
#`((0 . ,NATURAL) (1 . ,NATURAL) (2 . ,NATURAL) (3 . ,SHARP) (4 . ,NATURAL) (5 . ,NATURAL) (6 . ,NATURAL))
schemepairs = " ".join(build(x[0], x[1]) for x in self.keysigList)
schemepairs = " ".join(schemepairs.split()) # split and join again to reduce all whitespaces to single ones.
subtextRoot = "\\once \\override Staff.KeySignature #'stencil = #(lambda (grob) (ly:stencil-combine-at-edge (ly:key-signature-interface::print grob) Y DOWN (grob-interpret-markup grob (markup #:small \"" + pitchmath.pitch2ly[self.root].strip(",").title() + "\")) 3)) " #3 is the space between keysig and text
lyKeysignature = subtextRoot + "\\set Staff.keyAlterations = #`( {} )".format(schemepairs)
return lyKeysignature
return lyKeysignature
#Alternative
schemepairs = " ".join(build(num, alt) for num, alt in enumerate(self.deviationFromMajorScale))
schemepairs = " ".join(build(num, alt) for num, alt in enumerate(self.deviationFromMajorScale))
schemepairs = " ".join(schemepairs.split()) # split and join again to reduce all whitespaces to single ones.
subtextRoot = "\\once \\override Staff.KeySignature #'stencil = #(lambda (grob) (ly:stencil-combine-at-edge (ly:key-signature-interface::print grob) Y DOWN (grob-interpret-markup grob (markup #:small \"" + pitchmath.pitch2ly[self.root].strip(",").title() + "\")) 3)) " #3 is the space between keysig and text
if schemepairs:
if schemepairs:
lyKeysignature = subtextRoot + f"\\key {pitchmath.pitch2ly[self.root].strip(',')} #`( {schemepairs} )"
else: #no explicit accidental
lyKeysignature = subtextRoot + "\\set Staff.keyAlterations = #`(( 0 . ,NATURAL))" #TODO: subtextRoot position is broken
return lyKeysignature
else: #no explicit accidental
lyKeysignature = subtextRoot + "\\set Staff.keyAlterations = #`(( 0 . ,NATURAL))" #TODO: subtextRoot position is broken
return lyKeysignature
class Clef(Item):
"""A clef has no direct musical logical meaning. But it gives some hints:
Voice range. A typical musical voice in a polyphonic setup has between one and two octaves range
@ -2376,10 +2376,10 @@ class MetricalInstruction(Item):
if i == 0:
self.isMetrical = False #protection against a wrong combination
self.nominator = None
self.denominator = None
self.denominator = None
else:
self.nominator = len(list(flatList(self.treeOfInstructions))) #for compatibility with lilypond etc.
self.denominator = self.oneMeasureInTicks / self.nominator #for compatibility with lilypond etc.
self.denominator = self.oneMeasureInTicks / self.nominator #for compatibility with lilypond etc.
@classmethod
def instanceFromSerializedData(cls, serializedObject, parentObject):
@ -2647,7 +2647,7 @@ class InstrumentChange(Item):
assert 0 <= lsb <= 127, lsb #a CC value
self.program = program
self.msb = msb
self.lsb = lsb
self.lsb = lsb
self.shortInstrumentName = shortInstrumentName
self._secondInit(parentBlock = None) #see Item._secondInit.
@ -2664,7 +2664,7 @@ class InstrumentChange(Item):
self = cls.__new__(cls)
self.program = serializedObject["program"]
self.msb = serializedObject["msb"]
self.lsb = serializedObject["lsb"]
self.lsb = serializedObject["lsb"]
self.shortInstrumentName = serializedObject["shortInstrumentName"]
self.deserializeDurationAndNotelistInPlace(serializedObject)
self._secondInit(parentBlock = parentObject)
@ -2674,7 +2674,7 @@ class InstrumentChange(Item):
result = super().serialize() #call this in child classes
result["program"] = self.program
result["msb"] = self.msb
result["lsb"] = self.lsb
result["lsb"] = self.lsb
result["shortInstrumentName"] = self.shortInstrumentName
return result
@ -2700,17 +2700,17 @@ class InstrumentChange(Item):
"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(), 32, self.lsb), #position, status byte+channel, controller number, controller value
],
],
"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.
}
}
def _lilypond(self):
"""called by block.lilypond(), returns a string.
Don't create white-spaces yourself, this is done by the structures.
When in doubt prefer functionality and robustness over 'beautiful' lilypond syntax."""
if self.shortInstrumentName:
return f'\\mark \\markup {{\\small "{self.shortInstrumentName}"}}' + "\n" + f'\\set Staff.shortInstrumentName = #"{self.shortInstrumentName}"'
if self.shortInstrumentName:
return f'\\mark \\markup {{\\small "{self.shortInstrumentName}"}}' + "\n" + f'\\set Staff.shortInstrumentName = #"{self.shortInstrumentName}"'
else:
return ""
@ -2771,7 +2771,7 @@ class ChannelChange(Item):
"""called by block.lilypond(), returns a string.
Don't create white-spaces yourself, this is done by the structures.
When in doubt prefer functionality and robustness over 'beautiful' lilypond syntax."""
if self.text:
if self.text:
return f'\\mark \\markup {{\\small "{self.text}"}}'
else:
return ""
@ -2853,40 +2853,40 @@ class RecordedNote(Item):
class LilypondText(Item):
"""lilypond text as a last resort"""
def __init__(self, text):
super().__init__()
self.text = text
self.text = text
self._secondInit(parentBlock = None) #On item creation there is no parentBlock. The block adds itself to the item during insert.
def _secondInit(self, parentBlock):
"""see Item._secondInit"""
super()._secondInit(parentBlock) #Item._secondInit
def _lilypond(self):
"""called by block.lilypond(), returns a string.
Don't create white-spaces yourself, this is done by the structures.
When in doubt prefer functionality and robustness over 'beautiful' lilypond syntax."""
return self.text
When in doubt prefer functionality and robustness over 'beautiful' lilypond syntax."""
return self.text
@classmethod
def instanceFromSerializedData(cls, serializedObject, parentObject):
"""see Score.instanceFromSerializedData"""
"""see Score.instanceFromSerializedData"""
assert cls.__name__ == serializedObject["class"]
self = cls.__new__(cls)
self.text = serializedObject["text"]
self.text = serializedObject["text"]
self.deserializeDurationAndNotelistInPlace(serializedObject) #creates parentBlocks
self._secondInit(parentBlock = parentObject)
self._secondInit(parentBlock = parentObject)
return self
def serialize(self):
def serialize(self):
result = super().serialize() #call this in child classes
result["text"] = self.text
return result
def _copy(self):
"""return an independent copy of self"""
"""return an independent copy of self"""
new = LilypondText(self.text)
return new

91
engine/track.py

@ -373,30 +373,30 @@ class Track(object):
def __init__(self, parentData, name=None):
self.parentData = parentData
self.sequencerInterface = template.engine.sequencer.SequencerInterface(parentTrack=self, name=name)
self.sequencerInterface = template.engine.sequencer.SequencerInterface(parentTrack=self, name=name)
self.ccGraphTracks = {} #cc number, graphTrackCC. Every CC is its own SequencerTrack that routes to this tracks jack midi out
self.ccChannels = tuple() #unsorted numbers from 0-15 which represent the midi channels all CCs are sent to. Only replaced by a new tuple by the user directly. From json this becomes a list, so don't test for type. If empty then CC uses the initial midi channel.
self.blocks = [Block(track = self)]
self.blocks = [Block(track = self)]
self.durationSettingsSignature = DurationSettingsSignature() #only one per track
self.dynamicSettingsSignature = DynamicSettingsSignature() #only one per track
self.upbeatInTicks = 0 #playback does not care about upbeats. But exporting does. for example barlines.
self.dynamicSettingsSignature = DynamicSettingsSignature() #only one per track
self.upbeatInTicks = 0 #playback does not care about upbeats. But exporting does. for example barlines.
self.double = False #if true add 5 stafflines extra below. Clef will always stay in the upper staff. This is a representation-hint. E.g. a gui can offer a bigger staff system for the user.
#A set of starting values. They are user editable and represent the tracks playback state on head(). Therefore they get saved.
self.initialMidiChannel = 0 # 0-15
self.initialMidiProgram = -1 # 0-127. -1 is "don't send"
self.initialMidiBankMsb = 0 # 0-127. Depends on program >= 0
self.initialMidiBankLsb = 0 # 0-127. Depends on program >= 0
self.initialMidiBankLsb = 0 # 0-127. Depends on program >= 0
self.midiTranspose = 0 # -127 to +127 but the result is never outside of 0-127.1 Cannot change during the track.
#The instrument names are also handled by the trackState so that the cursor knows about instrument changes
self.initialInstrumentName = "" #different than the track name. e.g. "Violin"
self.initialShortInstrumentName = "" # e.g. "vl"
self.asMetronomeData = None #This track as metronome version. Is always up to date through export.
self._processAfterInit()
def _processAfterInit(self):
@ -417,8 +417,8 @@ class Track(object):
return self.sequencerInterface.name
@name.setter
def name(self, newValue):
self.sequencerInterface.name = newValue
def name(self, newValue):
self.sequencerInterface.name = newValue
def duration(self):
"""Return the duration of the whole track, in ticks"""
@ -569,7 +569,7 @@ class Track(object):
"""We don't use goToTickIndex for this because that
is a slow function that starts from head()"""
goalInTicks = self.state.tickindex - self.state.metricalInstruction().oneMeasureInTicks
if self.state.metricalInstruction().oneMeasureInTicks == 0:
goalInTicks -= D1
@ -808,7 +808,7 @@ class Track(object):
return result
#we don't need to unregister anything from cbox.
#Save / Load / Export
def lilypond(self):
"""Called by score.lilypond(), returns a string."""
@ -826,16 +826,16 @@ class Track(object):
return timeSig + upbeatLy + data
else:
return ""
def serialize(self)->dict:
return {
return {
"sequencerInterface" : self.sequencerInterface.serialize(),
"blocks" : [block.serialize() for block in self.blocks],
"ccGraphTracks" : {ccNumber:graphTrackCC.serialize() for ccNumber, graphTrackCC in self.ccGraphTracks.items()},
"durationSettingsSignature" : self.durationSettingsSignature.serialize(),
"dynamicSettingsSignature" : self.dynamicSettingsSignature.serialize(),
"double" : self.double,
"initialMidiChannel" : self.initialMidiChannel,
"initialMidiProgram" : self.initialMidiProgram,
@ -844,23 +844,23 @@ class Track(object):
"ccChannels" : self.ccChannels,
"midiTranspose" : self.midiTranspose,
"initialInstrumentName" : self.initialInstrumentName,
"initialShortInstrumentName" : self.initialShortInstrumentName,
"initialShortInstrumentName" : self.initialShortInstrumentName,
"upbeatInTicks" : self.upbeatInTicks,
}
@classmethod
def instanceFromSerializedData(cls, parentData, serializedData):
def instanceFromSerializedData(cls, parentData, serializedData):
self = cls.__new__(cls)
self.parentData = parentData
self.sequencerInterface = template.engine.sequencer.SequencerInterface.instanceFromSerializedData(self, serializedData["sequencerInterface"])
self.sequencerInterface = template.engine.sequencer.SequencerInterface.instanceFromSerializedData(self, serializedData["sequencerInterface"])
self.upbeatInTicks = int(serializedData["upbeatInTicks"])
self.blocks = [Block.instanceFromSerializedData(block, parentObject = self) for block in serializedData["blocks"]]
self.durationSettingsSignature = DurationSettingsSignature.instanceFromSerializedData(serializedData["durationSettingsSignature"], parentObject = self)
self.dynamicSettingsSignature = DynamicSettingsSignature.instanceFromSerializedData(serializedData["dynamicSettingsSignature"], parentObject = self)
self.double = serializedData["double"]
self.double = serializedData["double"]
self.initialMidiChannel = serializedData["initialMidiChannel"]
self.initialMidiProgram = serializedData["initialMidiProgram"]
self.initialMidiBankMsb = serializedData["initialMidiBankMsb"]
@ -869,8 +869,8 @@ class Track(object):
self.ccChannels = serializedData["ccChannels"]
self.midiTranspose = serializedData["midiTranspose"]
self.initialInstrumentName = serializedData["initialInstrumentName"]
self.initialShortInstrumentName = serializedData["initialShortInstrumentName"]
self.initialShortInstrumentName = serializedData["initialShortInstrumentName"]
self._processAfterInit()
return self
@ -929,7 +929,7 @@ class Track(object):
return result
def export(self)->dict:
return {
return {
"sequencerInterface" : self.sequencerInterface.export(),
}
@ -986,9 +986,9 @@ class Track(object):
#TODO: However, it is even less elegant to put this call in all rhythm editing methods and functions. inserts, block duplicate, content links, augment, tuplets undo etc.
#Taken out an placed in tempo Export. #self.score.tempoTrack.expandLastBlockToScoreDuration() #we guarantee that the tempo track is always at least as long as the music tracks.
midiNotesBinaryCboxData = bytes() # Create a binary blob that contains the MIDI events
midiNotesBinaryCboxData = bytes() # Create a binary blob that contains the MIDI events
instrumentChangesBinaryCboxData = bytes() # same as above, but only for instrument changes.
originalPosition = self.state.position()
self.getPreliminaryData()
barlines = OrderedDict() #tick:metricalInstruction . We do not use selft.state.barlines, which is simply barlines for the live cursor. This is to send Barlines to a UI and also to generate the Metronome by associating a metricalInstruction with each barline.
@ -1026,14 +1026,14 @@ class Track(object):
resultAppend(expObj)
dur = expObj["completeDuration"]
if expObj["type"] != "InstrumentChange":
for blob in expObj["midiBytes"]: #a list of
for blob in expObj["midiBytes"]: #a list of
midiNotesBinaryCboxData += blob
if expObj["type"] == "Chord" or expObj["type"] == "Rest": #save for later when we create Beams. No other use.
_allExportedChordsAppend(expObj)
else:
_allExportedChordsAppend(expObj)
else:
for blob in expObj["midiBytes"]: #a list of
instrumentChangesBinaryCboxData += blob
elif r == 2: #block end. Again, this is already the previous state. right now we are in the next block already.
lastBlock = self.blocks[self.state.blockindex-1] #why -1? see comment above
dur = lastBlock.staticExportEndMarkerDuration()
@ -1076,8 +1076,8 @@ class Track(object):
#Metronome start
#The metronome cannot be calculated by simply looking at metricalInstructions.
#We need to look at the barlines. Two instructions in a row at the same tick are wrong,
#but technically possible. These are not two full measures of metronome
self.asMetronomeData = tuple((pos, m.isMetrical, m.treeOfInstructions) for pos, m in barlines.items())
#but technically possible. These are not two full measures of metronome
self.asMetronomeData = tuple((pos, m.isMetrical, m.treeOfInstructions) for pos, m in barlines.items())
#Metronome end. Nothing below is connected with the metronome subtrack.
#Calculate the beam positions for the static groups.
@ -1120,14 +1120,14 @@ class Track(object):
if sum(s[2] for s in stems) >= 0: #beam upwards direction
direction = 1
length = -5
beamPosition = minimum + length
startDotOnLineKeyword = "highestPitchAsDotOnLine"
beamPosition = minimum + length - 1 #if not -1 it will be the same as the highest note, in wide intervals
startDotOnLineKeyword = "lowestPitchAsDotOnLine"
#assert beamPosition <= 0
else: #beam down direction
length = 5
beamPosition = maximum + length
beamPosition = maximum + length + 2 #if not +2 it will be in the position of lowest note, in wide intervals
direction = -1
startDotOnLineKeyword = "lowestPitchAsDotOnLine"
startDotOnLineKeyword = "highestPitchAsDotOnLine"
#assert beamPosition > 0
#beams have the same syntax and structure as a stem.
@ -1144,7 +1144,6 @@ class Track(object):
currentSubGroupFlag = obj["flag"]
subBeamGroups[-1].append(obj)
for subBeamGroup in subBeamGroups:
for obj in subBeamGroup:
startStaffLine = obj[startDotOnLineKeyword]
@ -1158,15 +1157,15 @@ class Track(object):
resultAppend(metaData)
metaData["barlines"] = barlines.keys()
metaData["duration"] = self.state.tickindex #tickindex is now at the end, so this is the end duration. This includes Blocks minimumDuration as well since it is included in left/right
metaData["beams"] = resultBeamGroups
#Notes
metaData["beams"] = resultBeamGroups
#Notes
t = (midiNotesBinaryCboxData, 0, self.state.tickindex)
self.sequencerInterface.setTrack([t]) #(bytes-blob, position, length) #tickindex is still on the last position, which means the second parameter is the length
#Instrument Changes
self.sequencerInterface.setSubtrack(key="instrumentChanges", blobs=[(instrumentChangesBinaryCboxData, 0, self.state.tickindex),]) #(bytes-blob, position, length)
self.sequencerInterface.setSubtrack(key="instrumentChanges", blobs=[(instrumentChangesBinaryCboxData, 0, self.state.tickindex),]) #(bytes-blob, position, length)
self.toPosition(originalPosition, strict = False) #has head() in it
return result

11
qtgui/items.py

@ -440,11 +440,20 @@ class GuiChord(GuiItem):
else:
stemOrBeam = self.staticItem["stem"]
#stem[2] is left/-1 or right/1 stem shifting.
#stemOrBeam = (starting point, length, direction) #0 is middle line, but the stem for 0 begins at -1, which is the room above.
#2 is treble-g which stem begins at 1.
#negative numbers are above the middle line with the same -1 stem offset, compared to the note.
#These numbers are calculated by the backend to span the whole chord. We just need to draw the stem here.
#For beam groups the length is different for each member because they get shorter/longer with each ascending/descending note
if stemOrBeam and stemOrBeam[2] > 0:
self.directionRightAndUpwards = False
else:
self.directionRightAndUpwards = True
print (stemOrBeam)
#Stem - Both for groups and standalone.
if stemOrBeam: #may be an empty tuple for whole notes and brevis
line = QtWidgets.QGraphicsLineItem(QtCore.QLineF(0, constantsAndConfigs.stafflineGap * stemOrBeam[1]/2, 0, 0)) #x1, y1, x2, y2
@ -461,7 +470,7 @@ class GuiChord(GuiItem):
self.flag = flag #store as persistent item. Otherwise qt will delete it.
flag.setPos(self.stem.pos().x(), self.stem.line().p1().y() + self.stem.pos().y()) #we already know where the stem-line is.
#Check if this item is the start or end of a beam group and mark it with lilypond syntax
#Check if this item is the start or end of a beam group and mark it with a textitem (lilypond syntax [ ])
if self.staticItem["beamGroup"]:
if self.staticItem["beamGroup"] == "open":
beamGroupGlyph = QtWidgets.QGraphicsSimpleTextItem("[")

6
qtgui/musicstructures.py

@ -360,6 +360,9 @@ class GuiTrack(QtWidgets.QGraphicsItem):
def createBeams(self, beamList):
"""This creates the beam-rectangle above/below the stems.
The stems theselves are created in items.py"""
for b in self.beams:
self.parentScore.removeWhenIdle(b)
self.beams = []
@ -378,7 +381,7 @@ class GuiTrack(QtWidgets.QGraphicsItem):
#non-scalable X-Offsets are not possible via setPos. zoom and stretch go haywire.
#Instead we use one item for strechting and for the offset position and wrap it in an item group that is used for positioing.
#Instead we use one item for streching for the offset position and wrap it in an item group that is used for positioing.
shifterAnchor = QtWidgets.QGraphicsItemGroup()
shifterAnchor.setParentItem(self)
@ -389,6 +392,7 @@ class GuiTrack(QtWidgets.QGraphicsItem):
shifterAnchor.rectangle = rectangle
rectangle.setPos(xOffset, 0)
#We need the beam no matter the note head mode.
if constantsAndConfigs.noteHeadMode:
rectangle.show()
else:

Loading…
Cancel
Save