/*
  !MidiPlay   A MIDI synthesiser and file player.

  audio.c -  DiskSample interface

  12/2021 created
  15/7/22 added SharedSound interface

  These interfaces provide 3 functions,
    Initialisation, Termination, Audio buffer fill.
*/

#include "wimp.h"
#include "lib.h"
#include "ro_main.h"
#include "midisyn.h"
#include "main.h"

#define NUM_CHANS     2 // stereo
#define METER_RATE   20 // meter update rate in Hz
static int meter_rate;  // holds sample rate divisor

void midi_file_player(void);
/*
 * reset_sleep_timer
 * -----------------
 * clears the sleep timer.
 */
void reset_sleep_timer(void)
{
  ro.sleep_timer = 0;
}


/*
 * level_meter
 * -----------
 * This is used by both audio interfaces. It expects to be called at the sample rate
 * If clear is non zero the meters are instantly cleared.
 */
static void level_meter(int sample, int clear)
{
  static int peak[NUM_CHANS], peak_dB[NUM_CHANS], count;
  int i, data[NUM_CHANS];

  if(clear)
  {
    count = meter_rate;
    for(i=0; i<NUM_CHANS; i++)
      peak[i] = peak_dB[i] = 0;
  }

  data[0] = sample >> 16;
  data[1] = (sample << 16) >> 16;

  for(i=0; i<NUM_CHANS; i++)
  {

    int d = data[i];
    if(d < 0)
      d = -d;
    if(d > peak[i])
      peak[i] = d;
  }

  if(++count > meter_rate)
  {
    count = 0;

    static const int db_tab[METER_RANGE] =
      {327,367,412,462,519,582,653,733,823,923,1036,1162,1304,1463,1642,1842,2067,2319,
       2602,2920,3276,3676,4125,4628,5193,5826,6537,7335,8230,9234,10361,11626,13044,
       14636,16422,18426,20674,23197,26027,29203,32767};

    for(i=0; i<NUM_CHANS; i++)
    {
      // dB & peak_dB values are as follows: 0 = -40dB, 40 = 0dB (16 bit clipping level)
      int dB = 0;
      while((dB < METER_RANGE) && (db_tab[dB] < peak[i]))
        dB++;

      peak[i] = 0;

      if(dB > peak_dB[i])
        peak_dB[i] = dB;

      slider_display_value(peak_dB[i], SLDR_METER, WIN_PLAYER, i*2+ICON_LEFT_SLDR);

      if(peak_dB[i] > 0)
        peak_dB[i]--;
    }
  }
}


/*
  ------------------------------------------------------------------------
  DiskSample interface
  --------------------
*/

// DiskSample SWI's
#define DiskSample_Version           0x52ec0
#define DiskSample_Configure         0x52ec1
#define DiskSample_FileOpen          0x52ec2
#define DiskSample_StreamClose       0x52ec3
#define DiskSample_StreamCreate      0x52ec4
#define DiskSample_StreamSource      0x52ec5
#define DiskSample_StreamReceiver    0x52ec6
#define DiskSample_Decoding          0x52ec7
#define DiskSample_StreamPlay        0x52ec8
#define DiskSample_StreamPause       0x52ec9
#define DiskSample_StreamStop        0x52eca
#define DiskSample_StreamPosition    0x52ecb
#define DiskSample_StreamVolume      0x52ecc
#define DiskSample_StreamIsReady     0x52ecd
#define DiskSample_StreamStatus      0x52ece
#define DiskSample_StreamChain       0x52ecf
#define DiskSample_StreamInfo        0x52ed0
#define DiskSample_StreamTexts       0x52ed1
#define DiskSample_StreamParam       0x52ed2
#define DiskSample_StreamDecoding    0x52ed3
#define DiskSample_ChannelParams     0x52ed4
#define DiskSample_ChannelStatus     0x52ed5

// stream buffer provided by DiskSample
typedef struct circ_buf_s
{
  unsigned int size;      // size of buffer in bytes
  unsigned int start;     // current read (playback) position, index to buffer
  unsigned int free;      // next write position, index to buffer
  unsigned int finish;    // if TRUE, no more data to follow
  unsigned int reserved[12];
  unsigned char buffer[]; // cicular data buffer of size 'size'
} circ_buf_t;

