/******************************************************************************
 *                                                                            *
 * 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: PcmWaveWriter.java                                                  *
 *                                                                            *
 * Author: James LAWRENCE                                                     *
 * Modified by: Marc GIMPEL                                                   *
 *                                                                            *
 * Date: March 2003                                                           *
 *                                                                            *
 ******************************************************************************/

/* $Id: PcmWaveWriter.java,v 1.2 2004/10/21 16:21:57 mgimpel Exp $ */

package org.xiph.speex;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * Writes basic PCM wave files from binary audio data.
 *
 * <p>Here's an example that writes 2 seconds of silence
 * <pre>
 * PcmWaveWriter s_wsw = new PcmWaveWriter(2, 44100);
 * byte[] silence = new byte[16*2*44100];
 * wsw.Open("C:\\out.wav");
 * wsw.WriteHeader(); 
 * wsw.WriteData(silence, 0, silence.length);
 * wsw.WriteData(silence, 0, silence.length);
 * wsw.Close(); 
 * </pre>
 *
 * @author Jim Lawrence, helloNetwork.com
 * @author Marc Gimpel, Wimba S.A. (mgimpel@horizonwimba.com)
 * @version $Revision: 1.2 $
 */
public class PcmWaveWriter
  extends AudioFileWriter
{
  /** Wave type code of PCM */
  public static final short WAVE_FORMAT_PCM = (short) 0x01;
  /** Wave type code of Speex */
  public static final short WAVE_FORMAT_SPEEX = (short) 0xa109;

  /**
   * Table describing the number of frames per packet in a Speex Wave file,
   * depending on its mode-1 (1=NB, 2=WB, 3=UWB), channels-1 (1=mono, 2=stereo)
   * and the quality setting (0 to 10).
   * See end of file for exerpt from SpeexACM code for more explanations.
   */
  public static final int[][][] WAVE_FRAME_SIZES = new int[][][]
    {{{8, 8, 8, 1, 1, 2, 2, 2, 2, 2, 2},   // NB mono
      {2, 1, 1, 7, 7, 8, 8, 8, 8, 3, 3}},  // NB stereo
     {{8, 8, 8, 2, 1, 1, 2, 2, 2, 2, 2},   // WB mono
      {1, 2, 2, 8, 7, 6, 3, 3, 3, 3, 3}},  // WB stereo
     {{8, 8, 8, 1, 2, 2, 1, 1, 1, 1, 1},   // UWB mono
      {2, 1, 1, 7, 8, 3, 6, 6, 5, 5, 5}}}; // UWB stereo

  /**
   * Table describing the number of bit per Speex frame, depending on its
   * mode-1 (1=NB, 2=WB, 3=UWB), channels-1 (1=mono, 2=stereo) and the quality
   * setting (0 to 10).
   * See end of file for exerpt from SpeexACM code for more explanations.
   */
  public static final int[][][] WAVE_BITS_PER_FRAME = new int[][][]
    {{{ 43,  79, 119, 160, 160, 220, 220, 300, 300, 364, 492},   // NB mono
      { 60,  96, 136, 177, 177, 237, 237, 317, 317, 381, 509}},  // NB stereo
     {{ 79, 115, 155, 196, 256, 336, 412, 476, 556, 684, 844},   // WB mono
      { 96, 132, 172, 213, 273, 353, 429, 493, 573, 701, 861}},  // WB stereo
     {{ 83, 151, 191, 232, 292, 372, 448, 512, 592, 720, 880},   // UWB mono
      {100, 168, 208, 249, 309, 389, 465, 529, 609, 737, 897}}}; // UWB stereo

  private RandomAccessFile raf; 
  /** Defines the encoder mode (0=NB, 1=WB and 2-UWB). */
  private int     mode;
  /** */
  private int     quality;
  /** Defines the sampling rate of the audio input. */
  private int     sampleRate;
  /** Defines the number of channels of the audio input (1=mono, 2=stereo). */
  private int     channels;
  /** Defines the number of frames per speex packet. */
  private int     nframes;
  /** Defines whether or not to use VBR (Variable Bit Rate). */
  private boolean vbr;
  /** */
  private int size;
  /** */
  private boolean isPCM;
  
  /**
   * Constructor. 
   */
  public PcmWaveWriter()
  {
    size = 0;
  }

  /**
   * Constructor. 
   * @param sampleRate the number of samples per second.
   * @param channels   the number of audio channels (1=mono, 2=stereo, ...).
   */
  public PcmWaveWriter(final int sampleRate, final int channels)
  {
    this();
    setPCMFormat(sampleRate, channels);
  }

  /**
   * Constructor. 
   * @param mode       the mode of the encoder (0=NB, 1=WB, 2=UWB).
   * @param quality
   * @param sampleRate the number of samples per second.
   * @param channels   the number of audio channels (1=mono, 2=stereo, ...).
   * @param nframes    the number of frames per speex packet.
   * @param vbr
   */
  public PcmWaveWriter(final int mode,
                       final int quality,
                       final int sampleRate,
                       final int channels,
                       final int nframes,
                       final boolean vbr)
  {
    this();
    setSpeexFormat(mode, quality, sampleRate, channels, nframes, vbr);
  }

  /**
   * Sets the output format for a PCM Wave file.
   * Must be called before WriteHeader().
   * @param sampleRate the number of samples per second.
   * @param channels   the number of audio channels (1=mono, 2=stereo, ...).
   */
  private void setPCMFormat(final int sampleRate, final int channels)
  {
    this.channels   = channels;
    this.sampleRate = sampleRate;
    isPCM = true;
  }

  /**
   * Sets the output format for a Speex Wave file.
   * Must be called before WriteHeader().
   * @param mode       the mode of the encoder (0=NB, 1=WB, 2=UWB).
   * @param quality    
   * @param sampleRate the number of samples per second.
   * @param channels   the number of audio channels (1=mono, 2=stereo, ...).
   * @param nframes    the number of frames per speex packet.
   * @param vbr
   */
  private void setSpeexFormat(final int mode,
                              final int quality,
                              final int sampleRate,
                              final int channels,
                              final int nframes,
                              final boolean vbr)
  {
    this.mode       = mode;
    this.quality    = quality;
    this.sampleRate = sampleRate;
    this.channels   = channels;
    this.nframes    = nframes;
    this.vbr        = vbr;
    isPCM = false;
  }
  
  /**
   * Closes the output file.
   * MUST be called to have a correct stream. 
   * @exception IOException if there was an exception closing the Audio Writer.
   */
  public void close()
    throws IOException 
  {
    /* update the total file length field from RIFF chunk */
    raf.seek(4);
    int fileLength = (int) raf.length() - 8;
    writeInt(raf, fileLength);
    
    /* update the data chunk length size */
    raf.seek(40);
    writeInt(raf, size);
    
    /* close the output file */
    raf.close(); 
  }
  
  /**
   * Open the output file. 
   * @param file - file to open.
   * @exception IOException if there was an exception opening the Audio Writer.
   */
  public void open(final File file)
    throws IOException
  {
    file.delete(); 
    raf = new RandomAccessFile(file, "rw");
    size = 0;   
  }

  /**
   * Open the output file. 
   * @param filename filename to open.
   * @exception IOException if there was an exception opening the Audio Writer.
   */
  public void open(final String filename)
    throws IOException 
  {
    open(new File(filename)); 
  }
    
  /**
   * Writes the initial data chunks that start the wave file. 
   * Prepares file for data samples to written.
   * @param comment ignored by the WAV header.
   * @exception IOException
   */
  public void writeHeader(final String comment)
    throws IOException
  {
    /* writes the RIFF chunk indicating wave format */
    byte[] chkid = "RIFF".getBytes(); 
    raf.write(chkid, 0, chkid.length);
    writeInt(raf, 0); /* total length must be blank */
    chkid = "WAVE".getBytes(); 
    raf.write(chkid, 0, chkid.length);
    
    /* format subchunk: of size 16 */
    chkid = "fmt ".getBytes(); 
    raf.write(chkid, 0, chkid.length);
    if (isPCM) {
      writeInt(raf, 16);                            // Size of format chunk
      writeShort(raf, WAVE_FORMAT_PCM);             // Format tag: PCM
      writeShort(raf, (short)channels);             // Number of channels
      writeInt(raf, sampleRate);                    // Sampling frequency
      writeInt(raf, sampleRate*channels*2);         // Average bytes per second
      writeShort(raf, (short) (channels*2));        // Blocksize of data
      writeShort(raf, (short) 16);                  // Bits per sample
    }
    else {
      int length = comment.length();
      writeInt(raf, (short) (18+2+80+length));      // Size of format chunk
      writeShort(raf, WAVE_FORMAT_SPEEX);           // Format tag: Speex
      writeShort(raf, (short)channels);             // Number of channels
      writeInt(raf, sampleRate);                    // Sampling frequency
      writeInt(raf, (calculateEffectiveBitrate(mode, channels, quality) + 7) >> 3); // Average bytes per second
      writeShort(raf, (short) calculateBlockSize(mode, channels, quality)); // Blocksize of data
      writeShort(raf, (short) quality);             // Bits per sample
      writeShort(raf, (short) (2+80+length));       // The count in bytes of the extra size
      raf.writeByte(0xff & 1);                      // ACM major version number
      raf.writeByte(0xff & 0);                      // ACM minor version number
      raf.write(buildSpeexHeader(sampleRate, mode, channels, vbr, nframes));
      raf.writeBytes(comment);
    }
    
    /* write the start of data chunk */
    chkid = "data".getBytes(); 
    raf.write(chkid, 0, chkid.length);
    writeInt(raf, 0);
  }
  
  /**
   * Writes a packet of audio. 
   * @param data audio data
   * @param offset the offset from which to start reading the data.
   * @param len the length of data to read.
   * @exception IOException
   */
  public void writePacket(final byte[] data,
                          final int offset,
                          final int len)
    throws IOException 
  {
    raf.write(data, offset, len);
    size+= len;
  }

  /**
   * Calculates effective bitrate (considering padding).
   * See end of file for exerpt from SpeexACM code for more explanations.
   * @param mode
   * @param channels
   * @param quality
   * @return effective bitrate (considering padding).
   */
  private static final int calculateEffectiveBitrate(final int mode,
                                                     final int channels,
                                                     final int quality)
  {
    return ((((WAVE_FRAME_SIZES[mode-1][channels-1][quality] *
               WAVE_BITS_PER_FRAME[mode-1][channels-1][quality]) + 7) >> 3) *
            50 * 8) / WAVE_BITS_PER_FRAME[mode-1][channels-1][quality];
  }

  /**
   * Calculates block size (considering padding).
   * See end of file for exerpt from SpeexACM code for more explanations.
   * @param mode
   * @param channels
   * @param quality
   * @return block size (considering padding).
   */
  private static final int calculateBlockSize(final int mode,
                                              final int channels,
                                              final int quality)
  {
    return (((WAVE_FRAME_SIZES[mode-1][channels-1][quality] *
              WAVE_BITS_PER_FRAME[mode-1][channels-1][quality]) + 7) >> 3);
  }
}
// The following is taken from the SpeexACM 1.0.1.1 Source code (codec.c file).

