/*
  MidiSynth, MIDI synthesiser driver module for the MidiSupport system

 module.c
 --------

 16/8/22

 Module interface for the Synth and Midi Interpreter.
 4/2/23 modified to work as a driver for midi support.

*/

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

#define ENV_RATE     500 // envelope sample rate in Hz

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

// MIDISupport SWI's
#define MIDISupport_InstallDriver   0x4EE80
#define MIDISupport_RemoveDriver    0x4EE81
#define MIDISupport_DriverInfo      0x4EE82
#define MIDISupport_CreateDriver    0x4EE83
#define MIDISupport_Send            0x4EE84
#define MIDISupport_Receive         0x4EE85
#define MIDISupport_Connect         0x4EE86
#define MIDISupport_ConnectName     0x4EE87
#define MIDISupport_GetAddress      0x4EE88
#define MIDISupport_Insert          0x4EE89

// MTimer SWI's
#define MTimer_Register             0xd25c0
#define MTimer_Remove               0xd25c1

// Serivice calls
#define Service_MIDI           0x58
// Sub reason codes
#define Service_MIDIAlive         0
#define Service_MIDIDying         1
#define Service_MIDISupportAlive  4
#define Service_MIDISupportDying  5

// misc
#define TickerV                0x1c

// midi support driver flags
#define CAN_SEND                  1
#define CAN_RECEIVE               2
#define REQUIRES_BUFFERING        4
#define CAN_RECEIVE_COMMAND       8

module_t mod;

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


const char * const errstr[] =
{
  "Unknown error",
  "Cannot allocate memory",
  "Problem opening file",
  "Invalid data",
  "Invalid size",
  "Problem reading file",
  "No instrument",
  "Too many instruments",
  "Too many banks",
  "Too many waveforms",
  "Unknown heading",
  "Not editable",
  "Invalid crc",
  "Missing \"Melodics\" line",
  "Missing \"Bank Lo\" line",
  "Missing \"Drum Kits\" line",
  "Missing \"Program\" (Kit) line",
  "Missing \"Program\" line",
  "Missing \"Key\" line",
  "Invalid Program number",
  "Invalid Bank number",
  "Invalid Key number",
  "Invalid Kit number",
  "Instrument not referenced",
  "Instrument must have a name",
  "Invalid Instrument number",
  "Instrument deleted",
  "Instruments use this waveform, it cannot be deleted",
  "Waveform number out of range",
  "Waveform data not provided"
};

extern const char choices_file[];

/*
 * 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
 */
_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 + err;
  strcpy(oserr.errmess, errstr[err]);

  return &oserr;
}


/*
 * driver_info_handler
 * -------------------
 * Called directly from the MIDISupport module.
 * Information for display by !Midiman
 */
_kernel_oserror *driver_info_handler(_kernel_swi_regs *r, void *pw)
{
  r->r[0] = (int)Module_Title;
  r->r[1] = Module_VersionNumber;
  r->r[2] = (int)Module_Date;

  return NULL;
}


/*
 * driver_init_handler
 * -------------------
 * Called directly from the MIDISupport module.
 * Resets the synthesiser.
 */
_kernel_oserror *driver_init_handler(_kernel_swi_regs *r, void *pw)
{
  midiSynth_reset();

  return NULL;
}


/*
 * driver_receive_handler
 * ----------------------
 * Called directly from the MIDISupport module.
 * Sends received midi commands to the midi interpreter
 */
_kernel_oserror *driver_receive_handler(_kernel_swi_regs *r, void *pw)
{
  int cmd = r->r[0];
  int len = (cmd >> 24) & 3;

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

  return NULL;
}


/*
 * driver_register
 * ---------------
 */
