/* libSoX SampleVision file format handler.
 * Output is always in little-endian (80x86/VAX) order.
 *
 * Derived from: libSoX skeleton handler file.
 *
 * Add: Loop point verbose info.  It's a start, anyway.
 */

/*
 * June 30, 1992
 * Copyright 1992 Leigh Smith And Sundry Contributors
 * This source code is freely redistributable and may be used for
 * any purpose.  This copyright notice must be maintained.
 * Leigh Smith And Sundry Contributors are not responsible for
 * the consequences of using this software.
 */

#include "sox_i.h"

#define NAMELEN    30           /* Size of Samplevision name */
#define COMMENTLEN 60           /* Size of Samplevision comment, not shared */
#define MIDI_UNITY 60           /* MIDI note number to play sample at unity */
#define MARKERLEN  (size_t)10           /* Size of Marker name */

/* The header preceeding the sample data */
struct smpheader {
        char Id[18];            /* File identifier */
        char version[4];        /* File version */
        char comments[COMMENTLEN];      /* User comments */
        char name[NAMELEN + 1]; /* Sample Name, left justified */
};
#define HEADERSIZE (sizeof(struct smpheader) - 1)       /* -1 for name's \0 */

/* Samplevision loop definition structure */
struct loop {
        uint32_t start; /* Sample count into sample data, not byte count */
        uint32_t end;   /* end point */
        unsigned char type;  /* 0 = loop off, 1 = forward, 2 = forw/back */
        unsigned short count;  /* No of times to loop */
};

/* Samplevision marker definition structure */
struct marker {
        char name[MARKERLEN + 1]; /* Ascii Marker name */
        uint32_t position;        /* Sample Number, not byte number */
};

/* The trailer following the sample data */
struct smptrailer {
        struct loop loops[8];           /* loops */
        struct marker markers[8];       /* markers */
        int8_t MIDInote;                /* for unity pitch playback */
        uint32_t rate;                  /* in hertz */
        uint32_t SMPTEoffset;           /* in subframes - huh? */
        uint32_t CycleSize;             /* sample count in one cycle of the */
                                        /* sampled sound -1 if unknown */
};

/* Private data for SMP file */
typedef struct {
  uint64_t NoOfSamps;           /* Sample data count in words */
  uint64_t dataStart;
  /* comment memory resides in private data because it's small */
  char comment[COMMENTLEN + NAMELEN + 3];
} priv_t;

static char const *SVmagic = "SOUND SAMPLE DATA ", *SVvers = "2.1 ";

/*
 * Read the SampleVision trailer structure.
 * Returns SOX_SUCCESS if everything was read ok,
 * SOX_EOF if there was an error.
 */
#define TRAILERSIZE (2 + 8*(4+4+1+2) + 8*(MARKERLEN+4) + (1+4+4+4))
static int readtrailer(sox_format_t * ft, struct smptrailer *trailer)
{
        int i;
        uint16_t trash16;

        if (lsx_readw(ft, &trash16)) /* read reserved word */
                return(SOX_EOF);
        for(i = 0; i < 8; i++) {        /* read the 8 loops */
                if (lsx_readdw(ft, &(trailer->loops[i].start)) ||
                    lsx_readdw(ft, &(trailer->loops[i].end)) ||
                    lsx_readb(ft, &(trailer->loops[i].type)) ||
                    lsx_readw(ft, &(trailer->loops[i].count)))
                        return(SOX_EOF);
                ft->oob.loops[i].start = trailer->loops[i].start;
                ft->oob.loops[i].length =
                        trailer->loops[i].end - trailer->loops[i].start;
                ft->oob.loops[i].type = trailer->loops[i].type;
                ft->oob.loops[i].count = trailer->loops[i].count;
        }
        for(i = 0; i < 8; i++) {        /* read the 8 markers */
                if (lsx_readbuf(ft, trailer->markers[i].name, MARKERLEN) != MARKERLEN)
                {
                    lsx_fail_errno(ft,SOX_EHDR,"unexpected EOF reading trailer");
                    return(SOX_EOF);
                }
                trailer->markers[i].name[MARKERLEN] = 0;
                if (lsx_readdw(ft, &(trailer->markers[i].position)))
                    return(SOX_EOF);
        }
        if (lsx_readsb(ft, &(trailer->MIDInote)) ||
            lsx_readdw(ft, &(trailer->rate)) ||
            lsx_readdw(ft, &(trailer->SMPTEoffset)) ||
            lsx_readdw(ft, &(trailer->CycleSize)))
                return(SOX_EOF);
        return(SOX_SUCCESS);
}

