/* libSoX libsndfile formats.
 *
 * Copyright 2007 Reuben Thomas <rrt@sc3d.org>
 * Copyright 1999-2005 Erik de Castro Lopo <eridk@mega-nerd.com>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library 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 Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "sox_i.h"

#ifdef HAVE_SNDFILE

#include <ctype.h>
#include <sndfile.h>

#define LOG_MAX 2048 /* As per the SFC_GET_LOG_INFO example */

#if !defined(HAVE_LIBLTDL)
#undef DL_SNDFILE
#endif

static const char* const sndfile_library_names[] =
{
#ifdef DL_SNDFILE
  "libsndfile",
  "libsndfile-1",
  "cygsndfile-1",
#endif
  NULL
};

#ifdef DL_SNDFILE
  #define SNDFILE_FUNC      LSX_DLENTRY_DYNAMIC
  #define SNDFILE_FUNC_STOP LSX_DLENTRY_STUB
#else
  #define SNDFILE_FUNC      LSX_DLENTRY_STATIC
  #define SNDFILE_FUNC_STOP LSX_DLENTRY_STUB
#endif /* DL_SNDFILE */

#define SNDFILE_FUNC_ENTRIES(f,x) \
  SNDFILE_FUNC(f,x, SNDFILE*, sf_open, (const char *path, int mode, SF_INFO *sfinfo)) \
  SNDFILE_FUNC(f,x, SNDFILE*, sf_open_virtual, (SF_VIRTUAL_IO *sfvirtual, int mode, SF_INFO *sfinfo, void *user_data)) \
  SNDFILE_FUNC_STOP(f,x, int, sf_stop, (SNDFILE *sndfile)) \
  SNDFILE_FUNC(f,x, int, sf_close, (SNDFILE *sndfile)) \
  SNDFILE_FUNC(f,x, int, sf_format_check, (const SF_INFO *info)) \
  SNDFILE_FUNC(f,x, int, sf_command, (SNDFILE *sndfile, int command, void *data, int datasize)) \
  SNDFILE_FUNC(f,x, sf_count_t, sf_read_int, (SNDFILE *sndfile, int *ptr, sf_count_t items)) \
  SNDFILE_FUNC(f,x, sf_count_t, sf_write_int, (SNDFILE *sndfile, const int *ptr, sf_count_t items)) \
  SNDFILE_FUNC(f,x, sf_count_t, sf_seek, (SNDFILE *sndfile, sf_count_t frames, int whence)) \
  SNDFILE_FUNC(f,x, const char*, sf_strerror, (SNDFILE *sndfile))

/* Private data for sndfile files */
typedef struct {
  SNDFILE *sf_file;
  SF_INFO *sf_info;
  char * log_buffer;
  char const * log_buffer_ptr;
  LSX_DLENTRIES_TO_PTRS(SNDFILE_FUNC_ENTRIES, sndfile_dl);
} priv_t;

/*
 * Drain LSF's wonderful log buffer
 */
static void drain_log_buffer(sox_format_t * ft)
{
  priv_t * sf = (priv_t *)ft->priv;
  sf->sf_command(sf->sf_file, SFC_GET_LOG_INFO, sf->log_buffer, LOG_MAX);
  while (*sf->log_buffer_ptr) {
    static char const warning_prefix[] = "*** Warning : ";
    char const * end = strchr(sf->log_buffer_ptr, '\n');
    if (!end)
      end = strchr(sf->log_buffer_ptr, '\0');
    if (!strncmp(sf->log_buffer_ptr, warning_prefix, strlen(warning_prefix))) {
      sf->log_buffer_ptr += strlen(warning_prefix);
      lsx_warn("`%s': %.*s",
          ft->filename, (int)(end - sf->log_buffer_ptr), sf->log_buffer_ptr);
    } else
      lsx_debug("`%s': %.*s",
          ft->filename, (int)(end - sf->log_buffer_ptr), sf->log_buffer_ptr);
    sf->log_buffer_ptr = end;
    if (*sf->log_buffer_ptr == '\n')
      ++sf->log_buffer_ptr;
  }
}

