/* sound.c */

#if defined(HAVE_CONFIG_H)
  #include "config.h"
#endif

#include <math.h>
#include <stdio.h>
#if (!defined(HAVE_CONFIG_H)) || (defined(HAVE_FCNTL_H))
  #include <fcntl.h>
#endif
#include <signal.h>
#if (!defined(HAVE_CONFIG_H)) || (defined(HAVE_LIMITS_H))
  #include <limits.h>
#endif
#include <errno.h>
#include <stdlib.h>

#if (defined(NEXT) || (defined(HAVE_LIBC_H) && (!defined(HAVE_UNISTD_H))))
  #include <libc.h>
#else
  #include <unistd.h>
  #include <string.h>
#endif

#include <ctype.h>
#include <stddef.h>

#include "sndlib.h"

#if MACOS
  #include <time.h>
  #include <stat.h>
#else
  #include <sys/types.h>
  #include <sys/stat.h>
#endif

static char *copy_string (char *str)
{
  char *newstr = NULL;
  if ((str) && (*str))
    {
      newstr = (char *)CALLOC(strlen(str)+1,sizeof(char));
      strcpy(newstr,str);
    }
  return(newstr);
}
      
static time_t file_write_date(char *filename)
{
  struct stat statbuf;
  int err;
  err = stat(filename,&statbuf);
  if (err < 0) return(err);
  return((time_t)(statbuf.st_mtime));
return(0);
}


static int sndlib_initialized = 0;

void initialize_sndlib(void)
{
  if (!sndlib_initialized)
    {
      sndlib_initialized = 1;
      create_header_buffer();
      create_descriptors();
      initialize_audio();
    }
}

typedef struct {
  char *file_name;  /* full path -- everything is keyed to this name */
  int table_pos;
  int *aux_comment_start,*aux_comment_end;
  int *loop_modes,*loop_starts,*loop_ends;
  int markers;
  int *marker_ids,*marker_positions;
  int data_size, datum_size, data_location, srate, chans, header_type, data_format, original_sound_format, true_file_length;
  int comment_start, comment_end, header_distributed, type_specifier, bits_per_sample, fact_samples, block_align;
  int write_date;
} sound_file;

static int sound_table_size = 0;
static sound_file **sound_table = NULL;

static void free_sound_file(sound_file *sf)
{
  if (sf)
    {
      sound_table[sf->table_pos] = NULL;
      if (sf->aux_comment_start) FREE(sf->aux_comment_start);
      if (sf->aux_comment_end) FREE(sf->aux_comment_end);
      if (sf->file_name) FREE(sf->file_name);
      if (sf->loop_modes) FREE(sf->loop_modes);
      if (sf->loop_starts) FREE(sf->loop_starts);
      if (sf->loop_ends) FREE(sf->loop_ends);
      if (sf->marker_ids) FREE(sf->marker_ids);
      if (sf->marker_positions) FREE(sf->marker_positions);
      FREE(sf);
    }
}

static sound_file *add_to_sound_table(void)
{
  int i,pos;
#ifdef MACOS
  sound_file **ptr;
#endif
  pos = -1;
  for (i=0;i<sound_table_size;i++)
    if (sound_table[i] == NULL) 
      {
	pos = i;
	break;
      }
  if (pos == -1)
    {
      pos = sound_table_size;
      sound_table_size += 16;
      if (sound_table == NULL)
	sound_table = (sound_file **)CALLOC(sound_table_size,sizeof(sound_file *));
      else 
	{
#ifdef MACOS
	  ptr = (sound_file **)CALLOC(sound_table_size,sizeof(sound_file *));
	  for (i=0;i<pos;i++) ptr[i] = sound_table[i];
	  FREE(sound_table);
	  sound_table = ptr;
#else
	  sound_table = (sound_file **)REALLOC(sound_table,sound_table_size * sizeof(sound_file *));
#endif
	  for (i=pos;i<sound_table_size;i++) sound_table[i] = NULL;
	}
    }
  sound_table[pos] = (sound_file *)CALLOC(1,sizeof(sound_file));
  sound_table[pos]->table_pos = pos;
  return(sound_table[pos]);
}

static sound_file *find_sound_file(char *name)
{
  int i,date;
  sound_file *sf;
  for (i=0;i<sound_table_size;i++)
    {
      if (sound_table[i])
	{
	  if (strcmp(name,sound_table[i]->file_name) == 0)
	    {
	      sf = sound_table[i];
	      date = file_write_date(name);
	      if (date == sf->write_date)
		return(sf);
	      else 
		{
		  free_sound_file(sf);
		  return(NULL);
		}
	    }
	}
    }
  return(NULL);
}

