/*
 module.c
 --------

 16/8/22

 Module interface for the Synth and Midi Interpreter.

*/

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

// SharedSound SWI numbers
#define SharedSound_InstallHandler  0x4b440
#define SharedSound_RemoveHandler   0x4b441
#define SharedSound_HandlerInfo     0x4b442
#define SharedSound_HandlerVolume   0x4b443
#define SharedSound_SampleRate      0x4b446

// Serivice calls
#define Service_MIDI           0x58
#define Service_MIDIAlive         0
#define Service_MIDIDying         1

// misc
#define TickerV                0x1c

module_t mod;

void audio_buffer_fill(void); // buffer.s
static int find_length(unsigned int data);
static int read_set_sys_rate(int default_rate);
static void read_options(int argc, const char *arg_string, void *pw);

static const _kernel_oserror err_no_audio  = {0x20422, "Cannot register audio driver"};
static const _kernel_oserror err_no_ssound = {0x20423, "Cannot load SharedSound"};

/*
 * module_init
 * -----------
 * Module initialisation. Initialises the synth and audio output.
 */
_kernel_oserror *module_init(const char *tail, int podule_base, void *pw)
{
  _kernel_swi_regs regs;

  // ensure SharedSound is present
  regs.r[0] = 18;
  regs.r[1] = (int)"SharedSound";
  if(_kernel_swi(OS_Module, &regs, &regs))
  {
    regs.r[0] = 1;
    regs.r[1] = (int)"System:Modules.SSound";
    if(_kernel_swi(OS_Module, &regs, &regs))
      return (_kernel_oserror*)&err_no_ssound;
  }

  // Set system sample rate equal to or nearest to the default synth rate
  // then set synth rate equal to it. If the rates are the same, the fractional
  // step rate will be 1.0 which is best, although the buffer fill routine will
  // cope with rate mismatches.
  midiSynth_init( read_set_sys_rate(44100) );

  mod.audio.size = AB_SIZE;
  mod.audio.buffer = mod.buffer;

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

  // Register SharedSound handler
  regs.r[0] = (int)audio_buffer_fill;
  regs.r[1] = (int)&mod.audio; // our parameter, data structure address for audio_buffer_fill
  regs.r[2] = 1;
  regs.r[3] = (int)"MidiSynth";
  regs.r[4] = 0; // immediate handler type
  if (_kernel_swi(SharedSound_InstallHandler,&regs,&regs))
    return (_kernel_oserror*)&err_no_audio;
  mod.audio.handler_id = regs.r[0];

  // Inform SharedSound of our sample rate, so it can provide us with
  // the fractional step rate, which should be 1.0.
  regs.r[1] = syn.sample_rate << 10;
  _kernel_swi(SharedSound_SampleRate,&regs,&regs);

  // Issue startup service call
  regs.r[0] = Service_MIDIAlive;
  regs.r[1] = Service_MIDI;
  _kernel_swi(OS_ServiceCall, &regs, &regs);

  mod.sleep_timer = 0;
  mod.sleep_timeout = 1000; // default 10 second timeout

  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);

  if(mod.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);

  if(mod.audio.handler_id)
  {
    regs.r[0] = mod.audio.handler_id;
    _kernel_swi(SharedSound_RemoveHandler,&regs,&regs);
  }

  regs.r[0] = Service_MIDIDying;
  regs.r[1] = Service_MIDI;
  _kernel_swi(OS_ServiceCall, &regs, &regs);

  midiSynth_term();

  return NULL;
}

/*
 * tx_command
 * ----------
 * Sends a midi command to the interpreter
 * cmd = up to 3 bytes of midi data, lsb = status
 * len = number of significant bytes in cmd to send
 */
void tx_command(unsigned int cmd, int len)
{
  mod.sleep_timer = 0; // clear timeout on receipt of data
  print_midi_msg(cmd, len); // if enabled, log message
  while(len--)
  {
    midiSynth_midi_in(cmd & 0xff);
    cmd >>= 8;
  }
}


/*
 * module_swi
 * ----------
 * SWI handler for MIDI data and control.
 */
