]> git.cworth.org Git - scherzo/blob - score.c
Draw ledger lines.
[scherzo] / score.c
1 /* scherzo - Music notation training
2  *
3  *      score - Utilities for drawing (simple) musical scores
4  *
5  * Copyright © 2010 Carl Worth
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see http://www.gnu.org/licenses/ .
19  */
20
21 #include <string.h>
22
23 #include "score.h"
24
25 struct score_staff
26 {
27     score_clef_t clef;
28
29     score_note_t **notes;
30     int num_notes;
31 };
32
33 typedef struct score_brace
34 {
35     int first_staff;
36     int num_staves;
37 } score_brace_t;
38
39 struct score
40 {
41     /* Height of a single staff */
42     int staff_height;
43
44     /* Height of one space within a staff */
45     int space_height;
46
47     /* Minimal line width for staff lines */
48     int line_width;
49
50     /* Full width of staff */
51     int width;
52
53     score_brace_t **braces;
54     int num_braces;
55     int brace_width;
56
57     score_staff_t **staves;
58     int num_staves;
59 };
60
61 score_t *
62 score_create (void *ctx)
63 {
64     score_t *score;
65
66     score = talloc (ctx, score_t);
67     if (score == NULL)
68         return NULL;
69
70     /* Also sets space_height and line_width */
71     score_set_staff_height (score, 24);
72
73     /* Just to have some nominal width. */
74     score->width = 800;
75
76     score->braces = NULL;
77     score->num_braces = 0;
78
79     score->staves = NULL;
80     score->num_staves = 0;
81
82     return score;
83 }
84
85 int
86 score_set_staff_height (score_t *score, int height)
87 {
88     score->space_height = (int) height / 4;
89     score->staff_height = score->space_height * 4;
90
91     score->line_width = score->space_height / 10;
92     if (score->line_width == 0)
93         score->line_width = 1;
94
95     return score->staff_height;
96 }
97
98 void
99 score_set_width (score_t *score, int width)
100 {
101     score->width = width;
102 }
103
104 /* Returns in brace_width the width of the brace */
105 static void
106 _draw_brace (score_t *score, cairo_t *cr,
107              score_brace_t *brace, int *brace_width)
108 {
109     cairo_glyph_t brace_glyph;
110     cairo_text_extents_t brace_extents;
111
112     cairo_save (cr);
113
114     cairo_select_font_face (cr, "Gonville-Brace", 0, 0);
115
116     /* XXX: This hard-coded glyph index is pretty ugly. We should
117      * figure out how to lookup the glyph we want, (though, as it
118      * turns out, this brace font pretty much just has numbered glyph
119      * names for different sizes, so it wouldn't be all that different
120      * than just the bare index here). */
121     brace_glyph.index = 300;
122     brace_glyph.x = 0;
123     brace_glyph.y = score->staff_height * (brace->first_staff + (2 * brace->num_staves - 1) / 2.0) + 1;
124
125     /* XXX: This font size (in conjunction with the glyph selection)
126      * is a rough guess at best. We should figure out how the brace
127      * font is intended to be used and actually measure to find the
128      * correctly-sized glyph. */
129     cairo_set_font_size (cr, (score->staff_height * 3) / 3.85);
130
131     cairo_glyph_extents (cr, &brace_glyph, 1, &brace_extents);
132
133     /* Subtract space for brace itself */
134     cairo_translate (cr, -brace_extents.x_bearing, 0);
135
136     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
137     cairo_show_glyphs (cr, &brace_glyph, 1);
138
139     cairo_restore (cr);
140
141     *brace_width = (int) -brace_extents.x_bearing;
142 }
143
144 /* Line containing middle C for the given clef. */
145 static int
146 _score_clef_c_line (score_clef_t clef)
147 {
148     switch (clef)
149     {
150     default:
151     case SCORE_CLEF_G:
152         return 5;
153     case SCORE_CLEF_F:
154         return -1;
155     }
156 }
157
158 static double
159 _score_note_to_line (score_staff_t *staff, score_note_t *note)
160 {
161     score_pitch_name_t name = SCORE_PITCH_NAME (note->pitch);
162     int c_line = _score_clef_c_line (staff->clef);
163
164     return c_line - (name - SCORE_PITCH_NAME_C) / 2.0 - 3.5 * (note->octave - 4);
165 }
166
167 static void
168 _draw_note (score_t *score, cairo_t *cr,
169             score_staff_t *staff, score_note_t *note)
170 {
171     double line;
172     cairo_glyph_t note_glyph;
173     static double extend_factor = 0.25;
174
175     void _draw_ledger_line (double line, double width) {
176         cairo_move_to (cr,
177                        - width * extend_factor / 2.0,
178                        score->space_height * line + score->line_width / 2.0);
179         cairo_rel_line_to (cr, (1 + extend_factor) * width, 0);
180         cairo_stroke (cr);
181     }
182
183     cairo_save (cr);
184
185     /* Which line should the note appear on? Line 0 is the top line of
186      * the staff and increasing downwards. (Negative values indicate a
187      * note on a ledger line above the staff). Values half way between
188      * integers indicate notes appearing on a space between two staff
189      * lines (or ledger lines). */
190     line = _score_note_to_line (staff, note);
191
192     cairo_select_font_face (cr, "Gonville-26", 0, 0);
193     cairo_set_font_size (cr, score->staff_height);
194
195     /* XXX: The hard-coded glyph indices here are very ugly. We should
196      * figure out how to lookup glyphs by name from this font. */
197     switch (note->duration) {
198     case SCORE_DURATION_1:
199         note_glyph.index = 127;
200         break;
201     case SCORE_DURATION_2:
202         note_glyph.index = 85;
203         break;
204     case SCORE_DURATION_4:
205     case SCORE_DURATION_8:
206     case SCORE_DURATION_16:
207     case SCORE_DURATION_32:
208     case SCORE_DURATION_64:
209     case SCORE_DURATION_128:
210     default:
211         note_glyph.index = 84;
212     }
213
214     note_glyph.x = 0;
215     note_glyph.y = score->space_height * line;
216
217     if (line < 0 || line > 4) {
218         int i;
219         cairo_text_extents_t note_extents;
220
221         cairo_glyph_extents (cr, &note_glyph, 1, &note_extents);
222
223         if (line < 0) {
224             for (i = -1; i >= line; i--)
225                 _draw_ledger_line (i, note_extents.width);
226         } else {
227             for (i = 5; i <= line; i++)
228                 _draw_ledger_line (i, note_extents.width);
229         }
230     }
231
232     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
233     cairo_show_glyphs (cr, &note_glyph, 1);
234
235     cairo_restore (cr);
236 }
237
238 static void
239 _draw_staff (score_t *score, cairo_t *cr,
240              score_staff_t *staff, int staff_width)
241 {
242     int i;
243     cairo_glyph_t clef_glyph;
244
245     cairo_save (cr);
246
247     cairo_select_font_face (cr, "Gonville-26", 0, 0);
248
249     cairo_set_font_size (cr, score->staff_height);
250
251     /* XXX: The hard-coded glyph indices here are very ugly. We should
252      * figure out how to lookup glyphs by name from this font. */
253     switch (staff->clef) {
254     case SCORE_CLEF_G:
255     default:
256         clef_glyph.index = 46;
257         clef_glyph.y = 3 * score->space_height;
258         break;
259     case SCORE_CLEF_F:
260         clef_glyph.index = 45;
261         clef_glyph.y = 1 * score->space_height;
262         break;
263     }
264     clef_glyph.x = 3 * score->line_width;
265     clef_glyph.y += 1;
266
267     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
268     cairo_show_glyphs (cr, &clef_glyph, 1);
269
270     /* Draw staff lines */
271     for (i = 0; i < 5; i++) {
272         cairo_move_to (cr, 0, i * score->space_height + score->line_width / 2.0);
273         cairo_rel_line_to (cr, staff_width, 0);
274     }
275
276     cairo_set_line_width (cr, score->line_width);
277
278     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
279     cairo_stroke (cr);
280
281     /* Make space for clef before drawing notes */
282     cairo_translate (cr, (int) (4 * score->space_height), 0);
283
284     /* Draw notes */
285     for (i = 0; i < staff->num_notes; i++) {
286         _draw_note (score, cr, staff, staff->notes[i]);
287         /* Draw all notes concurrent for now (as a chord)
288         cairo_translate (cr, score->space_height * 2.0, 0);
289         */
290     }
291
292     cairo_restore (cr);
293 }
294
295 void
296 score_draw (score_t *score, cairo_t *cr)
297 {
298     int i;
299     int staff_width = score->width;
300
301     cairo_save (cr);
302
303     if (score->num_braces)
304     {
305         int brace_width;
306
307         for (i = 0; i < score->num_braces; i++)
308             _draw_brace (score, cr, score->braces[i], &brace_width);
309
310         /* Subtract space for brace itself */
311         cairo_translate (cr, brace_width, 0);
312         staff_width -= brace_width;
313
314         /* As well as some padding */
315         cairo_translate (cr, 2, 0);
316         staff_width -= 2;
317     }
318
319     /* Vertical lines at each end */
320     cairo_rectangle (cr,
321                      score->line_width / 2.0,
322                      score->line_width / 2.0,
323                      staff_width - score->line_width,
324                      score->staff_height * 3);
325     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
326     cairo_set_line_width (cr, score->line_width);
327     cairo_stroke (cr);
328
329     for (i = 0; i < score->num_staves; i++) {
330         _draw_staff (score, cr, score->staves[i], staff_width);
331         cairo_translate (cr, 0, 2 * score->staff_height);
332     }
333
334     cairo_restore (cr);
335 }
336
337 void
338 score_add_brace (score_t *score, int staves)
339 {
340     score_brace_t *brace;
341
342     brace = talloc (score, score_brace_t);
343     if (brace == NULL)
344         return;
345
346     brace->first_staff = score->num_staves;
347     brace->num_staves = staves;
348
349     score->num_braces++;
350     score->braces = talloc_realloc (score,
351                                     score->braces,
352                                     score_brace_t*,
353                                     score->num_braces);
354     if (score->braces == NULL) {
355         score->num_braces = 0;
356         return;
357     }
358
359     score->braces[score->num_braces - 1] = brace;
360
361 }
362
363 score_staff_t *
364 score_add_staff (score_t *score, score_clef_t clef)
365 {
366     score_staff_t *staff;
367
368     staff = talloc (score, score_staff_t);
369     if (staff == NULL)
370         return NULL;
371
372     staff->clef = clef;
373
374     staff->notes = NULL;
375     staff->num_notes = 0;
376
377     score->num_staves++;
378     score->staves = talloc_realloc (score,
379                                     score->staves,
380                                     score_staff_t*,
381                                     score->num_staves);
382     if (score->staves == NULL) {
383         score->num_staves = 0;
384         return NULL;
385     }
386
387     score->staves[score->num_staves - 1] = staff;
388
389     return staff;
390 }
391
392 score_note_t *
393 score_add_note (score_staff_t *staff,
394                 score_pitch_t pitch,
395                 int octave,
396                 score_duration_t duration)
397 {
398     score_note_t *note;
399
400     note = talloc (staff, score_note_t);
401     if (note == NULL)
402         return NULL;
403
404     note->staff = staff;
405     note->pitch = pitch;
406     note->octave = octave;
407     note->duration = duration;
408
409     staff->num_notes++;
410     staff->notes = talloc_realloc (staff,
411                                    staff->notes,
412                                    score_note_t*,
413                                    staff->num_notes);
414     if (staff->notes == NULL) {
415         staff->num_notes = 0;
416         return NULL;
417     }
418
419     staff->notes[staff->num_notes - 1] = note;
420
421     return note;
422 }
423
424 void
425 score_remove_note (score_note_t *note)
426 {
427     score_staff_t *staff = note->staff;
428     int i;
429
430     for (i = 0; i < staff->num_notes; i++)
431         if (staff->notes[i] == note)
432             break;
433
434     if (i == staff->num_notes)
435         return;
436
437     if (i < staff->num_notes - 1)
438     {
439         memmove (staff->notes + i,
440                  staff->notes + i + 1, 
441                  (staff->num_notes - 1 - i) * sizeof (score_note_t *));
442     }
443
444     staff->num_notes -= 1;
445 }
446
447 score_note_t *
448 score_staff_find_note (score_staff_t *staff,
449                        score_pitch_t pitch,
450                        int octave,
451                        score_duration_t duration)
452 {
453     int i;
454     score_note_t *note;
455
456     for (i = 0; i < staff->num_notes; i++) {
457         note = staff->notes[i];
458         if (note->pitch == pitch &&
459             note->octave == octave &&
460             note->duration == duration)
461         {
462             return note;
463         }
464     }
465
466     return NULL;
467 }
468