/*
  MidiPlay - MIDI file player driver module

 module.c
 --------

 16/8/22

 Module interface for the Midi File Player.

*/

#include "main.h"
#include <kernel.h>
#include <swis.h>
#include "modhdr.h"


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

#define MTimer_Register             0xd25c0
#define MTimer_Remove               0xd25c1

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


#define TickerV 0x1c

module_t mod;

static void read_options(int argc, const char *arg_string, void *pw);
void create_driver(void);
void remove_driver(void);

const midi_io_if_t midi_mod;
const midi_io_if_t midi_sup;

/*
 * get_oserror
 * -----------
 * returns a pointer to an oserror block for the given error number
 * err = position in err_msg_e list, error numbers are negative
 */
static _kernel_oserror *get_oserror(int err)
{
  static _kernel_oserror oserr;

  if(err >= NO_ERROR_)
    return NULL;
  else if(err <= -NUM_ERRORS)
    err = -UNKNOWN_ERROR;

  err = -err - 1; // make err positive and remove the NO_ERROR_ case

  oserr.errnum = ERROR_BASE + 16 + err; // avoid original module error numbers
  strcpy(oserr.errmess, errstr[err]);

  return &oserr;
}


/*
 * module_init
 * -----------
 * Module initialisation. If MidiSupport is loaded, it will install itself
 * as a driver, if not it will use the acorn MIDI swi interface.
 */
_kernel_oserror *module_init(const char *tail, int podule_base, void *pw)
{
  _kernel_swi_regs regs;

  // check if midi support is loaded
  regs.r[0] = 18; // Lookup name
  regs.r[1] = (int)"MIDISupport";
  if(!_kernel_swi(OS_Module, &regs, &regs))
  {
    create_driver();
    mod.midi = &midi_sup; // use midi support interface
    mod.master_clock = 1000;

    // register with timer to drive the player timing
    regs.r[0] = (int)timer_callback;
    regs.r[1] = (int)pw;
    regs.r[2] = 1; // 1kHz player master clock rate
    regs.r[3] = (int)Module_Title;
    _kernel_swi(MTimer_Register, &regs, &regs);
  }
  else // use the system 10ms ticker
  {
    mod.midi = &midi_mod; // use acorn midi module interface
    mod.master_clock = 100;

    // Claim the system ticker to drive the player timing
    regs.r[0] = TickerV;
    regs.r[1] = (int)tickerv;
    regs.r[2] = (int)pw;
    _kernel_swi(OS_Claim,&regs,&regs);
  }

  midiPlayer_new();

  return NULL;
}

/*
 * module_term
 * -----------
 * Undoes initialisation
 */
_kernel_oserror *module_term(int fatal, int podule_base, void *pw)
{
  _kernel_swi_regs regs;

  if(mod.log)
    fclose(mod.log);


  regs.r[0] = 18; // Lookup name
  regs.r[1] = (int)"MIDISupport";
  if(!_kernel_swi(OS_Module, &regs, &regs))
  {
    // remove our timer entry
    regs.r[0] = (int)timer_callback;
    _kernel_swi(MTimer_Remove, &regs, &regs);

    remove_driver();
  }
  else // system tick was used
  {
    if(mod.ticker_callbacks)
    {
      regs.r[0] = (int)ticker_callback;
      regs.r[1] = (int)pw;
      _kernel_swi(OS_RemoveCallBack, &regs, &regs);
    }
    regs.r[0] = TickerV;
    regs.r[1] = (int)tickerv;
    regs.r[2] = (int)pw;
    _kernel_swi(OS_Release,&regs,&regs);
  }

  midiPlayer_term();

  return NULL;
}

/*
 * module_swi
 * ----------
 * SWI handler for the player control.
 */
