]> git.cworth.org Git - scherzo/blob - scherzo.c
e6a67ec1ce7bbc4e83046d9e94f2d10e7c5d1bcd
[scherzo] / scherzo.c
1 /* scherzo - Music notation training
2  *
3  * Copyright © 2010 Carl Worth
4  *
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.
9  *
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.
14  *
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/ .
17  */
18
19 #include <gtk/gtk.h>
20 #include <gdk/gdkkeysyms.h>
21
22 #include <asoundlib.h>
23
24 #include "score.h"
25
26 #define unused(foo) foo __attribute__((unused))
27
28 #define MIDI_BUF_SIZE 4096
29
30 typedef struct scherzo
31 {
32     GtkWidget *window;
33     score_t *score;
34     int staff_height;
35     score_staff_t *treble;
36     score_staff_t *bass;
37     int midi_fd;
38     snd_midi_event_t *snd_midi_event;
39 } scherzo_t;
40
41 static int
42 on_delete_event_quit (unused (GtkWidget *widget),
43                       unused (GdkEvent *event),
44                       unused (gpointer user_data))
45 {
46     gtk_main_quit ();
47
48     /* Returning FALSE allows the default handler for delete-event
49      * to proceed to cleanup the widget. */
50     return FALSE;
51 }
52
53 static int
54 on_expose_event_draw (GtkWidget *widget,
55                       unused (GdkEventExpose *expose),
56                       void * user_data)
57 {
58     scherzo_t *scherzo = user_data;
59     score_t *score = scherzo->score;
60     cairo_t *cr;
61     GtkAllocation allocation;
62     static const int pad = 10;
63     int widget_width;
64
65     gtk_widget_get_allocation (widget, &allocation);
66     widget_width = allocation.width;
67
68     cr = gdk_cairo_create (widget->window);
69
70     /* White background */
71     cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
72     cairo_paint (cr);
73
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);
78
79     score_draw (score, cr);
80  
81     return TRUE;
82 }
83
84 static int
85 on_key_press_event (GtkWidget *widget,
86                     GdkEventKey *key,
87                     void *user_data)
88 {
89     scherzo_t *scherzo = user_data;
90
91     switch (key->keyval) {
92     case GDK_KEY_plus:
93     case GDK_KEY_KP_Add:
94     case GDK_KEY_equal:
95     case GDK_KEY_KP_Equal:
96         scherzo->staff_height += 4;
97         gtk_widget_queue_draw (widget);
98         return TRUE;
99         break;
100     case GDK_KEY_minus:
101     case GDK_KEY_KP_Subtract:
102         scherzo->staff_height -= 4;
103         gtk_widget_queue_draw (widget);
104         return TRUE;
105         break;
106     case GDK_KEY_q:
107     case GDK_KEY_Q:
108     case GDK_KEY_Escape:
109         gtk_main_quit ();
110         return FALSE;
111     }
112
113     /* Allow the event to propagate to other handlers. */
114     return FALSE;
115 }
116
117 static void
118 _midi_to_score_pitch_and_octave (unsigned char midi_note,
119                                  score_pitch_t *pitch,
120                                  int *octave)
121 {
122     *octave = midi_note / 12 - 1;
123
124     switch (midi_note % 12)
125     {
126     case 0:
127         *pitch = SCORE_PITCH_C;
128         break;
129     case 1:
130         *pitch = SCORE_PITCH_Cs;
131         break;
132     case 2:
133         *pitch = SCORE_PITCH_D;
134         break;
135     case 3:
136         *pitch = SCORE_PITCH_Ds;
137         break;
138     case 4:
139         *pitch = SCORE_PITCH_E;
140         break;
141     case 5:
142         *pitch = SCORE_PITCH_F;
143         break;
144     case 6:
145         *pitch = SCORE_PITCH_Fs;
146         break;
147     case 7:
148         *pitch = SCORE_PITCH_G;
149         break;
150     case 8:
151         *pitch = SCORE_PITCH_Gs;
152         break;
153     case 9:
154         *pitch = SCORE_PITCH_A;
155         break;
156     case 10:
157         *pitch = SCORE_PITCH_As;
158         break;
159     case 11:
160         *pitch = SCORE_PITCH_B;
161         break;
162     }
163 }
164
165 static void
166 scherzo_add_note_midi (scherzo_t *scherzo, unsigned char midi_note)
167 {
168     score_staff_t *staff;
169     score_pitch_t pitch;
170     int octave;
171
172     /* Anything at Middle C and above goes on the treble staff by default. */
173     if (midi_note >= 60)
174         staff = scherzo->treble;
175     else
176         staff = scherzo->bass;
177
178     _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave);
179
180     score_staff_add_note (staff, pitch, octave, SCORE_DURATION_WHOLE);
181 }
182
183 static void
184 scherzo_remove_note_midi (scherzo_t *scherzo, unsigned char midi_note)
185 {
186     score_staff_t *staff;
187     score_pitch_t pitch;
188     int octave;
189     score_note_t *note;
190
191     /* Anything at Middle C and above goes on the treble staff by default. */
192     if (midi_note >= 60)
193         staff = scherzo->treble;
194     else
195         staff = scherzo->bass;
196
197     _midi_to_score_pitch_and_octave (midi_note, &pitch, &octave);
198
199     note = score_staff_find_note (staff, pitch, octave, SCORE_DURATION_WHOLE);
200     score_staff_remove_note (staff, note);
201 }
202
203 static int
204 on_midi_input (unused (GIOChannel *channel),
205                unused (GIOCondition condition),
206                void *user_data)
207 {
208     unsigned char buf[MIDI_BUF_SIZE], *next;
209     scherzo_t *scherzo = user_data;
210     ssize_t remaining;
211     snd_seq_event_t event;
212
213     remaining = read (scherzo->midi_fd, buf, MIDI_BUF_SIZE);
214
215     next = buf;
216     while (remaining) {
217         long consumed;
218
219         consumed = snd_midi_event_encode (scherzo->snd_midi_event,
220                                           next, remaining, &event);
221
222         remaining -= consumed;
223
224         switch (event.type) {
225         case SND_SEQ_EVENT_NONE:
226             /* Incomplete event. Nothing to do. */
227             break;
228         case SND_SEQ_EVENT_NOTEON:
229             scherzo_add_note_midi (scherzo, event.data.note.note);
230             gtk_widget_queue_draw (scherzo->window);
231             break;
232         case SND_SEQ_EVENT_NOTEOFF:
233             scherzo_remove_note_midi (scherzo, event.data.note.note);
234             gtk_widget_queue_draw (scherzo->window);
235             break;
236         case SND_SEQ_EVENT_CLOCK:
237             /* Ignore for now as my piano sends a constant stream of these. */
238             break;
239         case SND_SEQ_EVENT_SENSING:
240             /* Ignore for now as my piano sends a constant stream of these. */
241             break;
242         default:
243             fprintf (stderr, "Fixme: Do not yet know how to handle MIDI event %d\n",
244                      event.type);
245             break;
246         }
247     }
248     
249     /* Return TRUE to continue to get called in the future. */
250     return TRUE;
251 }
252
253 int
254 main (int argc, char *argv[])
255 {
256     GtkWidget *drawing_area;
257     scherzo_t scherzo;
258     int err;
259
260     gtk_init (&argc, &argv);
261
262     scherzo.score = score_create (NULL);
263     scherzo.staff_height = 48;
264     score_set_staff_height (scherzo.score, scherzo.staff_height);
265
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);
269
270     err = snd_midi_event_new (MIDI_BUF_SIZE, &scherzo.snd_midi_event);
271     if (err) {
272         fprintf (stderr, "Out of memory.\n");
273         return 1;
274     }
275
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");
280     } else {
281         GIOChannel *channel;
282
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);
286     }
287
288     scherzo.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
289
290     gtk_window_set_default_size (GTK_WINDOW (scherzo.window), 600, 400);
291
292     g_signal_connect (scherzo.window, "delete-event",
293                       G_CALLBACK (on_delete_event_quit), NULL);
294
295     drawing_area = gtk_drawing_area_new ();
296
297     gtk_container_add (GTK_CONTAINER (scherzo.window), drawing_area);
298
299     g_signal_connect (drawing_area, "expose-event",  
300                       G_CALLBACK (on_expose_event_draw),
301                       &scherzo);
302
303     g_signal_connect (scherzo.window, "key-press-event",  
304                       G_CALLBACK (on_key_press_event),
305                       &scherzo);
306     
307     gtk_widget_show_all (scherzo.window);
308     
309     gtk_main ();
310
311     snd_midi_event_free (scherzo.snd_midi_event);
312     talloc_free (scherzo.score);
313
314     return 0;
315 }