X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=scherzo.c;h=955f5b7551d773f865feabb1761424515a49a233;hb=3f6a9cdbf572c83c323f40cc66e301bdf128f076;hp=e6a67ec1ce7bbc4e83046d9e94f2d10e7c5d1bcd;hpb=03f4abc3182dfe2602d44d38ce7b3db2892e018e;p=scherzo diff --git a/scherzo.c b/scherzo.c index e6a67ec..955f5b7 100644 --- a/scherzo.c +++ b/scherzo.c @@ -22,13 +22,27 @@ #include #include "score.h" +#include "mnemon.h" #define unused(foo) foo __attribute__((unused)) #define MIDI_BUF_SIZE 4096 +typedef struct challenge +{ + bin_t *bin; + int item_index; + score_staff_t *staff; + score_note_t *note; + + int satisfied; + int mistaken; +} challenge_t; + typedef struct scherzo { + void *ctx; + GtkWidget *window; score_t *score; int staff_height; @@ -36,6 +50,12 @@ typedef struct scherzo score_staff_t *bass; int midi_fd; snd_midi_event_t *snd_midi_event; + + mnemon_t mnemon; + challenge_t challenge; + + int num_notes_pressed; + score_note_t **notes_pressed; } scherzo_t; static int @@ -72,7 +92,7 @@ on_expose_event_draw (GtkWidget *widget, cairo_paint (cr); /* Add some padding on the sides and top */ - cairo_translate (cr, pad, (int) scherzo->staff_height / 2); + cairo_translate (cr, pad, 2 * scherzo->staff_height); score_set_staff_height (score, scherzo->staff_height); score_set_width (score, widget_width - 2 * pad); @@ -162,42 +182,170 @@ _midi_to_score_pitch_and_octave (unsigned char midi_note, } } -static void +static score_note_t * scherzo_add_note_midi (scherzo_t *scherzo, unsigned char midi_note) { score_staff_t *staff; score_pitch_t pitch; int octave; + score_note_t *note; - /* Anything at Middle C and above goes on the treble staff by default. */ - if (midi_note >= 60) - staff = scherzo->treble; - else - staff = scherzo->bass; + staff = scherzo->challenge.staff; _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave); - score_staff_add_note (staff, pitch, octave, SCORE_DURATION_WHOLE); + note = score_add_note (staff, pitch, octave, SCORE_DURATION_WHOLE); + + scherzo->num_notes_pressed++; + scherzo->notes_pressed = talloc_realloc (scherzo->ctx, + scherzo->notes_pressed, + score_note_t*, + scherzo->num_notes_pressed); + if (scherzo->notes_pressed == NULL) { + fprintf (stderr, "Out of memory.\n"); + exit (1); + } + + scherzo->notes_pressed[scherzo->num_notes_pressed - 1] = note; + + return note; } static void scherzo_remove_note_midi (scherzo_t *scherzo, unsigned char midi_note) { - score_staff_t *staff; score_pitch_t pitch; int octave; score_note_t *note; + int i; - /* Anything at Middle C and above goes on the treble staff by default. */ - if (midi_note >= 60) - staff = scherzo->treble; + _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave); + + for (i = 0; i < scherzo->num_notes_pressed; i++) { + note = scherzo->notes_pressed[i]; + if (note->pitch == pitch && note->octave == octave) { + score_remove_note (note); + if (i < scherzo->num_notes_pressed - 1) { + memmove (scherzo->notes_pressed + i, + scherzo->notes_pressed + i + 1, + (scherzo->num_notes_pressed - 1 - i) * sizeof (score_note_t*)); + } + scherzo->num_notes_pressed--; + i--; + } + } +} + +void +_select_challenge (scherzo_t *scherzo) +{ + category_t *category_unused; + bool_t introduced_unused; + item_t *item; + challenge_t *challenge = &scherzo->challenge; + score_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, + &category_unused, + &introduced_unused); + + item = challenge->bin->items[challenge->item_index]; + + s = item->challenge; + if (strncmp (s, "treble:", 7) == 0) { + s += 7; + challenge->staff = scherzo->treble; + } else if (strncmp (s, "bass:", 5) == 0) { + s += 5; + challenge->staff = scherzo->bass; + } else { + fprintf (stderr, + "Malformed staff name: %s (expected 'treble:' or 'bass:')\n", + s); + exit (1); + } + + switch (*s) { + case 'C': + pitch = SCORE_PITCH_VALUE(C, NATURAL); + break; + case 'D': + pitch = SCORE_PITCH_VALUE(D, NATURAL); + break; + case 'E': + pitch = SCORE_PITCH_VALUE(E, NATURAL); + break; + case 'F': + pitch = SCORE_PITCH_VALUE(F, NATURAL); + break; + case 'G': + pitch = SCORE_PITCH_VALUE(G, NATURAL); + break; + case 'A': + pitch = SCORE_PITCH_VALUE(A, NATURAL); + break; + case 'B': + pitch = SCORE_PITCH_VALUE(B, NATURAL); + break; + default: + fprintf (stderr, "Malformed pitch name: %s (expected 'A' - 'G')\n", s); + exit (1); + } + s++; + + if (*s < '0' || *s > '9') { + fprintf (stderr, "Malformed octave number: %s (expected '0' - '9')\n", s); + exit (1); + } + + octave = *s - '0'; + + challenge->note = score_add_note (challenge->staff, pitch, octave, + SCORE_DURATION_WHOLE); + challenge->satisfied = 0; + challenge->mistaken = 0; +} + +/* Determine whether the user hit the correct note. */ +static void +_judge_note (scherzo_t *scherzo, score_note_t *note) +{ + challenge_t *challenge = &scherzo->challenge; + + if (note->pitch == challenge->note->pitch && + note->octave == challenge->note->octave) + { + challenge->satisfied = 1; + } else - staff = scherzo->bass; + { + challenge->mistaken = 1; + } +} - _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave); +/* If the user got the right note (eventually), then score it in + * mnemon and show the next note. */ +static void +_score_challenge (scherzo_t *scherzo) +{ + challenge_t *challenge = &scherzo->challenge; + + if (! challenge->satisfied) + return; + + mnemon_score_item (&scherzo->mnemon, challenge->bin, challenge->item_index, + ! challenge->mistaken); - note = score_staff_find_note (staff, pitch, octave, SCORE_DURATION_WHOLE); - score_staff_remove_note (staff, note); + _select_challenge (scherzo); } static int @@ -209,6 +357,7 @@ on_midi_input (unused (GIOChannel *channel), scherzo_t *scherzo = user_data; ssize_t remaining; snd_seq_event_t event; + score_note_t *note; remaining = read (scherzo->midi_fd, buf, MIDI_BUF_SIZE); @@ -226,11 +375,13 @@ on_midi_input (unused (GIOChannel *channel), /* Incomplete event. Nothing to do. */ break; case SND_SEQ_EVENT_NOTEON: - scherzo_add_note_midi (scherzo, event.data.note.note); + note = scherzo_add_note_midi (scherzo, event.data.note.note); + _judge_note (scherzo, note); gtk_widget_queue_draw (scherzo->window); break; case SND_SEQ_EVENT_NOTEOFF: scherzo_remove_note_midi (scherzo, event.data.note.note); + _score_challenge (scherzo); gtk_widget_queue_draw (scherzo->window); break; case SND_SEQ_EVENT_CLOCK: @@ -257,9 +408,13 @@ main (int argc, char *argv[]) scherzo_t scherzo; int err; + srand (time (NULL)); + gtk_init (&argc, &argv); - scherzo.score = score_create (NULL); + scherzo.ctx = talloc_new (NULL); + + scherzo.score = score_create (scherzo.ctx); scherzo.staff_height = 48; score_set_staff_height (scherzo.score, scherzo.staff_height); @@ -267,6 +422,16 @@ main (int argc, char *argv[]) scherzo.treble = score_add_staff (scherzo.score, SCORE_CLEF_G); scherzo.bass = score_add_staff (scherzo.score, SCORE_CLEF_F); + scherzo.num_notes_pressed = 0; + scherzo.notes_pressed = NULL; + + mnemon_init (&scherzo.mnemon); + /* XXX: Should create a default file if one cannot be loaded. */ + mnemon_load_category (&scherzo.mnemon, "scherzo"); + + scherzo.challenge.note = NULL; + _select_challenge (&scherzo); + err = snd_midi_event_new (MIDI_BUF_SIZE, &scherzo.snd_midi_event); if (err) { fprintf (stderr, "Out of memory.\n"); @@ -308,8 +473,11 @@ main (int argc, char *argv[]) gtk_main (); + mnemon_save (&scherzo.mnemon); + snd_midi_event_free (scherzo.snd_midi_event); - talloc_free (scherzo.score); + + talloc_free (scherzo.ctx); return 0; }