From db97d3caa4cc77a7a0e9fe485d4f4a3f550d5968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9-Paul=20Debroize?= Date: Wed, 30 Sep 2026 10:53:18 +0200 Subject: [PATCH] makeTies: fall back to the measure when the voice id is missing Look the voice up with voices.getElementById for string and int ids alike, so a string id absent from the next measure no longer raises KeyError. Fixes #2024. --- music21/stream/makeNotation.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/music21/stream/makeNotation.py b/music21/stream/makeNotation.py index c681463fb..c1dc87d90 100644 --- a/music21/stream/makeNotation.py +++ b/music21/stream/makeNotation.py @@ -7,7 +7,7 @@ # Jacob Walls # Evan Lynch # -# Copyright: Copyright © 2008-2024 Michael Scott Asato Cuthbert +# Copyright: Copyright © 2008-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ----------------------------------------------------------------------------- from __future__ import annotations @@ -1320,10 +1320,7 @@ def makeTies( # manage bridging voices if mNextHasVoices: if mHasVoices: # try to match voice id - if not isinstance(vId, int): - dst = mNext.voices[vId] - else: - dst = mNext.getElementById(vId) + dst = mNext.voices.getElementById(vId) # src does not have voice, but dst does else: # place in top-most voice dst = mNext.voices[0] @@ -2347,6 +2344,33 @@ def testMakeTiesChangingTimeSignatures(self): self.assertEqual(len(pp[stream.Measure][2].notes), 1) self.assertEqual(pp[stream.Measure][2].notes.first().duration.quarterLength, 24.0) + def testMakeTiesVoiceIdMissingFromNextMeasure(self): + ''' + A note crossing the barline in a voice whose (string) id is not + in the next measure goes to the measure itself, as it already + does for an int id, instead of raising KeyError. + + AI-assisted (Claude). + ''' + from music21 import stream + p = stream.Part() + m1 = stream.Measure(number=1) + m1.insert(0, meter.TimeSignature('4/4')) + soprano = stream.Voice(id='soprano') + soprano.insert(3, note.Note('C4', quarterLength=2.0)) + m1.insert(0, soprano) + m2 = stream.Measure(number=2) + m2.insert(0, stream.Voice(id='alto')) + p.insert(0, m1) + p.insert(4, m2) + + p.makeTies(inPlace=True) + self.assertEqual(soprano.notes.first().duration.quarterLength, 1.0) + self.assertEqual(soprano.notes.first().tie.type, 'start') + remainder = m2.getElementsByClass(note.Note).first() + self.assertEqual(remainder.duration.quarterLength, 1.0) + self.assertEqual(remainder.tie.type, 'stop') + def testConsolidateCompletedTupletsNoFalsePositive(self): from fractions import Fraction from music21 import converter