/*
  seek_ctrls.c
  ------------
  3/6/23
  Stores certain midi messages whilst seeking so that when the position
  is found, the current settings can be sent. This stops controls that
  are constantly being changed thoughout the song from being
  unnecessarily sent. Not all controls need to be remembered.
*/

#include "main.h"
#include "midi_spec.h"

#define NUM_STORED_VALUES 22 // maximum 32

typedef struct seek_ctrls_s
{
  unsigned int flags; // a bit set for each ctrl that has been changed
  unsigned int param; // registered parameter number
  unsigned char ctrl[NUM_STORED_VALUES];

} seek_ctrls_t;

static seek_ctrls_t seek_ctrls[NUM_MIDI_CHANS];

// seek_ctrls.ctrl[] elements
enum
{
  // voice messages
  SC_PROGRAM,
  SC_PITCH_WHEEL_lo,
  SC_PITCH_WHEEL_hi,

  // 14 bit controllers
  SC_BANK_hi,
  SC_PORTAMENTO_TIME_hi,
  SC_VOLUME_hi,
  SC_BALANCE_hi,
  SC_PAN_POSN_hi,
  SC_EXPRESSION_hi,
  SC_BANK_lo,
  SC_PORTAMENTO_TIME_lo,
  SC_VOLUME_lo,
  SC_BALANCE_lo,
  SC_PAN_POSN_lo,
  SC_EXPRESSION_lo,
  // 7 bit controllers
  SC_HOLD_PEDAL_on,
  SC_PORTAMENTO_on,
  SC_PORTAMENTO_CTRL,

  // 14 bit registered parameters
  SC_PITCH_BEND_RANGE_lo,
  SC_PITCH_BEND_RANGE_hi,
  SC_MOD_DEPTH_RANGE_lo,
  SC_MOD_DEPTH_RANGE_hi
};

// seek_ctrls.ctrl[] midi controller numbers
static const unsigned char ctrl_num[] =
{
  // 14 bit controllers
  BANK_hi, PORTAMENTO_TIME_hi, VOLUME_hi, BALANCE_hi, PAN_POSN_hi, EXPRESSION_hi,
  BANK_lo, PORTAMENTO_TIME_lo, VOLUME_lo, BALANCE_lo, PAN_POSN_lo, EXPRESSION_lo,
  // 7 bit controllers
  HOLD_PEDAL_on, PORTAMENTO_on, PORTAMENTO_CTRL
};

/*
 * sc_reset
 * --------
 * resets the seek_ctrls data structure
 */
void sc_reset(void)
{
  memset(&seek_ctrls, 0, sizeof(seek_ctrls));
}


/*
 * sc_store
 * --------
 * stores control messages whilst seeking
 */
void sc_store(int status, unsigned char *buff, int n)
{
  if(n > 2)
    return;

  seek_ctrls_t *sc = &seek_ctrls[status & 0x0f];
  int i = -1;

  switch(status & 0xf0)
  {
    case PROGRAM:
      sc->ctrl[SC_PROGRAM] = buff[0];
      sc->flags |= (1<<SC_PROGRAM);
      break;

    case PITCH_WHEEL:
      sc->ctrl[SC_PITCH_WHEEL_lo] = buff[0];
      sc->ctrl[SC_PITCH_WHEEL_hi] = buff[1];
      sc->flags |= (1<<SC_PITCH_WHEEL_lo)|(1<<SC_PITCH_WHEEL_hi);
      break;

    case CONTROL:
      switch(buff[0])
      {
        case BANK_hi:            i = SC_BANK_hi; break;
        case PORTAMENTO_TIME_hi: i = SC_PORTAMENTO_TIME_hi; break;
        case VOLUME_hi:          i = SC_VOLUME_hi; break;
        case BALANCE_hi:         i = SC_BALANCE_hi; break;
        case PAN_POSN_hi:        i = SC_PAN_POSN_hi; break;
        case EXPRESSION_hi:      i = SC_EXPRESSION_hi; break;
        case BANK_lo:            i = SC_BANK_lo; break;
        case PORTAMENTO_TIME_lo: i = SC_PORTAMENTO_TIME_lo; break;
        case VOLUME_lo:          i = SC_VOLUME_lo; break;
        case BALANCE_lo:         i = SC_BALANCE_lo; break;
        case PAN_POSN_lo:        i = SC_PAN_POSN_lo; break;
        case EXPRESSION_lo:      i = SC_EXPRESSION_lo; break;
        case HOLD_PEDAL_on:      i = SC_HOLD_PEDAL_on; break;
        case PORTAMENTO_on:      i = SC_PORTAMENTO_on; break;
        case PORTAMENTO_CTRL:    i = SC_PORTAMENTO_CTRL; break;

        case NON_REG_PARM_lo:
        case REG_PARM_lo:
          sc->param = (sc->param & ~0x7f) | buff[1];
          break;

        case NON_REG_PARM_hi:
        case REG_PARM_hi:
          sc->param = (sc->param & ~(0x7f << 7)) | (buff[1] << 7);
          break;

        case DATA_ENTRY_hi:
          if(sc->param == PITCH_BEND_RANGE)
            i = SC_PITCH_BEND_RANGE_hi;
          else if(sc->param == MOD_DEPTH_RANGE)
            i = SC_MOD_DEPTH_RANGE_hi;
          break;

        case DATA_ENTRY_lo:
          if(sc->param == PITCH_BEND_RANGE)
            i = SC_PITCH_BEND_RANGE_lo;
          else if(sc->param == MOD_DEPTH_RANGE)
            i = SC_MOD_DEPTH_RANGE_lo;
          break;
      }
      break;
  }
  if(i >= 0)
  {
    sc->ctrl[i] = buff[1];
    sc->flags |= (1<<i);
  }
}