/* Make libsndfile subtype from sample encoding and size */
static int ft_enc(unsigned size, sox_encoding_t e)
{
  if (e == SOX_ENCODING_ULAW      && size ==  8) return SF_FORMAT_ULAW;
  if (e == SOX_ENCODING_ALAW      && size ==  8) return SF_FORMAT_ALAW;
  if (e == SOX_ENCODING_SIGN2     && size ==  8) return SF_FORMAT_PCM_S8;
  if (e == SOX_ENCODING_SIGN2     && size == 16) return SF_FORMAT_PCM_16;
  if (e == SOX_ENCODING_SIGN2     && size == 24) return SF_FORMAT_PCM_24;
  if (e == SOX_ENCODING_SIGN2     && size == 32) return SF_FORMAT_PCM_32;
  if (e == SOX_ENCODING_UNSIGNED  && size ==  8) return SF_FORMAT_PCM_U8;
  if (e == SOX_ENCODING_FLOAT     && size == 32) return SF_FORMAT_FLOAT;
  if (e == SOX_ENCODING_FLOAT     && size == 64) return SF_FORMAT_DOUBLE;
  if (e == SOX_ENCODING_G721      && size ==  4) return SF_FORMAT_G721_32;
  if (e == SOX_ENCODING_G723      && size ==  3) return SF_FORMAT_G723_24;
  if (e == SOX_ENCODING_G723      && size ==  5) return SF_FORMAT_G723_40;
  if (e == SOX_ENCODING_MS_ADPCM  && size ==  4) return SF_FORMAT_MS_ADPCM;
  if (e == SOX_ENCODING_IMA_ADPCM && size ==  4) return SF_FORMAT_IMA_ADPCM;
  if (e == SOX_ENCODING_OKI_ADPCM && size ==  4) return SF_FORMAT_VOX_ADPCM;
  if (e == SOX_ENCODING_DPCM      && size ==  8) return SF_FORMAT_DPCM_8;
  if (e == SOX_ENCODING_DPCM      && size == 16) return SF_FORMAT_DPCM_16;
  if (e == SOX_ENCODING_DWVW      && size == 12) return SF_FORMAT_DWVW_12;
  if (e == SOX_ENCODING_DWVW      && size == 16) return SF_FORMAT_DWVW_16;
  if (e == SOX_ENCODING_DWVW      && size == 24) return SF_FORMAT_DWVW_24;
  if (e == SOX_ENCODING_DWVWN     && size ==  0) return SF_FORMAT_DWVW_N;
  if (e == SOX_ENCODING_GSM       && size ==  0) return SF_FORMAT_GSM610;
  if (e == SOX_ENCODING_FLAC      && size ==  8) return SF_FORMAT_PCM_S8;
  if (e == SOX_ENCODING_FLAC      && size == 16) return SF_FORMAT_PCM_16;
  if (e == SOX_ENCODING_FLAC      && size == 24) return SF_FORMAT_PCM_24;
  if (e == SOX_ENCODING_FLAC      && size == 32) return SF_FORMAT_PCM_32;
#ifdef HAVE_SF_FORMAT_OGG
  if (e == SOX_ENCODING_VORBIS)   return SF_FORMAT_VORBIS;
#endif
#ifdef HAVE_SF_FORMAT_MPEG
  if (e == SOX_ENCODING_MP2)      return SF_FORMAT_MPEG_LAYER_II;
  if (e == SOX_ENCODING_MP3)      return SF_FORMAT_MPEG_LAYER_III;
#endif
  return 0; /* Bad encoding */
}

