From: Carl Worth Date: Tue, 1 Oct 2013 15:18:43 +0000 (-0700) Subject: Add support for drawing a key signature X-Git-Url: https://git.cworth.org/git?p=scherzo;a=commitdiff_plain;h=54d2b1cb13bf52df22c93e58377a00b757644b94 Add support for drawing a key signature This works correctly for treble and bass clefs and up to 7 sharps or 7 flats. The key does not yet influence the way notes are drawn, (which accidentals are displayed), nor yet how chord names are determined. --- diff --git a/Makefile b/Makefile index c1ee124..b1a4d34 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ all: scherzo scherzo_srcs := \ mnemon/mnemon.c \ + pitch.c \ scherzo.c \ score.c @@ -12,7 +13,7 @@ WARN_CFLAGS = -Wall -Wextra -Wmissing-declarations SCHERZO_CFLAGS = $(CFLAGS) $(WARN_CFLAGS) $(CONFIGURE_CFLAGS) $(extra_cflags) -I./mnemon `pkg-config --cflags gtk+-2.0 alsa` -SCHERZO_LDFLAGS = $(LDFLAGS) `pkg-config --libs gtk+-2.0 alsa` -ltalloc +SCHERZO_LDFLAGS = $(LDFLAGS) `pkg-config --libs gtk+-2.0 alsa` -ltalloc -lm # The user has not set any verbosity, default to quiet mode and inform the # user how to enable verbose compiles. diff --git a/pitch.c b/pitch.c new file mode 100644 index 0000000..c628a76 --- /dev/null +++ b/pitch.c @@ -0,0 +1,98 @@ +/* scherzo - Music notation training + * + * pitch.c - Common structures and functions for pitches, etc. + * + * Copyright © 2013 Carl Worth + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/ . + */ + +#include "pitch.h" + +const char * +pitch_string (pitch_t pitch) +{ + static char double_flat[] = "X𝄫"; + static char flat[] = "X♭"; + static char natural[] = "X"; + static char sharp[] = "X♯"; + static char double_sharp[] = "X𝄪"; + char *ret; + + switch (PITCH_ACCIDENTAL (pitch)) { + case PITCH_ACCIDENTAL_DOUBLE_FLAT: + ret = double_flat; + break; + case PITCH_ACCIDENTAL_FLAT: + ret = flat; + break; + case PITCH_ACCIDENTAL_NATURAL: + ret = natural; + break; + case PITCH_ACCIDENTAL_SHARP: + ret = sharp; + break; + case PITCH_ACCIDENTAL_DOUBLE_SHARP: + ret = double_sharp; + break; + } + + switch (PITCH_NAME (pitch)) { + case PITCH_NAME_C: + ret[0] = 'C'; + break; + case PITCH_NAME_D: + ret[0] = 'D'; + break; + case PITCH_NAME_E: + ret[0] = 'E'; + break; + case PITCH_NAME_F: + ret[0] = 'F'; + break; + case PITCH_NAME_G: + ret[0] = 'G'; + break; + case PITCH_NAME_A: + ret[0] = 'A'; + break; + case PITCH_NAME_B: + ret[0] = 'B'; + break; + } + + return ret; +} + +pitch_t +pitch_raise_by_octaves (pitch_t pitch, int octaves) +{ + int new_octave = PITCH_OCTAVE (pitch) + octaves; + + if (new_octave > 8) + new_octave = 8; + + return PITCH (PITCH_NAME (pitch), PITCH_ACCIDENTAL (pitch), new_octave); +} + +pitch_t +pitch_lower_by_octaves (pitch_t pitch, int octaves) +{ + int new_octave = PITCH_OCTAVE (pitch) - octaves; + + if (new_octave < 0) + new_octave = 0; + + return PITCH (PITCH_NAME (pitch), PITCH_ACCIDENTAL (pitch), new_octave); +} diff --git a/pitch.h b/pitch.h index 657e8fe..4467aa8 100644 --- a/pitch.h +++ b/pitch.h @@ -54,6 +54,12 @@ typedef uint32_t pitch_t; #define PITCH_NATURAL(literal_name, octave) \ PITCH_LITERAL(literal_name, NATURAL, octave) +/* PITCH_CLASS is useful for comparing pitches while ignoring any octave. */ +#define PITCH_CLASS(name, accidental) PITCH(name, accidental, 0) + +#define PITCH_CLASS_LITERAL(literal_name, literal_accidental) \ + PITCH_LITERAL(literal_name, literal_accidental, 0) + typedef enum pitch_accidental { PITCH_ACCIDENTAL_DOUBLE_FLAT, @@ -78,4 +84,28 @@ typedef enum pitch_name * middle C to the B above middle C). */ +/* Get a string representation of a pitch_t value. + * + * Note: The returned value is static to the pithc_string function and + * may be modified by future calls to pitch_string. So the caller + * should copy the value if needed. */ +const char * +pitch_string (pitch_t pitch); + +/* Return a new pitch, a number of octaves above 'pitch' + * + * Note: Maximum octave value is 8. A pitch with octave 8 will not be + * raised. + */ +pitch_t +pitch_raise_by_octaves (pitch_t pitch, int octaves); + +/* Return a new pitch, a number of octaves below 'pitch' + * + * Note: Minimum octave value is 9. A pitch with octave 8 will not be + * lowered. + */ +pitch_t +pitch_lower_by_octaves (pitch_t pitch, int octaves); + #endif /* PITCH_H */ diff --git a/scherzo.c b/scherzo.c index 6a5f2b3..e8d0d27 100644 --- a/scherzo.c +++ b/scherzo.c @@ -78,6 +78,8 @@ typedef struct scherzo pitch_group_t notes_pedaled; int pedal_pressed; + + pitch_t key; } scherzo_t; /* Forward declarations. */ @@ -561,61 +563,6 @@ _chord_signature_matches (analyzed_pitch_t *pitches, return 1; } -static const char * -_pitch_str (pitch_t pitch) -{ - static char double_flat[] = "X𝄫"; - static char flat[] = "X♭"; - static char natural[] = "X"; - static char sharp[] = "X♯"; - static char double_sharp[] = "X𝄪"; - char *ret; - - switch (PITCH_ACCIDENTAL (pitch)) { - case PITCH_ACCIDENTAL_DOUBLE_FLAT: - ret = double_flat; - break; - case PITCH_ACCIDENTAL_FLAT: - ret = flat; - break; - case PITCH_ACCIDENTAL_NATURAL: - ret = natural; - break; - case PITCH_ACCIDENTAL_SHARP: - ret = sharp; - break; - case PITCH_ACCIDENTAL_DOUBLE_SHARP: - ret = double_sharp; - break; - } - - switch (PITCH_NAME(pitch)) { - case PITCH_NAME_C: - ret[0] = 'C'; - break; - case PITCH_NAME_D: - ret[0] = 'D'; - break; - case PITCH_NAME_E: - ret[0] = 'E'; - break; - case PITCH_NAME_F: - ret[0] = 'F'; - break; - case PITCH_NAME_G: - ret[0] = 'G'; - break; - case PITCH_NAME_A: - ret[0] = 'A'; - break; - case PITCH_NAME_B: - ret[0] = 'B'; - break; - } - - return ret; -} - typedef struct chord_signature { int num_degrees; @@ -1115,7 +1062,7 @@ scherzo_press_note (scherzo_t *scherzo, pitch_t pitch) signature, *root); chord_name = talloc_asprintf (local, "%s%s", - _pitch_str (*root), + pitch_string (*root), signature->name); } } else if (chord->num_pitches > 2) { @@ -1409,6 +1356,9 @@ main (int argc, char *argv[]) scherzo.pedal_pressed = 0; + scherzo.key = PITCH_CLASS_LITERAL (C, NATURAL); + score_set_key (scherzo.score, scherzo.key); + mnemon_init (&scherzo.mnemon); /* XXX: Should create a default file if one cannot be loaded. */ mnemon_load_category (&scherzo.mnemon, "scherzo-notes"); diff --git a/score.c b/score.c index ecfc235..0a6016b 100644 --- a/score.c +++ b/score.c @@ -25,6 +25,8 @@ #include "score.h" +#define ARRAY_SIZE(arr) ((int) (sizeof(arr) / sizeof(arr[0]))) + typedef struct score_note { score_staff_t *staff; @@ -76,6 +78,9 @@ struct score /* Full width of staff */ int width; + /* the pitch class of the current (diatonic) key */ + pitch_t key; + score_brace_t **braces; int num_braces; int brace_width; @@ -99,6 +104,9 @@ score_create (void *ctx) /* Just to have some nominal width. */ score->width = 1000; + /* Default to C, of course */ + score->key = PITCH_CLASS_LITERAL (C, NATURAL); + score->braces = NULL; score->num_braces = 0; @@ -127,6 +135,12 @@ score_set_width (score_t *score, int width) score->width = width; } +void +score_set_key (score_t *score, pitch_t key) +{ + score->key = key; +} + /* Returns in brace_width the width of the brace */ static void _draw_brace (score_t *score, cairo_t *cr, @@ -255,7 +269,12 @@ _draw_chord (score_t *score, cairo_t *cr, cairo_restore (cr); } -static void +/* Draw 'note' with accidental (if not NATURAL) at its correct + * position on 'staff'. + * + * Returns the width of the drawn glyphs. + */ +static double _draw_note (score_t *score, cairo_t *cr, score_staff_t *staff, score_note_t *note) { @@ -324,33 +343,38 @@ _draw_note (score_t *score, cairo_t *cr, note_glyph[0].x = - (extents.width + ACCIDENTAL_NOTE_SPACING); } - switch (note->duration) { - case SCORE_DURATION_1: - note_glyph[num_glyphs].index = 127; - break; - case SCORE_DURATION_2: - note_glyph[num_glyphs].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[num_glyphs].index = 84; - } + /* Support duration == 0 to draw accidental only */ + if (note->duration) + { + switch (note->duration) { + case SCORE_DURATION_1: + note_glyph[num_glyphs].index = 127; + break; + case SCORE_DURATION_2: + note_glyph[num_glyphs].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[num_glyphs].index = 84; + } + + note_glyph[num_glyphs].x = 0; + note_glyph[num_glyphs].y = score->space_height * line; - note_glyph[num_glyphs].x = 0; - note_glyph[num_glyphs].y = score->space_height * line; + num_glyphs++; + } - num_glyphs++; + cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents); if (line < 0 || line > 4) { double offset, width; int i; - cairo_glyph_extents (cr, note_glyph, num_glyphs, &extents); offset = note_glyph[0].x + extents.x_bearing; width = extents.width; @@ -370,6 +394,147 @@ _draw_note (score_t *score, cairo_t *cr, cairo_show_glyphs (cr, note_glyph, num_glyphs); cairo_restore (cr); + + return extents.width; +} + +/* Draw the accidental from 'pitch' only (no notehead) at the correct + * position as if drawing a note at 'pitch'. + * + * Returns the width of the drawn glyph. + */ +static double +_draw_accidental (score_t *score, + cairo_t *cr, + score_staff_t *staff, + pitch_t pitch) +{ + score_note_t note; + + note.staff = staff; + note.pitch = pitch; + + /* A duration of 0 indicates to draw only the accidental. */ + note.duration = 0; + + note.color.r = 0.0; + note.color.g = 0.0; + note.color.b = 0.0; + + return _draw_note (score, cr, staff, ¬e); +} + +static void +_draw_sharps_key_signature (score_t *score, cairo_t *cr, + score_staff_t *staff, int num_sharps) +{ + pitch_t pitch; + double width; + int i; + + /* These octave numbers are correct for treble clef. For bass + * clef, subtract two. + */ + pitch_t sharps_order[] = { + PITCH_LITERAL (F, SHARP, 5), + PITCH_LITERAL (C, SHARP, 5), + PITCH_LITERAL (G, SHARP, 5), + PITCH_LITERAL (D, SHARP, 5), + PITCH_LITERAL (A, SHARP, 4), + PITCH_LITERAL (E, SHARP, 5), + PITCH_LITERAL (B, SHARP, 4) + }; + + for (i = 0; i < num_sharps; i++) { + pitch = sharps_order[i]; + + if (staff->clef == SCORE_CLEF_BASS) + pitch = pitch_lower_by_octaves (pitch, 2); + + width = _draw_accidental (score, cr, staff, pitch); + +#define KEY_SIGNATURE_ACCIDENTAL_SPACING (score->space_height * .15) + cairo_translate (cr, ceil (width + KEY_SIGNATURE_ACCIDENTAL_SPACING), 0); + } +} + +static void +_draw_flats_key_signature (score_t *score, cairo_t *cr, + score_staff_t *staff, int num_flats) +{ + pitch_t pitch; + double width; + int i; + + /* These octave numbers are correct for treble clef. For bass + * clef, subtract two. + */ + pitch_name_t flats_order[] = { + PITCH_LITERAL (B, FLAT, 4), + PITCH_LITERAL (E, FLAT, 5), + PITCH_LITERAL (A, FLAT, 4), + PITCH_LITERAL (D, FLAT, 5), + PITCH_LITERAL (G, FLAT, 4), + PITCH_LITERAL (C, FLAT, 5), + PITCH_LITERAL (F, FLAT, 4), + }; + + for (i = 0; i < num_flats; i++) { + pitch = flats_order[i]; + + if (staff->clef == SCORE_CLEF_BASS) + pitch = pitch_lower_by_octaves (pitch, 2); + + width = _draw_accidental (score, cr, staff, pitch); + + cairo_translate (cr, ceil (width + KEY_SIGNATURE_ACCIDENTAL_SPACING), 0); + } +} + +static void +_draw_key_signature (score_t *score, cairo_t *cr, + score_staff_t *staff, pitch_t key) +{ + int i; + + pitch_t sharp_keys[] = { + PITCH_CLASS_LITERAL (C, NATURAL), + PITCH_CLASS_LITERAL (G, NATURAL), + PITCH_CLASS_LITERAL (D, NATURAL), + PITCH_CLASS_LITERAL (A, NATURAL), + PITCH_CLASS_LITERAL (E, NATURAL), + PITCH_CLASS_LITERAL (B, NATURAL), + PITCH_CLASS_LITERAL (F, SHARP), + PITCH_CLASS_LITERAL (C, SHARP), + }; + + pitch_t flat_keys[] = { + PITCH_CLASS_LITERAL (C, NATURAL), + PITCH_CLASS_LITERAL (F, NATURAL), + PITCH_CLASS_LITERAL (B, FLAT), + PITCH_CLASS_LITERAL (E, FLAT), + PITCH_CLASS_LITERAL (A, FLAT), + PITCH_CLASS_LITERAL (D, FLAT), + PITCH_CLASS_LITERAL (G, FLAT), + PITCH_CLASS_LITERAL (C, FLAT) + }; + + for (i = 0; i < ARRAY_SIZE (sharp_keys); i++) { + if (sharp_keys[i] == key) { + _draw_sharps_key_signature (score, cr, staff, i); + return; + } + } + + for (i = 0; i < ARRAY_SIZE (flat_keys); i++) { + if (flat_keys[i] == key) { + _draw_flats_key_signature (score, cr, staff, i); + return; + } + } + + fprintf (stderr, "Internal error: Not a key: %s\n", pitch_string (key)); + exit (1); } static void @@ -378,6 +543,7 @@ _draw_staff (score_t *score, cairo_t *cr, { int i; cairo_glyph_t clef_glyph; + cairo_text_extents_t clef_extents; cairo_save (cr); @@ -387,6 +553,19 @@ _draw_staff (score_t *score, cairo_t *cr, cairo_set_font_size (cr, score->staff_height); + /* Draw staff lines */ + for (i = 0; i < 5; i++) { + 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, score->line_width); + + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ + cairo_stroke (cr); + + /* Draw the clef */ + /* 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) { @@ -406,19 +585,15 @@ _draw_staff (score_t *score, cairo_t *cr, cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ cairo_show_glyphs (cr, &clef_glyph, 1); - /* Draw staff lines */ - for (i = 0; i < 5; i++) { - 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, score->line_width); + /* Make space for clef */ + cairo_glyph_extents (cr, &clef_glyph, 1, &clef_extents); - cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */ - cairo_stroke (cr); +#define CLEF_KEY_SIGNATURE_SPACING (score->space_height * .75) + cairo_translate (cr, ceil (clef_extents.width + + CLEF_KEY_SIGNATURE_SPACING), 0); - /* Make space for clef before drawing notes */ - cairo_translate (cr, (int) (4 * score->space_height), 0); + /* Draw the key signature */ + _draw_key_signature (score, cr, staff, score->key); /* Draw chord symbols */ cairo_save (cr); diff --git a/score.h b/score.h index e71b8ab..e96052a 100644 --- a/score.h +++ b/score.h @@ -84,6 +84,10 @@ score_set_staff_height (score_t *score, int height); void score_set_width (score_t *score, int width); +/* Set the key for this score */ +void +score_set_key (score_t *score, pitch_t key); + /* Add a brace to the score, connecting the given number of staves. * * The staves to be connected are those that will next be added to the