/* replay.c */

/* In this code I refer to the buffers used by the codec as "sample buffers",
   and buffers used by this code to buffer data from the file as "file buffers".
*/

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "swis.h"

#include "errors.h"
#include "log.h"
#include "mstream.h"
#include "osfstream.h"
#include "replay.h"
#include "xstr.h"

#undef TickerV
#define TickerV 0x1c

#if EVERY < 1
#error Illegal value for EVERY
#endif

struct replay_codec {
  /* The first 3 are actually code entry points, but typing them as function
     pointers would be incorrect and lead to confusion. Have to put up with
     warnings about casting non-functions to functions. */
  int play;
  int stop;
  int data;
  unsigned int ifflags;
  int *bufferA;
  int *bufferB;
};
typedef void (*replay_player)(int, replay_mute *);
typedef void (*replay_stopper)(void);
typedef void (*replay_data_er)(const char *, int);
#define RCPLAY(c, a, m) (((replay_player) (int) &(c)->play)((a), (m)))
#define RCSTOP(c) (((replay_stopper) (int) &(c)->stop)())
#define RCDATA(c, b, s) (((replay_data_er) (int) &(c)->data)((b), (s)))

#define MUTE_RESERVED (64 - 18)
struct replay_mute {
  unsigned int flags;
  int pause;
  int frequency_int, frequency_frac;
  char quality;
  char reversed;
  char reserved[MUTE_RESERVED];
};

extern void ticker_handler(replay_data *);

extern int cmhg_tick_callback(_kernel_swi_regs *, void *pw);
extern int cmhg_finished_callback(_kernel_swi_regs *, void *pw);

replay_data replay_master;

static unsigned int
replay_handle_mask;

static int replay_current_handle;

#define HAVEASHIT(func, handle) \
        if ((handle) != replay_current_handle) {\
          LOG(("%s called with handle %d, doesn't match current %d\n",\
               (func), (handle), replay_current_handle));\
          return &error_not_current; }

_kernel_oserror *
replay_get_handle(replay_handle *handle)
{
  int n;
  LOG(("Generating new Replay handle: "));
  for (n = 1; n < 32 && (replay_handle_mask & (1 << n)); ++n);
  if (n == 32)
  {
    LOG(("none available\n"));
    return &error_too_many_replay;
  }
  LOG(("%d\n", n));
  replay_handle_mask |= (1 << n);
  replay_current_handle = *handle = n;
  return 0;
}

_kernel_oserror *
replay_initialise(void *pw)
{
  replay_handle_mask = 0;
  replay_current_handle = -1;
  replay_master.pw = pw;
  replay_master.ticker_registered = replay_master.tick_callback_registered
                                  = replay_master.finished_callback_registered
                                  = 0;
  replay_master.fm.fp = 0;
  replay_master.sndrep = replay_master.decomp = 0;
  replay_master.codec = 0;
  replay_master.mute = 0;
  replay_master.file_buf = 0;
  replay_master.my_codec = replay_master.my_mute
                         = replay_master.my_samp = replay_master.my_file = 0;
  replay_master.pollword = 1;
  replay_master.chunks = 0;
  return 0;
}

