X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=scherzo.c;h=e57ccbe9b3fe7f831d535bf73d77544d62dce04e;hb=fbfb0b3acfebeb22fae538588557f523ee539617;hp=b8dd611a0a7e9ae9cff49b08b1a1090e4518acfc;hpb=367e517451123c7c79ead28eca6d2489bfed6467;p=scherzo diff --git a/scherzo.c b/scherzo.c index b8dd611..e57ccbe 100644 --- a/scherzo.c +++ b/scherzo.c @@ -24,7 +24,7 @@ #include "score.h" #include "mnemon.h" -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) +#define ARRAY_SIZE(arr) ((int) (sizeof(arr) / sizeof(arr[0]))) #define unused(foo) foo __attribute__((unused)) @@ -32,10 +32,11 @@ typedef struct challenge { + int active; bin_t *bin; int item_index; score_staff_t *staff; - score_note_t *note; + pitch_t pitch; int satisfied; int mistaken; @@ -65,7 +66,6 @@ typedef struct scherzo * keyboard". Any "piano keyboard" key knows its own octave and * accidental already. */ int keyboard_octave; - pitch_accidental_t keyboard_accidental; int midi_fd; snd_midi_event_t *snd_midi_event; @@ -77,6 +77,8 @@ typedef struct scherzo pitch_group_t notes_pedaled; int pedal_pressed; + + pitch_t key; } scherzo_t; /* Forward declarations. */ @@ -148,12 +150,10 @@ on_key_press_event (GtkWidget *widget, { scherzo_t *scherzo = user_data; int octave; - /* Initialize to keep the compiler quiet. */ - pitch_name_t pitch_name = PITCH_NATURAL (C, 0); - pitch_t pitch; + pitch_t pitch = PITCH_NOT_A_PITCH; - if (scherzo->challenge.note) - octave = PITCH_OCTAVE (scherzo->challenge.note->pitch); + if (scherzo->challenge.active) + octave = PITCH_OCTAVE (scherzo->challenge.pitch); else octave = scherzo->keyboard_octave; @@ -172,38 +172,89 @@ on_key_press_event (GtkWidget *widget, gtk_widget_queue_draw (widget); return TRUE; break; - case GDK_KEY_q: - case GDK_KEY_Q: case GDK_KEY_Escape: gtk_main_quit (); return FALSE; - case GDK_KEY_c: - case GDK_KEY_C: - pitch_name = PITCH_NAME_C; + /* Map a piano keyboard onto the computer keyboard with white keys + * along the home row, (piano C on computer A, up to next octave + * higher C on computer K). Black keys are in the expected + * positions, (C# is W, D# is E, etc.). + */ + case GDK_KEY_a: + case GDK_KEY_A: + pitch = PITCH_LITERAL (C, NATURAL, octave); + break; + case GDK_KEY_s: + case GDK_KEY_S: + pitch = PITCH_LITERAL (D, NATURAL, octave); break; case GDK_KEY_d: case GDK_KEY_D: - pitch_name = PITCH_NAME_D; - break; - case GDK_KEY_e: - case GDK_KEY_E: - pitch_name = PITCH_NAME_E; + pitch = PITCH_LITERAL (E, NATURAL, octave); break; case GDK_KEY_f: case GDK_KEY_F: - pitch_name = PITCH_NAME_F; + pitch = PITCH_LITERAL (F, NATURAL, octave); break; case GDK_KEY_g: case GDK_KEY_G: - pitch_name = PITCH_NAME_G; + pitch = PITCH_LITERAL (G, NATURAL, octave); break; - case GDK_KEY_a: - case GDK_KEY_A: - pitch_name = PITCH_NAME_A; + case GDK_KEY_h: + case GDK_KEY_H: + pitch = PITCH_LITERAL (A, NATURAL, octave); break; - case GDK_KEY_b: - case GDK_KEY_B: - pitch_name = PITCH_NAME_B; + case GDK_KEY_j: + case GDK_KEY_J: + pitch = PITCH_LITERAL (B, NATURAL, octave); + break; + case GDK_KEY_k: + case GDK_KEY_K: + pitch = PITCH_LITERAL (C, NATURAL, octave + 1); + break; + case GDK_KEY_l: + case GDK_KEY_L: + pitch = PITCH_LITERAL (D, NATURAL, octave + 1); + break; + case GDK_KEY_semicolon: + case GDK_KEY_colon: + pitch = PITCH_LITERAL (E, NATURAL, octave + 1); + break; + case GDK_KEY_apostrophe: + case GDK_KEY_quotedbl: + pitch = PITCH_LITERAL (F, NATURAL, octave + 1); + break; + case GDK_KEY_w: + case GDK_KEY_W: + pitch = PITCH_LITERAL (C, SHARP, octave); + break; + case GDK_KEY_e: + case GDK_KEY_E: + pitch = PITCH_LITERAL (D, SHARP, octave); + break; + case GDK_KEY_t: + case GDK_KEY_T: + pitch = PITCH_LITERAL (F, SHARP, octave); + break; + case GDK_KEY_y: + case GDK_KEY_Y: + pitch = PITCH_LITERAL (G, SHARP, octave); + break; + case GDK_KEY_u: + case GDK_KEY_U: + pitch = PITCH_LITERAL (A, SHARP, octave); + break; + case GDK_KEY_o: + case GDK_KEY_O: + pitch = PITCH_LITERAL (C, SHARP, octave + 1); + break; + case GDK_KEY_p: + case GDK_KEY_P: + pitch = PITCH_LITERAL (D, SHARP, octave + 1); + break; + case GDK_KEY_bracketright: + case GDK_KEY_braceright: + pitch = PITCH_LITERAL (F, SHARP, octave + 1); break; case GDK_KEY_0: case GDK_KEY_1: @@ -219,20 +270,9 @@ on_key_press_event (GtkWidget *widget, case GDK_KEY_space: scherzo_press_pedal (scherzo); break; - case GDK_KEY_Up: - if (scherzo->keyboard_accidental < PITCH_ACCIDENTAL_DOUBLE_SHARP) - scherzo->keyboard_accidental++; - break; - case GDK_KEY_Down: - if (scherzo->keyboard_accidental > PITCH_ACCIDENTAL_DOUBLE_FLAT) - scherzo->keyboard_accidental--; - break; } - pitch = PITCH (pitch_name, scherzo->keyboard_accidental, octave); - - if ((key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_G) || - (key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_g)) + if (pitch != PITCH_NOT_A_PITCH) { scherzo_press_note (scherzo, pitch); _judge_pitch (scherzo, pitch); @@ -253,53 +293,96 @@ on_key_release_event (unused (GtkWidget *widget), { scherzo_t *scherzo = user_data; int octave; - /* Initialize to keep the compiler quiet. */ - pitch_name_t pitch_name = PITCH_NAME_C; - pitch_t pitch; + pitch_t pitch = PITCH_NOT_A_PITCH; - if (scherzo->challenge.note) - octave = PITCH_OCTAVE (scherzo->challenge.note->pitch); + if (scherzo->challenge.active) + octave = PITCH_OCTAVE (scherzo->challenge.pitch); else octave = scherzo->keyboard_octave; switch (key->keyval) { - case GDK_KEY_c: - case GDK_KEY_C: - pitch_name = PITCH_NAME_C; + case GDK_KEY_a: + case GDK_KEY_A: + pitch = PITCH_LITERAL (C, NATURAL, octave); + break; + case GDK_KEY_s: + case GDK_KEY_S: + pitch = PITCH_LITERAL (D, NATURAL, octave); break; case GDK_KEY_d: case GDK_KEY_D: - pitch_name = PITCH_NAME_D; - break; - case GDK_KEY_e: - case GDK_KEY_E: - pitch_name = PITCH_NAME_E; + pitch = PITCH_LITERAL (E, NATURAL, octave); break; case GDK_KEY_f: case GDK_KEY_F: - pitch_name = PITCH_NAME_F; + pitch = PITCH_LITERAL (F, NATURAL, octave); break; case GDK_KEY_g: case GDK_KEY_G: - pitch_name = PITCH_NAME_G; + pitch = PITCH_LITERAL (G, NATURAL, octave); break; - case GDK_KEY_a: - case GDK_KEY_A: - pitch_name = PITCH_NAME_A; + case GDK_KEY_h: + case GDK_KEY_H: + pitch = PITCH_LITERAL (A, NATURAL, octave); break; - case GDK_KEY_b: - case GDK_KEY_B: - pitch_name = PITCH_NAME_B; + case GDK_KEY_j: + case GDK_KEY_J: + pitch = PITCH_LITERAL (B, NATURAL, octave); + break; + case GDK_KEY_k: + case GDK_KEY_K: + pitch = PITCH_LITERAL (C, NATURAL, octave + 1); + break; + case GDK_KEY_l: + case GDK_KEY_L: + pitch = PITCH_LITERAL (D, NATURAL, octave + 1); + break; + case GDK_KEY_semicolon: + case GDK_KEY_colon: + pitch = PITCH_LITERAL (E, NATURAL, octave + 1); + break; + case GDK_KEY_apostrophe: + case GDK_KEY_quotedbl: + pitch = PITCH_LITERAL (F, NATURAL, octave + 1); + break; + case GDK_KEY_w: + case GDK_KEY_W: + pitch = PITCH_LITERAL (C, SHARP, octave); + break; + case GDK_KEY_e: + case GDK_KEY_E: + pitch = PITCH_LITERAL (D, SHARP, octave); + break; + case GDK_KEY_t: + case GDK_KEY_T: + pitch = PITCH_LITERAL (F, SHARP, octave); + break; + case GDK_KEY_y: + case GDK_KEY_Y: + pitch = PITCH_LITERAL (G, SHARP, octave); + break; + case GDK_KEY_u: + case GDK_KEY_U: + pitch = PITCH_LITERAL (A, SHARP, octave); + break; + case GDK_KEY_o: + case GDK_KEY_O: + pitch = PITCH_LITERAL (C, SHARP, octave + 1); + break; + case GDK_KEY_p: + case GDK_KEY_P: + pitch = PITCH_LITERAL (D, SHARP, octave + 1); + break; + case GDK_KEY_bracketright: + case GDK_KEY_braceright: + pitch = PITCH_LITERAL (F, SHARP, octave + 1); break; case GDK_KEY_space: scherzo_release_pedal (scherzo); break; } - pitch = PITCH (pitch_name, scherzo->keyboard_accidental, octave); - - if ((key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_G) || - (key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_g)) + if (pitch != PITCH_NOT_A_PITCH) { scherzo_release_note (scherzo, pitch); _score_challenge (scherzo); @@ -410,6 +493,17 @@ _midi_to_pitch (unsigned char midi_note) } } +/* Return true if 'a' and 'b' sound identical, (even if spelled differently) + * + * This comparison considers octaves as significant. So Bb and A# in + * the same octave are considered enharmonic, but Bb and A# in + * different octaves are not. */ +static int +_pitches_are_enharmonic (pitch_t a, pitch_t b) +{ + return _pitch_to_midi (a) == _pitch_to_midi (b); +} + /* Determine a chord name (if any) from the current notes pressed */ typedef struct analyzed_pitch { @@ -549,61 +643,6 @@ _chord_signature_matches (analyzed_pitch_t *pitches, return 1; } -static const char * -_pitch_str (pitch_t pitch) -{ - static char double_flat[] = "X𝄫"; - static char flat[] = "X♭"; - static char natural[] = "X"; - static char sharp[] = "X♯"; - static char double_sharp[] = "X𝄪"; - char *ret; - - switch (PITCH_ACCIDENTAL (pitch)) { - case PITCH_ACCIDENTAL_DOUBLE_FLAT: - ret = double_flat; - break; - case PITCH_ACCIDENTAL_FLAT: - ret = flat; - break; - case PITCH_ACCIDENTAL_NATURAL: - ret = natural; - break; - case PITCH_ACCIDENTAL_SHARP: - ret = sharp; - break; - case PITCH_ACCIDENTAL_DOUBLE_SHARP: - ret = double_sharp; - break; - } - - switch (PITCH_NAME(pitch)) { - case PITCH_NAME_C: - ret[0] = 'C'; - break; - case PITCH_NAME_D: - ret[0] = 'D'; - break; - case PITCH_NAME_E: - ret[0] = 'E'; - break; - case PITCH_NAME_F: - ret[0] = 'F'; - break; - case PITCH_NAME_G: - ret[0] = 'G'; - break; - case PITCH_NAME_A: - ret[0] = 'A'; - break; - case PITCH_NAME_B: - ret[0] = 'B'; - break; - } - - return ret; -} - typedef struct chord_signature { int num_degrees; @@ -834,7 +873,6 @@ scherzo_adjust_pitch_to_match_modified_degree (pitch_t *pitch, static void _spell_chord_by_signature (pitch_group_t *chord, - int num_notes, chord_signature_t *signature, pitch_t root) { @@ -846,7 +884,7 @@ _spell_chord_by_signature (pitch_group_t *chord, root_midi = _pitch_to_midi (root); root = _midi_to_pitch (root_midi); - for (i = 0; i < num_notes; i++) { + for (i = 0; i < chord->num_pitches; i++) { note_half_steps = _pitch_from_root_in_half_steps (chord->pitches[i], root); for (degree = 0; degree < signature->num_degrees; degree++) { @@ -866,30 +904,24 @@ _spell_chord_by_signature (pitch_group_t *chord, } static void -scherzo_analyze_chord (scherzo_t *scherzo) +_analyze_chord (pitch_group_t *chord, + chord_signature_t **signature_ret, + pitch_t **root_ret) { void *local = talloc_new (NULL); analyzed_pitch_t *pitches; - pitch_group_t *pitch_group; - unsigned i, j, num_pitches; + int i, j, num_pitches; int bass_pitch, inversion; pitch_t root; chord_signature_t *match = NULL; - char *chord_name; - if (scherzo->pedal_pressed) - pitch_group = &scherzo->notes_pedaled; - else - pitch_group = &scherzo->notes_pressed; + /* Default to no-match for any early return */ + *signature_ret = NULL; + *root_ret = NULL; - num_pitches = pitch_group->num_pitches; - - if (scherzo->chord) { - score_remove_chord (scherzo->chord); - scherzo->chord = NULL; - } + num_pitches = chord->num_pitches; - if (num_pitches <= 1) + if (chord->num_pitches <= 1) goto DONE; pitches = talloc_array (local, analyzed_pitch_t, num_pitches); @@ -897,7 +929,7 @@ scherzo_analyze_chord (scherzo_t *scherzo) goto DONE; for (i = 0; i < num_pitches; i++) { - pitch_t pitch = pitch_group->pitches[i]; + pitch_t pitch = chord->pitches[i]; pitches[i].pitch = pitch; pitches[i].midi_pitch = _pitch_to_midi (pitch); /* Relative pitch will be filled in after sorting. */ @@ -959,27 +991,16 @@ scherzo_analyze_chord (scherzo_t *scherzo) } HAVE_MATCH: - chord_name = (char *) signatures[i].name; - if (match) { - /* Don't print root pitch for octaves and inversions, - * (since a pitch name alone looks like a major triad) */ - if (num_pitches < 3) { - chord_name = talloc_strdup (local, chord_name); - } else { - chord_name = talloc_asprintf (local, "%s%s", - _pitch_str (root), - chord_name); + *signature_ret = match; + for (i = 0; i < chord->num_pitches; i++) { + if (chord->pitches[i] == root) { + *root_ret = &chord->pitches[i]; + break; + } } - - _spell_chord_by_signature (pitch_group, num_pitches, - match, root); - } else { - chord_name = talloc_strdup (local, "?"); } - scherzo->chord = score_add_chord (scherzo->treble, chord_name); - DONE: talloc_free (local); } @@ -1037,6 +1058,22 @@ pitch_group_remove_pitch_at (pitch_group_t *group, int i) group->num_pitches--; } +/* Remove a 'pitch' from 'group', (including any enharmonic pitch) + * See also: pitch_group_remove_pitch + */ +static void +pitch_group_remove_pitch_enharmonic (pitch_group_t *group, pitch_t pitch) +{ + int i; + + for (i = 0; i < group->num_pitches; i++) + if (_pitches_are_enharmonic (group->pitches[i], pitch)) + pitch_group_remove_pitch_at (group, i); +} + +/* Remove an exactly-matching pitch from 'group' + * See also: pitch_group_remove_pitch_enharmonic + * static void pitch_group_remove_pitch (pitch_group_t *group, pitch_t pitch) { @@ -1046,6 +1083,7 @@ pitch_group_remove_pitch (pitch_group_t *group, pitch_t pitch) if (group->pitches[i] == pitch) pitch_group_remove_pitch_at (group, i); } +*/ static void scherzo_update_notes_and_chord (scherzo_t *scherzo) @@ -1055,8 +1093,7 @@ scherzo_update_notes_and_chord (scherzo_t *scherzo) { score_remove_notes (scherzo->score); - if (scherzo->chord) - score_remove_chord (scherzo->chord); + score_remove_chords (scherzo->score); gtk_widget_queue_draw (scherzo->window); } @@ -1065,6 +1102,11 @@ scherzo_update_notes_and_chord (scherzo_t *scherzo) static void scherzo_press_note (scherzo_t *scherzo, pitch_t pitch) { + void *local = talloc_new (NULL); + pitch_group_t *chord; + chord_signature_t *signature; + char *chord_name = NULL; + pitch_t *root; int i; /* Do nothing if this note is already pressed. */ @@ -1077,8 +1119,38 @@ scherzo_press_note (scherzo_t *scherzo, pitch_t pitch) if (scherzo->pedal_pressed) pitch_group_add_pitch (&scherzo->notes_pedaled, pitch); - /* Remove all notes from score, then add current notes. */ + /* Remove all notes/chords from score, then add current notes. */ score_remove_notes (scherzo->score); + score_remove_chords (scherzo->score); + + if (scherzo->pedal_pressed) + chord = &scherzo->notes_pedaled; + else + chord = &scherzo->notes_pressed; + + _analyze_chord (chord, &signature, &root); + + if (signature) { + /* Don't print root pitch for octaves and inversions, + * (since a pitch name alone looks like a major triad) */ + if (signature->num_degrees < 3) { + chord_name = talloc_strdup (local, signature->name); + } else { + _spell_chord_by_signature (&scherzo->notes_pressed, + signature, *root); + _spell_chord_by_signature (&scherzo->notes_pedaled, + signature, *root); + + chord_name = talloc_asprintf (local, "%s%s", + pitch_string (*root), + signature->name); + } + } else if (chord->num_pitches > 2) { + chord_name = talloc_strdup (local, "?"); + } + + if (chord_name) + scherzo->chord = score_add_chord (scherzo->treble, chord_name); for (i = 0; i < scherzo->notes_pressed.num_pitches; i++) { @@ -1092,13 +1164,13 @@ scherzo_press_note (scherzo_t *scherzo, pitch_t pitch) SCORE_DURATION_WHOLE); } - scherzo_analyze_chord (scherzo); + talloc_free (local); } static void scherzo_release_note (scherzo_t *scherzo, pitch_t pitch) { - pitch_group_remove_pitch (&scherzo->notes_pressed, pitch); + pitch_group_remove_pitch_enharmonic (&scherzo->notes_pressed, pitch); if (scherzo->notes_pressed.num_pitches == 0) scherzo_update_notes_and_chord (scherzo); @@ -1125,6 +1197,9 @@ scherzo_press_pedal (scherzo_t *scherzo) { int i; + if (scherzo->pedal_pressed) + return; + scherzo->pedal_pressed = 1; /* Copy all pressed notes to pedaled notes */ @@ -1137,6 +1212,9 @@ scherzo_release_pedal (scherzo_t *scherzo) { int i; + if (! scherzo->pedal_pressed) + return; + /* Remove all previously pedaled notes. */ for (i = scherzo->notes_pedaled.num_pitches - 1; i >=0; i--) pitch_group_remove_pitch_at (&scherzo->notes_pedaled, i); @@ -1146,7 +1224,7 @@ scherzo_release_pedal (scherzo_t *scherzo) scherzo_update_notes_and_chord (scherzo); } -void +static void _select_challenge (scherzo_t *scherzo) { category_t *category_unused; @@ -1154,15 +1232,9 @@ _select_challenge (scherzo_t *scherzo) item_t *item; challenge_t *challenge = &scherzo->challenge; pitch_name_t pitch_name; - pitch_t pitch; int octave; char *s; - if (challenge->note) { - score_remove_note (challenge->note); - challenge->note = NULL; - } - mnemon_select_item (&scherzo->mnemon, &challenge->bin, &challenge->item_index, @@ -1220,10 +1292,9 @@ _select_challenge (scherzo_t *scherzo) octave = *s - '0'; - pitch = PITCH (pitch_name, PITCH_ACCIDENTAL_NATURAL, octave); + challenge->pitch = PITCH (pitch_name, PITCH_ACCIDENTAL_NATURAL, octave); - challenge->note = score_staff_add_note (challenge->staff, pitch, - SCORE_DURATION_WHOLE); + challenge->active = 1; challenge->satisfied = 0; challenge->mistaken = 0; } @@ -1234,10 +1305,10 @@ _judge_pitch (scherzo_t *scherzo, pitch_t pitch) { challenge_t *challenge = &scherzo->challenge; - if (! challenge->note) + if (! challenge->active) return; - if (pitch == challenge->note->pitch) + if (pitch == challenge->pitch) challenge->satisfied = 1; else challenge->mistaken = 1; @@ -1250,7 +1321,7 @@ _score_challenge (scherzo_t *scherzo) { challenge_t *challenge = &scherzo->challenge; - if (! challenge->note) + if (! challenge->active) return; if (! challenge->satisfied) @@ -1356,20 +1427,22 @@ main (int argc, char *argv[]) scherzo.chord = NULL; - /* Default to octave 4 and natural for computer keyboard keypresses. */ + /* Default to octave 4 for computer keyboard keypresses. */ scherzo.keyboard_octave = 4; - scherzo.keyboard_accidental = PITCH_ACCIDENTAL_NATURAL; pitch_group_init (scherzo.ctx, &scherzo.notes_pressed); pitch_group_init (scherzo.ctx, &scherzo.notes_pedaled); scherzo.pedal_pressed = 0; + scherzo.key = PITCH_CLASS_LITERAL (C, NATURAL); + score_set_key (scherzo.score, scherzo.key); + mnemon_init (&scherzo.mnemon); /* XXX: Should create a default file if one cannot be loaded. */ mnemon_load_category (&scherzo.mnemon, "scherzo-notes"); - scherzo.challenge.note = NULL; + scherzo.challenge.active = 0; /* _select_challenge (&scherzo); */