]> git.cworth.org Git - scherzo/commitdiff
Use matched chord signature to guide spelling of note on staff.
authorCarl Worth <cworth@cworth.org>
Fri, 27 Sep 2013 15:24:53 +0000 (08:24 -0700)
committerCarl Worth <cworth@cworth.org>
Fri, 27 Sep 2013 15:24:53 +0000 (08:24 -0700)
We now use the chord's signature to choose the correct spelling of
each enharmonic note. For example, an A mjor triad has a C-sharp in
it, not a D-flat. We can determine this since a major triad should
have a note of the 3 scale degree (as recorded in the chord
signature).

Still to be done is to draw note heads without collision, draw
accidentals without collision, and to improve the heuristic for
deciding between ambiguous chord signatures.

scherzo.c
score.c

index 92dc595fd9c7ab3f94e4befbbb49d7b2be0d1178..ce57e9dfd8b3fc2db0ec28fe8fc4d8a971702003 100644 (file)
--- a/scherzo.c
+++ b/scherzo.c
@@ -363,12 +363,14 @@ _score_pitch_and_octave_to_midi (score_pitch_t pitch,
     return midi_note;
 }
 
+/* octave can optionally by NULL */
 static void
 _midi_to_score_pitch_and_octave (unsigned char midi_note,
                                 score_pitch_t *pitch,
                                 int *octave)
 {
-    *octave = midi_note / 12 - 1;
+    if (octave)
+       *octave = midi_note / 12 - 1;
 
     switch (midi_note % 12)
     {
@@ -457,7 +459,7 @@ _modified_degree_to_half_steps (const modified_degree_t *degree)
        scale_degree = (scale_degree % 8) + 1;
 
     /* Number of half steps from root to specified degree within a
-     * diatonic scaled. */
+     * diatonic scale. */
     switch (scale_degree) {
     case 1:
        half_steps = 0;
@@ -489,6 +491,25 @@ _modified_degree_to_half_steps (const modified_degree_t *degree)
     return half_steps + degree->modification;
 }
 
+/* Number of half steps from 'root' up to 'pitch' (that is, counting
+ * the steps up a chromatic scale). */
+static int
+_pitch_from_root_in_half_steps (score_pitch_t pitch, score_pitch_t root)
+{
+    int root_midi = _score_pitch_and_octave_to_midi (root, 0);
+    int pitch_midi = _score_pitch_and_octave_to_midi (pitch, 0);
+
+    if (pitch_midi < root_midi)
+       pitch_midi += 12;
+
+    if (pitch_midi < root_midi) {
+       fprintf (stderr, "Internal error: midi values differ by more than expected.\n");
+       exit (1);
+    }
+
+    return (pitch_midi - root_midi) % 12;
+}
+
 static int
 _chord_signature_matches (analyzed_note_t *notes,
                          int num_notes,
@@ -764,6 +785,118 @@ chord_signature_t signatures[] = {
     { 6, {{1, 0}, {9, -1}, {3, -1}, {11, -1}, {5, -1}, {7, -2}}, "°" SUP "11" PUS }
 };
 
+static void
+scherzo_adjust_note_to_match_modified_degree (score_note_t *note,
+                                             score_pitch_t root,
+                                             modified_degree_t *degree)
+{
+    score_pitch_name_t name = SCORE_PITCH_NAME (note->pitch);
+    score_pitch_accidental_t accidental = SCORE_PITCH_ACCIDENTAL (note->pitch);
+    int degree_zero_based = (degree->degree - 1) % 7;
+    int degree_delta;
+
+    int note_degree_zero_based = name - SCORE_PITCH_NAME (root);
+    if (note_degree_zero_based < 0)
+       note_degree_zero_based += 7;
+
+    if (note_degree_zero_based == degree_zero_based)
+       return;
+
+    degree_delta = note_degree_zero_based - degree_zero_based;
+    if (degree_delta > 3)
+       degree_delta = - (7 - degree_delta);
+    if (degree_delta < -3)
+       degree_delta = - (-7 - degree_delta);
+
+    if (abs (degree_delta) != 1) {
+       fprintf (stderr, "Internal error: Cannot adjust a note more than one degree (%d vs. %d).\n", note_degree_zero_based + 1, degree->degree);
+       exit (1);
+    }
+
+    if (degree_delta == -1) {
+       if (name == SCORE_PITCH_NAME_B) {
+           name = SCORE_PITCH_NAME_C;
+           note->octave++;
+       } else {
+           name++;
+       }
+       switch (name) {
+       case SCORE_PITCH_NAME_D:
+       case SCORE_PITCH_NAME_E:
+       case SCORE_PITCH_NAME_G:
+       case SCORE_PITCH_NAME_A:
+       case SCORE_PITCH_NAME_B:
+           accidental -= 2;
+           break;
+       case SCORE_PITCH_NAME_C:
+       case SCORE_PITCH_NAME_F:
+           accidental -= 1;
+           break;
+       }
+    }
+
+    if (degree_delta == +1) {
+       if (name == SCORE_PITCH_NAME_C) {
+           name = SCORE_PITCH_NAME_B;
+           note->octave--;
+       } else {
+           name--;
+       }
+       switch (name) {
+       case SCORE_PITCH_NAME_C:
+       case SCORE_PITCH_NAME_D:
+       case SCORE_PITCH_NAME_F:
+       case SCORE_PITCH_NAME_G:
+       case SCORE_PITCH_NAME_A:
+           accidental += 2;
+           break;
+       case SCORE_PITCH_NAME_E:
+       case SCORE_PITCH_NAME_B:
+           accidental += 1;
+       }
+    }
+
+    if (accidental < 0 || accidental > SCORE_PITCH_ACCIDENTAL_DOUBLE_SHARP) {
+       fprintf (stderr, "Internal error: Trying to adjust an accidental out of range (degree_delta == %d (%d - %d), new accidental == %d).\n", degree_delta, note_degree_zero_based, degree_zero_based, accidental);
+       exit (1);
+    }
+
+    note->pitch = SCORE_PITCH (name, accidental);
+}
+
+static void
+_spell_chord_by_signature (note_group_t *chord,
+                          int num_notes,
+                          chord_signature_t *signature,
+                          score_pitch_t root)
+{
+    int i, degree, note_half_steps, degree_half_steps;
+    int root_midi;
+
+    /* Sanitize root to eliminate things like double-flats,
+     * double-sharps, Cb, E#, etc. */
+    root_midi = _score_pitch_and_octave_to_midi (root, 0);
+    _midi_to_score_pitch_and_octave (root_midi, &root, NULL);
+    
+    for (i = 0; i < num_notes; i++) {
+       note_half_steps = _pitch_from_root_in_half_steps (chord->notes[i]->pitch,
+                                                         root);
+       for (degree = 0; degree < signature->num_degrees; degree++) {
+           degree_half_steps = _modified_degree_to_half_steps (&signature->degrees[degree]);
+           if (note_half_steps == degree_half_steps) {
+               scherzo_adjust_note_to_match_modified_degree (chord->notes[i],
+                                                             root,
+                                                             &signature->degrees[degree]);
+               break;
+           }
+       }
+       if (note_half_steps != degree_half_steps) {
+           fprintf (stderr, "Internal error: Chord and degree mis-match\n");
+           exit (1);
+       }
+    }
+}
+
 static void
 scherzo_analyze_chord (scherzo_t *scherzo)
 {
@@ -773,7 +906,8 @@ scherzo_analyze_chord (scherzo_t *scherzo)
     unsigned i, j, num_notes;
     int bass_pitch, inversion;
     score_pitch_t root;
-    const char *chord_name = NULL;
+    chord_signature_t *match = NULL;
+    char *chord_name;
 
     if (scherzo->pedal_pressed)
        note_group = &scherzo->notes_pedaled;
@@ -851,14 +985,16 @@ scherzo_analyze_chord (scherzo_t *scherzo)
                                          signatures[i].num_degrees,
                                          inversion, &root))
            {
-               chord_name = signatures[i].name;
-               goto CHORD_NAME_KNOWN;
+               match = &signatures[i];
+               goto HAVE_MATCH;
            }
        }
     }
