]> git.cworth.org Git - scherzo/commitdiff
Spell input notes according to key
authorCarl Worth <cworth@cworth.org>
Tue, 1 Oct 2013 17:36:58 +0000 (10:36 -0700)
committerCarl Worth <cworth@cworth.org>
Thu, 3 Oct 2013 17:50:55 +0000 (10:50 -0700)
Previously, scherzo always preferred the sharp form of any note
pressed on the keyboard. Now, it uses the key to resolve any ambiguous
spellings of notes pressed.

This also means that chord spellings will now fit the key much more
nicely. (For example, in the key of F major, one will now see a Bb
major triad, rather than the very-odd-looking A# major triad).

pitch.c
pitch.h
scherzo-key.c
scherzo-key.h
scherzo.c

diff --git a/pitch.c b/pitch.c
index c628a76ac3076f52e540dce83789c11dc891eba4..211e68c1aeb1252ff60c62b1d214475ffe5d5ce9 100644 (file)
--- a/pitch.c
+++ b/pitch.c
@@ -18,6 +18,8 @@
  * along with this program.  If not, see http://www.gnu.org/licenses/ .
  */
 
+#include <stdlib.h>
+
 #include "pitch.h"
 
 const char *
@@ -96,3 +98,193 @@ pitch_lower_by_octaves (pitch_t pitch, int octaves)
 
     return PITCH (PITCH_NAME (pitch), PITCH_ACCIDENTAL (pitch), new_octave);
 }
