1 /* scherzo - Music notation training
3 * score - Utilities for drawing (simple) musical scores
5 * Copyright © 2010 Carl Worth
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.
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.
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/ .
21 #include <pango/pangocairo.h>
31 score_chord_t **chords;
37 /* How many ledger lines are needed for current notes */
38 int upper_ledger_lines;
39 int lower_ledger_lines;
41 /* Y position of top full line of staff */
45 typedef struct score_brace
53 /* Nominal height of a single staff (ledger lines may make it larger) */
56 /* Height of one space within a staff */
59 /* Minimal line width for staff lines */
62 /* Full width of staff */
65 score_brace_t **braces;
69 score_staff_t **staves;
74 score_create (void *ctx)
78 score = talloc (ctx, score_t);
82 /* Also sets space_height and line_width */
83 score_set_staff_height (score, 76);
85 /* Just to have some nominal width. */
89 score->num_braces = 0;
92 score->num_staves = 0;
98 score_set_staff_height (score_t *score, int height)
100 score->space_height = (int) height / 4;
101 score->staff_height = score->space_height * 4;
103 score->line_width = score->space_height / 10;
104 if (score->line_width == 0)
105 score->line_width = 1;
107 return score->staff_height;
111 score_set_width (score_t *score, int width)
113 score->width = width;
116 /* Returns in brace_width the width of the brace */
118 _draw_brace (score_t *score, cairo_t *cr,
119 score_brace_t *brace, int *brace_width)
121 cairo_glyph_t brace_glyph;
122 cairo_text_extents_t brace_extents;
125 if (brace->num_staves == 0)
130 top = score->staves[brace->first_staff]->y_pos;
131 bottom = score->staves[brace->first_staff + brace->num_staves - 1]->y_pos + score->staff_height;
133 cairo_select_font_face (cr, "Gonville-Brace", 0, 0);
135 /* XXX: This hard-coded glyph index is pretty ugly. We should
136 * figure out how to lookup the glyph we want, (though, as it
137 * turns out, this brace font pretty much just has numbered glyph
138 * names for different sizes, so it wouldn't be all that different
139 * than just the bare index here). */
140 brace_glyph.index = 300;
142 brace_glyph.y = top + (bottom - top) / 2.0 + score->line_width / 2.0;
144 /* XXX: This font size (in conjunction with the glyph selection)
145 * is a rough guess at best. We should figure out how the brace
146 * font is intended to be used and actually measure to find the
147 * correctly-sized glyph. */
148 cairo_set_font_size (cr, (bottom - top) / 3.85);
150 cairo_glyph_extents (cr, &brace_glyph, 1, &brace_extents);
152 /* Subtract space for brace itself */
153 cairo_translate (cr, -brace_extents.x_bearing, 0);
155 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
156 cairo_show_glyphs (cr, &brace_glyph, 1);
160 *brace_width = (int) -brace_extents.x_bearing;
163 /* Line containing middle C for the given clef. */
165 _score_clef_c_line (score_clef_t clef)
178 _score_note_to_line (score_staff_t *staff, score_note_t *note)
180 pitch_name_t name = PITCH_NAME (note->pitch);
181 int octave = PITCH_OCTAVE (note->pitch);
182 int c_line = _score_clef_c_line (staff->clef);
184 return c_line - (name - PITCH_NAME_C) / 2.0 - 3.5 * (octave - 4);
187 /* chord->width is updated as a side effect */
189 _draw_chord (score_t *score, cairo_t *cr,
190 score_staff_t *staff, score_chord_t *chord)
192 PangoRectangle ink_extents;
193 PangoRectangle logical_extents;
194 double total_staff_height;
196 PangoFontDescription *font_description;
198 /* XXX: The staff should manage this height itself. */
199 total_staff_height = (staff->upper_ledger_lines * score->space_height +
200 score->staff_height +
201 staff->lower_ledger_lines * score->space_height);
205 font_description = pango_font_description_new ();
206 pango_font_description_set_family (font_description, "serif");
207 pango_font_description_set_absolute_size (font_description,
208 score->space_height * 3 * PANGO_SCALE);
210 layout = pango_cairo_create_layout (cr);
211 pango_layout_set_font_description (layout, font_description);
212 pango_layout_set_markup (layout, chord->name, -1);
214 pango_layout_line_get_pixel_extents (pango_layout_get_line (layout, 0),
215 &ink_extents, &logical_extents);
217 if (staff->clef == SCORE_CLEF_G)
218 cairo_move_to (cr, 0, - score->space_height * 0.5);
220 cairo_move_to (cr, 0, score->space_height * 0.5 + total_staff_height +
221 logical_extents.height);
223 pango_cairo_show_layout_line (cr, pango_layout_get_line (layout, 0));
225 g_object_unref (layout);
226 pango_font_description_free (font_description);
228 chord->width = logical_extents.width;
234 _draw_note (score_t *score, cairo_t *cr,
235 score_staff_t *staff, score_note_t *note)
238 cairo_glyph_t note_glyph[2];
239 static double extend_factor = 0.25;
240 cairo_text_extents_t extents;
243 void _draw_ledger_line (double line, double offset, double width) {
244 cairo_move_to (cr, offset - extend_factor * width / 2.0,
245 score->space_height * line + score->line_width / 2.0);
246 cairo_rel_line_to (cr, (1 + extend_factor) * width, 0);
252 /* Move right so that X==0 is natural position for non-displaced
255 cairo_translate (cr, score->space_height, 0);
257 /* Which line should the note appear on? Line 0 is the top line of
258 * the staff and increasing downwards. (Negative values indicate a
259 * note on a ledger line above the staff). Values half way between
260 * integers indicate notes appearing on a space between two staff
261 * lines (or ledger lines). */
262 line = _score_note_to_line (staff, note);
264 cairo_select_font_face (cr, "Gonville-26", 0, 0);
265 cairo_set_font_size (cr, score->staff_height);
267 /* XXX: The hard-coded glyph indices here are very ugly. We should
268 * figure out how to lookup glyphs by name from this font. */
269 switch (PITCH_ACCIDENTAL (note->pitch)) {
270 case PITCH_ACCIDENTAL_DOUBLE_FLAT:
271 note_glyph[num_glyphs].index = 77;
273 case PITCH_ACCIDENTAL_FLAT:
274 note_glyph[num_glyphs].index = 68;
276 case PITCH_ACCIDENTAL_NATURAL:
277 note_glyph[num_glyphs].index = 101;
279 case PITCH_ACCIDENTAL_SHARP:
280 note_glyph[num_glyphs].index = 134;
282 case PITCH_ACCIDENTAL_DOUBLE_SHARP:
283 note_glyph[num_glyphs].index = 142;
287 if (PITCH_ACCIDENTAL (note->pitch) != PITCH_ACCIDENTAL_NATURAL)
289 note_glyph[num_glyphs].x = 0;
291 note_glyph[num_glyphs].y = score->space_height * line;
295 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
297 #define ACCIDENTAL_NOTE_SPACING (score->space_height * .15)
299 note_glyph[0].x = - (extents.width + ACCIDENTAL_NOTE_SPACING);
302 switch (note->duration) {
303 case SCORE_DURATION_1:
304 note_glyph[num_glyphs].index = 127;
306 case SCORE_DURATION_2:
307 note_glyph[num_glyphs].index = 85;
309 case SCORE_DURATION_4:
310 case SCORE_DURATION_8:
311 case SCORE_DURATION_16:
312 case SCORE_DURATION_32:
313 case SCORE_DURATION_64:
314 case SCORE_DURATION_128:
316 note_glyph[num_glyphs].index = 84;
319 note_glyph[num_glyphs].x = 0;
320 note_glyph[num_glyphs].y = score->space_height * line;
324 if (line < 0 || line > 4) {
325 double offset, width;
328 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
329 offset = note_glyph[0].x + extents.x_bearing;
330 width = extents.width;
333 for (i = -1; i >= line; i--)
334 _draw_ledger_line (i, offset, width);
336 for (i = 5; i <= line; i++)
337 _draw_ledger_line (i, offset, width);
341 cairo_set_source_rgb (cr,
345 cairo_show_glyphs (cr, note_glyph, num_glyphs);
351 _draw_staff (score_t *score, cairo_t *cr,
352 score_staff_t *staff, int staff_width)
355 cairo_glyph_t clef_glyph;
359 cairo_translate (cr, 0, staff->y_pos);
361 cairo_select_font_face (cr, "Gonville-26", 0, 0);
363 cairo_set_font_size (cr, score->staff_height);
365 /* XXX: The hard-coded glyph indices here are very ugly. We should
366 * figure out how to lookup glyphs by name from this font. */
367 switch (staff->clef) {
370 clef_glyph.index = 46;
371 clef_glyph.y = 3 * score->space_height;
374 clef_glyph.index = 45;
375 clef_glyph.y = 1 * score->space_height;
378 clef_glyph.x = 3 * score->line_width;
379 clef_glyph.y += score->line_width / 2.0;
381 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
382 cairo_show_glyphs (cr, &clef_glyph, 1);
384 /* Draw staff lines */
385 for (i = 0; i < 5; i++) {
386 cairo_move_to (cr, 0, i * score->space_height + score->line_width / 2.0);
387 cairo_rel_line_to (cr, staff_width, 0);
390 cairo_set_line_width (cr, score->line_width);
392 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
395 /* Make space for clef before drawing notes */
396 cairo_translate (cr, (int) (4 * score->space_height), 0);
398 /* Draw chord symbols */
401 for (i = 0; i < staff->num_chords; i++) {
402 _draw_chord (score, cr, staff, staff->chords[i]);
403 cairo_translate (cr, staff->chords[i]->width, 0.0);
409 for (i = 0; i < staff->num_notes; i++) {
410 _draw_note (score, cr, staff, staff->notes[i]);
411 /* Draw all notes concurrent for now (as a chord)
412 cairo_translate (cr, score->space_height * 2.0, 0);
420 score_draw (score_t *score, cairo_t *cr)
423 int staff_width = score->width;
426 if (score->num_staves == 0)
431 /* Before drawing anything, position each staff based on the size
432 * of each (including ledger lines) */
434 for (i = 0; i < score->num_staves; i++) {
435 score_staff_t *staff = score->staves[i];
436 staff_y_pos += staff->upper_ledger_lines * score->space_height;
437 staff->y_pos = staff_y_pos;
438 staff_y_pos += (score->staff_height +
439 staff->lower_ledger_lines * score->space_height +
440 score->staff_height);
443 if (score->num_braces)
445 /* Initialize to keep the compiler quiet. */
448 for (i = 0; i < score->num_braces; i++)
449 _draw_brace (score, cr, score->braces[i], &brace_width);
451 /* Subtract space for brace itself */
452 cairo_translate (cr, brace_width, 0);
453 staff_width -= brace_width;
455 /* As well as some padding */
456 cairo_translate (cr, 2, 0);
460 /* Vertical lines at each end */
462 score->line_width / 2.0,
463 score->staves[0]->y_pos + score->line_width / 2.0,
464 staff_width - score->line_width,
465 score->staves[score->num_staves-1]->y_pos + score->staff_height - score->staves[0]->y_pos);
466 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
467 cairo_set_line_width (cr, score->line_width);
470 for (i = 0; i < score->num_staves; i++) {
471 score_staff_t *staff = score->staves[i];
472 _draw_staff (score, cr, staff, staff_width);
479 score_add_brace (score_t *score, int staves)
481 score_brace_t *brace;
483 brace = talloc (score, score_brace_t);
487 brace->first_staff = score->num_staves;
488 brace->num_staves = staves;
491 score->braces = talloc_realloc (score,
495 if (score->braces == NULL) {
496 score->num_braces = 0;
500 score->braces[score->num_braces - 1] = brace;
505 score_add_staff (score_t *score, score_clef_t clef)
507 score_staff_t *staff;
509 staff = talloc (score, score_staff_t);
516 staff->num_notes = 0;
518 staff->chords = NULL;
519 staff->num_chords = 0;
521 staff->upper_ledger_lines = 0;
522 staff->lower_ledger_lines = 0;
525 score->staves = talloc_realloc (score,
529 if (score->staves == NULL) {
530 score->num_staves = 0;
534 score->staves[score->num_staves - 1] = staff;
540 score_add_chord (score_staff_t *staff,
543 score_chord_t *chord;
545 chord = talloc (staff, score_chord_t);
549 talloc_steal (chord, name);
551 chord->staff = staff;
552 chord->name = talloc_strdup (chord, name);
554 /* The width will get set correctly the first time _draw_chord is
559 staff->chords = talloc_realloc (staff,
563 if (staff->chords == NULL) {
564 staff->num_chords = 0;
568 staff->chords[staff->num_chords - 1] = chord;
574 score_remove_chord (score_chord_t *chord)
576 score_staff_t *staff = chord->staff;
579 for (i = 0; i < staff->num_chords; i++)
580 if (staff->chords[i] == chord)
583 if (i == staff->num_chords)
586 if (i < staff->num_chords - 1)
588 memmove (staff->chords + i,
589 staff->chords + i + 1,
590 (staff->num_chords - 1 - i) * sizeof (score_chord_t *));
593 staff->num_chords -= 1;
597 score_add_note (score_staff_t *staff,
599 score_duration_t duration)
605 /* Return existing note if already present. */
606 for (i = 0; i < staff->num_notes; i++) {
607 note = staff->notes[i];
608 if (note->pitch == pitch &&
609 note->duration == duration)
615 note = talloc (staff, score_note_t);
621 note->duration = duration;
627 line = _score_note_to_line (staff, note);
629 int lines = (int) (- line);
630 if (lines > staff->upper_ledger_lines)
631 staff->upper_ledger_lines = lines;
633 int lines = (int) (line - 4);
634 if (lines > staff->lower_ledger_lines)
635 staff->lower_ledger_lines = lines;
639 staff->notes = talloc_realloc (staff,
643 if (staff->notes == NULL) {
644 staff->num_notes = 0;
648 staff->notes[staff->num_notes - 1] = note;
654 score_remove_note (score_note_t *note)
656 score_staff_t *staff = note->staff;
659 for (i = 0; i < staff->num_notes; i++)
660 if (staff->notes[i] == note)
663 if (i == staff->num_notes)
666 if (i < staff->num_notes - 1)
668 memmove (staff->notes + i,
669 staff->notes + i + 1,
670 (staff->num_notes - 1 - i) * sizeof (score_note_t *));
673 staff->num_notes -= 1;
675 if (staff->num_notes == 0) {
676 staff->upper_ledger_lines = 0;
677 staff->lower_ledger_lines = 0;
682 score_set_note_color_rgb (score_note_t *note,
693 score_staff_find_note (score_staff_t *staff,
695 score_duration_t duration)
700 for (i = 0; i < staff->num_notes; i++) {
701 note = staff->notes[i];
702 if (note->pitch == pitch && note->duration == duration)