/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2020 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: May 2020 */ /* This file contains code for transposing notes and key signatures. */ #include "pmwhdr.h" #include "readhdr.h" static uschar tp_keytable[] = { /* A B C D E F G */ 15, 2,17,18, 5,20,14, /* natural */ 0, 0, 0, 0, 0, 6, 0, /* sharp */ 0, 1, 2, 3, 4, 5, 6, /* flat */ 36,23,30,39,26,33,34, /* minor */ 22, 0,24,25, 0,27,21, /* sharp minor */ 21,22,23,24,25,25,26}; /* flat minor */ /* Table of enharmonic keys; the first of each pair is a key that is never automatically selected (i.e. is not in the above table); the second is the equivalent. */ static uschar enh_keytable[] = { 16, 1, /* C$ = B% */ 9, 17, /* C# = D$ */ 12, 20, /* F# = G$ */ 35, 34, /* A$m = G#m */ 31, 39, /* D#m = E$m */ 28, 36, /* A#m = B$m */ 255 }; /* Marks end */ static uschar sharpable[] = { TRUE, TRUE, FALSE, TRUE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE }; static uschar flatable[] = { FALSE, TRUE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, TRUE }; static uschar dsharpable[] = { FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE }; static uschar dflatable[] = { TRUE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE }; static uschar naturalable[] = { TRUE, FALSE, TRUE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE }; static uschar *able[] = { NULL, dsharpable, flatable, dflatable, naturalable, sharpable }; static uschar tp_newacc[] = { ac_dflat, ac_flat, ac_natural, ac_sharp, ac_dsharp }; static uschar tp_forward_offset[] = { 2, 0, 4, 0, 5, 7, 0, 9, 0, 11, 0, 0 }; static uschar tp_forward_pitch[] = { 2, 0, 2, 0, 1, 2, 0, 2, 0, 2, 0, 1 }; static uschar tp_reverse_offset[] = { 11, 0, 0, 0, 2, 4, 0, 5, 0, 7, 0, 9 }; static uschar tp_reverse_pitch[] = { 1, 0, 2, 0, 2, 1, 0, 2, 0, 2, 0, 2 }; /************************************************* * Transpose key signature * *************************************************/ /* As well as returning the transposed key, we also set up the variable stave_transpose_letter to contain the number of letter changes that are required for any transposition. This has to be done even when the key signature is N, which means "key signature does not transpose". However, in this case the new key is discarded. This function is called even when there is no transposition (in which case it does nothing). This is not the same as a transposition of zero. Arguments: key key signature amount number of semitones (signed) Returns: new key signature */ int transpose_key(int key, int amount) { int i, j; int letterkey, newkey, usekey; trkeystr *k; keytransposestr *kt; if (amount == no_transpose) return key; /* Get within octave transposition */ for (j = amount; j < 0; j += 12) {} while (j > 11) j -= 12; /* Check for custom transpose instruction */ for (kt = main_keytranspose; kt != NULL; kt = kt->next) { if (kt->oldkey == key) break; } if (kt != NULL) { newkey = kt->newkeys[j]; if (newkey >= 0) { int x = kt->letterchanges[j]; if (amount > 0) x = abs(x); else if (amount < 0) x = -abs(x); stave_transpose_letter = x; stave_transpose_letter_is_auto = FALSE; return newkey; } } /* Cannot transpose custom key without instruction */ if (key >= X_key) { error_moan(ERR145, key - X_key + 1, amount); return key; } /* Transpose a standard key */ newkey = usekey = (key == N_key)? C_key : key; for (i = 0; i < j; i++) newkey = tp_keytable[newkey]; letterkey = newkey; /* See if there's been a transposed key request for the new key. */ k = main_transposedkeys; while (k != NULL) { if (k->oldkey == newkey) { newkey = k->newkey; break; } k = k->next; } /* If the new key has changed to an enharmonic key, use the forced key to compute the number of letter changes; otherwise use the default new key. This copes with the two different uses of "transposedkey": (a) to use an enharmonic key and (b) to print music with a key signature different to the tonality. */ if (letterkey != newkey) { uschar *p = enh_keytable; while (*p < 255) { if (letterkey == p[1] && newkey == p[0]) { letterkey = newkey; break; } p += 2; } } stave_transpose_letter = (letterkey%7) - (usekey%7); stave_transpose_letter_is_auto = TRUE; if (amount > 0 && stave_transpose_letter < 0) stave_transpose_letter += 7; if (amount < 0 && stave_transpose_letter > 0) stave_transpose_letter -= 7; return (key == N_key)? key : newkey; } /************************************************* * Transpose a note * *************************************************/ /* This function is called when reading a note, and also when processing a string that contains note (chord) names. The amount by which to transpose is set in stave_transpose. Arguments: abspitch the absolute pitch pitch the normal pitch (updated) acc the accidental value (updated) transposeacc if not 0, accidental required transposedaccforce retain accidental, even if implied by new key acc_onenote TRUE if accidental is printed above/below, and hence applies only to a single note texttranspose TRUE if transposing a note name in text tiedcount < 0 if note is not tied, else note number in a tie Returns: the transposed absolute pitch the transposed pitch in *pitch the transposed accidental in *acc */ int transpose_note(int abspitch, int *pitch, int *acc, int transposeacc, BOOL transposedaccforce, BOOL acc_onenote, BOOL texttranspose, int tiedcount) { int impliedacc; int newpitch; int newacc; /* First, transpose the absolute pitch */ abspitch += stave_transpose; /* If a particular accidental is requested, and the new note is suitable, use it. */ if (transposeacc != 0 && able[transposeacc][abspitch%12]) { newacc = transposeacc; newpitch = abspitch - read_accpitch[newacc] + 2; } /* Otherwise we must change the note letter by the same amount as the note letter of the keysignature has changed. */ else { int i = stave_transpose_letter; int offset; newpitch = *pitch; offset = newpitch%12; /* The two cases are written out separately for maximum speed. (It also means the tables can be uschar rather than int.) */ if (i >= 0) { while (i-- > 0) { newpitch += tp_forward_pitch[offset]; offset = tp_forward_offset[offset]; } } else { while (i++ < 0) { newpitch -= tp_reverse_pitch[offset]; offset = tp_reverse_offset[offset]; } } /* Allow for >= octave transposition. */ while (newpitch <= abspitch - 12) newpitch += 12; while (newpitch >= abspitch + 12) newpitch -= 12; /* Offset is the difference between the true pitch and the pitch of the written note without an accidental. For transpositions around 12 we may have to correct for octave wraparound effects. */ offset = abspitch - newpitch; if (offset >= 10) { offset -= 12; newpitch += 12; } else if (offset <= -10) { offset += 12; newpitch -= 12; } /* There are also some rare cases when double accidentals are used. For example, C-double-flat cannot be transposed by +1 when the letter change derived from the key signature is also +1 because B cannot be represented as some accidental applied to D. Most probably these situations will never arise, but ... */ if (offset == -3 && *acc == ac_dflat) { int note_offset = newpitch%12; if (note_offset == 0 || note_offset == 5) /* C or F */ { newpitch -= 1; offset = -2; } else { newpitch -= 2; offset = -1; } } else if (offset == 3 && *acc == ac_dsharp) { int note_offset = newpitch%12; if (note_offset == 4 || note_offset == 11) /* E or B */ { newpitch += 1; offset = 2; } else { newpitch += 2; offset = 1; } } /* This double-check caught some bugs in the past for auto-selected letter changes. */ if (offset > 2 || offset < (-2)) { if (stave_transpose_letter_is_auto) error_moan(ERR50, *pitch, *acc, abspitch, newpitch); /* Hard error */ else { error_moan(ERR146, stave_transpose_letter, stave_transpose); /* Hard */ } } newacc = tp_newacc[offset+2]; } /* We now have the new pitch and its accidental. If we are transposing an actual note, as opposed to a note name in some text, there is extra work to do on the accidental. When the key is N (C major but never transposed) there are special rules for adjusting accidentals, as the default note transposing rules assume the result will have a key signature. Double sharps and flats are retained only if they were in the input; C-flat, B-sharp, E-sharp, and F-flat are converted to enharmonic notes unless the input was also a similar "white-note" sharp or flat. Then, if there was no accidental in the input, or if the noforce option is set, or if the key is N, see if this accidental is already implied and if so, cancel it. The case of a tied note is special and is handled by passing in the accidental to check against. Otherwise, remember the accidental for next time in this bar, unless acc_onenote is set, which means that the accidental applies only to this note (printed above/below) and does not apply to later notes in the bar. The remembering table is in baraccs format (pitch values + 2). */ if (!texttranspose) { int newaccpitch; if (stave_key == N_key) { int note_offset = newpitch%12; int old_offset = (*pitch)%12; BOOL isEorB = note_offset == 4 || note_offset == 11; BOOL isCorF = note_offset == 0 || note_offset == 5; BOOL old_isEorB = old_offset == 4 || old_offset == 11; BOOL old_isCorF = old_offset == 0 || old_offset == 5; switch (newacc) { case ac_dsharp: if (*acc != ac_dsharp) { if (isEorB) { newpitch += 1; newacc = ac_sharp; } else { newpitch += 2; newacc = ac_natural; } } break; case ac_dflat: if (*acc != ac_dflat) { if (isCorF) { newpitch -= 1; newacc = ac_flat; } else { newpitch -= 2; newacc = ac_natural; } } break; case ac_sharp: if (isEorB && !old_isEorB) { newpitch += 1; newacc = ac_natural; } break; case ac_flat: if (isCorF && !old_isCorF) { newpitch -= 1; newacc = ac_natural; } break; } } /* Now handle implied accidentals */ newaccpitch = read_accpitch[newacc]; if (tiedcount < 0) impliedacc = baraccs_tp[newpitch]; else impliedacc = stave_tiedata[tiedcount].acc_tp; if (impliedacc == newaccpitch && (*acc == ac_none || stave_key == N_key || !transposedaccforce)) newacc = ac_none; else if (!acc_onenote) baraccs_tp[newpitch] = newaccpitch; } /* Return the transposed pitch+accidental and absolute pitch */ *pitch = newpitch; *acc = newacc; return abspitch; } /* End of transpose_c */