From 03f4abc3182dfe2602d44d38ce7b3db2892e018e Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Sat, 24 Sep 2011 14:39:56 -0700 Subject: [PATCH] Open MIDI device and display notes being played in real time. Every time a key on the MIDI input device is pressed, a whole note appears on the appropriate staff. When the key is released, the note disappears. No ledger lines are drawn yet, and there appears to be a bug where notes get stuck on (refusing to disappear after a key is released) triggered most easily by pressing *many* keys simultaneously several times. --- Makefile | 6 +- scherzo.c | 210 +++++++++++++++++++++++++++++++++++++++++++++--------- score.c | 53 ++++++++++++-- score.h | 12 ++++ 4 files changed, 242 insertions(+), 39 deletions(-) diff --git a/Makefile b/Makefile index a00d6bf..db95a9d 100644 --- a/Makefile +++ b/Makefile @@ -8,9 +8,9 @@ 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` +SCHERZO_CFLAGS = $(CFLAGS) $(WARN_CFLAGS) $(CONFIGURE_CFLAGS) $(extra_cflags) `pkg-config --cflags gtk+-2.0 alsa` -SCHERZO_LDFLAGS = $(LDFLAGS) `pkg-config --libs gtk+-2.0` -ltalloc +SCHERZO_LDFLAGS = $(LDFLAGS) `pkg-config --libs gtk+-2.0 alsa` -ltalloc # The user has not set any verbosity, default to quiet mode and inform the # user how to enable verbose compiles. @@ -40,7 +40,7 @@ DEPS := $(scherzo_srcs:%.c=.deps/%.d) scherzo: $(scherzo_modules) $(call quiet,CC $(CFLAGS)) $^ $(SCHERZO_LDFLAGS) -o $@ -CLEAN := $(CLEAN) scherzo $(chorale_modules) +CLEAN := $(CLEAN) scherzo $(scherzo_modules) .PHONY : clean clean: diff --git a/scherzo.c b/scherzo.c index 329884d..e6a67ec 100644 --- a/scherzo.c +++ b/scherzo.c @@ -19,14 +19,23 @@ #include #include +#include + #include "score.h" #define unused(foo) foo __attribute__((unused)) +#define MIDI_BUF_SIZE 4096 + typedef struct scherzo { + GtkWidget *window; score_t *score; int staff_height; + score_staff_t *treble; + score_staff_t *bass; + int midi_fd; + snd_midi_event_t *snd_midi_event; } scherzo_t; static int @@ -105,14 +114,148 @@ on_key_press_event (GtkWidget *widget, return FALSE; } +static void +_midi_to_score_pitch_and_octave (unsigned char midi_note, + score_pitch_t *pitch, + int *octave) +{ + *octave = midi_note / 12 - 1; + + switch (midi_note % 12) + { + case 0: + *pitch = SCORE_PITCH_C; + break; + case 1: + *pitch = SCORE_PITCH_Cs; + break; + case 2: + *pitch = SCORE_PITCH_D; + break; + case 3: + *pitch = SCORE_PITCH_Ds; + break; + case 4: + *pitch = SCORE_PITCH_E; + break; + case 5: + *pitch = SCORE_PITCH_F; + break; + case 6: + *pitch = SCORE_PITCH_Fs; + break; + case 7: + *pitch = SCORE_PITCH_G; + break; + case 8: + *pitch = SCORE_PITCH_Gs; + break; + case 9: + *pitch = SCORE_PITCH_A; + break; + case 10: + *pitch = SCORE_PITCH_As; + break; + case 11: + *pitch = SCORE_PITCH_B; + break; + } +} + +static void +scherzo_add_note_midi (scherzo_t *scherzo, unsigned char midi_note) +{ + score_staff_t *staff; + score_pitch_t pitch; + int octave; + + /* Anything at Middle C and above goes on the treble staff by default. */ + if (midi_note >= 60) + staff = scherzo->treble; + else + staff = scherzo->bass; + + _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave); + + score_staff_add_note (staff, pitch, octave, SCORE_DURATION_WHOLE); +} + +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; + + /* Anything at Middle C and above goes on the treble staff by default. */ + if (midi_note >= 60) + staff = scherzo->treble; + else + staff = scherzo->bass; + + _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); +} + +static int +on_midi_input (unused (GIOChannel *channel), + unused (GIOCondition condition), + void *user_data) +{ + unsigned char buf[MIDI_BUF_SIZE], *next; + scherzo_t *scherzo = user_data; + ssize_t remaining; + snd_seq_event_t event; + + remaining = read (scherzo->midi_fd, buf, MIDI_BUF_SIZE); + + next = buf; + while (remaining) { + long consumed; + + consumed = snd_midi_event_encode (scherzo->snd_midi_event, + next, remaining, &event); + + remaining -= consumed; + + switch (event.type) { + case SND_SEQ_EVENT_NONE: + /* Incomplete event. Nothing to do. */ + break; + case SND_SEQ_EVENT_NOTEON: + scherzo_add_note_midi (scherzo, event.data.note.note); + gtk_widget_queue_draw (scherzo->window); + break; + case SND_SEQ_EVENT_NOTEOFF: + scherzo_remove_note_midi (scherzo, event.data.note.note); + gtk_widget_queue_draw (scherzo->window); + break; + case SND_SEQ_EVENT_CLOCK: + /* Ignore for now as my piano sends a constant stream of these. */ + break; + case SND_SEQ_EVENT_SENSING: + /* Ignore for now as my piano sends a constant stream of these. */ + break; + default: + fprintf (stderr, "Fixme: Do not yet know how to handle MIDI event %d\n", + event.type); + break; + } + } + + /* Return TRUE to continue to get called in the future. */ + return TRUE; +} + int main (int argc, char *argv[]) { - GtkWidget *window; GtkWidget *drawing_area; scherzo_t scherzo; - score_staff_t *treble; - score_staff_t *bass; + int err; gtk_init (&argc, &argv); @@ -121,49 +264,52 @@ main (int argc, char *argv[]) score_set_staff_height (scherzo.score, scherzo.staff_height); score_add_brace (scherzo.score, 2); - treble = score_add_staff (scherzo.score, SCORE_CLEF_G); - bass = score_add_staff (scherzo.score, SCORE_CLEF_F); - - score_staff_add_note (treble, SCORE_BUILD_NOTE (D, 4, WHOLE)); - score_staff_add_note (treble, SCORE_BUILD_NOTE (E, 4, WHOLE)); - score_staff_add_note (treble, SCORE_BUILD_NOTE (F, 4, WHOLE)); - score_staff_add_note (treble, SCORE_BUILD_NOTE (G, 4, WHOLE)); - score_staff_add_note (treble, SCORE_BUILD_NOTE (A, 4, WHOLE)); - score_staff_add_note (treble, SCORE_BUILD_NOTE (B, 4, WHOLE)); - score_staff_add_note (treble, SCORE_BUILD_NOTE (C, 5, WHOLE)); - score_staff_add_note (treble, SCORE_BUILD_NOTE (D, 5, WHOLE)); - - score_staff_add_note (bass, SCORE_BUILD_NOTE (B, 2, WHOLE)); - score_staff_add_note (bass, SCORE_BUILD_NOTE (C, 3, WHOLE)); - score_staff_add_note (bass, SCORE_BUILD_NOTE (D, 3, WHOLE)); - score_staff_add_note (bass, SCORE_BUILD_NOTE (E, 3, WHOLE)); - score_staff_add_note (bass, SCORE_BUILD_NOTE (F, 3, WHOLE)); - score_staff_add_note (bass, SCORE_BUILD_NOTE (G, 3, WHOLE)); - score_staff_add_note (bass, SCORE_BUILD_NOTE (A, 3, WHOLE)); - score_staff_add_note (bass, SCORE_BUILD_NOTE (B, 3, WHOLE)); - - window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - - gtk_window_set_default_size (GTK_WINDOW (window), 600, 400); - - g_signal_connect (window, "delete-event", + scherzo.treble = score_add_staff (scherzo.score, SCORE_CLEF_G); + scherzo.bass = score_add_staff (scherzo.score, SCORE_CLEF_F); + + err = snd_midi_event_new (MIDI_BUF_SIZE, &scherzo.snd_midi_event); + if (err) { + fprintf (stderr, "Out of memory.\n"); + return 1; + } + +#define MIDI_DEVICE "/dev/midi1" + scherzo.midi_fd = open (MIDI_DEVICE, O_RDONLY); + if (scherzo.midi_fd < 0) { + printf ("failed to open " MIDI_DEVICE ". Midi input will not be available.\n"); + } else { + GIOChannel *channel; + + channel = g_io_channel_unix_new (scherzo.midi_fd); + g_io_channel_set_encoding (channel, NULL, NULL); + g_io_add_watch (channel, G_IO_IN, on_midi_input, &scherzo); + } + + scherzo.window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_default_size (GTK_WINDOW (scherzo.window), 600, 400); + + g_signal_connect (scherzo.window, "delete-event", G_CALLBACK (on_delete_event_quit), NULL); drawing_area = gtk_drawing_area_new (); - gtk_container_add (GTK_CONTAINER (window), drawing_area); + gtk_container_add (GTK_CONTAINER (scherzo.window), drawing_area); g_signal_connect (drawing_area, "expose-event", G_CALLBACK (on_expose_event_draw), &scherzo); - g_signal_connect (window, "key-press-event", + g_signal_connect (scherzo.window, "key-press-event", G_CALLBACK (on_key_press_event), &scherzo); - gtk_widget_show_all (window); + gtk_widget_show_all (scherzo.window); gtk_main (); + snd_midi_event_free (scherzo.snd_midi_event); + talloc_free (scherzo.score); + return 0; } diff --git a/score.c b/score.c index 7a1c231..b286609 100644 --- a/score.c +++ b/score.c @@ -18,6 +18,8 @@ * along with this program. If not, see http://www.gnu.org/licenses/ . */ +#include + #include "score.h" struct score_staff @@ -222,7 +224,6 @@ _draw_staff (score_t *score, cairo_t *cr, { int i; cairo_glyph_t clef_glyph; - cairo_text_extents_t clef_extents; cairo_save (cr); @@ -249,8 +250,6 @@ _draw_staff (score_t *score, cairo_t *cr, cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ cairo_show_glyphs (cr, &clef_glyph, 1); - cairo_glyph_extents (cr, &clef_glyph, 1, &clef_extents); - /* Draw staff lines */ for (i = 0; i < 5; i++) { cairo_move_to (cr, 0, i * score->space_height + score->line_width / 2.0); @@ -263,12 +262,14 @@ _draw_staff (score_t *score, cairo_t *cr, cairo_stroke (cr); /* Make space for clef before drawing notes */ - cairo_translate (cr, (int) (1.5 * clef_extents.width), 0); + cairo_translate (cr, (int) (4 * score->space_height), 0); /* Draw notes */ for (i = 0; i < staff->num_notes; i++) { _draw_note (score, cr, staff, staff->notes[i]); + /* Draw all notes concurrent for now (as a chord) cairo_translate (cr, score->space_height * 2.0, 0); + */ } cairo_restore (cr); @@ -402,3 +403,47 @@ score_staff_add_note (score_staff_t *staff, return note; } +void +score_staff_remove_note (score_staff_t *staff, score_note_t *note) +{ + int i; + + for (i = 0; i < staff->num_notes; i++) + if (staff->notes[i] == note) + break; + + if (i == staff->num_notes) + return; + + if (i < staff->num_notes - 1) + { + memmove (staff->notes + i, + staff->notes + i + 1, + (staff->num_notes - 1 - i) * sizeof (score_note_t *)); + } + + staff->num_notes -= 1; +} + +score_note_t * +score_staff_find_note (score_staff_t *staff, + score_pitch_t pitch, + int octave, + score_duration_t duration) +{ + int i; + score_note_t *note; + + for (i = 0; i < staff->num_notes; i++) { + note = staff->notes[i]; + if (note->pitch == pitch && + note->octave == octave && + note->duration == duration) + { + return note; + } + } + + return NULL; +} + diff --git a/score.h b/score.h index 98dd26f..a07b926 100644 --- a/score.h +++ b/score.h @@ -178,6 +178,18 @@ score_staff_add_note (score_staff_t *staff, int octave, score_duration_t); +/* Remove the given note from the given staff. */ +void +score_staff_remove_note (score_staff_t *staff, score_note_t *note); + +/* Return the first note on the given staff with the given pitch, + * octave, and durations. Returns NULL if no match is found. */ +score_note_t * +score_staff_find_note (score_staff_t *staff, + score_pitch_t pitch, + int octave, + score_duration_t duration); + /* Draw the given score_t onto the given cairo_t. * * The caller can call cairo_translate before calling score_draw to -- 2.43.0