static void driver_register(void *pw)
{
  _kernel_swi_regs regs;

  regs.r[0] = CAN_RECEIVE | CAN_RECEIVE_COMMAND;
  regs.r[1] = (int)driver_info;
  regs.r[2] = (int)driver_init;
  regs.r[3] = (int)driver_receive;
  regs.r[4] = (int)pw;
  if(_kernel_swi(MIDISupport_InstallDriver, &regs, &regs) == NULL)
  {
    mod.driver_number = regs.r[0];
    mod.support_receive = regs.r[1];
    mod.support_give = regs.r[2];
    mod.support_pw = regs.r[3];
    mod.registered = TRUE;
  }
}


/*
 * driver_deregister
 * -----------------
 */
static void driver_deregister(void *pw)
{
  _kernel_swi_regs regs;

  regs.r[1] = mod.driver_number;
  _kernel_swi(MIDISupport_RemoveDriver, &regs, &regs);

  mod.registered = FALSE;
}


/*
 * module_init
 * -----------
 * Module initialisation. Initialises the synth and audio output.
 */
_kernel_oserror *module_init(const char *tail, int podule_base, void *pw)
{
  _kernel_oserror *err = NULL;
  _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((err = _kernel_swi(OS_Module, &regs, &regs)))
      return err;
  }

  // set these before initialising the synth as reading the choices may set the timeout
  mod.sleep_timer = 0;
  mod.sleep_timeout = TICKER_RATE * 10; // default 10 second timeout

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

  // 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((err = _kernel_swi(SharedSound_InstallHandler,&regs,&regs)))
    return err;
  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);

  // Register with timer to drive the synth
  regs.r[0] = (int)timer_callback;
  regs.r[1] = (int)pw;
  regs.r[2] = 1000 / TICKER_RATE; // 1ms ticker period
  regs.r[3] = (int)Module_Title;
  _kernel_swi(MTimer_Register, &regs, &regs);

  // If Midi Support is loaded, register with it, if not we will
  // register when we receive its startup service call.
  regs.r[0] = 18;
  regs.r[1] = (int)"MIDISupport";
  if(!_kernel_swi(OS_Module, &regs, &regs))
    driver_register(pw);

  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.audio.handler_id)
  {
    regs.r[0] = mod.audio.handler_id;
    _kernel_swi(SharedSound_RemoveHandler,&regs,&regs);
  }

  // If we are registered with Midi Support, remove ourselves
  if(mod.registered)
    driver_deregister(pw);

  // Remove our timer entry
  regs.r[0] = (int)timer_callback;
  _kernel_swi(MTimer_Remove, &regs, &regs);

  midiSynth_term();

  return NULL;
}


/*
 * module_swi
 * ----------
 */