+
+/* Number of half steps from 'root' up to 'pitch' (that is, counting
+ * the steps up a chromatic scale), within the same octave. */
+int
+pitch_from_root_in_half_steps (pitch_t pitch, pitch_t root)
+{
+    int root_midi = pitch_to_midi (root);
+    int pitch_midi = pitch_to_midi (pitch);
+
+    while (pitch_midi < root_midi)
+       pitch_midi += 12;
+
+    return (pitch_midi - root_midi) % 12;
+}
+
+pitch_t
+pitch_spell_as_degree (pitch_t pitch, pitch_t root, int degree)
+{
+    pitch_name_t name = PITCH_NAME (pitch);
+    pitch_accidental_t accidental = PITCH_ACCIDENTAL (pitch);
+    int octave = PITCH_OCTAVE (pitch);
+    int degree_zero_based = (degree - 1) % 7;
+    int degree_delta;
+
+    int note_degree_zero_based = name - PITCH_NAME (root);
+    if (note_degree_zero_based < 0)
+       note_degree_zero_based += 7;
+
+    if (note_degree_zero_based == degree_zero_based)
+       return pitch;
+
+    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);
+
+    /* Cannot re-spell pitch more than one degree away. Return
+     * original pitch. */
+    if (abs (degree_delta) != 1)
+       return pitch;
+
+    if (degree_delta == -1) {
+       if (name == PITCH_NAME_B) {
+           name = PITCH_NAME_C;
+           octave++;
+       } else {
+           name++;
+       }
+       switch (name) {
+       case PITCH_NAME_D:
+       case PITCH_NAME_E:
+       case PITCH_NAME_G:
+       case PITCH_NAME_A:
+       case PITCH_NAME_B:
+           accidental -= 2;
+           break;
+       case PITCH_NAME_C:
+       case PITCH_NAME_F:
+           accidental -= 1;
+           break;
+       }
+    }
+
+    if (degree_delta == +1) {
+       if (name == PITCH_NAME_C) {
+           name = PITCH_NAME_B;
+           octave--;
+       } else {
+           name--;
+       }
+       switch (name) {
+       case PITCH_NAME_C:
+       case PITCH_NAME_D:
+       case PITCH_NAME_F:
+       case PITCH_NAME_G:
+       case PITCH_NAME_A:
+           accidental += 2;
+           break;
+       case PITCH_NAME_E:
+       case PITCH_NAME_B:
+           accidental += 1;
+       }
+    }
+
+    /* Also cannot use accidentals to respell more than two half steps
+     * either direction. Return original pitch. */
+    if (accidental < 0 || accidental > PITCH_ACCIDENTAL_DOUBLE_SHARP)
+       return pitch;
+
+    return PITCH (name, accidental, octave);
+}
+
+/* Return a MIDI note value corresponding to 'pitch' */
+unsigned char
+pitch_to_midi (pitch_t pitch)
+{
+    int octave = PITCH_OCTAVE (pitch);
+    unsigned char midi_note = 12 * (octave + 1);
+
+    switch (PITCH_NAME (pitch)) {
+    case PITCH_NAME_C:
+       break;
+    case PITCH_NAME_D:
+       midi_note += 2;
+       break;
+    case PITCH_NAME_E:
+       midi_note += 4;
+       break;
+    case PITCH_NAME_F:
+       midi_note += 5;
+       break;
+    case PITCH_NAME_G:
+       midi_note += 7;
+       break;
+    case PITCH_NAME_A:
+       midi_note += 9;
+       break;
+    case PITCH_NAME_B:
+       midi_note += 11;
+       break;
+    }
+
+    switch (PITCH_ACCIDENTAL (pitch)) {
+    case PITCH_ACCIDENTAL_DOUBLE_FLAT:
+       midi_note -= 2;
+       break;
+    case PITCH_ACCIDENTAL_FLAT:
+       midi_note -= 1;
+       break;
+    case PITCH_ACCIDENTAL_NATURAL:
+       break;
+    case PITCH_ACCIDENTAL_SHARP:
+       midi_note += 1;
+       break;
+    case PITCH_ACCIDENTAL_DOUBLE_SHARP:
+       midi_note += 2;
+       break;
+    }
+
+    return midi_note;
+}
+
+/* Return the pitch_t value corresponding to the given MIDI note value. */
+pitch_t
+pitch_from_midi (unsigned char midi_note)
+{
+    int octave = octave = midi_note / 12 - 1;
+
+    switch (midi_note % 12)
+    {
+    default:
+    case 0:
+       return PITCH_LITERAL (C, NATURAL, octave);
+       break;
+    case 1:
+       return PITCH_LITERAL (C, SHARP, octave);
+       break;
+    case 2:
+       return PITCH_LITERAL (D, NATURAL, octave);
+       break;
+    case 3:
+       return PITCH_LITERAL (D, SHARP, octave);
+       break;
+    case 4:
+       return PITCH_LITERAL (E, NATURAL, octave);
+       break;
+    case 5:
+       return PITCH_LITERAL (F, NATURAL, octave);
+       break;
+    case 6:
+       return PITCH_LITERAL (F, SHARP, octave);
+       break;
+    case 7:
+       return PITCH_LITERAL (G, NATURAL, octave);
+       break;
+    case 8:
+       return PITCH_LITERAL (G, SHARP, octave);
+       break;
+    case 9:
+       return PITCH_LITERAL (A, NATURAL, octave);
+       break;
+    case 10:
+       return PITCH_LITERAL (A, SHARP, octave);
+       break;
+    case 11:
+       return PITCH_LITERAL (B, NATURAL, octave);
+       break;
+    }
+}
diff --git a/pitch.h b/pitch.h
index 3d1dd2e125b98cda6cc4c2ecfe150c54278258db..ffe9782c54b50cac05ba3470477f079afe31ddd6 100644 (file)
--- a/pitch.h
+++ b/pitch.h
@@ -110,4 +110,26 @@ pitch_raise_by_octaves (pitch_t pitch, int octaves);
 pitch_t
 pitch_lower_by_octaves (pitch_t pitch, int octaves);
 
+/* Number of half steps from 'root' up to 'pitch' (that is, counting
+ * the steps up a chromatic scale), within the same octave. */
+int
+pitch_from_root_in_half_steps (pitch_t pitch, pitch_t root);
+
+/* Respell 'pitch' to have a name that matches the (diatonic) scale
+ * 'degree' from 'root'.
+ *
+ * If the pitch is too distant from the specified degree to be
+ * respelled, the original pitch will be returned unmodified.
+ */
+pitch_t
+pitch_spell_as_degree (pitch_t pitch, pitch_t root, int degree);
+
+/* Return a MIDI note value corresponding to 'pitch' */
+unsigned char
+pitch_to_midi (pitch_t pitch);
+
+/* Return the pitch_t value corresponding to the given MIDI note value. */
+pitch_t
+pitch_from_midi (unsigned char midi_note);
+
 #endif /* PITCH_H */