-    CHORD_NAME_KNOWN:
+HAVE_MATCH:
+
+    chord_name = (char *) signatures[i].name;
 
-    if (chord_name) {
+    if (match) {
        /* Don't print root pitch for octaves and inversions,
         * (since a pitch name alone looks like a major triad) */
        if (num_notes < 3) {
@@ -868,6 +1004,9 @@ scherzo_analyze_chord (scherzo_t *scherzo)
                                          _pitch_str (root),
                                          chord_name);
        }
+
+       _spell_chord_by_signature (note_group, num_notes,
+                                  match, root);
     } else {
        chord_name = talloc_strdup (local, "Unknown chord");
     }
@@ -974,10 +1113,13 @@ scherzo_release_note (scherzo_t *scherzo, score_pitch_t pitch, int octave)
     score_note_t *note;
     int i;
     int found = 0;
+    int target_midi = _score_pitch_and_octave_to_midi (pitch, octave);
+    int note_midi;
 
     for (i = scherzo->notes_pressed.num_notes - 1; i >=0; i--) {
        note = scherzo->notes_pressed.notes[i];
-       if (note->pitch == pitch && note->octave == octave) {
+       note_midi = _score_pitch_and_octave_to_midi (note->pitch, note->octave);
+       if (note_midi == target_midi) {
            found = 1;
            if (! scherzo->pedal_pressed)
                score_remove_note (note);
diff --git a/score.c b/score.c
index e047f979b6f77d2ba36f2146cbe74cf8f38fcc06..b9df23b2577ba3dbacce8d40fe44642ac833fb57 100644 (file)
--- a/score.c
+++ b/score.c
@@ -712,4 +712,3 @@ score_staff_find_note (score_staff_t *staff,
 
     return NULL;
 }
-