_kernel_oserror *module_swi(int number, _kernel_swi_regs *r, void *pw)
{
  int err = NO_ERROR_;

  if(number != 5)
  if(mod.debug & (1<<DBG_SWI))
    if(mod.log)fprintf(mod.log, "swi %2d r0 %08X r1 %08X\n", number, r->r[0], r->r[1]);

  switch(number + MIDIPlay_00)
  {
    case MIDIPlay_File:
      if(r->r[0] == 0) // load file to memory
        err = midiPlayer_control(CTRL_LOAD, r->r[1], NULL);
      break;

    case MIDIPlay_Start:
      if(r->r[0] == 0) // start at position in r1
        err = midiPlayer_control(CTRL_PLAY, r->r[1], NULL);
      else if(r->r[0] == 1) // unpause
        err = midiPlayer_control(CTRL_UNPAUSE, 0, NULL);
      break;

    case MIDIPlay_Stop:
      err = midiPlayer_control(CTRL_PAUSE, 0, NULL);
      break;

    case MIDIPlay_Volume:
      if(mod.debug & (1<<DBG_FN_CALLS))
        if(mod.log)fprintf(mod.log, "player_volume(%d)\n", r->r[0]);
      mod.midi->volume(r->r[0]);
      break;

    case MIDIPlay_Tempo:
      err = midiPlayer_control(CTRL_TEMPO, r->r[0], NULL);
      break;

    case MIDIPlay_Info:
      err = midiPlayer_control(CTRL_INFO, 0, (int *)r);
      break;

    case MIDIPlay_Close:
      err = midiPlayer_control(CTRL_CLOSE, 0, NULL);
      break;

    // next swi only for this version
    // r0 = 0, read settings
    // r0 = 1, set tempo, r1 = 10 to 1000 in percent
    // r0 = 2, set pitch tranposition, r1 = +12 to -12 in semitones
    // r0 = 3, set options,  r1/r2: bit 0 = discard sysex
    //           flags = (flags &~r1 ^r2)
    // returns r0 = tempo, r1 = pitch, r2 = options
    case MIDIPlay_Control:
      err = midiPlayer_control(CTRL_CONTROLS, 0, (int *)r);
      break;
  }

  return get_oserror(err);
}

/*
 * module_command
 * --------------
 * Command handler
 */
_kernel_oserror *module_command(const char *arg_string, int argc, int number, void *pw)
{
  switch(number)
  {
    case CMD_MidiPlayer:
      if(argc > 0)
        read_options(argc, arg_string, pw);
      break;
  }

  return NULL;
}

/*
 * tickerv_handler
 * ---------------
 */
int tickerv_handler(_kernel_swi_regs *r, void *pw)
{
  _kernel_swi_regs regs;

  if(mod.ticker_callbacks == 0)
  {
    regs.r[0] = (int)ticker_callback;
    regs.r[1] = (int)pw;
    _kernel_swi(OS_AddCallBack, &regs, &regs);
  }
  mod.ticker_callbacks++;
  mod.ticks += 10;

  return VECTOR_PASSON;
}

/*
 * ticker_callback_handler
 * -----------------------
 */
_kernel_oserror *ticker_callback_handler(_kernel_swi_regs *r, void *pw)
{
  while(mod.ticker_callbacks > 0)
  {
    mod.ticker_callbacks--;
    midi_file_player();
  }

  return NULL;
}

/*
 * timer_callback_handler
 * ----------------------
 */
_kernel_oserror *timer_callback_handler(_kernel_swi_regs *r, void *pw)
{
  int n = r->r[2]; // ticks since last call (usually 1)
  mod.ticks = r->r[1]; // current timestamp

  switch(r->r[3])
  {
    case 1:  mod.master_clock = 1000; break;
    case 2:  mod.master_clock = 500; n >>= 1; break;
    case 10: mod.master_clock = 100; n /= 10; break;
  }

  if(mod.debug & (1<<DBG_CLK))
    if(mod.log)fprintf(mod.log,"%d,",n);

  while(n--)
    midi_file_player();

  return NULL;
}

/*
 * read_options
 * ------------
 * Reads options for the MidiPlayer command
 */
static void read_options(int argc, const char *arg_string, void *pw)
{
  char c;

  do
  {
    while((c = *arg_string++) == ' ');
    if(c == '-')
      switch((c = *arg_string++) & ~0x20)
      {
        case 'I': // Info
          if(*arg_string == '+')
          {
            arg_string++;
            midiPlayer_info(1); // additional info
          }
          else
            midiPlayer_info(0);
          break;

        case 'T': // Tempo in percent
          player_tempo(atoi(arg_string));
          break;

        case 'P': // Pitch transpose
          player_pitch(atoi(arg_string));
          if(*arg_string == '-')arg_string++;
          break;

        case 'O': // open log file
          if(mod.log)
            printf("Log file already open\n");
          else
          {
            mod.log = fopen("logP", "w"); // log file opened in the current directory
            if(!mod.log)
              printf("Couldn't open Log file\n");
            else
              printf("Log file opened\n");
          }
          break;

        case 'C': // close log file
          if(mod.log)
          {
            fclose(mod.log);
            printf("Log file closed\n");
            mod.log = 0;
          }
          else
            printf("Log file not open\n");
          break;

        case 'D': // debug options
          mod.debug = atoi(arg_string);
          break;

        default: printf("unknown opion (-%c)\n", c);
      }
  }
  while(c >= ' '); // continue to end og arg_string
}