_kernel_oserror *module_swi(int number, _kernel_swi_regs *r, void *pw)
{
  int len, r0 = r->r[0], r1 = r->r[1];

  if(mod.debug & (1<<DBG_SWI))
    if(mod.log)fprintf(mod.log, "swi %2d r0 %08X r1 %08X\n", number, r0, r1);

  switch(number + MIDI_00)
  {
    case MIDI_SetTxChannel:
      if(r0)
        mod.tx_chan = (r0 - 1) & 0xf; // accept all ports
      else
        r->r[0] = mod.tx_chan + 1;
      break;

    case MIDI_TxByte:
      mod.sleep_timer = 0; // clear timeout on receipt of data
      midiSynth_midi_in(r0 & 0xff); // accept all ports
      break;

    case MIDI_TxCommand:
      len = (r0 >> 24) & 3; // accept all ports
      if(len == 0)
      {
        len = find_length(r0);
        r0 = (r0 & 0xffffff) | (len << 24); // ensure length is always defined
      }

      // check data validity
      if(len == 0)
        break; // zero length
/*
      if((r0 & 0x80) == 0)
        break; // invalid status byte
      if((len >= 2) && ((r0 & 0x8000) != 0))
        break; // invalid 1st data byte
      if((len >= 3) && ((r0 & 0x800000) != 0))
        break; // invalid 2nd data byte
*/
      if(mod.fast_clk && (r1 > 0))
        r->r[0] = queue_write(r0, r1);
      else
      {
        tx_command(r0, len);
        r->r[0] = 512; // Spec. doesn't say this needs to be returned
      }
      break;

    case MIDI_TxNoteOff:
      tx_command(NOTE_OFF | mod.tx_chan | (r0 << 8) | (r1 << 16), 3);
      break;

    case MIDI_TxNoteOn:
      tx_command(NOTE_ON | mod.tx_chan | (r0 << 8) | (r1 << 16), 3);
      break;

    case MIDI_TxPolyKeyPressure:
      tx_command(KEY_PRESSURE | mod.tx_chan | (r0 << 8) | (r1 << 16), 3);
      break;

    case MIDI_TxControlChange:
      tx_command(CONTROL | mod.tx_chan | (r0 << 8) | (r1 << 16), 3);
      break;

    case MIDI_TxLocalControl:
      tx_command(CONTROL | mod.tx_chan | (LOCAL_KEYBOARD_on << 8) | (r0 << 16), 3);
      break;

    case MIDI_TxAllNotesOff:
      tx_command(CONTROL | mod.tx_chan | (ALL_NOTES_OFF << 8), 2);
      break;

    case MIDI_TxOmniModeOff:
      tx_command(CONTROL | mod.tx_chan | (OMNI_MODE_OFF << 8), 2);
      break;

    case MIDI_TxOmniModeOn:
      tx_command(CONTROL | mod.tx_chan | (OMNI_MODE_ON << 8), 2);
      break;

    case MIDI_TxMonoModeOn:
      tx_command(CONTROL | mod.tx_chan | (MONO_OPERATION << 8) | (r0 << 16), 3);
      break;

    case MIDI_TxPolyModeOn:
      tx_command(CONTROL | mod.tx_chan | (POLY_OPERATION << 8), 2);
      break;

    case MIDI_TxProgramChange:
      tx_command(PROGRAM | mod.tx_chan | (r0 << 8), 2);
      break;

    case MIDI_TxChannelPressure:
      tx_command(CHAN_PRESSURE | mod.tx_chan | (r0 << 8), 2);
      break;

    case MIDI_TxPitchWheel:
      tx_command(PITCH_WHEEL | mod.tx_chan | ((r0 & 0x7f) << 8) | ((r0 & 0x3f80) << 9), 3);
      break;

    case MIDI_TxSongPositionPointer:
      tx_command(SONG_POSITION | ((r0 & 0x7f) << 8) | ((r0 & 0x3f80) << 9), 3);
      break;

    case MIDI_TxSongSelect:
      tx_command(SONG_SELECT | (r0 << 8), 2);
      break;

    case MIDI_TxTuneRequest:
      tx_command(TUNE_REQUEST, 1);
      break;

    case MIDI_TxStart:
      tx_command(START, 1);
      break;

    case MIDI_TxContinue:
      tx_command(CONTINUE, 1);
      break;

    case MIDI_TxStop:
      tx_command(STOP, 1);
      break;

    case MIDI_TxSystemReset:
      midiSynth_reset();
      break;

    case MIDI_Init:
      if(r0 == 0)
      {
        mod.fast_clk = 0;
        midiSynth_reset();
      }
      else
      {
        if(r0 & (1<<3))
          queue_clear();
      }
      r->r[0] = 1; // we provide 1 active port, but will play all 4 anyway
      break;

    case MIDI_FastClock:
      {
        int clk = queue_clock(0);
        if(r0 < 0)
          r->r[0] = clk;
        if(r0 == 0)
          mod.fast_clk = 0;
        else // (r0 > 0)
        {
          mod.fast_clk = 1;
          queue_set(r1);
        }
        r->r[1] = clk;
      }
      break;

// Specific to this synth module:

    case MIDI_SynthControl:
      /*
        r[0] = 0, Turn off all generator gates
        r[0] = 1. r[1] = Number of generators (polyphony), 4 to NUM_GENS, default 32
        r[0] = 2, r[1] = Mono/Stereo audio, 0 = stereo, 1 = mono, default 0
        r[0] = 3, r[1] = Swap L/R audio channels, 0 = normal, 1 = swapped, default 0
        r[0] = 4, r[1] = Glide time scale factor, 0 to 1000, default 100
        r[0] = 5, r[1] = Sleep timeout in seconds, 0 to 3600, 0 = disable, default 10
        r[0] = 6, r[1] = GM variation banks,  0 = disable, 1 = allowed, default 0
      */
      switch(r0)
      {
        case 0: // Turn off all generator gates
          reset_all_gates(REMOTE_CHAN); // (port)
          break;

        case 1: // Number of generators (polyphony), 4 to 128, default 32
          LIMIT(4, r1, NUM_GENS);
          syn.num_gens = r1;
          midiSynth_reset();
          break;

        case 2: // Mono/Stereo audio, 0 = stereo, 1 = mono, default 0
          syn.switches = (syn.switches & ~(1<<MONO_AUDIO)) | ((r1 & 1) << MONO_AUDIO);
          break;

        case 3: // Swap L/R audio channels, 0 = normal, 1 = swapped, default 0
          syn.switches = (syn.switches & ~(1<<L_R_SWAP)) | ((r1 & 1) << L_R_SWAP);
          break;

        case 4: // Glide time scale factor, 0 to 1000, default 100
          LIMIT(0, r1, 1000);
          syn.scale_factor = r1;
          break;

        case 5: // Sleep timeout in seconds, 0 to 3600, 0 = disable, default 10
          LIMIT(0, r1, 3600);
          mod.sleep_timeout = r1 * 100;
          break;

        case 6: // variation banks,  0 = disable, 1 = allowed, default 0
          syn.switches = (syn.switches & ~(1<<ALL_BANKS)) | ((r1 & 1) << ALL_BANKS);
          break;

        case 7: // variation kits,  0 = disable, 1 = allowed, default 0
          syn.switches = (syn.switches & ~(1<<ALL_KITS)) | ((r1 & 1) << ALL_KITS);
          break;

        case 8: // Bank Override
          if((r1 >= 0) && (r1 < 128))
            syn.bank_override = r1;
          break;

        case 9: // Drum Kit Override
          if((r1 >= 0) && (r1 < 128))
            syn.kit_override = r1;
          break;

      }
      break;

// These provide replies, but have no further effect:

    case MIDI_InqSongPositionPointer:
      r->r[0] = 0;
      r->r[1] = (mod.fast_clk) ? (1<<2)|(1<<3) : 0;
      break;

    case MIDI_InqBufferSize:
      r->r[0] = 512;
      break;

    case MIDI_InqError:
      r->r[0] = 0;
      break;

    case MIDI_RxByte:
      r->r[0] = 0;
      break;

    case MIDI_RxCommand:
      r->r[0] = 0;
      break;

    case MIDI_SetMode:
      r->r[0] = 1; // always, mode 1, Omni on, Poly
      r->r[1] = 0x101; // always, basic channel 1, 1 momo channel
      break;

// These do nothing
//    case MIDI_SetTxActiveSensing:
//    case MIDI_IgnoreTiming:
//    case MIDI_SoundEnable:
//    case MIDI_SynchSoundScheduler:
//    case MIDI_SetBufferSize:
//    case MIDI_Interface:
//      break;

  }

  return NULL;
}

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

