Skip to content
Open
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
16 changes: 10 additions & 6 deletions evaluation_function/compare_MIDI.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@
chord_summary_messages,
tempo_messages,
completeness_level_messages,
focus_messages,
focus_preamble_messages,
focus_advice_messages,
report_section_titles,
report_closing_message,
)
# Detail report -- appended only when show_detail is on.
from .feedback_messages import (
detail_caveat_message,
detail_section_titles,
note_detail_missing_message,
note_detail_extra_message,
note_detail_wrong_pitch_message,
Expand Down Expand Up @@ -1005,14 +1006,14 @@ def detail_feedback(event_details, response_events, ref_events, stats):
)

if note_detail_messages:
all_messages = ["Note Detail:"] + note_detail_messages
all_messages = [detail_section_titles["notes"]] + note_detail_messages
else:
all_messages = [no_note_errors_message]

if stats["total_chords_in_reference"] > 0:
if chord_detail_messages:
all_messages = (
all_messages + ["", "Chord Detail:"] + chord_detail_messages
all_messages + ["", detail_section_titles["chords"]] + chord_detail_messages
)
else:
all_messages = all_messages + ["", no_chord_errors_message]
Expand Down Expand Up @@ -1168,13 +1169,16 @@ def summary_feedback(event_details, response_events, ref_events, stats,
if main_focus_score is not None and main_focus_score >= 0.90:
focus_message = focus_advice_messages["excellent_overall"]
elif main_focus == "pitch":
overall_message = focus_messages["pitch"] if main_focus_score >= 0.70 else focus_messages["developing"]
overall_message = (focus_preamble_messages["rhythm"] if main_focus_score >= 0.70
else focus_preamble_messages["developing"])
focus_message = overall_message + focus_advice_messages["pitch"]
elif main_focus == "timing":
overall_message = focus_messages["timing"] if main_focus_score >= 0.70 else focus_messages["developing"]
overall_message = (focus_preamble_messages["melody"] if main_focus_score >= 0.70
else focus_preamble_messages["developing"])
focus_message = overall_message + focus_advice_messages["timing"]
elif main_focus == "chords":
overall_message = focus_messages["chords"] if main_focus_score >= 0.70 else focus_messages["developing"]
overall_message = (focus_preamble_messages["melody_and_rhythm"] if main_focus_score >= 0.70
else focus_preamble_messages["developing"])
focus_message = overall_message + focus_advice_messages["chords"]
else:
# scores was empty: no reference notes/chords to evaluate at all.
Expand Down
18 changes: 11 additions & 7 deletions evaluation_function/evaluation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@
DEFAULT_CHORD_ONSET_WINDOW,
SHOW_DETAIL,
)
from .feedback_messages import detail_caveat_message, report_section_titles
from .feedback_messages import (
detail_caveat_message,
detail_section_titles,
report_section_titles,
)
from .evaluation import evaluation_function


Expand Down Expand Up @@ -724,7 +728,7 @@ def test_detail_is_off_by_default(self):
assert SHOW_DETAIL is False
feedback = compare_performance_ED(self.RES, self.REF).feedback_message
assert report_section_titles["summary"] in feedback
assert "Note Detail:" not in feedback
assert detail_section_titles["notes"] not in feedback
assert detail_caveat_message not in feedback

def test_show_detail_appends_detail_below_the_summary(self):
Expand All @@ -733,20 +737,20 @@ def test_show_detail_appends_detail_below_the_summary(self):
).feedback_message
# The summary is not replaced by the detail, it is still on top.
assert report_section_titles["summary"] in feedback
assert "Note Detail:" in feedback
assert feedback.index(report_section_titles["summary"]) < feedback.index("Note Detail:")
assert detail_section_titles["notes"] in feedback
assert feedback.index(report_section_titles["summary"]) < feedback.index(detail_section_titles["notes"])

def test_detail_section_is_introduced_by_the_caveat(self):
feedback = compare_performance_ED(
self.RES, self.REF, show_detail=True
).feedback_message
assert detail_caveat_message in feedback
assert feedback.index(detail_caveat_message) < feedback.index("Note Detail:")
assert feedback.index(detail_caveat_message) < feedback.index(detail_section_titles["notes"])

def test_show_detail_passed_through_params(self):
assert "Note Detail:" not in evaluation_function(self.RES, self.REF, {})["feedback"]
assert detail_section_titles["notes"] not in evaluation_function(self.RES, self.REF, {})["feedback"]
with_detail = evaluation_function(self.RES, self.REF, {"show_detail": True})
assert "Note Detail:" in with_detail["feedback"]
assert detail_section_titles["notes"] in with_detail["feedback"]