index 67d2c1e2875f7a2a4388b5726a338e38bf91ac43..18cd261f6d5823e6727978b19397a415af23c592 100644 (file)
@@ -135,3 +135,30 @@ scherzo_key_contains_pitch (scherzo_key_t *key, pitch_t pitch)
     else
        return false;
 }
+
+pitch_t
+scherzo_key_spell_pitch (scherzo_key_t *key, pitch_t pitch)
+{
+    pitch_t root = key->pitch;
+    int half_steps = pitch_from_root_in_half_steps (pitch, root);
+    pitch_name_t new_pitch_name;
+    int i;
+    int half_steps_per_degree[] = {
+       0, 2, 4, 5, 7, 9, 11
+    };
+
+    for (i = 0; i < ARRAY_SIZE (half_steps_per_degree); i++)
+       if (half_steps_per_degree[i] == half_steps)
+           goto WITHIN_SCALE;
+
+    return pitch;
+
+WITHIN_SCALE:
+    new_pitch_name = (PITCH_NAME (root) + i) % 7;
+
+    /* Original pitch is spelled correctly. Return it as is. */
+    if (new_pitch_name == PITCH_NAME (pitch))
+       return pitch;
+
+    return pitch_spell_as_degree (pitch, root, i + 1);
+}
index 07cdd702ce0864785cddb864125429c37338983f..8aaaf6d857e5ae673a505abd3a310852567208fb 100644 (file)
@@ -38,7 +38,20 @@ typedef struct scherzo_key
 void
 scherzo_key_init (scherzo_key_t *key, pitch_t pitch);
 
+/* Return true if 'pitch' is within the key as spelled.
+ *
+ * For example, a PITCH(A, SHARP) is not considered as contained
+ * within the key of PITCH(B, FLAT).
+ */
 bool
 scherzo_key_contains_pitch (scherzo_key_t *key, pitch_t pitch);
 
+/* For a pitch that is enharmonic with a pitch within 'key', return
+ * the key's spelling of that pitch.
+ *
+ * Otherwise, return the pitch unmodified.
+ */
+pitch_t
+scherzo_key_spell_pitch (scherzo_key_t *key, pitch_t pitch);
+
 #endif /* KEY_H */
index d1a9e9f5d98bf5fd0cdaf993fccb6a8f2dea5d2f..696e816e7e3f4ede87856be7dd294c754b4b23c2 100644 (file)
--- a/scherzo.c
+++ b/scherzo.c
@@ -78,7 +78,7 @@ typedef struct scherzo
 
     int pedal_pressed;
 
-    pitch_t key;
+    scherzo_key_t key;
 } scherzo_t;
 
 /* Forward declarations. */