static void fill_sf_record(sound_file *sf)
{
  sf->data_location = c_snd_header_data_location();
  sf->data_size = c_snd_header_data_size();
  sf->data_format = c_snd_header_format();
  sf->srate = c_snd_header_srate();
  sf->chans = c_snd_header_chans();
  sf->datum_size = c_snd_header_datum_size();
  sf->header_type = c_snd_header_type();
  sf->original_sound_format = c_snd_header_original_format();
  sf->true_file_length = c_true_file_length();
  sf->comment_start = c_snd_header_comment_start();
  sf->comment_end = c_snd_header_comment_end();
  sf->header_distributed = c_snd_header_distributed();
  sf->type_specifier = c_snd_header_type_specifier();
  sf->bits_per_sample = c_snd_header_bits_per_sample();
  sf->fact_samples = c_snd_header_fact_samples();
  sf->block_align = c_snd_header_block_align();
  sf->write_date = file_write_date(sf->file_name);
  /* loop points and aux comments */
}

static sound_file *read_sound_file_header_with_fd(int fd, char *arg)
{
  sound_file *sf = NULL;
  initialize_sndlib();
  c_read_header_with_fd(fd);
  sf = add_to_sound_table();
  sf->file_name = copy_string(arg);
  fill_sf_record(sf);
  return(sf);
}

static sound_file *read_sound_file_header_with_name(char *name)
{
  sound_file *sf = NULL;
  initialize_sndlib();
  if (c_read_header(name) != -1)
    {
      sf = add_to_sound_table();
      sf->file_name = copy_string(name);
      fill_sf_record(sf);
    }
  return(sf);
}

static sound_file *getsf(char *arg) 
{
  sound_file *sf = NULL;
  if ((sf = find_sound_file(arg)) == NULL)
    {
      sf = read_sound_file_header_with_name(arg);
      if (sf == NULL) set_audio_error(CANT_OPEN);
    }
  return(sf);
}

int sound_samples (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->data_size); else return(-1);}
int sound_datum_size (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->datum_size); else return(-1);}
int sound_data_location (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->data_location); else return(-1);}
int sound_chans (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->chans); else return(-1);}
int sound_srate (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->srate); else return(-1);}
int sound_header_type (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->header_type); else return(-1);}
int sound_data_format (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->data_format); else return(-1);}
int sound_original_format (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->original_sound_format); else return(-1);}
int sound_comment_start (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->comment_start); else return(-1);}
int sound_comment_end (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->comment_end); else return(-1);}
int sound_length (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->true_file_length); else return(-1);}
int sound_fact_samples (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->fact_samples); else return(-1);}
int sound_distributed (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->header_distributed); else return(-1);}
int sound_write_date (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->write_date); else return(-1);}
int sound_type_specifier (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->type_specifier); else return(-1);}
int sound_align (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->block_align); else return(-1);}
int sound_bits_per_sample (char *arg) {sound_file *sf; sf = getsf(arg); if (sf) return(sf->bits_per_sample); else return(-1);}

char *sound_comment(char *name)
{
  int start,end,fd,len;
  char *sc = NULL;
  start = sound_comment_start(name);
  end = sound_comment_end(name);
  len = end-start;
  if (len>0)
    {
      /* open and get the comment */
      sc = (char *)CALLOC(len+1,sizeof(char));
#if MACOS
      fd = open(name,O_RDONLY);
#else
  #ifdef WINDOZE
      fd = open(name,O_RDONLY | O_BINARY);
  #else
      fd = open(name,O_RDONLY,0);
  #endif
#endif
      lseek(fd,start,0);
      read(fd,sc,len+1);
      close(fd);
      return(sc);
    }
  else return(NULL);
}