_kernel_oserror *module_swi(int number, _kernel_swi_regs *r, void *pw)
{
  int
    err = 0,
    *blk = (int *)r->r[1], // for returned data
    n = r->r[2],
    m = r->r[3];

  static int cur_instr;

  switch(number + MIDISynth_00)
  {
    case MIDISynth_Control: // Read/Write general controls
      switch(r->r[0])
      {
        case 1: // number of generators (polyphony)
          if((n < 4) || (n > NUM_GENS))
            break;
          syn.num_gens = n;
          midiSynth_reset();
          break;

        case 2: // Glide time scale factor
          if((n >= 0) && (n <= 1000))
            syn.scale_factor = n;
          break;

        case 3: // Sleep timeout
          if((n >= 0) && (n <= 3600))
            mod.sleep_timeout = n * TICKER_RATE;
          break;

        case 4: // Flags ( = &~n ^m )
          n &= FLAG_MASK;
          m &= FLAG_MASK;
          syn.switches = (syn.switches & ~(n << MONO_AUDIO)) ^ (m << MONO_AUDIO);
          if(n & (((1<<PATCH_NO_KITS)|(1<<PATCH_BY_BANK))>>MONO_AUDIO))
            update_list(&syn.idat); // some flags affect patch list format
          break;

        case 5: // Synthesiser reset
          midiSynth_reset();
          break;

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

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

        case 8: // Percussion Channel, default & current
          if((n >= 1) && (n <= 16))
            syn.def_perc_chan = syn.percussion_chan = n - 1;
          break;

        case 9: // Master Volume
          if((n >= 0) && (n < 1024))
            syn.master_volume = n;
          break;

        case 10: // Master Balance
          if((n >= 0) && (n < 1024))
            syn.master_balance = n;
          break;

        case 11: // Master Coarse Tuning
          if((n >= -64) && (n < 64))
            syn.master_coarse = n;
          break;

        case 12: // Master Fine Tuning
          if((n >= -8192) && (n < 8192))
            syn.master_fine = n;
          break;

        case 13: // Save Choices
          choices_write(&syn, choices_file);
          break;
      }
      blk[0] = syn.num_gens;
      blk[1] = syn.scale_factor;
      blk[2] = mod.sleep_timeout / TICKER_RATE;
      blk[3] = ((syn.switches >> MONO_AUDIO) & FLAG_MASK);
      blk[4] = syn.bank_override;
      blk[5] = syn.kit_override;
      blk[6] = syn.percussion_chan;
      blk[7] = syn.def_perc_chan;
      blk[8] = syn.sample_rate;
      blk[9] = syn.master_volume;
      blk[10] = syn.master_balance;
      blk[11] = syn.master_coarse;
      blk[12] = syn.master_fine;
      break;

    case MIDISynth_Edit: // Provides control, and access, to the user instrument.
                         // Also operations on Banks and the whole Sound Set.
      syn.config_ok = 1;
      switch(r->r[0])
      {
        case 1: // Load user from the given patch
          err = load_instrument(n);
          break;

        case 2: // Load user from the given instrument
          {
            int k;
            patch_t p;

            if((n < 0) || (n >= syn.idat.num_instruments))
              err = -INVALID_INSTRUMENT_NUMBER;
            else if(syn.idat.instrument[n].name[0] == 0)
              err = -INSTRUMENT_DELETED;

            if(err < NO_ERROR_)
              break;

            syn.user = syn.idat.instrument[n];

            // find the first patch that references the instrument
            for(k=0; k < syn.kbd_patch.num; k++)
            {
              p = syn.kbd_patch.list[k];
              if(p.hi < PERCUSSION_BANK)
              {
                if(syn.idat.bank[syn.idat.banks[p.lo]-1].ins[p.prg]-1 == n)
                  break;
              }
              else if(p.hi > PERCUSSION_BANK)
              {
                if(syn.idat.bank[syn.idat.kits[p.prg]-1].ins[p.lo]-1 == n)
                  break;
              }
            }

            if(k < syn.kbd_patch.num)
              syn.kbd_patch.cur = k;
            else // instrument does not have a bank/kit location
              err = -INVALID_INSTRUMENT_REF;
          }
          break;

        case 3: // Save user to the given patch number
          err = update_instrument(&syn.idat, n);
          break;

        case 4: // Save user as new instrument at the given location: Hi,Lo,Prg
          // r2 bits: 23-16 prg, 15-8 lo, 7-0 hi
          // r3 = 0: Create new instrument from user and locate it at location in r1
          // r3 = 1: Just locate current (unedited) instrument
          // r3 = 2: move instrument at current patch to the given location in r1
          err = save_instrument(&syn.idat, n, m, cur_instr);
          break;

        case 5: // Save user as new instrument in the next bank up
          err = save_new(&syn.idat, syn.kbd_patch.cur);
          break;

        case 6: // Delete the current instrument
          // r2 = 0: delete instrument and all references
          // r2 = 1: clear current patch reference only
          err = clear_instrument(&syn.idat, syn.kbd_patch.cur, n);
          break;

        case 7: // Save Sound Set to file.
          err = save_data(&syn.idat, (char*)n, ALL);
          break;

        case 8: // Save Sound Set without waveforms to file.
          err = save_data(&syn.idat, (char*)n, NO_WAVES);
          break;

        case 9: // Save Sound Set in a variety of formats
          err = save_source(&syn.idat, (char*)n, m);
          break;

        case 10: // Load the default Sound Set.
          err = load_defaults(&syn.idat);
          break;

        case 11: // Load the Sound Set from file.
          err = load_file(&syn.idat, (char*)n);
          break;

        case 12: // Load a CSV file and create a Sound Set.
          err = load_csv(&syn.idat, (char*)n);
          break;

        case 13: // Switches to control the use of the edit waveform by the user instrument
          n &= 7;
          m &= 7;
          syn.switches = (syn.switches & ~(n << EDIT_WAVE_OSC1)) ^ (m << EDIT_WAVE_OSC1);
          harm_to_wave((harm_t *)r->r[4], syn.edt_wave);
          break;

        case 14: // Save / delete wave harmonics data
          // n = pointer to harmonic data
          // m = array index
          err = save_harm_wave(&syn.idat, (harm_t *)n, m);
          break;

      }

      blk[0] = (int)&syn.user;
      blk[2] = (int)syn.idat.harm;
      blk[3] = syn.idat.num_waves;
      blk[4] = syn.kbd_patch.num;
      blk[7] = (int)syn.idat.name;
      blk[9] = syn.idat.num_instruments;

      if(err == -INVALID_INSTRUMENT_REF) // no patch to report
      {
        blk[1] = 0; // patch number
        blk[5] = 0; // patch values
        blk[6] = 0; // bank name
        blk[8] = n; // instrument number
      }
      else
      {
        blk[1] = syn.kbd_patch.cur;
        {
          patch_t p = syn.kbd_patch.list[syn.kbd_patch.cur];
          blk[5] = *(int *)(&p);
          bank_t *b;
          if(p.hi < PERCUSSION_BANK)
          {
            b = &syn.idat.bank[syn.idat.banks[p.lo]-1];
            blk[8] = b->ins[p.prg]-1;
          }
          else
          {
            b = &syn.idat.bank[syn.idat.kits[p.prg]-1];
            blk[8] = b->ins[p.lo]-1;
          }
          blk[6] = (int)b->name;
        }
      }
      cur_instr = blk[8]; // used when relocating unused instruments
      break;

    case MIDISynth_Note:  // Sends note on/off commands to the user instrument.
      {
        int status = r->r[0] & 0xf0;
        if((status == NOTE_OFF) || (status == NOTE_ON))
        {
          mod.sleep_timer = 0; // clear timeout on receipt of data
          int key = (r->r[0] >> 8) & 0x7f;
          int channel = (r->r[0] & 0xf) + KBD_CHAN_OFFSET; // user instrument channel block
          int velocity = (r->r[0] >> 16) & 0x7f;
          int on = (status == NOTE_ON) && (velocity > 0);
          poly_action_key(key, on, channel, velocity);
        }
      }
      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_MidiSynth:
      if(argc > 0)
        read_options(argc, arg_string, pw);
      break;
  }

  return NULL;
}


