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
67 typedef struct score_key
69 /* Pitch class (diatonic) */
72 /* Number of sharps/flats in key (if any) */
79 /* Nominal height of a single staff (ledger lines may make it larger) */
82 /* Height of one space within a staff */
85 /* Minimal line width for staff lines */
88 /* Full width of staff */
91 /* Current (diatonic) key */
94 score_brace_t **braces;
98 score_staff_t **staves;
103 score_create (void *ctx)
107 score = talloc (ctx, score_t);
111 /* Also sets space_height and line_width */
112 score_set_staff_height (score, 76);
114 /* Just to have some nominal width. */
117 /* Default to C, of course */
118 score->key.pitch = PITCH_CLASS_LITERAL (C, NATURAL);
119 score->key.num_sharps = 0;
120 score->key.num_flats = 0;
122 score->braces = NULL;
123 score->num_braces = 0;
125 score->staves = NULL;
126 score->num_staves = 0;
132 score_set_staff_height (score_t *score, int height)
134 score->space_height = (int) height / 4;
135 score->staff_height = score->space_height * 4;
137 score->line_width = score->space_height / 10;
138 if (score->line_width == 0)
139 score->line_width = 1;
141 return score->staff_height;
145 score_set_width (score_t *score, int width)
147 score->width = width;
151 score_set_key (score_t *score, pitch_t key)
155 pitch_t sharp_keys[] = {
156 PITCH_CLASS_LITERAL (C, NATURAL),
157 PITCH_CLASS_LITERAL (G, NATURAL),
158 PITCH_CLASS_LITERAL (D, NATURAL),
159 PITCH_CLASS_LITERAL (A, NATURAL),
160 PITCH_CLASS_LITERAL (E, NATURAL),
161 PITCH_CLASS_LITERAL (B, NATURAL),
162 PITCH_CLASS_LITERAL (F, SHARP),
163 PITCH_CLASS_LITERAL (C, SHARP),
166 pitch_t flat_keys[] = {
167 PITCH_CLASS_LITERAL (C, NATURAL),
168 PITCH_CLASS_LITERAL (F, NATURAL),
169 PITCH_CLASS_LITERAL (B, FLAT),
170 PITCH_CLASS_LITERAL (E, FLAT),
171 PITCH_CLASS_LITERAL (A, FLAT),
172 PITCH_CLASS_LITERAL (D, FLAT),
173 PITCH_CLASS_LITERAL (G, FLAT),
174 PITCH_CLASS_LITERAL (C, FLAT)
177 score->key.pitch = PITCH_CLASS (PITCH_NAME (key), PITCH_ACCIDENTAL (key));
179 score->key.num_sharps = 0;
180 for (i = 0; i < ARRAY_SIZE (sharp_keys); i++)
181 if (sharp_keys[i] == score->key.pitch)
182 score->key.num_sharps = i;
184 score->key.num_flats = 0;
185 for (i = 0; i < ARRAY_SIZE (flat_keys); i++)
186 if (flat_keys[i] == score->key.pitch)
187 score->key.num_flats = i;
191 score_key_contains_pitch (score_key_t *key, pitch_t pitch)
193 pitch_accidental_t accidental = PITCH_ACCIDENTAL (pitch);
196 pitch_name_t sharps_order[] = {
206 pitch_name_t flats_order[] = {
216 if (accidental == PITCH_ACCIDENTAL_DOUBLE_SHARP ||
217 accidental == PITCH_ACCIDENTAL_DOUBLE_FLAT)
222 if (key->num_sharps) {
223 if (accidental == PITCH_ACCIDENTAL_FLAT)
225 for (i = 0; i < key->num_sharps; i++) {
226 if (sharps_order[i] == PITCH_NAME (pitch)) {
227 if (accidental == PITCH_ACCIDENTAL_SHARP)
233 if (accidental == PITCH_ACCIDENTAL_SHARP)
239 if (key->num_flats) {
240 if (accidental == PITCH_ACCIDENTAL_SHARP)
242 for (i = 0; i < key->num_flats; i++) {
243 if (flats_order[i] == PITCH_NAME (pitch)) {
244 if (accidental == PITCH_ACCIDENTAL_FLAT)
250 if (accidental == PITCH_ACCIDENTAL_FLAT)
256 if (accidental == PITCH_ACCIDENTAL_NATURAL)
262 /* Returns in brace_width the width of the brace */
264 _draw_brace (score_t *score, cairo_t *cr,
265 score_brace_t *brace, int *brace_width)
267 cairo_glyph_t brace_glyph;
268 cairo_text_extents_t brace_extents;
271 if (brace->num_staves == 0)
276 top = score->staves[brace->first_staff]->y_pos;
277 bottom = score->staves[brace->first_staff + brace->num_staves - 1]->y_pos + score->staff_height;
279 cairo_select_font_face (cr, "Gonville-Brace", 0, 0);
281 /* XXX: This hard-coded glyph index is pretty ugly. We should
282 * figure out how to lookup the glyph we want, (though, as it
283 * turns out, this brace font pretty much just has numbered glyph
284 * names for different sizes, so it wouldn't be all that different
285 * than just the bare index here). */
286 brace_glyph.index = 300;
288 brace_glyph.y = top + (bottom - top) / 2.0 + score->line_width / 2.0;
290 /* XXX: This font size (in conjunction with the glyph selection)
291 * is a rough guess at best. We should figure out how the brace
292 * font is intended to be used and actually measure to find the
293 * correctly-sized glyph. */
294 cairo_set_font_size (cr, (bottom - top) / 3.85);
296 cairo_glyph_extents (cr, &brace_glyph, 1, &brace_extents);
298 /* Subtract space for brace itself */
299 cairo_translate (cr, -brace_extents.x_bearing, 0);
301 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
302 cairo_show_glyphs (cr, &brace_glyph, 1);
306 *brace_width = (int) -brace_extents.x_bearing;
309 /* Line containing middle C for the given clef. */
311 _score_clef_c_line (score_clef_t clef)
323 /* On which line would 'pitch' appear on 'staff'.
325 * Lines are numbered with line 0 as the top full line of the staff
326 * and increasing downward. So line values less than 0 will appear as
327 * ledger lines above the staff while line values greater than 4 will
328 * appear on ledger lines below the staff.
330 * A line value of 2 will be centered verticall on the staff.
332 * For notes appearing on a space, the line value will be half-way
333 * between two integers. */
335 _score_staff_pitch_to_line (score_staff_t *staff, pitch_t pitch)
337 pitch_name_t name = PITCH_NAME (pitch);
338 int octave = PITCH_OCTAVE (pitch);
339 int c_line = _score_clef_c_line (staff->clef);
341 return c_line - (name - PITCH_NAME_C) / 2.0 - 3.5 * (octave - 4);
344 /* chord->width is updated as a side effect */
346 _draw_chord (score_t *score, cairo_t *cr,
347 score_staff_t *staff, score_chord_t *chord)
349 PangoRectangle ink_extents;
350 PangoRectangle logical_extents;
351 double total_staff_height;
353 PangoFontDescription *font_description;
355 /* XXX: The staff should manage this height itself. */
356 total_staff_height = (staff->upper_ledger_lines * score->space_height +
357 score->staff_height +
358 staff->lower_ledger_lines * score->space_height);
362 font_description = pango_font_description_new ();
363 pango_font_description_set_family (font_description, "serif");
364 pango_font_description_set_absolute_size (font_description,
365 score->space_height * 3 * PANGO_SCALE);
367 layout = pango_cairo_create_layout (cr);
368 pango_layout_set_font_description (layout, font_description);
369 pango_layout_set_markup (layout, chord->name, -1);
371 pango_layout_line_get_pixel_extents (pango_layout_get_line (layout, 0),
372 &ink_extents, &logical_extents);
374 if (staff->clef == SCORE_CLEF_G)
375 cairo_move_to (cr, 0, - score->space_height * 0.5);
377 cairo_move_to (cr, 0, score->space_height * 0.5 + total_staff_height +
378 logical_extents.height);
380 pango_cairo_show_layout_line (cr, pango_layout_get_line (layout, 0));
382 g_object_unref (layout);
383 pango_font_description_free (font_description);
385 chord->width = logical_extents.width;
390 /* Draw 'note' at its correct position on 'staff'.
391 * If the accidental of 'note' is not contained within the current
392 * key, then draw the accidental as well.
394 * As a special case, if the note's duration is 0, draw the accidental
395 * alone, regardless of the key. This is useful for drawing the
396 * accidentals of the key signature.
398 * Returns the width of the drawn glyphs.
401 _draw_note (score_t *score, cairo_t *cr,
402 score_staff_t *staff, score_note_t *note)
405 cairo_glyph_t note_glyph[2];
406 static double extend_factor = 0.25;
407 cairo_text_extents_t extents;
410 void _draw_ledger_line (double line, double offset, double width) {
411 cairo_move_to (cr, offset - extend_factor * width / 2.0,
412 score->space_height * line + score->line_width / 2.0);
413 cairo_rel_line_to (cr, (1 + extend_factor) * width, 0);
419 /* Move right so that X==0 is natural position for non-displaced
422 cairo_translate (cr, score->space_height, 0);
424 /* Which line should the note appear on? Line 0 is the top line of
425 * the staff and increasing downwards. (Negative values indicate a
426 * note on a ledger line above the staff). Values half way between
427 * integers indicate notes appearing on a space between two staff
428 * lines (or ledger lines). */
429 line = _score_staff_pitch_to_line (staff, note->pitch);
431 cairo_select_font_face (cr, "Gonville-26", 0, 0);
432 cairo_set_font_size (cr, score->staff_height);
434 /* XXX: The hard-coded glyph indices here are very ugly. We should
435 * figure out how to lookup glyphs by name from this font. */
436 switch (PITCH_ACCIDENTAL (note->pitch)) {
437 case PITCH_ACCIDENTAL_DOUBLE_FLAT:
438 note_glyph[num_glyphs].index = 77;
440 case PITCH_ACCIDENTAL_FLAT:
441 note_glyph[num_glyphs].index = 68;
443 case PITCH_ACCIDENTAL_NATURAL:
444 note_glyph[num_glyphs].index = 101;
446 case PITCH_ACCIDENTAL_SHARP:
447 note_glyph[num_glyphs].index = 134;
449 case PITCH_ACCIDENTAL_DOUBLE_SHARP:
450 note_glyph[num_glyphs].index = 142;
454 if (note->duration == 0 ||
455 ! score_key_contains_pitch (&score->key, note->pitch))
457 note_glyph[num_glyphs].x = 0;
459 note_glyph[num_glyphs].y = score->space_height * line;
463 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
465 #define ACCIDENTAL_NOTE_SPACING (score->space_height * .15)
467 note_glyph[0].x = - (extents.width + ACCIDENTAL_NOTE_SPACING);
470 /* Support duration == 0 to draw accidental only */
473 switch (note->duration) {
474 case SCORE_DURATION_1:
475 note_glyph[num_glyphs].index = 127;
477 case SCORE_DURATION_2:
478 note_glyph[num_glyphs].index = 85;
480 case SCORE_DURATION_4:
481 case SCORE_DURATION_8:
482 case SCORE_DURATION_16:
483 case SCORE_DURATION_32:
484 case SCORE_DURATION_64:
485 case SCORE_DURATION_128:
487 note_glyph[num_glyphs].index = 84;
490 note_glyph[num_glyphs].x = 0;
491 note_glyph[num_glyphs].y = score->space_height * line;
496 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
498 if (line < 0 || line > 4) {
499 double offset, width;
502 offset = note_glyph[0].x + extents.x_bearing;
503 width = extents.width;
506 for (i = -1; i >= line; i--)
507 _draw_ledger_line (i, offset, width);
509 for (i = 5; i <= line; i++)
510 _draw_ledger_line (i, offset, width);
514 cairo_set_source_rgb (cr,
518 cairo_show_glyphs (cr, note_glyph, num_glyphs);
522 return extents.width;
525 /* Draw the accidental from 'pitch' only (no notehead) at the correct
526 * position as if drawing a note at 'pitch'.
528 * Returns the width of the drawn glyph.
531 _draw_accidental (score_t *score,
533 score_staff_t *staff,
541 /* A duration of 0 indicates to draw only the accidental. */
548 return _draw_note (score, cr, staff, ¬e);
552 _draw_key_signature (score_t *score, cairo_t *cr,
553 score_staff_t *staff)
559 /* These octave numbers are correct for treble clef. For bass
560 * clef, subtract two.
562 pitch_t sharps_order[] = {
563 PITCH_LITERAL (F, SHARP, 5),
564 PITCH_LITERAL (C, SHARP, 5),
565 PITCH_LITERAL (G, SHARP, 5),
566 PITCH_LITERAL (D, SHARP, 5),
567 PITCH_LITERAL (A, SHARP, 4),
568 PITCH_LITERAL (E, SHARP, 5),
569 PITCH_LITERAL (B, SHARP, 4)
572 pitch_t flats_order[] = {
573 PITCH_LITERAL (B, FLAT, 4),
574 PITCH_LITERAL (E, FLAT, 5),
575 PITCH_LITERAL (A, FLAT, 4),
576 PITCH_LITERAL (D, FLAT, 5),
577 PITCH_LITERAL (G, FLAT, 4),
578 PITCH_LITERAL (C, FLAT, 5),
579 PITCH_LITERAL (F, FLAT, 4),
582 for (i = 0; i < score->key.num_sharps; i++) {
583 pitch = sharps_order[i];
585 if (staff->clef == SCORE_CLEF_BASS)
586 pitch = pitch_lower_by_octaves (pitch, 2);
588 width = _draw_accidental (score, cr, staff, pitch);
590 #define KEY_SIGNATURE_ACCIDENTAL_SPACING (score->space_height * .15)
591 cairo_translate (cr, ceil (width + KEY_SIGNATURE_ACCIDENTAL_SPACING), 0);
594 for (i = 0; i < score->key.num_flats; i++) {
595 pitch = flats_order[i];
597 if (staff->clef == SCORE_CLEF_BASS)
598 pitch = pitch_lower_by_octaves (pitch, 2);
600 width = _draw_accidental (score, cr, staff, pitch);
602 cairo_translate (cr, ceil (width + KEY_SIGNATURE_ACCIDENTAL_SPACING), 0);
607 _draw_staff (score_t *score, cairo_t *cr,
608 score_staff_t *staff, int staff_width)
611 cairo_glyph_t clef_glyph;
612 cairo_text_extents_t clef_extents;
616 cairo_translate (cr, 0, staff->y_pos);
618 cairo_select_font_face (cr, "Gonville-26", 0, 0);
620 cairo_set_font_size (cr, score->staff_height);
622 /* Draw staff lines */
623 for (i = 0; i < 5; i++) {
624 cairo_move_to (cr, 0, i * score->space_height + score->line_width / 2.0);
625 cairo_rel_line_to (cr, staff_width, 0);
628 cairo_set_line_width (cr, score->line_width);
630 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
635 /* XXX: The hard-coded glyph indices here are very ugly. We should
636 * figure out how to lookup glyphs by name from this font. */
637 switch (staff->clef) {
640 clef_glyph.index = 46;
641 clef_glyph.y = 3 * score->space_height;
644 clef_glyph.index = 45;
645 clef_glyph.y = 1 * score->space_height;
648 clef_glyph.x = 3 * score->line_width;
649 clef_glyph.y += score->line_width / 2.0;
651 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
652 cairo_show_glyphs (cr, &clef_glyph, 1);
654 /* Make space for clef */
655 cairo_glyph_extents (cr, &clef_glyph, 1, &clef_extents);
657 #define CLEF_KEY_SIGNATURE_SPACING (score->space_height * .75)
658 cairo_translate (cr, ceil (clef_extents.width +
659 CLEF_KEY_SIGNATURE_SPACING), 0);
661 /* Draw the key signature */
662 _draw_key_signature (score, cr, staff);
664 /* Draw chord symbols */
667 for (i = 0; i < staff->num_chords; i++) {
668 _draw_chord (score, cr, staff, staff->chords[i]);
669 cairo_translate (cr, staff->chords[i]->width, 0.0);
675 for (i = 0; i < staff->num_notes; i++) {
676 _draw_note (score, cr, staff, staff->notes[i]);
677 /* Draw all notes concurrent for now (as a chord)
678 cairo_translate (cr, score->space_height * 2.0, 0);
686 score_draw (score_t *score, cairo_t *cr)
689 int staff_width = score->width;
692 if (score->num_staves == 0)
697 /* Before drawing anything, position each staff based on the size
698 * of each (including ledger lines) */
700 for (i = 0; i < score->num_staves; i++) {
701 score_staff_t *staff = score->staves[i];
702 staff_y_pos += staff->upper_ledger_lines * score->space_height;
703 staff->y_pos = staff_y_pos;
704 staff_y_pos += (score->staff_height +
705 staff->lower_ledger_lines * score->space_height +
706 score->staff_height);
709 if (score->num_braces)
711 /* Initialize to keep the compiler quiet. */
714 for (i = 0; i < score->num_braces; i++)
715 _draw_brace (score, cr, score->braces[i], &brace_width);
717 /* Subtract space for brace itself */
718 cairo_translate (cr, brace_width, 0);
719 staff_width -= brace_width;
721 /* As well as some padding */
722 cairo_translate (cr, 2, 0);
726 /* Vertical lines at each end */
728 score->line_width / 2.0,
729 score->staves[0]->y_pos + score->line_width / 2.0,
730 staff_width - score->line_width,
731 score->staves[score->num_staves-1]->y_pos + score->staff_height - score->staves[0]->y_pos);
732 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
733 cairo_set_line_width (cr, score->line_width);
736 for (i = 0; i < score->num_staves; i++) {
737 score_staff_t *staff = score->staves[i];
738 _draw_staff (score, cr, staff, staff_width);
745 score_add_brace (score_t *score, int staves)
747 score_brace_t *brace;
749 brace = talloc (score, score_brace_t);
753 brace->first_staff = score->num_staves;
754 brace->num_staves = staves;
757 score->braces = talloc_realloc (score,
761 if (score->braces == NULL) {
762 score->num_braces = 0;
766 score->braces[score->num_braces - 1] = brace;
771 score_add_staff (score_t *score, score_clef_t clef)
773 score_staff_t *staff;
775 staff = talloc (score, score_staff_t);
782 staff->num_notes = 0;
784 staff->chords = NULL;
785 staff->num_chords = 0;
787 staff->upper_ledger_lines = 0;
788 staff->lower_ledger_lines = 0;
791 score->staves = talloc_realloc (score,
795 if (score->staves == NULL) {
796 score->num_staves = 0;
800 score->staves[score->num_staves - 1] = staff;
806 score_add_chord (score_staff_t *staff,
809 score_chord_t *chord;
811 chord = talloc (staff, score_chord_t);
815 talloc_steal (chord, name);
817 chord->staff = staff;
818 chord->name = talloc_strdup (chord, name);
820 /* The width will get set correctly the first time _draw_chord is
825 staff->chords = talloc_realloc (staff,
829 if (staff->chords == NULL) {
830 staff->num_chords = 0;
834 staff->chords[staff->num_chords - 1] = chord;
840 score_staff_remove_chords (score_staff_t *staff)
842 talloc_free (staff->chords);
843 staff->chords = NULL;
845 staff->num_chords = 0;
849 score_remove_chords (score_t *score)
853 for (i = 0; i < score->num_staves; i++)
854 score_staff_remove_chords (score->staves[i]);
858 score_staff_add_note (score_staff_t *staff,
860 score_duration_t duration)
866 /* Return existing note if already present. */
867 for (i = 0; i < staff->num_notes; i++) {
868 note = staff->notes[i];
869 if (note->pitch == pitch &&
870 note->duration == duration)
876 note = talloc (staff, score_note_t);
882 note->duration = duration;
888 line = _score_staff_pitch_to_line (staff, note->pitch);
890 int lines = (int) (- line);
891 if (lines > staff->upper_ledger_lines)
892 staff->upper_ledger_lines = lines;
894 int lines = (int) (line - 4);
895 if (lines > staff->lower_ledger_lines)
896 staff->lower_ledger_lines = lines;
900 staff->notes = talloc_realloc (staff,
904 if (staff->notes == NULL) {
905 staff->num_notes = 0;
909 staff->notes[staff->num_notes - 1] = note;
913 score_add_note (score_t *score, pitch_t pitch, score_duration_t duration)
915 score_staff_t *staff, *nearest_staff = NULL;
916 double distance, nearest_distance = 0.0;
919 /* Nothing to do if we have no staff, (there's no place to add a note) . */
920 if (score->num_staves == 0)
923 /* Find the staff where the note will be closest to the center of
925 for (i = 0; i < score->num_staves; i++) {
926 staff = score->staves[i];
927 distance = fabs (_score_staff_pitch_to_line (staff, pitch) - 2.0);
928 if (nearest_staff == NULL || distance < nearest_distance) {
929 nearest_staff = staff;
930 nearest_distance = distance;
934 score_staff_add_note (nearest_staff, pitch, duration);
938 score_staff_remove_notes (score_staff_t *staff)
940 talloc_free (staff->notes);
942 staff->num_notes = 0;
944 staff->upper_ledger_lines = 0;
945 staff->lower_ledger_lines = 0;
949 score_remove_notes (score_t *score)
953 for (i = 0; i < score->num_staves; i++)
954 score_staff_remove_notes (score->staves[i]);