static circ_buf_t *cb;    // DiskSample circular buffer pointer
static int handle;        // DiskSample stream handle

/*
 * close_audio
 * -----------
 * Closes the DiskSample streaming interface
 */
static void close_audio(void)
{
  if(!(ro.flags & (1<<PLAYER_OK))) // wait until player & synth have been initialised
    return;

  _kernel_swi_regs regs;

  regs.r[0] = handle;
  _kernel_swi(DiskSample_StreamClose, &regs, &regs);
  cb = 0;
}


/*
 * next
 * ----
 * returns the next buffer index
 */
static unsigned int next(unsigned int current)
{
  return (current < (cb->size - 4)) ? current + 4 : 0;
}


/*
 * buffer_fill
 * -----------
 * DiskSample buffer fill poll. Attempts to keep the buffer as full as possible.
 */
static void buffer_fill(void)
{
  _kernel_swi_regs regs;

  if(!cb)
    return;

  if(mpg.debug & 256)
  {
    _kernel_swi(OS_ReadMonotonicTime, &regs, &regs);
    myprintf("%d:%d,", regs.r[0] & 7, ro.sleep_timer);
  }

  unsigned int start = cb->start; // make local copy as it's volatile
  unsigned int free  = cb->free;
  unsigned int buff  = (unsigned int)cb->buffer;

  if(!(midiPlayer_status() & MP_RECORDING)) // not recording
  {
    if(mpg.debug & 256)
    {
      myprintf("*");
    }
    // sleep timeout
    if(ro.sleep_timeout > 0)
    {
      if(ro.sleep_timer >= ro.sleep_timeout)
      {
        if(ro.sleep_timer == ro.sleep_timeout)
        {
          // clear buffer before sleeping
          int i;
          for(i = 0; i < cb->size; i+=4)
            *(int *)(buff+i) = 0;
          level_meter(0,1);
          ro.sleep_timer++;
        }

        // we need to call the player for the number of samples that it would have been called for
        int n = start - free;
        if(n < 0)
          n += cb->size;
        n /= 4;
        while(n--)
          midi_file_player();

        cb->free = (start > 3) ? start - 4 : cb->size - 4; // make buffer look full
        return;
      }
      else // not timed out yet
        ro.sleep_timer++;
    }
    // end of sleep timeout

    if(next(free) == start)
    {  // if buffer is full before filling, start the stream
      regs.r[0] = handle;
      regs.r[1] = 0;
      regs.r[2] = 0;
      _kernel_swi(DiskSample_StreamStatus, &regs, &regs);
      if((regs.r[1] & 3) == 1) // "is ready" and "not playing"
        _kernel_swi(DiskSample_StreamPlay, &regs, &regs);
    }
    else
    {
      // fill the buffer
      while(next(free) != start)
      {
        int sample = *midiPlayer_sample();
        level_meter(sample,0);
        *(int *)(buff+free) = sample;
        free = next(free);
      }
      cb->free = free; // update end marker
    }
  }
}


/*
 * init_audio
 * ----------
 * Registers our audio stream with DiskSample
 * Returns zero on error.
 *
 * The buffering ahould be kept to a minimum to provide reasonable sync
 * between audio out, lyrics, and user input, but be large enough to avoid breakup.
 */
static int init_audio(void)
{
  if(!(ro.flags & (1<<PLAYER_OK))) // wait until player & synth have been initialised
    return 0;

  _kernel_swi_regs regs;
  int blk[64];

  if(cb)
    close_audio();

  int sample_rate = midiPlayer_status() >> MP_SAMPLE_RATE;
  meter_rate = sample_rate / METER_RATE; // rate divisor for level meters

  regs.r[0] = 257;
  regs.r[1] = 64; // output buffer in kB
  _kernel_swi(DiskSample_Configure, &regs, &regs);

  regs.r[0] = 0;            // flags (should be 0)
  regs.r[1] = 32;           // requested buffer size in kB
  _kernel_swi(DiskSample_StreamCreate, &regs, &regs);
  handle = regs.r[0];       // stream handle
  regs.r[1] = 0;            // input type, external input filler
  regs.r[2] = (int)blk;     // input type parameters, descriptor block
  blk[0] = 0;               // expected song length in bytes, (unknown)
  blk[1] = (int)"pcm";      // stream type
  blk[2] = 1;               // flags
  blk[3] = NUM_CHANS;       // channels
  blk[4] = sample_rate;     // sample rate in Hz
  blk[5] = 16;              // bits per sample (per channel)
  blk[6] = 0;               // flags (0 = signed samples)
  _kernel_swi(DiskSample_StreamSource, &regs, &regs);
  cb = (circ_buf_t *)regs.r[1];

  return (int)cb;
}


