Package de.maramuse.soundcomp.files

Source Code of de.maramuse.soundcomp.files.OutputFile

package de.maramuse.soundcomp.files;

/*
* Copyright 2010 Jan Schmidt-Reinisch
*
* SoundComp - a sound processing library
*
* 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; in version 2.1
*
* 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
*
*/

/**
* A file export class generating RIFF (wave) compatible (and similar) files.
* At first this class only provides wave file and Monkey's Audio write access.
*/
import java.io.OutputStream;
import java.io.InputStream;
import java.io.IOException;

import de.maramuse.soundcomp.process.NamedSource;
import de.maramuse.soundcomp.process.ParameterMap;
import de.maramuse.soundcomp.process.ProcessElement;
import de.maramuse.soundcomp.process.SourceStore;
import de.maramuse.soundcomp.process.StandardParameters;
import de.maramuse.soundcomp.process.Stateful;
import de.maramuse.soundcomp.process.TypeMismatchException;
import de.maramuse.soundcomp.process.UnknownConnectionException;
import de.maramuse.soundcomp.process.ValueType;
import de.maramuse.soundcomp.process.StandardParameters.Parameter;
import de.maramuse.soundcomp.util.GlobalParameters;
import de.maramuse.soundcomp.util.NotImplementedException;
import de.maramuse.soundcomp.util.ReadOnlyMap;
import de.maramuse.soundcomp.util.ReadOnlyMapImpl;
import de.maramuse.soundcomp.util.NativeObjects;

public class OutputFile implements ProcessElement, Stateful {

 
  public long nativeSpace=-1;
  private String instanceName;
  private String abstractName;
  private String outputFileName;
 
  private static final double MIN32=(-2147483648.0-0.4999)/2147483648.0, MAX32=(2147483648.0-0.5001)/(2147483648.0);
  private static final double MIN24=(-(1<<23)-0.4999)/(1<<23), MAX24=((1<<23)-0.5001)/(1<<23);
  private static final double MIN16=-32768.499/32768, MAX16=32767.499/32768;
  private static final double SC64=9223372036854775808.0;
  private static final double SC32=2147483648.0;
  private static final double SC24=(1<<23);
  private static final int SC24I=(1<<23);
  // private static final double SC16=32768;
  // private static final int SC16I=32768;
 
  private int format=FileFormats.FMT_WAVE_S16;

  private ReadOnlyMapImpl<Integer, ValueType> srcMap=new ReadOnlyMapImpl<Integer, ValueType>();
  private ReadOnlyMapImpl<Integer, SourceStore> sourceStoreMap=new ReadOnlyMapImpl<Integer, SourceStore>();
  private static ParameterMap inputsMap=new ParameterMap();
  private static ParameterMap outputsMap=new ParameterMap();
  static{
  // TODO special handling, like mixer
  inputsMap.put(StandardParameters.FACTOR);
  inputsMap.put(StandardParameters.IN);
  outputsMap.put(StandardParameters.OUT);
  }

  private SourceStore[] sources;
  private final int nChannels;
  private final DataList[] samples;
  private int wSampleRate;

  /**
   * Create a wave file output element for a n-channel file.
   * The exact format is specified when writing.
   * @param nChannels the number of channels to be written to the file
   */
  public OutputFile(int nChannels) {
  NativeObjects.registerNativeObject(this);
  this.nChannels=nChannels;
  sources=new SourceStore[nChannels];
  samples=new DataList[nChannels];
  for(int i=0; i<nChannels; i++)
    samples[i]=new DataList();
  wSampleRate=(int)GlobalParameters.get().getSampleRate();
  }

  /**
   * This constructor would apply in the hypothetical case that a wave output
   * element is to be created from C++ code. This is not yet possible.
   * @param nChannels  the number of channels to be written to the file
   * @param s      ignored, used for distinguishing constructors
   */
  OutputFile(boolean s, int nChannels) {
  this.nChannels=nChannels;
  samples=new DataList[nChannels];
  for(int i=0; i<nChannels; i++)
    samples[i]=new DataList();
  wSampleRate=(int)GlobalParameters.get().getSampleRate();
  }

