From: Carl Worth Date: Wed, 18 Sep 2013 03:04:06 +0000 (-0700) Subject: Add support for pedal X-Git-Url: https://git.cworth.org/git?p=scherzo;a=commitdiff_plain;h=f9ffdfde546d16f24ab5d30ed9caef2c687b4755 Add support for pedal As scherzo listens to the pedal, it will analyze the chord harmony based on notes that continue to sustain due to the pedal. I'm not 100% comfortable with the empirically-derived value of 64 for the sustain pedal. I'd feel a bit better if I had found a symbolic constant for this in the ALSA header files. As things stand now, I'm not sure if the sustain pedal on another keyboard will return the same value. --- diff --git a/scherzo.c b/scherzo.c index 55a2cc9..676f5b4 100644 --- a/scherzo.c +++ b/scherzo.c @@ -41,6 +41,14 @@ typedef struct challenge int mistaken; } challenge_t; +typedef struct note_group +{ + void *ctx; + score_note_t **notes; + int size; + int num_notes; +} note_group_t; + typedef struct scherzo { void *ctx; @@ -62,16 +70,24 @@ typedef struct scherzo mnemon_t mnemon; challenge_t challenge; - int num_notes_pressed; - score_note_t **notes_pressed; + note_group_t notes_pressed; + note_group_t notes_pedaled; + + int pedal_pressed; } scherzo_t; /* Forward declarations. */ static score_note_t * -scherzo_add_note (scherzo_t *scherzo, score_pitch_t pitch, int octave); +scherzo_press_note (scherzo_t *scherzo, score_pitch_t pitch, int octave); static void -scherzo_remove_note (scherzo_t *scherzo, score_pitch_t pitch, int octave); +scherzo_release_note (scherzo_t *scherzo, score_pitch_t pitch, int octave); + +static void +scherzo_press_pedal (scherzo_t *scherzo); + +static void +scherzo_release_pedal (scherzo_t *scherzo); static void _judge_note (scherzo_t *scherzo, score_note_t *note); @@ -195,6 +211,9 @@ on_key_press_event (GtkWidget *widget, case GDK_KEY_8: scherzo->keyboard_octave = key->keyval - GDK_KEY_0; break; + case GDK_KEY_space: + scherzo_press_pedal (scherzo); + break; } if ((key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_G) || @@ -202,7 +221,7 @@ on_key_press_event (GtkWidget *widget, { score_note_t *note; - note = scherzo_add_note (scherzo, pitch, octave); + note = scherzo_press_note (scherzo, pitch, octave); _judge_note (scherzo, note); gtk_widget_queue_draw (scherzo->window); @@ -257,12 +276,15 @@ on_key_release_event (unused (GtkWidget *widget), case GDK_KEY_B: pitch = SCORE_PITCH_B; break; + case GDK_KEY_space: + scherzo_release_pedal (scherzo); + break; } if ((key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_G) || (key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_g)) { - scherzo_remove_note (scherzo, pitch, octave); + scherzo_release_note (scherzo, pitch, octave); _score_challenge (scherzo); gtk_widget_queue_draw (scherzo->window); @@ -427,10 +449,18 @@ scherzo_analyze_chord (scherzo_t *scherzo) { void *local = talloc_new (NULL); analyzed_note_t *notes; - unsigned i, num_notes = scherzo->num_notes_pressed; + note_group_t *note_group; + unsigned i, num_notes; int bass_pitch; const char *chord_name = NULL; + if (scherzo->pedal_pressed) + note_group = &scherzo->notes_pedaled; + else + note_group = &scherzo->notes_pressed; + + num_notes = note_group->num_notes; + struct { int pitches[2]; const char *name; } intervals[] = { { {0, 1}, "Minor 2nd"}, { {0, 2}, "Major 2nd"}, @@ -477,7 +507,7 @@ scherzo_analyze_chord (scherzo_t *scherzo) goto DONE; for (i = 0; i < num_notes; i++) { - score_note_t *note = scherzo->notes_pressed[i]; + score_note_t *note = note_group->notes[i]; notes[i].note = note; notes[i].midi_pitch = _score_pitch_and_octave_to_midi (note->pitch, note->octave); @@ -517,8 +547,51 @@ DONE: talloc_free (local); } +static void +note_group_init (void *ctx, note_group_t *group) +{ + group->ctx = ctx; + group->notes = NULL; + group->size = 0; + group->num_notes = 0; +} + +static void +note_group_add_note (note_group_t *group, score_note_t *note) +{ + group->num_notes++; + + if (group->num_notes > group->size) { + group->size++; + group->notes = talloc_realloc (group->ctx, group->notes, + score_note_t*, group->size); + + if (group->notes == NULL) { + fprintf (stderr, "Out of memory.\n"); + exit (1); + } + } + + group->notes[group->num_notes - 1] = note; +} + +static void +note_group_remove_note_at (note_group_t *group, int i) +{ + if (i >= group->num_notes) { + fprintf (stderr, "Internal error: No note to remove at index %d\n", i); + exit (1); + } + + if (i < group->num_notes - 1) { + memmove (group->notes + i, group->notes + i + 1, + (group->num_notes - 1 - i) * sizeof (score_note_t*)); + } + group->num_notes--; +} + static score_note_t * -scherzo_add_note (scherzo_t *scherzo, score_pitch_t pitch, int octave) +scherzo_press_note (scherzo_t *scherzo, score_pitch_t pitch, int octave) { score_staff_t *staff; score_note_t *note; @@ -533,78 +606,109 @@ scherzo_add_note (scherzo_t *scherzo, score_pitch_t pitch, int octave) } /* Do nothing if this note is already pressed. */ - for (i = 0; i < scherzo->num_notes_pressed; i++) { - if (scherzo->notes_pressed[i]->pitch == pitch && - scherzo->notes_pressed[i]->octave == octave) + for (i = 0; i < scherzo->notes_pressed.num_notes; i++) { + if (scherzo->notes_pressed.notes[i]->pitch == pitch && + scherzo->notes_pressed.notes[i]->octave == octave) { - return scherzo->notes_pressed[i]; + return scherzo->notes_pressed.notes[i]; } } 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); - } + note_group_add_note (&scherzo->notes_pressed, note); - scherzo->notes_pressed[scherzo->num_notes_pressed - 1] = note; + if (scherzo->pedal_pressed) + note_group_add_note (&scherzo->notes_pedaled, note); scherzo_analyze_chord (scherzo); return note; } - static void -scherzo_remove_note (scherzo_t *scherzo, score_pitch_t pitch, int octave) +scherzo_release_note (scherzo_t *scherzo, score_pitch_t pitch, int octave) { score_note_t *note; int i; + int found = 0; - for (i = 0; i < scherzo->num_notes_pressed; i++) { - note = scherzo->notes_pressed[i]; + for (i = scherzo->notes_pressed.num_notes - 1; i >=0; i--) { + note = scherzo->notes_pressed.notes[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--; + found = 1; + if (! scherzo->pedal_pressed) + score_remove_note (note); + note_group_remove_note_at (&scherzo->notes_pressed, i); } } + if (found == 0) { + fprintf (stderr, "Internal error: Failed to find note to release.\n"); + } + scherzo_analyze_chord (scherzo); } static score_note_t * -scherzo_add_note_midi (scherzo_t *scherzo, unsigned char midi_note) +scherzo_press_note_midi (scherzo_t *scherzo, unsigned char midi_note) { score_pitch_t pitch; int octave; _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave); - return scherzo_add_note (scherzo, pitch, octave); + return scherzo_press_note (scherzo, pitch, octave); } - static void -scherzo_remove_note_midi (scherzo_t *scherzo, unsigned char midi_note) +scherzo_release_note_midi (scherzo_t *scherzo, unsigned char midi_note) { score_pitch_t pitch; int octave; _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave); - scherzo_remove_note (scherzo, pitch, octave); + scherzo_release_note (scherzo, pitch, octave); +} + +static void +scherzo_press_pedal (scherzo_t *scherzo) +{ + int i; + + scherzo->pedal_pressed = 1; + + /* Copy all pressed notes to pedaled notes */ + for (i = 0; i < scherzo->notes_pressed.num_notes; i++) + note_group_add_note (&scherzo->notes_pedaled, scherzo->notes_pressed.notes[i]); +} + +static void +scherzo_release_pedal (scherzo_t *scherzo) +{ + score_note_t *note, *new_note; + int i; + + /* Make new notes in score for all pressed notes. */ + for (i = 0; i < scherzo->notes_pressed.num_notes; i++) { + note = scherzo->notes_pressed.notes[i]; + new_note = score_add_note (note->staff, note->pitch, note->octave, note->duration); + scherzo->notes_pressed.notes[i] = new_note; + } + + /* Then remove all previously pedaled notes from the score. */ + for (i = scherzo->notes_pedaled.num_notes - 1; i >=0; i--) { + note = scherzo->notes_pedaled.notes[i]; + score_remove_note (note); + note_group_remove_note_at (&scherzo->notes_pedaled, i); + } + + scherzo->pedal_pressed = 0; + + scherzo_analyze_chord (scherzo); + + gtk_widget_queue_draw (scherzo->window); } void @@ -758,12 +862,12 @@ on_midi_input (unused (GIOChannel *channel), /* Incomplete event. Nothing to do. */ break; case SND_SEQ_EVENT_NOTEON: - note = scherzo_add_note_midi (scherzo, event.data.note.note); + note = scherzo_press_note_midi (scherzo, event.data.note.note); _judge_note (scherzo, note); need_redraw = TRUE; break; case SND_SEQ_EVENT_NOTEOFF: - scherzo_remove_note_midi (scherzo, event.data.note.note); + scherzo_release_note_midi (scherzo, event.data.note.note); _score_challenge (scherzo); need_redraw = TRUE; break; @@ -773,6 +877,19 @@ on_midi_input (unused (GIOChannel *channel), case SND_SEQ_EVENT_SENSING: /* Ignore for now as my piano sends a constant stream of these. */ break; + case SND_SEQ_EVENT_CONTROLLER: + /* XXX: My piano gives me 64 for the sustain pedal, is + * that universal? */ + if (event.data.control.param == 64) { + if (event.data.control.value == 0) + scherzo_release_pedal (scherzo); + else + scherzo_press_pedal (scherzo); + } else { + fprintf (stderr, "Fixme: Unhandled MIDI Control event (param=%d, value=%d)\n", + event.data.control.param, event.data.control.value); + } + break; default: fprintf (stderr, "Fixme: Do not yet know how to handle MIDI event %d\n", event.type); @@ -813,8 +930,10 @@ main (int argc, char *argv[]) /* Default to octave 4 for computer keyboard keypresses. */ scherzo.keyboard_octave = 4; - scherzo.num_notes_pressed = 0; - scherzo.notes_pressed = NULL; + note_group_init (scherzo.ctx, &scherzo.notes_pressed); + note_group_init (scherzo.ctx, &scherzo.notes_pedaled); + + scherzo.pedal_pressed = 0; mnemon_init (&scherzo.mnemon); /* XXX: Should create a default file if one cannot be loaded. */