/*
 * set the trailer data - loops and markers, to reasonably benign values
 */
static void settrailer(sox_format_t * ft, struct smptrailer *trailer, sox_rate_t rate)
{
        int i;

        for(i = 0; i < 8; i++) {        /* copy the 8 loops */
            if (ft->oob.loops[i].type != 0) {
                trailer->loops[i].start = ft->oob.loops[i].start > UINT_MAX
                    ? UINT_MAX
                    : ft->oob.loops[i].start;
                /* to mark it as not set */
                trailer->loops[i].end = ft->oob.loops[i].start + ft->oob.loops[i].length > UINT_MAX
                    ? UINT_MAX
                    : ft->oob.loops[i].start + ft->oob.loops[i].length;
                trailer->loops[i].type = ft->oob.loops[i].type;
                trailer->loops[i].count = ft->oob.loops[i].count;
            } else {
                /* set first loop start as FFFFFFFF */
                trailer->loops[i].start = ~0u;
                /* to mark it as not set */
                trailer->loops[i].end = 0;
                trailer->loops[i].type = 0;
                trailer->loops[i].count = 0;
            }
        }
        for(i = 0; i < 8; i++) {        /* write the 8 markers */
                strcpy(trailer->markers[i].name, "          ");
                trailer->markers[i].position = ~0u;
        }
        trailer->MIDInote = MIDI_UNITY;         /* Unity play back */
        trailer->rate = rate;
        trailer->SMPTEoffset = 0;
        trailer->CycleSize = ~0u;
}

/*
 * Write the SampleVision trailer structure.
 * Returns SOX_SUCCESS if everything was written ok,
 *         SOX_EOF if there was an error.
 */
static int writetrailer(sox_format_t * ft, struct smptrailer *trailer)
{
        int i;

        if (lsx_writew(ft, 0))
                return(SOX_EOF);
        for(i = 0; i < 8; i++) {        /* write the 8 loops */
                if (lsx_writedw(ft, trailer->loops[i].start) ||
                    lsx_writedw(ft, trailer->loops[i].end) ||
                    lsx_writeb(ft, trailer->loops[i].type) ||
                    lsx_writew(ft, trailer->loops[i].count))
                        return(SOX_EOF);
        }
        for(i = 0; i < 8; i++) {        /* write the 8 markers */
                if (lsx_writes(ft, trailer->markers[i].name) == SOX_EOF)
                {
                    lsx_fail_errno(ft,SOX_EHDR,"unexpected EOF writing trailer");
                    return(SOX_EOF);
                }
                if (lsx_writedw(ft, trailer->markers[i].position))
                    return(SOX_EOF);
        }
        if (lsx_writeb(ft, (uint8_t)(trailer->MIDInote)) ||
            lsx_writedw(ft, trailer->rate) ||
            lsx_writedw(ft, trailer->SMPTEoffset) ||
            lsx_writedw(ft, trailer->CycleSize))
                return(SOX_EOF);

        return(SOX_SUCCESS);
}

