/*
  !MidiKey
  Provides a keyboard note entry and nonitors channel activity.

  midi_io.c
  9/7/22

  MIDI input/output interface.

*/

#include "wimp.h"
#include "lib.h"
#include "main.h"
#include "midi_spec.h"
#include "kbd_dsplay.h"
#include "gm.h"
#include "channel.h"

// swi's
#define MIDI_RxCommand   0x404c8
#define MIDI_TxCommand   0x404ca

extern menu_t channel_menu;


/*
 * midi_rx_command
 * ---------------
 * Interprets received midi messages
 */
static void midi_rx_command(unsigned int cmd)
{
      static int sysex = -1; // index to buffer, >=0 when receiving a system exclusive message
      #define SYSEX_LEN 6
      static unsigned char sysex_msg[SYSEX_LEN];
      static const unsigned char reset_msg[SYSEX_LEN] = {SYSTEM_EXCLUSIVE,0x7e,0x7f,0x09,0x01,EOX}; // GM1 reset msg
      static const unsigned char colour[NUM_SWITCHES] = {0xa0,0xe7,0xf7,0x97,0xb0,0x80}; // pedal switch highlight colour

      int status = cmd & 0xf0;
      int channel = cmd & 0xf;
      int data1 = (cmd >> 8) & 0xff;
      int data2 = (cmd >> 16) & 0xff;
      chan_t *c = &midi.chan[channel]; // point to the current channel (not always needed)
      int on = data2 >> 6; // // not always needed

      if((cmd & 0xff) == SYSTEM_EXCLUSIVE)
        sysex = 0;

      if(sysex < 0)
        switch(status)
        {
          case NOTE_OFF:
            highlight_midi_key(data1, 0, channel); // data1 = pitch
            break;

          case NOTE_ON:
            highlight_midi_key(data1, (data2 != 0), channel); // data1 = pitch, data2 = velocity
            break;

          case CONTROL:
            switch(data1) // data1 = controller
            {
              case ALL_NOTES_OFF:
                all_notes_off(channel);
                break;

              case VOLUME_hi:
                c->volume = (c->volume & ~(0x7f << 7)) | (data2 << 7); // data2 = volume msb
                slider_display_value(c->volume, SLDR_CHN_VOL, WIN_CHANNELS, ICON_CHN_VOL_SLDR + (CHN_ROW_ICONS * channel));
                break;

              case VOLUME_lo:
                c->volume = (c->volume & ~0x7f) | data2; // data2 = volume lsb
                break;

              case EXPRESSION_hi:
                c->expression = (c->expression & ~(0x7f << 7)) | (data2 << 7); // data2 = volume msb
                slider_display_value(c->expression, SLDR_CHN_VOL, WIN_CHANNELS, ICON_CHN_EXP_SLDR + (CHN_ROW_ICONS * channel));
                break;

              case EXPRESSION_lo:
                c->expression = (c->expression & ~0x7f) | data2; // data2 = volume lsb
                break;

              case BANK_lo:
                c->bank = data2;
                icon_text_change(itoa(data2), ro.handle[WIN_CHANNELS], ICON_CHN_BNK_VAL + (channel * CHN_ROW_ICONS));
                break;

              default:
                if((data1 >= HOLD_PEDAL_on) && (data1 <= HOLD_2_PEDAL_on)) // pedal switches
                {
                  int pedal = data1 - HOLD_PEDAL_on;
                  int icon = ICON_CHN_PRG_NAME + (CHN_ROW_ICONS * channel);
                  midi.chan[channel].switches = (midi.chan[channel].switches & ~(1 << pedal)) | (on << pedal);
                  icon_colour_change((on) ? colour[pedal] : 0x17, ro.handle[WIN_CHANNELS], icon);
                }
                break;
            }
            break;

          case PITCH_WHEEL:
                c->pitch = data1 + (data2 << 7);
                slider_display_value(c->pitch, SLDR_CHN_VOL, WIN_CHANNELS, ICON_CHN_PIT_SLDR + (CHN_ROW_ICONS * channel));
            break;

          case PROGRAM:
            c->prg = data1; // data1 = program
            {
             const char *p;
              if(channel == (PERCUSSION_CHAN-1))
                p = "GM Drum Kit";
              else
                p = gm_melodic[data1];
              int icon = channel * CHN_ROW_ICONS;
              icon_text_change(itoa(data1), ro.handle[WIN_CHANNELS], ICON_CHN_PRG_VAL + icon);
              icon_text_change((char *)p, ro.handle[WIN_CHANNELS], ICON_CHN_PRG_NAME + icon);
            }
            break;
        }

      else // sysex >= 0
      {
        int i;
        int len = (cmd >> 24) & 3;
        while(len--)
        {
          int data = cmd & 0xff;
          cmd >>= 8;
          if(sysex < SYSEX_LEN)
            sysex_msg[sysex++] = data;
          if(data == EOX)
          {
/*
            {
              char str[24], *s = str;
              for(i=0; i<6; i++)
                s += sprintf(s,"%02X ", sysex_msg[i]);
              report_error(str,0);
            }
*/
            sysex = -1;
            for(i=0; i<SYSEX_LEN; i++)
              if(sysex_msg[i] != reset_msg[i])
                break;
            if(i == SYSEX_LEN)
              init_chan_window();
            break;
          }
        }
      }
}


