/* cep2aup.c -- Main File

   CoolEdit Pro (c) Adobe (prev. Syntrillium) to .aup (Audacity Project).
	
   Copyright (C) 2004
   Arturo "Buanzo" Busleiman <buanzo@buanzo.com.ar> <buanzo@linux.org.ar>
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

#define STDERR		stderr
#define DEBUG		1

typedef unsigned long	DWORD;
typedef unsigned short	WORD;
typedef unsigned long	BOOL;

int buennoc_n_chunks = 0;

float longest = 0;

struct Tcep_hdr
{
  int rate;
  DWORD n_samples;
  int n_wblocks;
  WORD bps;
  WORD n_chan;
  double master_vol_l;
  double master_vol_r;
  DWORD stois;
  DWORD assoc;
  DWORD private;
  char filenam[256];
}
cep_hdr;

struct Tcep_vers
{
  char txt[256];
}
cep_vers;

struct Tcep_stat
{
  double left_view;
  double right_view;
  double bottom_view;
  double top_view;
  DWORD low_samp_sel;
  DWORD high_samp_sel;
}
cep_stat;

struct Tcep_tmpo
{
  double bpm;
  int beats_per_bar;
  int ticks_per_beat;
  double beat_offset;
}
cep_tmpo;

struct Tcep_trks
{
  DWORD n_trks;
}
cep_trks;

struct Tcep_trks_data
{
  double left_vol;
  double right_vol;
  DWORD flags;
  char title[36];
  DWORD wave_in_id;
  DWORD wave_out_id;
  WORD wave_in_mode;
  WORD wave_out_mode;
  WORD unused_1;
  WORD unused_2;
}
cep_trks_data[256];

struct Tcep_wav_header
{
  DWORD offset;
  DWORD len;
}
cep_wav_header[256];

struct Tcep_wav_data
{
  DWORD wav_id;
  DWORD file_format;
  char filename[256];
}
cep_wav_data[256];

struct Tcep_blk_header
{
  DWORD count;
  DWORD size;
}
cep_blk_header;

struct Tcep_blk_data
{
  double left_vol;
  double right_vol;
  double unused_1;
  double unused_2;
  DWORD offset;
  DWORD size_samp;
  DWORD wav_id;
  DWORD flags;
  DWORD uniq_wav_id;
  int track;
  int parent_group;
  int unused_3;
  DWORD offset_wav;
  DWORD unused_4;
}
cep_blk_data[256];

void *buennoc (size_t size, int salir);
unsigned int validName (unsigned char *cv);
int TStell (char *cv);

struct Tsection
{
  char *name;
  DWORD offset;
  DWORD len;
}
ts[30];

/* This array contains a list of valid section names for the SESsion file
We can differentiate 1.x from 2.x formats by the number of sections, clearly
related to the greater functionality inherent to CoolEdit 2.x */

char *vs[] = { "COOLNESS", "hdr ", "vers", "stat", "tmpo", "trks", "LISTFILE",
  "bk20", "tkeq", "blk ", "blk2", "envp", "loop", "cues", "mtro", "time",
  "midx",
  NULL
};

/* 
WAVE format chunk / section. We need it to get a wav file rate. Thanks to
google for the 'wav file format' search, and its first result,
http://www.borg.com/~jglatt/tech/wave.htm. This is a really good description
of the wave file format, written by Jeff Glatt. Check out his site at 
http://www.borg.com/~jglatt/. This is the disclaimer at the site:

WARNING! This Web Site contains pictures of bare male and female DIN
connectors, as well as explicit talk about "dumps with handshakes".
Before continuing, you must be an adult, preferably one who isn't a
hypocritical U.S. legislator who sponsors a "porn" bill while he's
cheating on his spouse with an illegal alien prostitute whom he pays
(using taxpayer's money) to spank his swollen buttocks with a
rolled-up copy of The New Republic.
                     
There are also a lot of annoying, abrasive attempts at humor. Get used
to it, or kill yourself.
*/

struct FormatChunk {
long	chunkSize;
short	wFormatTag;
WORD	wChannels;
DWORD	dwSamplesPerSec;
DWORD	dwAvgBytesPerSec;
WORD	wBlockAlign;
WORD	wBitsPerSample;
} * fmtchunk[256];

char cur_section[20] = "";
int n_sections = 0;
int n_waves = 0;