_kernel_oserror *
replay_remove(replay_handle handle)
{
  _kernel_oserror *e;
  if (handle != replay_current_handle && handle != -1)
  {
    LOG(("replay_remove called with handle %d doesn't match current %d\n",
         handle, replay_current_handle));
    return &error_not_current;
  }
  if (handle != -1)
    replay_handle_mask &= ~(1 << handle);
  replay_current_handle = -1;
  replay_master.fill_needed = replay_master.finished = 0;
  LOG(("replay_remove called:...\n"));
  if (replay_master.ticker_registered)
  {
    LOG(("Releasing ticker handler\n"));
  #if EVERY == 1
    e = _swix(OS_Release, _INR(0,2), TickerV, ticker_handler, &replay_master);
  #else
    e = _swix(OS_RemoveTickerEvent, _INR(0,1), ticker_handler, &replay_master);
  #endif
    if (e)
      return e;
    replay_master.ticker_registered = 0;
  }
  if (replay_master.tick_callback_registered)
  {
    LOG(("Releasing tick_callback handler\n"));
    e = _swix(OS_RemoveCallBack, _INR(0,1),
              cmhg_tick_callback, replay_master.pw);
    if (e)
      return e;
    replay_master.tick_callback_registered = 0;
  }
  if (replay_master.finished_callback_registered)
  {
    LOG(("Releasing finished_callback handler\n"));
    e = _swix(OS_RemoveCallBack, _INR(0,1),
              cmhg_finished_callback, replay_master.pw);
    if (e)
      return e;
    replay_master.tick_callback_registered = 0;
  }
  if (!replay_master.pollword)
  {
    LOG(("Stopping playing\n"));
    RCSTOP(replay_master.codec);
    replay_master.pollword = 1;
  }
  if (replay_master.fm.fp)
  {
    LOG(("Closing source file handle\n"));
    replay_master.close(replay_master.fm);
    replay_master.fm.fp = 0;
  }
  free(replay_master.sndrep);
  replay_master.sndrep = 0;
  free(replay_master.decomp);
  replay_master.decomp = 0;
  if (replay_master.my_mute)
  {
    free(replay_master.mute);
    replay_master.my_mute = 0;
  }
  replay_master.mute = 0;
  if (replay_master.my_samp && replay_master.codec)
  {
    free(replay_master.codec->bufferA);
    replay_master.my_samp = 0;
  }
  if (replay_master.my_codec)
  {
    free(replay_master.codec);
    replay_master.my_codec = 0;
  }
  replay_master.codec = 0;
  if (replay_master.my_file)
  {
    free(replay_master.file_buf);
    replay_master.my_file = 0;
  }
  replay_master.file_buf = 0;
  free(replay_master.chunks);
  replay_master.chunks = 0;
  return 0;
}

_kernel_oserror *
replay_remove_current(void)
{
  return replay_remove(replay_current_handle);
}

_kernel_oserror *
replay_finalise(void)
{
  LOG(("\nreplay_finalise\n"));
  return replay_remove_current();
}

int *
replay_get_pollword(replay_handle handle)
{
  return (handle < 32) ? &replay_master.pollword : 0;
}


/* Extract value from string for n'th track.
   Inferring from Acorn's example code, each string can contain values for
   more than one track and they are in the form:

   <value 1> |2 <value 2> |3 <value 3> ....
*/
static char *
replay_extract_track(const char *all, int track)
{
  char *result;
  const char *start = all;
  int len;

  if (track > 1)
  {
    char foo[8];

    /* Acorn's code doesn't assume spaces after |<num>,
       but some unspecified char. Have to copy it to be on the safe
       side and trust that tracks are in order. */
    sprintf(foo, "|%d", track);
    start = strstr(all, foo);
    if (!start)
    {
      LOG(("replay_extract_track: No info for track %d\n", track));
      return 0;
    }
    start += strlen(foo) + 1;
  }
  len = strcspn(start, "|");
  result = malloc(len + 1);
  if (!result)
  {
    LOG(("replay_extract_track: Out of memory\n"));
    return 0;
  }
  result[len] = 0;
  /* Naughty non-idiomatic loop saves a variable */
  for (--len; len >= 0; --len)
    result[len] = start[len];
  LOG(("Track %02d: %s\n", track, result));
  return result;
}

/* Skips superfluous Replay headers, returning NZ if something is wrong */
static int
replay_skip_headers(int howmany)
{
  int n;
  LOG(("Skipping %d unused headers:\n", howmany));
  for (n = 0; n < howmany; ++n)
  {
    char *line;
    line = replay_master.read_line(replay_master.fm);
    if (!line)
      return EOF;
    LOG(("%s\n", line));
    free(line);
  }
  LOG(("\n"));
  return 0;
}