// These do nothing
//    case CMD_MidiSound:
//    case CMD_MidiTouch:
//    case CMD_MidiChannel:
//    case CMD_MidiMode:
//    case CMD_MidiStart:
//    case CMD_MidiStop:
//    case CMD_MidiContinue:
//      break;

  }

  return NULL;
}

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

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

  return VECTOR_PASSON;
}

/*
 * ticker_callback_handler
 * -----------------------
 * Fills the audio buffer with synth samples, effectively running the synthesiser.
 * Checks for changes in sample rate.
 */
_kernel_oserror *ticker_callback_handler(_kernel_swi_regs *r, void *pw)
{
  _kernel_swi_regs regs;

  int read   = mod.audio.read; // modified by interrupt (volatile)
  int write  = mod.audio.write;
  unsigned int *buff  = mod.audio.buffer;
  int end    = mod.audio.size - 1;
  int next;

  // check for changes in the system sample rate
  int sys_rate = mod.audio.sys_rate >> 10;
  if(syn.sample_rate != sys_rate)
  {
    if(mod.log)fprintf(mod.log, "Sample rate changed from %d to %d\n", syn.sample_rate, sys_rate);
    calculate_tables(sys_rate);
    regs.r[0] = mod.audio.handler_id;
    regs.r[1] = syn.sample_rate << 10;
    _kernel_swi(SharedSound_SampleRate,&regs,&regs);
  }

  queue_clock(10); // advance clock by 10ms
  queue_read(); // check for any scheduled events

  // sleep timeout
  if(mod.sleep_timeout > 0)
  {
    if(mod.sleep_timer >= mod.sleep_timeout)
    {
      if(mod.sleep_timer == mod.sleep_timeout)
      {
        // clear buffer before sleeping
        for(write = 0; write <= end; write++)
          buff[write] = 0;
        mod.sleep_timer++;
      }
      mod.audio.write = (read > 0) ? read - 1 : end; // make buffer look full
      mod.callbacks = 0;
      return NULL;
    }
    else // not timed out yet
      mod.sleep_timer++;
  }
  // end of sleep timeout

  int count = 0; // testing
  while((next = (write < end) ? write + 1 : 0) != read)
  {
    buff[write] = midiSynth_sample();
    write = next;
    count++; // testing
  }
  mod.audio.write = write; // update end marker

  if(mod.debug & (1<<DBG_CALLBACKS))
    if(mod.log)fprintf(mod.log, "%d:%d, ", mod.callbacks, count); // testing

  mod.callbacks = 0;

  return NULL;
}