void *
buennoc (size_t size, int salir)
{
  void *memry = NULL;
  memry = malloc (size);
  if (memry == NULL)
    {
      if (salir > 0)
	{
	  perror ("buennoc");
	  exit (127);
	}
      else
	return (NULL);
    }
  else
    {
      memset (memry, 0, size);
      buennoc_n_chunks++;
      return (memry);
    }
}

unsigned int
validName (unsigned char *cv)
{
  int i = 0;
  for (i = 0;; i++)
    {
      if (vs[i] == NULL)
	return (0);
      if (strncmp ((const char *) cv, vs[i], strlen (vs[i])) == 0)
	{
#warning Corregir el strcpy de validname()
	  strcpy (cur_section, vs[i]);	/* No need to strN here */
	  return (strlen (vs[i]));
	}
    }
}

int
TStell (char *cv)
{
  int i = 0;
  for (i = 0;; i++)
    {
      if (ts[i].name == NULL)
	return (-1);
      if (strcmp (cv, ts[i].name) == 0)
	return (i);
    }

}

char *
uwid2filename (DWORD id)
{
  int i = 0;
  char *p = NULL;
  for (i = 0; i < n_waves; i++)
    if (cep_wav_data[i].wav_id == id)
      {
	p = buennoc (strlen (cep_wav_data[i].filename) + 1, 1);
	strncpy (p, cep_wav_data[i].filename,
		 strlen (cep_wav_data[i].filename));
	return (p);
      }
  return NULL;
}

struct FormatChunk * read_FormatChunk(char *wavefile)
{
struct FormatChunk *localfmt;
FILE *wfp;
wfp = fopen(wavefile,"r");
fseek(wfp,16,SEEK_SET);
fread(localfmt,sizeof(struct FormatChunk),1,wfp);
fclose(wfp);
return localfmt;
}