static int sox_smpseek(sox_format_t * ft, sox_uint64_t offset)
{
    uint64_t new_offset;
    size_t channel_block, alignment;
    priv_t * smp = (priv_t *) ft->priv;

    new_offset = offset * (ft->encoding.bits_per_sample >> 3);
    /* Make sure request aligns to a channel block (ie left+right) */
    channel_block = ft->signal.channels * (ft->encoding.bits_per_sample >> 3);
    alignment = new_offset % channel_block;
    /* Most common mistaken is to compute something like
     * "skip everthing upto and including this sample" so
     * advance to next sample block in this case.
     */
    if (alignment != 0)
        new_offset += (channel_block - alignment);
    new_offset += smp->dataStart;

    ft->sox_errno = lsx_seeki(ft, (off_t)new_offset, SEEK_SET);

    if( ft->sox_errno == SOX_SUCCESS )
        smp->NoOfSamps = ft->signal.length - (new_offset / (ft->encoding.bits_per_sample >> 3));

    return(ft->sox_errno);
}
/*
 * 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 sox_smpstartread(sox_format_t * ft)
{
        priv_t * smp = (priv_t *) ft->priv;
        int namelen, commentlen;
        off_t samplestart;
        size_t i;
        unsigned dw;
        struct smpheader header;
        struct smptrailer trailer;

        /* 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);
        }

        /* Read SampleVision header */
        if (lsx_readbuf(ft, &header, HEADERSIZE) != HEADERSIZE)
        {
                lsx_fail_errno(ft,SOX_EHDR,"unexpected EOF in header");
                return(SOX_EOF);
        }
        if (strncmp(header.Id, SVmagic, (size_t)17) != 0)
        {
                lsx_fail_errno(ft,SOX_EHDR,"header does not begin with magic word %s", SVmagic);
                return(SOX_EOF);
        }
        if (strncmp(header.version, SVvers, (size_t)4) != 0)
        {
                lsx_fail_errno(ft,SOX_EHDR,"header is not version %s", SVvers);
                return(SOX_EOF);
        }

        /* Format the sample name and comments to a single comment */
        /* string. We decrement the counters till we encounter non */
        /* padding space chars, so the *lengths* are low by one */
        for (namelen = NAMELEN-1;
            namelen >= 0 && header.name[namelen] == ' '; namelen--)
          ;
        for (commentlen = COMMENTLEN-1;
            commentlen >= 0 && header.comments[commentlen] == ' '; commentlen--)
          ;
        sprintf(smp->comment, "%.*s: %.*s", namelen+1, header.name,
                commentlen+1, header.comments);
        sox_append_comments(&ft->oob.comments, smp->comment);

        /* Extract out the sample size (always intel format) */
        if (lsx_readdw(ft, &dw)) {
                lsx_fail_errno(ft,errno,"unable to read sample size");
                return(SOX_EOF);
        }
        smp->NoOfSamps = dw;
        /* mark the start of the sample data */
        samplestart = lsx_tell(ft);

        /* seek from the current position (the start of sample data) by */
        /* NoOfSamps * sizeof(int16_t) */
        if (lsx_seeki(ft, (off_t)(smp->NoOfSamps * 2), 1) == -1)
        {
                lsx_fail_errno(ft,errno,"unable to seek to trailer");
                return(SOX_EOF);
        }
        if (readtrailer(ft, &trailer) != SOX_SUCCESS)
        {
                lsx_fail_errno(ft,SOX_EHDR,"unexpected EOF in trailer");
                return(SOX_EOF);
        }

        /* seek back to the beginning of the data */
        if (lsx_seeki(ft, (off_t)samplestart, 0) == -1)
        {
                lsx_fail_errno(ft,errno,"unable to seek back to start of sample data");
                return(SOX_EOF);
        }

        ft->signal.rate = (int) trailer.rate;
        ft->encoding.bits_per_sample = 16;
        ft->encoding.encoding = SOX_ENCODING_SIGN2;
        ft->signal.channels = 1;
        smp->dataStart = samplestart;
        ft->signal.length = smp->NoOfSamps;

        /* This is only used in computing the bitrate,
         * so include the size of the trailer */
        ft->data_start = samplestart + TRAILERSIZE;

        lsx_report("SampleVision trailer:");
        for(i = 0; i < 8; i++) if (1 || trailer.loops[i].count) {
                lsx_report("loop %lu: start: %6d", (unsigned long)i, trailer.loops[i].start);
                lsx_report(" end:   %6d", trailer.loops[i].end);
                lsx_report(" count: %6d", trailer.loops[i].count);
                switch(trailer.loops[i].type) {
                    case 0: lsx_report("type:  off"); break;
                    case 1: lsx_report("type:  forward"); break;
                    case 2: lsx_report("type:  forward/backward"); break;
                }
        }
        lsx_report("MIDI Note number: %d", trailer.MIDInote);

        ft->oob.instr.nloops = 0;
        for(i = 0; i < 8; i++)
                if (trailer.loops[i].type)
                        ft->oob.instr.nloops++;
        for(i = 0; i < ft->oob.instr.nloops; i++) {
                ft->oob.loops[i].type = trailer.loops[i].type;
                ft->oob.loops[i].count = trailer.loops[i].count;
                ft->oob.loops[i].start = trailer.loops[i].start;
                ft->oob.loops[i].length = trailer.loops[i].end
                        - trailer.loops[i].start;
        }
        ft->oob.instr.MIDIlow = ft->oob.instr.MIDIhi =
                ft->oob.instr.MIDInote = trailer.MIDInote;
        if (ft->oob.instr.nloops > 0)
                ft->oob.instr.loopmode = SOX_LOOP_8;
        else
                ft->oob.instr.loopmode = SOX_LOOP_NONE;

        return(SOX_SUCCESS);
}