// public function pointers
const struct audio_if_s disksample =
{
  init_audio,
  close_audio,
  buffer_fill
};


/*
  ------------------------------------------------------------------------
  SharedSound interface
  ---------------------
*/

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

extern char ss_start, ss_buffer_fill, ss_error_handler,
            ss_circ_buffer, ss_end; // buffer.s

#define BUFF_SIZE   0x8000 // default size, samples not bytes
#define AREA_SIZE  0x20000 // allow for maximum buffer size

// dynamic area info
static struct area_s
{
  int num;
  int *base;
  int size;
  int rma; // base address of rma, (not part of dynamic area but a convienient place to put it)
} area;

// Structure to hold data passed between us and the SharedSound interrupt handler.
typedef struct ss_buff_s
{
  int size;        // length of circular buffer (samples not bytes)
  int read;        // read index (samples not bytes)
  int write;       // write index (samples not bytes)
  int rate_acc;    // 8.24 fractional rate step accumulator
  int handler_id;  // sound handler id
  int base;        // dest buffer start               ) copies of parameters passed to
  int end;         // dest buffer end                 ) handler by SharedSound
  int sample_freq; // system sample rate (1/1024 Hz)  ) (mostly for info only)
  int step;        // fractional step (8.24)          )
  int *buffer;     // pointer to buffer of length 'size' samples (int's)

} ss_buff_t;

ss_buff_t *ss_buff = 0;

static int ss_fill, ss_error; // relocated assembler function addresses
static int init_buffer_size = BUFF_SIZE; // default to maximum buffer size


/*
 * term_ss
 * -------
 * Terminate the SharedSound interface
 */
static void term_ss(void)
{
  if(!(ro.flags & (1<<PLAYER_OK))) // wait until player & synth have been initialised
    return;

  _kernel_swi_regs regs;

  if(area.num)
  {
    if(ss_buff)
    {
      // remove sharedsound handler
      if(ss_buff->handler_id)
      {
        regs.r[0] = ss_buff->handler_id;
        _kernel_swi(SharedSound_RemoveHandler,&regs,&regs);
        ss_buff->handler_id = 0;
      }

      // remove error handler
      regs.r[0] = 1;
      regs.r[1] = ss_error;
      regs.r[2] = 0;
      _kernel_swi(OS_Release,&regs,&regs);
      ss_buff = 0;
    }

    // free rma
    regs.r[0] = 7;
    regs.r[2] = area.rma;
    _kernel_swi(OS_Module, &regs, &regs);

    // remove dynamic area
    regs.r[0] = 1;
    regs.r[1] = area.num;
    _kernel_swi(OS_DynamicArea, &regs, &regs);
    area.num = 0;
  }
}


/*
 * init_ss
 * -------
 * Initialise the SharedSound interface
 */