  public int getNrSamples() {
  int nrSamples=0;
  for(DataList l:samples){
    if(l!=null&&l.size()>nrSamples)
    nrSamples=l.size();
  }
  return nrSamples;
  }
 
  public void setFormat(int format){
  this.format=format;
  }
 
  public int getFormat(){
  return format;
  }

 
  public void write(OutputStream s) throws IOException{
  switch(format){
    case FileFormats.FMT_WAVE_DOUBLE:
    writeDoubleWavStream(s);
    break;
    case FileFormats.FMT_WAVE_FLOAT:
    writeFloatWavStream(s);
    break;
    case FileFormats.FMT_WAVE_S16:
    writeWindowsStream(s);
    break;
    case FileFormats.FMT_WAVE_S24:
    write24WindowsStream(s);
    break;
    case FileFormats.FMT_WAVE_S32:
    write32WindowsStream(s);
    break;
    case FileFormats.FMT_MONKEY_8:
    writeApeStream(s, 8);
    break;
    case FileFormats.FMT_MONKEY_16:
    writeApeStream(s, 16);
    break;
    case FileFormats.FMT_MONKEY_24:
    writeApeStream(s, 24);
    break;
    case FileFormats.FMT_WAVE_U8:
    writeByteWavStream(s);
    break;
    case FileFormats.FMT_FLAC_RAW_16:
    writeFlacStream(s, false, 16);
    break;
    case FileFormats.FMT_FLAC_OGG_16:
    writeFlacStream(s, true, 16);
    break;
    case FileFormats.FMT_WAVE_U32:
     throw new NotImplementedException();
    case FileFormats.FMT_VORBIS_OGG:
    writeOggVorbisStream(s);
    break;
    case FileFormats.FMT_MP3:
    writeMp3Stream(s);
    break;
    case FileFormats.FMT_MPC7_16: // will we ever support that? or is MPC8 enough?
    case FileFormats.FMT_MPC8_16:
    case FileFormats.FMT_WAVE_S8:
    case FileFormats.FMT_WAVE_U16:
    case FileFormats.FMT_WAVE_U24:
     throw new NotImplementedException();
    default:
    throw new IllegalArgumentException();
  }
  } 
  /**
   * write an mp3 stream
   * @param s  the Stream to write to
   */
  private void writeMp3Stream(OutputStream os){
   throw new NotImplementedException();
  }
  /**
   * write an ogg vorbis stream
   * @param s  the Stream to write to
   */
  private void writeOggVorbisStream(OutputStream os){
   throw new NotImplementedException();
  }
  /**
   * write a monkey's audio file.
   * @param s        the stream to write to.
   * @throws IOException  if anything is wrong with the stream
   * @throws IllegalArgumentException if the number of channels is not 1 or 2.
   */
  private void writeApeStream(OutputStream s, int bits) throws IOException {
  if(nChannels<1||nChannels>2)
    throw new IllegalArgumentException("Monkey's Audio can only handle 1- or 2-channel audio data");
  double[] ar=samples[0].getArray();
  if(nChannels==1)
    nWriteApe(nativeSpace, s, bits, samples[0].size(), ar, null);
  else
    nWriteApe(nativeSpace, s, bits, samples[0].size(), ar, samples[1].getArray());
  }
 
  /**
   * write a flac audio file.
   * @param s        the stream to write to.
   * @throws IOException  if anything is wrong with the stream
   * @throws IllegalArgumentException if the number of channels is not 1 or 2.
   */
  private void writeFlacStream(OutputStream s, boolean ogg, int bits) throws IOException {
  if(nChannels<1||nChannels>2)
    throw new IllegalArgumentException("Monkey's Audio can only handle 1- or 2-channel audio data");
  double[] ar=samples[0].getArray();
  if(nChannels==1)
    nWriteFlac(nativeSpace, s, ogg, bits, samples[0].size(), ar, null);
  else
    nWriteFlac(nativeSpace, s, ogg, bits, samples[0].size(), ar, samples[1].getArray());
  }
 