static int
replay_decode_type2(char *lin, int *sndmul)
{
  int n;
  char f2info[64];
  fmstream fm;
  /* Isolate the codec name in lin */
  for (n = 2; lin[n] > 32; ++n);
  lin[n] = 0;
  /* Make decomp string a little longer to allow for
     n channels to be grafted on */
  replay_master.decomp = malloc(strlen(lin) + 12);
  if (!replay_master.decomp)
  {
    LOG(("Not enough memory for codec string for type 2\n"));
    return EOF;
  }
  sprintf(replay_master.decomp, "%s.Play", lin + 2);
  /* Generate the info filename */
  sprintf(f2info, "<ARMovie$SoundDir>.%s.Info", lin + 2);
  fm.fp = osfstream_open(f2info);
  if (!fm.fp)
    return EOF;
  lin = osfstream_read_line(fm);      /* name */
  if (!lin)
  {
    osfstream_close(fm);
    return EOF;
  }
  free(lin);
  lin = osfstream_read_line(fm);      /* copyright */
  if (!lin)
  {
    osfstream_close(fm);
    return EOF;
  }
  free(lin);
  lin = osfstream_read_line(fm);      /* 0/1 */
  if (!lin)
  {
    osfstream_close(fm);
    return EOF;
  }
  free(lin);
  lin = osfstream_read_line(fm);      /* bits per sample */
  if (!lin)
  {
    osfstream_close(fm);
    return EOF;
  }
  *sndmul = atoi(lin);
  free(lin);
  osfstream_close(fm);
  return 0;
}

static char *
replay_read_extract(int track)
{
  char *extract;
  char *line = replay_master.read_line(replay_master.fm);
  if (!line)
    return 0;
  LOG(("\nWhole line: %s\n", line));
  extract = replay_extract_track(line, track);
  free(line);
  LOG(("\n"));
  return extract;
}

static int
replay_read_chunks(int nchunks,
                   long int offset, int track)
{
  int chunk;
  int chunk_size = 0;
  LOG(("Reading chunk catalogue:\n"));
  replay_master.chunks = malloc(nchunks * sizeof(replay_chunk_data));
  if (!replay_master.chunks)
    return EOF;
  if (replay_master.seek(replay_master.fm, offset))
  {
    LOG(("Unable to seek to chunk catalogue\n"));
    return EOF;
  }
  for (chunk = 0; chunk < nchunks; ++chunk)
  {
    char *line;
    char *subline;
    char *extract;
    long int offset;
    int trk;
    line = replay_master.read_line(replay_master.fm);
    if (!line)
      return EOF;
    LOG(("Chunk %d: '%s'\n", chunk, line));
    offset = atoi(line);
    if (!offset)
    {
      LOG(("Invalid chunk offset\n"));
      free(line);
      return EOF;
    }
    subline = strchr(line, ',');
    if (!subline)
    {
      LOG(("Missing video size data\n"));
      free(line);
      return EOF;
    }
    offset += atoi(subline + 1);
    LOG(("Video size %d\n", atoi(subline + 1)));
    subline = strchr(subline, ';');
    if (!subline)
    {
      LOG(("Missing sound size data\n"));
      free(line);
      return EOF;
    }
    ++subline;
    for (trk = 1; trk < track; ++trk)
    {
      extract = replay_extract_track(subline, trk);
      if (!extract)
      {
        free(line);
        return EOF;
      }
      offset += atoi(extract);
      free(extract);
    }
    extract = replay_extract_track(subline, track);
    if (!extract)
    {
      free(line);
      return EOF;
    }
    replay_master.chunks[chunk].size = atoi(extract);
    replay_master.chunks[chunk].offset = offset;
    LOG(("Offset %d, size %d\n", offset, replay_master.chunks[chunk].size));
    if (chunk_size < replay_master.chunks[chunk].size)
      chunk_size = replay_master.chunks[chunk].size;
    free(extract);
    free(line);
  }
  replay_master.chunk_size = chunk_size;
  LOG(("Chunk size %d\n\n", chunk_size));
  return 0;
}

