text: /* FluidSynth - A Software Synthesizer
   *
   * Copyright (C) 2003  Peter Hanappe and others.
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Library General Public License
   * as published by the Free Software Foundation; either version 2 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
   * Library General Public License for more details.
   *
   * You should have received a copy of the GNU Library 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
   */
  
   /* 
    * Low-level routines for file output.
    */
  
  #include <stdio.h>
  #include "fluidsynth_priv.h"
  #include "fluid_synth.h"
  #include "fluid_sys.h"
  #include "fluid_settings.h"
  #include "errno.h"
  
  #if LIBSNDFILE_SUPPORT
  #include <sndfile.h>
  #endif
  
  struct _fluid_file_renderer_t {
  	fluid_synth_t* synth;
  
  #if LIBSNDFILE_SUPPORT
  	SNDFILE* sndfile;
  	float* buf;
  #else
  	FILE* file;
  	short* buf;
  #endif
  
  	int period_size;
  	int buf_size;
  };
  
  #if LIBSNDFILE_SUPPORT
  
  /* Default file type used, if none specified and auto extension search fails */
  #define FLUID_FILE_RENDERER_DEFAULT_FILE_TYPE   SF_FORMAT_WAV
  
  /* File audio format names.
   * !! Keep in sync with format_ids[] */
  const char *format_names[] = {
    "s8",
    "s16",
    "s24",
    "s32",
    "u8",
    "float",
    "double",
    NULL          /* Terminator */
  };
    
  
  /* File audio format IDs.
   * !! Keep in sync with format_names[] */
  const int format_ids[] = {
    SF_FORMAT_PCM_S8,
    SF_FORMAT_PCM_16,
    SF_FORMAT_PCM_24,
    SF_FORMAT_PCM_32,
    SF_FORMAT_PCM_U8,
    SF_FORMAT_FLOAT,
    SF_FORMAT_DOUBLE
  };
  
  /* File endian byte order names.
   * !! Keep in sync with endian_ids[] */
  const char *endian_names[] = {
    "auto",
    "little",
    "big",
    "cpu",
    NULL
  };
  
  /* File endian byte order ids.
   * !! Keep in sync with endian_names[] */
  const int endian_ids[] = {
    SF_ENDIAN_FILE,
    SF_ENDIAN_LITTLE,
    SF_ENDIAN_BIG,
    SF_ENDIAN_CPU
  };
  
  static int fluid_file_renderer_parse_options (char *filetype, char *format,
                                                char *endian, char *filename, SF_INFO *info);
  static int fluid_file_renderer_find_file_type (char *extension, int *type);
  static int fluid_file_renderer_find_valid_format (SF_INFO *info);
  
  
  #else   /* No libsndfile support */
  
  
  /* File type names. */
  const char *type_names[] = {
    "raw",
    NULL          /* Terminator */
  };
  
  /* File audio format names. */
  const char *format_names[] = {
    "s16",
    NULL          /* Terminator */
  };
  
  /* File endian byte order names. */
  const char *endian_names[] = {
    "cpu",
    NULL
  };
  
  #endif
  
  
  void
  fluid_file_renderer_settings (fluid_settings_t* settings)
  {
  #if LIBSNDFILE_SUPPORT
    SF_FORMAT_INFO finfo, cmpinfo;
    int major_count;
    int i, i2;
    const char **np;
  
    fluid_settings_register_str(settings, "audio.file.name", "fluidsynth.wav",
                                FLUID_HINT_FILENAME, NULL, NULL);
    fluid_settings_register_str(settings, "audio.file.type", "auto", 0, NULL, NULL);
    fluid_settings_register_str(settings, "audio.file.format", "s16", 0, NULL, NULL);
    fluid_settings_register_str(settings, "audio.file.endian", "auto", 0, NULL, NULL);
  
    fluid_settings_add_option (settings, "audio.file.type", "auto");
  
    sf_command (NULL, SFC_GET_FORMAT_MAJOR_COUNT, &major_count, sizeof (int));
  
    for (i = 0; i < major_count; i++)
    {
      finfo.format = i;
      sf_command (NULL, SFC_GET_FORMAT_MAJOR, &finfo, sizeof (finfo));
  
      /* Check for duplicates */
      for (i2 = 0; i2 < i; i2++)
      {
        cmpinfo.format = i2;
        sf_command (NULL, SFC_GET_FORMAT_MAJOR, &cmpinfo, sizeof (cmpinfo));
  
        if (FLUID_STRCMP (cmpinfo.extension, finfo.extension) == 0)
          break;
      }
  
      if (i2 == i)
        fluid_settings_add_option (settings, "audio.file.type", finfo.extension);
    }
  
    for (np = format_names; *np; np++)
      fluid_settings_add_option (settings, "audio.file.format", *np);
  
    for (np = endian_names; *np; np++)
      fluid_settings_add_option (settings, "audio.file.endian", *np);
  
  #else
  
    fluid_settings_register_str(settings, "audio.file.name", "fluidsynth.raw", 0, NULL, NULL);
    fluid_settings_register_str(settings, "audio.file.type", "raw", 0, NULL, NULL);
    fluid_settings_add_option (settings, "audio.file.type", "raw");
    fluid_settings_register_str(settings, "audio.file.format", "s16", 0, NULL, NULL);
    fluid_settings_add_option (settings, "audio.file.format", "s16");
    fluid_settings_register_str(settings, "audio.file.endian", "cpu", 0, NULL, NULL);
    fluid_settings_add_option (settings, "audio.file.endian", "cpu");
  #endif
  }
  
  /**
   * Create a new file renderer and open the file.
   * @param synth The synth that creates audio data.
   * @return the new object, or NULL on failure
   * @since 1.1.0
   *
   * NOTE: Available file types and formats depends on if libfluidsynth was
   * built with libsndfile support or not.  If not then only RAW 16 bit output is
   * supported.
   *
   * Uses the following settings from the synth object:
   *   - audio.file.name: Output filename
   *   - audio.file.type: File type, "auto" tries to determine type from filename
   *     extension with fallback to "wav".
   *   - audio.file.format: Audio format
   *   - audio.file.endian: Endian byte order, "auto" for file type's default byte order
   *   - audio.period-size: Size of audio blocks to process
   *   - synth.sample-rate: Sample rate to use
   */
  fluid_file_renderer_t *
  new_fluid_file_renderer(fluid_synth_t* synth)
  {
  #if LIBSNDFILE_SUPPORT
  	char *type, *format, *endian;
  	SF_INFO info;
  	double samplerate;
  	int retval;
  #endif
          char *filename = NULL;
  	fluid_file_renderer_t* dev;
  
  	fluid_return_val_if_fail (synth != NULL, NULL);
  	fluid_return_val_if_fail (synth->settings != NULL, NULL);
  
  	dev = FLUID_NEW(fluid_file_renderer_t);
  	if (dev == NULL) {
  		FLUID_LOG(FLUID_ERR, "Out of memory");
  		return NULL;
  	}
  	FLUID_MEMSET(dev, 0, sizeof(fluid_file_renderer_t));
  
  	dev->synth = synth;
  	fluid_settings_getint (synth->settings, "audio.period-size", &dev->period_size);
  
  #if LIBSNDFILE_SUPPORT
  	dev->buf_size = 2 * dev->period_size * sizeof (float);
  	dev->buf = FLUID_ARRAY(float, 2 * dev->period_size);
  #else
  	dev->buf_size = 2 * dev->period_size * sizeof (short);
  	dev->buf = FLUID_ARRAY(short, 2 * dev->period_size);
  #endif
  
  	if (dev->buf == NULL) {
  		FLUID_LOG(FLUID_ERR, "Out of memory");
  		goto error_recovery;
  	}
  
  	fluid_settings_dupstr (synth->settings, "audio.file.name", &filename);
  	if (filename == NULL) {
  		FLUID_LOG(FLUID_ERR, "No file name specified");
  		goto error_recovery;
  	}
  
  #if LIBSNDFILE_SUPPORT
  	memset (&info, 0, sizeof (info));
  
          info.format = FLUID_FILE_RENDERER_DEFAULT_FILE_TYPE | SF_FORMAT_PCM_16;
  
  	fluid_settings_dupstr (synth->settings, "audio.file.type", &type);
  	fluid_settings_dupstr (synth->settings, "audio.file.format", &format);
  	fluid_settings_dupstr (synth->settings, "audio.file.endian", &endian);
  
  	retval = fluid_file_renderer_parse_options (type, format, endian, filename, &info);
  
  	if (type) FLUID_FREE (type);
  	if (format) FLUID_FREE (format);
  	if (endian) FLUID_FREE (endian);
  
  	if (!retval) goto error_recovery;
  
  	fluid_settings_getnum (synth->settings, "synth.sample-rate", &samplerate);
  	info.samplerate = samplerate + 0.5;
  	info.channels = 2;
  
  	/* Search for valid format for given file type, if invalid and no format was specified.
  	 * To handle Ogg/Vorbis and possibly future file types with new formats.
  	 * Checking if format is SF_FORMAT_PCM_16 isn't a fool proof way to check if
  	 * format was specified or not (if user specifies "s16" itself), but should suffice. */
  	if (!sf_format_check (&info)
              && ((info.format & SF_FORMAT_SUBMASK) != SF_FORMAT_PCM_16
                  || !fluid_file_renderer_find_valid_format (&info)))
  	{
  	  FLUID_LOG(FLUID_ERR, "Invalid or unsupported audio file format settings");
  	  goto error_recovery;
  	}
  
  	dev->sndfile = sf_open (filename, SFM_WRITE, &info);
  
  	if (!dev->sndfile)
  	{
  	  FLUID_LOG(FLUID_ERR, "Failed to open audio file '%s' for writing", filename);
            goto error_recovery;
  	}
  
  	/* Turn on clipping and normalization of floats (-1.0 - 1.0) */
  	sf_command (dev->sndfile, SFC_SET_CLIPPING, NULL, SF_TRUE);
  	sf_command (dev->sndfile, SFC_SET_NORM_FLOAT, NULL, SF_TRUE);
  #else
  	dev->file = fopen(filename, "wb");
  	if (dev->file == NULL) {
  		FLUID_LOG(FLUID_ERR, "Failed to open the file '%s'", filename);
  		goto error_recovery;
  	}
  #endif
  
  	return dev;
  
   error_recovery:
  	if (filename) FLUID_FREE (filename);
  	delete_fluid_file_renderer(dev);
  	return NULL;
  }
  
  /**
   * Close file and destroy a file renderer object.
   * @param dev File renderer object.
   * @since 1.1.0
   */
  void delete_fluid_file_renderer(fluid_file_renderer_t* dev)
  {
  	if (dev == NULL) {
  		return;
  	}
  
  #if LIBSNDFILE_SUPPORT
  	if (dev->sndfile != NULL) {
  		int retval = sf_close (dev->sndfile);
  		if (retval != 0) FLUID_LOG (FLUID_WARN, "Error closing audio file: %s",
  					    sf_error_number (retval));
          }
  #else
  	if (dev->file != NULL) {
  		fclose(dev->file);
  	}
  #endif
  
  	if (dev->buf != NULL) {
  		FLUID_FREE(dev->buf);
  	}
  
  	FLUID_FREE(dev);
  	return;
  }
  
  /**
   * Write period_size samples to file.
   * @param dev File renderer instance
   * @return #FLUID_OK or #FLUID_FAILED if an error occurred
   * @since 1.1.0
   */
  int
  fluid_file_renderer_process_block(fluid_file_renderer_t* dev)
  {
  #if LIBSNDFILE_SUPPORT
  	int n;
  
  	fluid_synth_write_float(dev->synth, dev->period_size, dev->buf, 0, 2, dev->buf, 1, 2);
  
  	n = sf_writef_float (dev->sndfile, dev->buf, dev->period_size);
  
  	if (n != dev->period_size) {
  		FLUID_LOG (FLUID_ERR, "Audio file write error: %s",
  			   sf_strerror (dev->sndfile));
  		return FLUID_FAILED;
  	}
  	return FLUID_OK;
  
  #else   /* No libsndfile support */
  
  	int n, offset;
  
  	fluid_synth_write_s16(dev->synth, dev->period_size, dev->buf, 0, 2, dev->buf, 1, 2);
  
  	for (offset = 0; offset < dev->buf_size; offset += n) {
  
  		n = fwrite((char*) dev->buf + offset, 1, dev->buf_size - offset, dev->file);
  		if (n < 0) {
  			FLUID_LOG(FLUID_ERR, "Audio output file write error: %s",
  				  strerror (errno));
  			return FLUID_FAILED;
  		}
  	}
  	return FLUID_OK;
  #endif
  }
  
  
  #if LIBSNDFILE_SUPPORT
  
  /**
   * Parse a colon separated format string and configure an SF_INFO structure accordingly.
   * @param filetype File type string (NULL or "auto" to attempt to identify format
   *   by filename extension, with fallback to "wav")
   * @param format File audio format string or NULL to use "s16"
   * @param endian File endian string or NULL to use "auto" which uses the file type's
   *   default endian byte order.
   * @param filename File name (used by "auto" type to determine type, based on extension)
   * @param info Audio file info structure to configure
   * @return TRUE on success, FALSE otherwise
   */
  static int
  fluid_file_renderer_parse_options (char *filetype, char *format, char *endian,
                                     char *filename, SF_INFO *info)
  {
    int type = -1;        /* -1 indicates "auto" type */
    char *s;
    int i;
  
    /* If "auto" type, then use extension to search for a match */
    if (!filetype || FLUID_STRCMP (filetype, "auto") == 0)
    {
      type = FLUID_FILE_RENDERER_DEFAULT_FILE_TYPE;
      s = FLUID_STRRCHR (filename, '.');
  
      if (s && s[1] != '\0')
      {	
        if (!fluid_file_renderer_find_file_type (s + 1, &type))
          FLUID_LOG (FLUID_WARN, "Failed to determine audio file type from filename, defaulting to WAV");
      }
    }
    else if (!fluid_file_renderer_find_file_type (filetype, &type))
    {
      FLUID_LOG(FLUID_ERR, "Invalid or unsupported audio file type '%s'", filetype);
      return FALSE;
    }
  
  
    info->format = (info->format & ~SF_FORMAT_TYPEMASK) | type;
  
    /* Look for subtype */
    if (format)
    {
      for (i = 0; format_names[i]; i++)
        if (FLUID_STRCMP (format, format_names[i]) == 0)
          break;
  
      if (!format_names[i])
      {
        FLUID_LOG (FLUID_ERR, "Invalid or unsupported file audio format '%s'", format);
        return FALSE;
      }
  
      info->format = (info->format & ~SF_FORMAT_SUBMASK) | format_ids[i];
    }
  
  #if LIBSNDFILE_HASVORBIS
    /* Force subformat to vorbis as nothing else would make sense currently */
    if ((info->format & SF_FORMAT_TYPEMASK) == SF_FORMAT_OGG) {
      info->format = (info->format & ~SF_FORMAT_SUBMASK) | SF_FORMAT_VORBIS;
    }
  #endif
  
    /* Look for endian */
    if (endian)
    {
      for (i = 0; endian_names[i]; i++)
        if (FLUID_STRCMP (endian, endian_names[i]) == 0)
          break;
  
      if (!endian_names[i])
      {
        FLUID_LOG (FLUID_ERR, "Invalid or unsupported endian byte order '%s'", endian);
        return FALSE;
      }
  
      info->format = (info->format & ~SF_FORMAT_ENDMASK) | endian_ids[i];
    }
  
    return TRUE;
  }
  
  /**
   * Searches for a supported libsndfile file type by extension.
   * @param extension The extension string
   * @param ext_len Length of the extension string
   * @param type Location to store the type (unmodified if not found)
   * @return TRUE if found, FALSE otherwise
   */
  static int
  fluid_file_renderer_find_file_type (char *extension, int *type)
  {
    SF_FORMAT_INFO finfo;
    int major_count;
    int i;
  
    sf_command (NULL, SFC_GET_FORMAT_MAJOR_COUNT, &major_count, sizeof (int));
  
    for (i = 0; i < major_count; i++)
    {
      finfo.format = i;
      sf_command (NULL, SFC_GET_FORMAT_MAJOR, &finfo, sizeof (finfo));
  
      if (FLUID_STRCMP (extension, finfo.extension) == 0)
        break;
    }
  
    if (i < major_count)
    {
      *type = finfo.format;
      return TRUE;
    }
  
    return FALSE;
  }
  
  /* Search for a valid audio format for a given file type */
  static int
  fluid_file_renderer_find_valid_format (SF_INFO *info)
  {
    SF_FORMAT_INFO format_info;
    int count, i;
  
    sf_command (NULL, SFC_GET_FORMAT_SUBTYPE_COUNT, &count, sizeof (int));
  
    for (i = 0; i < count; i++)
    {
      format_info.format = i;
  
      sf_command (NULL, SFC_GET_FORMAT_SUBTYPE, &format_info, sizeof (format_info));
  
      info->format = (info->format & ~SF_FORMAT_SUBMASK) | format_info.format;
  
      if (sf_format_check (info)) return TRUE;
    }
  
    return FALSE;
  }
  
  #endif
  