//------------------------------------------------------------

// midi i/o interface

/*
   Midi module interface
   ---------------------
*/

#define MIDI_TxByte         0x404c9
#define MIDI_TxCommand      0x404ca

/*
 * midi_reset
 * ----------
 */
void midi_reset(void)
{
  _kernel_swi_regs regs;
  int i;
  unsigned char data[6] = {0xf0,0x7e,0x7F,0x09,0x01,0xf7}; // sysex gm control

  if((mod.debug & (1<<DBG_MSG)) && mod.log)
  {
    fprintf(mod.log, "%u: [", mod.ticks);
    for(i=0; i<sizeof(data); i++)
      fprintf(mod.log, "%02X%s", data[i], (i < (sizeof(data) - 1)) ? " " : "] (gm reset)\n");
  }

  for(i=0; i<sizeof(data); i++)
  {
    regs.r[0] = data[i];
    _kernel_swi(MIDI_TxByte, &regs, &regs);
  }
}

/*
 * midi_tx_byte
 * ------------
 */
void midi_tx_byte(int data)
{
  if(mod.debug & (1<<DBG_MSG))
    if(mod.log)fprintf(mod.log, "%u: [%02X]\n", mod.ticks, data & 0xff);

  _kernel_swi_regs regs;

  regs.r[0] = data;
  _kernel_swi(MIDI_TxByte, &regs, &regs);
}

/*
 * midi_tx_word
 * -----------------
 */
void midi_tx_word(int status, unsigned char *buff, int n)
{
  if((mod.debug & (1<<DBG_MSG)) && mod.log)
  {
    if(n == 0)
      fprintf(mod.log, "%u: [%02X]\n", mod.ticks, status);
    else if(n == 1)
      fprintf(mod.log, "%u: [%02X %02X]\n", mod.ticks, status, buff[0]);
    else
      fprintf(mod.log, "%u: [%02X %02X %02X]\n", mod.ticks, status, buff[0], buff[1]);
  }

  _kernel_swi_regs regs;

  regs.r[0] = ((n+1) << 24) | status | ((int)buff[0] << 8) | ((int)buff[1] << 16);
  regs.r[1] = 0;
  _kernel_swi(MIDI_TxCommand, &regs, &regs);
}

/*
 * midi_all_notes_off
 * -------------------
 * resets gates on all channels
 */
void midi_all_notes_off(void)
{
  _kernel_swi_regs regs;
  int i;

  for(i=0; i<16; i++)
  {
    if(mod.debug & (1<<DBG_MSG))
      if(mod.log)fprintf(mod.log, "%u: [%02X %02X] (all notes off)\n", mod.ticks, CONTROL | i, ALL_NOTES_OFF);

    regs.r[0] = (3 << 24) | (CONTROL | i) | (ALL_NOTES_OFF << 8);
    regs.r[1] = 0;
    _kernel_swi(MIDI_TxCommand, &regs, &regs);
  }
}

/*
 * midi_volume
 * ----------------
 * volume is 0 to 255
 */
void midi_volume(int volume)
{
  _kernel_swi_regs regs;
  int i;
  unsigned char data[8] = {0xf0,0x7f,0x7F,0x04,0x01,0,0,0xf7}; // sysex master volume

  data[5] = (volume & 1) << 6; // lsb
  data[6] = volume >> 1;       // msb

  if((mod.debug & (1<<DBG_MSG)) && mod.log)
  {
    fprintf(mod.log, "%u: [", mod.ticks);
    for(i=0; i<sizeof(data); i++)
      fprintf(mod.log, "%02X%s", data[i], (i < (sizeof(data) - 1)) ? " " : "] (master volume)\n");
  }

  for(i=0; i<sizeof(data); i++)
  {
    regs.r[0] = data[i];
    _kernel_swi(MIDI_TxByte, &regs, &regs);
  }
}

const midi_io_if_t midi_mod =
{
  midi_reset,
  midi_tx_byte,
  midi_tx_word,
  midi_all_notes_off,
  midi_volume
};


/*
 Midi Support interface
*/


static int midi_support_handle;

/*
 * create_driver
 * -------------
 */