  /**
   * write a standard windows compliant 16-bit integer wav file.
   * @param s        the stream to write to.
   * @throws IOException  if anything is wrong with the stream
   */
  private void writeWindowsStream(OutputStream s) throws IOException {
  int bytesPerFrame=nChannels*2;
  int dataLen=bytesPerFrame*samples[0].size();
  int headerLen=36;
  writeString(s, "RIFF");
  writeInt(s, dataLen+headerLen);
  writeString(s, "WAVE");
  writeString(s, "fmt ");
  writeInt(s, 16); // index 16 10h len of fmt header
  writeWord(s, 1); // format 1 = int PCM, index 20 14h
  writeWord(s, nChannels); // index 22 16h
  writeInt(s, this.wSampleRate); // index 24 18h
  writeInt(s, bytesPerFrame*wSampleRate); // index 28 1Ch
  writeWord(s, nChannels<<1); // bytes per sample index 32 20h
  writeWord(s, 16); // bits per sample index 34 22h
  writeString(s, "data");
  writeInt(s, dataLen); // index 40 28h
  for(int i=0; i<samples[0].size(); i++){
    for(DataList sample1:samples){
    double d=sample1.get(i);
    if(d<MIN16||d>MAX16)
      throw new IOException("Attempt to write non-normalized PCM File, value "+d+" at index "+i);
    }
    for(DataList sample:samples){
    writeWord(s, sample.get(i));
    }
  }
  }

  /**
   * write a windows compliant 24-bit integer wav file.
   * @param s        the stream to write to.
   * @throws IOException  if anything is wrong with the stream
   */
  private void write24WindowsStream(OutputStream s) throws IOException {
  int bytesPerFrame=nChannels*3;
  boolean pad=false;
  int dataLen=bytesPerFrame*samples[0].size();
  if((dataLen&1)!=0){
    pad=true;
    dataLen++;
  }
  int headerLen=36;
  writeString(s, "RIFF");
  writeInt(s, dataLen+headerLen);
  writeString(s, "WAVE");
  writeString(s, "fmt ");
  writeInt(s, 16); // len of fmt header
  // windows media player in some versions is overly picky and plays 24-bit files
  // only if they are announced as WAVE_FORMAT_EXTENSIBLE (format 2) files
  // other versions and all other players are happy with 1=int PCM and no header extension size,
  // we ignore the picky WMP versions here to make it play everywhere else.
  writeWord(s, 1); // format 1 = int PCM, -2 = extensible, 3 = float
  writeWord(s, nChannels);
  writeInt(s, this.wSampleRate);
  writeInt(s, bytesPerFrame*wSampleRate);
  writeWord(s, 3*nChannels); // bytes per sample
  writeWord(s, 24); // bits per sample
 
  // if we'd want to write WAVE_FMT_EXTENSIBLE for WMP, we'd have to add at least this:
  // writeWord(s, 0); // header extension size, needed for WMP to play 24-bit file.
  // writeString(s, "fact");
  // writeInt(s, 4);
  // writeInt(s, nChannels*samples[0].size());
 
 
  writeString(s, "data");
  writeInt(s, dataLen);

  for(int i=0; i<samples[0].size(); i++){
    for(DataList sample1:samples){
    double d=sample1.get(i);
    if(d<MIN24||d>MAX24)
      throw new IOException("Attempt to write non-normalized PCM File");
    }
    for(DataList sample:samples){
    writeInt24(s, sample.get(i));
    }
  }
  if(pad)
    writeByte(s, 0);
  }