/*
 * Read up to len samples from file.
 * Convert to signed longs.
 * Place in buf[].
 * Return number of samples read.
 */
static size_t sox_smpread(sox_format_t * ft, sox_sample_t *buf, size_t len)
{
        priv_t * smp = (priv_t *) ft->priv;
        unsigned short datum;
        size_t done = 0;

        for(; done < len && smp->NoOfSamps; done++, smp->NoOfSamps--) {
                if (lsx_readw(ft, &datum))
                        break;
                /* scale signed up to long's range */
                *buf++ = SOX_SIGNED_16BIT_TO_SAMPLE(datum,);
        }
        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 sox_smpstartwrite(sox_format_t * ft)
{
        priv_t * smp = (priv_t *) ft->priv;
        struct smpheader header;
        char * comment = lsx_cat_comments(ft->oob.comments);

        /* 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);
        }

        memcpy(header.Id, SVmagic, sizeof(header.Id));
        memcpy(header.version, SVvers, sizeof(header.version));
        sprintf(header.comments, "%-*s", COMMENTLEN - 1, "Converted using Sox.");
        sprintf(header.name, "%-*.*s", NAMELEN, NAMELEN, comment);
        free(comment);

        /* Write file header */
        if(lsx_writebuf(ft, &header, HEADERSIZE) != HEADERSIZE)
        {
            lsx_fail_errno(ft,errno,"can't write header completely");
            return(SOX_EOF);
        }
        if (lsx_writedw(ft, 0))      /* write as zero length for now, update later */
            write_error();
        smp->NoOfSamps = 0;

        return(SOX_SUCCESS);
}

static size_t sox_smpwrite(sox_format_t * ft, const sox_sample_t *buf, size_t len)
{
        priv_t * smp = (priv_t *) ft->priv;
        int datum;
        size_t done = 0;

        while(done < len) {
                SOX_SAMPLE_LOCALS;
                datum = (int) SOX_SAMPLE_TO_SIGNED_16BIT(*buf++, ft->clips);
                if (lsx_writew(ft, (uint16_t)datum))
                        write_error();
                smp->NoOfSamps++;
                done++;
        }

        return(done);
}

static int sox_smpstopwrite(sox_format_t * ft)
{
        priv_t * smp = (priv_t *) ft->priv;
        struct smptrailer trailer;

        /* Assign the trailer data */
        settrailer(ft, &trailer, ft->signal.rate);
        if (writetrailer(ft, &trailer) != SOX_SUCCESS)
                write_error();
        if (lsx_seeki(ft, (off_t)112, 0) == -1)
        {
                lsx_fail_errno(ft,errno,"unable to seek back to save size");
                return(SOX_EOF);
        }
        if (lsx_writedw(ft, smp->NoOfSamps > UINT_MAX ? UINT_MAX : (unsigned)smp->NoOfSamps))
                write_error();

        return(SOX_SUCCESS);
}

LSX_FORMAT_HANDLER(smp)
{
  static char const * const names[] = {"smp", NULL};
  static unsigned const write_encodings[] = {SOX_ENCODING_SIGN2, 16, 0, 0};
  static sox_format_handler_t handler = {SOX_LIB_VERSION_CODE,
    "Turtle Beach SampleVision", names, SOX_FILE_LIT_END | SOX_FILE_MONO,
    sox_smpstartread, sox_smpread, NULL,
    sox_smpstartwrite, sox_smpwrite, sox_smpstopwrite,
    sox_smpseek, write_encodings, NULL, sizeof(priv_t)
  };
  return &handler;
}