/* Convert format's encoding type to libSoX encoding type & size. */
static sox_encoding_t sox_enc(int ft_encoding, unsigned * size)
{
  int sub = ft_encoding & SF_FORMAT_SUBMASK;
  int type = ft_encoding & SF_FORMAT_TYPEMASK;

  if (type == SF_FORMAT_FLAC) switch (sub) {
    case SF_FORMAT_PCM_S8   : *size =  8; return SOX_ENCODING_FLAC;
    case SF_FORMAT_PCM_16   : *size = 16; return SOX_ENCODING_FLAC;
    case SF_FORMAT_PCM_24   : *size = 24; return SOX_ENCODING_FLAC;
  }
  switch (sub) {
    case SF_FORMAT_ULAW     : *size =  8; return SOX_ENCODING_ULAW;
    case SF_FORMAT_ALAW     : *size =  8; return SOX_ENCODING_ALAW;
    case SF_FORMAT_PCM_S8   : *size =  8; return SOX_ENCODING_SIGN2;
    case SF_FORMAT_PCM_16   : *size = 16; return SOX_ENCODING_SIGN2;
    case SF_FORMAT_PCM_24   : *size = 24; return SOX_ENCODING_SIGN2;
    case SF_FORMAT_PCM_32   : *size = 32; return SOX_ENCODING_SIGN2;
    case SF_FORMAT_PCM_U8   : *size =  8; return SOX_ENCODING_UNSIGNED;
    case SF_FORMAT_FLOAT    : *size = 32; return SOX_ENCODING_FLOAT;
    case SF_FORMAT_DOUBLE   : *size = 64; return SOX_ENCODING_FLOAT;
    case SF_FORMAT_G721_32  : *size =  4; return SOX_ENCODING_G721;
    case SF_FORMAT_G723_24  : *size =  3; return SOX_ENCODING_G723;
    case SF_FORMAT_G723_40  : *size =  5; return SOX_ENCODING_G723;
    case SF_FORMAT_MS_ADPCM : *size =  4; return SOX_ENCODING_MS_ADPCM;
    case SF_FORMAT_IMA_ADPCM: *size =  4; return SOX_ENCODING_IMA_ADPCM;
    case SF_FORMAT_VOX_ADPCM: *size =  4; return SOX_ENCODING_OKI_ADPCM;
    case SF_FORMAT_DPCM_8   : *size =  8; return SOX_ENCODING_DPCM;
    case SF_FORMAT_DPCM_16  : *size = 16; return SOX_ENCODING_DPCM;
    case SF_FORMAT_DWVW_12  : *size = 12; return SOX_ENCODING_DWVW;
    case SF_FORMAT_DWVW_16  : *size = 16; return SOX_ENCODING_DWVW;
    case SF_FORMAT_DWVW_24  : *size = 24; return SOX_ENCODING_DWVW;
    case SF_FORMAT_DWVW_N   : *size =  0; return SOX_ENCODING_DWVWN;
    case SF_FORMAT_GSM610   : *size =  0; return SOX_ENCODING_GSM;
#if HAVE_SF_FORMAT_OGG
    case SF_FORMAT_VORBIS   : *size =  0; return SOX_ENCODING_VORBIS;
#endif
#if HAVE_SF_FORMAT_MPEG
    /* By default, SoX declares MP3 precision as 16-bit so that
     * decoding them gives CD quality, which is what people expect. */
    case SF_FORMAT_MPEG_LAYER_I
                            : *size = 16; return SOX_ENCODING_MP1;
    case SF_FORMAT_MPEG_LAYER_II
                            : *size = 16; return SOX_ENCODING_MP2;
    case SF_FORMAT_MPEG_LAYER_III
                            : *size = 16; return SOX_ENCODING_MP3;
#endif
    default                 : *size =  0; return SOX_ENCODING_UNKNOWN;
  }
}