char *sound_type_name(int type)
{
  switch (type)
    {
    case NeXT_sound_file: return("Sun"); break;
    case AIFF_sound_file: return("AIFF"); break;
    case RIFF_sound_file: return("RIFF"); break;
    case BICSF_sound_file: return("BICSF"); break;
    case NIST_sound_file: return("NIST"); break;
    case INRS_sound_file: return("INRS"); break;
    case ESPS_sound_file: return("ESPS"); break;
    case SVX_sound_file: return("SVX8"); break;
    case VOC_sound_file: return("VOC"); break;
    case SNDT_sound_file: return("SNDT"); break;
    case raw_sound_file: return("raw (no header)"); break;
    case SMP_sound_file: return("SMP"); break;
    case SD2_sound_file: return("Sound Designer 2"); break;
    case AVR_sound_file: return("AVR"); break;
    case IRCAM_sound_file: return("IRCAM"); break;
    case SD1_sound_file: return("Sound Designer 1"); break;
    case SPPACK_sound_file: return("SPPACK"); break;
    case MUS10_sound_file: return("Mus10"); break;
    case HCOM_sound_file: return("HCOM"); break;
    case PSION_sound_file: return("PSION"); break;
    case MAUD_sound_file: return("MAUD"); break;
    case IEEE_sound_file: return("IEEE text"); break;
    case DeskMate_sound_file: return("DeskMate"); break;
    case DeskMate_2500_sound_file: return("DeskMate_2500"); break;
    case Matlab_sound_file: return("Matlab"); break;
    case ADC_sound_file: return("ADC/OGI"); break;
    case SoundEdit_sound_file: return("SoundEdit"); break;
    case SoundEdit_16_sound_file: return("SoundEdit 16"); break;
    case DVSM_sound_file: return("DVSM"); break;
    case MIDI_file: return("MIDI"); break;
    case Esignal_file: return("Esignal"); break;
    case soundfont_sound_file: return("SoundFont"); break;
    case gravis_sound_file: return("Gravis Ultrasound patch"); break;
    case comdisco_sound_file: return("Comdisco SPW signal"); break;
    case goldwave_sound_file: return("Goldwave sample"); break;
    case srfs_sound_file: return("SRFS"); break;
    case MIDI_sample_dump: return("MIDI sample dump"); break;
    case DiamondWare_sound_file: return("DiamondWare"); break;
    case RealAudio_sound_file: return("RealAudio"); break;
    case ADF_sound_file: return("CSRE adf"); break;
    case SBStudioII_sound_file: return("SBStudioII"); break;
    case Delusion_sound_file: return("Delusion"); break;
    case Farandole_sound_file: return("Farandole"); break;
    case Sample_dump_sound_file: return("Sample dump"); break;
    case Ultratracker_sound_file: return("Ultratracker"); break;
    case Yamaha_TX16_sound_file: return("TX-16"); break;
    case Yamaha_SY85_sound_file: return("Sy-85"); break;
    case digiplayer_sound_file: return("Digiplayer ST3"); break;
    case Covox_sound_file: return("Covox V8"); break;
    case SPL_sound_file: return("Digitracker SPL"); break;
    case AVI_sound_file: return("AVI"); break;
    case OMF_sound_file: return("OMF"); break;
    case Quicktime_sound_file: return("Quicktime"); break;
    case asf_sound_file: return("asf"); break;
    default: return("Unknown"); break;
    }
}

char *sound_format_name(int format)
{
  switch (format)
    {
    case snd_unsupported: return("unsupported"); break;
    case snd_no_snd: return("no_snd"); break;
    case snd_16_linear: return("16-bit big endian"); break;
    case snd_8_mulaw: return("mulaw"); break;
    case snd_8_linear: return("8-bit"); break;
    case snd_32_float: return("32-bit big endian float"); break;
    case snd_32_linear: return("32-bit big endian"); break;
    case snd_8_alaw: return("alaw"); break;
    case snd_8_unsigned: return("8-bit unsigned"); break;
    case snd_24_linear: return("24-bit big endian"); break;
    case snd_64_double: return("64-bit big endian double"); break;
    case snd_16_linear_little_endian: return("16-bit little endian"); break;
    case snd_32_linear_little_endian: return("32-bit little endian"); break;
    case snd_32_float_little_endian: return("32-bit little endian float"); break;
    case snd_64_double_little_endian: return("64-bit little endian double"); break;
    case snd_16_unsigned: return("16-bit big endian unsigned"); break;
    case snd_16_unsigned_little_endian: return("16-bit little endian unsigned"); break;
    case snd_12_unsigned: return("12-bit big endian unsigned"); break;
    case snd_12_unsigned_little_endian: return("12-bit little endian unsigned"); break;
    case snd_12_linear: return("12-bit big endian"); break;
    case snd_12_linear_little_endian: return("12-bit little endian"); break;
    case snd_24_linear_little_endian: return("24-bit little endian"); break;
    case snd_32_vax_float: return("vax float"); break;
    default: return("unknown"); break;
    }
}

