/******************************************************************************
 *                                                                            *
 * Copyright (c) 1999-2003 Wimba S.A., All Rights Reserved.                   *
 *                                                                            *
 * COPYRIGHT:                                                                 *
 *      This software is the property of Wimba S.A.                           *
 *      This software is redistributed under the Xiph.org variant of          *
 *      the BSD license.                                                      *
 *      Redistribution and use in source and binary forms, with or without    *
 *      modification, are permitted provided that the following conditions    *
 *      are met:                                                              *
 *      - Redistributions of source code must retain the above copyright      *
 *      notice, this list of conditions and the following disclaimer.         *
 *      - Redistributions in binary form must reproduce the above copyright   *
 *      notice, this list of conditions and the following disclaimer in the   *
 *      documentation and/or other materials provided with the distribution.  *
 *      - Neither the name of Wimba, the Xiph.org Foundation nor the names of *
 *      its contributors may be used to endorse or promote products derived   *
 *      from this software without specific prior written permission.         *
 *                                                                            *
 * WARRANTIES:                                                                *
 *      This software is made available by the authors in the hope            *
 *      that it will be useful, but without any warranty.                     *
 *      Wimba S.A. is not liable for any consequence related to the           *
 *      use of the provided software.                                         *
 *                                                                            *
 * Class: SpeexFormatConvertionProvider.java                                  *
 *                                                                            *
 * Author: Marc GIMPEL                                                        *
 *                                                                            *
 * Date: 12th July 2003                                                       *
 *                                                                            *
 ******************************************************************************/

/* $Id: SpeexFormatConvertionProvider.java,v 1.2 2004/10/21 16:21:58 mgimpel Exp $ */

package org.xiph.speex.spi;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.spi.FormatConversionProvider;

/**
 * A format conversion provider provides format conversion services from one or
 * more input formats to one or more output formats. Converters include codecs,
 * which encode and/or decode audio data, as well as transcoders, etc.
 * Format converters provide methods for determining what conversions are
 * supported and for obtaining an audio stream from which converted data can be
 * read.
 * 
 * The source format represents the format of the incoming audio data, which
 * will be converted.
 * 
 * The target format represents the format of the processed, converted audio
 * data. This is the format of the data that can be read from the stream
 * returned by one of the getAudioInputStream methods.
 * 
 * @author Marc Gimpel, Wimba S.A. (mgimpel@horizonwimba.com)
 * @version $Revision: 1.2 $
 */