//
//  This array describes how many bits are required by an encoded audio frame.
//  It also specifies the optimal framesperblock parameter to minimize
//  padding loss. It also lists the effective bitrate (considering padding).
//
//  The array indices are rate, channels, quality (each as a 0 based index)
//
/*
struct tagQualityInfo {
  UINT nBitsPerFrame;
  UINT nFrameSize;
  UINT nFramesPerBlock;
  UINT nEffectiveBitrate;
} QualityInfo[3][2][11] = {
     43, 160, 8,  2150, //  8000 1 0
     79, 160, 8,  3950, //  8000 1 1
    119, 160, 8,  5950, //  8000 1 2
    160, 160, 1,  8000, //  8000 1 3
    160, 160, 1,  8000, //  8000 1 4
    220, 160, 2, 11000, //  8000 1 5
    220, 160, 2, 11000, //  8000 1 6
    300, 160, 2, 15000, //  8000 1 7
    300, 160, 2, 15000, //  8000 1 8
    364, 160, 2, 18200, //  8000 1 9
    492, 160, 2, 24600, //  8000 1 10
     60, 160, 2,  3000, //  8000 2 0
     96, 160, 1,  4800, //  8000 2 1
    136, 160, 1,  6800, //  8000 2 2
    177, 160, 7,  8857, //  8000 2 3
    177, 160, 7,  8857, //  8000 2 4
    237, 160, 8, 11850, //  8000 2 5
    237, 160, 8, 11850, //  8000 2 6
    317, 160, 8, 15850, //  8000 2 7
    317, 160, 8, 15850, //  8000 2 8
    381, 160, 3, 19066, //  8000 2 9
    509, 160, 3, 25466, //  8000 2 10
     79, 320, 8,  3950, // 16000 1 0
    115, 320, 8,  5750, // 16000 1 1
    155, 320, 8,  7750, // 16000 1 2
    196, 320, 2,  9800, // 16000 1 3
    256, 320, 1, 12800, // 16000 1 4
    336, 320, 1, 16800, // 16000 1 5
    412, 320, 2, 20600, // 16000 1 6
    476, 320, 2, 23800, // 16000 1 7
    556, 320, 2, 27800, // 16000 1 8
    684, 320, 2, 34200, // 16000 1 9
    844, 320, 2, 42200, // 16000 1 10
     96, 320, 1,  4800, // 16000 2 0
    132, 320, 2,  6600, // 16000 2 1
    172, 320, 2,  8600, // 16000 2 2
    213, 320, 8, 10650, // 16000 2 3
    273, 320, 7, 13657, // 16000 2 4
    353, 320, 6, 17666, // 16000 2 5
    429, 320, 3, 21466, // 16000 2 6
    493, 320, 3, 24666, // 16000 2 7
    573, 320, 3, 28666, // 16000 2 8
    701, 320, 3, 35066, // 16000 2 9
    861, 320, 3, 43066, // 16000 2 10
     83, 640, 8,  4150, // 32000 1 0
    151, 640, 8,  7550, // 32000 1 1
    191, 640, 8,  9550, // 32000 1 2
    232, 640, 1, 11600, // 32000 1 3
    292, 640, 2, 14600, // 32000 1 4
    372, 640, 2, 18600, // 32000 1 5
    448, 640, 1, 22400, // 32000 1 6
    512, 640, 1, 25600, // 32000 1 7
    592, 640, 1, 29600, // 32000 1 8
    720, 640, 1, 36000, // 32000 1 9
    880, 640, 1, 44000, // 32000 1 10
    100, 640, 2,  5000, // 32000 2 0
    168, 640, 1,  8400, // 32000 2 1
    208, 640, 1, 10400, // 32000 2 2
    249, 640, 7, 12457, // 32000 2 3
    309, 640, 8, 15450, // 32000 2 4
    389, 640, 3, 19466, // 32000 2 5
    465, 640, 6, 23266, // 32000 2 6
    529, 640, 6, 26466, // 32000 2 7
    609, 640, 5, 30480, // 32000 2 8
    737, 640, 5, 36880, // 32000 2 9
    897, 640, 5, 44880, // 32000 2 10
};
*/
