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 c_line = _score_clef_c_line (staff->clef);
183 return c_line - (name - PITCH_NAME_C) / 2.0 - 3.5 * (note->octave - 4);
186 /* chord->width is updated as a side effect */
188 _draw_chord (score_t *score, cairo_t *cr,
189 score_staff_t *staff, score_chord_t *chord)
191 PangoRectangle ink_extents;
192 PangoRectangle logical_extents;
193 double total_staff_height;
195 PangoFontDescription *font_description;
197 /* XXX: The staff should manage this height itself. */
198 total_staff_height = (staff->upper_ledger_lines * score->space_height +
199 score->staff_height +
200 staff->lower_ledger_lines * score->space_height);
204 font_description = pango_font_description_new ();
205 pango_font_description_set_family (font_description, "serif");
206 pango_font_description_set_absolute_size (font_description,
207 score->space_height * 3 * PANGO_SCALE);
209 layout = pango_cairo_create_layout (cr);
210 pango_layout_set_font_description (layout, font_description);
211 pango_layout_set_markup (layout, chord->name, -1);
213 pango_layout_line_get_pixel_extents (pango_layout_get_line (layout, 0),
214 &ink_extents, &logical_extents);
216 if (staff->clef == SCORE_CLEF_G)
217 cairo_move_to (cr, 0, - score->space_height * 0.5);
219 cairo_move_to (cr, 0, score->space_height * 0.5 + total_staff_height +
220 logical_extents.height);
222 pango_cairo_show_layout_line (cr, pango_layout_get_line (layout, 0));
224 g_object_unref (layout);
225 pango_font_description_free (font_description);
227 chord->width = logical_extents.width;
233 _draw_note (score_t *score, cairo_t *cr,
234 score_staff_t *staff, score_note_t *note)
237 cairo_glyph_t note_glyph[2];
238 static double extend_factor = 0.25;
239 cairo_text_extents_t extents;
242 void _draw_ledger_line (double line, double offset, double width) {
243 cairo_move_to (cr, offset - extend_factor * width / 2.0,
244 score->space_height * line + score->line_width / 2.0);
245 cairo_rel_line_to (cr, (1 + extend_factor) * width, 0);
251 /* Move right so that X==0 is natural position for non-displaced
254 cairo_translate (cr, score->space_height, 0);
256 /* Which line should the note appear on? Line 0 is the top line of
257 * the staff and increasing downwards. (Negative values indicate a
258 * note on a ledger line above the staff). Values half way between
259 * integers indicate notes appearing on a space between two staff
260 * lines (or ledger lines). */
261 line = _score_note_to_line (staff, note);
263 cairo_select_font_face (cr, "Gonville-26", 0, 0);
264 cairo_set_font_size (cr, score->staff_height);
266 /* XXX: The hard-coded glyph indices here are very ugly. We should
267 * figure out how to lookup glyphs by name from this font. */
268 switch (PITCH_ACCIDENTAL (note->pitch)) {
269 case PITCH_ACCIDENTAL_DOUBLE_FLAT:
270 note_glyph[num_glyphs].index = 77;
272 case PITCH_ACCIDENTAL_FLAT:
273 note_glyph[num_glyphs].index = 68;
275 case PITCH_ACCIDENTAL_NATURAL:
276 note_glyph[num_glyphs].index = 101;
278 case PITCH_ACCIDENTAL_SHARP:
279 note_glyph[num_glyphs].index = 134;
281 case PITCH_ACCIDENTAL_DOUBLE_SHARP:
282 note_glyph[num_glyphs].index = 142;
286 if (PITCH_ACCIDENTAL (note->pitch) != PITCH_ACCIDENTAL_NATURAL)
288 note_glyph[num_glyphs].x = 0;
290 note_glyph[num_glyphs].y = score->space_height * line;
294 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
296 #define ACCIDENTAL_NOTE_SPACING (score->space_height * .15)
298 note_glyph[0].x = - (extents.width + ACCIDENTAL_NOTE_SPACING);
301 switch (note->duration) {
302 case SCORE_DURATION_1:
303 note_glyph[num_glyphs].index = 127;
305 case SCORE_DURATION_2:
306 note_glyph[num_glyphs].index = 85;
308 case SCORE_DURATION_4:
309 case SCORE_DURATION_8:
310 case SCORE_DURATION_16:
311 case SCORE_DURATION_32:
312 case SCORE_DURATION_64:
313 case SCORE_DURATION_128:
315 note_glyph[num_glyphs].index = 84;
318 note_glyph[num_glyphs].x = 0;
319 note_glyph[num_glyphs].y = score->space_height * line;
323 if (line < 0 || line > 4) {
324 double offset, width;
327 cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents);
328 offset = note_glyph[0].x + extents.x_bearing;
329 width = extents.width;
332 for (i = -1; i >= line; i--)
333 _draw_ledger_line (i, offset, width);
335 for (i = 5; i <= line; i++)
336 _draw_ledger_line (i, offset, width);
340 cairo_set_source_rgb (cr,
344 cairo_show_glyphs (cr, note_glyph, num_glyphs);
350 _draw_staff (score_t *score, cairo_t *cr,
351 score_staff_t *staff, int staff_width)
354 cairo_glyph_t clef_glyph;
358 cairo_translate (cr, 0, staff->y_pos);
360 cairo_select_font_face (cr, "Gonville-26", 0, 0);
362 cairo_set_font_size (cr, score->staff_height);
364 /* XXX: The hard-coded glyph indices here are very ugly. We should
365 * figure out how to lookup glyphs by name from this font. */
366 switch (staff->clef) {
369 clef_glyph.index = 46;
370 clef_glyph.y = 3 * score->space_height;
373 clef_glyph.index = 45;
374 clef_glyph.y = 1 * score->space_height;
377 clef_glyph.x = 3 * score->line_width;
378 clef_glyph.y += score->line_width / 2.0;
380 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
381 cairo_show_glyphs (cr, &clef_glyph, 1);
383 /* Draw staff lines */
384 for (i = 0; i < 5; i++) {
385 cairo_move_to (cr, 0, i * score->space_height + score->line_width / 2.0);
386 cairo_rel_line_to (cr, staff_width, 0);
389 cairo_set_line_width (cr, score->line_width);
391 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
394 /* Make space for clef before drawing notes */
395 cairo_translate (cr, (int) (4 * score->space_height), 0);
397 /* Draw chord symbols */
400 for (i = 0; i < staff->num_chords; i++) {
401 _draw_chord (score, cr, staff, staff->chords[i]);
402 cairo_translate (cr, staff->chords[i]->width, 0.0);
408 for (i = 0; i < staff->num_notes; i++) {
409 _draw_note (score, cr, staff, staff->notes[i]);
410 /* Draw all notes concurrent for now (as a chord)
411 cairo_translate (cr, score->space_height * 2.0, 0);
419 score_draw (score_t *score, cairo_t *cr)
422 int staff_width = score->width;
425 if (score->num_staves == 0)
430 /* Before drawing anything, position each staff based on the size
431 * of each (including ledger lines) */
433 for (i = 0; i < score->num_staves; i++) {
434 score_staff_t *staff = score->staves[i];
435 staff_y_pos += staff->upper_ledger_lines * score->space_height;
436 staff->y_pos = staff_y_pos;
437 staff_y_pos += (score->staff_height +
438 staff->lower_ledger_lines * score->space_height +
439 score->staff_height);
442 if (score->num_braces)
444 /* Initialize to keep the compiler quiet. */
447 for (i = 0; i < score->num_braces; i++)
448 _draw_brace (score, cr, score->braces[i], &brace_width);
450 /* Subtract space for brace itself */
451 cairo_translate (cr, brace_width, 0);
452 staff_width -= brace_width;
454 /* As well as some padding */
455 cairo_translate (cr, 2, 0);
459 /* Vertical lines at each end */
461 score->line_width / 2.0,
462 score->staves[0]->y_pos + score->line_width / 2.0,
463 staff_width - score->line_width,
464 score->staves[score->num_staves-1]->y_pos + score->staff_height - score->staves[0]->y_pos);
465 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
466 cairo_set_line_width (cr, score->line_width);
469 for (i = 0; i < score->num_staves; i++) {
470 score_staff_t *staff = score->staves[i];
471 _draw_staff (score, cr, staff, staff_width);
478 score_add_brace (score_t *score, int staves)
480 score_brace_t *brace;
482 brace = talloc (score, score_brace_t);
486 brace->first_staff = score->num_staves;
487 brace->num_staves = staves;
490 score->braces = talloc_realloc (score,
494 if (score->braces == NULL) {
495 score->num_braces = 0;
499 score->braces[score->num_braces - 1] = brace;
504 score_add_staff (score_t *score, score_clef_t clef)
506 score_staff_t *staff;
508 staff = talloc (score, score_staff_t);
515 staff->num_notes = 0;
517 staff->chords = NULL;
518 staff->num_chords = 0;
520 staff->upper_ledger_lines = 0;
521 staff->lower_ledger_lines = 0;
524 score->staves = talloc_realloc (score,
528 if (score->staves == NULL) {
529 score->num_staves = 0;
533 score->staves[score->num_staves - 1] = staff;
539 score_add_chord (score_staff_t *staff,
542 score_chord_t *chord;
544 chord = talloc (staff, score_chord_t);
548 talloc_steal (chord, name);
550 chord->staff = staff;
551 chord->name = talloc_strdup (chord, name);
553 /* The width will get set correctly the first time _draw_chord is
558 staff->chords = talloc_realloc (staff,
562 if (staff->chords == NULL) {
563 staff->num_chords = 0;
567 staff->chords[staff->num_chords - 1] = chord;
573 score_remove_chord (score_chord_t *chord)
575 score_staff_t *staff = chord->staff;
578 for (i = 0; i < staff->num_chords; i++)
579 if (staff->chords[i] == chord)
582 if (i == staff->num_chords)
585 if (i < staff->num_chords - 1)
587 memmove (staff->chords + i,
588 staff->chords + i + 1,
589 (staff->num_chords - 1 - i) * sizeof (score_chord_t *));
592 staff->num_chords -= 1;
596 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->octave == octave &&
610 note->duration == duration)
616 note = talloc (staff, score_note_t);
622 note->octave = octave;
623 note->duration = duration;
629 line = _score_note_to_line (staff, note);
631 int lines = (int) (- line);
632 if (lines > staff->upper_ledger_lines)
633 staff->upper_ledger_lines = lines;
635 int lines = (int) (line - 4);
636 if (lines > staff->lower_ledger_lines)
637 staff->lower_ledger_lines = lines;
641 staff->notes = talloc_realloc (staff,
645 if (staff->notes == NULL) {
646 staff->num_notes = 0;
650 staff->notes[staff->num_notes - 1] = note;
656 score_remove_note (score_note_t *note)
658 score_staff_t *staff = note->staff;
661 for (i = 0; i < staff->num_notes; i++)
662 if (staff->notes[i] == note)
665 if (i == staff->num_notes)
668 if (i < staff->num_notes - 1)
670 memmove (staff->notes + i,
671 staff->notes + i + 1,
672 (staff->num_notes - 1 - i) * sizeof (score_note_t *));
675 staff->num_notes -= 1;
677 if (staff->num_notes == 0) {
678 staff->upper_ledger_lines = 0;
679 staff->lower_ledger_lines = 0;
684 score_set_note_color_rgb (score_note_t *note,
695 score_staff_find_note (score_staff_t *staff,
698 score_duration_t duration)
703 for (i = 0; i < staff->num_notes; i++) {
704 note = staff->notes[i];
705 if (note->pitch == pitch &&
706 note->octave == octave &&
707 note->duration == duration)