X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=score.c;h=7a1c231a4b82cfdcef0d972dcf88ac6c1168e645;hb=f6b8f7f23bb8a4a5b30bfde8242578cca90ddacf;hp=4f0efdd2f8ba7dcc4aab9a191ce32cdd73a8a5db;hpb=66812a216aa78f33ddff8753dd62f8450ef3220a;p=scherzo diff --git a/score.c b/score.c index 4f0efdd..7a1c231 100644 --- a/score.c +++ b/score.c @@ -20,40 +20,385 @@ #include "score.h" -void -score_init (score_t *score) +struct score_staff +{ + score_clef_t clef; + + score_note_t **notes; + int num_notes; +}; + +typedef struct score_brace +{ + int first_staff; + int num_staves; +} score_brace_t; + +struct score +{ + /* Height of a single staff */ + int staff_height; + + /* Height of one space within a staff */ + int space_height; + + /* Minimal line width for staff lines */ + int line_width; + + /* Full width of staff */ + int width; + + score_brace_t **braces; + int num_braces; + int brace_width; + + score_staff_t **staves; + int num_staves; +}; + +typedef struct score_note +{ + score_pitch_t pitch; + int octave; + score_duration_t duration; +} score_note_t; + +score_t * +score_create (void *ctx) { - score->space_height = 6; + score_t *score; + + score = talloc (ctx, score_t); + if (score == NULL) + return NULL; + + /* Also sets space_height and line_width */ + score_set_staff_height (score, 24); + + /* Just to have some nominal width. */ + score->width = 800; + + score->braces = NULL; + score->num_braces = 0; + + score->staves = NULL; + score->num_staves = 0; + + return score; +} + +int +score_set_staff_height (score_t *score, int height) +{ + score->space_height = (int) height / 4; + score->staff_height = score->space_height * 4; + + score->line_width = score->space_height / 10; + if (score->line_width == 0) + score->line_width = 1; + + return score->staff_height; } void -_draw_staff (score_t *score, cairo_t *cr) +score_set_width (score_t *score, int width) +{ + score->width = width; +} + +/* Returns in brace_width the width of the brace */ +static void +_draw_brace (score_t *score, cairo_t *cr, + score_brace_t *brace, int *brace_width) +{ + cairo_glyph_t brace_glyph; + cairo_text_extents_t brace_extents; + + cairo_save (cr); + + cairo_select_font_face (cr, "Gonville-Brace", 0, 0); + + /* XXX: This hard-coded glyph index is pretty ugly. We should + * figure out how to lookup the glyph we want, (though, as it + * turns out, this brace font pretty much just has numbered glyph + * names for different sizes, so it wouldn't be all that different + * than just the bare index here). */ + brace_glyph.index = 300; + brace_glyph.x = 0; + brace_glyph.y = score->staff_height * (brace->first_staff + (2 * brace->num_staves - 1) / 2.0) + 1; + + /* XXX: This font size (in conjunction with the glyph selection) + * is a rough guess at best. We should figure out how the brace + * font is intended to be used and actually measure to find the + * correctly-sized glyph. */ + cairo_set_font_size (cr, (score->staff_height * 3) / 3.85); + + cairo_glyph_extents (cr, &brace_glyph, 1, &brace_extents); + + /* Subtract space for brace itself */ + cairo_translate (cr, -brace_extents.x_bearing, 0); + + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ + cairo_show_glyphs (cr, &brace_glyph, 1); + + cairo_restore (cr); + + *brace_width = (int) -brace_extents.x_bearing; +} + +/* Line containing middle C for the given clef. */ +static int +_score_clef_c_line (score_clef_t clef) +{ + switch (clef) + { + default: + case SCORE_CLEF_G: + return 5; + case SCORE_CLEF_F: + return -1; + } +} + +static double +_score_note_to_line (score_staff_t *staff, score_note_t *note) +{ + score_pitch_name_t name = SCORE_PITCH_NAME (note->pitch); + int c_line = _score_clef_c_line (staff->clef); + + return c_line - (name - SCORE_PITCH_NAME_C) / 2.0 - 3.5 * (note->octave - 4); +} + +static void +_draw_note (score_t *score, cairo_t *cr, + score_staff_t *staff, score_note_t *note) +{ + double line; + cairo_glyph_t note_glyph; + + cairo_save (cr); + + /* Which line should the note appear on? Line 0 is the top line of + * the staff and increasing downwards. (Negative values indicate a + * note on a ledger line above the staff). Values half way between + * integers indicate notes appearing on a space between two staff + * lines (or ledger lines). */ + line = _score_note_to_line (staff, note); + + cairo_select_font_face (cr, "Gonville-26", 0, 0); + cairo_set_font_size (cr, score->staff_height); + + /* XXX: The hard-coded glyph indices here are very ugly. We should + * figure out how to lookup glyphs by name from this font. */ + switch (note->duration) { + case SCORE_DURATION_1: + note_glyph.index = 127; + break; + case SCORE_DURATION_2: + note_glyph.index = 85; + break; + case SCORE_DURATION_4: + case SCORE_DURATION_8: + case SCORE_DURATION_16: + case SCORE_DURATION_32: + case SCORE_DURATION_64: + case SCORE_DURATION_128: + default: + note_glyph.index = 84; + } + + note_glyph.x = 0; + note_glyph.y = score->space_height * line + score->line_width / 2.0; + + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ + cairo_show_glyphs (cr, ¬e_glyph, 1); + + cairo_restore (cr); +} + +static void +_draw_staff (score_t *score, cairo_t *cr, + score_staff_t *staff, int staff_width) { int i; + cairo_glyph_t clef_glyph; + cairo_text_extents_t clef_extents; cairo_save (cr); + cairo_select_font_face (cr, "Gonville-26", 0, 0); + + cairo_set_font_size (cr, score->staff_height); + + /* XXX: The hard-coded glyph indices here are very ugly. We should + * figure out how to lookup glyphs by name from this font. */ + switch (staff->clef) { + case SCORE_CLEF_G: + default: + clef_glyph.index = 46; + clef_glyph.y = 3 * score->space_height; + break; + case SCORE_CLEF_F: + clef_glyph.index = 45; + clef_glyph.y = 1 * score->space_height; + break; + } + clef_glyph.x = 3 * score->line_width; + clef_glyph.y += 1; + + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ + cairo_show_glyphs (cr, &clef_glyph, 1); + + cairo_glyph_extents (cr, &clef_glyph, 1, &clef_extents); + + /* Draw staff lines */ for (i = 0; i < 5; i++) { - cairo_move_to (cr, 0, i * score->space_height + 0.5); - cairo_rel_line_to (cr, score->width, 0); + cairo_move_to (cr, 0, i * score->space_height + score->line_width / 2.0); + cairo_rel_line_to (cr, staff_width, 0); } - cairo_set_line_width (cr, 1.0); + cairo_set_line_width (cr, score->line_width); cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ cairo_stroke (cr); + /* Make space for clef before drawing notes */ + cairo_translate (cr, (int) (1.5 * clef_extents.width), 0); + + /* Draw notes */ + for (i = 0; i < staff->num_notes; i++) { + _draw_note (score, cr, staff, staff->notes[i]); + cairo_translate (cr, score->space_height * 2.0, 0); + } + cairo_restore (cr); } void -score_set_width (score_t *score, int width) +score_draw (score_t *score, cairo_t *cr) { - score->width = width; + int i; + int staff_width = score->width; + + cairo_save (cr); + + if (score->num_braces) + { + int brace_width; + + for (i = 0; i < score->num_braces; i++) + _draw_brace (score, cr, score->braces[i], &brace_width); + + /* Subtract space for brace itself */ + cairo_translate (cr, brace_width, 0); + staff_width -= brace_width; + + /* As well as some padding */ + cairo_translate (cr, 2, 0); + staff_width -= 2; + } + + /* Vertical lines at each end */ + cairo_rectangle (cr, + score->line_width / 2.0, + score->line_width / 2.0, + staff_width - score->line_width, + score->staff_height * 3); + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ + cairo_set_line_width (cr, score->line_width); + cairo_stroke (cr); + + for (i = 0; i < score->num_staves; i++) { + _draw_staff (score, cr, score->staves[i], staff_width); + cairo_translate (cr, 0, 2 * score->staff_height); + } + + cairo_restore (cr); } void -score_draw (score_t *score, cairo_t *cr) +score_add_brace (score_t *score, int staves) +{ + score_brace_t *brace; + + brace = talloc (score, score_brace_t); + if (brace == NULL) + return; + + brace->first_staff = score->num_staves; + brace->num_staves = staves; + + score->num_braces++; + score->braces = talloc_realloc (score, + score->braces, + score_brace_t*, + score->num_braces); + if (score->braces == NULL) { + score->num_braces = 0; + return; + } + + score->braces[score->num_braces - 1] = brace; + +} + +score_staff_t * +score_add_staff (score_t *score, score_clef_t clef) +{ + score_staff_t *staff; + + staff = talloc (score, score_staff_t); + if (staff == NULL) + return NULL; + + staff->clef = clef; + + staff->notes = NULL; + staff->num_notes = 0; + + score->num_staves++; + score->staves = talloc_realloc (score, + score->staves, + score_staff_t*, + score->num_staves); + if (score->staves == NULL) { + score->num_staves = 0; + return NULL; + } + + score->staves[score->num_staves - 1] = staff; + + return staff; +} + +score_note_t * +score_staff_add_note (score_staff_t *staff, + score_pitch_t pitch, + int octave, + score_duration_t duration) { - _draw_staff (score, cr); + score_note_t *note; + + note = talloc (staff, score_note_t); + if (note == NULL) + return NULL; + + note->pitch = pitch; + note->octave = octave; + note->duration = duration; + + staff->num_notes++; + staff->notes = talloc_realloc (staff, + staff->notes, + score_note_t*, + staff->num_notes); + if (staff->notes == NULL) { + staff->num_notes = 0; + return NULL; + } + + staff->notes[staff->num_notes - 1] = note; + + return note; } +