Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AugmentedNet/annotation_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@


def _m21Parse(f):
return music21.converter.parse(f, format="romantext")
return music21.converter.parse(f, format="romantext", forceSource=True)


def from_tsv(tsv, sep="\t"):
Expand Down
62 changes: 40 additions & 22 deletions AugmentedNet/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from . import __version__
from . import cli
from .chord_vocabulary import frompcset
from .chord_vocabulary import frompcset, cosineSimilarity
from .cache import forceTonicization, getTonicizationScaleDegree
from .score_parser import parseScore
from .input_representations import available_representations as availableInputs
Expand Down Expand Up @@ -56,15 +56,27 @@ def solveChordSegmentation(df):
return df.dropna()[df.HarmonicRhythm7 == 0]


def resolveRomanNumeral(b, t, a, s, pcs, key, tonicizedKey):
def resolveRomanNumeralCosine(b, t, a, s, pcs, key, numerator, tonicizedKey):
pcsetVector = np.zeros(12)
chord = music21.chord.Chord(f"{b}2 {t}3 {a}4 {s}5")
pcset = tuple(sorted(set(chord.pitchClasses)))
# if the SATB notes don't make sense, use the pcset classifier
if pcset not in frompcset:
# which is guaranteed to exist in the chord vocabulary
pcset = pcs
# if the chord is nondiatonic to the tonicizedKey
# force a tonicization where the chord does exist
for n in chord.pitches:
pcsetVector[n.pitchClass] += 1
for pc in pcs:
pcsetVector[pc] += 1
chordNumerator = music21.roman.RomanNumeral(
numerator.replace("Cad", "Cad64"), tonicizedKey
).pitchClasses
for pc in chordNumerator:
pcsetVector[pc] += 1
smallestDistance = -2
for pcs in frompcset:
v2 = np.zeros(12)
for p in pcs:
v2[p] = 1
similarity = cosineSimilarity(pcsetVector, v2)
if similarity > smallestDistance:
pcset = pcs
smallestDistance = similarity
if tonicizedKey not in frompcset[pcset]:
# print("Forcing a tonicization")
candidateKeys = list(frompcset[pcset].keys())
Expand All @@ -83,6 +95,8 @@ def resolveRomanNumeral(b, t, a, s, pcs, key, tonicizedKey):
elif invfigure in ["6", "64"]:
rnfigure += invfigure
rn = rnfigure
if numerator == "Cad" and inv == 2:
rn = "Cad64"
if tonicizedKey != key:
denominator = getTonicizationScaleDegree(key, tonicizedKey)
rn = f"{rn}/{denominator}"
Expand All @@ -92,22 +106,17 @@ def resolveRomanNumeral(b, t, a, s, pcs, key, tonicizedKey):
return rn, chordLabel


