/* sdif2adsyn.c: convert SDIF 1TRC additive synthesis data to Csound adsyn format
 * v1.01 Richard W Dobson September 26 2000  */


/* updates:
 * 26:9:2000   deal with Ircam 1NVT frames, and handle other skipped
 * frames properly
*/



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

#include "sdif.h"
#include "sdif-mem.h"

#define MAX(a,b) ((a)>(b) ? (a) : (b))
#define MIN(a,b) ((a)>(b) ? (b) : (a))

/* SDIF indices start from 1 */
#define MAXPARTIALS (1025)
/* some old but worthy CNMAT functions */

typedef struct {
    sdif_float32 index, freq, amp, phase;
} SDIF_RowOf1TRC;



int
SDIF_Read1TRCVals(FILE *f,
                  sdif_float32 *indexp, sdif_float32 *freqp,
                  sdif_float32 *ampp,   sdif_float32 *phasep)
{
    SDIF_RowOf1TRC data;

#ifdef LITTLE_ENDIAN
    if (SDIF_Read4(&data, 4, f) != ESDIF_SUCCESS) return -1;
#else
    if (fread(&data, sizeof(data), 1, f) != 1) return -1;
#endif

    *indexp = data.index;
    *freqp  = data.freq;
    *ampp   = data.amp;
    *phasep = data.phase;

    return 0;
}

void usage(void)
{
    printf("SDIF2ADS v1.01: convert sdif 1TRC data to Csound hetro data file.\n"
           "usage: [-sN][-pN] sdif2adsyn infile.sdif outfile.ads\n"
           "        -s   : apply amplitude scale factor N\n"
           "        -p   : keep only the first N partials.\n"
           "               (limit: 1024 partials)\n"
           "  NB: the source partial track indices are used directly\n"
           "      to select internal storage.\n"
           "      As these can be arbitrary values, the maximum\n"
           "      of 1024 partials may not be realised in all cases.\n");
}


typedef struct partial_point {
        float amp;
        float freq;
        float pos;
        struct partial_point *next;
} P_POINT;


typedef struct partial_props {
        long numpoints;
        float fmaxamp;
        float maxfreq;
        float minfreq;
        P_POINT *head;
} PPROPS;

int write_partial(FILE *fp, const P_POINT *partial, float sfac, int do_scale);

P_POINT *new_ppoint(float amp, float freq, float pos)
{
    P_POINT *point;

    point = (P_POINT *) malloc(sizeof(P_POINT));
    if (point) {
      point->amp  = amp;
      point->freq = freq;
      point->pos  = pos;
      point->next = NULL;
    }
    return point;
}


/* if anyone wants it, there is enough information to select a frequency
 * range to output.*/

PPROPS *new_pprops(void)
{
    PPROPS *prop;
    prop = (PPROPS *) malloc(sizeof(PPROPS));
    if (prop) {
      prop->numpoints= 0;
      prop->fmaxamp = 0.0f;
      prop->maxfreq = 0.0f;
      prop->minfreq = 0.0f;
      prop->head = NULL;        /* will point to head of partial[n]     */
    }
    return prop;
}



