### Get note IDs for AnnVoice Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Returns a list of the GeneralNote ids for the annotated voice by extracting the `id` attribute from each annotated note. The return type is annotated as `list[str | int]`. ```python 1149 def get_note_ids(self) -> list[str | int]: 1150 ''' 1151 Computes a list of the GeneralNote ids for this `AnnVoice`. 1152 1153 Returns: 1154 [int]: A list containing the GeneralNote ids contained in this voice 1155 ''' 1156 return [an.id for an in self.annot_notes] ``` -------------------------------- ### create_header_names_once static method Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/visualization.html Initializes the Visualization class's header name mappings and ordered header names list once. If already initialized, returns early. When the detail level includes voicing, it updates the header name mapping with voicing-specific extras. Builds an ordered list of header names, appending a '% contribution to OMR-NED' variant for each. ```python @staticmethod def create_header_names_once(detail: DetailLevel | int): if Visualization._ORDERED_HEADER_NAMES: return if DetailLevel.includesVoicing(detail): Visualization._HEADER_NAME_OF_EDIT_NAME.update( Visualization._VOICING_HEADER_NAME_OF_EDIT_NAME_EXTRAS ) ordered_names: list[str] = [] for en in Visualization._HEADER_NAME_OF_EDIT_NAME: hn: str = Visualization._HEADER_NAME_OF_EDIT_NAME[en] if hn in ordered_names: continue ordered_names.append(hn) ordered_names.append(re.sub('OMR-ED', '% contribution to OMR-NED', hn)) Visualization._ORDERED_HEADER_NAMES = ordered_names ``` -------------------------------- ### get_output_csv_header static method Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/visualization.html Generates a CSV header string by concatenating pre-edit, ordered, and post-edit header names, each prefixed with a comma. Calls create_header_names_once to ensure header names are initialized. The header starts with an empty column for the 'Totals:' row. ```python @staticmethod def get_output_csv_header(detail: DetailLevel | int) -> str: Visualization.create_header_names_once(detail) header: str = '' for name in Visualization._PRE_EDITS_HEADER_NAMES: header += ', ' # even at start of header (empty column, for 'Totals:' at the bottom) header += name for name in Visualization._ORDERED_HEADER_NAMES: header += ', ' header += name for name in Visualization._POST_EDITS_HEADER_NAMES: header += ', ' header += name return header ``` -------------------------------- ### AnnNote.__init__ constructor Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Initializes an AnnNote by extending a music21 GeneralNote with precomputed comparison data. Handles chord membership, stores gap duration, beamings, tuplets, and computes a lowercase fullNameSuffix. When voicing is not included, records note offset and visual duration; when style is included, applies style from the individual note. ```python 63 def __init__( 64 self, 65 general_note: m21.note.GeneralNote, 66 gap_dur: OffsetQL, 67 enhanced_beam_list: list[str], 68 tuplet_list: list[str], 69 tuplet_info: list[str], 70 parent_chord: m21.chord.ChordBase | None = None, 71 chord_offset: OffsetQL | None = None, # only set if this note is inside a chord 72 detail: DetailLevel | int = DetailLevel.Default, 73 ) -> None: 74 ''' 75 Extend music21 GeneralNote with some precomputed, easily compared information about it. 76 77 Args: 78 general_note (music21.note.GeneralNote): The music21 note/chord/rest to extend. 79 gap_dur (OffsetQL): gap since end of last note (or since start of measure, if 80 first note in measure). Usually zero. 81 enhanced_beam_list (list): A list of beaming information about this GeneralNote. 82 tuplet_list (list): A list of basic tuplet info about this GeneralNote. 83 tuplet_info (list): A list of detailed tuplet info about this GeneralNote. 84 detail (DetailLevel | int): What level of detail to use during the diff. 85 Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently 86 AllObjects), or any combination (with | or &~) of those or NotesAndRests, 87 Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, 88 Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, 89 LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. 90 ''' 91 super().__init__(general_note) 92 self.is_in_chord: bool = False 93 self.note_idx_in_chord: int | None = None 94 if parent_chord is not None: 95 # This is what visualization uses to color the note red (chord id and note idx) 96 self.id = id(parent_chord) 97 self.ref = weakref.ref(parent_chord) 98 self.is_in_chord = True 99 self.note_idx_in_chord = parent_chord.notes.index(general_note) 100 101 # A lot of stuff is carried by the parent_chord (if present) or the 102 # general_note (if parent_chord not present); we call that the carrier 103 carrier: m21.note.GeneralNote = parent_chord or general_note 104 curr_clef: m21.clef.Clef | None = carrier.getContextByClass(m21.clef.Clef) 105 106 self.gap_dur: OffsetQL = gap_dur 107 self.beamings: list[str] = enhanced_beam_list 108 self.tuplets: list[str] = tuplet_list 109 self.tuplet_info: list[str] = tuplet_info 110 111 self.note_offset: OffsetQL = 0. 112 self.note_dur_type: str = '' 113 self.note_dur_dots: int = 0 114 self.graceness: str = '' # '', 'slash', 'noslash' 115 116 # fullNameSuffix is only for text output, it is not involved in comparison at all. 117 # It is of the form 'Dotted Quarter Rest', etc. 118 self.fullNameSuffix: str = general_note.duration.fullName 119 if isinstance(general_note, m21.note.Rest): 120 self.fullNameSuffix += ' Rest' 121 elif isinstance(general_note, m21.chord.ChordBase): 122 if parent_chord is None: 123 self.fullNameSuffix += ' Chord' 124 else: 125 # we're actually annotating one of the notes in the chord 126 self.fullNameSuffix += ' Note' 127 elif isinstance(general_note, (m21.note.Note, m21.note.Unpitched)): 128 self.fullNameSuffix += ' Note' 129 else: 130 self.fullNameSuffix += ' Note' 131 self.fullNameSuffix = self.fullNameSuffix.lower() 132 133 if not DetailLevel.includesVoicing(detail): 134 # if we're comparing the individual notes, we need to make a note of 135 # offset and visual duration to be used later when searching for matching 136 # notes in the measures being compared. 137 138 # offset 139 if chord_offset is None: 140 self.note_offset = general_note.offset 141 else: self.note_offset = chord_offset 142 143 # visual duration 144 self.note_dur_type = carrier.duration.type 145 self.note_dur_dots = carrier.duration.dots 146 147 if DetailLevel.includesStyle(detail): 148 # we will take style from the individual note, and then override with ``` -------------------------------- ### AnnPart.__init__ Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Initializes an AnnPart instance, extending a music21 Part/PartStaff with precomputed information for comparison. It processes measures into AnnMeasure objects and computes a precomputed hash string. ```APIDOC ## AnnPart.__init__ ### Description Initializes an AnnPart instance, extending a music21 Part/PartStaff with precomputed, easily compared information. It processes each measure in the part into AnnMeasure objects and computes a precomputed hash string for fast comparison. ### Method Constructor ### Signature ```python AnnPart( part: music21.stream.base.Part, score: music21.stream.base.Score, part_idx: int, spannerBundle: music21.spanner.SpannerBundle, detail: DetailLevel | int = ) ``` ### Parameters - **part** (music21.stream.Part, music21.stream.PartStaff) - Required - The music21 Part/PartStaff to extend. - **score** (music21.stream.Score) - Required - The enclosing music21 Score. - **part_idx** (int) - Required - The index of the part in the score. - **spannerBundle** (music21.spanner.SpannerBundle) - Required - A bundle of all the spanners in the score. - **detail** (DetailLevel | int) - Optional - What level of detail to use during the diff. Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ### Attributes Set - **part_idx** (int) - The index of the part. - **bar_list** (list[AnnMeasure]) - List of AnnMeasure objects for measures with elements. - **n_of_bars** (int) - Number of bars in the part. - **precomputed_str** (int) - Hash of the string representation of the part. - **_cached_notation_size** (int | None) - Cached notation size, initially None. ``` -------------------------------- ### objIsIncluded class method Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/detaillevel.html Checks if a given music21 object is included in the detail level. Uses _included_m21_types to get the types, then special-cases ChordSymbol to ensure it is only included when ChordSymbol is explicitly in the types list, not just because GeneralNote is included. Returns True if the object is an instance of the included types. ```python 286 @classmethod 287 def objIsIncluded(cls, obj: m21.base.Music21Object, val: int) -> bool: 288 types: tuple[t.Type, ...] = cls._included_m21_types(val) 289 290 # We have to check ChordSymbol by hand, since ChordSymbol is derived 291 # from GeneralNote, and should ONLY be included if ChordSymbol is in 292 # the list, NOT just because GeneralNote is in the list. 293 # I would note that GeneralNote is currently _never_ in the list, 294 # but I leave this code in place so that we don't break something 295 # unexpectedly if we put GeneralNote in the list in the future. 296 if isinstance(obj, m21.harmony.ChordSymbol): 297 return m21.harmony.ChordSymbol in types 298 299 return isinstance(obj, types) ``` -------------------------------- ### AnnNote.__init__ Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Initializes an AnnNote object, extending a music21 GeneralNote with precomputed comparison information. It sets attributes for gap duration, beamings, tuplets, and detail level, and handles notes within chords. ```APIDOC ## AnnNote.__init__ ### Description Initializes an AnnNote object, extending a music21 GeneralNote with precomputed comparison information. It sets attributes for gap duration, beamings, tuplets, and detail level, and handles notes within chords. ### Method Constructor ### Signature ```python AnnNote( general_note: music21.note.GeneralNote, gap_dur: float | fractions.Fraction, enhanced_beam_list: list[str], tuplet_list: list[str], tuplet_info: list[str], parent_chord: music21.chord.ChordBase | None = None, chord_offset: float | fractions.Fraction | None = None, detail: musicdiff.detaillevel.DetailLevel | int = ) ``` ### Parameters - **general_note** (music21.note.GeneralNote) - Required - The music21 note/chord/rest to extend. - **gap_dur** (float | fractions.Fraction) - Required - Gap since end of last note (or since start of measure, if first note in measure). Usually zero. - **enhanced_beam_list** (list[str]) - Required - A list of beaming information about this GeneralNote. - **tuplet_list** (list[str]) - Required - A list of basic tuplet info about this GeneralNote. - **tuplet_info** (list[str]) - Required - A list of detailed tuplet info about this GeneralNote. - **parent_chord** (music21.chord.ChordBase | None) - Optional - The parent chord if this note is inside a chord. - **chord_offset** (float | fractions.Fraction | None) - Optional - Offset to use if this note is inside a chord; only set if note is inside a chord. - **detail** (DetailLevel | int) - Optional - What level of detail to use during the diff. Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ### Attributes Set - **is_in_chord** (bool) - Whether this note is part of a chord. - **note_idx_in_chord** (int | None) - Index of this note within the parent chord, if applicable. - **gap_dur** (OffsetQL) - The gap duration. - **beamings** (list[str]) - The enhanced beam list. - **tuplets** (list[str]) - The tuplet list. - **tuplet_info** (list[str]) - The detailed tuplet info. - **note_offset** (OffsetQL) - The offset of the note (either from general_note.offset or chord_offset). - **note_dur_type** (str) - The duration type of the note (from carrier). - **note_dur_dots** (int) - The number of dots in the duration. - **graceness** (str) - Grace note type: '', 'slash', or 'noslash'. - **fullNameSuffix** (str) - A lowercase string describing the note type (e.g., 'dotted quarter rest', 'chord', 'note'). ### Notes - If parent_chord is provided, the note's id and ref are set to the parent chord's id and a weak reference to it. - The carrier (parent_chord or general_note) is used to get clef context and duration info. - If detail does not include voicing, note_offset and visual duration are computed. - If detail includes style, style is taken from the individual note and then overridden (as per source). ``` -------------------------------- ### AnnMetadataItem.__init__: initialize metadata item Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Initializes an AnnMetadataItem from a key and a value. Handles Text, Copyright, and Contributor values specially, converting them to strings and adjusting the key for poet/lyricist equivalence. Sets _cached_notation_size to None for lazy computation. ```python def __init__( self, key: str, value: t.Any ) -> None: # Normally this would be the id of the Music21Object, but we just have a key/value # pair, so we just make up an id, by using our own address. In this case, we will # not be looking this id up in the score, but only using it as a memo-ization key. super().__init__() self.key = key if isinstance(value, m21.metadata.Text): # Create a string representing the text, but not the language or isTranslated, # since language/isTranslated cannot be represented in many file formats. self.value = self.make_value_string(value) if isinstance(value, m21.metadata.Copyright): self.value += f' role={value.role}' elif isinstance(value, m21.metadata.Contributor): # Create a string (same thing: language and isTranslated will differ randomly) # Currently I am also ignoring more than one name, and birth/death. if not value._names: # ignore this metadata item self.key = '' self.value = '' return self.value = self.make_value_string(value) roleEmitted: bool = False if value.role: if value.role == 'poet': # special case: many MusicXML files have the lyricist listed as the poet. # We compare them as equivalent here. lyr: str = 'lyricist' self.key = lyr self.value += f'(role={lyr}' else: self.value += f'(role={value.role}' roleEmitted = True if roleEmitted: self.value += ')' else: # Date types self.value = str(value) self._cached_notation_size: int | None = None ``` -------------------------------- ### AnnVoice.__init__ Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Initializes an AnnVoice instance, extending a music21 Voice or Measure with precomputed comparison data. It extracts notes and grace notes, computes beaming and tuplet information, and creates AnnNote objects for each note. ```APIDOC ## AnnVoice.__init__ ### Description Initializes an AnnVoice instance, extending a music21 Voice or Measure with precomputed comparison data. It extracts notes and grace notes, computes beaming and tuplet information, and creates AnnNote objects for each note. ### Method Constructor ### Signature ```python AnnVoice( voice: music21.stream.base.Voice | music21.stream.base.Measure, enclosingMeasure: music21.stream.base.Measure, detail: musicdiff.detaillevel.DetailLevel | int = ) ``` ### Parameters - **voice** (music21.stream.base.Voice | music21.stream.base.Measure) - Required - The music21 voice to extend. Can be a Measure, but only if it contains no Voices. - **enclosingMeasure** (music21.stream.base.Measure) - Required - The enclosing Measure. - **detail** (DetailLevel | int) - Optional - What level of detail to use during the diff. Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. Defaults to DetailLevel.AllObjects. ### Attributes Set - **en_beam_list** (list[list[str]]) - List of enhanced beamings for each note. - **tuplet_list** (list[list[str]]) - Corrected tuplets with 'start' and 'continue'. - **tuplet_info** (list[list[str]]) - Tuplet information. - **annot_notes** (list[AnnNote]) - List of AnnNote objects with beaming and tuplet information attached. - **n_of_notes** (int) - Number of annotated notes. - **precomputed_str** (str) - Precomputed string representation. - **_cached_notation_size** (int | None) - Cached notation size. ### Notes Only ever called if detail includes Voicing. ``` -------------------------------- ### AnnVoice.__init__: Extend music21 Voice with precomputed diff data Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Constructor for AnnVoice, extending music21 Voice with precomputed comparison data. Only called when detail includes Voicing. It extracts notes and grace notes, computes beamings, tuplets, and per-note gap durations relative to expected offsets, then stores them in annot_notes. ```python 1014 def __init__( 1015 self, 1016 voice: m21.stream.Voice | m21.stream.Measure, 1017 enclosingMeasure: m21.stream.Measure, 1018 detail: DetailLevel | int = DetailLevel.Default 1019 ) -> None: 1020 ''' 1021 Extend music21 Voice with some precomputed, easily compared information about it. 1022 Only ever called if detail includes Voicing. 1023 1024 Args: 1025 voice (music21.stream.Voice or Measure): The music21 voice to extend. This 1026 can be a Measure, but only if it contains no Voices. 1027 enclosingMeasure (m21.stream.Measure): the enclosing Measure. 1028 detail (DetailLevel | int): What level of detail to use during the diff. 1029 Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently 1030 AllObjects), or any combination (with | or &~) of those or NotesAndRests, 1031 Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, 1032 Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, 1033 LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. 1034 ''' 1035 super().__init__(voice) 1036 note_list: list[m21.note.GeneralNote] = [] 1037 1038 if DetailLevel.includesNotesAndRests(detail): 1039 note_list = M21Utils.get_notes_and_gracenotes(voice) 1040 1041 self.en_beam_list: list[list[str]] = [] 1042 self.tuplet_list: list[list[str]] = [] 1043 self.tuplet_info: list[list[str]] = [] 1044 self.annot_notes: list[AnnNote] = [] 1045 1046 if note_list: 1047 self.en_beam_list = M21Utils.get_enhance_beamings( 1048 note_list, 1049 detail 1050 ) # beams ('partial' can mean partial beam or just a flag) 1051 self.tuplet_list = M21Utils.get_tuplets_type( 1052 note_list 1053 ) # corrected tuplets (with 'start' and 'continue') 1054 self.tuplet_info = M21Utils.get_tuplets_info(note_list, detail) 1055 # create a list of notes with beaming and tuplets information attached 1056 self.annot_notes = [] 1057 for i, n in enumerate(note_list): 1058 expectedOffsetInMeas: OffsetQL = 0 1059 if i > 0: 1060 prevNoteStart: OffsetQL = ( 1061 note_list[i - 1].getOffsetInHierarchy(enclosingMeasure) 1062 ) 1063 prevNoteDurQL: OffsetQL = ( 1064 note_list[i - 1].duration.quarterLength 1065 ) 1066 expectedOffsetInMeas = opFrac(prevNoteStart + prevNoteDurQL) 1067 1068 gapDurQL: OffsetQL = ( 1069 n.getOffsetInHierarchy(enclosingMeasure) - expectedOffsetInMeas 1070 ) 1071 self.annot_notes.append( 1072 AnnNote( 1073 n, 1074 gapDurQL, 1075 self.en_beam_list[i], 1076 self.tuplet_list[i], 1077 self.tuplet_info[i], 1078 detail=detail 1079 ) 1080 ) 1081 1082 self.n_of_notes: int = len(self.annot_notes) 1083 self.precomputed_str: str = self.__str__() 1084 self._cached_notation_size: int | None = None ``` -------------------------------- ### AnnMeasure.__init__() Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Extends a music21 Measure with precomputed, easily compared information about it. Initializes an AnnMeasure object. ```APIDOC ## AnnMeasure.__init__() ### Description Extend music21 Measure with some precomputed, easily compared information about it. ### Method `__init__(self, measure: m21.stream.Measure, prev_ann_measure, part: m21.stream.Part, score: m21.stream.Score, spannerBundle: m21.spanner.SpannerBundle, detail: DetailLevel | int = DetailLevel.Default) -> None` ### Parameters - **measure** (music21.stream.Measure) - Required - The music21 Measure to extend. - **prev_ann_measure** (AnnMeasure | None) - Required - The previous (annotated) measure. - **part** (music21.stream.Part) - Required - The enclosing music21 Part. - **score** (music21.stream.Score) - Required - The enclosing music21 Score. - **spannerBundle** (music21.spanner.SpannerBundle) - Required - A bundle of all the spanners in the score. - **detail** (DetailLevel | int) - Optional - What level of detail to use during the diff. Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ``` -------------------------------- ### AnnVoice.__init__ Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Initializes an AnnVoice object, extending a music21 Voice or Measure with precomputed information for diffing. Only called if detail includes Voicing. ```APIDOC ## AnnVoice.__init__ ### Description Initializes an AnnVoice object, extending a music21 Voice or Measure with precomputed information for diffing. Only called if detail includes Voicing. ### Method `__init__(self, voice: m21.stream.Voice | m21.stream.Measure, enclosingMeasure: m21.stream.Measure, detail: DetailLevel | int = DetailLevel.Default) -> None` ### Parameters - **voice** (music21.stream.Voice or Measure) - Required - The music21 voice to extend. This can be a Measure, but only if it contains no Voices. - **enclosingMeasure** (m21.stream.Measure) - Required - The enclosing Measure. - **detail** (DetailLevel | int) - Optional - What level of detail to use during the diff. Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ``` -------------------------------- ### AnnScore.__init__: initialize score annotation Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Initializes an AnnScore from a music21 score, storing it as a sequence of Full Trees. Accepts a DetailLevel or integer to control diff detail, and initializes empty lists for parts, staff groups, and metadata items, plus a syntax error counter. ```python class AnnScore(AnnObject): def __init__( self, score: m21.stream.Score, detail: DetailLevel | int = DetailLevel.Default ) -> None: ''' Take a music21 score and store it as a sequence of Full Trees. The hierarchy is 'score -> parts -> measures -> voices -> notes' Args: score (music21.stream.Score): The music21 score detail (DetailLevel | int): What level of detail to use during the diff. Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ''' super().__init__(score) self.part_list: list[AnnPart] = [] self.staff_group_list: list[AnnStaffGroup] = [] self.metadata_items_list: list[AnnMetadataItem] = [] self.num_syntax_errors_fixed: int = 0 ``` -------------------------------- ### AnnLyric __init__ method Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Initializes an AnnLyric object, extending a music21 GeneralNote with precomputed lyric comparison data. It stores the lyric's raw text, number, identifier (if included in detail level), style dictionary (if style is included), and precomputed string representation. The offset is obtained from the lyric holder within the measure. ```python def __init__( self, lyric_holder: m21.note.GeneralNote, # note containing the lyric lyric: m21.note.Lyric, # the lyric itself measure: m21.stream.Measure, detail: DetailLevel | int = DetailLevel.Default ) -> None: ''' Extend a lyric from a music21 GeneralNote with some precomputed, easily compared information about it. Args: lyric_holder (music21.note.GeneralNote): The note/chord containing the lyric. lyric (music21.note.Lyric): The music21 Lyric object to extend. measure (music21.stream.Measure): The music21 Measure the lyric was found in. If the lyric was found in a Voice, this is the Measure that the voice was found in. detail (DetailLevel | int): What level of detail to use during the diff. Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. ''' super().__init__(lyric_holder) # for comparison: lyric, number, identifier, offset, styledict self.lyric: str = '' self.number: int = 0 self.identifier: str = '' self.offset = lyric_holder.getOffsetInHierarchy(measure) # ignore .syllabic and .text, what is visible is .rawText (and there # are several .syllabic/.text combos that create the same .rawText). self.lyric = lyric.rawText # lyric.number is not compared, but it is used to sort lyrics # within a measure before comparison if lyric.number is not None: self.number = lyric.number 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 if DetailLevel.includesStyle(detail) and M21Utils.has_style(lyric): self.styledict = M21Utils.obj_to_styledict(lyric, detail) if self.styledict: # sort styleDict before converting to string so we can compare strings self.styledict = dict(sorted(self.styledict.items())) # precomputed/cached representations for faster comparison self.precomputed_str: str = self.__str__() self._cached_notation_size: int | None = None ``` -------------------------------- ### diff_ml_training function implementation Source: https://gregchapman-dev.github.io/musicdiff/musicdiff.html Compares two folders of musical scores and produces a CSV spreadsheet of results, including the overall OMR-NED score for the batch. Requires predicted and ground truth folders with matching filenames, and an output folder. The detail parameter controls comparison granularity. Returns a tuple of the overall OMR-NED score and the path to the output CSV. ```python def diff_ml_training( predicted_folder: str, ground_truth_folder: str, output_folder: str, detail: DetailLevel | int = DetailLevel.Default, ) -> tuple[float, str]: ''' Compare two folders of musical scores, and produce a CSV spreadsheet of results, including the overall OMR-NED score for the batch. Args: predicted_folder (str): The folder full of predicted scores. The scores can be of any format readable by music21 (e.g. MusicXML, MEI, Humdrum, etc). Each score must have the exact same filename as the corresponding ground truth score. ground_truth_folder (str): The folder full of ground truth scores. Each score must have the exact same filename as the corresponding predicted score. output_folder (str): The folder in which to save the output spreadsheet (output.csv). detail (DetailLevel | int): What level of detail to use during the comparisons. Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently AllObjects), or any combination (with | or &~) of those or NotesAndRests, Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. Returns: tuple[float, str]: Overall OMR-NED score for the batch, and the full path to the output spreadsheet (output.csv). ''' converter21.register() output_file_path: str = output_folder + '/output.csv' # expand tildes predicted_folder = os.path.expanduser(predicted_folder) ground_truth_folder = os.path.expanduser(ground_truth_folder) output_folder = os.path.expanduser(output_folder) metrics_list: list[EvaluationMetrics] = [] for name in os.listdir(predicted_folder): predpath: str = os.path.join(predicted_folder, name) # check if it is a file if not os.path.isfile(predpath): continue # check if there is a same-named file in ground_truth_folder gtpath: str = os.path.join(ground_truth_folder, name) if not os.path.isfile(gtpath): continue metrics: EvaluationMetrics | None = _diff_omr_ned_metrics( predpath=predpath, gtpath=gtpath, detail=detail ) if metrics is None: continue # append metrics to metrics_list metrics_list.append(metrics) # sort metrics_list the way you want it to appear in the csv file. # I like it sorted by omr_ned (ascending), so the omr_ned == 0.0 entries # are together at the top, and the omr_ned == 1.0 entries are together # at the bottom. Within each group of "same omr_ned", sort by filename. def natsortkey(path: str): # splits path into chunks of digits and non-digits. Converts the digit # chunks to integers for numerical comparison and the non-digit chunks # to lowercase for case-insensitive comparison. key: list[int | str] = [] for chunk in re.split(r'(\d+)', path): if chunk.isdigit(): key.append(int(chunk)) else: key.append(chunk.lower()) return key metrics_list.sort(key=lambda m: (m.omr_ned, natsortkey(str(m.gt_path)))) with open(output_file_path, 'wt', encoding='utf-8') as outf: print(Visualization.get_output_csv_header(detail), file=outf) for metrics in metrics_list: # append CSV line to output file # (gt path, pred path, gt numsyms, pred numsyms, sym edit cost, omr_ned print(Visualization.get_output_csv_line(metrics, detail), file=outf) # append overall score to output file (currently average SER) total_gt_numsyms: int = 0 total_pred_numsyms: int = 0 total_omr_edit_distance: int = 0 if metrics_list: for metrics in metrics_list: total_gt_numsyms += metrics.gt_numsyms total_pred_numsyms += metrics.pred_numsyms total_omr_edit_distance += metrics.omr_edit_distance overall_score: float = Visualization.get_omr_ned( total_omr_edit_distance, total_pred_numsyms, total_gt_numsyms ) print(Visualization.get_output_csv_trailer(metrics_list, detail), file=outf) outf.flush() return overall_score, output_file_path ``` -------------------------------- ### diff_ml_training(predicted_folder, ground_truth_folder, output_folder, detail=DetailLevel.AllObjects) Source: https://gregchapman-dev.github.io/musicdiff Compares predicted and ground truth scores in batch mode for machine learning training, returning the total cost and printing text output. ```APIDOC ## diff_ml_training(predicted_folder, ground_truth_folder, output_folder, detail=DetailLevel.AllObjects) ### Description Compares predicted and ground truth scores in batch mode for machine learning training. Returns the total cost and prints text output. ### Method Function call ### Endpoint `diff_ml_training(predicted_folder: str, ground_truth_folder: str, output_folder: str, detail: musicdiff.detaillevel.DetailLevel | int = ) -> tuple[float, str]` ### Parameters - **predicted_folder** (str) - Required - The folder containing predicted scores. - **ground_truth_folder** (str) - Required - The folder containing ground truth scores. - **output_folder** (str) - Required - The folder where output will be saved. - **detail** (DetailLevel | int) - Optional - The level of detail to use during the diff. Default is DetailLevel.AllObjects. ### Returns - **tuple[float, str]** - A tuple containing the total cost (float) and a string output. ``` -------------------------------- ### AnnVoice.__init__() - extend music21 Voice Source: https://gregchapman-dev.github.io/musicdiff/musicdiff/annotation.html Constructor for AnnVoice, extending a music21 Voice or Measure with precomputed comparison data. Only called when detail includes Voicing; initializes empty beam, tuplet, and annotated note lists, and populates note_list if NotesAndRests are included. ```python 1013class AnnVoice(AnnObject): 1014 def __init__( 1015 self, 1016 voice: m21.stream.Voice | m21.stream.Measure, 1017 enclosingMeasure: m21.stream.Measure, 1018 detail: DetailLevel | int = DetailLevel.Default 1019 ) -> None: 1020 ''' 1021 Extend music21 Voice with some precomputed, easily compared information about it. 1022 Only ever called if detail includes Voicing. 1023 1024 Args: 1025 voice (music21.stream.Voice or Measure): The music21 voice to extend. This 1026 can be a Measure, but only if it contains no Voices. 1027 enclosingMeasure (m21.stream.Measure): the enclosing Measure. 1028 detail (DetailLevel | int): What level of detail to use during the diff. 1029 Can be DecoratedNotesAndRests, OtherObjects, AllObjects, Default (currently 1030 AllObjects), or any combination (with | or &~) of those or NotesAndRests, 1031 Beams, Tremolos, Ornaments, Articulations, Ties, Slurs, Signatures, 1032 Directions, Barlines, StaffDetails, ChordSymbols, Ottavas, Arpeggios, Lyrics, 1033 LyricIdentifiers, Style, Metadata, Voicing, or NoteStaffPosition. 1034 ''' 1035 super().__init__(voice) 1036 note_list: list[m21.note.GeneralNote] = [] 1037 1038 if DetailLevel.includesNotesAndRests(detail): 1039 note_list = M21Utils.get_notes_and_gracenotes(voice) 1040 1041 self.en_beam_list: list[list[str]] = [] 1042 self.tuplet_list: list[list[str]] = [] 1043 self.tuplet_info: list[list[str]] = [] 1044 self.annot_notes: list[AnnNote] = [] 1045 1046 if note_list: ```