void create_driver(void)
{
  _kernel_swi_regs regs;

  regs.r[0] = CAN_SEND;
  regs.r[1] = (int)Module_Title;
  regs.r[2] = Module_VersionNumber;
  regs.r[3] = (int)Module_Date;
  _kernel_swi(MIDISupport_CreateDriver, &regs, &regs);
  midi_support_handle = regs.r[0];
}

/*
 * remove_driver
 * -------------
 */
void remove_driver(void)
{
  _kernel_swi_regs regs;

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

/*
 * midi_sup_reset
 * --------------
 */
void midi_sup_reset(void)
{
  _kernel_swi_regs regs;
  unsigned char data[6] = {0xf0,0x7e,0x7F,0x09,0x01,0xf7}; // sysex gm control
  int i;

  if((mod.debug & (1<<DBG_MSG)) && mod.log)
  {
    fprintf(mod.log, "%u: [", mod.ticks);
    for(i=0; i<sizeof(data); i++)
      fprintf(mod.log, "%02X%s", data[i], (i < (sizeof(data) - 1)) ? " " : "] (gm reset)\n");
  }

  regs.r[0] = 2; // send a block
  regs.r[1] = midi_support_handle;
  regs.r[2] = (int)data;
  regs.r[3] = sizeof(data);
  _kernel_swi(MIDISupport_Send, &regs, &regs);
}

/*
 * midi_sup_tx_byte
 * ------------
 */
void midi_sup_tx_byte(int data)
{
  if(mod.debug & (1<<DBG_MSG))
    if(mod.log)fprintf(mod.log, "%u: [%02X]\n", mod.ticks, data & 0xff);

  _kernel_swi_regs regs;

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

/*
 * midi_sup_tx_word
 * -----------------
 */
void midi_sup_tx_word(int status, unsigned char *buff, int n)
{
  if((mod.debug & (1<<DBG_MSG)) && mod.log)
  {
    if(n == 0)
      fprintf(mod.log, "%u: [%02X]\n", mod.ticks, status);
    else if(n == 1)
      fprintf(mod.log, "%u: [%02X %02X]\n", mod.ticks, status, buff[0]);
    else
      fprintf(mod.log, "%u: [%02X %02X %02X]\n", mod.ticks, status, buff[0], buff[1]);
  }

  _kernel_swi_regs regs;

  regs.r[0] = 1; // send a command
  regs.r[1] = midi_support_handle;
  regs.r[2] = ((n+1) << 24) | status | ((int)buff[0] << 8) | ((int)buff[1] << 16);
  _kernel_swi(MIDISupport_Send, &regs, &regs);
}


/*
 * midi_sup_all_notes_off
 * -------------------
 * resets gates on all channels
 */
void midi_sup_all_notes_off(void)
{
  _kernel_swi_regs regs;
  int i;

  for(i=0; i<16; i++)
  {
    if(mod.debug & (1<<DBG_MSG))
      if(mod.log)fprintf(mod.log, "%u: [%02X %02X] (all notes off)\n", mod.ticks, CONTROL | i, ALL_NOTES_OFF);

    regs.r[0] = 1; // send a command
    regs.r[1] = midi_support_handle;
    regs.r[2] = (3 << 24) | (CONTROL | i) | (ALL_NOTES_OFF << 8);
    _kernel_swi(MIDISupport_Send, &regs, &regs);
  }
}

/*
 * midi_sup_volume
 * ----------------
 * volume is 0 to 255
 */
void midi_sup_volume(int volume)
{
  _kernel_swi_regs regs;
  unsigned char data[8] = {0xf0,0x7f,0x7F,0x04,0x01,0,0,0xf7}; // sysex master volume
  int i;

  data[5] = (volume & 1) << 6; // lsb
  data[6] = volume >> 1;       // msb

  if((mod.debug & (1<<DBG_MSG)) && mod.log)
  {
    fprintf(mod.log, "%u: [", mod.ticks);
    for(i=0; i<sizeof(data); i++)
      fprintf(mod.log, "%02X%s", data[i], (i < (sizeof(data) - 1)) ? " " : "] (master volume)\n");
  }

  regs.r[0] = 2; // send a block
  regs.r[1] = midi_support_handle;
  regs.r[2] = (int)data;
  regs.r[3] = sizeof(data);
  _kernel_swi(MIDISupport_Send, &regs, &regs);
}

const midi_io_if_t midi_sup =
{
  midi_sup_reset,
  midi_sup_tx_byte,
  midi_sup_tx_word,
  midi_sup_all_notes_off,
  midi_sup_volume
};



