mirror of
https://github.com/sshlien/abcmidi.git
synced 2025-12-06 15:05:07 +00:00
362 lines
10 KiB
C
362 lines
10 KiB
C
/* 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 <https://www.gnu.org/licenses/>
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#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;
|
|
}
|