From c85c0da09fd11a6bb53e4f45c0141f9701eb3aaa Mon Sep 17 00:00:00 2001 From: "Peter B. Johnson" Date: Thu, 10 Sep 2026 23:00:43 +0100 Subject: [PATCH] Tidy up feedback_messages.py - Record the cut-offs each dict is keyed by. The labels live here but the thresholds live in compare_MIDI.py, so "excellent" gave no clue that it meant >= 0.90. - focus_messages -> focus_preamble_messages, keyed by what is being praised rather than by the focus area. focus_messages["pitch"] praised rhythm, which read backwards next to focus_advice_messages["pitch"]. - Move "Note Detail:" and "Chord Detail:" into detail_section_titles. They were the last student-facing strings left in compare_MIDI.py. - Strip the trailing spaces that do nothing (three messages that are joined with newlines) and note why the remaining ones are needed: the focus preambles and the chord suffixes are concatenated. - Pin the detail section titles by constant in the tests. Co-Authored-By: Claude Opus 5 --- evaluation_function/compare_MIDI.py | 16 ++++++---- evaluation_function/evaluation_test.py | 18 +++++++----- evaluation_function/feedback_messages.py | 37 +++++++++++++++++++----- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/evaluation_function/compare_MIDI.py b/evaluation_function/compare_MIDI.py index cbd67d0..cb717fe 100644 --- a/evaluation_function/compare_MIDI.py +++ b/evaluation_function/compare_MIDI.py @@ -27,7 +27,7 @@ chord_summary_messages, tempo_messages, completeness_level_messages, - focus_messages, + focus_preamble_messages, focus_advice_messages, report_section_titles, report_closing_message, @@ -35,6 +35,7 @@ # 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, @@ -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] @@ -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. diff --git a/evaluation_function/evaluation_test.py b/evaluation_function/evaluation_test.py index 6c9d135..aa25223 100755 --- a/evaluation_function/evaluation_test.py +++ b/evaluation_function/evaluation_test.py @@ -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 @@ -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): @@ -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 diff --git a/evaluation_function/feedback_messages.py b/evaluation_function/feedback_messages.py index 11763a8..e6cd0a3 100644 --- a/evaluation_function/feedback_messages.py +++ b/evaluation_function/feedback_messages.py @@ -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 " @@ -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 " @@ -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 " @@ -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": ( @@ -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 " @@ -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 " @@ -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 " @@ -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)." @@ -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. " @@ -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."