From 53335d2bde28c48a4bbcb9e2dc8a0e8b117a6c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9-Paul=20Debroize?= Date: Mon, 10 Aug 2026 15:55:19 +0200 Subject: [PATCH] Add a LyricIdentifiers detail, off by default A lyric identifier ('verse', 'part1verse1', etc) names a verse inside the encoding and prints nothing in the score, so an OMR engine has nothing in the image to guess it from, and two encodings of the same music routinely name their verses differently. Give it its own bit, next to Style, Metadata and Voicing: outside of every combination, so AllObjects (the default) no longer counts a differing verse name as an error, and lyrics are compared on their syllables and verse numbers alone. Ask for it by hand ('-i allobjects lyricidentifiers') to compare the names too. --- musicdiff/__init__.py | 4 ++-- musicdiff/__main__.py | 14 ++++++++++---- musicdiff/annotation.py | 17 +++++++++-------- musicdiff/detaillevel.py | 11 +++++++++++ 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/musicdiff/__init__.py b/musicdiff/__init__.py index e6c714f..3c3e792 100644 --- a/musicdiff/__init__.py +++ b/musicdiff/__init__.py @@ -97,7 +97,7 @@ def diff( AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, - Style, Metadata, Voicing, or NoteStaffPosition. + LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. Returns: int | None: The total OMR Edit Distance, i.e. the number of individual symbols @@ -355,7 +355,7 @@ def diff_ml_training( AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, - Style, Metadata, Voicing, or NoteStaffPosition. + LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. Returns: tuple[float, str]: Overall OMR-NED score for the batch, and the full path to the diff --git a/musicdiff/__main__.py b/musicdiff/__main__.py index 801f12d..6a30d0a 100644 --- a/musicdiff/__main__.py +++ b/musicdiff/__main__.py @@ -25,8 +25,8 @@ ''' if __name__ == '__main__': usage: str = '''python3 -m musicdiff [-h] - [-i [{decoratednotesandrests,otherobjects,allobjects,style,metadata,voicing,notestaffposition,notesandrests,beams,tremolos,ornaments,articulations,ties,slurs,signatures,directions,barlines,staffdetails,chordsymbols,ottavas,arpeggios,lyrics} ...]] - [-x [{decoratednotesandrests,otherobjects,allobjects,style,metadata,voicing,notestaffposition,notesandrests,beams,tremolos,ornaments,articulations,ties,slurs,signatures,directions,barlines,staffdetails,chordsymbols,ottavas,arpeggios,lyrics} ...]] + [-i [{decoratednotesandrests,otherobjects,allobjects,style,metadata,voicing,notestaffposition,lyricidentifiers,notesandrests,beams,tremolos,ornaments,articulations,ties,slurs,signatures,directions,barlines,staffdetails,chordsymbols,ottavas,arpeggios,lyrics} ...]] + [-x [{decoratednotesandrests,otherobjects,allobjects,style,metadata,voicing,notestaffposition,lyricidentifiers,notesandrests,beams,tremolos,ornaments,articulations,ties,slurs,signatures,directions,barlines,staffdetails,chordsymbols,ottavas,arpeggios,lyrics} ...]] [-o [{visual,v,text,t,omrned,o} ...]] [--fix_first_file_syntax] file1 file2 @@ -37,8 +37,8 @@ --ground_truth_folder gtfolderpath --predicted_folder predfolderpath --output_folder outputfolderpath - [-i [{decoratednotesandrests,otherobjects,allobjects,style,metadata,voicing,notestaffposition,notesandrests,beams,tremolos,ornaments,articulations,ties,slurs,signatures,directions,barlines,staffdetails,chordsymbols,ottavas,arpeggios,lyrics} ...]] - [-x [{decoratednotesandrests,otherobjects,allobjects,style,metadata,voicing,notestaffposition,notesandrests,beams,tremolos,ornaments,articulations,ties,slurs,signatures,directions,barlines,staffdetails,chordsymbols,ottavas,arpeggios,lyrics} ...]] + [-i [{decoratednotesandrests,otherobjects,allobjects,style,metadata,voicing,notestaffposition,lyricidentifiers,notesandrests,beams,tremolos,ornaments,articulations,ties,slurs,signatures,directions,barlines,staffdetails,chordsymbols,ottavas,arpeggios,lyrics} ...]] + [-x [{decoratednotesandrests,otherobjects,allobjects,style,metadata,voicing,notestaffposition,lyricidentifiers,notesandrests,beams,tremolos,ornaments,articulations,ties,slurs,signatures,directions,barlines,staffdetails,chordsymbols,ottavas,arpeggios,lyrics} ...]] ''' epilog: str = '''\ @@ -102,6 +102,7 @@ 'metadata', 'voicing', 'notestaffposition', + 'lyricidentifiers', 'notesandrests', 'beams', @@ -135,6 +136,7 @@ 'metadata', 'voicing', 'notestaffposition', + 'lyricidentifiers', 'notesandrests', 'beams', @@ -246,6 +248,8 @@ detail |= DetailLevel.Metadata elif det == 'notestaffposition': detail |= DetailLevel.NoteStaffPosition + elif det == 'lyricidentifiers': + detail |= DetailLevel.LyricIdentifiers # bits in the DecoratedNotesAndRests combo elif det == 'notesandrests': @@ -300,6 +304,8 @@ detail &= ~DetailLevel.Metadata elif det == 'notestaffposition': detail &= ~DetailLevel.NoteStaffPosition + elif det == 'lyricidentifiers': + detail &= ~DetailLevel.LyricIdentifiers # bits in the DecoratedNotesAndRests combo elif det == 'notesandrests': diff --git a/musicdiff/annotation.py b/musicdiff/annotation.py index cbe3f5f..392606a 100755 --- a/musicdiff/annotation.py +++ b/musicdiff/annotation.py @@ -85,7 +85,7 @@ def __init__( AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, - Style, Metadata, Voicing, or NoteStaffPosition. + LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ''' super().__init__(general_note) self.is_in_chord: bool = False @@ -672,7 +672,7 @@ def __init__( AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, - Style, Metadata, Voicing, or NoteStaffPosition. + LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ''' super().__init__(extra) self.kind: str = M21Utils.extra_to_kind(extra) @@ -894,7 +894,7 @@ def __init__( AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, - Style, Metadata, Voicing, or NoteStaffPosition. + LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ''' super().__init__(lyric_holder) @@ -911,7 +911,8 @@ def __init__( if lyric.number is not None: self.number = lyric.number - if (lyric._identifier is not None + if (DetailLevel.includesLyricIdentifiers(detail) + and lyric._identifier is not None and lyric._identifier != lyric.number and lyric._identifier != str(lyric.number)): self.identifier = lyric._identifier @@ -1027,7 +1028,7 @@ def __init__( AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, - Style, Metadata, Voicing, or NoteStaffPosition. + LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ''' super().__init__(voice) note_list: list[m21.note.GeneralNote] = [] @@ -1176,7 +1177,7 @@ def __init__( AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, - Style, Metadata, Voicing, or NoteStaffPosition. + LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ''' super().__init__(measure) self.includes_voicing: bool = DetailLevel.includesVoicing(detail) @@ -1411,7 +1412,7 @@ def __init__( AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, - Style, Metadata, Voicing, or NoteStaffPosition. + LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ''' super().__init__(part) self.part_idx: int = part_idx @@ -1720,7 +1721,7 @@ def __init__( AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, - Style, Metadata, Voicing, or NoteStaffPosition. + LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ''' super().__init__(score) self.part_list: list[AnnPart] = [] diff --git a/musicdiff/detaillevel.py b/musicdiff/detaillevel.py index 1233317..35ed9be 100644 --- a/musicdiff/detaillevel.py +++ b/musicdiff/detaillevel.py @@ -108,6 +108,13 @@ class DetailLevel(IntEnum): # into every affected note. If this is set, NotesAndRests will also be considered set. NoteStaffPosition = 1 << 18 + # The names the encoding gives to the lyric verses ('verse', 'part1verse1', etc). + # They print nothing in the score, so they are off by default; turn them on to + # compare them. + # Note that if Lyrics is not also requested, no lyric identifier differences + # will be found. + LyricIdentifiers = 1 << 19 + # default detail level is all objects: Default = AllObjects @@ -193,6 +200,10 @@ def includesVoicing(cls, val: int) -> bool: def includesNoteStaffPosition(cls, val: int) -> bool: return val & cls.NoteStaffPosition != 0 + @classmethod + def includesLyricIdentifiers(cls, val: int) -> bool: + return val & cls.LyricIdentifiers != 0 + @classmethod def _included_m21_types(cls, val: int) -> tuple[t.Type, ...]: # Not all types go in here, just the ones where we will have pulled