/*
 * inform_config
 * -------------
 * Sends a message to the config app, if running, whenever any displayed parameters are changed.
 * This can be due to a received midi message, or for sample rate by another application.
 */
void inform_config(void)
{
  if(!syn.config_ok)
    return; // fix's crashing on startup before desktop running

  int blk[11], handle;
  _kernel_swi_regs regs;

  // get config task handle
  regs.r[0] = 0;
  handle = 0;
  while ((regs.r[0] >= 0) && !handle)
  {
    regs.r[1] = (int) blk;
    regs.r[2] = 4 * sizeof(int);
    if(_kernel_swi(TaskManager_EnumerateTasks, &regs, &regs) == 0)
      if (strcmp((char*) blk[1], "Midisyn") == 0)
        handle = blk[0];
  }
  if (!handle)
    return;

  // tell the config task of changes
  regs.r[0] = 17; // User message
  regs.r[1] = (int) blk;
  regs.r[2] = handle;
  blk[0] = sizeof(blk);
  blk[3] = 0;
  blk[4] = 12345;
  blk[5] = syn.sample_rate;
  blk[6] = syn.percussion_chan;
  blk[7] = syn.master_volume;
  blk[8] = syn.master_balance;
  blk[9] = syn.master_coarse;
  blk[10] = syn.master_fine;
  _kernel_swi(Wimp_SendMessage, &regs, &regs);
}