int
main (int argc, char **argv)
{
  struct stat fin_stat;
  FILE *fin = NULL;
  unsigned char *buf = NULL;
  DWORD pos = 0;
  int t = 0;
  int vsz = 0;
  int i = 0;
int wbi = 0;
  if (argc < 2)
    {
      fprintf
	(STDERR,"cep2aup 0.1.0 by Arturo 'Buanzo' Busleiman <buanzo@buanzo.com.ar>\nUsage:\n\tThis program should not be called directly. Instead use the provided\nses2aup.sh script.\n");
      exit (128);
    }
  else
    {
      if (DEBUG)
	fprintf (STDERR, "Opening '%s' ... ", argv[1]);
      fin = fopen (argv[1], "r");
      if (DEBUG)
	fprintf (STDERR, "%s\n", fin ? "Ok" : "Error");
    }
  if (!fin)
    exit (255);
  fstat (fileno (fin), &fin_stat);
  if (DEBUG)
    fprintf (STDERR, "Allocating %ld bytes... ", fin_stat.st_size);
  if ((buf = buennoc (fin_stat.st_size, 0)) == NULL)
    {
      if (DEBUG)
	fprintf (STDERR, "Error!\n");
      exit (255);
    }
  else if (DEBUG)
    fprintf (STDERR, "Ok\n");
  free (buf);
  if (DEBUG)
    fprintf (STDERR, "Reading session file to memory...");
  fread (buf, fin_stat.st_size, 1, fin);
  if (DEBUG)
    fprintf (STDERR, "Done\n");
  fclose (fin);
  if (DEBUG)
    fprintf (STDERR, "File closed. Getting structures...\n");

/* ROUTINE TO GET names, offsets, and lengths of sections.
Seems to automatically manage different CoolEdit Session format versions. */

  i = -1;

/* I use the pos variable as an absolute index to the .ses file contents */

  for (pos = 0; pos < fin_stat.st_size;)
    {
      if ((vsz = validName (buf + pos)))
	{
/* When we get a valid section name, we record its name and offset on the ts
structure, so later we simple use the helper function TSTell to get a
section's offset and length */
	  i++;
	  ts[i].name = buennoc (vsz + 1, 0);
	  strncpy (ts[i].name, cur_section, vsz);
	  ts[i].offset = pos;
	  memcpy (&ts[i].len, buf + pos + vsz, 4);
	  if (DEBUG)
	    fprintf (STDERR, "Section '%s' found at offset 0x%.4lx\n",
		     ts[i].name, ts[i].offset);
	  n_sections++;
	  if (strncmp (ts[i].name, "COOLNESS", 8) == 0)
	    {
	      pos = 12;
	    }
	  else
/* If we find a section, then we can guess where the next one begins, and 
set pos to that offset. */
	    pos = ts[i].offset + ts[i].len + 8;
	}
      else
/* If we don't, keep going */
	pos++;
    }


/* I need to test more CoolEdit sessions.. I only had access to 1.2a and 2.x. */

  if (DEBUG)
    fprintf (STDERR,
	     "Found %d sections. Seems like a CoolEdit Pro %s session.\n",
	     n_sections, n_sections < 12 ? "1.x" : "2.x");
/* END OF name/len/offset routine */

/* Now, I have to fill-in each C-structure for the buffered session */
/* Variables t, i and vsz now are multipurpose */

  t = TStell ("hdr ");
  memcpy (&cep_hdr, buf + ts[t].offset + 8, ts[t].len);

  if (DEBUG)
    fprintf (STDERR, "\
Session at %d kHz, %d bits of resolution.\n\
Number of Samples     : %ld\n\
Number of Wave Blocks : %d\n\
Master Volume         : %f dB\n\
", cep_hdr.rate, cep_hdr.bps, cep_hdr.n_samples, cep_hdr.n_wblocks, cep_hdr.master_vol_l);

  t = TStell ("vers");
  strncpy (cep_vers.txt, (const char *) buf + ts[t].offset + 8, ts[t].len);
  if (cep_vers.txt[strlen (cep_vers.txt) - 1] == ' ')
    {
      cep_vers.txt[strlen (cep_vers.txt) - 1] = 0;
      ts[t].len--;
    }

  if (DEBUG)
    fprintf (STDERR, "\033[0;36m%s\033[0;37m session file.\n", cep_vers.txt);

  cep_vers.txt[255] = 0;

  t = TStell ("stat");
  memcpy (&cep_stat, buf + ts[t].offset + 8, sizeof (cep_stat));

  t = TStell ("tmpo");
  memcpy (&cep_tmpo, buf + ts[t].offset + 8, sizeof (cep_tmpo));

  if (DEBUG)
    fprintf (STDERR,
	     "Tempo: \033[0;36m%f\033[0;37m BPM (used or unused, I can't tell).\n",
	     cep_tmpo.bpm);

  t = TStell ("trks");
  memcpy (&cep_trks, buf + ts[t].offset + 8, sizeof (cep_trks));

  vsz = (ts[t].len - 4) / cep_trks.n_trks;

  if (DEBUG)
    fprintf (STDERR, "There are %ld tracks of %d bytes each (%ld total).\n",
	     cep_trks.n_trks, vsz, ts[t].len - 4);

  for (i = 0; i < cep_trks.n_trks; i++)
    {
      if (DEBUG)
	fprintf (STDERR, "Track %3.3d ", i + 1);
      memcpy (&cep_trks_data[i], buf + ts[t].offset + 12 + (vsz * i), vsz);
      if (DEBUG)
	fprintf (STDERR, "%c%c%c",
		 cep_trks_data[i].flags & 1 ? 'M' : '-',
		 cep_trks_data[i].flags & 2 ? 'S' : '-',
		 cep_trks_data[i].flags & 4 ? 'R' : '-');
      if (DEBUG)
	fprintf (STDERR,
		 " Volume (L/R): '\033[0;36m%f\033[0;37m/\033[0;36m%f\033[0;37m'",
		 cep_trks_data[i].left_vol, cep_trks_data[i].right_vol);
      if (DEBUG)
	fprintf (STDERR, " Title: '\033[0;36m%s\033[0;37m'\n",
		 cep_trks_data[i].title);
    }

  t = TStell ("LISTFILE");

  i = -1;
  if (DEBUG)
    fprintf (STDERR, "Starting waves search at offset 0x%lx\n",
	     ts[t].offset + 12);
  for (pos = ts[t].offset + 12; pos < ts[t].offset + ts[t].len;)
    {
      if (strncmp ((const char *) buf + pos, "wav ", 4) == 0)
	{
	  i++;
	  cep_wav_header[i].offset = pos;
	  memcpy (&cep_wav_header[i].len, buf + pos + 4, 4);
	  if (DEBUG)
	    fprintf (STDERR, "Wave found at offset 0x%.4lx (Len=%ld)\n",
		     cep_wav_header[i].offset, cep_wav_header[i].len);
	  n_waves++;
	  pos = cep_wav_header[i].offset + cep_wav_header[i].len + 8;
	}
      else
	pos++;
    }
  if (DEBUG)
    fprintf (STDERR, "Found %d waves:\n", n_waves);

  for (i = 0; i < n_waves; i++)
    {
      memcpy (&cep_wav_data[i], buf + cep_wav_header[i].offset + 8,
	      cep_wav_header[i].len);
      if (DEBUG)
	fprintf (STDERR, "[%4.4d] ID: 0x%lx - '%s'\n", i,
		 cep_wav_data[i].wav_id, cep_wav_data[i].filename);
    }

  if (DEBUG)
    fprintf (STDERR, "Scanning for Blocks...\n");

  t = TStell ("blk ");
  memcpy (&cep_blk_header, buf + ts[t].offset + 8,
	  sizeof (cep_blk_header.count));
  cep_blk_header.size = (ts[t].len - 4) / cep_blk_header.count;
  if (DEBUG)
    fprintf (STDERR,
	     "There are \033[1;36m%ld\033[0;37m blocks of %ld bytes each.\n",
	     cep_blk_header.count, cep_blk_header.size);
  for (i = 0; i < cep_blk_header.count; i++)
    {
      memcpy (&cep_blk_data[i],
	      buf + ts[t].offset + 12 + (cep_blk_header.size * i),
	      sizeof (struct Tcep_blk_data));
      if ((double) cep_blk_data[i].offset / cep_hdr.rate > longest)
	longest = (double) cep_blk_data[i].offset / cep_hdr.rate;
    }
  printf ("<?xml version=\"1.0\"?>\n");
  printf("<audacityproject projname=\"%s\" version=\"1.1.0\" audacityversion=\"1.2.3\" sel0=\"0.0000000000\" sel1=\"0.0000000000\" vpos=\"0\" h=\"0.0000000000\" zoom=\"%s\" rate=\"%f\" >\n","INSERTAR_TITULO_DE_SESION_EN_PORCIENTO_S","INSERTAR_ZOOM_AQUI_EN_PORCIENTO_F",(double) cep_hdr.rate);
printf("\t<tags title=\"\" artist=\"\" album=\"\" track=\"-1\" year=\"\" genre=\"-1\" comments=\"\" id3v2=\"1\" />\n");
  for (i = 0; i < cep_blk_header.count; i++)
    {
      memcpy (&cep_blk_data[i],
	      buf + ts[t].offset + 12 + (cep_blk_header.size * i),
	      sizeof (struct Tcep_blk_data));
      if (DEBUG)
	fprintf (STDERR,
		 "[%4.4d] Into track %4.4d, sess_in %f, wav_in %f - Refers. to '%s'\n",
		 i, cep_blk_data[i].track,
		 (double) cep_blk_data[i].offset / cep_hdr.rate,
		 (double) cep_blk_data[i].offset_wav / cep_hdr.rate,
		 uwid2filename (cep_blk_data[i].uniq_wav_id));

printf ("\t<wavetrack name=\"%s\" channel=\"%s\" linked=\"0\" offset=\"%f\" rate=\"%s\" gain=\"%s\" pan=\"%s\" >\n",cep_trks_data[i].title,"INSERTAR_CANTIDAD_DE_CANALES_DEL_TRACK",(double) cep_blk_data[i].offset / cep_hdr.rate,"INSERTAR_RATE","INSERTAR_GAIN","INSERTAR_PAN");
printf("\t\t<sequence maxsamples=\"262144\" sampleformat=\"262159\" numsamples=\"%u\" >\n",cep_blk_data[i].size_samp);

for (wbi = 0;wbi < 0;wbi++) {
}

/*      printf ("# Original filename: %s\nfile \"%s\" offset %f\n",
	      uwid2filename (cep_blk_data[i].uniq_wav_id),
	      rindex (uwid2filename (cep_blk_data[i].uniq_wav_id), '\\') + 1,
	      (double) cep_blk_data[i].offset / cep_hdr.rate);
*/
printf("\t\t</sequence>\n");
printf("\t\t<envelope numpoints='0'>\n");
/* TODO: set envelope parameters */
printf("\t\t</envelope>\n");
printf("\t</wavetrack>\n");
    }
printf("</audacityproject>\n");
  return (0);

}