static struct {
  const char *ext;
  int format;
} format_map[] =
{
  { "aif",      SF_FORMAT_AIFF },
  { "aiff",     SF_FORMAT_AIFF },
  { "wav",      SF_FORMAT_WAV },
  { "au",       SF_FORMAT_AU },
  { "snd",      SF_FORMAT_AU },
  { "caf",      SF_FORMAT_CAF },
  { "flac",     SF_FORMAT_FLAC },
  { "wve",      SF_FORMAT_WVE },  /* Probably broken before 1.0.18 */
#ifdef HAVE_SF_FORMAT_OGG
  { "ogg",      SF_FORMAT_OGG | SF_FORMAT_VORBIS },  /* From 1.0.16 */
#endif
#ifdef HAVE_SF_FORMAT_MPC2K
  { "mpc2k",    SF_FORMAT_MPC2K },  /* From 1.0.25 */
#endif
#ifdef HAVE_SF_FORMAT_MPEG
  { "mp2",      SF_FORMAT_MPEG | SF_FORMAT_MPEG_LAYER_II },  /* From 1.1.0 */
  { "mp3",      SF_FORMAT_MPEG | SF_FORMAT_MPEG_LAYER_III },  /* From 1.1.0 */
#endif
  { "svx",      SF_FORMAT_SVX },
  { "8svx",     SF_FORMAT_SVX },
  { "paf",      SF_ENDIAN_BIG | SF_FORMAT_PAF },
  { "fap",      SF_ENDIAN_LITTLE | SF_FORMAT_PAF },
  { "gsm",      SF_FORMAT_RAW | SF_FORMAT_GSM610 },
  { "nist",     SF_FORMAT_NIST },
  { "sph",      SF_FORMAT_NIST },
  { "ircam",    SF_FORMAT_IRCAM },
  { "sf",       SF_FORMAT_IRCAM },
  { "voc",      SF_FORMAT_VOC },
  { "w64",      SF_FORMAT_W64 },
  { "raw",      SF_FORMAT_RAW },
  { "mat4",     SF_FORMAT_MAT4 },
  { "mat5",     SF_FORMAT_MAT5 },
  { "mat",      SF_FORMAT_MAT4 },
  { "pvf",      SF_FORMAT_PVF },
  { "sds",      SF_FORMAT_SDS },
  { "sd2",      SF_FORMAT_SD2 },
  { "vox",      SF_FORMAT_RAW | SF_FORMAT_VOX_ADPCM },
  { "xi",       SF_FORMAT_XI }
};

static int sf_stop_stub(SNDFILE *sndfile UNUSED)
{
    return 1;
}

static sf_count_t vio_get_filelen(void *user_data)
{
  sox_format_t *ft = (sox_format_t *)user_data;

  /* lsf excepts unbuffered I/O behavior for get_filelen() so force that */
  lsx_flush(ft);

  return (sf_count_t)lsx_filelength((sox_format_t *)user_data);
}

static sf_count_t vio_seek(sf_count_t offset, int whence, void *user_data)
{
    if (lsx_seeki((sox_format_t *)user_data, (off_t)offset, whence))
       return SOX_EINVAL;
    return lsx_tell((sox_format_t *)user_data);
}

static sf_count_t vio_read(void *ptr, sf_count_t count, void *user_data)
{
    return lsx_readbuf((sox_format_t *)user_data, ptr, (size_t)count);
}

static sf_count_t vio_write(const void *ptr, sf_count_t count, void *user_data)
{
    return lsx_writebuf((sox_format_t *)user_data, ptr, (size_t)count);
}

static sf_count_t vio_tell(void *user_data)
{
    return lsx_tell((sox_format_t *)user_data);
}

static SF_VIRTUAL_IO vio =
{
    vio_get_filelen,
    vio_seek,
    vio_read,
    vio_write,
    vio_tell
};

/* Convert file name or type to libsndfile format */
static int name_to_format(const char *name)
{
  int k;
#define FILE_TYPE_BUFLEN (size_t)15
  char buffer[FILE_TYPE_BUFLEN + 1], *cptr;

  if ((cptr = strrchr(name, '.')) != NULL) {
    strncpy(buffer, cptr + 1, FILE_TYPE_BUFLEN);
    buffer[FILE_TYPE_BUFLEN] = '\0';

    for (k = 0; buffer[k]; k++)
      buffer[k] = tolower((int)(buffer[k]));
  } else {
    strncpy(buffer, name, FILE_TYPE_BUFLEN);
    buffer[FILE_TYPE_BUFLEN] = '\0';
  }

  for (k = 0; k < (int)(sizeof(format_map) / sizeof(format_map [0])); k++) {
    if (strcmp(buffer, format_map[k].ext) == 0)
      return format_map[k].format;
  }

  return 0;
}