static int init_ss(void)
{
  if(!(ro.flags & (1<<PLAYER_OK))) // wait until player & synth have been initialised
    return 0;

  _kernel_swi_regs regs;
  _kernel_oserror *err;
  if(!ss_buff)
  {
    // create dynamic area to hold buffer
    regs.r[0] = 0;
    regs.r[1] = -1;
    regs.r[2] = AREA_SIZE;
    regs.r[3] = -1;
    regs.r[4] = (1<<7);
    regs.r[5] = AREA_SIZE;
    regs.r[6] = 0;
    regs.r[7] = 0;
    regs.r[8] = (int)APP_NAME;
    if(_kernel_swi(OS_DynamicArea, &regs, &regs))
    {
      myprintf("Error: Couldn't create dynamic area\n");
      return 1;
    }
    area.num = regs.r[1];
    area.base = (int *)regs.r[3];
    area.size = regs.r[5];

    // claim rma for handler code
    int len = &ss_end - &ss_start;
    regs.r[0] = 6; // claim
    regs.r[3] = len;
    if(_kernel_swi(OS_Module, &regs, &regs))
    {
      myprintf("Error: Couldn't claim RMA\n");
      return 1;
    }
    area.rma = regs.r[2];

    // relocate code
    memcpy((char *)area.rma, &ss_start, len);
    // work out new addresses
    ss_fill = area.rma + (&ss_buffer_fill - &ss_start); // buffer fill code
    ss_error = area.rma + (&ss_error_handler - &ss_start); // error handler
    ss_buff = (ss_buff_t *)(area.rma + (&ss_circ_buffer - &ss_start)); // buffer data structure
    ss_buff->buffer = area.base; // sample buffer

    // initialise data
    ss_buff->size = init_buffer_size;
    ss_buff->read = 0;
    ss_buff->write = 0;
    ss_buff->rate_acc = 0;
    ss_buff->handler_id = 0;

    // register SharedSound handler
    regs.r[0] = ss_fill;
    regs.r[1] = 0;
    regs.r[2] = 1;
    regs.r[3] = (int)APP_NAME;
    regs.r[4] = 0; // interrupt handler
    if ((err = _kernel_swi(SharedSound_InstallHandler,&regs,&regs)))
    {
      myprintf("Error: SharedSound, %d, %s\n",err->errnum,err->errmess);
      return 1;
    }
    ss_buff->handler_id = regs.r[0];

    // Install error handler
    regs.r[0] = 1;
    regs.r[1] = ss_error;
    regs.r[2] = 0;
    _kernel_swi(OS_Claim,&regs,&regs);
  }

  // Inform SharedSound of our sample rate
  int sample_rate = midiPlayer_status() >> MP_SAMPLE_RATE;
  meter_rate = sample_rate / METER_RATE; // rate divisor for level meters
  regs.r[0] = ss_buff->handler_id;
  regs.r[1] = sample_rate << 10;
  _kernel_swi(SharedSound_SampleRate,&regs,&regs);

  // update buffer menu
  midiPlayer_display(ITEM_BUFFER, &ss_buff->size);
/*
  myprintf("area %d, base &%08X, size %d\n", area.num, (int)area.base, area.size);
  myprintf("rma &%08X\n", area.rma);
  myprintf("ss_fill &%08X, ss_error &%08X, ss_buff &%08X, buffer &%08X\n",
            ss_fill, ss_error, (int)ss_buff, (int)ss_buff->buffer);
  myprintf("handler_id %d\n", ss_buff->handler_id);
*/
  return 0;
}


/*
 * buffer_fill
 * -----------
 * SharedSound buffer fill poll. Attempts to keep the buffer as full as possible.
 */
static void fill_ss(void)
{
  if(!ss_buff)
    return;

  int read   = ss_buff->read; // modified by interrupt (volatile)
  int write  = ss_buff->write;
  int *buff  = ss_buff->buffer;
  int end    = ss_buff->size - 1;

  if(!(midiPlayer_status() & MP_RECORDING)) // not recording
  {
    // sleep timeout
    if(ro.sleep_timeout > 0)
    {
      if(ro.sleep_timer >= ro.sleep_timeout)
      {
        if(ro.sleep_timer == ro.sleep_timeout)
        {
          // clear buffer before sleeping
          int i;
          for(i = 0; i < ss_buff->size; i++)
            buff[i] = 0;
          level_meter(0,1);
          ro.sleep_timer++;
        }

        // we need to call the player for the number of samples that it would have been called for
        int n = read - write;
        if(n < 0)
          n += end + 1;
        while(n--)
          midi_file_player();

        ss_buff->write = (read > 0) ? read - 1 : end; // make buffer look full
        return;
      }
      else // not timed out yet
        ro.sleep_timer++;
    }
    // end of sleep timeout

    // fill the buffer
    int next;
    while((next = (write < end) ? write + 1 : 0) != read)
    {
      int sample = *midiPlayer_sample();
      level_meter(sample,0);
      buff[write] = sample;
      write = next;
    }
    ss_buff->write = write; // update end marker
  }
}


// public function pointers
const struct audio_if_s sharedsound =
{
  init_ss,
  term_ss,
  fill_ss
};


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