/*
 * sc_send
 * -------
 * sends the final values of controls that have been changed
 * whilst seeking.
 */
void sc_send(void)
{
  int i, j;
  unsigned char buff[4];

  for(i=0; i<NUM_MIDI_CHANS; i++)
  {
    seek_ctrls_t *sc = &seek_ctrls[i];

    if(mod.debug & (1<<DBG_FN_CALLS))
      if(mod.log)fprintf(mod.log, "sc_send start, chan %d, flags %X\n", i+1, sc->flags);

    for(j=0; j<NUM_STORED_VALUES; j++)
    {
      if(sc->flags & (1<<j))
      {
        if(j == SC_PROGRAM)
          mod.midi->tx_word(PROGRAM | i, &sc->ctrl[SC_PROGRAM], 1);
        else if(j == SC_PITCH_WHEEL_lo) // do _hi as well
        {
          buff[0] = sc->ctrl[SC_PITCH_WHEEL_lo];
          buff[1] = sc->ctrl[SC_PITCH_WHEEL_hi];
          mod.midi->tx_word(PITCH_WHEEL | i, buff, 2);
        }
        else if((j >= SC_BANK_hi)&&(j <= SC_PORTAMENTO_CTRL)) // first and last stored controller
        {
          buff[0] = ctrl_num[j-SC_BANK_hi]; // -offest is first stored controller
          buff[1] = sc->ctrl[j];
          mod.midi->tx_word(CONTROL | i, buff, 2);
        }
        // for PITCH_BEND_RANGE and MOD_DEPTH_RANGE send command when _hi or _lo encountered
        // but ignore any following _lo or _hi
        else if((j == SC_PITCH_BEND_RANGE_hi) || (j == SC_PITCH_BEND_RANGE_lo))
        {
          sc->flags &= ~((1<<SC_PITCH_BEND_RANGE_hi) || (1 << SC_PITCH_BEND_RANGE_lo));
          buff[0] = REG_PARM_lo;
          buff[1] = PITCH_BEND_RANGE;
          mod.midi->tx_word(CONTROL | i, buff, 2);
          buff[0] = REG_PARM_hi;
          buff[1] = PITCH_BEND_RANGE >> 7;
          mod.midi->tx_word(CONTROL | i, buff, 2);
          buff[0] = DATA_ENTRY_lo;
          buff[1] = sc->ctrl[SC_PITCH_BEND_RANGE_lo];
          mod.midi->tx_word(CONTROL | i, buff, 2);
          buff[0] = DATA_ENTRY_hi;
          buff[1] = sc->ctrl[SC_PITCH_BEND_RANGE_hi];
          mod.midi->tx_word(CONTROL | i, buff, 2);
        }
        else if((j == SC_MOD_DEPTH_RANGE_hi) || (j == SC_MOD_DEPTH_RANGE_lo))
        {
          sc->flags &= ~((1<<SC_MOD_DEPTH_RANGE_hi) || (1 << SC_MOD_DEPTH_RANGE_lo));
          buff[0] = REG_PARM_lo;
          buff[1] = MOD_DEPTH_RANGE;
          mod.midi->tx_word(CONTROL | i, buff, 2);
          buff[0] = REG_PARM_hi;
          buff[1] = MOD_DEPTH_RANGE >> 7;
          mod.midi->tx_word(CONTROL | i, buff, 2);
          buff[0] = DATA_ENTRY_lo;
          buff[1] = sc->ctrl[SC_MOD_DEPTH_RANGE_lo];
          mod.midi->tx_word(CONTROL | i, buff, 2);
          buff[0] = DATA_ENTRY_hi;
          buff[1] = sc->ctrl[SC_MOD_DEPTH_RANGE_hi];
          mod.midi->tx_word(CONTROL | i, buff, 2);
        }
      }
    }
  }

  if(mod.debug & (1<<DBG_FN_CALLS))
    if(mod.log)fprintf(mod.log, "sc_send end\n");
}