int main(int argc, char **argv)
{
    int i, framecount, partials_written = 0;
    int result, iindex;
    int max_partials = MAXPARTIALS;
    int n_partials = 0, maxpoints = 0;
    sdif_int32 frame_id = -1;
    SDIF_FrameHeader fh;
    SDIF_MatrixHeader   mh;

    FILE *infile, *outfile;
    P_POINT **partials;
    PPROPS **pprops;
    sdif_float32 index, amp, freq, phase;

    float thistime = 0.0f;
    float maxfreq  = 0.0f, minfreq = 100000.0f;
    float fmaxamp  = 0.0f;
    float scalefac = 1.0f;
    int doscale    = 0;
    short stemp;                 /* to write partial count to adsyn file */

    if (argc < 2) {
      usage();
      exit(1);
    }
    printf("SDIF2ADS v1.01\n");

    if (SDIF_Init() != ESDIF_SUCCESS) {
      fprintf(stderr, "OOPS: SDIF does not work on this machine!\n");
      exit(1);
    }

    while (argv[1][0]=='-') {
      switch (argv[1][1]) {
      case 's':
        if (argv[1][2] == '\0') {
          fprintf(stderr, "-s flag requires parameter.\n");
          usage();
          exit(1);
        }
        scalefac = (float) atof(&(argv[1][2]));
        if (scalefac==0.0f) {
          fprintf(stderr, "silly value for scalefac!\n");
          exit(1);
        }
        doscale = 1;
        break;
      case 'p':
        if (argv[1][2] == '\0') {
          fprintf(stderr, "-p flag requires parameter.\n");
          usage();
          exit(1);
        }
        max_partials = atoi(&(argv[1][2]));
        if (max_partials < 1) {
          fprintf(stderr, "-p value too low: need at least one partial!\n");
          usage();
          exit(1);
        }
        if (max_partials > (MAXPARTIALS-1)) {
          printf("Warning: -p value too high: setting to %d\n",
                 MAXPARTIALS-1);
        }
        break;
      default:
        fprintf(stderr, "\nunrecognised flag option %s", argv[1]);
        usage();
        exit(1);
      }
      argc--;
      argv++;
    }
    if (argc < 2) {
      fprintf(stderr, "\nSDIF2ADS: insufficient arguments");
      usage();
      exit(1);
    }

    result = SDIF_OpenRead(argv[1], &infile);
    if (infile == NULL) {
      fprintf(stderr, "Couldn't open %s: %s\n",
              argv[1], SDIF_GetErrorString(result));
      exit(1);
    }

    if ((outfile = fopen(argv[2], "wb"))==NULL) {
      fprintf(stderr, "\nunable to open outfile %s", argv[3]);
      fclose(infile);
      exit(1);
    }

    /* initialize partials array */
    /* NB index value 0 NOT USED */
    /* allocate for 1024 partials; use user limit on writing outfile */
    partials  = (P_POINT **) malloc(MAXPARTIALS * sizeof(P_POINT *));
    if (partials==NULL) {
      puts("No memory for partial data!\n");
      exit(1);
    }
    pprops = (PPROPS **) malloc (MAXPARTIALS * sizeof(PPROPS *));
    if (pprops==NULL) {
      puts("No memory for partial data!\n");
      free(partials);
      exit(1);
    }

    for (i=0;i < MAXPARTIALS; i++) {
      partials[i] = NULL;
      pprops[i] = NULL;
    }

    /* STRATEGY: a very straight conversion from a frame structure to
     * a breakpoint structure.  For each new partial in the SDIF file,
     * we start a new partial in the array, at the given index.
     * When a partial dies, the amplitude is (presumed to be) zero,
     * but the list is not terminated.   If an SDIF frame with that
     * index starts a new partial, the partial with that index
     * continues accordingly.
     *
     *  Though in most cases, track indices will small and sequential,
     *  this cannot be presumed. If most or all indices are above
     *  MAXPARTIALS, we are b******d. Of course, the SDIF file will
     *  not tell us what the index range is. That would be too easy.
     */

    /* main loop to read sdif frames */
    framecount = 0;
    /* assume the frame is kosher for now...*/
    while ((result = SDIF_ReadFrameHeader(&fh, infile))==ESDIF_SUCCESS) {
      if (strncmp(fh.frameType, "1TRC", 4)) {
        /* could be frame such as 1NVT, with no matrices, but text data,
           so must always skip data explicitly */
        if (SDIF_SkipFrame(&fh, infile) != ESDIF_SUCCESS) {
          fprintf(stderr, "Error skipping frame.\n");
          exit(1);
        }
        continue;
      }

      /*we can only copy one stream - use the first one! */
      if (frame_id==-1)
        frame_id = fh.streamID;
      else if (frame_id != fh.streamID) {
        fprintf(stderr, "\nWARNING: multiple stream IDs found - skipping");
        if (SDIF_SkipFrame(&fh, infile) != ESDIF_SUCCESS) {
          fprintf(stderr, "Error skipping frame.\n");
          exit(1);
        }
        continue;
      }
#ifdef _DEBUG
      printf("\nReading SDIF Frame %d, time = %.4f", framecount, fh.time);
#endif
      thistime = (float) fh.time;
      /* Csound adsyn counts time in msecs, in shorts, so can only
       * have 32.767 seconds! */
      if (thistime > 32.76f) {
        printf("Sorry! Reached ADSYN limit of 32.76 seconds.\n");
        break;
      }
      framecount++;
      for (i = 0; i < fh.matrixCount; ++i) {
        int j;

        if (SDIF_ReadMatrixHeader(&mh, infile) != ESDIF_SUCCESS) {
          fprintf(stderr, "\nerror reading infile");
          exit(1);
        }
        /* for now, only accept floats */
        if (mh.matrixDataType != SDIF_FLOAT32) {
          fprintf(stderr, "\nsorry - data not floats");
          exit(1);
        }

        n_partials = mh.rowCount;
#ifdef _DEBUG
        printf("\nReading Matrix %ld: %ld rows", i+1, n_partials);
#endif

        for (j=0;j < n_partials; j++) {
          if (SDIF_Read1TRCVals(infile, &index, &freq, &amp, &phase))      {
            fprintf(stderr, "\nerror reading tracks");
            exit(1);
          }
          if (index < 1.0f) {
            fprintf(stderr, "Illegal partial index %.2f found in frame %d.\n",
                    index, framecount);
            exit(1);
          }
          iindex = (int) index;
                                /* a small (unlikely) sacrifice for
                                   the sake of a simple algorithm! */
          if (iindex >= MAXPARTIALS) {
            printf("Warning: high partial index %d in frame %d; skipping.\n",
                   iindex, framecount);
            continue;
          }
                                /* make a new list if we need it */
          if (partials[iindex]==NULL) {
            partials[iindex] = new_ppoint(amp, freq, thistime);
            if (partials[iindex]==NULL) {
              puts("No memory for partial data!\n");
              exit(1);
            }
            pprops[iindex] = new_pprops();
            if (pprops[iindex]==NULL) {
              puts("No memory for partial data!\n");
              exit(1);
            }
            pprops[iindex]->head      = partials[iindex];
            pprops[iindex]->fmaxamp   = amp;
            pprops[iindex]->maxfreq   = pprops[iindex]->minfreq = freq;
            pprops[iindex]->numpoints = 1;
          }
          else {
            /*attach new point to existing list, and update props */
            partials[iindex]->next = new_ppoint(amp, freq, thistime);
            if (partials[iindex]->next==NULL) {
              puts("No memory for partial data!\n");
              exit(1);
            }
            partials[iindex] = partials[iindex]->next;

            pprops[iindex]->fmaxamp = MAX(pprops[iindex]->fmaxamp, amp);
            pprops[iindex]->maxfreq = MAX(pprops[iindex]->maxfreq, freq);
            pprops[iindex]->minfreq = MIN(pprops[iindex]->minfreq, freq);
            pprops[iindex]->numpoints++;
          }
        }
      }
    }
    /* maybe a single frame is legit? */
    if (framecount < 2) {
      if (framecount==1)
        fprintf(stderr, "Only one frame found: at least two required.\n");
      else
        fprintf(stderr, "No 1TRC frames found in this sdif file.\n");
      SDIF_CloseRead(infile);
      fclose(outfile);
      free(partials);
      free(pprops);
      exit(0);
    }

    /* now we have to write it all out for Csound */
    /* how many partials do we have, and what might a good srate be?*/
    n_partials = 0;
    for (i=0;i < MAXPARTIALS;i++) {
      if (partials[i] != NULL) {
        maxfreq = MAX(maxfreq, pprops[i]->maxfreq);
        minfreq = MIN(minfreq, pprops[i]->minfreq);
        fmaxamp = MAX(fmaxamp, pprops[i]->fmaxamp);
        maxpoints = MAX(maxpoints, pprops[i]->numpoints);
        n_partials++;
      }
    }
    printf("total partials read   = %d\n", n_partials);
    printf("maximum breakpoints   = %d\n", maxpoints);
    printf("max frequency found   = %.4f\n", maxfreq);
    printf("min frequency found   = %.4f\n", minfreq);
    printf("max partial amp found = %.4f\n", fmaxamp);
    printf("last frame time       = %.4f\n", thistime);

    /* OK, write this all to  adsyn file as shorts (for now....) */
    stemp = (short) n_partials;
    stemp = MIN(stemp, max_partials);   /* set user limit, if any */
    /* write number of partial tracks */
    if (fwrite((short *) &stemp, sizeof(short), 1, outfile) < 1) {
      fprintf(stderr, "Error writing to outfile.\n");
      exit(1);
    }

    /* now write each set of amp,freq sets */
    for (i=0;i < MAXPARTIALS;i++) {
      if (partials[i]==NULL)
        continue;
      if (write_partial(outfile, pprops[i]->head, scalefac, doscale) < 0) {
        fprintf(stderr, "Error writing partial %d.\n", i);
        exit(1);
      }
      partials_written++;
      if (partials_written == (int) stemp)
        break;
    }

    /* release linked lists */
    for (i=0;i < MAXPARTIALS;i++) {
      P_POINT *tthis, *next;
      if (partials[i]==NULL)
        continue;
      tthis = pprops[i]->head;
      free(pprops[i]);
      next = tthis->next;
      while(next) {
        free(tthis);
        tthis = next;
        next = next->next;
      }
      free(tthis);
    }
    free(partials);
    free(pprops);
    fclose(outfile);
    printf("File conversion completed.\n");
    printf("%d partials written to %s\n", (int)stemp, argv[2]);
    SDIF_CloseRead(infile);
    return 0;
}