public class SpeexFormatConvertionProvider
  extends FormatConversionProvider
{
  /** */
  public static final AudioFormat.Encoding[] NO_ENCODING = {};
  /** */
  public static final AudioFormat.Encoding[] PCM_ENCODING =
                        {AudioFormat.Encoding.PCM_SIGNED};
  /** */
  public static final AudioFormat.Encoding[] SPEEX_ENCODING =
                        {SpeexEncoding.SPEEX};
  /** */
  public static final AudioFormat.Encoding[] BOTH_ENCODINGS =
                        {SpeexEncoding.SPEEX, AudioFormat.Encoding.PCM_SIGNED};
  /** */
  public static final AudioFormat[] NO_FORMAT = {};

  /**
   * Obtains the set of source format encodings from which format conversion
   * services are provided by this provider.
   * @return array of source format encodings.
   * The array will always have a length of at least 1.
   */
  public AudioFormat.Encoding[] getSourceEncodings()
  {
    AudioFormat.Encoding[] encodings = {SpeexEncoding.SPEEX,
                                        AudioFormat.Encoding.PCM_SIGNED};
    return encodings;
  }
  
  /**
   * Obtains the set of target format encodings to which format conversion
   * services are provided by this provider.
   * @return array of target format encodings.
   * The array will always have a length of at least 1.
   */
  public AudioFormat.Encoding[] getTargetEncodings()
  {
    AudioFormat.Encoding[] encodings = {SpeexEncoding.SPEEX_Q0,
                                        SpeexEncoding.SPEEX_Q1,
                                        SpeexEncoding.SPEEX_Q2,
                                        SpeexEncoding.SPEEX_Q3,
                                        SpeexEncoding.SPEEX_Q4,
                                        SpeexEncoding.SPEEX_Q5,
                                        SpeexEncoding.SPEEX_Q6,
                                        SpeexEncoding.SPEEX_Q7,
                                        SpeexEncoding.SPEEX_Q8,
                                        SpeexEncoding.SPEEX_Q9,
                                        SpeexEncoding.SPEEX_Q10,
                                        SpeexEncoding.SPEEX_VBR0,
                                        SpeexEncoding.SPEEX_VBR1,
                                        SpeexEncoding.SPEEX_VBR2,
                                        SpeexEncoding.SPEEX_VBR3,
                                        SpeexEncoding.SPEEX_VBR4,
                                        SpeexEncoding.SPEEX_VBR5,
                                        SpeexEncoding.SPEEX_VBR6,
                                        SpeexEncoding.SPEEX_VBR7,
                                        SpeexEncoding.SPEEX_VBR8,
                                        SpeexEncoding.SPEEX_VBR9,
                                        SpeexEncoding.SPEEX_VBR10,
                                        AudioFormat.Encoding.PCM_SIGNED};
    return encodings;
  }
  
  /**
   * Obtains the set of target format encodings supported by the format
   * converter given a particular source format. If no target format encodings
   * are supported for this source format, an array of length 0 is returned.
   * @param sourceFormat format of the incoming data.
   * @return array of supported target format encodings.
   */
  public AudioFormat.Encoding[] getTargetEncodings(final AudioFormat sourceFormat)
  {
    if (sourceFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)) {
      AudioFormat.Encoding[] encodings = {SpeexEncoding.SPEEX_Q0,
                                          SpeexEncoding.SPEEX_Q1,
                                          SpeexEncoding.SPEEX_Q2,
                                          SpeexEncoding.SPEEX_Q3,
                                          SpeexEncoding.SPEEX_Q4,
                                          SpeexEncoding.SPEEX_Q5,
                                          SpeexEncoding.SPEEX_Q6,
                                          SpeexEncoding.SPEEX_Q7,
                                          SpeexEncoding.SPEEX_Q8,
                                          SpeexEncoding.SPEEX_Q9,
                                          SpeexEncoding.SPEEX_Q10,
                                          SpeexEncoding.SPEEX_VBR0,
                                          SpeexEncoding.SPEEX_VBR1,
                                          SpeexEncoding.SPEEX_VBR2,
                                          SpeexEncoding.SPEEX_VBR3,
                                          SpeexEncoding.SPEEX_VBR4,
                                          SpeexEncoding.SPEEX_VBR5,
                                          SpeexEncoding.SPEEX_VBR6,
                                          SpeexEncoding.SPEEX_VBR7,
                                          SpeexEncoding.SPEEX_VBR8,
                                          SpeexEncoding.SPEEX_VBR9,
                                          SpeexEncoding.SPEEX_VBR10};
      return encodings;
    }
    else if (sourceFormat.getEncoding() instanceof SpeexEncoding) {
      AudioFormat.Encoding[] encodings = {AudioFormat.Encoding.PCM_SIGNED};
      return encodings;
    }
    else {
      AudioFormat.Encoding[] encodings = {};
      return encodings;
    }
  }

  /**
   * Obtains the set of target formats with the encoding specified supported by
   * the format converter. If no target formats with the specified encoding are
   * supported for this source format, an array of length 0 is returned.
   * @param targetEncoding desired encoding of the outgoing data.
   * @param sourceFormat format of the incoming data.
   * @return array of supported target formats.
   */
  public AudioFormat[] getTargetFormats(final AudioFormat.Encoding targetEncoding,
                                        final AudioFormat sourceFormat)
  {
    if (sourceFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED) &&
        targetEncoding instanceof SpeexEncoding) {
      if (sourceFormat.getChannels() > 2 || sourceFormat.getChannels() <= 0 ||
          sourceFormat.isBigEndian()) {
        AudioFormat[] formats = {};
        return formats;
      }
      else {
        AudioFormat[] formats = {new AudioFormat(targetEncoding,
                                                 sourceFormat.getSampleRate(),
                                                 -1, // sample size in bits
                                                 sourceFormat.getChannels(),
                                                 -1, // frame size
                                                 -1, // frame rate
                                                 false)}; // little endian
        return formats;
      }
    }
    else if (sourceFormat.getEncoding() instanceof SpeexEncoding &&
             targetEncoding.equals(AudioFormat.Encoding.PCM_SIGNED)) {
      AudioFormat[] formats = {new AudioFormat(sourceFormat.getSampleRate(),
                                               16, // sample size in bits
                                               sourceFormat.getChannels(),
                                               true, // signed
                                               false)}; // little endian (for PCM wav)
      return formats;
    }
    else {
      AudioFormat[] formats = {};
      return formats;
    }
  }
  
  /**
   * Obtains an audio input stream with the specified encoding from the given
   * audio input stream.
   * @param targetEncoding - desired encoding of the stream after processing.
   * @param sourceStream - stream from which data to be processed should be read.
   * @return stream from which processed data with the specified target
   * encoding may be read.
   * @exception IllegalArgumentException - if the format combination supplied
   * is not supported.
   */
  public AudioInputStream getAudioInputStream(final AudioFormat.Encoding targetEncoding,
                                              final AudioInputStream sourceStream)
  {
    if (isConversionSupported(targetEncoding, sourceStream.getFormat())) {
      AudioFormat[] formats = getTargetFormats(targetEncoding,
                                               sourceStream.getFormat());
      if (formats != null && formats.length > 0) {
        AudioFormat sourceFormat = sourceStream.getFormat();
        AudioFormat targetFormat = formats[0];
        if (sourceFormat.equals(targetFormat)) {
          return sourceStream;
        }
        else if (sourceFormat.getEncoding() instanceof SpeexEncoding &&
                 targetFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)) {
          return new Speex2PcmAudioInputStream(sourceStream, targetFormat, -1);
        }
        else if (sourceFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED) &&
                 targetFormat.getEncoding() instanceof SpeexEncoding) {
          return new Pcm2SpeexAudioInputStream(sourceStream, targetFormat, -1);
        }
        else {
          throw new IllegalArgumentException("unable to convert " + sourceFormat.toString() +
                                             " to " + targetFormat.toString());
        }
      }
      else {
        throw new IllegalArgumentException("target format not found");
      }
    }
    else {
      throw new IllegalArgumentException("conversion not supported");
    }
  }
  
  /**
   * Obtains an audio input stream with the specified format from the given
   * audio input stream.
   * @param targetFormat - desired data format of the stream after processing.
   * @param sourceStream - stream from which data to be processed should be read.
   * @return stream from which processed data with the specified format may be
   * read.
   * @exception IllegalArgumentException - if the format combination supplied
   * is not supported.
   */
  public AudioInputStream getAudioInputStream(final AudioFormat targetFormat,
                                              final AudioInputStream sourceStream)
  {
    if (isConversionSupported(targetFormat, sourceStream.getFormat())) {
      AudioFormat[] formats = getTargetFormats(targetFormat.getEncoding(),
                                               sourceStream.getFormat());
      if (formats != null && formats.length > 0) {
        AudioFormat sourceFormat = sourceStream.getFormat();
        if (sourceFormat.equals(targetFormat)) {
          return sourceStream;
        }
        else if (sourceFormat.getEncoding() instanceof SpeexEncoding &&
                 targetFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)) {
          return new Speex2PcmAudioInputStream(sourceStream, targetFormat, -1);
        }
        else if (sourceFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED) &&
                 targetFormat.getEncoding() instanceof SpeexEncoding) {
          return new Pcm2SpeexAudioInputStream(sourceStream, targetFormat, -1);
        }
        else {
          throw new IllegalArgumentException("unable to convert " + sourceFormat.toString() +
                                             " to " + targetFormat.toString());
        }
      }
      else {
        throw new IllegalArgumentException("target format not found");
      }
    }
    else {
      throw new IllegalArgumentException("conversion not supported");
    }
  }
}
