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 /* 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 = PITCH_CLASS_LITERAL (C, NATURAL);
109 score->key.num_sharps = 0;
110 score->key.num_flats = 0;
112 score->braces = NULL;
113 score->num_braces = 0;
115 score->staves = NULL;
116 score->num_staves = 0;
122 score_set_staff_height (score_t *score, int height)
124 score->space_height = (int) height / 4;
125 score->staff_height = score->space_height * 4;
127 score->line_width = score->space_height / 10;
128 if (score->line_width == 0)
129 score->line_width = 1;
131 return score->staff_height;
135 score_set_width (score_t *score, int width)
137 score->width = width;
141 score_set_key (score_t *score, pitch_t key)
143 scherzo_key_init (&score->key, key);
147 /* Returns in brace_width the width of the brace */
149 _draw_brace (score_t *score, cairo_t *cr,
150 score_brace_t *brace, int *brace_width)
152 cairo_glyph_t brace_glyph;
153 cairo_text_extents_t brace_extents;
156 if (brace->num_staves == 0)
161 top = score->staves[brace->first_staff]->y_pos;
162 bottom = score->staves[brace->first_staff + brace->num_staves - 1]->y_pos + score->staff_height;
164 cairo_select_font_face (cr, "Gonville-Brace", 0, 0);
166 /* XXX: This hard-coded glyph index is pretty ugly. We should
167 * figure out how to lookup the glyph we want, (though, as it
168 * turns out, this brace font pretty much just has numbered glyph
169 * names for different sizes, so it wouldn't be all that different
170 * than just the bare index here). */
171 brace_glyph.index = 300;
173 brace_glyph.y = top + (bottom - top) / 2.0 + score->line_width / 2.0;
175 /* XXX: This font size (in conjunction with the glyph selection)
176 * is a rough guess at best. We should figure out how the brace
177 * font is intended to be used and actually measure to find the
178 * correctly-sized glyph. */
179 cairo_set_font_size (cr, (bottom - top) / 3.85);
181 cairo_glyph_extents (cr, &brace_glyph, 1, &brace_extents);
183 /* Subtract space for brace itself */
184 cairo_translate (cr, -brace_extents.x_bearing, 0);
186 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
187 cairo_show_glyphs (cr, &brace_glyph, 1);
191 *brace_width = (int) -brace_extents.x_bearing;
194 /* Line containing middle C for the given clef. */
196 _score_clef_c_line (score_clef_t clef)
208 /* On which line would 'pitch' appear on 'staff'.
210 * Lines are numbered with line 0 as the top full line of the staff
211 * and increasing downward. So line values less than 0 will appear as
212 * ledger lines above the staff while line values greater than 4 will
213 * appear on ledger lines below the staff.
215 * A line value of 2 will be centered verticall on the staff.
217 * For notes appearing on a space, the line value will be half-way
218 * between two integers. */
220 _score_staff_pitch_to_line (score_staff_t *staff, pitch_t pitch)
222 pitch_name_t name = PITCH_NAME (pitch);
223 int octave = PITCH_OCTAVE (pitch);
224 int c_line = _score_clef_c_line (staff->clef);
226 return c_line - (name - PITCH_NAME_C) / 2.0 - 3.5 * (octave - 4);
229 /* chord->width is updated as a side effect */
231 _draw_chord (score_t *score, cairo_t *cr,
232 score_staff_t *staff, score_chord_t *chord)
234 PangoRectangle ink_extents;
235 PangoRectangle logical_extents;
236 double total_staff_height;
238 PangoFontDescription *font_description;
240 /* XXX: The staff should manage this height itself. */
241 total_staff_height = (staff->upper_ledger_lines * score->space_height +
242 score->staff_height +
243 staff->lower_ledger_lines * score->space_height);
247 font_description = pango_font_description_new ();
248 pango_font_description_set_family (font_description, "serif");
249 pango_font_description_set_absolute_size (font_description,
250 score->space_height * 3 * PANGO_SCALE);
252 layout = pango_cairo_create_layout (cr);
253 pango_layout_set_font_description (layout, font_description);
254 pango_layout_set_markup (layout, chord->name, -1);
256 pango_layout_line_get_pixel_extents (pango_layout_get_line (layout, 0),
257 &ink_extents, &logical_extents);
259 if (staff->clef == SCORE_CLEF_G)
260 cairo_move_to (cr, 0, - score->space_height * 0.5);
262 cairo_move_to (cr, 0, score->space_height * 0.5 + total_staff_height +
263 logical_extents.height);
265 pango_cairo_show_layout_line (cr, pango_layout_get_line (layout, 0));
267 g_object_unref (layout);
268 pango_font_description_free (font_description);
270 chord->width = logical_extents.width;
275 /* Draw 'note' at its correct position on 'staff'.
276 * If the accidental of 'note' is not contained within the current
277 * key, then draw the accidental as well.
279 * As a special case, if the note's duration is 0, draw the accidental
280 * alone, regardless of the key. This is useful for drawing the
281 * accidentals of the key signature.
283 * Returns the width of the drawn glyphs.
286 _draw_note (score_t *score, cairo_t *cr,
287 score_staff_t *staff, score_note_t *note)
290 cairo_glyph_t note_glyph[2];
291 static double extend_factor = 0.25;
292 cairo_text_extents_t extents;
295 void _draw_ledger_line (double line, double offset, double width) {
296 cairo_move_to (cr, offset - extend_factor * width / 2.0,
297 score->space_height * line + score->line_width / 2.0);
298 cairo_rel_line_to (cr, (1 + extend_factor) * width, 0);
304 /* Move right so that X==0 is natural position for non-displaced
307 cairo_translate (cr, score->space_height, 0);
309 /* Which line should the note appear on? Line 0 is the top line of
310 * the staff and increasing downwards. (Negative values indicate a
311 * note on a ledger line above the staff). Values half way between
312 * integers indicate notes appearing on a space between two staff
313 * lines (or ledger lines). */
314 line = _score_staff_pitch_to_line (staff, note->pitch);
316 cairo_select_font_face (cr, "Gonville-26", 0, 0);
317 cairo_set_font_size (cr, score->staff_height);
319 /* XXX: The hard-coded glyph indices here are very ugly. We should
320 * figure out how to lookup glyphs by name from this font. */
321 switch (PITCH_ACCIDENTAL (note->pitch)) {
322 case PITCH_ACCIDENTAL_DOUBLE_FLAT:
323 note_glyph[num_glyphs].index = 77;
325 case PITCH_ACCIDENTAL_FLAT:
326 note_glyph[num_glyphs].index = 68;
328 case PITCH_ACCIDENTAL_NATURAL:
329 note_glyph[num_glyphs].index = 101;
331 case PITCH_ACCIDENTAL_SHARP:
332 note_glyph[num_glyphs].index = 134;
334 case PITCH_ACCIDENTAL_DOUBLE_SHARP:
335 note_glyph[num_glyphs].index = 142;
339 if (note->duration == 0 ||
340 ! scherzo_key_contains_pitch (&score->key, note->pitch))
342 note_glyph[num_glyphs].x = 0;
344 note_glyph[num_glyphs].y = score->space_height * line;
348 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
350 #define ACCIDENTAL_NOTE_SPACING (score->space_height * .15)
352 note_glyph[0].x = - (extents.width + ACCIDENTAL_NOTE_SPACING);
355 /* Support duration == 0 to draw accidental only */
358 switch (note->duration) {
359 case SCORE_DURATION_1:
360 note_glyph[num_glyphs].index = 127;
362 case SCORE_DURATION_2:
363 note_glyph[num_glyphs].index = 85;
365 case SCORE_DURATION_4:
366 case SCORE_DURATION_8:
367 case SCORE_DURATION_16:
368 case SCORE_DURATION_32:
369 case SCORE_DURATION_64:
370 case SCORE_DURATION_128:
372 note_glyph[num_glyphs].index = 84;
375 note_glyph[num_glyphs].x = 0;
376 note_glyph[num_glyphs].y = score->space_height * line;
381 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
383 if (line < 0 || line > 4) {
384 double offset, width;
387 offset = note_glyph[0].x + extents.x_bearing;
388 width = extents.width;
391 for (i = -1; i >= line; i--)
392 _draw_ledger_line (i, offset, width);
394 for (i = 5; i <= line; i++)
395 _draw_ledger_line (i, offset, width);
399 cairo_set_source_rgb (cr,
403 cairo_show_glyphs (cr, note_glyph, num_glyphs);
407 return extents.width;
410 /* Draw the accidental from 'pitch' only (no notehead) at the correct
411 * position as if drawing a note at 'pitch'.
413 * Returns the width of the drawn glyph.
416 _draw_accidental (score_t *score,
418 score_staff_t *staff,
426 /* A duration of 0 indicates to draw only the accidental. */
433 return _draw_note (score, cr, staff, ¬e);
437 _draw_key_signature (score_t *score, cairo_t *cr,
438 score_staff_t *staff)
444 /* These octave numbers are correct for treble clef. For bass
445 * clef, subtract two.
447 pitch_t sharps_order[] = {
448 PITCH_LITERAL (F, SHARP, 5),
449 PITCH_LITERAL (C, SHARP, 5),
450 PITCH_LITERAL (G, SHARP, 5),
451 PITCH_LITERAL (D, SHARP, 5),
452 PITCH_LITERAL (A, SHARP, 4),
453 PITCH_LITERAL (E, SHARP, 5),
454 PITCH_LITERAL (B, SHARP, 4)
457 pitch_t flats_order[] = {
458 PITCH_LITERAL (B, FLAT, 4),
459 PITCH_LITERAL (E, FLAT, 5),
460 PITCH_LITERAL (A, FLAT, 4),
461 PITCH_LITERAL (D, FLAT, 5),
462 PITCH_LITERAL (G, FLAT, 4),
463 PITCH_LITERAL (C, FLAT, 5),
464 PITCH_LITERAL (F, FLAT, 4),
467 for (i = 0; i < score->key.num_sharps; i++) {
468 pitch = sharps_order[i];
470 if (staff->clef == SCORE_CLEF_BASS)
471 pitch = pitch_lower_by_octaves (pitch, 2);
473 width = _draw_accidental (score, cr, staff, pitch);
475 #define KEY_SIGNATURE_ACCIDENTAL_SPACING (score->space_height * .15)
476 cairo_translate (cr, ceil (width + KEY_SIGNATURE_ACCIDENTAL_SPACING), 0);
479 for (i = 0; i < score->key.num_flats; i++) {
480 pitch = flats_order[i];
482 if (staff->clef == SCORE_CLEF_BASS)
483 pitch = pitch_lower_by_octaves (pitch, 2);
485 width = _draw_accidental (score, cr, staff, pitch);
487 cairo_translate (cr, ceil (width + KEY_SIGNATURE_ACCIDENTAL_SPACING), 0);
492 _draw_staff (score_t *score, cairo_t *cr,
493 score_staff_t *staff, int staff_width)
496 cairo_glyph_t clef_glyph;
497 cairo_text_extents_t clef_extents;
501 cairo_translate (cr, 0, staff->y_pos);
503 cairo_select_font_face (cr, "Gonville-26", 0, 0);
505 cairo_set_font_size (cr, score->staff_height);
507 /* Draw staff lines */
508 for (i = 0; i < 5; i++) {
509 cairo_move_to (cr, 0, i * score->space_height + score->line_width / 2.0);
510 cairo_rel_line_to (cr, staff_width, 0);
513 cairo_set_line_width (cr, score->line_width);
515 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
520 /* XXX: The hard-coded glyph indices here are very ugly. We should
521 * figure out how to lookup glyphs by name from this font. */
522 switch (staff->clef) {
525 clef_glyph.index = 46;
526 clef_glyph.y = 3 * score->space_height;
529 clef_glyph.index = 45;
530 clef_glyph.y = 1 * score->space_height;
533 clef_glyph.x = 3 * score->line_width;
534 clef_glyph.y += score->line_width / 2.0;
536 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
537 cairo_show_glyphs (cr, &clef_glyph, 1);
539 /* Make space for clef */
540 cairo_glyph_extents (cr, &clef_glyph, 1, &clef_extents);
542 #define CLEF_KEY_SIGNATURE_SPACING (score->space_height * .75)
543 cairo_translate (cr, ceil (clef_extents.width +
544 CLEF_KEY_SIGNATURE_SPACING), 0);
546 /* Draw the key signature */
547 _draw_key_signature (score, cr, staff);
549 #define KEY_SIGNATURE_NOTE_SPACING (score->space_height)
550 cairo_translate (cr, ceil (KEY_SIGNATURE_NOTE_SPACING), 0);
552 /* Draw chord symbols */
555 for (i = 0; i < staff->num_chords; i++) {
556 _draw_chord (score, cr, staff, staff->chords[i]);
557 cairo_translate (cr, staff->chords[i]->width, 0.0);
563 for (i = 0; i < staff->num_notes; i++) {
564 _draw_note (score, cr, staff, staff->notes[i]);
565 /* Draw all notes concurrent for now (as a chord)
566 cairo_translate (cr, score->space_height * 2.0, 0);
574 score_draw (score_t *score, cairo_t *cr)
577 int staff_width = score->width;
580 if (score->num_staves == 0)
585 /* Before drawing anything, position each staff based on the size
586 * of each (including ledger lines) */
588 for (i = 0; i < score->num_staves; i++) {
589 score_staff_t *staff = score->staves[i];
590 staff_y_pos += staff->upper_ledger_lines * score->space_height;
591 staff->y_pos = staff_y_pos;
592 staff_y_pos += (score->staff_height +
593 staff->lower_ledger_lines * score->space_height +
594 score->staff_height);
597 if (score->num_braces)
599 /* Initialize to keep the compiler quiet. */
602 for (i = 0; i < score->num_braces; i++)
603 _draw_brace (score, cr, score->braces[i], &brace_width);
605 /* Subtract space for brace itself */
606 cairo_translate (cr, brace_width, 0);
607 staff_width -= brace_width;
609 /* As well as some padding */
610 cairo_translate (cr, 2, 0);
614 /* Vertical lines at each end */
616 score->line_width / 2.0,
617 score->staves[0]->y_pos + score->line_width / 2.0,
618 staff_width - score->line_width,
619 score->staves[score->num_staves-1]->y_pos + score->staff_height - score->staves[0]->y_pos);
620 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
621 cairo_set_line_width (cr, score->line_width);
624 for (i = 0; i < score->num_staves; i++) {
625 score_staff_t *staff = score->staves[i];
626 _draw_staff (score, cr, staff, staff_width);
633 score_add_brace (score_t *score, int staves)
635 score_brace_t *brace;
637 brace = talloc (score, score_brace_t);
641 brace->first_staff = score->num_staves;
642 brace->num_staves = staves;
645 score->braces = talloc_realloc (score,
649 if (score->braces == NULL) {
650 score->num_braces = 0;
654 score->braces[score->num_braces - 1] = brace;
659 score_add_staff (score_t *score, score_clef_t clef)
661 score_staff_t *staff;
663 staff = talloc (score, score_staff_t);
670 staff->num_notes = 0;
672 staff->chords = NULL;
673 staff->num_chords = 0;
675 staff->upper_ledger_lines = 0;
676 staff->lower_ledger_lines = 0;
679 score->staves = talloc_realloc (score,
683 if (score->staves == NULL) {
684 score->num_staves = 0;
688 score->staves[score->num_staves - 1] = staff;
694 score_add_chord (score_staff_t *staff,
697 score_chord_t *chord;
699 chord = talloc (staff, score_chord_t);
703 talloc_steal (chord, name);
705 chord->staff = staff;
706 chord->name = talloc_strdup (chord, name);
708 /* The width will get set correctly the first time _draw_chord is
713 staff->chords = talloc_realloc (staff,
717 if (staff->chords == NULL) {
718 staff->num_chords = 0;
722 staff->chords[staff->num_chords - 1] = chord;
728 score_staff_remove_chords (score_staff_t *staff)
730 talloc_free (staff->chords);
731 staff->chords = NULL;
733 staff->num_chords = 0;
737 score_remove_chords (score_t *score)
741 for (i = 0; i < score->num_staves; i++)
742 score_staff_remove_chords (score->staves[i]);
746 score_staff_add_note (score_staff_t *staff,
748 score_duration_t duration)
754 /* Return existing note if already present. */
755 for (i = 0; i < staff->num_notes; i++) {
756 note = staff->notes[i];
757 if (note->pitch == pitch &&
758 note->duration == duration)
764 note = talloc (staff, score_note_t);
770 note->duration = duration;
776 line = _score_staff_pitch_to_line (staff, note->pitch);
778 int lines = (int) (- line);
779 if (lines > staff->upper_ledger_lines)
780 staff->upper_ledger_lines = lines;
782 int lines = (int) (line - 4);
783 if (lines > staff->lower_ledger_lines)
784 staff->lower_ledger_lines = lines;
788 staff->notes = talloc_realloc (staff,
792 if (staff->notes == NULL) {
793 staff->num_notes = 0;
797 staff->notes[staff->num_notes - 1] = note;
801 score_add_note (score_t *score, pitch_t pitch, score_duration_t duration)
803 score_staff_t *staff, *nearest_staff = NULL;
804 double distance, nearest_distance = 0.0;
807 /* Nothing to do if we have no staff, (there's no place to add a note) . */
808 if (score->num_staves == 0)
811 /* Find the staff where the note will be closest to the center of
813 for (i = 0; i < score->num_staves; i++) {
814 staff = score->staves[i];
815 distance = fabs (_score_staff_pitch_to_line (staff, pitch) - 2.0);
816 if (nearest_staff == NULL || distance < nearest_distance) {
817 nearest_staff = staff;
818 nearest_distance = distance;
822 score_staff_add_note (nearest_staff, pitch, duration);
826 score_staff_remove_notes (score_staff_t *staff)
828 talloc_free (staff->notes);
830 staff->num_notes = 0;
832 staff->upper_ledger_lines = 0;
833 staff->lower_ledger_lines = 0;
837 score_remove_notes (score_t *score)
841 for (i = 0; i < score->num_staves; i++)
842 score_staff_remove_notes (score->staves[i]);