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