/* 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;
}