/* pass props->head */
/* NB does not check for over-range times */
/* it also trusts that there is a freq element matching each amp element */
int write_partial(FILE *fp, const P_POINT *partial, float sfac, int do_scale)
{
    int length = 0;
    const P_POINT *head;
    float scalefac = 32767.0f;
    short amp, freq, time;
    short amptag = -1, freqtag = -2;
    short trackend = 32767;

    if (fp==NULL)
      return -1;
    if (partial==NULL)
      return -1;

    if (do_scale)
      scalefac *= sfac;

    head = partial;
    if ((fwrite((char *)&amptag, sizeof(short), 1, fp)) < 1)
      return -1;

    /* nothing clever, just go through each list twice */

    /*write amps, scaled to 32767 (oh for a f/p adsyn format...) */
    while (head) {
      time = (short)(1000.0f * head->pos);
      if ((fwrite((char *) &time, sizeof(short), 1, fp)) < 1)
        return -1;
      amp = (short) (scalefac * head->amp);
      if ((fwrite((char *)&amp, sizeof(short), 1, fp)) < 1)
        return -1;
      length++;
      head = head->next;
    }
    if ((fwrite(&trackend, sizeof(short), 1, fp)) < 1)
      return -1;

    head = partial;
    if ((fwrite(&freqtag, sizeof(short), 1, fp)) < 1)
      return -1;
    while (head) {
      time = (short)(1000.0f * head->pos);
      if ((fwrite((char *) &time, sizeof(short), 1, fp)) < 1)
        return -1;
      freq = (short)(head->freq);
      if ((fwrite(&freq, sizeof(short), 1, fp)) < 1)
        return -1;
      head = head->next;
    }
    if ((fwrite(&trackend, sizeof(short), 1, fp)) < 1)
      return -1;
    return length;
}