@@ -286,6 +286,8 @@ on_key_press_event (GtkWidget *widget,
 
     if (pitch != PITCH_NOT_A_PITCH) 
     {
+       pitch = scherzo_key_spell_pitch (&scherzo->key, pitch);
+
        scherzo_press_note (scherzo, pitch);
        _judge_pitch (scherzo, pitch);
        gtk_widget_queue_draw (scherzo->window);
@@ -395,6 +397,8 @@ on_key_release_event (unused (GtkWidget *widget),
 
     if (pitch != PITCH_NOT_A_PITCH)
     {
+       pitch = scherzo_key_spell_pitch (&scherzo->key, pitch);
+
        scherzo_release_note (scherzo, pitch);
        _score_challenge (scherzo);
        gtk_widget_queue_draw (scherzo->window);
@@ -407,103 +411,6 @@ on_key_release_event (unused (GtkWidget *widget),
     return FALSE;
 }
 
-static unsigned char
-_pitch_to_midi (pitch_t pitch)
-{
-    int octave = PITCH_OCTAVE (pitch);
-    unsigned char midi_note = 12 * (octave + 1);
-
-    switch (PITCH_NAME (pitch)) {
-    case PITCH_NAME_C:
-       break;
-    case PITCH_NAME_D:
-       midi_note += 2;
-       break;
-    case PITCH_NAME_E:
-       midi_note += 4;
-       break;
-    case PITCH_NAME_F:
-       midi_note += 5;
-       break;
-    case PITCH_NAME_G:
-       midi_note += 7;
-       break;
-    case PITCH_NAME_A:
-       midi_note += 9;
-       break;
-    case PITCH_NAME_B:
-       midi_note += 11;
-       break;
-    }
-
-    switch (PITCH_ACCIDENTAL (pitch)) {
-    case PITCH_ACCIDENTAL_DOUBLE_FLAT:
-       midi_note -= 2;
-       break;
-    case PITCH_ACCIDENTAL_FLAT:
-       midi_note -= 1;
-       break;
-    case PITCH_ACCIDENTAL_NATURAL:
-       break;
-    case PITCH_ACCIDENTAL_SHARP:
-       midi_note += 1;
-       break;
-    case PITCH_ACCIDENTAL_DOUBLE_SHARP:
-       midi_note += 2;
-       break;
-    }
-
-    return midi_note;
-}
-
-/* octave can optionally by NULL */
-static pitch_t
-_midi_to_pitch (unsigned char midi_note)
-{
-    int octave = octave = midi_note / 12 - 1;
-
-    switch (midi_note % 12)
-    {
-    default:
-    case 0:
-       return PITCH_LITERAL (C, NATURAL, octave);
-       break;
-    case 1:
-       return PITCH_LITERAL (C, SHARP, octave);
-       break;
-    case 2:
-       return PITCH_LITERAL (D, NATURAL, octave);
-       break;
-    case 3:
-       return PITCH_LITERAL (D, SHARP, octave);
-       break;
-    case 4:
-       return PITCH_LITERAL (E, NATURAL, octave);
-       break;
-    case 5:
-       return PITCH_LITERAL (F, NATURAL, octave);
-       break;
-    case 6:
-       return PITCH_LITERAL (F, SHARP, octave);
-       break;
-    case 7:
-       return PITCH_LITERAL (G, NATURAL, octave);
-       break;
-    case 8:
-       return PITCH_LITERAL (G, SHARP, octave);
-       break;
-    case 9:
-       return PITCH_LITERAL (A, NATURAL, octave);
-       break;
-    case 10:
-       return PITCH_LITERAL (A, SHARP, octave);
-       break;
-    case 11:
-       return PITCH_LITERAL (B, NATURAL, octave);
-       break;
-    }
-}
-
 /* Return true if 'a' and 'b' sound identical, (even if spelled differently)
  *
  * This comparison considers octaves as significant. So Bb and A# in
@@ -512,7 +419,7 @@ _midi_to_pitch (unsigned char midi_note)
 static int
 _pitches_are_enharmonic (pitch_t a, pitch_t b)
 {
-    return _pitch_to_midi (a) == _pitch_to_midi (b);
+    return pitch_to_midi (a) == pitch_to_midi (b);
 }
 
 /* Determine a chord name (if any) from the current notes pressed */
@@ -593,20 +500,6 @@ _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), within the same octave. */
-static int
-_pitch_from_root_in_half_steps (pitch_t pitch, pitch_t root)
-{
-    int root_midi = _pitch_to_midi (root);
-    int pitch_midi = _pitch_to_midi (pitch);
-
-    while (pitch_midi < root_midi)
-       pitch_midi += 12;
-
-    return (pitch_midi - root_midi) % 12;
-}
-
 static int
 _chord_signature_matches (analyzed_pitch_t *pitches,
                          int num_pitches,
@@ -802,108 +695,22 @@ chord_signature_t signatures[] = {
     { 6, {{1, 0}, {9, -1}, {3, -1}, {11, -1}, {5, -1}, {7, -2}}, "°" SUP "11" PUS }
 };
 
-static void
-scherzo_adjust_pitch_to_match_modified_degree (pitch_t *pitch,
-                                              pitch_t root,
-                                              modified_degree_t *degree)
-{
-    pitch_name_t name = PITCH_NAME (*pitch);
-    pitch_accidental_t accidental = PITCH_ACCIDENTAL (*pitch);
-    int octave = PITCH_OCTAVE (*pitch);
-    int degree_zero_based = (degree->degree - 1) % 7;
-    int degree_delta;
-
-    int note_degree_zero_based = name - 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 == PITCH_NAME_B) {
-           name = PITCH_NAME_C;
-           octave++;
-       } else {
-           name++;
-       }
-       switch (name) {
-       case PITCH_NAME_D:
-       case PITCH_NAME_E:
-       case PITCH_NAME_G:
-       case PITCH_NAME_A:
-       case PITCH_NAME_B:
-           accidental -= 2;
-           break;
-       case PITCH_NAME_C:
-       case PITCH_NAME_F:
-           accidental -= 1;
-           break;
-       }
-    }
-
-    if (degree_delta == +1) {
-       if (name == PITCH_NAME_C) {
-           name = PITCH_NAME_B;
-           octave--;
-       } else {
-           name--;
-       }
-       switch (name) {
-       case PITCH_NAME_C:
-       case PITCH_NAME_D:
-       case PITCH_NAME_F:
-       case PITCH_NAME_G:
-       case PITCH_NAME_A:
-           accidental += 2;
-           break;
-       case PITCH_NAME_E:
-       case PITCH_NAME_B:
-           accidental += 1;
-       }
-    }
-
-    if (accidental < 0 || accidental > 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);
-    }
-
-    *pitch = PITCH (name, accidental, octave);
-}
-
 static void
 _spell_chord_by_signature (pitch_group_t *chord,
                           chord_signature_t *signature,
                           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 = _pitch_to_midi (root);
-    root = _midi_to_pitch (root_midi);
     
     for (i = 0; i < chord->num_pitches; i++) {
-       note_half_steps = _pitch_from_root_in_half_steps (chord->pitches[i],
+       note_half_steps = pitch_from_root_in_half_steps (chord->pitches[i],
                                                          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_pitch_to_match_modified_degree (&chord->pitches[i],
-                                                              root,
-                                                              &signature->degrees[degree]);
+               chord->pitches[i] = pitch_spell_as_degree (chord->pitches[i],
+                                                          root,
+                                                          signature->degrees[degree].degree);
                break;
            }
        }
@@ -942,7 +749,7 @@ _analyze_chord (pitch_group_t *chord,
     for (i = 0; i < num_pitches; i++) {
        pitch_t pitch = chord->pitches[i];
        pitches[i].pitch = pitch;
-       pitches[i].midi_pitch = _pitch_to_midi (pitch);
+       pitches[i].midi_pitch = pitch_to_midi (pitch);
        /* Relative pitch will be filled in after sorting. */
        pitches[i].relative_pitch = 0;
     }
@@ -1198,7 +1005,7 @@ scherzo_release_note (scherzo_t *scherzo, pitch_t pitch)
 static pitch_t
 scherzo_press_note_midi (scherzo_t *scherzo, unsigned char midi_note)
 {
-    pitch_t pitch = _midi_to_pitch (midi_note);
+    pitch_t pitch = pitch_from_midi (midi_note);
 
     scherzo_press_note (scherzo, pitch);
 
@@ -1208,7 +1015,7 @@ scherzo_press_note_midi (scherzo_t *scherzo, unsigned char midi_note)
 static void
 scherzo_release_note_midi (scherzo_t *scherzo, unsigned char midi_note)
 {
-    scherzo_release_note (scherzo, _midi_to_pitch (midi_note));
+    scherzo_release_note (scherzo, pitch_from_midi (midi_note));
 }
 
 static void
@@ -1454,8 +1261,9 @@ main (int argc, char *argv[])
 
     scherzo.pedal_pressed = 0;
 
-    scherzo.key = PITCH_CLASS_LITERAL (C, NATURAL);
-    score_set_key (scherzo.score, scherzo.key);
+    /* Default to key of C Major, naturally. */
+    scherzo_key_init (&scherzo.key, PITCH_CLASS_LITERAL (C, NATURAL));
+    score_set_key (scherzo.score, scherzo.key.pitch);
 
     mnemon_init (&scherzo.mnemon);
     /* XXX: Should create a default file if one cannot be loaded. */