/*
  midi
  ----
  13/3/23

  Partially interprets the data stream to build midi commands
*/

#include <stdio.h>
#include <kernel.h>
#include "midi_spec.h"
#include "module.h"

#define STREAMS MAX_PORTS

/*
 * stream_to_cmd
 * -------------
 * Converts a MIDI byte stream into MIDI commands and returns them.
 * Returns 0 if a command is not completed.
 * 'n' is the stream number.
 */
int stream_to_cmd(int n, int data)
{
  int real_time;
  int cmd = 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[STREAMS];

  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:                  // Single-byte System Common Message
          cmd = (1 << 24) | data;
          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;
          cmd = (1 << 24) | data;
          break;
      }

    // check for an unterminated system exclusive message
    if(!real_time && (p->ix > 0))
    { // send sys ex
      if(p->ix == 1)
        cmd = (1 << 24) | EOX;         // SysEx ends with single byte.
      else if(p->ix == 2)
        cmd = (2 << 24) | (EOX << 8) | p->ex;  // SysEx ends with two bytes.
      else
        cmd = (3 << 24) | (EOX << 16) | p->ex; // SysEx ends with three bytes
      p->ix = 0;
    }
  }
  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:
           cmd = (3 << 24) | (data << 16) | p->ex; // SysEx starts or continues
           p->ix = 1;
           break;
        }
        break;

      case SONG_SELECT:
        cmd = (2 << 24) | p->type | (data << 8); // Two-byte System Common message
        break;

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

      case NOTE_OFF:                        // Three-byte, Note Off
      case NOTE_ON:                         // Three-byte, Note On
      case KEY_PRESSURE:                    // Three-byte, Key Pressure
      case PITCH_WHEEL:                     // Three-byte, Pitch Bend change
        if(p->count == 2)
          p->temp = data;
        else
          cmd = (3 << 24) | (p->type + p->channel) | (p->temp << 8) | (data << 16);
        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))
//            cmd = (2 << 24) | (p->type + p->channel) | (data << 8); // Two-byte, control change, channel mode
//          else
            p->temp = data;
        }
        else
          cmd = (3 << 24) | (p->type + p->channel) | (p->temp << 8) | (data << 16); // Three-byte control change
        break;


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