/*
 * find_length
 * -----------
 * Returns the number of midi message bytes in the passed 32 bit word.
 */
static int find_length(unsigned int data)
{
  switch(data & 0xf0)
  {
    case NOTE_OFF:
    case NOTE_ON:
    case KEY_PRESSURE:
    case CONTROL:
    case PITCH_WHEEL: return 3;

    case PROGRAM:
    case CHAN_PRESSURE: return 2;

    case 0xf0:
      switch(data)
      {
        case SONG_POSITION: return 3;

        case SONG_SELECT: return 2;

        case TUNE_REQUEST:
        case TIMING_CLOCK:
        case START:
        case CONTINUE:
        case STOP:
        case ACTIVE_SENSING:
        case SYSTEM_RESET: return 1;
      }
      break;
  }

  return 0;
}

/*
 * read_set_sys_rate
 * -----------------
 * Reads the available system sample rates. Sets the rate nearest to the
 * default synth rate. Returns the new rate.
 */
static int read_set_sys_rate(int default_rate)
{
  _kernel_swi_regs regs;
  int i, num, rate = default_rate, idx = 0, diff = 0x7fffffff;

  regs.r[0] = 1; // read current rate and index
  _kernel_swi(Sound_SampleRate, &regs, &regs);
  if((regs.r[2] >> 10) == default_rate)
    return default_rate; // no change needed

  regs.r[0] = 0; // read number of available rates
  _kernel_swi(Sound_SampleRate, &regs, &regs);
  num = regs.r[1];

  for(i=0; i<num; i++)
  {
    regs.r[0] = 2; // read rate from index
    regs.r[1] = i;
    _kernel_swi(Sound_SampleRate, &regs, &regs);
    int r = regs.r[2] >> 10;
    int d = r - default_rate;
    if(d < 0)
      d = -d;
    if(diff > d)
    {
      diff = d;
      idx = i;
      rate = r;
    }
  }

  regs.r[0] = 3; // set rate from index
  regs.r[1] = idx;
  _kernel_swi(Sound_SampleRate, &regs, &regs);

  return rate;
}

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

  do
  {
    while((c = *arg_string++) == ' ');
    if(c == '-')
      switch((c = *arg_string++) & ~0x20)
      {
        case 'I': // Info
          printf("\nSynth\n");
          TimestampToDate(syn.idat.date, &d);
          printf("  sound set: %s (%d/%d/%d %d:%02d:%02d)\n", syn.idat.name,
                  (int)d.day, (int)d.month, (int)d.year, (int)d.hour, (int)d.minute, (int)d.second);
          printf("  audio output: %s\n",
                  (syn.switches & (1<<MONO_AUDIO)) ? "Mono" :
                  (syn.switches & (1<<L_R_SWAP)) ? "Stereo L/R swapped" : "Stereo");

          printf("  glide scaler: %d\n", syn.scale_factor);
          printf("  polyphony: %d\n", syn.num_gens);
          printf("  variation banks: %s\n", (syn.switches & (1<<ALL_BANKS)) ? "Allowed" : "Disabled");
          printf("  variation kits: %s\n", (syn.switches & (1<<ALL_KITS)) ? "Allowed" : "Disabled");
          printf("  bank override: %d\n", syn.bank_override);
          printf("  kit override: %d\n", syn.kit_override);
          printf("  sleep timeout: %d seconds\n", mod.sleep_timeout / 100);
          printf("  master volume: %d\n", syn.master_volume);
          printf("  master balance: %d\n", syn.master_balance);
          printf("  master coarse tuning: %d\n", syn.master_coarse);
          printf("  master fine tuning: %d\n", syn.master_fine);
          printf("  sample rate: system %d, synth %d\n", mod.audio.sys_rate >> 10, syn.sample_rate);
          if(*arg_string == '+')
          {
            arg_string++;
            _kernel_swi_regs regs;

            regs.r[0] = 0;
            _kernel_swi(SharedSound_HandlerInfo, &regs, &regs);
            while(regs.r[0] != 0)
            {
              n = regs.r[0];
              _kernel_swi(SharedSound_HandlerInfo, &regs, &regs);
              printf("  SharedSound handler, %s, id %d, flags %d, rate %d, type %d, vol %d\n",
                (char *)regs.r[2], n, regs.r[1], regs.r[3] / 1024, regs.r[4], regs.r[5]);
            }

            printf("  system buffer, base &%X, size %d, rate %d, step &%X.%06X\n",
                 mod.audio.sys_base, (mod.audio.sys_end - mod.audio.sys_base)/4,
                 mod.audio.sys_rate / 1024,
                 mod.audio.sys_step >> 24, mod.audio.sys_step & 0xffffff);
          }
          printf("\n");
          break;

        case 'N': // number of generators (polyphony)
          n = atoi(arg_string);
          LIMIT(4, n, NUM_GENS);
          syn.num_gens = n;
          midiSynth_reset();
          break;

        case 'S': // Stereo/Mono audio, 1 = stereo, 0 = mono
          syn.switches = (syn.switches & ~(1<<MONO_AUDIO)) | (((*arg_string & 1) ^ 1) << MONO_AUDIO);
          break;

        case 'R': // Synthesiser reset
          midiSynth_reset();
          printf("Synthesiser reset.\n");
          break;

        case 'W': // Swap audio channels
          syn.switches ^= (1<<L_R_SWAP);
          break;

        case 'G': // Glide time scale factor
          if((*arg_string >= '0') && (*arg_string <= '9'))
          {
            n = atoi(arg_string);
            if((n >= 0) && (n <= 1000))
              syn.scale_factor = n;
          }
          break;

        case 'T': // Sleep timeout
          n = atoi(arg_string);
          if((n >= 0) && (n <= 3600))
            mod.sleep_timeout = n * 100;
          break;

        case 'V': // variation banks
          syn.switches = (syn.switches & ~(1<<ALL_BANKS)) | ((*arg_string & 1) << ALL_BANKS);
          break;

        case 'A': // variation kits
          syn.switches = (syn.switches & ~(1<<ALL_KITS)) | ((*arg_string & 1) << ALL_KITS);
          break;

        case 'K': // Kit override
          n = atoi(arg_string);
          if((n >= 0) && (n <= 127))
            syn.kit_override = n;
          break;

        case 'B': // Bank override
          n = atoi(arg_string);
          if((n >= 0) && (n <= 127))
            syn.bank_override = n;
          break;

        case 'O': // open log file
          if(mod.log)
            printf("Log file already open\n");
          else
          {
            mod.log = fopen("logS", "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 >= ' ');
}







