From 16d07286a5344861bd26ad97e97612d3806d1da7 Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Sun, 25 Sep 2011 23:34:46 -0700 Subject: [PATCH] Integrate some simple mnemon quizzing into scherzo. This will be a lot more interesting once we start drawing ledger lines and accidentals. But it's already useful for white-key practice for the notes directly on the staff. Could definitely use better feedback such as different colors (or even just different horizontal spacing for challenge vs. response notes). --- Makefile | 3 +- mnemon | 2 +- scherzo.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++++----- score.c | 7 -- score.h | 32 +++++---- 5 files changed, 211 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index db95a9d..08511cc 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ all: scherzo scherzo_srcs := \ + mnemon/mnemon.c \ scherzo.c \ score.c @@ -8,7 +9,7 @@ scherzo_modules = $(scherzo_srcs:.c=.o) WARN_CFLAGS = -Wall -Wextra -SCHERZO_CFLAGS = $(CFLAGS) $(WARN_CFLAGS) $(CONFIGURE_CFLAGS) $(extra_cflags) `pkg-config --cflags gtk+-2.0 alsa` +SCHERZO_CFLAGS = $(CFLAGS) $(WARN_CFLAGS) $(CONFIGURE_CFLAGS) $(extra_cflags) -I./mnemon `pkg-config --cflags gtk+-2.0 alsa` SCHERZO_LDFLAGS = $(LDFLAGS) `pkg-config --libs gtk+-2.0 alsa` -ltalloc diff --git a/mnemon b/mnemon index 10d0f1e..12dfc35 160000 --- a/mnemon +++ b/mnemon @@ -1 +1 @@ -Subproject commit 10d0f1e757ef8c22bf8c1d921b624296a25ab238 +Subproject commit 12dfc3592e237017c9a9afd8708728f58f8ab68a diff --git a/scherzo.c b/scherzo.c index e6a67ec..c285891 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,22 +182,33 @@ _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_staff_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 @@ -187,17 +218,137 @@ scherzo_remove_note_midi (scherzo_t *scherzo, unsigned char midi_note) 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; - else - staff = scherzo->bass; + staff = scherzo->challenge.staff; _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave); - note = score_staff_find_note (staff, pitch, octave, SCORE_DURATION_WHOLE); - score_staff_remove_note (staff, note); + for (i = 0; i < scherzo->num_notes_pressed; i++) { + note = scherzo->notes_pressed[i]; + if (note->pitch == pitch && note->octave == octave) { + score_staff_remove_note (staff, 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_staff_remove_note (challenge->staff, 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_staff_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 + { + challenge->mistaken = 1; + } +} + +/* 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); + + _select_challenge (scherzo); } static int @@ -209,6 +360,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 +378,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 +411,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 +425,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 +476,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; } diff --git a/score.c b/score.c index b286609..965d127 100644 --- a/score.c +++ b/score.c @@ -58,13 +58,6 @@ struct score int num_staves; }; -typedef struct score_note -{ - score_pitch_t pitch; - int octave; - score_duration_t duration; -} score_note_t; - score_t * score_create (void *ctx) { diff --git a/score.h b/score.h index a07b926..1e74b1a 100644 --- a/score.h +++ b/score.h @@ -26,7 +26,6 @@ typedef struct score score_t; typedef struct score_staff score_staff_t; -typedef struct score_note score_note_t; #define SCORE_PITCH_ACCIDENTAL_MASK 0x07 #define SCORE_PITCH_ACCIDENTAL_SHIFT 0 @@ -62,18 +61,6 @@ typedef enum score_pitch_name typedef enum score_pitch { - SCORE_PITCH_Aff = SCORE_PITCH_VALUE (A, DOUBLE_FLAT), - SCORE_PITCH_Af = SCORE_PITCH_VALUE (A, FLAT), - SCORE_PITCH_A = SCORE_PITCH_VALUE (A, NATURAL), - SCORE_PITCH_As = SCORE_PITCH_VALUE (A, SHARP), - SCORE_PITCH_Ass = SCORE_PITCH_VALUE (A, DOUBLE_SHARP), - - SCORE_PITCH_Bff = SCORE_PITCH_VALUE (B, DOUBLE_FLAT), - SCORE_PITCH_Bf = SCORE_PITCH_VALUE (B, FLAT), - SCORE_PITCH_B = SCORE_PITCH_VALUE (B, NATURAL), - SCORE_PITCH_Bs = SCORE_PITCH_VALUE (B, SHARP), - SCORE_PITCH_Bss = SCORE_PITCH_VALUE (B, DOUBLE_SHARP), - SCORE_PITCH_Cff = SCORE_PITCH_VALUE (C, DOUBLE_FLAT), SCORE_PITCH_Cf = SCORE_PITCH_VALUE (C, FLAT), SCORE_PITCH_C = SCORE_PITCH_VALUE (C, NATURAL), @@ -103,6 +90,18 @@ typedef enum score_pitch SCORE_PITCH_G = SCORE_PITCH_VALUE (G, NATURAL), SCORE_PITCH_Gs = SCORE_PITCH_VALUE (G, SHARP), SCORE_PITCH_Gss = SCORE_PITCH_VALUE (G, DOUBLE_SHARP), + + SCORE_PITCH_Aff = SCORE_PITCH_VALUE (A, DOUBLE_FLAT), + SCORE_PITCH_Af = SCORE_PITCH_VALUE (A, FLAT), + SCORE_PITCH_A = SCORE_PITCH_VALUE (A, NATURAL), + SCORE_PITCH_As = SCORE_PITCH_VALUE (A, SHARP), + SCORE_PITCH_Ass = SCORE_PITCH_VALUE (A, DOUBLE_SHARP), + + SCORE_PITCH_Bff = SCORE_PITCH_VALUE (B, DOUBLE_FLAT), + SCORE_PITCH_Bf = SCORE_PITCH_VALUE (B, FLAT), + SCORE_PITCH_B = SCORE_PITCH_VALUE (B, NATURAL), + SCORE_PITCH_Bs = SCORE_PITCH_VALUE (B, SHARP), + SCORE_PITCH_Bss = SCORE_PITCH_VALUE (B, DOUBLE_SHARP) } score_pitch_t; typedef enum score_duration @@ -127,6 +126,13 @@ typedef enum score_duration #define SCORE_BUILD_NOTE(pitch, octave, duration) SCORE_PITCH_##pitch, (octave), SCORE_DURATION_##duration +typedef struct score_note +{ + score_pitch_t pitch; + int octave; + score_duration_t duration; +} score_note_t; + typedef enum score_clef { SCORE_CLEF_G, -- 2.43.0