/* libSoX Yamaha TX-16W sampler file support
 *
 * May 20, 1993
 * Copyright 1993 Rob Talley   (rob@aii.com)
 * This source code is freely redistributable and may be used for
 * any purpose. This copyright notice and the following copyright
 * notice must be maintained intact. No warranty whatsoever is
 * provided. This code is furnished AS-IS as a component of the
 * larger work Copyright 1991 Lance Norskog and Sundry Contributors.
 * Much appreciation to ross-c  for his sampConv utility for SGI/IRIX
 * from where these methods were derived.
 *
 * Jan 24, 1994
 * Pat McElhatton, HP Media Technology Lab <patmc@apollo.hp.com>
 * Handles reading of files which do not have the sample rate field
 * set to one of the expected by looking at some other bytes in the
 * attack/loop length fields, and defaulting to 33kHz if the sample
 * rate is still unknown.
 *
 * January 12, 1995
 * Copyright 1995 Mark Lakata (lakata@physics.berkeley.edu)
 * Additions to tx16w.c SOX handler.  This version writes as well as
 * reads TX16W format.
 *
 * July 31, 1998
 * Cleaned up by Leigh Smith (leigh@psychokiller.dialix.oz.au)
 * for incorporation into the main sox distribution.
 *
 * September 24, 1998
 * Forced output to mono signed words to match input.  It was basically
 * doing this anyways but now the user will see a display that it's been
 * overridden.  cbagwell@sprynet.com
 *
 */

#include "sox_i.h"

#define TXMAXLEN 0x3FF80

/* Private data for TX16 file */
typedef struct {
  size_t   samples_out;
  size_t   bytes_out;
  size_t   rest;                 /* bytes remaining in sample file */
  sox_sample_t odd;
  sox_bool     odd_flag;
} priv_t;

struct WaveHeader_ {
  char filetype[6]; /* = "LM8953", */
  unsigned char
    nulls[10],
    dummy_aeg[6],    /* space for the AEG (never mind this) */
    format,          /* 0x49 = looped, 0xC9 = non-looped */
    sample_rate,     /* 1 = 33 kHz, 2 = 50 kHz, 3 = 16 kHz */
    atc_length[3],   /* I'll get to this... */
    rpt_length[3],
    unused[2];       /* set these to null, to be on the safe side */
};

static const unsigned char magic1[4] = {0, 0x06, 0x10, 0xF6};
static const unsigned char magic2[4] = {0, 0x52, 0x00, 0x52};

static const char read_error_msg[] = "file is truncated";
#define read_error() { \
    lsx_fail_errno(ft, SOX_EOF, read_error_msg); \
    return(SOX_EOF); \
}

/*
 * Do anything required before you start reading samples.
 * Read file header.
 *      Find out sampling rate,
 *      size and encoding of samples,
 *      mono/stereo/quad.
 */
static int startread_tx16w(sox_format_t * ft)
{
    int c;
    char filetype[7];
    int8_t format;
    unsigned char sample_rate;
    size_t num_samp_bytes = 0;
    unsigned char gunk[8];
    int blewIt;
    uint8_t trash;

    priv_t * sk = (priv_t *) ft->priv;
    /* If you need to seek around the input file. */
    if (! ft->seekable)
    {
        lsx_fail_errno(ft,SOX_EOF,"input must be a file, not a pipe");
        return(SOX_EOF);
    }

    /* This is dumb but portable, just count the bytes til EOF */
    while (lsx_read_b_buf(ft, &trash, (size_t) 1) == 1)
        num_samp_bytes++;
    num_samp_bytes -= 32;         /* calculate num samples by sub header size */
    if (lsx_seeki(ft, (off_t)0, 0)) /* rewind file */
        return(SOX_EOF);
    sk->rest = num_samp_bytes;    /* set how many sample bytes to read */

    /* first 6 bytes are file type ID LM8953 */
    if (lsx_readchars(ft, filetype, sizeof(filetype) - 1))
        read_error();
    filetype[6] = '\0';
    for( c = 16; c > 0 ; c-- )    /* Discard next 16 bytes */
        if (lsx_readb(ft, &trash))
            read_error();
    if (lsx_readsb(ft, &format) ||
        lsx_readb(ft, &sample_rate))
            read_error();
    /*
     * save next 8 bytes - if sample rate is 0, then we need
     *  to look at gunk[2] and gunk[5] to get real rate
     */
    for( c = 0; c < 8; c++ )
        if (lsx_readb(ft, &(gunk[c])))
            read_error();
    /*
     * We should now be pointing at start of raw sample data in file
     */
    ft->data_start = lsx_tell(ft);

    /* Check to make sure we got a good filetype ID from file */
    lsx_debug("Found header filetype %s",filetype);
    if(strcmp(filetype,"LM8953"))
    {
        lsx_fail_errno(ft,SOX_EHDR,"invalid filetype ID in input file header, != LM8953");
        return(SOX_EOF);
    }
    /*
     * Set up the sample rate as indicated by the header
     */

    switch( sample_rate ) {
        case 1:
            ft->signal.rate = 1e5 / 3;
            break;
        case 2:
            ft->signal.rate = 1e5 / 2;
            break;
        case 3:
            ft->signal.rate = 1e5 / 6;
            break;
        default:
            blewIt = 1;
            switch( gunk[2] & 0xFE ) {
                case 0x06:
                    if ( (gunk[5] & 0xFE) == 0x52 ) {
                        blewIt = 0;
                        ft->signal.rate = 1e5 / 3;
                    }
                    break;
                case 0x10:
                    if ( (gunk[5] & 0xFE) == 0x00 ) {
                        blewIt = 0;
                        ft->signal.rate = 1e5 / 2;
                    }
                    break;
                case 0xF6:
                    if ( (gunk[5] & 0xFE) == 0x52 ) {
                        blewIt = 0;
                        ft->signal.rate = 1e5 / 6;
                    }
                    break;
            }
            if ( blewIt ) {
                lsx_debug("Invalid sample rate identifier found %d", sample_rate);
                ft->signal.rate = 1e5 / 3;
            }
    }
    lsx_debug("Sample rate = %g", ft->signal.rate);

    ft->signal.channels = 1 ; /* not sure about stereo sample data yet ??? */
    ft->encoding.bits_per_sample = 12;
    ft->encoding.encoding = SOX_ENCODING_SIGN2;
    ft->signal.length = num_samp_bytes * 3 / 2;  /* 12 bits per sample */

    return(SOX_SUCCESS);
}