/*
  MIDI Support Interface
  ----------------------
*/

// MIDISupport SWI numbers
#define MIDISupport_RemoveDriver    0x4EE81
#define MIDISupport_CreateDriver    0x4EE83
#define MIDISupport_Send            0x4EE84
#define MIDISupport_Receive         0x4EE85

// Driver flags
#define CAN_SEND             1
#define CAN_RECEIVE          2
#define REQUIRES_BUFFERING   4
#define CAN_RECEIVE_COMMAND  8

static int midi_support_handle;

/*
 * create_driver
 * -------------
 * Instructs Midi Support to create a driver for us.
 */
void create_driver(void)
{
  _kernel_swi_regs regs;

  regs.r[0] = CAN_SEND | CAN_RECEIVE | REQUIRES_BUFFERING | CAN_RECEIVE_COMMAND;
  regs.r[1] = (ro.flags & (1<<RUNNING)) ? (int)"MIDIKeyboard(2)" : (int)"MIDIKeyboard";
  regs.r[2] = 6; // version
  regs.r[3] = (int)"31 Oct 2024";
  _kernel_swi(MIDISupport_CreateDriver, &regs, &regs);
  midi_support_handle = regs.r[0];
}

/*
 * remove_driver
 * -------------
 * Commands Midi Support to remove (delete) our driver.
 */
void remove_driver(void)
{
  _kernel_swi_regs regs;

  regs.r[1] = midi_support_handle;
  _kernel_swi(MIDISupport_RemoveDriver, &regs, &regs);
}

/*
 * midi_out_port
 * -------------
 * Sends a midi message to Midi Support.
 */
void midi_out_port(int status, int data1, int data2, int n)
{
  _kernel_swi_regs regs;

   regs.r[2] = status |
               (data1 << 8) |
               (data2 << 16) |
               (n << 24);

   regs.r[0] = 1; // send a command
   regs.r[1] = midi_support_handle;
   _kernel_swi(MIDISupport_Send, &regs, &regs);
}


/*
 * midi_in_port
 * ------------
 * Requests midi messages from Midi Support until no more returned.
 */
void midi_in_port(void)
{
  _kernel_swi_regs regs;

  do
  {
    regs.r[0] = 1; // receive byte or command
    regs.r[1] = midi_support_handle;
    regs.r[2] = 2; // receive command
    if(_kernel_swi(MIDISupport_Receive, &regs, &regs) != NULL)
      return;
    if(regs.r[0])
      midi_rx_command(regs.r[0]); // interpret message
  }
  while(regs.r[0]);
}