static int
replay_parse_headers(int track, int *fps, int *fpc,
                     int *channels, int *sndmul)
{
  char *extract;
  char *sndcompstr;
  int sndcomp;
  int sndbits;
  long int chunkoff;
  extract = replay_master.read_line(replay_master.fm);
  if (!extract)
    return EOF;
  LOG(("fps: %s\n\n", extract));
  *fps = atoi(extract);
  free(extract);
  LOG(("Sound comp field"));
  sndcompstr = replay_read_extract(track);
  if (!sndcompstr)
    return EOF;
  sndcomp = atoi(sndcompstr);
  if (sndcomp != 1 && sndcomp != 2)
  {
    LOG(("Format not 1 or 2\n"));
    free(sndcompstr);
    return EOF;
  }
  LOG(("Replay rate"));
  replay_master.sndrep = replay_read_extract(track);
  if (!replay_master.sndrep)
  {
    free(sndcompstr);
    return EOF;
  }
  LOG(("Channels"));
  extract = replay_read_extract(track);
  if (!extract)
  {
    free(sndcompstr);
    return EOF;
  }
  xstr_toupper(extract);
  replay_master.reversed = strstr(extract, "REVER") != 0;
  *channels = atoi(extract);
  free(extract);
  LOG(("Precision/Linear"));
  extract = replay_read_extract(track);
  if (!extract)
  {
    free(sndcompstr);
    return EOF;
  }
  xstr_toupper(extract);
  sndbits = atoi(extract);
  *sndmul = 8;                    /* Default */
  if (sndcomp == 1)
  {
    char linchar;
    LOG(("Type 1\n"));
    if (sndbits == 16 || strstr(extract, "LIN"))
    {
      if (strstr(extract, "UNSIGN"))
        linchar = 'U';
      else
        linchar = 'S';
    }
    else if (sndbits == 4 || strstr(extract, "ADPCM"))
    {
      linchar = 'A';
      *sndmul = 16;
    }
    else
    {
      linchar = 'E';
    }
    replay_master.decomp = malloc(20);
    if (!replay_master.decomp)
    {
      LOG(("No memory for decompressor name\n"));
      free(sndcompstr);
      return EOF;
    }
    sprintf(replay_master.decomp, "Sound%c%d", linchar, sndbits);
  }
  else
  {
    LOG(("Type 2\n"));
    if (replay_decode_type2(sndcompstr, sndmul))
      return EOF;
  }
  if (*channels > 1)
  {
    char foo[8];
    sprintf(foo, "x%d", *channels);
    strcat(replay_master.decomp, foo);
  }
  LOG(("Codec name '%s'\n\n", replay_master.decomp));
  free(sndcompstr);
  LOG(("fpc: "));
  extract = replay_master.read_line(replay_master.fm);
  if (!extract)
  {
    LOG(("unreadable\n"));
    return EOF;
  }
  LOG(("%s\n\n", extract));
  *fpc = atoi(extract);
  free(extract);
  LOG(("N chunks field: "));
  extract = replay_master.read_line(replay_master.fm);
  if (!extract)
  {
    LOG(("unreadable\n"));
    return EOF;
  }
  LOG(("%s\n\n", extract));
  replay_master.nchunks = atoi(extract) + 1;
  if (replay_master.nchunks == 1 && !isdigit(extract[0]))
  {
    LOG(("N chunks field invalid\n"));
    free(extract);
    return EOF;
  }
  free(extract);
  if (replay_skip_headers(2))
  {
    LOG(("Odd/even chunk size unreadable\n"));
    return EOF;
  }
  LOG(("chunk catalogue offset field: "));
  extract = replay_master.read_line(replay_master.fm);
  if (!extract)
  {
    LOG(("unreadable\n"));
    return EOF;
  }
  LOG(("%s\n\n", extract));
  chunkoff = atoi(extract);
  xstr_toupper(extract);
  if (strstr(extract, "FETCHER"))
  {
    LOG(("Fetchers not supported\n"));
    free(extract);
    return EOF;
  }
  free(extract);
  if (replay_read_chunks(replay_master.nchunks, chunkoff, track))
  {
    return EOF;
  }
  return 0;
}