/*
 * Read up to len samples from file.
 * Convert to sox_sample_t.
 * Place in buf[].
 * Return number of samples read.
 */

static size_t read_samples_tx16w(sox_format_t * ft, sox_sample_t *buf, size_t len)
{
    priv_t * sk = (priv_t *) ft->priv;
    size_t done = 0;
    unsigned char uc1,uc2,uc3;
    unsigned short s1,s2;

    /*
     * This gets called by the top level 'process' routine.
     * We will essentially get called with a buffer pointer
     * and a max length to read. Graciously, it is always
     * an even amount so we don't have to worry about
     * hanging onto the left over odd samples since there
     * won't be any. Something to look out for though :-(
     * We return the number of samples we read.
     * We will get called over and over again until we return
     *  0 bytes read.
     */

    /*
     * This is ugly but it's readable!
     * Read three bytes from stream, then decompose these into
     * two unsigned short samples.
     * TCC 3.0 appeared to do unwanted things, so we really specify
     *  exactly what we want to happen.
     * Convert unsigned short to sox_sample_t then shift up the result
     *  so that the 12-bit sample lives in the most significant
     *  12-bits of the sox_sample_t.
     * This gets our two samples into the internal format which we
     * deposit into the given buffer and adjust our counts respectivly.
     */
    for(done = 0; done < len; ) {
        if(sk->rest < 3) break; /* Finished reading from file? */
        if (lsx_readb(ft, &uc1) ||
            lsx_readb(ft, &uc2) ||
            lsx_readb(ft, &uc3))
	        break;
        sk->rest -= 3; /* adjust remaining for bytes we just read */
        s1 = (unsigned short) (uc1 << 4) | (((uc2 >> 4) & 017));
        s2 = (unsigned short) (uc3 << 4) | (( uc2 & 017 ));
        *buf = (sox_sample_t) s1;
        *buf = (*buf << 20);
        buf++; /* sample one is done */
        *buf = (sox_sample_t) s2;
        *buf = (*buf << 20);
        buf++; /* sample two is done */
        done += 2; /* adjust converted & stored sample count */
    }
    return done;
}

static const char write_error_msg[] = "write error";
#define write_error() { \
    lsx_fail_errno(ft, SOX_EOF, write_error_msg); \
    return(SOX_EOF); \
}

static int startwrite_tx16w(sox_format_t * ft)
{
    priv_t * sk = (priv_t *) ft->priv;
    struct WaveHeader_ WH;

    lsx_debug("tx16w selected output");

    memset(&WH, 0, sizeof(struct WaveHeader_));

    /* If you have to seek around the output file */
    if (! ft->seekable)
    {
        lsx_fail_errno(ft,SOX_EOF,"output must be a file, not a pipe");
        return(SOX_EOF);
    }

    /* dummy numbers, just for place holder, real header is written
       at end of processing, since byte count is needed */

    if (lsx_writebuf(ft, &WH, (size_t) 32) != 32)
      write_error();
    sk->bytes_out = 32;
    return(SOX_SUCCESS);
}

