1 /* scherzo - Music notation training
3 * Copyright © 2010 Carl Worth
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see http://www.gnu.org/licenses/ .
20 #include <gdk/gdkkeysyms.h>
22 #include <asoundlib.h>
26 #define unused(foo) foo __attribute__((unused))
28 #define MIDI_BUF_SIZE 4096
30 typedef struct scherzo
35 score_staff_t *treble;
38 snd_midi_event_t *snd_midi_event;
42 on_delete_event_quit (unused (GtkWidget *widget),
43 unused (GdkEvent *event),
44 unused (gpointer user_data))
48 /* Returning FALSE allows the default handler for delete-event
49 * to proceed to cleanup the widget. */
54 on_expose_event_draw (GtkWidget *widget,
55 unused (GdkEventExpose *expose),
58 scherzo_t *scherzo = user_data;
59 score_t *score = scherzo->score;
61 GtkAllocation allocation;
62 static const int pad = 10;
65 gtk_widget_get_allocation (widget, &allocation);
66 widget_width = allocation.width;
68 cr = gdk_cairo_create (widget->window);
70 /* White background */
71 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
74 /* Add some padding on the sides and top */
75 cairo_translate (cr, pad, (int) scherzo->staff_height / 2);
76 score_set_staff_height (score, scherzo->staff_height);
77 score_set_width (score, widget_width - 2 * pad);
79 score_draw (score, cr);
85 on_key_press_event (GtkWidget *widget,
89 scherzo_t *scherzo = user_data;
91 switch (key->keyval) {
95 case GDK_KEY_KP_Equal:
96 scherzo->staff_height += 4;
97 gtk_widget_queue_draw (widget);
101 case GDK_KEY_KP_Subtract:
102 scherzo->staff_height -= 4;
103 gtk_widget_queue_draw (widget);
113 /* Allow the event to propagate to other handlers. */
118 _midi_to_score_pitch_and_octave (unsigned char midi_note,
119 score_pitch_t *pitch,
122 *octave = midi_note / 12 - 1;
124 switch (midi_note % 12)
127 *pitch = SCORE_PITCH_C;
130 *pitch = SCORE_PITCH_Cs;
133 *pitch = SCORE_PITCH_D;
136 *pitch = SCORE_PITCH_Ds;
139 *pitch = SCORE_PITCH_E;
142 *pitch = SCORE_PITCH_F;
145 *pitch = SCORE_PITCH_Fs;
148 *pitch = SCORE_PITCH_G;
151 *pitch = SCORE_PITCH_Gs;
154 *pitch = SCORE_PITCH_A;
157 *pitch = SCORE_PITCH_As;
160 *pitch = SCORE_PITCH_B;
166 scherzo_add_note_midi (scherzo_t *scherzo, unsigned char midi_note)
168 score_staff_t *staff;
172 /* Anything at Middle C and above goes on the treble staff by default. */
174 staff = scherzo->treble;
176 staff = scherzo->bass;
178 _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave);
180 score_staff_add_note (staff, pitch, octave, SCORE_DURATION_WHOLE);
184 scherzo_remove_note_midi (scherzo_t *scherzo, unsigned char midi_note)
186 score_staff_t *staff;
191 /* Anything at Middle C and above goes on the treble staff by default. */
193 staff = scherzo->treble;
195 staff = scherzo->bass;
197 _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave);
199 note = score_staff_find_note (staff, pitch, octave, SCORE_DURATION_WHOLE);
200 score_staff_remove_note (staff, note);
204 on_midi_input (unused (GIOChannel *channel),
205 unused (GIOCondition condition),
208 unsigned char buf[MIDI_BUF_SIZE], *next;
209 scherzo_t *scherzo = user_data;
211 snd_seq_event_t event;
213 remaining = read (scherzo->midi_fd, buf, MIDI_BUF_SIZE);
219 consumed = snd_midi_event_encode (scherzo->snd_midi_event,
220 next, remaining, &event);
222 remaining -= consumed;
224 switch (event.type) {
225 case SND_SEQ_EVENT_NONE:
226 /* Incomplete event. Nothing to do. */
228 case SND_SEQ_EVENT_NOTEON:
229 scherzo_add_note_midi (scherzo, event.data.note.note);
230 gtk_widget_queue_draw (scherzo->window);
232 case SND_SEQ_EVENT_NOTEOFF:
233 scherzo_remove_note_midi (scherzo, event.data.note.note);
234 gtk_widget_queue_draw (scherzo->window);
236 case SND_SEQ_EVENT_CLOCK:
237 /* Ignore for now as my piano sends a constant stream of these. */
239 case SND_SEQ_EVENT_SENSING:
240 /* Ignore for now as my piano sends a constant stream of these. */
243 fprintf (stderr, "Fixme: Do not yet know how to handle MIDI event %d\n",
249 /* Return TRUE to continue to get called in the future. */
254 main (int argc, char *argv[])
256 GtkWidget *drawing_area;
260 gtk_init (&argc, &argv);
262 scherzo.score = score_create (NULL);
263 scherzo.staff_height = 48;
264 score_set_staff_height (scherzo.score, scherzo.staff_height);
266 score_add_brace (scherzo.score, 2);
267 scherzo.treble = score_add_staff (scherzo.score, SCORE_CLEF_G);
268 scherzo.bass = score_add_staff (scherzo.score, SCORE_CLEF_F);
270 err = snd_midi_event_new (MIDI_BUF_SIZE, &scherzo.snd_midi_event);
272 fprintf (stderr, "Out of memory.\n");
276 #define MIDI_DEVICE "/dev/midi1"
277 scherzo.midi_fd = open (MIDI_DEVICE, O_RDONLY);
278 if (scherzo.midi_fd < 0) {
279 printf ("failed to open " MIDI_DEVICE ". Midi input will not be available.\n");
283 channel = g_io_channel_unix_new (scherzo.midi_fd);
284 g_io_channel_set_encoding (channel, NULL, NULL);
285 g_io_add_watch (channel, G_IO_IN, on_midi_input, &scherzo);
288 scherzo.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
290 gtk_window_set_default_size (GTK_WINDOW (scherzo.window), 600, 400);
292 g_signal_connect (scherzo.window, "delete-event",
293 G_CALLBACK (on_delete_event_quit), NULL);
295 drawing_area = gtk_drawing_area_new ();
297 gtk_container_add (GTK_CONTAINER (scherzo.window), drawing_area);
299 g_signal_connect (drawing_area, "expose-event",
300 G_CALLBACK (on_expose_event_draw),
303 g_signal_connect (scherzo.window, "key-press-event",
304 G_CALLBACK (on_key_press_event),
307 gtk_widget_show_all (scherzo.window);
311 snd_midi_event_free (scherzo.snd_midi_event);
312 talloc_free (scherzo.score);