Fractionnal offsets in musicxml

51 views
Skip to first unread message

Essi Parent

unread,
Feb 8, 2024, 4:27:15 PMFeb 8
to music21
Hi,

I couldn't find a way to properly display the following score.

from music21 import stream, note

pitches = [60, 62, 64, 65]
durations = [1, 1, 1, 1]
offsets = [0, 1, 2.5, 3]

s = stream.Part()
for i in range(4):
n_ = note.Note(pitch=pitches[i], quarterLength=durations[i])
s.insert(offsets[i], n_)

score = stream.Score()
score.append(s)
score.show()

When the offsets are all integers, or when the fraction is at the end of the list, the displayed score is fine. However, when a fractional offset is used elsewhere, the score is empty. If I run s.show() instead, notes are displayed, but the offset on item 2 (which is 2.5) is not displayed, even if I use s.show(quantizePost=False).

Any idea?

Essi

Cy Nelson

unread,
Feb 9, 2024, 12:33:23 PMFeb 9
to music21
Hi Essi

I've run into similar issues and found a work around by adding the offset as a lyric.

from music21 import stream, note

pitches = [60, 62, 64, 65]
durations = [1, 1, 1, 1]
offsets = [0, 1, 2.5, 3]

s = stream.Part()
for i in range(4):
    n_ = note.Note(pitch=pitches[i], quarterLength=durations[i])
    n_.addLyric(str(offsets[i]))
    s.append(n_)

score = stream.Score()
score.append(s)
score.show('musicxml')


Screenshot from 2024-02-09 09-29-51.png

Best,

Cy

Cy Nelson

unread,
Feb 9, 2024, 1:07:31 PMFeb 9
to music21
An idea for your consideration:

The list:

offsets = [0, 1, 2.5, 3]

contains both int and float datatypes. In my example, I used this list but I also used the str() function to convert both of these to a string datatype.

While Python handles mixed datatypes in lists, sometimes datatypes must be converted.  For me, it is easier to prevent errors by using the same datatype.  I would change the offset list to all floats:   [0.0, 1.0, 2.5, 3.0]

I'm open to discussion regarding best coding practices for others with more experience.

Essi Parent

unread,
Feb 13, 2024, 11:56:08 AMFeb 13
to music21
Thanks Cy! After scratching my head, I figured out that the score missed some rests and had note overlaps, which can cause troubles with score notation. I came up with these functions, the first to fill gaps with rests, the other to trim not durations which overlapped the next note. Thanks ChatGPT!


from music21 import stream, note

def fill_gaps_with_rests(s: stream.Stream, parent_offset: float = 0.0) -> stream.Stream:
"""
Recursively analyze a music21 stream (including Score, Part, Measure, Stream, etc.)
and insert rests to fill gaps between notes, ensuring that rests are inserted
at the correct offsets, considering the structure of the stream.

Args:
s (stream.Stream): The music21 stream to be processed.
parent_offset (float): The offset to consider from the parent stream, used in recursion.

Returns:
stream.Stream: The modified stream with gaps filled with rests.
"""
elements = list(s.elements)
last_offset = 0.0 # Keep track of the offset after the last note or rest
for element in elements:
if isinstance(element, (stream.Score, stream.Part, stream.Measure, stream.Stream)):
# If the element is a container, recursively process it
fill_gaps_with_rests(element, parent_offset + element.offset)
else:
current_offset = parent_offset + element.offset
if 'Note' in element.classes or 'Rest' in element.classes:
if current_offset > last_offset:
# There is a gap that needs to be filled with a rest
gap_duration = current_offset - last_offset
rest_to_insert = note.Rest(quarterLength=gap_duration)
s.insert(last_offset - parent_offset, rest_to_insert) # Adjust offset for insertion
element_end_offset = current_offset + element.duration.quarterLength
last_offset = max(last_offset, element_end_offset) # Update the last offset
# Handle the case where there's a gap at the end of the stream
if isinstance(s, stream.Measure) and last_offset < s.barDuration.quarterLength:
# If the stream is a Measure, ensure it's filled to its bar duration
gap_duration = s.barDuration.quarterLength - last_offset
rest_to_insert = note.Rest(quarterLength=gap_duration)
s.insert(last_offset - parent_offset, rest_to_insert)

return s

def adjust_note_durations_to_prevent_overlaps(s: stream.Stream) -> stream.Stream:
"""
Recursively adjust the durations of notes in a music21 stream (including scores, parts, measures, and any nested streams)
to prevent overlaps, while keeping their offsets intact.

Args:
s (stream.Stream): The music21 stream containing the notes or other streams to be adjusted.

Returns:
stream.Stream: The modified stream with adjusted note durations.
"""
if s.isStream:
s.sort() # Ensure the stream is sorted by offset
elements = list(s) # Work with a list of elements in the stream
notes = [e for e in elements if isinstance(e, note.Note)] # Get only Note objects
for i in range(len(notes) - 1): # Loop through all notes except the last one
current_note = notes[i]
next_note = notes[i + 1]
# Calculate the current end of the note
current_note_end = current_note.offset + current_note.duration.quarterLength
# If the current note ends after the next note starts, adjust its duration
if current_note_end > next_note.offset:
# Adjust duration to avoid overlap
new_duration = next_note.offset - current_note.offset
current_note.duration.quarterLength = new_duration
# Recursively adjust durations in nested streams (like Parts, Measures, Voices)
for el in elements:
if el.isStream:
adjust_note_durations_to_prevent_overlaps(el)

return s

pitches = [60, 62, 64, 65]
durations = [1, 1, 1, 1]
offsets = [0, 1, 2.5, 3]

s = stream.Part()
for i in range(4):
n_ = note.Note(pitch=pitches[i], quarterLength=durations[i])
s.insert(offsets[i], n_)

score = stream.Score()
score.append(s)
score = fill_gaps_with_rests(score)
score = adjust_note_durations_to_prevent_overlaps(score)
score.show()
Reply all
Reply to author
Forward
0 new messages