Skip to content

Commit

Permalink
Support for comparison of lyrics (#7)
Browse files Browse the repository at this point in the history
* Support comparison of lyrics.

* Make the lyric string a bit more thorough.
  • Loading branch information
gregchapman-dev authored May 4, 2022
1 parent b33c955 commit 4919319
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 3 deletions.
28 changes: 25 additions & 3 deletions musicdiff/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
__docformat__ = "google"

from fractions import Fraction
from typing import Optional
from typing import Optional, List

import music21 as m21

Expand Down Expand Up @@ -92,6 +92,23 @@ def __init__(self, general_note: m21.note.GeneralNote, enhanced_beam_list, tuple
if self.expressions:
self.expressions.sort()

# lyrics
self.lyrics: List[str] = []
for lyric in general_note.lyrics:
lyricStr: str = ""
if lyric.number is not None:
lyricStr += f"number={lyric.number}"
if lyric._identifier is not None:
lyricStr += f" identifier={lyric._identifier}"
if lyric.syllabic is not None:
lyricStr += f" syllabic={lyric.syllabic}"
if lyric.text is not None:
lyricStr += f" text={lyric.text}"
lyricStr += f" rawText={lyric.rawText}"
if M21Utils.has_style(lyric):
lyricStr += f" style={M21Utils.obj_to_styledict(lyric, detail)}"
self.lyrics.append(lyricStr)

# precomputed representations for faster comparison
self.precomputed_str = self.__str__()

Expand All @@ -116,13 +133,15 @@ def notation_size(self):
size += len(self.articulations)
# add for the expressions
size += len(self.expressions)
# add for the lyrics
size += len(self.lyrics)
return size

def __repr__(self):
# does consider the MEI id!
return (f"{self.pitches},{self.note_head},{self.dots},{self.beamings}," +
f"{self.tuplets},{self.general_note},{self.articulations},{self.expressions}" +
f"{self.styledict}")
f"{self.tuplets},{self.general_note},{self.articulations},{self.expressions}," +
f"{self.lyrics},{self.styledict}")

def __str__(self):
"""
Expand Down Expand Up @@ -172,6 +191,9 @@ def __str__(self):
if len(self.expressions) > 0: # add for articulations
for e in self.expressions:
string += e
if len(self.lyrics) > 0: # add for lyrics
for lyric in self.lyrics:
string += lyric

if self.noteshape != 'normal':
string += f"noteshape={self.noteshape}"
Expand Down
12 changes: 12 additions & 0 deletions musicdiff/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,18 @@ def _annotated_note_diff(annNote1: AnnNote, annNote2: AnnNote):
)
op_list.extend(expr_op_list)
cost += expr_cost
# add for the lyrics
if annNote1.lyrics != annNote2.lyrics:
lyr_op_list, lyr_cost = Comparison._generic_leveinsthein_diff(
annNote1.lyrics,
annNote2.lyrics,
annNote1,
annNote2,
"lyric",
)
op_list.extend(lyr_op_list)
cost += lyr_cost

# add for noteshape
if annNote1.noteshape != annNote2.noteshape:
cost += 1
Expand Down
49 changes: 49 additions & 0 deletions musicdiff/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,55 @@ def mark_diffs(
textExp.style.color = Visualization.CHANGED_COLOR
note2.activeSite.insert(note2.offset, textExp)

# lyrics
elif op[0] == "inslyric":
assert isinstance(op[1], AnnNote)
assert isinstance(op[2], AnnNote)
# color the modified note in both scores using Visualization.INSERTED_COLOR
note1 = score1.recurse().getElementById(op[1].general_note)
note1.style.color = Visualization.INSERTED_COLOR
textExp = m21.expressions.TextExpression("inserted lyric")
textExp.style.color = Visualization.INSERTED_COLOR
note1.activeSite.insert(note1.offset, textExp)

note2 = score2.recurse().getElementById(op[2].general_note)
note2.style.color = Visualization.INSERTED_COLOR
textExp = m21.expressions.TextExpression("inserted lyric")
textExp.style.color = Visualization.INSERTED_COLOR
note2.activeSite.insert(note2.offset, textExp)

elif op[0] == "dellyric":
assert isinstance(op[1], AnnNote)
assert isinstance(op[2], AnnNote)
# color the modified note in both scores using Visualization.DELETED_COLOR
note1 = score1.recurse().getElementById(op[1].general_note)
note1.style.color = Visualization.DELETED_COLOR
textExp = m21.expressions.TextExpression("deleted lyric")
textExp.style.color = Visualization.DELETED_COLOR
note1.activeSite.insert(note1.offset, textExp)

note2 = score2.recurse().getElementById(op[2].general_note)
note2.style.color = Visualization.DELETED_COLOR
textExp = m21.expressions.TextExpression("deleted lyric")
textExp.style.color = Visualization.DELETED_COLOR
note2.activeSite.insert(note2.offset, textExp)

elif op[0] == "editlyric":
assert isinstance(op[1], AnnNote)
assert isinstance(op[2], AnnNote)
# color the modified note (in both scores) using Visualization.CHANGED_COLOR
note1 = score1.recurse().getElementById(op[1].general_note)
note1.style.color = Visualization.CHANGED_COLOR
textExp = m21.expressions.TextExpression("changed lyric")
textExp.style.color = Visualization.CHANGED_COLOR
note1.activeSite.insert(note1.offset, textExp)

note2 = score2.recurse().getElementById(op[2].general_note)
note2.style.color = Visualization.CHANGED_COLOR
textExp = m21.expressions.TextExpression("changed lyric")
textExp.style.color = Visualization.CHANGED_COLOR
note2.activeSite.insert(note2.offset, textExp)

else:
print(f"Annotation type {op[0]} not yet supported for visualization", file=sys.stderr)

Expand Down

0 comments on commit 4919319

Please sign in to comment.