  /**
   * write a windows compliant 32-bit integer wav file.
   * @param s        the stream to write to.
   * @throws IOException  if anything is wrong with the stream
   */
  private void write32WindowsStream(OutputStream s) throws IOException {
  int bytesPerFrame=nChannels*4;
  int dataLen=bytesPerFrame*samples[0].size();
  int headerLen=36;
  writeString(s, "RIFF");
  writeInt(s, dataLen+headerLen);
  writeString(s, "WAVE");
  writeString(s, "fmt ");
  writeInt(s, 16); // len of fmt header
  writeWord(s, 1); // format 1 = int PCM
  writeWord(s, nChannels);
  writeInt(s, this.wSampleRate);
  writeInt(s, bytesPerFrame*wSampleRate);
  writeWord(s, bytesPerFrame); // bytes per sample
  writeWord(s, 32); // bits per sample
  writeString(s, "data");
  writeInt(s, dataLen);

  for(int i=0; i<samples[0].size(); i++){
    for(DataList sample1:samples){
    double d=sample1.get(i);
    if(d<MIN32||d>MAX32)
      throw new IOException("Attempt to write non-normalized PCM File. Got "+d+", allowed "+MIN32+".."+MAX32);
    }
    for(DataList sample:samples){
    writeInt(s, sample.get(i));
    }
  }
  }

  /**
   * write a 32-bit float wav file.
   * @param s        the stream to write to.
   * @throws IOException  if anything is wrong with the stream
   */
  private void writeFloatWavStream(OutputStream s) throws IOException {
  int bytesPerFrame=nChannels*4;
  int dataLen=bytesPerFrame*samples[0].size();
  int headerLen=46+8+8+nChannels*(4+4); // PEAK chunk size is variable
  writeString(s, "RIFF");
  writeInt(s, dataLen+headerLen);
  writeString(s, "WAVE");
  writeString(s, "fmt ");
  writeInt(s, 18); // len of fmt header
  writeWord(s, 3); // format 3 = float/double PCM
  writeWord(s, nChannels);
  writeInt(s, this.wSampleRate);
  writeInt(s, bytesPerFrame*wSampleRate);
  writeWord(s, nChannels<<2); // bytes per sample
  writeWord(s, 32); // bits per sample
  writeWord(s, 0)// size of header extension field, see http://www-mmsp.ece.mcgill.ca/documents/audioformats/wave/wave.html
  writeString(s, "fact");
  writeInt(s, 4);
  writeInt(s, samples[0].size());
  writeString(s, "PEAK");
  writeInt(s, 8+8*nChannels); // size of PEAK chunk
  writeInt(s, 1); // int peak format version
  writeInt(s, 0x4b0ab342); // float time(NULL)
  for(int i=0; i<nChannels; i++){
    int pos=getPeakPos(i);
    writeFloat(s, ((float)samples[i].get(pos))/32768f); // per channel: peak value, peak position
    writeInt(s, pos); // int
  }
  writeString(s, "data");
  writeInt(s, dataLen);
  for(int i=0; i<samples[0].size(); i++){
    for(DataList sample1:samples){
    double d=sample1.get(i);
    if(d<-Float.MAX_VALUE||d>Float.MAX_VALUE)
      throw new IOException("Attempt to write non-normalized PCM File");
    }
    for(DataList sample:samples){
    writeFloat(s, sample.get(i));
    }
  }
  }

