Skip to content

Commit e7ef83d

Browse files
musicdiff verson 3.1.0
Squashed commit of the following: commit 082377f Author: Greg Chapman <[email protected]> Date: Thu Mar 7 11:25:53 2024 -0800 Prepare for 3.1.0 release. commit 86ef128 Author: Greg Chapman <[email protected]> Date: Wed Mar 6 17:12:52 2024 -0800 Back out the pitch diff change (_do_ compare accidental and ties for rests, we annotated them as "None" and False, respectively). commit 9ee8a4c Author: Greg Chapman <[email protected]> Date: Wed Mar 6 16:02:43 2024 -0800 Lyrics is now part of AllObjects, not GeneralNotes. commit ce262bd Author: Greg Chapman <[email protected]> Date: Wed Mar 6 15:25:14 2024 -0800 Fix a bunch of stuff noticed when running new MusicXML import vs MEI/Humdrum export tests. Squashed commit of the following: commit 9618f6f Author: Greg Chapman <[email protected]> Date: Wed Mar 6 14:38:25 2024 -0800 Don't compare accidentals or ties if one or both notes are actually rests. commit 7c0ea3e Author: Greg Chapman <[email protected]> Date: Sat Mar 2 17:48:46 2024 -0800 Treat MetronomeMark with explicit number and no explicit text the same as one with no explicit number and explicit text saying "x = NNN" (where x is a SMUFL note code followed by 1-4 SMUFL dots). They are printed identically, so they are the same. commit abc005d Author: Greg Chapman <[email protected]> Date: Sun Feb 25 12:11:59 2024 -0800 Be smarter about what is a playback-only MetronomeMark. commit 15bb7df Author: Greg Chapman <[email protected]> Date: Sun Feb 25 11:46:21 2024 -0800 Ignore (as invisible) playback-only MetronomeMarks. commit a183cd3 Author: Greg Chapman <[email protected]> Date: Sun Jan 21 10:42:31 2024 -0800 Ignore empty lyrics. commit 115543e Author: Greg Chapman <[email protected]> Date: Sat Jan 20 16:01:55 2024 -0800 For lyrics, ignore placement=='below' and justify=='left', as equivalent to not specifying. commit bdffd48 Author: Greg Chapman <[email protected]> Date: Sat Jan 20 15:42:38 2024 -0800 Improve lyric style comparison, improve absoluteY <-> placement assumptions. commit d6d63fe Author: Greg Chapman <[email protected]> Date: Sat Jan 20 14:15:37 2024 -0800 Better comparison of lyrics; better comparison of style.absoluteY vs. style.placement. commit 0852f1d Author: Greg Chapman <[email protected]> Date: Sat Jan 20 11:57:00 2024 -0800 Ignore any StaffGroup that contains all the parts, and has no symbol and has no barThrough. commit b4314c7 Author: Greg Chapman <[email protected]> Date: Fri Jan 19 15:19:23 2024 -0800 Skip over non-realized ChordSymbols just like we do unsupported extras. commit 0e2e7e3 Author: Greg Chapman <[email protected]> Date: Fri Jan 19 15:13:04 2024 -0800 Compare general note offsets. commit 00ef8b0 Author: Greg Chapman <[email protected]> Date: Tue Jan 16 15:09:33 2024 -0800 Handle Grace vs Appoggiatura more carefully (MusicXML import creates GraceDurations that are accented). commit cd47caf Author: Greg Chapman <[email protected]> Date: Sun Jan 14 17:16:31 2024 -0800 Oops, we do care about style.alignVertical. commit 7ef1c32 Author: Greg Chapman <[email protected]> Date: Sun Jan 14 17:03:20 2024 -0800 And we don't need the invisible notes fix, since they were already being removed. commit ff4565c Author: Greg Chapman <[email protected]> Date: Sat Jan 13 16:25:39 2024 -0800 That last commit also ignores invisible notes during annotation. commit 353a2f8 Author: Greg Chapman <[email protected]> Date: Sat Jan 13 16:24:32 2024 -0800 Ignore more style stuff we don't do anything with yet. commit b007ccc Author: Greg Chapman <[email protected]> Date: Thu Jan 11 15:07:59 2024 -0800 Ignore extraneous SystemLayout and PageLayouts. commit f37d8a8 Author: Greg Chapman <[email protected]> Date: Thu Jan 11 14:00:32 2024 -0800 During visualization check for existence of pitch attribute before looking at it. commit 4046d50 Author: Greg Chapman <[email protected]> Date: Thu Jan 11 13:01:06 2024 -0800 Ignore fontSize, and regularize how 'bold' is represented. commit 4d263f5 Author: Greg Chapman <[email protected]> Date: Thu Jan 11 12:27:18 2024 -0800 Consider lyric.syllabic == None and 'single' to be exactly the same. commit 1e8083c Author: Greg Chapman <[email protected]> Date: Thu Jan 11 10:44:04 2024 -0800 Don't put style in lyric if it is empty (hasStyleInformation is not sufficient anymore). commit ac98055 Author: Greg Chapman <[email protected]> Date: Tue Jan 9 16:05:44 2024 -0800 Ignore note stemStyle. commit 6c92d39 Author: Greg Chapman <[email protected]> Date: Mon Jan 8 17:34:34 2024 -0800 Metadata: Contributor might not have any names. Deal with it. commit 7ea8505 Author: Greg Chapman <[email protected]> Date: Mon Jan 8 14:59:54 2024 -0800 MusicXML import often produces uninteresting style things (absX/absY/etc) that musicdiff should ignore. commit 673fbce Author: Greg Chapman <[email protected]> Date: Fri Jan 5 15:17:49 2024 -0800 No specified barline is the same as normal barline (at start and end of measure, that is) Ignore invisible ('none') barlines Compare tuplet_info (num format/visibility, bracket visibility), not just tuplet (start/continue/stop) Squashed commit of the following: commit e7d9d24 Author: Greg Chapman <[email protected]> Date: Thu Dec 28 15:34:00 2023 -0800 Only ignore regular barlines that are at start/end of measure. Do ignore hidden barlines. commit 42dffce Author: Greg Chapman <[email protected]> Date: Thu Dec 28 12:16:09 2023 -0800 Improve comparison of tuplets (we were only comparing starts and stops, missing out on numbers, brackets, visibility, etc). Also, regularize treatment of regular and invisible barlines, and stop comparing offset of barline within a measure, since that is redundant with all the object durations within that measure. commit 39dc387 Author: Greg Chapman <[email protected]> Date: Thu Nov 30 11:31:20 2023 -0800 And one more py.typed. commit 77fedeb Author: Greg Chapman <[email protected]> Date: Thu Nov 30 11:30:03 2023 -0800 Add py.typed so clients can use mypy. commit a3a1f60 Author: Greg Chapman <[email protected]> Date: Mon Nov 27 22:08:22 2023 -0800 Even more metadata keys to skip (because they are specific to one particular file). commit 338d5b3 Author: Greg Chapman <[email protected]> Date: Fri Nov 24 16:32:58 2023 -0800 Better annotation of metadata text. Also, ignore some metadata items. commit 3a5bf4b Author: Greg Chapman <[email protected]> Date: Fri Nov 10 12:29:23 2023 -0800 Date in LICENSE wasn't right. commit 2a7e62e Author: Greg Chapman <[email protected]> Date: Fri Nov 10 12:23:24 2023 -0800 Update docs again, to pick up new copyright years. commit afc1633 Author: Greg Chapman <[email protected]> Date: Fri Nov 10 12:22:28 2023 -0800 Update copyright years. commit a07df54 Author: Greg Chapman <[email protected]> Date: Fri Nov 10 12:18:12 2023 -0800 More last-minute tweaks. commit 0631249 Author: Greg Chapman <[email protected]> Date: Fri Nov 10 12:14:08 2023 -0800 Last tweaks for release 3. commit 52bb06b Author: Greg Chapman <[email protected]> Date: Fri Nov 10 11:32:39 2023 -0800 Re-generated docs. commit dd4e6d8 Author: Greg Chapman <[email protected]> Date: Tue Nov 7 21:24:46 2023 -0800 Handle spanners whose end is not in the score (I've seen a few), by pretending the end element isn't there. commit 21c7d7a Author: Greg Chapman <[email protected]> Date: Wed Oct 25 14:21:14 2023 -0700 Support comparison of metadata. Detail level is now bits ORed together. Squashed commit of the following: commit f51b59d Author: Greg Chapman <[email protected]> Date: Sat Oct 14 16:53:38 2023 -0700 Be judicious about Contributor and Text metadata annotations, so non-meaningful diffs don't show up. commit 93e3ed5 Author: Greg Chapman <[email protected]> Date: Sat Aug 26 11:38:34 2023 -0700 Check for metadata keys that start with 'raw:', 'meiraw:', 'humdrumraw:' more carefully ('raw:' in key is a bit cavalier). commit cbf501f Author: Greg Chapman <[email protected]> Date: Tue Aug 22 14:49:27 2023 -0700 Don't compare metadata that is uninterestingly different (e.g. fileFormat and various verbatim transcriptions of format-specific metadata). commit 20774c1 Author: Greg Chapman <[email protected]> Date: Sun Aug 20 11:38:04 2023 -0700 Proper support (and doc) for new Metadata detail level. Full internal support for any combination of Detail bits. commit 0f43bc1 Author: Greg Chapman <[email protected]> Date: Sat Aug 19 16:25:44 2023 -0700 Don't compare meiraw:meiHead metadata... the differences are redundant. commit 5856884 Author: Greg Chapman <[email protected]> Date: Fri Aug 18 09:57:36 2023 -0700 Don't crash if there is no metadata in the score. commit 0f2642a Author: Greg Chapman <[email protected]> Date: Thu Aug 17 17:40:37 2023 -0700 Sort metadata primitives by hand (music21 can't do it). commit 1d63b75 Author: Greg Chapman <[email protected]> Date: Thu Aug 17 16:36:53 2023 -0700 Don't compare metadata 'software' key; it's almost always different between two different versions of a score file. commit 07e5042 Author: Greg Chapman <[email protected]> Date: Sat Aug 12 16:12:11 2023 -0700 Compare metadata if detail & DetailLevel.Metadata != 0. Includes some (but not all) of the necessary changes to make DetailLevel a lot more flexible. What's missing is: full definitions of all the bits, and command line support for the new options. commit 1badb6a Author: Greg Chapman <[email protected]> Date: Sun Aug 6 12:55:42 2023 -0700 Squash merge of gregc/meiWriterStuff. Squashed commit of the following: commit c4970cf Author: Greg Chapman <[email protected]> Date: Sat Aug 5 12:55:55 2023 -0700 A fix to the new "ignore rest positions, but do all the rest of style" undocumented feature. commit c3a7783 Author: Greg Chapman <[email protected]> Date: Sat Aug 5 12:19:21 2023 -0700 Undocumented ability to disable rest position comparison (for use by converter21 tests that compare Humdrum import vs MEI import of verovio-converted Humdrum; verovio makes up rest positions for every rest that isn't explicitly positioned) commit b097632 Author: Greg Chapman <[email protected]> Date: Mon Jul 31 14:49:30 2023 -0700 If detail includes style, compare rest positioning. No longer compare chord.tie, since it is simply a derivation of all the note.tie in the chord. commit 30eb0a9 Author: Greg Chapman <[email protected]> Date: Fri Jul 21 17:44:19 2023 -0700 When annotating RepeatBrackets, include .overrideDisplay if present. commit 67e7fe6 Author: Greg Chapman <[email protected]> Date: Sun Jul 9 10:47:58 2023 -0700 Finish up support for comparing bowed and fingered tremolos. commit 676c783 Author: Greg Chapman <[email protected]> Date: Fri Jun 16 10:52:29 2023 -0700 Update tests to (1) use converter21's converters, (2) adjust expectations for that, and (3) fix up some tie stuff to match new reality. commit fb1c766 Author: Greg Chapman <[email protected]> Date: Wed Jun 14 13:50:40 2023 -0700 Fix tie diff. commit 1deca2e Author: Greg Chapman <[email protected]> Date: Mon May 29 16:30:54 2023 -0700 Don't crash if you see tuplet.type == 'startStop'. commit 0ee87a2 Author: Greg Chapman <[email protected]> Date: Mon May 29 13:45:50 2023 -0700 Lint. commit cdfa9bd Author: Greg Chapman <[email protected]> Date: Mon May 29 13:29:09 2023 -0700 Compare StaffLayout.staffLines (e.g. 5) and staffLayout.staffSize (percent of normal). commit ec533b1 Author: Greg Chapman <[email protected]> Date: Sun May 28 16:23:50 2023 -0700 If diffing at Style level of detail, compare placement (above/below) of articulations and expressions. commit d4c9182 Author: Greg Chapman <[email protected]> Date: Wed May 10 08:43:02 2023 -0700 StaffGroup.symbol (brace, bracket, line, etc) should be considered style, not substance. commit 58d226b Author: Greg Chapman <[email protected]> Date: Sun May 7 12:17:33 2023 -0700 Support comparison of StaffGroups. Squashed commit of the following: commit c0b7491 Author: Greg Chapman <[email protected]> Date: Sun May 7 10:21:12 2023 -0700 AnnStaffGroups are in AllObjects (like AnnExtras) commit 9934e79 Author: Greg Chapman <[email protected]> Date: Tue May 2 15:20:56 2023 -0700 Stop trying to differentiate between Part and PartStaff. That's not a visible difference. commit 2ef16d0 Author: Greg Chapman <[email protected]> Date: Tue May 2 15:13:43 2023 -0700 Support comparison of StaffGroups, and differentiate between Parts and PartStaffs. commit 21edc39 Author: Greg Chapman <[email protected]> Date: Fri Apr 21 17:35:15 2023 -0700 Update tests to include the new AnnNote parameter tuplet_info (tuplet number/bracket visibility and placement). commit e17a228 Author: Greg Chapman <[email protected]> Date: Fri Apr 21 16:57:41 2023 -0700 Update dependencies: python 3.10, converter21 3.0.0, music21 v9.1. commit 921d547 Author: Greg Chapman <[email protected]> Date: Fri Apr 21 16:52:41 2023 -0700 Squashed commit of the following: commit d897d39 Author: Greg Chapman <[email protected]> Date: Fri Apr 21 14:10:13 2023 -0700 Fix a couple of introduced bugs. commit 75182dd Author: Greg Chapman <[email protected]> Date: Fri Apr 21 13:57:53 2023 -0700 A bunch of new typing for mypy to think about. commit 17dcd40 Author: Greg Chapman <[email protected]> Date: Fri Apr 21 12:43:20 2023 -0700 Get pylint, mypy, flake8 all happy. commit 237e41f Author: Greg Chapman <[email protected]> Date: Tue Apr 18 15:24:07 2023 -0700 Support comparison of ornament accidentals. Squashed commit of the following: commit d75e1a3 Author: Greg Chapman <[email protected]> Date: Sat Apr 15 17:21:42 2023 -0700 Do proper annotation of InvertedMordent, too, not just Mordent. commit f97099a Author: Greg Chapman <[email protected]> Date: Sat Apr 15 15:08:33 2023 -0700 Add expression_to_string so we only see ornament accidental names if the accidental is visible. commit b45bf9f Author: Greg Chapman <[email protected]> Date: Mon Mar 27 19:59:24 2023 -0700 Undo some of that (match music21 changes). commit 4be373e Author: Greg Chapman <[email protected]> Date: Sat Mar 25 16:32:24 2023 -0700 Custom name for Turn/Mordent/Trill that includes accidentals, but only those with displayStatus == True. commit b7835fd Author: Greg Chapman <[email protected]> Date: Fri Mar 10 13:21:05 2023 -0800 Support comparison of tuplet visibility/format/placement (placement only if diffing with style). commit 143edd1 Author: Greg Chapman <[email protected]> Date: Sat Feb 11 10:35:28 2023 -0800 Support comparison of page breaks and system breaks (but only if AllObjectsWithStyle is requested). commit e0b5a9e Author: Greg Chapman <[email protected]> Date: Fri Feb 10 13:03:49 2023 -0800 Support for Ottavas (filling and transposition), and delayed turns. Squashed commit of the following: commit a00f0de Author: Greg Chapman <[email protected]> Date: Tue Feb 7 14:59:56 2023 -0800 Support delayed vs non-delayed turns. No actual code changes necessary yet, since if music21 supports them, the turn.name will reflect the delay (and thus be incorporated into the diff). commit 3f53d2f Author: Greg Chapman <[email protected]> Date: Fri Feb 3 13:19:37 2023 -0800 Preserve accidental display status when transposing to written pitch. commit 888e3d8 Author: Greg Chapman <[email protected]> Date: Fri Jan 27 17:27:47 2023 -0800 Catch up with music21 gregc/ottavaTransposeFix. commit 2058a72 Author: Greg Chapman <[email protected]> Date: Fri Jan 20 14:00:45 2023 -0800 Keep up with music21 gregc/ottavaTransposeFix. commit 5b4ef4d Author: Greg Chapman <[email protected]> Date: Fri Jan 20 13:27:03 2023 -0800 Pick up a lost fix from main branch. commit 007b0ff Merge: 5f264da a0a2b0a Author: Greg Chapman <[email protected]> Date: Fri Jan 20 10:37:25 2023 -0800 Merge branch 'main' into gregc/ottavas commit 5f264da Author: Greg Chapman <[email protected]> Date: Sun Jan 15 14:46:54 2023 -0800 Use new converter21.register() API if it is there. commit 49c8212 Author: Greg Chapman <[email protected]> Date: Wed Dec 7 17:14:45 2022 -0800 Before de-duping clefs from multiple voices, sort initialList by offset in measure, so potential duplicates are next to each other. commit 717d21f Author: Greg Chapman <[email protected]> Date: Tue Dec 6 17:12:21 2022 -0800 Ignore redundant clef changes. commit 7990f54 Author: Greg Chapman <[email protected]> Date: Mon Dec 5 15:09:42 2022 -0800 Make ArpeggioMarkSpanner duration consistent when order of elements is different (0 will do). commit 000b9b7 Author: Greg Chapman <[email protected]> Date: Mon Dec 5 12:56:41 2022 -0800 Lose the lint. commit b2749a4 Author: Greg Chapman <[email protected]> Date: Mon Dec 5 12:52:18 2022 -0800 Instead of getFirst() to find primary location of spanner, for ArpeggioMarkSpanner use getHighestDiatonicNoteOrChord() so a difference in order of ArpeggioMarkSpanner elements is not seen as a difference worth noting. commit d865632 Author: Greg Chapman <[email protected]> Date: Sat Dec 3 12:46:04 2022 -0800 Change the name of a M21Utils routine. commit 4944340 Author: Greg Chapman <[email protected]> Date: Fri Dec 2 17:41:07 2022 -0800 Actually compare ottavas themselves. Deal with it if music21 doesn't have fillIntermediateSpannedElements or inheritAccidentalDisplay. commit 30f00a0 Author: Greg Chapman <[email protected]> Date: Thu Dec 1 14:16:17 2022 -0800 A couple fixes around inheritsAccidentalDisplay. commit 0014f92 Author: Greg Chapman <[email protected]> Date: Tue Nov 29 20:32:02 2022 -0800 At start of annotation, transpose the entire score to written pitch. This means we will be comparing apples with apples, both for transposing instruments, ottavas, and sometimes both at the same time.
1 parent c6c9afc commit e7ef83d