/*
 * cmd_interface
 * -------------
 * Selects the audio output interface
 */
int cmd_interface(char *msg, int len)
{
  int data = -1;

  if(len == 0) // status
    myprintf("Audio interface is %s\n", (ro.audio->init == init_ss) ? "SharedSound" : "DiskSample");

  else if(msg[0] == '?') // help
    myprintf(" Selects the audio output interface\n"
             "  ,D   Selects the DiskSample module\n"
             "  ,S   Selects the SharedSound module\n");

  else if(msg[0] == '!') // report
    myprintf("INTERFACE,%c\n", (ro.audio->init == init_ss) ? 'S' : 'D');

  else if((msg[1] == 'd')||(msg[1] == 'D')) // DiskSample
    data = 1;

  else if((msg[1] == 's')||(msg[1] == 'S')) // SharedSound
    data = 0;

  else
    return -PARAMETER_ERROR;

  if(data > -1)
  {
    const struct audio_if_s *new = (data == 0) ? &sharedsound : &disksample;
    if(ro.audio != new) // only do this if it has changed
    {
      ro.audio->term(); // stop the current interface
      ro.audio = new;   // select the new interface
      ro.audio->init(); // start the new interface
    }
    midiPlayer_display(ITEM_INTERFACE, &data);
  }

  return NO_ERROR_;
}


/*
 * cmd_buffer
 * ----------
 * Sets/Reports the audio buffer size
 */
int cmd_buffer(char *msg, int len)
{
  int n;

  if(len == 0) // display status
  {
    myprintf("Buffer size %d samples\n", init_buffer_size);

    if(ss_buff)
    {
      _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);
        myprintf("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]);
      }

      int used = ss_buff->write - ss_buff->read;
      if(used < 0)
        used += ss_buff->size;
      used = (used * 100) / (ss_buff->size - 1);
      myprintf("Synth buffer,  base &%X, size %d, rate %d, %d%% full\n",
                (int)ss_buff->buffer, ss_buff->size, midiPlayer_status() >> MP_SAMPLE_RATE, used);
      myprintf("System buffer, base &%X, size %d, rate %d, step &%X.%06X\n",
           ss_buff->base, (ss_buff->end - ss_buff->base)/4, ss_buff->sample_freq / 1024,
           ss_buff->step >> 24, ss_buff->step & 0xffffff);
    }
  }

  else if(msg[0] == '?') // display help
    myprintf(" Sets/Reports the SharedSound audio buffer size.\n"
             "   ,<size>  set audio buffer to <size> samples\n");

  else if(msg[0] == '!')
  {
    if(ss_buff)
      myprintf("BUFFER,%d\n", init_buffer_size);
  }

  else if(sscanf(msg, ",%d", &n) == 1)
  {
    if(n < 1024)
      n = 1024;
    else if(n > 32768)
      n = 32768;

    init_buffer_size = n; // will be picked up later if not initialised

    if(ss_buff) // if already initialised, action it now
    {
      ss_buff->size = n; // set size
      ss_buff->read = ss_buff->write = 0; // flush buffer
    }

    midiPlayer_display(ITEM_BUFFER, &n); // update menu
  }

  else
    return -PARAMETER_ERROR;

  return NO_ERROR_;
}


/*
 * cmd_sleep
 * ---------
 * Sets/Reports the sleep timeout
 */
int cmd_sleep(char *msg, int len)
{
  int n;

  if(len == 0) // display status
    myprintf("Sleep timeout %d seconds\n", ro.sleep_timeout / 100);

  else if(msg[0] == '?') // display help
    myprintf(" Sets/Reports the sleep timeout.\n"
             "   ,<time>  set timeout to <time> in seconds\n"
             "            0..3600, 0 = disable\n");

  else if(msg[0] == '!')
  {
    if(ss_buff)
      myprintf("SLEEP,%d\n", ro.sleep_timeout / 100);
  }

  else if(sscanf(msg, ",%d", &n) != 1)
    return -PARAMETER_ERROR;

  else if((n < 0) || (n > 3600))
    return -OUT_OF_RANGE;

  else
    ro.sleep_timeout = n * 100;

  return NO_ERROR_;
}