def generateRomanText(h):
def generateRomanText(h, ts):
metadata = h.metadata
metadata.composer = metadata.composer or "Unknown"
metadata.title = metadata.title or "Unknown"
composer = metadata.composer.split("\n")[0]
title = metadata.title.split("\n")[0]
ts = {
(ts.measureNumber, float(ts.beat)): ts.ratioString
for ts in h.flat.getElementsByClass("TimeSignature")
}
rntxt = f"""\
Composer: {composer}
Title: {title}
Analyst: AugmentedNet v{__version__} - https://github.com/napulen/AugmentedNet
"""
setKey = False
currentMeasure = -1
for n in h.flat.notes:
if not n.lyric:
Expand Down Expand Up @@ -160,14 +169,20 @@ def predict(model, inputPath):
dfout["offset"] = paddedIndex
dfout["measure"] = paddedMeasure
chords = solveChordSegmentation(dfout)
s = music21.converter.parse(inputPath)
s = music21.converter.parse(inputPath, forceSource=True)
ts = {
(ts.measureNumber, float(ts.beat)): ts.ratioString
for ts in s.flat.getElementsByClass("TimeSignature")
}
schord = s.chordify().flat.notesAndRests
schord.metadata = s.metadata
# remove all lyrics from score
for note in s.recurse().notes:
note.lyrics = []
# for note in s.recurse().notes:
# note.lyrics = []
prevkey = ""
for analysis in chords.itertuples():
notes = []
for n in s.flat.notes.getElementsByOffset(analysis.offset):
for n in schord.getElementsByOffset(analysis.offset):
if isinstance(n, music21.note.Note):
notes.append((n, n.pitch.midi))
elif isinstance(n, music21.chord.Chord) and not isinstance(
Expand All @@ -180,13 +195,15 @@ def predict(model, inputPath):
thiskey = analysis.LocalKey38
tonicizedKey = analysis.TonicizedKey38
pcset = analysis.PitchClassSet121
rn2, chordLabel = resolveRomanNumeral(
numerator = analysis.RomanNumeral31
rn2, chordLabel = resolveRomanNumeralCosine(
analysis.Bass35,
analysis.Tenor35,
analysis.Alto35,
analysis.Soprano35,
pcset,
thiskey,
numerator,
tonicizedKey,
)
if thiskey != prevkey:
Expand All @@ -196,12 +213,12 @@ def predict(model, inputPath):
rn2fig = rn2
bass.addLyric(formatRomanNumeral(rn2fig, thiskey))
bass.addLyric(formatChordLabel(chordLabel))
rntxt = generateRomanText(s)
rntxt = generateRomanText(schord, ts)
filename, _ = inputPath.rsplit(".", 1)
annotatedScore = f"{filename}_annotated.musicxml"
annotationCSV = f"{filename}_annotated.csv"
annotatedRomanText = f"{filename}_annotated.rntxt"
s.write(fp=annotatedScore)
schord.write(fp=annotatedScore)
dfout.to_csv(annotationCSV)
with open(annotatedRomanText, "w") as fd:
fd.write(rntxt)
Expand All @@ -224,6 +241,7 @@ def batch(inputPath, dir, modelPath, useGpu):
# do not recursively annotate an annotated_file
continue
filepath = os.path.join(root, f)
print(filepath)
predict(model, inputPath=filepath)


Expand Down
2 changes: 1 addition & 1 deletion AugmentedNet/score_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@


def _m21Parse(f, fmt=None):
s = music21.converter.parse(f, format=fmt)
s = music21.converter.parse(f, format=fmt, forceSource=True)
perc = [
p
for p in s.parts
Expand Down
29 changes: 9 additions & 20 deletions misc/compare_rntxt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from requests import PreparedRequest

from AugmentedNet.annotation_parser import parseAnnotation
from AugmentedNet.feature_representation import COMMON_ROMAN_NUMERALS


def compare_annotation_df(a, b):
b = b.reindex_like(a, method="ffill")
b = b.fillna(method="bfill")
pcset_accuracy = sum(a.a_pcset == b.a_pcset) / len(a.index)
key_accuracy = sum(a.a_tonicizedKey == b.a_tonicizedKey) / len(a.index)
inversion_accuracy = sum(a.a_inversion == b.a_inversion) / len(a.index)
Expand All @@ -19,8 +16,6 @@ def compare_annotation_df(a, b):


def compute_confusion_matrix(a, b):
b = b.reindex_like(a, method="ffill")
b = b.fillna(method="bfill")
roman_numeral_classes = len(COMMON_ROMAN_NUMERALS)
matrix = np.zeros((roman_numeral_classes, roman_numeral_classes))
for gt, pred in zip(a.a_romanNumeral, b.a_romanNumeral):
Expand All @@ -35,18 +30,6 @@ def compute_confusion_matrix(a, b):
groundtruth = "predictions_groundtruth"
models = [m for m in os.listdir(root) if m != groundtruth and "rntxt" in m]
gtdir = os.path.join(root, groundtruth)
crashing_july_7_2022 = [
"abc-op127-2.rntxt",
"abc-op18-no1-1.rntxt",
"bps-07-op010-no3-1.rntxt",
"bps-10-op014-no2-1.rntxt",
"bps-23-op057-appassionata-1.rntxt",
"tavern-beethoven-woo-75-a.rntxt",
"tavern-beethoven-woo-75-b.rntxt",
"wir-openscore-liedercorpus-hensel-6-lieder-op-9-1-die-ersehnte.rntxt",
"wir-openscore-liedercorpus-schumann-dichterliebe-op-48-16-die-alten-bosen-lieder.rntxt",
"wirwtc-bach-wtc-i-8.rntxt",
]
dfdict = {
"file": [],
"model": [],
Expand All @@ -58,8 +41,8 @@ def compute_confusion_matrix(a, b):
}
i = 0
for f in sorted(os.listdir(gtdir)):
if f in crashing_july_7_2022 or "wir-bach-chorales-19" not in f:
continue
# if "bps-07" not in f:
# continue
print(f)
path = os.path.join(root, groundtruth, f)
a = parseAnnotation(path)
Expand All @@ -68,7 +51,13 @@ def compute_confusion_matrix(a, b):
if not os.path.exists(mpath):
continue
print(f"\t{mpath}", end=" ")
b = parseAnnotation(mpath)
try:
b = parseAnnotation(mpath)
except:
print("\tFAILED")
continue
b = b.reindex_like(a, method="ffill")
b = b.fillna(method="bfill")
diff = compare_annotation_df(a, b)
confm = compute_confusion_matrix(a, b)
plt.imshow(confm, cmap="Blues")
Expand Down
342 changes: 245 additions & 97 deletions notebooks/confusion_matrix.ipynb

Large diffs are not rendered by default.