/*
  midi support system
  midi.c
  ------
  15/4/23

  midi data to/from usb event packets
*/

#include <stdio.h>
#include <stdlib.h>
#include <kernel.h>
#include <swis.h>
#include "modhdr.h"
#include "module.h"
#include "midi_spec.h"


/*
  The data interface to the read/write functions is in midi support format
  command words. This means that it has to be converted to/from usb midi event packets
  here.

  midisupport command word format:
    data byte 1 usually the status, bits 0-7
    data byte 2, bits 8-15
    data byte 3, bits 16-23
    number of bytes, bits 24,25 (1..3)

  midi usb event packet
    event number, bits 0-3
    cable number, bits 4-7
    data byte 1 usually the status, bits 8-15
    data byte 2, bits 16-23
    data byte 3, bits 24-31

  The data is sent over usb, event byte first, data byte 3 last.
*/

/*
 * event_to_cmd
 * ------------
 * Converts a usb event packet to a midi support command
 * Returns the command;
 */
unsigned int event_to_cmd(unsigned int event)
{
  const unsigned char msg_len[16] = {0,0,2,3,3,1,2,3,3,3,3,3,2,2,3,1}; // event packet length

  unsigned int cmd = (event >> 8) | (msg_len[event & 0xf] << 24);

  return cmd;
}


/*
 * stream_to_event
 * ---------------
 * Converts a MIDI byte stream into usb events and returns them.
 * Returns 0 if an event is not completed.
 * 'n' is the stream number.
 */
unsigned int stream_to_event(int n, int data)
{
  int real_time;
  unsigned int event = 0;
  static struct port_s
  {
    unsigned char temp, type, channel, count;
    unsigned int ex; // 3 byte buffer for system exclusive messages
    int ix; // byte count for above buffer
  } port[MAX_PORTS];

  struct port_s *p = &port[n];
  data &= 0xff;

  if(data & 0x80) // status byte
  {
    real_time = 0;

    if(data < 0xf0) // voice messages
    {
      p->type    = data & 0xf0;
      p->channel = data & 0x0f;
      p->count = ((p->type == PROGRAM) || (p->type == CHAN_PRESSURE)) ? 1 : 2; // message length
    }
    else // system common commands
      switch (data)
      {
        case SYSTEM_EXCLUSIVE: // variable length until terminated by an EOX or any status byte
          p->type = data;
          p->ix = 0;
          break;

        case SONG_POSITION:
          p->type = data;
          p->count = 2;
          break;

        case SONG_SELECT:
          p->type = data;
          p->count = 1;
          break;

        case TUNE_REQUEST:
          event = 5 | (data << 8);                // code index 5, Single-byte System Common Message
          break;

        case EOX: // system exclusive terminator
          // no need to do anything here, it will be picked up after the switch
          break;

        // real time (no associated data)      Single byte packet
        case TIMING_CLOCK:
        case START:
        case STOP:
        case CONTINUE:
        case ACTIVE_SENSING:
        case SYSTEM_RESET:
          real_time = 1;
          event = 0x0f | (data << 8);            // code index F, Single byte packet
          break;
      }

    // check for an unterminated system exclusive message
    if(!real_time && (p->ix > 0))
    { // send sys ex
      if(p->ix == 1)
        event = 5 | (EOX << 8);                  // code index 5, SysEx ends with single byte.
      else if(p->ix == 2)
        event = 6 | (EOX << 16) | (p->ex << 8);  // code index 6, SysEx ends with two bytes.
      else
        event = 7 | (EOX << 24) | (p->ex << 8);  // code index 7, SysEx ends with three bytes
      p->ix = 0;
//      return 0;   //  <<<----------- 28/4/23 testing, discard sysex messages -----------------
    }
  }
  else // data byte
  {
    switch (p->type)
    {
      case SYSTEM_EXCLUSIVE:
        switch(p->ix)
        {
          case 0:
            p->ex = SYSTEM_EXCLUSIVE | (data << 8);
            p->ix = 3;
            break;

          case 1:
            p->ex = data;
            p->ix = 2;
            break;

          case 2:
            p->ex |= (data << 8);
            p->ix = 3;
            break;

          case 3:
           event = 4 | (data << 24) | (p->ex << 8);  // code index 4, SysEx starts or continues
           p->ix = 1;
//           return 0;   //  <<<----------- 28/4/23 testing, discard sysex messages -----------------
           break;
        }
        break;

      case SONG_SELECT:
        event = 2 | (p->type << 8) | (data << 16);   // code index 2, Two-byte System Common message
        break;

      case SONG_POSITION:
        if(p->count == 2)
          p->temp = data;
        else
          event = 3 | (p->type << 8) | (p->temp << 16) | (data << 24);   // code index 3, Three-byte System Common message
        break;

      case NOTE_OFF:                        // code index 8, Three-byte Note Off
      case NOTE_ON:                         // code index 9, Three-byte Note On
      case KEY_PRESSURE:                    // code index A, Three-byte Key Pressure
      case PITCH_WHEEL:                     // code index E, Three-byte Pitch Bend change
        if(p->count == 2)
          p->temp = data;
        else
          event = (p->type >> 4) | ((p->type + p->channel) << 8) | (p->temp << 16) | (data << 24);
        break;

      case CONTROL:
        if(p->count == 2)
        {
//          // check for channel mode messages with no associated data (except MONO_OPERATION which does)
//          if(((data >= ALL_SOUND_OFF) && (data <= OMNI_MODE_ON)) || (data == POLY_OPERATION))
//            event = 0xb | ((p->type + p->channel) << 8) | (data << 16); // code index B, Two-byte control change, channel mode
//          else
            p->temp = data;
        }
        else
          event = 0x0b | ((p->type + p->channel) << 8) | (p->temp << 16) | (data << 24); // code index B, Three-byte Control change
        break;


      case PROGRAM:                         // code index C, Two-byte Program change
      case CHAN_PRESSURE:                   // code index D, Two-byte Channel Pressure change
        event = (p->type >> 4) | ((p->type + p->channel) << 8) | (data << 16);
        break;
    }
    p->count ^= 3; // toggle between 1 and 2 (MSB and LSB for 14 bit values)
  }
  return event;
}

/*
 * cmd_to_event
 * ------------
 * Converts a midi support command to a usb event packet.
 * Returns the event.
 * It is important to catch any commands less than 3 that are incomplete as if
 * these are sent as is they will have the wrong event code and will end up
 * having stray bytes inserted.
 */
unsigned int cmd_to_event(int port, unsigned int cmd)
{
  int len = cmd >> 24;
  unsigned int event = 0, data = cmd;

  while(len--)
  {
    event = stream_to_event(port, (data & 0xff));
    if(event != 0)
      break;
    data >>= 8;
  }

  return event;
}