static int start_sndfile(sox_format_t * ft)
{
  priv_t * sf = (priv_t *)ft->priv;
  int subtype = ft_enc(ft->encoding.bits_per_sample? ft->encoding.bits_per_sample : ft->signal.precision, ft->encoding.encoding);
  int open_library_result;

  LSX_DLLIBRARY_OPEN(
      sf,
      sndfile_dl,
      SNDFILE_FUNC_ENTRIES,
      "libsndfile library",
      sndfile_library_names,
      open_library_result);
  if (open_library_result)
    return SOX_EOF;

  sf->log_buffer_ptr = sf->log_buffer = lsx_malloc((size_t)LOG_MAX);
  sf->sf_info = lsx_calloc(1, sizeof(SF_INFO));

  /* Copy format info */
  if (subtype) {
    if (strcmp(ft->filetype, "sndfile") == 0)
      sf->sf_info->format = name_to_format(ft->filename);
    else
      sf->sf_info->format = name_to_format(ft->filetype);

    /* For linear types, name_to_format only returns the major format type
     * and we need to OR in the bit depth */
    switch (sf->sf_info->format) {
    case SF_FORMAT_OGG | SF_FORMAT_VORBIS:
#if HAVE_SF_FORMAT_MPEG
    case SF_FORMAT_MPEG | SF_FORMAT_MPEG_LAYER_I:
    case SF_FORMAT_MPEG | SF_FORMAT_MPEG_LAYER_II:
    case SF_FORMAT_MPEG | SF_FORMAT_MPEG_LAYER_III:
#endif
      break;
    default:
      sf->sf_info->format |= subtype;
      break;
    }
  }
  sf->sf_info->samplerate = (int)ft->signal.rate;
  sf->sf_info->channels = ft->signal.channels;
  if (ft->signal.channels)
    sf->sf_info->frames = ft->signal.length / ft->signal.channels;

  return SOX_SUCCESS;
}

/*
 * Close file for libsndfile (this doesn't close the file handle)
 */
static int stop_sndfile(sox_format_t * ft)
{
  priv_t * sf = (priv_t *)ft->priv;
  sf->sf_stop(sf->sf_file);
  drain_log_buffer(ft);
  sf->sf_close(sf->sf_file);
  free((void *)sf->log_buffer);
  free((void *)sf->sf_info);
  LSX_DLLIBRARY_CLOSE(sf, sndfile_dl);
  return SOX_SUCCESS;
}

static int check_read_params(sox_format_t * ft, unsigned channels,
    sox_rate_t rate, sox_encoding_t encoding, unsigned bits_per_sample, uint64_t length)
{
  ft->signal.length = length;

  if (channels && ft->signal.channels && ft->signal.channels != channels)
    lsx_warn("`%s': overriding number of channels", ft->filename);
  else ft->signal.channels = channels;

  if (rate && ft->signal.rate && ft->signal.rate != rate)
    lsx_warn("`%s': overriding sample rate", ft->filename);
  else ft->signal.rate = rate;

  if (encoding && ft->encoding.encoding && ft->encoding.encoding != encoding)
    lsx_warn("`%s': overriding encoding type", ft->filename);
  else ft->encoding.encoding = encoding;

  if (bits_per_sample && ft->encoding.bits_per_sample && ft->encoding.bits_per_sample != bits_per_sample)
    lsx_warn("`%s': overriding encoding size", ft->filename);
  ft->encoding.bits_per_sample = bits_per_sample;

  if (sox_precision(ft->encoding.encoding, ft->encoding.bits_per_sample))
    return SOX_SUCCESS;
  lsx_fail_errno(ft, EINVAL, "invalid format for this file type");
  return SOX_EOF;
}

/*
 * Open file in sndfile.
 */