  /**
   * write a 64-bit double precision float wav file.
   * @param s        the stream to write to.
   * @throws IOException  if anything is wrong with the stream
   */
  private void writeDoubleWavStream(OutputStream s) throws IOException {
  int bytesPerFrame=nChannels*8;
  int dataLen=bytesPerFrame*samples[0].size();
  int headerLen=46+// up to before PEAK
    8 /* DATA + len */+8+nChannels*(4+4); // PEAK chunk size is variable
  writeString(s, "RIFF");
  writeInt(s, dataLen+headerLen);
  writeString(s, "WAVE");
  writeString(s, "fmt ");
  writeInt(s, 18); // len of fmt header
  writeWord(s, 3); // format 3 = float/double PCM
  writeWord(s, nChannels);
  writeInt(s, this.wSampleRate);
  writeInt(s, bytesPerFrame*wSampleRate);
  writeWord(s, nChannels<<3); // bytes per sample
  writeWord(s, 64); // bits per sample
  writeWord(s, 0)// size of header extension field, see http://www-mmsp.ece.mcgill.ca/documents/audioformats/wave/wave.html
  writeString(s, "fact");
  writeInt(s, 4);
  writeInt(s, samples[0].size());
  writeString(s, "PEAK");
  writeInt(s, 8+8*nChannels); // size of PEAK chunk
  writeInt(s, 1); // int peak format version
  writeInt(s, 0x4b0ab342); // float time(NULL)
  for(int i=0; i<nChannels; i++){
    int pos=getPeakPos(i);
    writeFloat(s, ((float)samples[i].get(pos))/32768f); // per channel: peak value, peak position
    // nota bene: ridiculously, exactly this peak value field can overflow
    writeInt(s, pos); // int
  }
  writeString(s, "data");
  writeInt(s, dataLen);
  for(int i=0; i<samples[0].size(); i++){
    for(DataList sample:samples){
    writeDouble(s, sample.get(i));
    }
  }
  }

  /**
   * write a 8-bit wav file.
   * @param s        the stream to write to.
   * @throws IOException  if anything is wrong with the stream
   */
  private void writeByteWavStream(OutputStream s) throws IOException {
  int bytesPerFrame=nChannels;
  int dataLen=bytesPerFrame*samples[0].size();
  int headerLen=36;
  writeString(s, "RIFF");
  writeInt(s, dataLen+headerLen);
  writeString(s, "WAVE");
  writeString(s, "fmt ");
  writeInt(s, 16); // len of fmt header
  writeWord(s, 1); // format 3 = float/double PCM
  writeWord(s, nChannels);
  writeInt(s, this.wSampleRate);
  writeInt(s, bytesPerFrame*wSampleRate);
  writeWord(s, nChannels); // bytes per sample
  writeWord(s, 8); // bits per sample
  writeString(s, "data");
  writeInt(s, dataLen);
  for(int i=0; i<samples[0].size(); i++){
    for(DataList sample:samples){
    writeByte(s, sample.get(i));
    }
  }
  }

  /**
   * Writes a String to the output stream, without adding a terminating zero byte
   * @param s  the stream to write to
   * @param ss  the string value to write
   * @throws IOException
   */
  private static void writeString(OutputStream s, String ss) throws IOException {
  byte[] ls=new byte[ss.length()];
  int i=0;
  for(char c:ss.toCharArray()){
    ls[i++]=(byte)c;
  }
  s.write(ls);
  }