File tree

10 files changed

+433
-93
lines changed

10 files changed

+433
-93
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
The MIT License (MIT)
3-
Copyright (c) 2022, 2023 Francesco Foscarin, Greg Chapman
3+
Copyright (c) 2022-2024 Francesco Foscarin, Greg Chapman
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
66

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ musicdiff is derived from: [music-score-diff](https://github.com/fosfrancesco/mu
77
by [Francesco Foscarin](https://github.com/fosfrancesco).
88

99
## Setup
10-
Depends on [music21](https://pypi.org/project/music21) (version 9.1+), [numpy](https://pypi.org/project/numpy), and [converter21](https://pypi.org/project/converter21) (version 3.0+). You also will need to configure music21 (instructions [here](https://web.mit.edu/music21/doc/usersGuide/usersGuide_01_installing.html)) to display a musical score (e.g. with MuseScore). Requires Python 3.10+.
10+
Depends on [music21](https://pypi.org/project/music21) (version 9.1+), [numpy](https://pypi.org/project/numpy), and [converter21](https://pypi.org/project/converter21) (version 3.1+). You also will need to configure music21 (instructions [here](https://web.mit.edu/music21/doc/usersGuide/usersGuide_01_installing.html)) to display a musical score (e.g. with MuseScore). Requires Python 3.10+.
1111

1212
## Usage
1313
On the command line:

ReleaseNotes_3.1.0.txt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
Changes since 3.0.0:
2+
3+
Feature-level changes:
4+
- Move lyrics diff from GeneralNotesOnly detail level to AllObjects detail level.
5+
- Compare GeneralNote offsets (visually, this is horizontal position in measure).
6+
- compare tuplet_info (num and numBase, num format/visibility, bracket visibility),
7+
not just tuplet (start/continue/stop). This is technically a bugfix, but tuplet
8+
comparisons were pretty much not working at all, so I'm calling it a new feature.
9+
- Add py.typed, so clients can do mypy against musicdiff APIs.
10+
11+
Bugfixes:
12+
Metadata:
13+
- Contributor might not have any names. Deal with it.
14+
- skip more metadata keys (that are specific to a particular file, so shouldn't
15+
be compared against another file): 'EMD', 'EST', 'VTS', 'RLN', 'PUB'.
16+
- html-unescape during annotation of metadata items.
17+
Style:
18+
- Ignore some style stuff we don't care about (e.g. absX/absY).
19+
- Ignore fontSize, and regularize how 'bold' is represented.
20+
Lyrics:
21+
- Don't annotate lyric style if it is empty (hasStyleInformation could still be True).
22+
- Consider lyric.syllabic == None and 'single' to be the same.
23+
- Better comparison of lyrics, treat style.absoluteY as placement fallback when
24+
placement is None.
25+
- For lyrics, ignore placement=='below' and justify=='left', as equivalent to not
26+
specifying.
27+
- Ignore empty lyrics.
28+
MetronomeMarks:
29+
- Ignore playback-only MetronomeMarks (they're not marked invisible, but they are
30+
not printed).
31+
- Treat MetronomeMark with explicit number and no explicit text the same as one with no
32+
explicit number and explicit text saying "x = NNN" (where x is a SMUFL note code
33+
followed by 1-4 SMUFL dots). They are printed identically, so they are the same.
34+
Barlines:
35+
- no specified barline is the same as normal barline (at start and end of measure,
36+
that is).
37+
- ignore invisible ('none') barlines.
38+
Other fixes:
39+
- Fix crash when diff is visualized between note with accidental and unpitched note/rest
40+
- Ignore redundant system and page breaks
41+
- Grace vs Appoggiatura is more complex (in music21) than I thought.
42+
- Skip over non-realized ChordSymbols (realized ChordSymbols get treated as Chords)
43+
- Skip over StaffGroup that contains all the staves and has neither barTogether nor
44+
symbol. Some file formats have one (MEI), and some file formats imply/don't need
45+
it (MusicXML).
46+

musicdiff/annotation.py

Lines changed: 105 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414

1515
__docformat__ = "google"
1616

17+
import html
1718
from fractions import Fraction
18-
1919
import typing as t
2020

2121
import music21 as m21
22+
from music21.common.numberTools import OffsetQL
2223

2324
from musicdiff import M21Utils
2425
from musicdiff import DetailLevel
@@ -27,6 +28,7 @@ class AnnNote:
2728
def __init__(
2829
self,
2930
general_note: m21.note.GeneralNote,
31+
offsetInMeasure: OffsetQL,
3032
enhanced_beam_list: list[str],
3133
tuplet_list: list[str],
3234
tuplet_info: list[str],
@@ -46,6 +48,7 @@ def __init__(
4648
4749
"""
4850
self.general_note: int | str = general_note.id
51+
self.offsetInMeasure: OffsetQL = offsetInMeasure
4952
self.beamings: list[str] = enhanced_beam_list
5053
self.tuplets: list[str] = tuplet_list
5154
self.tuplet_info: list[str] = tuplet_info
@@ -99,7 +102,21 @@ def __init__(
99102
self.graceType: str = 'acc'
100103
self.graceSlash: bool | None = general_note.duration.slash
101104
elif isinstance(general_note.duration, m21.duration.GraceDuration):
102-
self.graceType = 'nonacc'
105+
# might be accented or unaccented. duration.slash isn't always reliable
106+
# (historically), but we can use it as a fallback.
107+
# Check duration.stealTimePrevious and duration.stealTimeFollowing first.
108+
if general_note.duration.stealTimePrevious is not None:
109+
self.graceType = 'unacc'
110+
elif general_note.duration.stealTimeFollowing is not None:
111+
self.graceType = 'acc'
112+
elif general_note.duration.slash is True:
113+
self.graceType = 'unacc'
114+
elif general_note.duration.slash is False:
115+
self.graceType = 'acc'
116+
else:
117+
# by default, GraceDuration with no other indications (slash is None)
118+
# is assumed to be unaccented.
119+
self.graceType = 'unacc'
103120
self.graceSlash = general_note.duration.slash
104121
else:
105122
self.graceType = ''
@@ -119,20 +136,25 @@ def __init__(
119136

120137
# lyrics
121138
self.lyrics: list[str] = []
122-
for lyric in general_note.lyrics:
123-
lyricStr: str = ""
124-
if lyric.number is not None:
125-
lyricStr += f"number={lyric.number}"
126-
if lyric._identifier is not None:
127-
lyricStr += f" identifier={lyric._identifier}"
128-
if lyric.syllabic is not None:
129-
lyricStr += f" syllabic={lyric.syllabic}"
130-
if lyric.text is not None:
131-
lyricStr += f" text={lyric.text}"
132-
lyricStr += f" rawText={lyric.rawText}"
133-
if M21Utils.has_style(lyric):
134-
lyricStr += f" style={M21Utils.obj_to_styledict(lyric, detail)}"
135-
self.lyrics.append(lyricStr)
139+
if DetailLevel.includesLyrics(detail):
140+
for lyric in general_note.lyrics:
141+
if not lyric.rawText:
142+
continue
143+
lyricStr: str = ""
144+
if lyric.number is not None:
145+
lyricStr += f"number={lyric.number}"
146+
if lyric._identifier is not None:
147+
lyricStr += f" identifier={lyric._identifier}"
148+
# ignore .syllabic and .text, what is visible is .rawText (and there
149+
# are several .syllabic/.text combos that create the same .rawText).
150+
lyricStr += f" rawText={lyric.rawText}"
151+
if M21Utils.has_style(lyric):
152+
styleDict: dict[str, str] = M21Utils.obj_to_styledict(lyric, detail)
153+
if styleDict:
154+
# sort styleDict before converting to string so we can compare strings
155+
styleDict = dict(sorted(styleDict.items()))
156+
lyricStr += f" style={styleDict}"
157+
self.lyrics.append(lyricStr)
136158

137159
# precomputed representations for faster comparison
138160
self.precomputed_str: str = self.__str__()
@@ -239,6 +261,9 @@ def __str__(self) -> str:
239261
if self.stemDirection != 'unspecified':
240262
string += f"stemDirection={self.stemDirection}"
241263

264+
# offset
265+
string += f" {self.offsetInMeasure}"
266+
242267
# and then the style fields
243268
for i, (k, v) in enumerate(self.styledict.items()):
244269
if i > 0:
@@ -335,6 +360,11 @@ def __init__(
335360
except m21.sites.SitesException:
336361
endOffsetInScore = startOffsetInScore
337362
self.duration = endOffsetInScore - startOffsetInScore
363+
elif isinstance(extra, m21.bar.Barline):
364+
# we ignore offset for barlines; barline offset is derived from the objects in the
365+
# measure, which are already being compared.
366+
self.offset = 0.0
367+
self.duration = float(extra.duration.quarterLength)
338368
else:
339369
self.offset = float(extra.getOffsetInHierarchy(measure))
340370
self.duration = float(extra.duration.quarterLength)
@@ -344,7 +374,21 @@ def __init__(
344374

345375
if M21Utils.has_style(extra):
346376
# includes extra.placement if present
347-
self.styledict = M21Utils.obj_to_styledict(extra, detail)
377+
378+
# special case: MM with text='SMUFLNote = nnn" is being annotated as if there is
379+
# no text, so none of the text style stuff should be added.
380+
smuflTextSuppressed: bool = False
381+
if (isinstance(extra, m21.tempo.MetronomeMark)
382+
and not extra.textImplicit
383+
and extra.text
384+
and not self.content.startswith('MM:TX:')):
385+
smuflTextSuppressed = True
386+
387+
self.styledict = M21Utils.obj_to_styledict(
388+
extra,
389+
detail,
390+
smuflTextSuppressed=smuflTextSuppressed
391+
)
348392

349393
# so far, always 1, but maybe some extra will be bigger someday
350394
self._notation_size: int = 1
@@ -386,6 +430,7 @@ class AnnVoice:
386430
def __init__(
387431
self,
388432
voice: m21.stream.Voice | m21.stream.Measure,
433+
enclosingMeasure: m21.stream.Measure,
389434
detail: DetailLevel = DetailLevel.Default
390435
) -> None:
391436
"""
@@ -421,9 +466,11 @@ def __init__(
421466
# create a list of notes with beaming and tuplets information attached
422467
self.annot_notes = []
423468
for i, n in enumerate(note_list):
469+
offset: OffsetQL = n.getOffsetInHierarchy(enclosingMeasure)
424470
self.annot_notes.append(
425471
AnnNote(
426472
n,
473+
offset,
427474
self.en_beam_list[i],
428475
self.tuplet_list[i],
429476
self.tuplet_info[i],
@@ -510,19 +557,19 @@ def __init__(
510557

511558
if len(measure.voices) == 0:
512559
# there is a single AnnVoice (i.e. in the music21 Measure there are no voices)
513-
ann_voice = AnnVoice(measure, detail)
560+
ann_voice = AnnVoice(measure, measure, detail)
514561
if ann_voice.n_of_notes > 0:
515562
self.voices_list.append(ann_voice)
516563
else: # there are multiple voices (or an array with just one voice)
517564
for voice in measure.voices:
518-
ann_voice = AnnVoice(voice, detail)
565+
ann_voice = AnnVoice(voice, measure, detail)
519566
if ann_voice.n_of_notes > 0:
520567
self.voices_list.append(ann_voice)
521568
self.n_of_voices: int = len(self.voices_list)
522569

523570
self.extras_list: list[AnnExtra] = []
524571
if DetailLevel.includesOtherMusicObjects(detail):
525-
for extra in M21Utils.get_extras(measure, part, spannerBundle, detail):
572+
for extra in M21Utils.get_extras(measure, part, score, spannerBundle, detail):
526573
self.extras_list.append(AnnExtra(extra, measure, score, detail))
527574

528575
# For correct comparison, sort the extras_list, so that any list slices
@@ -756,11 +803,24 @@ def __init__(
756803
if isinstance(value, m21.metadata.Text):
757804
# Create a string representing both the text and the language, but not isTranslated,
758805
# since isTranslated cannot be represented in many file formats.
759-
self.value = str(value) + f'(language={value.language})'
806+
self.value = (
807+
self.make_value_string(value)
808+
+ f'(language={value.language})'
809+
)
760810
elif isinstance(value, m21.metadata.Contributor):
761811
# Create a string (same thing: value.name.isTranslated will differ randomly)
762812
# Currently I am also ignoring more than one name, and birth/death.
763-
self.value = str(value) + f'(role={value.role}, language={value._names[0].language})'
813+
self.value = self.make_value_string(value)
814+
roleEmitted: bool = False
815+
if value.role:
816+
self.value += f'(role={value.role}'
817+
roleEmitted = True
818+
if value._names:
819+
if roleEmitted:
820+
self.value += ', '
821+
self.value += f'language={value._names[0].language}'
822+
if roleEmitted:
823+
self.value += ')'
764824
else:
765825
self.value = value
766826

@@ -791,6 +851,12 @@ def notation_size(self) -> int:
791851
"""
792852
return 1
793853

854+
def make_value_string(self, value: m21.metadata.Contributor | m21.metadata.Text) -> str:
855+
# Unescapes a bunch of stuff
856+
output: str = str(value)
857+
output = html.unescape(output)
858+
return output
859+
794860

795861
class AnnScore:
796862
def __init__(
@@ -834,6 +900,13 @@ def __init__(
834900
if DetailLevel.includesOtherMusicObjects(detail):
835901
# staffgroups are extras (a.k.a. OtherMusicObjects)
836902
for staffGroup in score[m21.layout.StaffGroup]:
903+
# ignore any StaffGroup that contains all the parts, and has no symbol
904+
# and has no barthru (this is just a placeholder generated by some
905+
# file formats, and has the same meaning if it is missing).
906+
if len(staffGroup) == len(part_to_index):
907+
if not staffGroup.symbol and not staffGroup.barTogether:
908+
continue
909+
837910
ann_staff_group = AnnStaffGroup(staffGroup, part_to_index, detail)
838911
if ann_staff_group.n_of_parts > 0:
839912
self.staff_group_list.append(ann_staff_group)
@@ -855,6 +928,16 @@ def __init__(
855928
# 'raw:freeform', 'humdrumraw:XXX'), it's often deleted
856929
# when made obsolete by conversions/edits.
857930
continue
931+
if key in ('humdrum:EMD', 'humdrum:EST', 'humdrum:VTS',
932+
'humdrum:RLN', 'humdrum:PUB'):
933+
# Don't compare metadata items that should never be transferred
934+
# from one file to another. 'humdrum:EMD' is a modification
935+
# description entry, humdrum:EST is "current encoding status"
936+
# (i.e. complete or some value of not complete), 'humdrum:VTS'
937+
# is a checksum of the Humdrum file, 'humdrum:RLN' is the
938+
# extended ASCII encoding of the Humdrum file, 'humdrum:PUB'
939+
# is the publication status of the file (published or not?).
940+
continue
858941
self.metadata_items_list.append(AnnMetadataItem(key, value))
859942

860943
def __eq__(self, other) -> bool:

musicdiff/comparison.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ def _pitches_diff(pitch1, pitch2, noteNode1, noteNode2, ids):
349349
op_list.append(("pitchtypeedit", noteNode1, noteNode2, 1, ids))
350350
else: # they are two notes
351351
op_list.append(("pitchnameedit", noteNode1, noteNode2, 1, ids))
352+
352353
# add for the accidentals
353354
if pitch1[1] != pitch2[1]: # if the accidental is different
354355
cost += 1
@@ -896,6 +897,13 @@ def _annotated_note_diff(annNote1: AnnNote, annNote2: AnnNote):
896897
)
897898
op_list.extend(tuplet_op_list)
898899
cost += tuplet_cost
900+
# add for the tuplet info
901+
if annNote1.tuplet_info != annNote2.tuplet_info:
902+
tuplet_info_op_list, tuplet_info_cost = Comparison._beamtuplet_leveinsthein_diff(
903+
annNote1.tuplet_info, annNote2.tuplet_info, annNote1, annNote2, "tuplet"
904+
)
905+
op_list.extend(tuplet_info_op_list)
906+
cost += tuplet_info_cost
899907
# add for the articulations
900908
if annNote1.articulations != annNote2.articulations:
901909
artic_op_list, artic_cost = Comparison._generic_leveinsthein_diff(
@@ -930,6 +938,11 @@ def _annotated_note_diff(annNote1: AnnNote, annNote2: AnnNote):
930938
op_list.extend(lyr_op_list)
931939
cost += lyr_cost
932940

941+
# add for offset in quarter notes from start of measure (i.e. horizontal position)
942+
if annNote1.offsetInMeasure != annNote2.offsetInMeasure:
943+
cost += 1
944+
op_list.append(("editnoteoffset", annNote1, annNote2, 1))
945+
933946
# add for noteshape
934947
if annNote1.noteshape != annNote2.noteshape:
935948
cost += 1

0 commit comments

Comments
 (0)