static int startread_sndfile(sox_format_t * ft)
{
  priv_t * sf = (priv_t *)ft->priv;
  unsigned bits_per_sample;
  sox_encoding_t encoding;
  sox_rate_t rate;
  const char *extension = lsx_find_file_extension(ft->filename);

  if (start_sndfile(ft) == SOX_EOF)
      return SOX_EOF;

  /*
   * The SD2 format relies on a resource fork,
   * which cannot be supported with virtual IO.
   */
  if (extension && strcasecmp(extension, "sd2") == 0) {
    memset(sf->sf_info, 0, sizeof (*sf->sf_info));
    sf->sf_file = sf->sf_open(ft->filename, SFM_READ, sf->sf_info);
    drain_log_buffer(ft);
  } else {
    sf->sf_file = sf->sf_open_virtual(&vio, SFM_READ, sf->sf_info, ft);
    drain_log_buffer(ft);
  }

  if (sf->sf_file == NULL) {
    memset(ft->sox_errstr, 0, sizeof(ft->sox_errstr));
    strncpy(ft->sox_errstr, sf->sf_strerror(sf->sf_file), sizeof(ft->sox_errstr)-1);
    free(sf->sf_file);
    return SOX_EOF;
  }

  if (!(encoding = sox_enc(sf->sf_info->format, &bits_per_sample))) {
    lsx_fail_errno(ft, SOX_EFMT, "unsupported encoding %#x", sf->sf_info->format);
    return SOX_EOF;
  }

  /* Don't believe LSF's rate for raw files */
  if ((sf->sf_info->format & SF_FORMAT_TYPEMASK) == SF_FORMAT_RAW && !ft->signal.rate) {
    lsx_warn("`%s': sample rate not specified; trying 8kHz", ft->filename);
    rate = 8000;
  }
  else rate = sf->sf_info->samplerate;

  if ((sf->sf_info->format & SF_FORMAT_SUBMASK) == SF_FORMAT_FLOAT) {
    sf->sf_command(sf->sf_file, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE);
    sf->sf_command(sf->sf_file, SFC_SET_CLIPPING, NULL, SF_TRUE);
  }

#if 0 /* FIXME */
    sox_append_comments(&ft->oob.comments, buf);
#endif

  return check_read_params(ft, (unsigned)sf->sf_info->channels, rate,
      encoding, bits_per_sample, (uint64_t)(sf->sf_info->frames * sf->sf_info->channels));
}

/*
 * Read up to len samples of type sox_sample_t from file into buf[].
 * Return number of samples read.
 */
static size_t read_samples_sndfile(sox_format_t * ft, sox_sample_t *buf, size_t len)
{
  priv_t * sf = (priv_t *)ft->priv;
  /* FIXME: We assume int == sox_sample_t here */
  return (size_t)sf->sf_read_int(sf->sf_file, (int *)buf, (sf_count_t)len);
}

static int startwrite_sndfile(sox_format_t * ft)
{
  priv_t * sf = (priv_t *)ft->priv;

  if (start_sndfile(ft) == SOX_EOF)
      return SOX_EOF;

  /* If output format is invalid, try to find a sensible default */
  if (!sf->sf_format_check(sf->sf_info)) {
    SF_FORMAT_INFO format_info;
    int i, count;

    sf->sf_command(sf->sf_file, SFC_GET_SIMPLE_FORMAT_COUNT, &count, (int) sizeof(int));
    for (i = 0; i < count; i++) {
      format_info.format = i;
      sf->sf_command(sf->sf_file, SFC_GET_SIMPLE_FORMAT, &format_info, (int) sizeof(format_info));
      if ((format_info.format & SF_FORMAT_TYPEMASK) == (sf->sf_info->format & SF_FORMAT_TYPEMASK)) {
        sf->sf_info->format = format_info.format;
        /* FIXME: Print out exactly what we chose, needs sndfile ->
           sox encoding conversion functions */
        break;
      }
    }

    if (!sf->sf_format_check(sf->sf_info)) {
      lsx_fail("cannot find a usable output encoding");
      return SOX_EOF;
    }
    if ((sf->sf_info->format & SF_FORMAT_TYPEMASK) != SF_FORMAT_RAW 
#if HAVE_SF_FORMAT_MPEG
        && (sf->sf_info->format & SF_FORMAT_TYPEMASK) != SF_FORMAT_MPEG
#endif
	)
      lsx_warn("cannot use desired output encoding, choosing default");
  }

  /*
   * The SD2 format relies on a resource fork,
   * which cannot be supported with virtual IO.
   */
  if ((sf->sf_info->format & SF_FORMAT_TYPEMASK) == SF_FORMAT_SD2) {
    sf->sf_file = sf->sf_open(ft->filename, SFM_WRITE, sf->sf_info);
  } else {
    sf->sf_file = sf->sf_open_virtual(&vio, SFM_WRITE, sf->sf_info, ft);
  }
  drain_log_buffer(ft);

  if (sf->sf_file == NULL) {
    memset(ft->sox_errstr, 0, sizeof(ft->sox_errstr));
    strncpy(ft->sox_errstr, sf->sf_strerror(sf->sf_file), sizeof(ft->sox_errstr)-1);
    free(sf->sf_file);
    return SOX_EOF;
  }

#ifdef HAVE_SFC_SET_SCALE_INT_FLOAT_WRITE
  if ((sf->sf_info->format & SF_FORMAT_SUBMASK) == SF_FORMAT_FLOAT)
    sf->sf_command(sf->sf_file, SFC_SET_SCALE_INT_FLOAT_WRITE, NULL, SF_TRUE);
#endif

  return SOX_SUCCESS;
}