/*
 * timer_callback_handler
 * ----------------------
 * Fills the audio buffer with synth samples, effectively running the synthesiser.
 * Checks for changes in sample rate.
 * r0 = our timer period, return this unchanged
 * r1 = ticks since initialisation
 * r2 = ticks since last call
 */
_kernel_oserror *timer_callback_handler(_kernel_swi_regs *r, void *pw)
{
  _kernel_swi_regs regs;
  static unsigned int tick_acc;

  mod.ticks = r->r[1];

  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);
    inform_config(); // inform synth front end
  }

  // 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
      return NULL;
    }
    else // not timed out yet
      mod.sleep_timer++;
  }
  // end of sleep timeout

  tick_acc += r->r[2];
  while(tick_acc >= (TICKER_RATE / ENV_RATE))
  {
    tick_acc -= (TICKER_RATE / ENV_RATE);
    synth_control();
  }

  unsigned int count = 0;
  // work out the number of samples that should be required every callback and add about
  // 10% so the buffer slowly fills up. This saves having a load of samples to synthesise
  // every time the sound dma partially empties the buffer and so spreads the synth loading
  // more evenly. The spreading also improves midi message execution timing.
  // (note: maths assumes a ticker rate of 1kHz)
  unsigned int req_samples = ((syn.sample_rate + (syn.sample_rate >> 3)) >> 10) * r->r[2]; // +9.86%
  while(((next = (write < end) ? write + 1 : 0) != read) && (count < req_samples))
  {
    buff[write] = midiSynth_sample();
    write = next;
    count++;
  }
  mod.audio.write = write; // update end marker

  if(mod.debug & (1<<DBG_CALLBACKS))
    if(mod.log)fprintf(mod.log, "%d:%d, ", r->r[2], count);

  return NULL;
}


/*
 * service_callback_handler
 * ------------------------
 */
_kernel_oserror *service_callback_handler(_kernel_swi_regs *r, void *pw)
{
  if(!mod.registered)
    driver_register(pw);

  return NULL;
}


/*
 * service_midi
 * ------------
 * Handles MIDI service calls
 */
void service_midi(int service, _kernel_swi_regs *r, void *pw)
{
  _kernel_swi_regs regs;

  if(service == Service_MIDI)
  {
    if((r->r[0] == Service_MIDISupportAlive) && !mod.registered)
    {
      regs.r[0] = (int)service_callback;
      regs.r[1] = (int)pw;
      _kernel_swi(OS_AddCallBack, &regs, &regs);
    }
    else if((r->r[0] == Service_MIDISupportDying) && mod.registered)
      mod.registered = FALSE;
  }
}


/*
 * 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++) == ' '); // skip spaces before option
    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 time scale factor: %d\n", syn.scale_factor);
          printf("  polyphony: %d\n", syn.num_gens);
          printf("  sleep timeout: %d seconds\n", mod.sleep_timeout / TICKER_RATE);
          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("  Midi Support driver number: %d\n", mod.driver_number);
          }
          printf("\n");
          break;

        case 'N': // number of generators (polyphony)
          n = atoi(arg_string);
          if((n >= 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();
          break;

        case 'W': // Swap audio channels, 1 = swapped, 0 = normal
          syn.switches = (syn.switches & ~(1<<L_R_SWAP)) | ((*arg_string & 1) << L_R_SWAP);
          break;

        case 'G': // Glide time scale factor
          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 * TICKER_RATE;
          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 >= ' ');
}