_kernel_oserror *
replay_read_headers(const char *file, size_t size, int track, int from_file,
                    replay_handle *handle_out, const char **codec,
                    const char **sndrep, int *fps, int *fpc,
                    int *channels, int *multiplier, int *chunk_size)
{
  _kernel_oserror *e;
  LOG(("\n\nLoading new file\n"));
  replay_remove_current();
  e = replay_get_handle(handle_out);
  if (e)
    return e;
  if (from_file)
  {
    replay_master.fm.fp = osfstream_open(file);
    if (!replay_master.fm.fp)
      return &error_bad_replay;
    replay_master.read_line = osfstream_read_line;
    replay_master.close = osfstream_close;
    replay_master.seek = osfstream_seek;
    replay_master.read_bytes = osfstream_read_bytes;
  }
  else
  {
    replay_master.fm.mp = mstream_open(file, size);
    if (!replay_master.fm.mp)
      return &error_memory;
    replay_master.read_line = mstream_read_line;
    replay_master.close = mstream_close;
    replay_master.seek = mstream_seek;
    replay_master.read_bytes = mstream_read_bytes;
  }
  if (replay_skip_headers(8))
  {
    replay_remove(*handle_out);
    return &error_bad_replay;
  }
  if (replay_parse_headers(track, fps, fpc, channels, multiplier))
  {
    replay_remove(*handle_out);
    return &error_bad_replay;
  }
  *codec = replay_master.decomp;
  *sndrep = replay_master.sndrep;
  *chunk_size = replay_master.chunk_size;
  return 0;
}

static void
replay_init_mute(replay_handle handle, int quality)
{
  replay_master.mute->flags = 0;
  replay_master.mute->pause = 0;
  replay_master.mute->quality = quality;
  replay_master.mute->reversed = replay_master.reversed;
  memset(replay_master.mute->reserved, 0, MUTE_RESERVED);
}

_kernel_oserror *
replay_prepare_codec(replay_handle handle, replay_codec *codec,
                     replay_mute *mute,
                     int frequency_int, int frequency_frac, int quality)
{
  _kernel_oserror *e;
  HAVEASHIT("replay_prepare_codec", handle);
  /* Load codec if user hasn't already */
  if (!codec)
  {
    char decomp[32];
    int objtype, size;
    LOG(("replay_prepare_codec: User hasn't supplied codec, loading %s\n",
         replay_master.decomp));
    sprintf(decomp, "<ARMovie$SoundDir>.%s", replay_master.decomp);
    e = _swix(OS_File, _INR(0,1)|_OUT(0)|_OUT(4), 5, decomp, &objtype, &size);
    if (!e && objtype != 1)
      e = &error_no_codec;
    if (e)
    {
      LOG(("Can't find codec\n"));
      replay_remove(handle);
      return e;
    }
    LOG(("replay_prepare_codec allocating %d bytes for codec\n", size));
    replay_master.codec = malloc(size);
    if (!replay_master.codec)
    {
      LOG(("No memory for codec\n"));
      return &error_memory;
    }
    e = _swix(OS_File, _INR(0,3), 255, decomp, replay_master.codec, 1u<<31);
    if (e)
    {
      LOG(("Codec didn't load\n"));
      replay_remove(handle);
      return e;
    }
    replay_master.my_codec = 1;
  }
  else
  {
    LOG(("User has supplied codec %s\n", replay_master.decomp));
    replay_master.codec = codec;
    replay_master.my_codec = 0;
  }
  LOG(("codec loaded at %p\n", replay_master.codec));
  replay_master.codec->bufferA = replay_master.codec->bufferB = 0;
  /* Allocate mute block if user hasn't already */
  if (!mute)
  {
    replay_master.mute = malloc(64);
    if (!replay_master.mute)
    {
      replay_remove(handle);
      return &error_memory;
    }
    replay_master.my_mute = 1;
  }
  else
  {
    replay_master.mute = mute;
    replay_master.my_mute = 0;
  }
  /* Initialise mute block */
  replay_init_mute(handle, quality);
  replay_master.mute->frequency_int = frequency_int;
  replay_master.mute->frequency_frac = frequency_frac;
  /* Read time and start timing check */
  e = _swix(OS_ReadMonotonicTime, _OUT(0), &replay_master.time);
  if (e) /* As if! */
  {
    replay_remove(handle);
    return e;
  }
  RCPLAY(replay_master.codec, 1, replay_master.mute);
  replay_master.pollword = 0;
  return 0;
}