/*
 * Write len samples of type sox_sample_t from buf[] to file.
 * Return number of samples written.
 */
static size_t write_samples_sndfile(sox_format_t * ft, const sox_sample_t *buf, size_t len)
{
  priv_t * sf = (priv_t *)ft->priv;
  /* FIXME: We assume int == sox_sample_t here */
  return (size_t)sf->sf_write_int(sf->sf_file, (int *)buf, (sf_count_t)len);
}

static int seek_sndfile(sox_format_t * ft, sox_uint64_t offset)
{
  priv_t * sf = (priv_t *)ft->priv;
  sf->sf_seek(sf->sf_file, (sf_count_t)(offset / ft->signal.channels), SEEK_CUR);
  return SOX_SUCCESS;
}

LSX_FORMAT_HANDLER(sndfile)
{
  static char const * const names[] = {
    /* Special type to force use of sndfile for
     * LSF implementation of formats built in to SoX:
     * aif, au, gsm, mp3, nist, raw, sf/ircam, snd, svx, voc, vox, wav, wve
     * and LSF wrappers of formats already wrapped in SoX: flac */
    "sndfile",
    /* formats that have their own handlers below */
    "caf",
    "fap",
    "mat4",
    "mat5",
    "mpc2k",
    "paf",
    "pvf",
    "sd2",
    "sds",
    "w64",
    "xi",
    /* Other extensions that it can code that have always-static built-in
     * handlers */
    "aif", "au",
#ifdef HAVE_SF_FORMAT_AVR
    "avr",
#endif
    "ircam",
    "nist",
    "raw", "sf", "snd", "svx", "voc", "vox", "wav", "wve",
    /* Other extensions that can be dynamically loaded, omitted here
     * otherwise "configure --with-dyn-default --with-sndfile=yes"
     * makes it the default handler instead of using the dynamic module.
    "gsm",
#ifdef HAVE_SF_FORMAT_FLAC
    "flac",
#endif
#ifdef HAVE_SF_FORMAT_MPEG
    "mp1", "mp2", "mp3",
#endif
#ifdef HAVE_SF_FORMAT_OGG
    "ogg",
#endif
     */
    NULL
  };

  static unsigned const write_encodings[] = {
    SOX_ENCODING_SIGN2, 16, 24, 32, 8, 0,
    SOX_ENCODING_UNSIGNED, 8, 0,
    SOX_ENCODING_FLOAT, 32, 64, 0,
    SOX_ENCODING_ALAW, 8, 0,
    SOX_ENCODING_ULAW, 8, 0,
    SOX_ENCODING_IMA_ADPCM, 4, 0,
    SOX_ENCODING_MS_ADPCM, 4, 0,
    SOX_ENCODING_OKI_ADPCM, 4, 0,
    SOX_ENCODING_GSM, 0,
    SOX_ENCODING_MP2, 0,
    SOX_ENCODING_MP3, 0,
    0};

  static sox_format_handler_t const format = {SOX_LIB_VERSION_CODE,
    "Pseudo format to use libsndfile", names, 0,
    startread_sndfile, read_samples_sndfile, stop_sndfile,
    startwrite_sndfile, write_samples_sndfile, stop_sndfile,
    seek_sndfile, write_encodings, NULL, sizeof(priv_t)
  };

  return &format;
}