static size_t write_samples_tx16w(sox_format_t * ft, const sox_sample_t *buf, size_t len0)
{
  priv_t * sk = (priv_t *) ft->priv;
  size_t last_i, i = 0, len = min(len0, TXMAXLEN - sk->samples_out);
  sox_sample_t w1, w2;

  while (i < len) {
    last_i = i;
    if (sk->odd_flag) {
      w1 = sk->odd;
      sk->odd_flag = sox_false;
    }
    else w1 = *buf++ >> 20, ++i;

    if (i < len) {
      w2 = *buf++ >> 20, ++i;
      if (lsx_writesb(ft, (w1 >> 4) & 0xFF) ||
          lsx_writesb(ft, (((w1 & 0x0F) << 4) | (w2 & 0x0F)) & 0xFF) ||
          lsx_writesb(ft, (w2 >> 4) & 0xFF)) {
        i = last_i;
        break;
      }
      sk->samples_out += 2;
      sk->bytes_out += 3;
    }
    else {
      sk->odd = w1;
      sk->odd_flag = sox_true;
    }
  }
  return i;
}

static int stopwrite_tx16w(sox_format_t * ft)
{
  priv_t * sk = (priv_t *) ft->priv;
    struct WaveHeader_ WH;
    int AttackLength, LoopLength, i;

    if (sk->odd_flag) {
      sox_sample_t pad = 0;
      if (write_samples_tx16w(ft, &pad, (size_t) 1) != 1)
        write_error();
    }

    /* All samples are already written out. */
    /* If file header needs fixing up, for example it needs the */
    /* the number of samples in a field, seek back and write them here. */

    lsx_debug("tx16w:output finished");

    memset(&WH, 0, sizeof(struct WaveHeader_));
    memcpy(WH.filetype,"LM8953",(size_t)6);
    for (i=0;i<10;i++) WH.nulls[i]=0;
    for (i=0;i<6;i++)  WH.dummy_aeg[i]=0;
    for (i=0;i<2;i++)  WH.unused[i]=0;
    for (i=0;i<2;i++)  WH.dummy_aeg[i] = 0;
    for (i=2;i<6;i++)  WH.dummy_aeg[i] = 0x7F;

    WH.format = 0xC9;   /* loop off */

    /* the actual sample rate is not that important ! */
    if (ft->signal.rate < 24000)      WH.sample_rate = 3;
    else if (ft->signal.rate < 41000) WH.sample_rate = 1;
    else                            WH.sample_rate = 2;

    if (sk->samples_out >= TXMAXLEN) {
        lsx_warn("sound too large. Truncating, Loop Off");
        AttackLength       = TXMAXLEN/2;
        LoopLength         = TXMAXLEN/2;
    }
    else if (sk->samples_out >=TXMAXLEN/2) {
        AttackLength       = TXMAXLEN/2;
        LoopLength         = sk->samples_out - TXMAXLEN/2;
        if (LoopLength < 0x40) {
            LoopLength   +=0x40;
            AttackLength -= 0x40;
        }
    }
    else if (sk->samples_out >= 0x80) {
        AttackLength                       = sk->samples_out -0x40;
        LoopLength                         = 0x40;
    }
    else {
        AttackLength                       = 0x40;
        LoopLength                         = 0x40;
        for(i=sk->samples_out;i<0x80;i++) {
            if (lsx_writeb(ft, 0) ||
                lsx_writeb(ft, 0) ||
                lsx_writeb(ft, 0))
                write_error();
            sk->bytes_out += 3;
        }
    }

    /* Fill up to 256 byte blocks; the TX16W seems to like that */

    while ((sk->bytes_out % 0x100) != 0) {
        if (lsx_writeb(ft, 0))
            write_error();
        sk->bytes_out++;
    }

    WH.atc_length[0] = 0xFF & AttackLength;
    WH.atc_length[1] = 0xFF & (AttackLength >> 8);
    WH.atc_length[2] = (0x01 & (AttackLength >> 16)) +
        magic1[WH.sample_rate];

    WH.rpt_length[0] = 0xFF & LoopLength;
    WH.rpt_length[1] = 0xFF & (LoopLength >> 8);
    WH.rpt_length[2] = (0x01 & (LoopLength >> 16)) +
        magic2[WH.sample_rate];

    /* If a memopen()ed file is closed with the seek pointer before the end,
     * the file gets truncated and fseek(SEEK_END) doesn't work, so
     * remember the length, rewrite the header and seek to the end again.
     */
    {
        off_t o = ftell(ft->fp);

        lsx_rewind(ft);
        if (lsx_writebuf(ft, &WH, (size_t) 32) != 32)
            write_error();
        fseek(ft->fp, o, SEEK_SET);

        return(SOX_SUCCESS);
    }
}

LSX_FORMAT_HANDLER(txw)
{
  static char const * const names[] = {"txw", NULL};
  static sox_rate_t   const write_rates[] = {1e5/6, 1e5/3, 1e5/2, 0};
  static unsigned const write_encodings[] = {SOX_ENCODING_SIGN2, 12, 0, 0};
  static sox_format_handler_t const handler = {SOX_LIB_VERSION_CODE,
    "Yamaha TX-16W sampler", names, SOX_FILE_MONO,
    startread_tx16w, read_samples_tx16w, NULL,
    startwrite_tx16w, write_samples_tx16w, stopwrite_tx16w,
    NULL, write_encodings, write_rates, sizeof(priv_t)
  };
  return &handler;
}
