/* music_utils.c * * Copyright James Allwright 2020 * * This file provides basic functions for manipulating music * * 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 2 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 */ #include #include #include #include "music_utils.h" typedef struct clef_item { char *name; basic_cleftype_t basic_clef; int staveline; int octave_offset; } clef_item_t; /* entries are name, basic_clef, staveline, octave_offset */ static const clef_item_t clef_conversion_table[] = { {"treble", basic_clef_treble, 2, 0}, {"alto", basic_clef_alto, 3, 0}, {"bass", basic_clef_bass, 4, 0}, {"soprano", basic_clef_alto, 1, 0}, {"mezzosoprano", basic_clef_alto, 2, 0}, {"tenor", basic_clef_alto, 4, 0}, {"baritone", basic_clef_bass, 0, 0}, }; #define NUM_CLEFS (sizeof(clef_conversion_table)/sizeof(clef_item_t)) /* The following 3 are not really clefs, but abc standard * 2.2 allows them. We treat them as treble clef and only * allow them after clef= . This ensures that K:none is not * interpreted as a clef instead of a key signature. * * the clef defines how a pitch value maps to a y position * on the stave. If there is a clef of "none", then you don't * know where to put the notes! */ static const clef_item_t odd_clef_conversion_table[] = { {"auto", basic_clef_treble, 2, 0}, {"perc", basic_clef_treble, 2, 0}, {"none", basic_clef_treble, 2, 0} }; #define NUM_ODD_CLEFS (sizeof(odd_clef_conversion_table)/sizeof(clef_item_t)) /* array giving notes in the order used by key_acc array * This can be used to implement a lookup function which is * the reverse of note_index(). */ const char note_array[] = "cdefgab"; /* These are musical modes. Each specifies a scale of notes starting on a * particular tonic note. Effectively the notes are the same as used in a * major scale, but the starting note is different. This means that each * mode can be notated using the standard key signatures used for major * keys. The abc standard specifies that only the first 3 characters of * the mode are significant, and further that "minor" can be abbreviated * as "m" and "major" omitted altogether. The full names are: * Major, Minor, Aeolian, Locrian, Ionian, Dorian, Phyrgian, Lydian and * Mixolydian. In addition, we have "exp" short for "explicit" to indicate * that arbitrary accidentals can be applied to each stave line in the * key signature and "" the empty string to represent "major" where this * is inferred as the default value rather than being supplied. */ const char *mode[12] = { "maj", "min", "m", "aeo", "loc", "ion", "dor", "phr", "lyd", "mix", "exp", "" }; /* This is a table for finding the sharps/flats representation of * a key signature for a given mode. * Suppose we want to find the sharps/flats representation for * K:GDor * If we know the major mode key K:G is 1 sharp, and have the index of the * mode we want within the mode table, we can work out the new key * signature as follows: * * Original major mode key signature +1 (1 sharp) * Desired new mode Dorian is at position 6 in table * modeshift[6] is -2. * new key signature is 1 - 2 = -1 (1 flat) * GDor is 1 flat. */ const int modeshift[12] = { 0, -3, -3, -3, -5, 0, -2, -4, 1, -1, 0, 0 }; /* convert a note a-g to an index into the key signature arrays * src.key_acc, target.key_acc, src_key_mult, target_key_mult */ noteletter_t note_index (char note_ch) { switch (note_ch) { case 'c': case 'C': return note_c; case 'd': case 'D': return note_d; case 'e': case 'E': return note_e; case 'f': case 'F': return note_f; case 'g': case 'G': return note_g; case 'a': case 'A': return note_a; case 'b': case 'B': return note_b; default: printf ("Internal error: note_index called with bad value %c\n", note_ch); exit (1); } } /* convert a letter a - g into a note value in semitones */ int semitone_value_for_note (noteletter_t note) { switch (note) { case note_c: return 0; case note_d: return 2; case note_e: return 4; case note_f: return 5; case note_g: return 7; case note_a: return 9; case note_b: return 11; default: printf ("Internal error: Unexpected note %d\n", note); return 0; } } /* convert an accidental indicator into a semitone shift */ int semitone_shift_for_acc (char acc) { switch (acc) { case '_': case 'b': return -1; case '^': case '#': return 1; default: return 0; } } /* Given a semitone value 0 - 11, convert to note + accidental. * This function always returns sharp as the accidental */ void note_for_semitone (int semitones, noteletter_t * note, char *accidental) { char note_for_semi[12] = "CCDDEFFGGAAB"; char acc_for_semi[12] = " # # # # # "; if (semitones < 0) { semitones = -(-semitones) % 12 + 12; } if (semitones > 11) { semitones = semitones % 12; } *note = note_index (note_for_semi[semitones]); *accidental = acc_for_semi[semitones]; } /* look for any octave shift specified after a clef name */ static int get_clef_octave_offset (char *clef_ending) { if (strncmp (clef_ending, "+8", 2) == 0) { return 1; } if (strncmp (clef_ending, "+15", 2) == 0) { return 2; } if (strncmp (clef_ending, "-8", 2) == 0) { return -1; } if (strncmp (clef_ending, "-15", 2) == 0) { return -2; } return 0; } void init_new_clef (cleftype_t * new_clef) { new_clef->basic_clef = basic_clef_undefined; new_clef->staveline = 0; new_clef->octave_offset = 0; new_clef->named = 0; } /* copy contents of cleftype_t structure */ void copy_clef (cleftype_t * target_clef, cleftype_t * source_clef) { target_clef->basic_clef = source_clef->basic_clef; target_clef->staveline = source_clef->staveline; target_clef->octave_offset = source_clef->octave_offset; target_clef->named = source_clef->named; } /* use lookup table to get details of clef from it's name string */ //int get_standard_clef (char *name, basic_cleftype_t * basic_clef, // int *staveline, int *octave_offset) int get_standard_clef (char *name, cleftype_t * new_clef) { int i; int len; for (i = 0; i < NUM_CLEFS; i++) { const clef_item_t *table_row = &clef_conversion_table[i]; len = strlen (table_row->name); if (strncmp (name, table_row->name, len) == 0) { new_clef->basic_clef = table_row->basic_clef; new_clef->staveline = table_row->staveline; new_clef->named = 1; new_clef->octave_offset = get_clef_octave_offset (name + len); return 1; /* lookup succeeded */ } } return 0; } /* look for a clef using C, F or G and a number 1 - 5 or * one of the specials (none, perc, auto) */ //int get_extended_clef_details (char *name, basic_cleftype_t * basic_clef, // int *staveline, int *octave_offset) int get_extended_clef_details (char *name, cleftype_t * new_clef) { int i; int len; int num; int items_read; for (i = 0; i < NUM_ODD_CLEFS; i++) { const clef_item_t *table_row = &odd_clef_conversion_table[i]; len = strlen (table_row->name); if (strncmp (name, table_row->name, len) == 0) { new_clef->basic_clef = table_row->basic_clef; new_clef->staveline = table_row->staveline; new_clef->octave_offset = table_row->octave_offset; new_clef->named = 1; new_clef->octave_offset = get_clef_octave_offset (name + len); return 1; /* lookup succeeded */ } } new_clef->octave_offset = 0; /* try [C/F/G]{1-5] format */ switch (name[0]) { case 'C': new_clef->basic_clef = basic_clef_alto; break; case 'F': new_clef->basic_clef = basic_clef_bass; break; case 'G': new_clef->basic_clef = basic_clef_treble; break; default: return 0; /* not recognized */ } items_read = sscanf (&name[1], "%d", &num); if ((items_read == 1) && (num >= 1) && (num <= 5)) { /* we have a valid clef specification */ new_clef->staveline = num; new_clef->named = 0; new_clef->octave_offset = get_clef_octave_offset (name + 2); return 1; } return 0; } static void append_octave_offset(char * name, int octave_offset) { switch (octave_offset) { case -2: strcat(name, "-15"); break; case -1: strcat(name, "-8"); break; case 1: strcat(name, "+8"); break; case 2: strcat(name, "+15"); break; default: break; } } /* given clef basic type and staveline, we try to work out it's name */ int get_clef_name (cleftype_t * new_clef, char *name) { int i; if (new_clef->named) { for (i = 0; i < NUM_CLEFS; i++) { if ((clef_conversion_table[i].basic_clef == new_clef->basic_clef) && (clef_conversion_table[i].staveline == new_clef->staveline)) { strcpy (name, clef_conversion_table[i].name); append_octave_offset(name, new_clef->octave_offset); return 1; /* lookup succeeded */ } } } switch (new_clef->basic_clef) { default: case basic_clef_undefined: case basic_clef_none : return 0; case basic_clef_auto : strcpy(name, "auto"); return 1; case basic_clef_perc : strcpy(name, "perc"); return 1; case basic_clef_treble: name[0] = 'G'; break; case basic_clef_bass: name[0] = 'F'; break; case basic_clef_alto: name[0] = 'C'; break; } name[1] = '0' + new_clef->staveline; name[2] = '\0'; append_octave_offset(name, new_clef->octave_offset); return 1; }