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>
28 #define ARRAY_SIZE(arr) ((int) (sizeof(arr) / sizeof(arr[0])))
30 typedef struct score_note
34 score_duration_t duration;
47 score_chord_t **chords;
53 /* How many ledger lines are needed for current notes */
54 int upper_ledger_lines;
55 int lower_ledger_lines;
57 /* Y position of top full line of staff */
61 typedef struct score_brace
69 /* Nominal height of a single staff (ledger lines may make it larger) */
72 /* Height of one space within a staff */
75 /* Minimal line width for staff lines */
78 /* Full width of staff */
81 /* the pitch class of the current (diatonic) key */
84 score_brace_t **braces;
88 score_staff_t **staves;
93 score_create (void *ctx)
97 score = talloc (ctx, score_t);
101 /* Also sets space_height and line_width */
102 score_set_staff_height (score, 76);
104 /* Just to have some nominal width. */
107 /* Default to C, of course */
108 score->key = PITCH_CLASS_LITERAL (C, NATURAL);
110 score->braces = NULL;
111 score->num_braces = 0;
113 score->staves = NULL;
114 score->num_staves = 0;
120 score_set_staff_height (score_t *score, int height)
122 score->space_height = (int) height / 4;
123 score->staff_height = score->space_height * 4;
125 score->line_width = score->space_height / 10;
126 if (score->line_width == 0)
127 score->line_width = 1;
129 return score->staff_height;
133 score_set_width (score_t *score, int width)
135 score->width = width;
139 score_set_key (score_t *score, pitch_t key)
144 /* Returns in brace_width the width of the brace */
146 _draw_brace (score_t *score, cairo_t *cr,
147 score_brace_t *brace, int *brace_width)
149 cairo_glyph_t brace_glyph;
150 cairo_text_extents_t brace_extents;
153 if (brace->num_staves == 0)
158 top = score->staves[brace->first_staff]->y_pos;
159 bottom = score->staves[brace->first_staff + brace->num_staves - 1]->y_pos + score->staff_height;
161 cairo_select_font_face (cr, "Gonville-Brace", 0, 0);
163 /* XXX: This hard-coded glyph index is pretty ugly. We should
164 * figure out how to lookup the glyph we want, (though, as it
165 * turns out, this brace font pretty much just has numbered glyph
166 * names for different sizes, so it wouldn't be all that different
167 * than just the bare index here). */
168 brace_glyph.index = 300;
170 brace_glyph.y = top + (bottom - top) / 2.0 + score->line_width / 2.0;
172 /* XXX: This font size (in conjunction with the glyph selection)
173 * is a rough guess at best. We should figure out how the brace
174 * font is intended to be used and actually measure to find the
175 * correctly-sized glyph. */
176 cairo_set_font_size (cr, (bottom - top) / 3.85);
178 cairo_glyph_extents (cr, &brace_glyph, 1, &brace_extents);
180 /* Subtract space for brace itself */
181 cairo_translate (cr, -brace_extents.x_bearing, 0);
183 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
184 cairo_show_glyphs (cr, &brace_glyph, 1);
188 *brace_width = (int) -brace_extents.x_bearing;
191 /* Line containing middle C for the given clef. */
193 _score_clef_c_line (score_clef_t clef)
205 /* On which line would 'pitch' appear on 'staff'.
207 * Lines are numbered with line 0 as the top full line of the staff
208 * and increasing downward. So line values less than 0 will appear as
209 * ledger lines above the staff while line values greater than 4 will
210 * appear on ledger lines below the staff.
212 * A line value of 2 will be centered verticall on the staff.
214 * For notes appearing on a space, the line value will be half-way
215 * between two integers. */
217 _score_staff_pitch_to_line (score_staff_t *staff, pitch_t pitch)
219 pitch_name_t name = PITCH_NAME (pitch);
220 int octave = PITCH_OCTAVE (pitch);
221 int c_line = _score_clef_c_line (staff->clef);
223 return c_line - (name - PITCH_NAME_C) / 2.0 - 3.5 * (octave - 4);
226 /* chord->width is updated as a side effect */
228 _draw_chord (score_t *score, cairo_t *cr,
229 score_staff_t *staff, score_chord_t *chord)
231 PangoRectangle ink_extents;
232 PangoRectangle logical_extents;
233 double total_staff_height;
235 PangoFontDescription *font_description;
237 /* XXX: The staff should manage this height itself. */
238 total_staff_height = (staff->upper_ledger_lines * score->space_height +
239 score->staff_height +
240 staff->lower_ledger_lines * score->space_height);
244 font_description = pango_font_description_new ();
245 pango_font_description_set_family (font_description, "serif");
246 pango_font_description_set_absolute_size (font_description,
247 score->space_height * 3 * PANGO_SCALE);
249 layout = pango_cairo_create_layout (cr);
250 pango_layout_set_font_description (layout, font_description);
251 pango_layout_set_markup (layout, chord->name, -1);
253 pango_layout_line_get_pixel_extents (pango_layout_get_line (layout, 0),
254 &ink_extents, &logical_extents);
256 if (staff->clef == SCORE_CLEF_G)
257 cairo_move_to (cr, 0, - score->space_height * 0.5);
259 cairo_move_to (cr, 0, score->space_height * 0.5 + total_staff_height +
260 logical_extents.height);
262 pango_cairo_show_layout_line (cr, pango_layout_get_line (layout, 0));
264 g_object_unref (layout);
265 pango_font_description_free (font_description);
267 chord->width = logical_extents.width;
272 /* Draw 'note' with accidental (if not NATURAL) at its correct
273 * position on 'staff'.
275 * Returns the width of the drawn glyphs.
278 _draw_note (score_t *score, cairo_t *cr,
279 score_staff_t *staff, score_note_t *note)
282 cairo_glyph_t note_glyph[2];
283 static double extend_factor = 0.25;
284 cairo_text_extents_t extents;
287 void _draw_ledger_line (double line, double offset, double width) {
288 cairo_move_to (cr, offset - extend_factor * width / 2.0,
289 score->space_height * line + score->line_width / 2.0);
290 cairo_rel_line_to (cr, (1 + extend_factor) * width, 0);
296 /* Move right so that X==0 is natural position for non-displaced
299 cairo_translate (cr, score->space_height, 0);
301 /* Which line should the note appear on? Line 0 is the top line of
302 * the staff and increasing downwards. (Negative values indicate a
303 * note on a ledger line above the staff). Values half way between
304 * integers indicate notes appearing on a space between two staff
305 * lines (or ledger lines). */
306 line = _score_staff_pitch_to_line (staff, note->pitch);
308 cairo_select_font_face (cr, "Gonville-26", 0, 0);
309 cairo_set_font_size (cr, score->staff_height);
311 /* XXX: The hard-coded glyph indices here are very ugly. We should
312 * figure out how to lookup glyphs by name from this font. */
313 switch (PITCH_ACCIDENTAL (note->pitch)) {
314 case PITCH_ACCIDENTAL_DOUBLE_FLAT:
315 note_glyph[num_glyphs].index = 77;
317 case PITCH_ACCIDENTAL_FLAT:
318 note_glyph[num_glyphs].index = 68;
320 case PITCH_ACCIDENTAL_NATURAL:
321 note_glyph[num_glyphs].index = 101;
323 case PITCH_ACCIDENTAL_SHARP:
324 note_glyph[num_glyphs].index = 134;
326 case PITCH_ACCIDENTAL_DOUBLE_SHARP:
327 note_glyph[num_glyphs].index = 142;
331 if (PITCH_ACCIDENTAL (note->pitch) != PITCH_ACCIDENTAL_NATURAL)
333 note_glyph[num_glyphs].x = 0;
335 note_glyph[num_glyphs].y = score->space_height * line;
339 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
341 #define ACCIDENTAL_NOTE_SPACING (score->space_height * .15)
343 note_glyph[0].x = - (extents.width + ACCIDENTAL_NOTE_SPACING);
346 /* Support duration == 0 to draw accidental only */
349 switch (note->duration) {
350 case SCORE_DURATION_1:
351 note_glyph[num_glyphs].index = 127;
353 case SCORE_DURATION_2:
354 note_glyph[num_glyphs].index = 85;
356 case SCORE_DURATION_4:
357 case SCORE_DURATION_8:
358 case SCORE_DURATION_16:
359 case SCORE_DURATION_32:
360 case SCORE_DURATION_64:
361 case SCORE_DURATION_128:
363 note_glyph[num_glyphs].index = 84;
366 note_glyph[num_glyphs].x = 0;
367 note_glyph[num_glyphs].y = score->space_height * line;
372 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
374 if (line < 0 || line > 4) {
375 double offset, width;
378 offset = note_glyph[0].x + extents.x_bearing;
379 width = extents.width;
382 for (i = -1; i >= line; i--)
383 _draw_ledger_line (i, offset, width);
385 for (i = 5; i <= line; i++)
386 _draw_ledger_line (i, offset, width);
390 cairo_set_source_rgb (cr,
394 cairo_show_glyphs (cr, note_glyph, num_glyphs);
398 return extents.width;
401 /* Draw the accidental from 'pitch' only (no notehead) at the correct
402 * position as if drawing a note at 'pitch'.
404 * Returns the width of the drawn glyph.
407 _draw_accidental (score_t *score,
409 score_staff_t *staff,
417 /* A duration of 0 indicates to draw only the accidental. */
424 return _draw_note (score, cr, staff, ¬e);
428 _draw_sharps_key_signature (score_t *score, cairo_t *cr,
429 score_staff_t *staff, int num_sharps)
435 /* These octave numbers are correct for treble clef. For bass
436 * clef, subtract two.
438 pitch_t sharps_order[] = {
439 PITCH_LITERAL (F, SHARP, 5),
440 PITCH_LITERAL (C, SHARP, 5),
441 PITCH_LITERAL (G, SHARP, 5),
442 PITCH_LITERAL (D, SHARP, 5),
443 PITCH_LITERAL (A, SHARP, 4),
444 PITCH_LITERAL (E, SHARP, 5),
445 PITCH_LITERAL (B, SHARP, 4)
448 for (i = 0; i < num_sharps; i++) {
449 pitch = sharps_order[i];
451 if (staff->clef == SCORE_CLEF_BASS)
452 pitch = pitch_lower_by_octaves (pitch, 2);
454 width = _draw_accidental (score, cr, staff, pitch);
456 #define KEY_SIGNATURE_ACCIDENTAL_SPACING (score->space_height * .15)
457 cairo_translate (cr, ceil (width + KEY_SIGNATURE_ACCIDENTAL_SPACING), 0);
462 _draw_flats_key_signature (score_t *score, cairo_t *cr,
463 score_staff_t *staff, int num_flats)
469 /* These octave numbers are correct for treble clef. For bass
470 * clef, subtract two.
472 pitch_name_t flats_order[] = {
473 PITCH_LITERAL (B, FLAT, 4),
474 PITCH_LITERAL (E, FLAT, 5),
475 PITCH_LITERAL (A, FLAT, 4),
476 PITCH_LITERAL (D, FLAT, 5),
477 PITCH_LITERAL (G, FLAT, 4),
478 PITCH_LITERAL (C, FLAT, 5),
479 PITCH_LITERAL (F, FLAT, 4),
482 for (i = 0; i < num_flats; i++) {
483 pitch = flats_order[i];
485 if (staff->clef == SCORE_CLEF_BASS)
486 pitch = pitch_lower_by_octaves (pitch, 2);
488 width = _draw_accidental (score, cr, staff, pitch);
490 cairo_translate (cr, ceil (width + KEY_SIGNATURE_ACCIDENTAL_SPACING), 0);
495 _draw_key_signature (score_t *score, cairo_t *cr,
496 score_staff_t *staff, pitch_t key)
500 pitch_t sharp_keys[] = {
501 PITCH_CLASS_LITERAL (C, NATURAL),
502 PITCH_CLASS_LITERAL (G, NATURAL),
503 PITCH_CLASS_LITERAL (D, NATURAL),
504 PITCH_CLASS_LITERAL (A, NATURAL),
505 PITCH_CLASS_LITERAL (E, NATURAL),
506 PITCH_CLASS_LITERAL (B, NATURAL),
507 PITCH_CLASS_LITERAL (F, SHARP),
508 PITCH_CLASS_LITERAL (C, SHARP),
511 pitch_t flat_keys[] = {
512 PITCH_CLASS_LITERAL (C, NATURAL),
513 PITCH_CLASS_LITERAL (F, NATURAL),
514 PITCH_CLASS_LITERAL (B, FLAT),
515 PITCH_CLASS_LITERAL (E, FLAT),
516 PITCH_CLASS_LITERAL (A, FLAT),
517 PITCH_CLASS_LITERAL (D, FLAT),
518 PITCH_CLASS_LITERAL (G, FLAT),
519 PITCH_CLASS_LITERAL (C, FLAT)
522 for (i = 0; i < ARRAY_SIZE (sharp_keys); i++) {
523 if (sharp_keys[i] == key) {
524 _draw_sharps_key_signature (score, cr, staff, i);
529 for (i = 0; i < ARRAY_SIZE (flat_keys); i++) {
530 if (flat_keys[i] == key) {
531 _draw_flats_key_signature (score, cr, staff, i);
536 fprintf (stderr, "Internal error: Not a key: %s\n", pitch_string (key));
541 _draw_staff (score_t *score, cairo_t *cr,
542 score_staff_t *staff, int staff_width)
545 cairo_glyph_t clef_glyph;
546 cairo_text_extents_t clef_extents;
550 cairo_translate (cr, 0, staff->y_pos);
552 cairo_select_font_face (cr, "Gonville-26", 0, 0);
554 cairo_set_font_size (cr, score->staff_height);
556 /* Draw staff lines */
557 for (i = 0; i < 5; i++) {
558 cairo_move_to (cr, 0, i * score->space_height + score->line_width / 2.0);
559 cairo_rel_line_to (cr, staff_width, 0);
562 cairo_set_line_width (cr, score->line_width);
564 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
569 /* XXX: The hard-coded glyph indices here are very ugly. We should
570 * figure out how to lookup glyphs by name from this font. */
571 switch (staff->clef) {
574 clef_glyph.index = 46;
575 clef_glyph.y = 3 * score->space_height;
578 clef_glyph.index = 45;
579 clef_glyph.y = 1 * score->space_height;
582 clef_glyph.x = 3 * score->line_width;
583 clef_glyph.y += score->line_width / 2.0;
585 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
586 cairo_show_glyphs (cr, &clef_glyph, 1);
588 /* Make space for clef */
589 cairo_glyph_extents (cr, &clef_glyph, 1, &clef_extents);
591 #define CLEF_KEY_SIGNATURE_SPACING (score->space_height * .75)
592 cairo_translate (cr, ceil (clef_extents.width +
593 CLEF_KEY_SIGNATURE_SPACING), 0);
595 /* Draw the key signature */
596 _draw_key_signature (score, cr, staff, score->key);
598 /* Draw chord symbols */
601 for (i = 0; i < staff->num_chords; i++) {
602 _draw_chord (score, cr, staff, staff->chords[i]);
603 cairo_translate (cr, staff->chords[i]->width, 0.0);
609 for (i = 0; i < staff->num_notes; i++) {
610 _draw_note (score, cr, staff, staff->notes[i]);
611 /* Draw all notes concurrent for now (as a chord)
612 cairo_translate (cr, score->space_height * 2.0, 0);
620 score_draw (score_t *score, cairo_t *cr)
623 int staff_width = score->width;
626 if (score->num_staves == 0)
631 /* Before drawing anything, position each staff based on the size
632 * of each (including ledger lines) */
634 for (i = 0; i < score->num_staves; i++) {
635 score_staff_t *staff = score->staves[i];
636 staff_y_pos += staff->upper_ledger_lines * score->space_height;
637 staff->y_pos = staff_y_pos;
638 staff_y_pos += (score->staff_height +
639 staff->lower_ledger_lines * score->space_height +
640 score->staff_height);
643 if (score->num_braces)
645 /* Initialize to keep the compiler quiet. */
648 for (i = 0; i < score->num_braces; i++)
649 _draw_brace (score, cr, score->braces[i], &brace_width);
651 /* Subtract space for brace itself */
652 cairo_translate (cr, brace_width, 0);
653 staff_width -= brace_width;
655 /* As well as some padding */
656 cairo_translate (cr, 2, 0);
660 /* Vertical lines at each end */
662 score->line_width / 2.0,
663 score->staves[0]->y_pos + score->line_width / 2.0,
664 staff_width - score->line_width,
665 score->staves[score->num_staves-1]->y_pos + score->staff_height - score->staves[0]->y_pos);
666 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
667 cairo_set_line_width (cr, score->line_width);
670 for (i = 0; i < score->num_staves; i++) {
671 score_staff_t *staff = score->staves[i];
672 _draw_staff (score, cr, staff, staff_width);
679 score_add_brace (score_t *score, int staves)
681 score_brace_t *brace;
683 brace = talloc (score, score_brace_t);
687 brace->first_staff = score->num_staves;
688 brace->num_staves = staves;
691 score->braces = talloc_realloc (score,
695 if (score->braces == NULL) {
696 score->num_braces = 0;
700 score->braces[score->num_braces - 1] = brace;
705 score_add_staff (score_t *score, score_clef_t clef)
707 score_staff_t *staff;
709 staff = talloc (score, score_staff_t);
716 staff->num_notes = 0;
718 staff->chords = NULL;
719 staff->num_chords = 0;
721 staff->upper_ledger_lines = 0;
722 staff->lower_ledger_lines = 0;
725 score->staves = talloc_realloc (score,
729 if (score->staves == NULL) {
730 score->num_staves = 0;
734 score->staves[score->num_staves - 1] = staff;
740 score_add_chord (score_staff_t *staff,
743 score_chord_t *chord;
745 chord = talloc (staff, score_chord_t);
749 talloc_steal (chord, name);
751 chord->staff = staff;
752 chord->name = talloc_strdup (chord, name);
754 /* The width will get set correctly the first time _draw_chord is
759 staff->chords = talloc_realloc (staff,
763 if (staff->chords == NULL) {
764 staff->num_chords = 0;
768 staff->chords[staff->num_chords - 1] = chord;
774 score_staff_remove_chords (score_staff_t *staff)
776 talloc_free (staff->chords);
777 staff->chords = NULL;
779 staff->num_chords = 0;
783 score_remove_chords (score_t *score)
787 for (i = 0; i < score->num_staves; i++)
788 score_staff_remove_chords (score->staves[i]);
792 score_staff_add_note (score_staff_t *staff,
794 score_duration_t duration)
800 /* Return existing note if already present. */
801 for (i = 0; i < staff->num_notes; i++) {
802 note = staff->notes[i];
803 if (note->pitch == pitch &&
804 note->duration == duration)
810 note = talloc (staff, score_note_t);
816 note->duration = duration;
822 line = _score_staff_pitch_to_line (staff, note->pitch);
824 int lines = (int) (- line);
825 if (lines > staff->upper_ledger_lines)
826 staff->upper_ledger_lines = lines;
828 int lines = (int) (line - 4);
829 if (lines > staff->lower_ledger_lines)
830 staff->lower_ledger_lines = lines;
834 staff->notes = talloc_realloc (staff,
838 if (staff->notes == NULL) {
839 staff->num_notes = 0;
843 staff->notes[staff->num_notes - 1] = note;
847 score_add_note (score_t *score, pitch_t pitch, score_duration_t duration)
849 score_staff_t *staff, *nearest_staff = NULL;
850 double distance, nearest_distance = 0.0;
853 /* Nothing to do if we have no staff, (there's no place to add a note) . */
854 if (score->num_staves == 0)
857 /* Find the staff where the note will be closest to the center of
859 for (i = 0; i < score->num_staves; i++) {
860 staff = score->staves[i];
861 distance = fabs (_score_staff_pitch_to_line (staff, pitch) - 2.0);
862 if (nearest_staff == NULL || distance < nearest_distance) {
863 nearest_staff = staff;
864 nearest_distance = distance;
868 score_staff_add_note (nearest_staff, pitch, duration);
872 score_staff_remove_notes (score_staff_t *staff)
874 talloc_free (staff->notes);
876 staff->num_notes = 0;
878 staff->upper_ledger_lines = 0;
879 staff->lower_ledger_lines = 0;
883 score_remove_notes (score_t *score)
887 for (i = 0; i < score->num_staves; i++)
888 score_staff_remove_notes (score->staves[i]);