_kernel_oserror *
replay_setup_buffers(replay_handle handle, char *samp_buf, int samp_size,
                     char *file_buf, int file_size)
{
  HAVEASHIT("replay_setup_buffers", handle);
  if (!samp_buf)
  {
    replay_master.codec->bufferA = malloc(samp_size);
    if (!replay_master.codec->bufferA)
    {
      LOG(("Not enough memory for sample buffer\n"));
      replay_remove(handle);
      return &error_memory;
    }
    replay_master.my_samp = 1;
  }
  else
  {
    replay_master.codec->bufferA = (int *) samp_buf;
    replay_master.my_samp = 0;
  }
  replay_master.codec->bufferB = (int *) ((char *) replay_master.codec->bufferA
                                             + samp_size / 2);
  replay_master.codec->bufferA[1] = replay_master.codec->bufferB[1] = 1;
  replay_master.samp_size = (samp_size - 32) / 2;
  LOG(("Sample bufferA %p, total size %d\n",
       replay_master.codec->bufferA, samp_size));
  LOG(("Sample bufferB %p, buffering size %d\n",
       replay_master.codec->bufferB, replay_master.samp_size));
  /* If ADPCM, 4 bits are converted to 16 bits, so can only feed in
     1/4 buffer size */
  xstr_toupper(replay_master.decomp);
  if (strstr(replay_master.decomp, "ADPCM"))
  {
    replay_master.samp_size /= 4;
    LOG(("ADPCM, quartered advertised buffer space to %d\n",
         replay_master.samp_size));
  }
  if (!file_size)
  {
    file_size = 32768;
    if (file_size < replay_master.samp_size * 2)
      file_size = replay_master.samp_size * 2;
    if (file_size < replay_master.chunk_size * 2)
      file_size = replay_master.chunk_size * 2;
  }
  if (!file_buf)
  {
    replay_master.file_buf = malloc(file_size);
    if (!replay_master.file_buf)
    {
      LOG(("Not enough memory for file buffer\n"));
      replay_remove(handle);
      return &error_memory;
    }
    replay_master.my_file = 1;
  }
  else
  {
    replay_master.file_buf = file_buf;
    replay_master.my_file = 0;
  }
  replay_master.file_size = file_size / 2;
  LOG(("File buffer %p, size %d (%d)\n\n",
       replay_master.file_buf, file_size, replay_master.file_size));
  return 0;
}

_kernel_oserror *
replay_complete_timing_check(replay_handle handle, int *quality_hint)
{
  HAVEASHIT("replay_complete_timing_check", handle);
  /* Wait for the rest of the second if necessary */
  if (!(replay_master.codec->ifflags & 1))
  {
    int time;
    LOG(("Waiting for rest of second to complete timing check\n"));
    do
    {
       _kernel_oserror *e = _swix(OS_ReadMonotonicTime, _OUT(0), &time);
       if (e)
       {
          LOG(("OS_ReadMonotonicTime returned an error, shock horror!\n"));
          replay_remove(handle);
          return e;
       }
    } while (time - replay_master.time < 100);
  }
  else
  {
    LOG(("Timing check can be stopped immediately\n"));
  }
  RCSTOP(replay_master.codec);
  replay_master.pollword = 1;
  *quality_hint = replay_master.codec->ifflags & 2;
  return 0;
}

/* Fills one of the file buffers.
   Returns EOF if no sample data left, other NZ if error */
