]> git.cworth.org Git - scherzo/commitdiff
Add support for pedal
authorCarl Worth <cworth@cworth.org>
Wed, 18 Sep 2013 03:04:06 +0000 (20:04 -0700)
committerCarl Worth <cworth@cworth.org>
Wed, 18 Sep 2013 03:04:06 +0000 (20:04 -0700)
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.

scherzo.c

index 55a2cc98d40d00b7f80f40d572a07846a6351c76..676f5b459001412ad4ce7c4605dbc07e007cba8c 100644 (file)
--- 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. */