int bytes_per_sample(int format) {return(c_snd_datum_size(format));}

int open_sound_input (char *arg) 
{
  int fd;
  sound_file *sf = NULL;
  set_audio_error(NO_ERROR);
  initialize_sndlib();
  fd = clm_open_read(arg);
  if (fd != -1)
    {
      if ((sf = find_sound_file(arg)) == NULL)
	{
	  sf = read_sound_file_header_with_fd(fd,arg);
	}
    }
  if (sf) 
    {
      open_clm_file_descriptors(fd,sf->data_format,sf->datum_size,sf->data_location);
      clm_seek(fd,sf->data_location,0);
    }
  else set_audio_error(CANT_OPEN); 
  return(fd);
}

typedef struct {
  int fd;
  int header_type;
} output_info;

static output_info **header_types = NULL;
static int header_types_size = 0;

static void save_header_type(int fd, int header_type)
{
  int i,loc;
#ifdef MACOS
  output_info **ptr;
#endif
  loc = -1;
  for (i=0;i<header_types_size;i++)
    {
      if (header_types[i] == NULL)
	{
	  loc = i;
	  break;
	}
    }
  if (loc == -1)
    {
      loc = header_types_size;
      header_types_size += 4;
      if (header_types == NULL)
	header_types = (output_info **)CALLOC(header_types_size,sizeof(output_info *));
      else 
	{
#ifdef MACOS
	  ptr = (output_info **)CALLOC(header_types_size,sizeof(output_info *));
	  for (i=0;i<loc;i++) ptr[i] = header_types[i];
	  FREE(header_types);
	  header_types = ptr;
#else
	  header_types = (output_info **)REALLOC(header_types,header_types_size * sizeof(output_info *));
#endif
	  for (i=loc;i<header_types_size;i++) header_types[i] = NULL;
	}
    }
  header_types[loc] = (output_info *)CALLOC(1,sizeof(output_info));
  header_types[loc]->fd = fd;
  header_types[loc]->header_type = header_type;
}

static int get_and_flush_header_type(int fd)
{
  int i,loc,val;
  loc = -1;
  val = -1;
  for (i=0;i<header_types_size;i++)
    {
      if (header_types[i])
	{
	  if (header_types[i]->fd == fd)
	    {
	      loc = i;
	      break;
	    }
	}
    }
  if (loc != -1)
    {
      val = header_types[loc]->header_type;
      FREE(header_types[loc]);
      header_types[loc] = NULL;
    }
  return(val);
}

int open_sound_output (char *arg, int srate, int chans, int data_format, int header_type, char *comment)
{
  int fd,err,comlen = 0;
  if (comment) comlen = strlen(comment);
  set_audio_error(NO_ERROR);
  initialize_sndlib();
  err = c_write_header(arg,header_type,srate,chans,0,0,data_format,comment,comlen);
  if (err != -1)
    {
      fd = clm_open_write(arg);
      open_clm_file_descriptors(fd,data_format,c_snd_datum_size(data_format),c_snd_header_data_location());
      save_header_type(fd,header_type);
    }
  else set_audio_error(CANT_OPEN); 
  return(fd);
}

int close_sound_input (int fd) 
{
  clm_close(fd); /* this closes the clm file descriptors */
  return(0);
}

int close_sound_output (int fd, int bytes_of_data) 
{
  c_update_header_with_fd(fd,get_and_flush_header_type(fd),bytes_of_data);
  clm_close(fd);
  return(0);
}

int read_sound (int fd, int beg, int end, int chans, int **bufs) 
{
  clm_read(fd,beg,end,chans,bufs);
  return(0);
}

int write_sound (int tfd, int beg, int end, int chans, int **bufs) 
{
  clm_write(tfd,beg,end,chans,bufs);
  return(0);
}

int seek_sound (int tfd, long offset, int origin) 
{
  return(clm_seek(tfd,offset,origin));
}

int override_sound_header(char *arg, int srate, int chans, int format, int type, int location, int size)
{
  sound_file *sf; 
  sf = getsf(arg); 
  if (sf)
    {
      sf->data_location = location;
      sf->data_size = size;
      sf->data_format = format;
      sf->srate = srate;
      sf->chans = chans;
      sf->datum_size = c_snd_datum_size(format);
      sf->header_type = type;
      return(0);
    }
  else return(-1);
}
