From 5b1797d26aff3a1b2f0cbe83583496b42abcd056 Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Tue, 1 Oct 2013 10:36:58 -0700 Subject: [PATCH] Spell input notes according to key 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 | 192 +++++++++++++++++++++++++++++++++++++++++++ pitch.h | 22 +++++ scherzo-key.c | 27 ++++++ scherzo-key.h | 13 +++ scherzo.c | 224 ++++---------------------------------------------- 5 files changed, 270 insertions(+), 208 deletions(-) diff --git a/pitch.c b/pitch.c index c628a76..211e68c 100644 --- a/pitch.c +++ b/pitch.c @@ -18,6 +18,8 @@ * along with this program. If not, see http://www.gnu.org/licenses/ . */ +#include + #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 3d1dd2e..ffe9782 100644 --- 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 */ diff --git a/scherzo-key.c b/scherzo-key.c index 67d2c1e..18cd261 100644 --- a/scherzo-key.c +++ b/scherzo-key.c @@ -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); +} diff --git a/scherzo-key.h b/scherzo-key.h index 07cdd70..8aaaf6d 100644 --- a/scherzo-key.h +++ b/scherzo-key.h @@ -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 */ diff --git a/scherzo.c b/scherzo.c index d1a9e9f..696e816 100644 --- 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. */ -- 2.43.0