]> git.cworth.org Git - scherzo/blob - score.c
Fix bugs in mismatched spelling of chord name and notes
[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 <pango/pangocairo.h>
22
23 #include <string.h>
24 #include <math.h>
25
26 #include "score.h"
27
28 typedef struct score_note
29 {
30     score_staff_t *staff;
31     pitch_t pitch;
32     score_duration_t duration;
33
34     struct {
35         double r;
36         double g;
37         double b;
38     } color;
39 } score_note_t;
40
41 struct score_staff
42 {
43     score_clef_t clef;
44
45     score_chord_t **chords;
46     int num_chords;
47
48     score_note_t **notes;
49     int num_notes;
50
51     /* How many ledger lines are needed for current notes */
52     int upper_ledger_lines;
53     int lower_ledger_lines;
54
55     /* Y position of top full line of staff */
56     int y_pos;
57 };
58
59 typedef struct score_brace
60 {
61     int first_staff;
62     int num_staves;
63 } score_brace_t;
64
65 struct score
66 {
67     /* Nominal height of a single staff (ledger lines may make it larger) */
68     int staff_height;
69
70     /* Height of one space within a staff */
71     int space_height;
72
73     /* Minimal line width for staff lines */
74     int line_width;
75
76     /* Full width of staff */
77     int width;
78
79     score_brace_t **braces;
80     int num_braces;
81     int brace_width;
82
83     score_staff_t **staves;
84     int num_staves;
85 };
86
87 score_t *
88 score_create (void *ctx)
89 {
90     score_t *score;
91
92     score = talloc (ctx, score_t);
93     if (score == NULL)
94         return NULL;
95
96     /* Also sets space_height and line_width */
97     score_set_staff_height (score, 76);
98
99     /* Just to have some nominal width. */
100     score->width = 1000;
101
102     score->braces = NULL;
103     score->num_braces = 0;
104
105     score->staves = NULL;
106     score->num_staves = 0;
107
108     return score;
109 }
110
111 int
112 score_set_staff_height (score_t *score, int height)
113 {
114     score->space_height = (int) height / 4;
115     score->staff_height = score->space_height * 4;
116
117     score->line_width = score->space_height / 10;
118     if (score->line_width == 0)
119         score->line_width = 1;
120
121     return score->staff_height;
122 }
123
124 void
125 score_set_width (score_t *score, int width)
126 {
127     score->width = width;
128 }
129
130 /* Returns in brace_width the width of the brace */
131 static void
132 _draw_brace (score_t *score, cairo_t *cr,
133              score_brace_t *brace, int *brace_width)
134 {
135     cairo_glyph_t brace_glyph;
136     cairo_text_extents_t brace_extents;
137     double top, bottom;
138
139     if (brace->num_staves == 0)
140         return;
141
142     cairo_save (cr);
143
144     top = score->staves[brace->first_staff]->y_pos;
145     bottom = score->staves[brace->first_staff + brace->num_staves - 1]->y_pos + score->staff_height;
146
147     cairo_select_font_face (cr, "Gonville-Brace", 0, 0);
148
149     /* XXX: This hard-coded glyph index is pretty ugly. We should
150      * figure out how to lookup the glyph we want, (though, as it
151      * turns out, this brace font pretty much just has numbered glyph
152      * names for different sizes, so it wouldn't be all that different
153      * than just the bare index here). */
154     brace_glyph.index = 300;
155     brace_glyph.x = 0;
156     brace_glyph.y = top + (bottom - top) / 2.0 + score->line_width / 2.0;
157
158     /* XXX: This font size (in conjunction with the glyph selection)
159      * is a rough guess at best. We should figure out how the brace
160      * font is intended to be used and actually measure to find the
161      * correctly-sized glyph. */
162     cairo_set_font_size (cr, (bottom - top) / 3.85);
163
164     cairo_glyph_extents (cr, &brace_glyph, 1, &brace_extents);
165
166     /* Subtract space for brace itself */
167     cairo_translate (cr, -brace_extents.x_bearing, 0);
168
169     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
170     cairo_show_glyphs (cr, &brace_glyph, 1);
171
172     cairo_restore (cr);
173
174     *brace_width = (int) -brace_extents.x_bearing;
175 }
176
177 /* Line containing middle C for the given clef. */
178 static int
179 _score_clef_c_line (score_clef_t clef)
180 {
181     switch (clef)
182     {
183     default:
184     case SCORE_CLEF_G:
185         return 5;
186     case SCORE_CLEF_F:
187         return -1;
188     }
189 }
190
191 /* On which line would 'pitch' appear on 'staff'.
192  *
193  * Lines are numbered with line 0 as the top full line of the staff
194  * and increasing downward. So line values less than 0 will appear as
195  * ledger lines above the staff while line values greater than 4 will
196  * appear on ledger lines below the staff.
197  *
198  * A line value of 2 will be centered verticall on the staff.
199  *
200  * For notes appearing on a space, the line value will be half-way
201  * between two integers. */
202 static double
203 _score_staff_pitch_to_line (score_staff_t *staff, pitch_t pitch)
204 {
205     pitch_name_t name = PITCH_NAME (pitch);
206     int octave = PITCH_OCTAVE (pitch);
207     int c_line = _score_clef_c_line (staff->clef);
208
209     return c_line - (name - PITCH_NAME_C) / 2.0 - 3.5 * (octave - 4);
210 }
211
212 /* chord->width is updated as a side effect */
213 static void
214 _draw_chord (score_t *score, cairo_t *cr,
215              score_staff_t *staff, score_chord_t *chord)
216 {
217     PangoRectangle ink_extents;
218     PangoRectangle logical_extents;
219     double total_staff_height;
220     PangoLayout *layout;
221     PangoFontDescription *font_description;
222
223     /* XXX: The staff should manage this height itself. */
224     total_staff_height = (staff->upper_ledger_lines * score->space_height +
225                           score->staff_height +
226                           staff->lower_ledger_lines * score->space_height);
227
228     cairo_save (cr);
229
230     font_description = pango_font_description_new ();
231     pango_font_description_set_family (font_description, "serif");
232     pango_font_description_set_absolute_size (font_description,
233                         score->space_height * 3 * PANGO_SCALE);
234
235     layout = pango_cairo_create_layout (cr);
236     pango_layout_set_font_description (layout, font_description);
237     pango_layout_set_markup (layout, chord->name, -1);
238
239     pango_layout_line_get_pixel_extents (pango_layout_get_line (layout, 0),
240                                          &ink_extents, &logical_extents);
241
242     if (staff->clef == SCORE_CLEF_G)
243         cairo_move_to (cr, 0, - score->space_height * 0.5);
244     else
245         cairo_move_to (cr, 0, score->space_height * 0.5 + total_staff_height +
246                        logical_extents.height);
247
248     pango_cairo_show_layout_line (cr, pango_layout_get_line (layout, 0));
249
250     g_object_unref (layout);
251     pango_font_description_free (font_description);
252
253     chord->width = logical_extents.width;
254
255     cairo_restore (cr);
256 }
257
258 static void
259 _draw_note (score_t *score, cairo_t *cr,
260             score_staff_t *staff, score_note_t *note)
261 {
262     double line;
263     cairo_glyph_t note_glyph[2];
264     static double extend_factor = 0.25;
265     cairo_text_extents_t extents;
266     int num_glyphs = 0;
267
268     void _draw_ledger_line (double line, double offset, double width) {
269         cairo_move_to (cr, offset - extend_factor * width / 2.0,
270                        score->space_height * line + score->line_width / 2.0);
271         cairo_rel_line_to (cr, (1 + extend_factor) * width, 0);
272         cairo_stroke (cr);
273     }
274
275     cairo_save (cr);
276
277     /* Move right so that X==0 is natural position for non-displaced
278      * noteheads.
279      */
280     cairo_translate (cr, score->space_height, 0);
281
282     /* Which line should the note appear on? Line 0 is the top line of
283      * the staff and increasing downwards. (Negative values indicate a
284      * note on a ledger line above the staff). Values half way between
285      * integers indicate notes appearing on a space between two staff
286      * lines (or ledger lines). */
287     line = _score_staff_pitch_to_line (staff, note->pitch);
288
289     cairo_select_font_face (cr, "Gonville-26", 0, 0);
290     cairo_set_font_size (cr, score->staff_height);
291
292     /* XXX: The hard-coded glyph indices here are very ugly. We should
293      * figure out how to lookup glyphs by name from this font. */
294     switch (PITCH_ACCIDENTAL (note->pitch)) {
295     case PITCH_ACCIDENTAL_DOUBLE_FLAT:
296             note_glyph[num_glyphs].index = 77;
297             break;
298     case PITCH_ACCIDENTAL_FLAT:
299             note_glyph[num_glyphs].index = 68;
300             break;
301     case PITCH_ACCIDENTAL_NATURAL:
302             note_glyph[num_glyphs].index = 101;
303             break;
304     case PITCH_ACCIDENTAL_SHARP:
305             note_glyph[num_glyphs].index = 134;
306             break;
307     case PITCH_ACCIDENTAL_DOUBLE_SHARP:
308             note_glyph[num_glyphs].index = 142;
309             break;
310     }
311
312     if (PITCH_ACCIDENTAL (note->pitch) != PITCH_ACCIDENTAL_NATURAL)
313     {
314             note_glyph[num_glyphs].x = 0;
315
316             note_glyph[num_glyphs].y = score->space_height * line;
317
318             num_glyphs++;
319
320             cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
321
322 #define ACCIDENTAL_NOTE_SPACING (score->space_height * .15)
323
324             note_glyph[0].x = - (extents.width + ACCIDENTAL_NOTE_SPACING);
325     }
326
327     switch (note->duration) {
328     case SCORE_DURATION_1:
329         note_glyph[num_glyphs].index = 127;
330         break;
331     case SCORE_DURATION_2:
332         note_glyph[num_glyphs].index = 85;
333         break;
334     case SCORE_DURATION_4:
335     case SCORE_DURATION_8:
336     case SCORE_DURATION_16:
337     case SCORE_DURATION_32:
338     case SCORE_DURATION_64:
339     case SCORE_DURATION_128:
340     default:
341         note_glyph[num_glyphs].index = 84;
342     }
343
344     note_glyph[num_glyphs].x = 0;
345     note_glyph[num_glyphs].y = score->space_height * line;
346
347     num_glyphs++;
348
349     if (line < 0 || line > 4) {
350         double offset, width;
351         int i;
352
353         cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
354         offset = note_glyph[0].x + extents.x_bearing;
355         width = extents.width;
356
357         if (line < 0) {
358             for (i = -1; i >= line; i--)
359                 _draw_ledger_line (i, offset, width);
360         } else {
361             for (i = 5; i <= line; i++)
362                 _draw_ledger_line (i, offset, width);
363         }
364     }
365
366     cairo_set_source_rgb (cr,
367                           note->color.r,
368                           note->color.g,
369                           note->color.b);
370     cairo_show_glyphs (cr, note_glyph, num_glyphs);
371
372     cairo_restore (cr);
373 }
374
375 static void
376 _draw_staff (score_t *score, cairo_t *cr,
377              score_staff_t *staff, int staff_width)
378 {
379     int i;
380     cairo_glyph_t clef_glyph;
381
382     cairo_save (cr);
383
384     cairo_translate (cr, 0, staff->y_pos);
385
386     cairo_select_font_face (cr, "Gonville-26", 0, 0);
387
388     cairo_set_font_size (cr, score->staff_height);
389
390     /* XXX: The hard-coded glyph indices here are very ugly. We should
391      * figure out how to lookup glyphs by name from this font. */
392     switch (staff->clef) {
393     case SCORE_CLEF_G:
394     default:
395         clef_glyph.index = 46;
396         clef_glyph.y = 3 * score->space_height;
397         break;
398     case SCORE_CLEF_F:
399         clef_glyph.index = 45;
400         clef_glyph.y = 1 * score->space_height;
401         break;
402     }
403     clef_glyph.x = 3 * score->line_width;
404     clef_glyph.y += score->line_width / 2.0;
405
406     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
407     cairo_show_glyphs (cr, &clef_glyph, 1);
408
409     /* Draw staff lines */
410     for (i = 0; i < 5; i++) {
411         cairo_move_to (cr, 0, i * score->space_height + score->line_width / 2.0);
412         cairo_rel_line_to (cr, staff_width, 0);
413     }
414
415     cairo_set_line_width (cr, score->line_width);
416
417     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
418     cairo_stroke (cr);
419
420     /* Make space for clef before drawing notes */
421     cairo_translate (cr, (int) (4 * score->space_height), 0);
422
423     /* Draw chord symbols */
424     cairo_save (cr);
425     {
426         for (i = 0; i < staff->num_chords; i++) {
427             _draw_chord (score, cr, staff, staff->chords[i]);
428             cairo_translate (cr, staff->chords[i]->width, 0.0);
429         }
430     }
431     cairo_restore (cr);
432
433     /* Draw notes */
434     for (i = 0; i < staff->num_notes; i++) {
435         _draw_note (score, cr, staff, staff->notes[i]);
436         /* Draw all notes concurrent for now (as a chord)
437         cairo_translate (cr, score->space_height * 2.0, 0);
438         */
439     }
440
441     cairo_restore (cr);
442 }
443
444 void
445 score_draw (score_t *score, cairo_t *cr)
446 {
447     int i;
448     int staff_width = score->width;
449     int staff_y_pos;
450
451     if (score->num_staves == 0)
452         return;
453
454     cairo_save (cr);
455
456     /* Before drawing anything, position each staff based on the size
457      * of each (including ledger lines) */
458     staff_y_pos = 0;
459     for (i = 0; i < score->num_staves; i++) {
460         score_staff_t *staff = score->staves[i];
461         staff_y_pos += staff->upper_ledger_lines * score->space_height;
462         staff->y_pos = staff_y_pos;
463         staff_y_pos += (score->staff_height +
464                         staff->lower_ledger_lines * score->space_height +
465                         score->staff_height);
466     }
467
468     if (score->num_braces)
469     {
470         /* Initialize to keep the compiler quiet. */
471         int brace_width = 0;
472
473         for (i = 0; i < score->num_braces; i++)
474             _draw_brace (score, cr, score->braces[i], &brace_width);
475
476         /* Subtract space for brace itself */
477         cairo_translate (cr, brace_width, 0);
478         staff_width -= brace_width;
479
480         /* As well as some padding */
481         cairo_translate (cr, 2, 0);
482         staff_width -= 2;
483     }
484
485     /* Vertical lines at each end */
486     cairo_rectangle (cr,
487                      score->line_width / 2.0,
488                      score->staves[0]->y_pos + score->line_width / 2.0,
489                      staff_width - score->line_width,
490                      score->staves[score->num_staves-1]->y_pos + score->staff_height - score->staves[0]->y_pos);
491     cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
492     cairo_set_line_width (cr, score->line_width);
493     cairo_stroke (cr);
494
495     for (i = 0; i < score->num_staves; i++) {
496         score_staff_t *staff = score->staves[i];
497         _draw_staff (score, cr, staff, staff_width);
498     }
499
500     cairo_restore (cr);
501 }
502
503 void
504 score_add_brace (score_t *score, int staves)
505 {
506     score_brace_t *brace;
507
508     brace = talloc (score, score_brace_t);
509     if (brace == NULL)
510         return;
511
512     brace->first_staff = score->num_staves;
513     brace->num_staves = staves;
514
515     score->num_braces++;
516     score->braces = talloc_realloc (score,
517                                     score->braces,
518                                     score_brace_t*,
519                                     score->num_braces);
520     if (score->braces == NULL) {
521         score->num_braces = 0;
522         return;
523     }
524
525     score->braces[score->num_braces - 1] = brace;
526
527 }
528
529 score_staff_t *
530 score_add_staff (score_t *score, score_clef_t clef)
531 {
532     score_staff_t *staff;
533
534     staff = talloc (score, score_staff_t);
535     if (staff == NULL)
536         return NULL;
537
538     staff->clef = clef;
539
540     staff->notes = NULL;
541     staff->num_notes = 0;
542
543     staff->chords = NULL;
544     staff->num_chords = 0;
545
546     staff->upper_ledger_lines = 0;
547     staff->lower_ledger_lines = 0;
548
549     score->num_staves++;
550     score->staves = talloc_realloc (score,
551                                     score->staves,
552                                     score_staff_t*,
553                                     score->num_staves);
554     if (score->staves == NULL) {
555         score->num_staves = 0;
556         return NULL;
557     }
558
559     score->staves[score->num_staves - 1] = staff;
560
561     return staff;
562 }
563
564 score_chord_t *
565 score_add_chord (score_staff_t *staff,
566                  const char *name)
567 {
568     score_chord_t *chord;
569
570     chord = talloc (staff, score_chord_t);
571     if (chord == NULL)
572         return NULL;
573
574     talloc_steal (chord, name);
575
576     chord->staff = staff;
577     chord->name = talloc_strdup (chord, name);
578
579     /* The width will get set correctly the first time _draw_chord is
580      * called. */
581     chord->width = 0.0;
582
583     staff->num_chords++;
584     staff->chords = talloc_realloc (staff,
585                                     staff->chords,
586                                     score_chord_t*,
587                                     staff->num_chords);
588     if (staff->chords == NULL) {
589         staff->num_chords = 0;
590         return NULL;
591     }
592
593     staff->chords[staff->num_chords - 1] = chord;
594
595     return chord;
596 }
597
598 void
599 score_staff_remove_chords (score_staff_t *staff)
600 {
601     talloc_free (staff->chords);
602     staff->chords = NULL;
603
604     staff->num_chords = 0;
605 }
606
607 void
608 score_remove_chords (score_t *score)
609 {
610     int i;
611
612     for (i = 0; i < score->num_staves; i++)
613         score_staff_remove_chords (score->staves[i]);
614 }
615
616 void
617 score_staff_add_note (score_staff_t *staff,
618                       pitch_t pitch,
619                       score_duration_t duration)
620 {
621     score_note_t *note;
622     double line;
623     int i;
624
625     /* Return existing note if already present. */
626     for (i = 0; i < staff->num_notes; i++) {
627         note = staff->notes[i];
628         if (note->pitch == pitch &&
629             note->duration == duration)
630         {
631             return;
632         }
633     }
634
635     note = talloc (staff, score_note_t);
636     if (note == NULL)
637         return;
638
639     note->staff = staff;
640     note->pitch = pitch;
641     note->duration = duration;
642
643     note->color.r = 0.0;
644     note->color.g = 0.0;
645     note->color.b = 0.0;
646
647     line = _score_staff_pitch_to_line (staff, note->pitch);
648     if (line < 0) {
649         int lines = (int) (- line);
650         if (lines > staff->upper_ledger_lines)
651             staff->upper_ledger_lines = lines;
652     } else {
653         int lines = (int) (line - 4);
654         if (lines > staff->lower_ledger_lines)
655             staff->lower_ledger_lines = lines;
656     }
657
658     staff->num_notes++;
659     staff->notes = talloc_realloc (staff,
660                                    staff->notes,
661                                    score_note_t*,
662                                    staff->num_notes);
663     if (staff->notes == NULL) {
664         staff->num_notes = 0;
665         return;
666     }
667
668     staff->notes[staff->num_notes - 1] = note;
669 }
670
671 void
672 score_add_note (score_t *score, pitch_t pitch, score_duration_t duration)
673 {
674     score_staff_t *staff, *nearest_staff = NULL;
675     double distance, nearest_distance = 0.0;
676     int i;
677
678     /* Nothing to do if we have no staff, (there's no place to add a note) . */
679     if (score->num_staves == 0)
680         return;
681
682     /* Find the staff where the note will be closest to the center of
683      * the staff. */
684     for (i = 0; i < score->num_staves; i++) {
685         staff = score->staves[i];
686         distance = fabs (_score_staff_pitch_to_line (staff, pitch) - 2.0);
687         if (nearest_staff == NULL || distance < nearest_distance) {
688             nearest_staff = staff;
689             nearest_distance = distance;
690         }
691     }
692
693     score_staff_add_note (nearest_staff, pitch, duration);
694 }
695
696 void
697 score_staff_remove_notes (score_staff_t *staff)
698 {
699     talloc_free (staff->notes);
700     staff->notes = NULL;
701     staff->num_notes = 0;
702
703     staff->upper_ledger_lines = 0;
704     staff->lower_ledger_lines = 0;
705 }
706
707 void
708 score_remove_notes (score_t *score)
709 {
710     int i;
711
712     for (i = 0; i < score->num_staves; i++)
713         score_staff_remove_notes (score->staves[i]);
714 }