  /**
   * writes a double value as 32 bit integer
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeInt(OutputStream s, double d) throws IOException {
  /* prevent wrap-around, force clipping on excess */
  int i=(int)(d*SC32);
  if(d>SC32-1)
    i=2147483647;
  if(d<-SC32)
    i=-2147483648;
  writeInt(s, i);
  }

  /**
   * writes an integer value as 32 bit integer
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeInt(OutputStream s, int i) throws IOException {
  s.write((byte)(i&255));
  s.write((byte)((i>>8)&255));
  s.write((byte)((i>>16)&255));
  s.write((byte)((i>>24)&255));
  }
 
  /**
   * writes a double value as 24 bit integer
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeInt24(OutputStream s, double d) throws IOException {
  /* prevent wrap-around, force clipping on excess */
  int i=(int)(d*SC24);
  if(i>SC24-1)
    i=SC24I-1;
  if(i<-SC24)
    i=-SC24I;
  writeInt24(s, i);
  }

  /**
   * writes an integer value as 24 bit integer
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeInt24(OutputStream s, int i) throws IOException {
  s.write((byte)(i&255));
  s.write((byte)((i>>8)&255));
  s.write((byte)((i>>16)&255));
  }

  /**
   * writes a double value as 64 bit integer
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  @SuppressWarnings("unused")
  private static void writeLong(OutputStream s, double d) throws IOException {
  /* prevent wrap-around, force clipping on excess */
  long l=(long)(d*SC64);
  if(d>SC64-1)
    l=9223372036854775807L;
  if(d<-SC64)
    l=-9223372036854775808L;
  writeLong(s, l);
  }

  /**
   * writes a long value as 64 bit integer
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeLong(OutputStream s, long l) throws IOException {
  s.write((byte)(l&255));
  s.write((byte)((l>>8)&255));
  s.write((byte)((l>>16)&255));
  s.write((byte)((l>>24)&255));
  s.write((byte)((l>>32)&255));
  s.write((byte)((l>>40)&255));
  s.write((byte)((l>>48)&255));
  s.write((byte)((l>>56)&255));
  }

  /**
   * writes a double value as 16 bit integer
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeWord(OutputStream s, double _d) throws IOException {
  /* prevent wrap-around, force clipping on excess */
  double d=_d*32768;
  if(d<-32768)
    d=-32768;
  else if(d>32767)
    d=32767;
  writeWord(s, (int)d);
  }

  /**
   * writes an integer value as 16 bit integer
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeWord(OutputStream s, int i) throws IOException {
  writeByte(s, (byte)(i&255));
  writeByte(s, (byte)((i>>8)&255));
  }

  /**
   * Writes a double value as 8 bit unsigned integer.
   * For old 8-bit file signal values.
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeByte(OutputStream s, double _d) throws IOException {
  /* prevent wrap around, force clipping on excess */
  double d=_d*128;
  if(d<-128)
    d=-128;
  else if(d>127)
    d=127;
  writeByte(s, (int)d+128);
  }

  /**
   * writes an integer value as 8 bit integer
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeByte(OutputStream s, int i) throws IOException {
  s.write((byte)(i&255));
  }

  /**
   * writes a double value as 32 bit float
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeFloat(OutputStream s, double d) throws IOException {
  /*
   * wrong order int i=Float.floatToIntBits(f); byte[] lc = new byte[4]; lc[3] = (byte) (i & 255); lc[2] = (byte) ((i
   * >> 8) & 255); lc[1] = (byte) ((i >> 16) & 255); lc[0] = (byte) ((i >> 24) & 255); s.write(lc);
   */
  /* prevent wrap around, force clipping on excess */
  Float f;
  if(d<-Float.MAX_VALUE)
    f=-Float.MAX_VALUE;
  else if(d>Float.MAX_VALUE)
    f=Float.MAX_VALUE;
  else
    f=(float)d;
  writeInt(s, Float.floatToIntBits(f));
  }

  /**
   * writes a float value as 32 bit float
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeFloat(OutputStream s, float f) throws IOException {
  writeInt(s, Float.floatToIntBits(f));
  }

  /**
   * writes a double value as 64 bit double precision float
   * @param s  the stream to write to
   * @param d  the value to write
   * @throws IOException
   */
  private static void writeDouble(OutputStream s, double d) throws IOException {
  /*
   * wrong order long l=Double.doubleToLongBits(d); byte[] lc = new byte[8]; lc[7] = (byte) (l & 255); lc[6] = (byte)
   * ((l >> 8) & 255); lc[5] = (byte) ((l >> 16) & 255); lc[4] = (byte) ((l >> 24) & 255); lc[3] = (byte) ((l >> 32) &
   * 255); lc[2] = (byte) ((l >> 40) & 255); lc[1] = (byte) ((l >> 48) & 255); lc[0] = (byte) ((l >> 56) & 255);
   * s.write(lc);
   */
  writeLong(s, Double.doubleToLongBits(d));
  }

  /**
   * converts a variable-size byte array to the corresponding int value
   * @param bytes
   * @return
   */
  private static int getInt(byte[] bytes) {
  if(bytes.length==2){
    return ((bytes[1]&255)<<8)|(bytes[0]&255);
  }else if(bytes.length==4){
    return ((((((bytes[3]&255)<<8)|(bytes[2]&255))<<8)|(bytes[1]&255))<<8)|(bytes[0]&255);
  }else
    return 0;
  }

  public static OutputFile readStream(InputStream s) {
  byte lc[]=new byte[4];
  byte wc[]=new byte[2];
  OutputFile w=null;
  try{
    s.read(lc);
    if(lc[0]!='R'||lc[1]!='I'||lc[2]!='F'||lc[3]!='F')
    return null;
    s.read(lc);
    int len=getInt(lc)-28; // size of the data chunks
    if(len<=0)
    return null;
    s.read(lc);
    if(lc[0]!='W'||lc[1]!='A'||lc[2]!='V'||lc[3]!='E')
    return null;
    s.read(lc);
    if(lc[0]!='f'||lc[1]!='m'||lc[2]!='t')
    return null;
    s.read(lc);
    int fmtsize=getInt(lc);
    if(fmtsize<16)
    return null;
    s.read(wc);
    int wFormat=getInt(wc);
    if(wFormat!=1)
    return null;
    s.read(wc);
    int wChannels=getInt(wc);
    w=new OutputFile(wChannels);
    s.read(lc);
    w.wSampleRate=getInt(lc);
    s.read(lc);
    /* int bytesPerSec = */
    getInt(lc);
    s.read(wc);
    /* int bytesPerSample = */
    getInt(wc);
    s.read(wc);
    int bitsPerSample=getInt(wc);
    int rlen=0;
    int l;
    while(rlen<len){
    l=w.readChunk(s, bitsPerSample);
    if(l<=0)
      break;
    rlen+=l;
    }

  }catch(IOException ex){
    //
  }
  return w;
  }

  private int readChunk(InputStream s, int bitsPerSample) {
  byte lc[]=new byte[4];
  int len;
  try{
    s.read(lc);
    boolean isData=(lc[0]=='d')&&(lc[1]==lc[3])&&(lc[2]=='t')&&(lc[1]=='a');
    s.read(lc);
    len=getInt(lc);
    if((len&1)!=0)
    len++;
    if(isData){
    // read in the samples
    int bytesPerSample=((bitsPerSample+7)>>3);
    int nFrames=len/nChannels/bytesPerSample;
    int bytesPerFrame=bytesPerSample*nChannels;
    byte[] frame=new byte[bytesPerFrame];
    for(int i=0; i<nFrames; i++){
      s.read(frame);
      for(int c=0; c<nChannels; c++)
      switch(bytesPerSample){
        case 1: // 8 bit int with offset
        samples[c].add((double)(frame[c]&255)-128);
        break;
        case 2: // 16 bit int
        int v=((frame[1+c*2]&255)<<8)+(frame[c*2]&255);
        if((v&32768)!=0)
          v-=65536;
        samples[c].add(v);
        break;
        case 3: // 24 bit int
        v=((frame[2+c*3]&255)<<16)+((frame[1+c*3]&255)<<8)+(frame[c*3]&255);
        if((v&0x800000)!=0)
          v-=0x1000000;
        samples[c].add(v);
        break;
        case 4: // float
        v=((frame[3+c*4]&255)<<24)+((frame[2+c*4]&255)<<16)+((frame[1+c*4]&255)<<8)
          +(frame[c*4]&255);
        samples[c].add(Float.intBitsToFloat(v));
        break;
        case 8: // double
        long ld=(((long)(frame[7+c*8]&255))<<56)+(((long)(frame[6+c*8]&255))<<48)
          +(((long)(frame[5+c*8]&255))<<40)+(((long)(frame[4+c*8]&255))<<32)
          +((frame[3+c*8]&255)<<24)+((frame[2+c*8]&255)<<16)+((frame[1+c*8]&255)<<8)
          +(frame[c*8]&255);
        double d=Double.longBitsToDouble(ld);
        samples[c].add(d);
        break;
      }
    }
    }else{
    // just skip this chunk
    s.skip(len);
    }
  }catch(IOException ex){
    return -1;
  }
  return len+8;
  }

  /////////////////////////////////////////////////////////////////////////////
  // standard getters/setters
  /////////////////////////////////////////////////////////////////////////////

 
  public int getWSampleRate() {
  return wSampleRate;
  }

  public void setWSampleRate(int wSampleRate) {
  this.wSampleRate=wSampleRate;
  }

  public int getNChannels() {
  return nChannels;
  }

  public int getNSamples() {
  return samples[0].size();
  }

  public DataList getChannel(int ch) {
  if(ch<0||ch>=nChannels)
    throw new IllegalArgumentException("Channel "+ch+" does not exist");
  return samples[ch];
  }

  public void setOutputFileName(String name) {
  this.outputFileName=name;
  }

  public String getOutputFileName() {
  return outputFileName;
  }

  private int getPeakPos(int channel) {
  if(channel<0||channel>nChannels)
    return 0;
  DataList dd=samples[channel];
  double d=-Double.MAX_VALUE;
  int ix=0;
  for(int i=0;i<dd.size();i++){
    double v=dd.get(i);
    if(d<v){
    d=v;
    ix=i;
    }
    i++;
  }
  return ix;
  }

  /////////////////////////////////////////////////////////////////////////////
  // ProcessElement interface
  /////////////////////////////////////////////////////////////////////////////
 
  @Override
  public ReadOnlyMap<Integer, ValueType> getDestinationTypes() {
  return srcMap;
  }

  @Override
  public void setSource(int connectionIndex, NamedSource source,
            int sourceIndex) throws UnknownConnectionException,
      TypeMismatchException {
  if(connectionIndex<=0&&(-connectionIndex<nChannels)){
    this.sources[-connectionIndex]=new SourceStore(source, sourceIndex);
  }
  sourceStoreMap.put(connectionIndex, new SourceStore(source, sourceIndex));
  }

  @Override
  public ReadOnlyMap<Integer, ValueType> getSourceTypes() {
  return srcMap;
  }

  @Override
  public double getValue(int index) {
  // this element provides no sources yet
  throw new IllegalStateException("");
  }

  @Override
  public void advanceOutput() {
  // nothing to do
  }

  @Override
  public void advanceState() {
  for(int i=0; i<nChannels; i++){
    if(sources[i]!=null)
    getChannel(i).add(sources[i].getValue());
    else
    getChannel(i).add(0d);
  }
  }

  @Override
  public String getAbstractName() {
  return abstractName;
  }

  @Override
  public String getInstanceName() {
  return instanceName;
  }

  @Override
  public void setAbstractName(String abstractName) {
  this.abstractName=abstractName;
  }

  @Override
  public void setInstanceName(String instanceName) {
  this.instanceName=instanceName;
  }

  @Override
  public long getNativeSpace() {
  return nativeSpace;
  }

  // private void cleanupSubObjects(){
  //
  // }

  @Override
  public ReadOnlyMap<Integer, SourceStore> getSourceMap() {
  return sourceStoreMap;
  }

  /**
   * @see de.maramuse.soundcomp.process.ProcessElement#clone()
   *
   *      But: this ProcessElement is usually not for use in single events
   *      Should we throw an Exception on cloning attempt?
   *      Maybe not, as we might have "voice templates" later on.
   */
  @Override
  public OutputFile clone() {
  OutputFile c=new OutputFile(nChannels);
  c.abstractName=abstractName;
  return c;
  }

  /*
   * (non-Javadoc)
   *
   * @see de.maramuse.soundcomp.process.ProcessElement#outputsByName()
   */
  @Override
  public ReadOnlyMap<String, Parameter> outputsByName() {
  return outputsMap;
  }

  /*
   * (non-Javadoc)
   *
   * @see de.maramuse.soundcomp.process.ProcessElement#inputsByName()
   */
  @Override
  public ReadOnlyMap<String, Parameter> inputsByName() {
  return inputsMap;
  }
 
  private native void nWriteApe(long _nativeSpace, OutputStream s, int bits, int _samples, double[] left, double[] right);
  private native void nWriteFlac(long _nativeSpace, OutputStream s, boolean ogg, int bits, int _samples, double[] left, double[] right);
}
TOP

Related Classes of de.maramuse.soundcomp.files.OutputFile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.