def test_note_held_too_short_is_reported_as_shorter(self):
# Note 3 is held for a quarter of its reference duration while every
Expand Down
37 changes: 30 additions & 7 deletions evaluation_function/feedback_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# --- summary ---
# =================================================================
# ---------- current performance summary (pitch / timing / chords) ----------
# Keyed by note pitch accuracy: excellent >= 0.90, good >= 0.70,
# else needs_practice.
pitch_summary_messages = {
"excellent": (
"Great! Most notes were played correctly, you've got a good "
Expand All @@ -32,6 +34,7 @@
),
}

# Keyed by note timing accuracy, same cut-offs as pitch.
timing_summary_messages = {
"excellent": (
"Great timing consistency between notes, you've got a good "
Expand All @@ -52,6 +55,8 @@
),
}

# Keyed by median chord accuracy, same cut-offs. needs_practice is also
# used when no chord could be scored at all.
chord_summary_messages = {
"needs_practice": (
"Simultaneous notes are often hard to play correctly at the "
Expand All @@ -60,7 +65,7 @@
"practice each chord separately first and make sure all "
"required notes sound together. Then you can reconnect the "
"chords to their surrounding sections and practice at a "
"slower tempo carefully. "
"slower tempo carefully."
),
"excellent": "Nice! The chords were played accurately overall.",
"good": (
Expand All @@ -72,6 +77,9 @@
}

# ---------- overall tempo feedback ----------
# Keyed by timing_scale against GLOBAL_SLOW_THRESHOLD / GLOBAL_FAST_THRESHOLD
# in compare_MIDI.py: above slow -> "slow", below fast -> "fast", else
# "on_tempo".
tempo_messages = {
"slow": (
"Your overall tempo was slower than the reference. This is not "
Expand All @@ -93,6 +101,8 @@
}

# ---------- completeness feedback (missing / extra notes & chords) ----------
# Keyed by the share of reference events missed or added: none -> "perfect",
# up to 10% -> "mostly_complete", else "needs_more_practice".
completeness_level_messages = {
"perfect": (
"You completed the performance without missing or adding any "
Expand All @@ -107,18 +117,24 @@
"No worries! It is common to miss or play extra notes when "
"learning a new piece, especially difficult passages. You can "
"slow down in your next practice and pay more attention to "
"your fingering and hand position. "
"your fingering and hand position."
),
}

# ---------- next practice focus ----------
focus_messages = {
"pitch": "You've got a good understanding of the rhythm. ",
"timing": "You've got a good understanding of the melody. ",
"chords": "You've got a good understanding of the melody and the rhythm. ",
# Opens the focus section by praising the areas that are NOT the focus, so
# the key names what is being praised. Picked by the weakest score: >= 0.70
# earns the named praise, below that "developing". The trailing spaces are
# needed -- these are concatenated onto focus_advice_messages.
focus_preamble_messages = {
"rhythm": "You've got a good understanding of the rhythm. ",
"melody": "You've got a good understanding of the melody. ",
"melody_and_rhythm": "You've got a good understanding of the melody and the rhythm. ",
"developing": "Good progress! ",
}

# Keyed by the weakest area. "excellent_overall" is used instead whenever
# that weakest score is already >= 0.90.
focus_advice_messages = {
"excellent_overall": (
"Excellent work! You already have a good understanding of the "
Expand Down Expand Up @@ -168,7 +184,7 @@
)

note_detail_missing_message = "Note {index} (pitch {pitch}) is missing in your performance."
note_detail_extra_message = "Extra note played: pitch {pitch} at t={time:.2f}s "
note_detail_extra_message = "Extra note played: pitch {pitch} at t={time:.2f}s"
note_detail_wrong_pitch_message = (
"Note {index}: wrong pitch — expected {expected}, played {played} "
"({semitones} semitone(s) off)."
Expand All @@ -185,6 +201,8 @@

chord_detail_missing_message = "Chord {index} ({chord_name}) is missing in your performance."
chord_detail_extra_message = "Extra chord played: {chord_name} at event position {index}."
# The trailing spaces below are needed -- the two suffixes are concatenated
# onto the accuracy message when a chord has missing or extra notes.
chord_detail_accuracy_message = (
"Chord {index} (expected {expected}, you played {played}): "
"{accuracy}% accurate. "
Expand All @@ -196,5 +214,10 @@
"({relative_pct:.0f}% of the expected interval)."
)

detail_section_titles = {
"notes": "Note Detail:",
"chords": "Chord Detail:",
}

no_note_errors_message = "All melody notes played correctly!!"
no_chord_errors_message = "Great performance! No further issues on chords found."
Loading