static int
replay_fill_file_buf(char *buf, int *buf_size)
{
  if (replay_master.chunk == replay_master.nchunks)
    return EOF;
  for (*buf_size = 0;
       replay_master.chunk < replay_master.nchunks
         && *buf_size + replay_master.chunks[replay_master.chunk].size
         <= replay_master.file_size;
       ++replay_master.chunk)
  {
    LOG((" %d", replay_master.chunk));
    if (!replay_master.read_bytes(replay_master.fm,
                            replay_master.chunks[replay_master.chunk].offset,
                            replay_master.chunks[replay_master.chunk].size,
                            buf + *buf_size))
    {
      return 1;
    }
    *buf_size += replay_master.chunks[replay_master.chunk].size;
  }
  LOG(("\nTotal size %d", *buf_size));
  return 0;
}

static int
replay_fill_file_bufs(void)
{
  int result;
#ifdef MAKELOG
  int time1, time2;
  int filled = 0;
  if (!replay_master.A_buf || !replay_master.B_buf)
  {
    _swix(OS_ReadMonotonicTime, _OUT(0), &time1);
    filled = 1;
  }
#endif
  if (!replay_master.A_buf)
  {
    LOG(("Filling file buffer A with chunks"));
    result = replay_fill_file_buf(replay_master.file_buf,
                                  &replay_master.A_size);
    replay_master.A_buf = replay_master.file_buf;
    LOG(("\n"));
    if (result)
    {
      replay_master.A_buf = 0;
      return result;
    }
  }
  if (!replay_master.B_buf)
  {
    LOG(("Filling file buffer B with chunks"));
    result = replay_fill_file_buf(replay_master.file_buf
                                  + replay_master.file_size,
                                  &replay_master.B_size);
    replay_master.B_buf = replay_master.file_buf + replay_master.file_size;
    LOG(("\n"));
    if (result)
    {
      replay_master.B_buf = 0;
      return result;
    }
  }
#ifdef MAKELOG
  if (filled)
  {
    _swix(OS_ReadMonotonicTime, _OUT(0), &time2);
    LOG(("Reading chunks took %dcs\n", time2 - time1));
  }
#endif
  return 0;
}

static fill_result
replay_fill_specific_samp_buf(char **buf, int size)
{
  int tofill;
  if (!*buf)
  {
    LOG(("No data in file buffers\n"));
    return replay_master.chunk >= replay_master.nchunks
           ? fill_Finished : fill_OutOfData;
  }
  tofill = size - replay_master.samp_offset;
  if (tofill > replay_master.samp_size)
    tofill = replay_master.samp_size;
  LOG(("Filling sample buffer with %d bytes at offset %d from buffer\n",
       tofill, replay_master.samp_offset));
  RCDATA(replay_master.codec, *buf + replay_master.samp_offset, tofill);
  replay_master.samp_offset += tofill;
  if (replay_master.samp_offset == size)
  {
    LOG(("End of file buffer\n"));
    *buf = 0;      /* Indicate that file buffer needs filling */
    replay_master.samp_offset = 0;
    replay_master.fill_from_A = !replay_master.fill_from_A;
    return fill_EndOfBuffer;
  }
  return fill_Filled;
}

static fill_result
replay_fill_samp_buf(void)
{
  fill_result result;
  if (replay_master.codec->bufferA[1] || replay_master.codec->bufferB[1])
  {
  #ifdef MAKELOG
    int time;
    _swix(OS_ReadMonotonicTime, _OUT(0), &time);
    LOG(("%dcs since last buffer fill\n", time - replay_master.time));
    replay_master.time = time;
  #endif
    if (replay_master.fill_from_A)
    {
      LOG(("Buffer A needs filling\n"));
      result = replay_fill_specific_samp_buf(&replay_master.A_buf,
                                             replay_master.A_size);
    }
    else
    {
      LOG(("Buffer B needs filling\n"));
      result = replay_fill_specific_samp_buf(&replay_master.B_buf,
                                             replay_master.B_size);
    }
  #ifdef MAKELOG
    _swix(OS_ReadMonotonicTime, _OUT(0), &time);
    LOG(("Buffer fill took %dcs\n", time - replay_master.time));
    replay_master.time = time;
  #endif
    return result;
  }
  return fill_AlreadyFull;
}