LSX_FORMAT_HANDLER(caf)
{
  static char const * const names[] = {"caf", NULL};
  static unsigned const write_encodings[] = {
    SOX_ENCODING_SIGN2, 16, 24, 32, 8, 0,
    SOX_ENCODING_FLOAT, 32, 64, 0,
    SOX_ENCODING_ALAW, 8, 0,
    SOX_ENCODING_ULAW, 8, 0,
    0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description = "Apples's Core Audio Format";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

LSX_FORMAT_HANDLER(fap)
{
  static char const * const names[] = {"fap", NULL};
  static unsigned const write_encodings[] = {SOX_ENCODING_SIGN2, 24, 16, 8,0,0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description =
    "Ensoniq PARIS digital audio editing system (little endian)";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

LSX_FORMAT_HANDLER(mat4)
{
  static char const * const names[] = {"mat4", "mat", NULL};
  static unsigned const write_encodings[] = {
    SOX_ENCODING_SIGN2, 16, 32, 0,
    SOX_ENCODING_FLOAT, 32, 64, 0,
    0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description = "Gnu Octave 2.0 format";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

LSX_FORMAT_HANDLER(mat5)
{
  static char const * const names[] = {"mat5", NULL};
  static unsigned const write_encodings[] = {
    SOX_ENCODING_SIGN2, 16, 32, 0,
    SOX_ENCODING_FLOAT, 32, 64, 0,
    0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description = "Gnu Octave 2.1 format";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

#if HAVE_SF_FORMAT_MPC2K
LSX_FORMAT_HANDLER(mpc2k)
{
  static char const * const names[] = { "mpc2k", NULL };
  static unsigned const write_encodings[] = {SOX_ENCODING_SIGN2, 16, 0, 0};
  static sox_format_handler_t handler;

  handler = *lsx_sndfile_format_fn();
  handler.description = "Akai MPC-2000 format";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}
#endif

LSX_FORMAT_HANDLER(paf)
{
  static char const * const names[] = {"paf", NULL};
  static unsigned const write_encodings[] = {SOX_ENCODING_SIGN2, 24, 16, 8,0,0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description =
    "Ensoniq PARIS digital audio editing system (big endian)";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

LSX_FORMAT_HANDLER(pvf)
{
  static char const * const names[] = {"pvf", NULL};
  static unsigned const write_encodings[] = {SOX_ENCODING_SIGN2, 32, 16, 8,0,0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description = "Portable Voice Format";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

LSX_FORMAT_HANDLER(sd2)
{
  static char const * const names[] = {"sd2", NULL};
  static unsigned const write_encodings[] = {SOX_ENCODING_SIGN2, 24, 16, 8,0,0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description = "Sound Designer II";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

LSX_FORMAT_HANDLER(sds)
{
  static char const * const names[] = {"sds", NULL};
  /* SDS can encode from 8 to 28 bits */
  static unsigned const write_encodings[] = {SOX_ENCODING_SIGN2, 24, 16, 8,0,0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description = "MIDI Sample Dump Standard";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

LSX_FORMAT_HANDLER(w64)
{
  static char const * const names[] = {"w64", NULL};
  static unsigned const write_encodings[] = {
    SOX_ENCODING_SIGN2, 16, 24, 32, 0,
    SOX_ENCODING_UNSIGNED, 8, 0,
    SOX_ENCODING_FLOAT, 32, 64, 0,
    SOX_ENCODING_ALAW, 8, 0,
    SOX_ENCODING_ULAW, 8, 0,
    SOX_ENCODING_IMA_ADPCM, 4, 0,
    SOX_ENCODING_MS_ADPCM, 4, 0,
    SOX_ENCODING_GSM, 0,
    0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description = "Sound Forge Audio Format";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

LSX_FORMAT_HANDLER(xi)
{
  static char const * const names[] = {"xi", NULL};
  static unsigned const write_encodings[] = {SOX_ENCODING_DPCM, 16, 8, 0, 0};
  static sox_format_handler_t handler;
  handler = *lsx_sndfile_format_fn();
  handler.description = "Fasttracker 2";
  handler.names = names;
  handler.write_formats = write_encodings;
  return &handler;
}

#endif