/* When entered as a callback routine, pw is reserved for CMHG stuff,
   so we can't get any other info in params, have to rely on replay_master.
   Must always return 1 to ensure a MOV(S) PC, R14 is performed.
*/
int
replay_finished_callback(_kernel_swi_regs *r, void *pw)
{
  replay_master.finished_callback_registered = 0;
  if (replay_master.ticker_registered)
  {
  #if EVERY == 1
    _swix(OS_Release, _INR(0,2), TickerV, ticker_handler, &replay_master);
  #else
    _swix(OS_RemoveTickerEvent, _INR(0,1), ticker_handler, &replay_master);
  #endif
    replay_master.ticker_registered = 0;
  }
  RCSTOP(replay_master.codec);
  replay_master.pollword = (replay_master.sfr == fill_Finished) ? -1 : 1;
  replay_master.semaphore = 0;
  return 1;
}

int
replay_tick_callback(_kernel_swi_regs *r, void *pw)
{
  int result;
  replay_master.tick_callback_registered = 0;
  if (!replay_master.codec->bufferA[1] && !replay_master.codec->bufferB[1])
  {
    replay_master.semaphore = 0;
    return 1;
  }
  replay_master.sfr = replay_fill_samp_buf();
  switch (replay_master.sfr)
  {
    case fill_Finished:
      break;
    case fill_Error:
      replay_finished_callback(r, pw);
      break;
    case fill_EndOfBuffer:
    case fill_OutOfData:
      result = replay_fill_file_bufs();
      if (result && result != EOF)
      {
        replay_finished_callback(r, pw);
      }
      break;
  }
  replay_master.semaphore = 0;
  return 1;
}

_kernel_oserror *
replay_play(replay_handle handle, int quality)
{
  int result;
  _kernel_oserror *e;
  HAVEASHIT("replay_play", handle);
  replay_master.chunk = 0;
  replay_master.A_buf = replay_master.B_buf = 0;
  replay_master.fill_from_A = 1;
  replay_master.samp_offset = 0;
  replay_master.codec->bufferA[1] = replay_master.codec->bufferB[1] = 1;
  result = replay_fill_file_bufs();
  if (result && result != EOF)
  {
    replay_remove(handle);
    return &error_stream;
  }
#ifdef MAKELOG
  _swix(OS_ReadMonotonicTime, _OUT(0), &replay_master.time);
#endif
  replay_master.sfr = replay_fill_samp_buf();
  if (replay_master.sfr == fill_Error)
  {
    replay_remove(handle);
    return &error_stream;
  }
  replay_init_mute(handle, quality);
  LOG(("\nStarting playing now\n\n"));
  RCPLAY(replay_master.codec, 2, replay_master.mute);
  replay_master.pollword = 0;
#if EVERY == 1
  e = _swix(OS_AddToVector, _INR(0,2), TickerV, ticker_handler, &replay_master);
#else
  e = _swix(OS_CallEvery, _INR(0,2), EVERY - 1, ticker_handler, &replay_master);
#endif
  if (e)
  {
    replay_remove(handle);
    replay_master.pollword = 1;
    return e;
  }
  replay_master.ticker_registered = 1;
  /*
  do {
    int inkey;
    _swix(OS_Byte, _INR(0,2)|_OUT(2), 129, 0, 0, &inkey);
    if (inkey == 0x1b)
    {
      LOG(("Escape\n"));
      break;
    }
    //ticker_handler(handle);
    if (replay_master.fill_needed)
    {
      replay_tick_callback(0, 0);
      replay_master.fill_needed = 0;
    }
    if (replay_master.finished)
      replay_finished_callback(0, 0);
  //} while (replay_master.sfr != fill_Finished);
  } while (!replay_master.finished);
  if (!replay_master.pollword)
  {
    LOG(("Forced to call replay_finished_callback\n"));
    replay_finished_callback(0, 0);
  }
  */
  return 0;
}

_kernel_oserror *
replay_stop(replay_handle handle)
{
  HAVEASHIT("replay_stop", handle);
  if (!replay_master.pollword)
  {
    LOG(("Stopping playing\n"));
    RCSTOP(replay_master.codec);
    replay_master.pollword = 1;
  }
  return 0;
}
