From 9fee0ec4ca0c5b7a334cc29dbb58e76c7a4c736e Mon Sep 17 00:00:00 2001 From: Michiel Van Der Kolk Date: Mon, 11 Jul 2005 15:42:37 +0000 Subject: Songdb java version, source. only 1.5 compatible git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7101 a1c6a512-1295-4272-9138-f99709370657 --- .../org/tritonus/share/sampled/AudioFileTypes.java | 155 +++ .../org/tritonus/share/sampled/AudioFormatSet.java | 155 +++ .../org/tritonus/share/sampled/AudioFormats.java | 131 +++ .../tritonus/share/sampled/AudioSystemShadow.java | 115 ++ songdbj/org/tritonus/share/sampled/AudioUtils.java | 181 +++ songdbj/org/tritonus/share/sampled/Encodings.java | 183 +++ .../tritonus/share/sampled/FloatSampleBuffer.java | 734 ++++++++++++ .../tritonus/share/sampled/FloatSampleTools.java | 696 +++++++++++ .../org/tritonus/share/sampled/TAudioFormat.java | 110 ++ .../tritonus/share/sampled/TConversionTool.java | 1224 ++++++++++++++++++++ .../org/tritonus/share/sampled/TVolumeUtils.java | 55 + .../TAsynchronousFilteredAudioInputStream.java | 256 ++++ .../share/sampled/convert/TAudioInputStream.java | 120 ++ .../convert/TEncodingFormatConversionProvider.java | 129 +++ .../sampled/convert/TFormatConversionProvider.java | 170 +++ .../convert/TMatrixFormatConversionProvider.java | 182 +++ .../convert/TSimpleFormatConversionProvider.java | 367 ++++++ .../TSynchronousFilteredAudioInputStream.java | 271 +++++ .../tritonus/share/sampled/convert/package.html | 17 + .../share/sampled/file/AudioOutputStream.java | 113 ++ .../sampled/file/HeaderlessAudioOutputStream.java | 58 + .../share/sampled/file/TAudioFileFormat.java | 113 ++ .../share/sampled/file/TAudioFileReader.java | 510 ++++++++ .../share/sampled/file/TAudioFileWriter.java | 484 ++++++++ .../share/sampled/file/TAudioOutputStream.java | 197 ++++ .../share/sampled/file/TDataOutputStream.java | 79 ++ .../sampled/file/THeaderlessAudioFileWriter.java | 84 ++ .../sampled/file/TNonSeekableDataOutputStream.java | 109 ++ .../sampled/file/TSeekableDataOutputStream.java | 86 ++ .../org/tritonus/share/sampled/file/package.html | 18 + .../share/sampled/mixer/TBaseDataLine.java | 107 ++ .../share/sampled/mixer/TBooleanControl.java | 128 ++ .../org/tritonus/share/sampled/mixer/TClip.java | 340 ++++++ .../share/sampled/mixer/TCompoundControl.java | 90 ++ .../share/sampled/mixer/TCompoundControlType.java | 55 + .../share/sampled/mixer/TControlController.java | 98 ++ .../share/sampled/mixer/TControllable.java | 62 + .../tritonus/share/sampled/mixer/TDataLine.java | 304 +++++ .../tritonus/share/sampled/mixer/TEnumControl.java | 92 ++ .../share/sampled/mixer/TFloatControl.java | 134 +++ .../org/tritonus/share/sampled/mixer/TLine.java | 362 ++++++ .../org/tritonus/share/sampled/mixer/TMixer.java | 506 ++++++++ .../tritonus/share/sampled/mixer/TMixerInfo.java | 56 + .../share/sampled/mixer/TMixerProvider.java | 240 ++++ .../org/tritonus/share/sampled/mixer/TPort.java | 77 ++ .../tritonus/share/sampled/mixer/TSoftClip.java | 318 +++++ .../org/tritonus/share/sampled/mixer/package.html | 14 + songdbj/org/tritonus/share/sampled/package.html | 10 + 48 files changed, 10095 insertions(+) create mode 100644 songdbj/org/tritonus/share/sampled/AudioFileTypes.java create mode 100644 songdbj/org/tritonus/share/sampled/AudioFormatSet.java create mode 100644 songdbj/org/tritonus/share/sampled/AudioFormats.java create mode 100644 songdbj/org/tritonus/share/sampled/AudioSystemShadow.java create mode 100644 songdbj/org/tritonus/share/sampled/AudioUtils.java create mode 100644 songdbj/org/tritonus/share/sampled/Encodings.java create mode 100644 songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java create mode 100644 songdbj/org/tritonus/share/sampled/FloatSampleTools.java create mode 100644 songdbj/org/tritonus/share/sampled/TAudioFormat.java create mode 100644 songdbj/org/tritonus/share/sampled/TConversionTool.java create mode 100644 songdbj/org/tritonus/share/sampled/TVolumeUtils.java create mode 100644 songdbj/org/tritonus/share/sampled/convert/TAsynchronousFilteredAudioInputStream.java create mode 100644 songdbj/org/tritonus/share/sampled/convert/TAudioInputStream.java create mode 100644 songdbj/org/tritonus/share/sampled/convert/TEncodingFormatConversionProvider.java create mode 100644 songdbj/org/tritonus/share/sampled/convert/TFormatConversionProvider.java create mode 100644 songdbj/org/tritonus/share/sampled/convert/TMatrixFormatConversionProvider.java create mode 100644 songdbj/org/tritonus/share/sampled/convert/TSimpleFormatConversionProvider.java create mode 100644 songdbj/org/tritonus/share/sampled/convert/TSynchronousFilteredAudioInputStream.java create mode 100644 songdbj/org/tritonus/share/sampled/convert/package.html create mode 100644 songdbj/org/tritonus/share/sampled/file/AudioOutputStream.java create mode 100644 songdbj/org/tritonus/share/sampled/file/HeaderlessAudioOutputStream.java create mode 100644 songdbj/org/tritonus/share/sampled/file/TAudioFileFormat.java create mode 100644 songdbj/org/tritonus/share/sampled/file/TAudioFileReader.java create mode 100644 songdbj/org/tritonus/share/sampled/file/TAudioFileWriter.java create mode 100644 songdbj/org/tritonus/share/sampled/file/TAudioOutputStream.java create mode 100644 songdbj/org/tritonus/share/sampled/file/TDataOutputStream.java create mode 100644 songdbj/org/tritonus/share/sampled/file/THeaderlessAudioFileWriter.java create mode 100644 songdbj/org/tritonus/share/sampled/file/TNonSeekableDataOutputStream.java create mode 100644 songdbj/org/tritonus/share/sampled/file/TSeekableDataOutputStream.java create mode 100644 songdbj/org/tritonus/share/sampled/file/package.html create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TBaseDataLine.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TBooleanControl.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TClip.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TCompoundControl.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TCompoundControlType.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TControlController.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TControllable.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TDataLine.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TEnumControl.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TFloatControl.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TLine.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TMixer.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TMixerInfo.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TMixerProvider.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TPort.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/TSoftClip.java create mode 100644 songdbj/org/tritonus/share/sampled/mixer/package.html create mode 100644 songdbj/org/tritonus/share/sampled/package.html (limited to 'songdbj/org/tritonus/share/sampled') diff --git a/songdbj/org/tritonus/share/sampled/AudioFileTypes.java b/songdbj/org/tritonus/share/sampled/AudioFileTypes.java new file mode 100644 index 0000000000..45e4a0ffbc --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/AudioFileTypes.java @@ -0,0 +1,155 @@ +/* + * AudioFileTypes.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Florian Bomers + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +package org.tritonus.share.sampled; + +import javax.sound.sampled.AudioFileFormat; +import org.tritonus.share.StringHashedSet; +import org.tritonus.share.TDebug; + +/** + * This class is a proposal for generic handling of audio file format types. + * The main purpose is to provide a standardized way of + * implementing audio file format types. Like this, file types + * are only identified by their String name, and not, as currently, + * by their object instance. + *

+ * A standard registry of file type names will + * be maintained by the Tritonus team. + *

+ * In a specification request to JavaSoft, these static methods + * could be integrated intoAudioFileFormat.Type. The static + * instances of AIFF, AIFC, AU, SND, and WAVE types in class + * AudioFileFormat.Type should be retrieved + * using this method, too (internally).
+ * At best, the protected constructor of that class + * should also be replaced to be a private constructor. + * Like this it will be prevented that developers create + * instances of Type, which causes problems with the + * equals method. In fact, the equals method should be redefined anyway + * so that it compares the names and not the objects. + *

+ * Also, the file name extension should be deprecated and moved + * to AudioFileFormat. There are some file formats + * which have varying extensions depending, e.g. on the encoding. + * An example for this is MPEG: the special encoding Mpeg 1, layer 3 + * has the extension mp3, whereas other Mpeg files use mpeg or mpg.
+ * This could be implemented with 2 methods in AudioFileFormat: + *

  1. String[] getFileExtensions(): returns all usable extensions + * for this file. + *
  2. String getDefaultFileExtension(): returns the preferred extension. + *
+ * + * @author Florian Bomers + */ +public class AudioFileTypes extends AudioFileFormat.Type { + + /** contains all known types */ + private static StringHashedSet types = new StringHashedSet(); + + // initially add the standard types + static { + types.add(AudioFileFormat.Type.AIFF); + types.add(AudioFileFormat.Type.AIFC); + types.add(AudioFileFormat.Type.AU); + types.add(AudioFileFormat.Type.SND); + types.add(AudioFileFormat.Type.WAVE); + } + + AudioFileTypes(String name, String ext) { + super(name, ext); + } + + /** + * Use this method to retrieve an instance of + * AudioFileFormat.Type of the specified + * name. If no type of this name is in the internally + * maintained list, null is returned. + *

+ * This method is supposed to be used by user programs. + *

+ * In order to assure a well-filled internal list, + * call AudioSystem.getAudioFileTypes() + * at initialization time. + * + * @see #getType(String, String) + */ + public static AudioFileFormat.Type getType(String name) { + return getType(name, null); + } + + /** + * Use this method to retrieve an instance of + * AudioFileFormat.Type of the specified + * name. If it does not exist in the internal list + * of types, a new type is created and returned. + * If it a type of that name already exists (regardless + * of extension), it is returned. In this case it can + * not be guaranteed that the extension is the same as + * passed as parameter.
+ * If extension is null, + * this method returns null if the + * type of the specified name does not exist in the + * internal list. + *

+ * This method is supposed to be used by file providers. + * Every file reader and file writer provider should + * exclusively use this method for retrieving instances + * of AudioFileFormat.Type. + */ + public static AudioFileFormat.Type getType(String name, String extension) { + AudioFileFormat.Type res=(AudioFileFormat.Type) types.get(name); + if (res==null) { + // it is not already in the string set. + if (extension==null) { + return null; + } + // Create a new type instance. + res=new AudioFileTypes(name, extension); + // and save it for the future + types.add(res); + } + return res; + } + + /** + * Tests for equality of 2 file types. They are equal when their names match. + *

+ * This function should be AudioFileFormat.Type.equals and must + * be considered as a temporary workaround until it flows into the + * JavaSound API. + */ + // IDEA: create a special "NOT_SPECIFIED" file type + // and a AudioFileFormat.Type.matches method. + public static boolean equals(AudioFileFormat.Type t1, AudioFileFormat.Type t2) { + return t2.toString().equals(t1.toString()); + } + +} + +/*** AudioFileTypes.java ***/ + diff --git a/songdbj/org/tritonus/share/sampled/AudioFormatSet.java b/songdbj/org/tritonus/share/sampled/AudioFormatSet.java new file mode 100644 index 0000000000..8d89541d77 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/AudioFormatSet.java @@ -0,0 +1,155 @@ +/* + * AudioFormatSet.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Florian Bomers + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import javax.sound.sampled.AudioFormat; + +import org.tritonus.share.ArraySet; +import org.tritonus.share.sampled.AudioFormats; + + +/** + * A set where the elements are uniquely referenced by + * AudioFormats.equals rather than their object reference. + * No 2 equal AudioFormats can exist in the set. + *

+ * This class provide convenience methods like + * getAudioFormat(AudioFormat) and + * matches(AudioFormat). + *

+ * The contains(Object elem) and get(Object elem) + * fail, if elem is not an instance of AudioFormat. + *

+ * You shouldn't use the ArrayList specific functions + * like those that take index parameters. + *

+ * It is not possible to add null elements. + *

+ * Currently, the methods equals(.,.) and matches(.,.) of + * class AudioFormats are used. Let's hope that they will + * be integrated into AudioFormat. + */ + +public class AudioFormatSet extends ArraySet +{ + protected static final AudioFormat[] EMPTY_FORMAT_ARRAY = new AudioFormat[0]; + + public AudioFormatSet() { + super(); + } + + public AudioFormatSet(Collection c) { + super(c); + } + + public boolean add(AudioFormat elem) { + if (elem==null || !(elem instanceof AudioFormat)) { + return false; + } + return super.add(elem); + } + + public boolean contains(AudioFormat elem) { + if (elem==null || !(elem instanceof AudioFormat)) { + return false; + } + AudioFormat comp=(AudioFormat) elem; + Iterator it=iterator(); + while (it.hasNext()) { + if (AudioFormats.equals(comp, (AudioFormat) it.next())) { + return true; + } + } + return false; + } + + public AudioFormat get(AudioFormat elem) { + if (elem==null || !(elem instanceof AudioFormat)) { + return null; + } + AudioFormat comp=(AudioFormat) elem; + Iterator it=iterator(); + while (it.hasNext()) { + AudioFormat thisElem=(AudioFormat) it.next(); + if (AudioFormats.equals(comp, thisElem)) { + return thisElem; + } + } + return null; + } + + public AudioFormat getAudioFormat(AudioFormat elem) { + return (AudioFormat) get(elem); + } + + /** + * Checks whether this Set contains an AudioFormat + * that matches elem. + * The first matching format is returned. If no element + * matches elem, null is returned. + *

+ * @see AudioFormats#matches(AudioFormat, AudioFormat) + */ + public AudioFormat matches(AudioFormat elem) { + if (elem==null) { + return null; + } + Iterator it=iterator(); + while (it.hasNext()) { + AudioFormat thisElem=(AudioFormat) it.next(); + if (AudioFormats.matches(elem, thisElem)) { + return thisElem; + } + } + return null; + } + + + // $$mp: TODO: remove; should be obsolete + public AudioFormat[] toAudioFormatArray() { + return (AudioFormat[]) toArray(EMPTY_FORMAT_ARRAY); + } + + + public void add(int index, AudioFormat element) { + throw new UnsupportedOperationException("unsupported"); + } + + public AudioFormat set(int index, AudioFormat element) { + throw new UnsupportedOperationException("unsupported"); + } +} + +/*** AudioFormatSet.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/AudioFormats.java b/songdbj/org/tritonus/share/sampled/AudioFormats.java new file mode 100644 index 0000000000..41ac3f37c7 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/AudioFormats.java @@ -0,0 +1,131 @@ +/* + * AudioFormats.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999,2000 by Matthias Pfisterer + * Copyright (c) 1999 by Florian Bomers + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; + + + +public class AudioFormats +{ + private static boolean doMatch(int i1, int i2) + { + return i1 == AudioSystem.NOT_SPECIFIED + || i2 == AudioSystem.NOT_SPECIFIED + || i1 == i2; + } + + + + private static boolean doMatch(float f1, float f2) + { + return f1 == AudioSystem.NOT_SPECIFIED + || f2 == AudioSystem.NOT_SPECIFIED + || Math.abs(f1 - f2) < 1.0e-9; + } + + + + /** + * Tests whether 2 AudioFormats have matching formats. + * A field matches when it is AudioSystem.NOT_SPECIFIED in + * at least one of the formats or the field is the same + * in both formats.
+ * Exceptions: + *

+ * This is a proposition to be used as AudioFormat.matches. + * It can therefore be considered as a temporary workaround. + */ + // IDEA: create a special "NOT_SPECIFIED" encoding + // and a AudioFormat.Encoding.matches method. + public static boolean matches(AudioFormat format1, + AudioFormat format2) + { + //$$fb 19 Dec 99: endian must be checked, too. + // + // we do have a problem with redundant elements: + // e.g. + // encoding=ALAW || ULAW -> bigEndian and samplesizeinbits don't matter + // sample size in bits == 8 -> bigEndian doesn't matter + // sample size in bits > 8 -> PCM is always signed. + // This is an overall issue in JavaSound, I think. + // At present, it is not consistently implemented to support these + // redundancies and implicit definitions + // + // As a workaround of this issue I return in the converters + // all combinations, e.g. for ULAW I return bigEndian and !bigEndian formats. +/* old version +*/ + // as proposed by florian + return format1.getEncoding().equals(format2.getEncoding()) + && (format2.getSampleSizeInBits()<=8 + || format1.getSampleSizeInBits()==AudioSystem.NOT_SPECIFIED + || format2.getSampleSizeInBits()==AudioSystem.NOT_SPECIFIED + || format1.isBigEndian()==format2.isBigEndian()) + && doMatch(format1.getChannels(),format2.getChannels()) + && doMatch(format1.getSampleSizeInBits(), format2.getSampleSizeInBits()) + && doMatch(format1.getFrameSize(), format2.getFrameSize()) + && doMatch(format1.getSampleRate(), format2.getSampleRate()) + && doMatch(format1.getFrameRate(),format2.getFrameRate()); + } + + /** + * Tests for exact equality of 2 AudioFormats. + * This is the behaviour of AudioFormat.matches in JavaSound 1.0. + *

+ * This is a proposition to be used as AudioFormat.equals. + * It can therefore be considered as a temporary workaround. + */ + public static boolean equals(AudioFormat format1, + AudioFormat format2) + { + return format1.getEncoding().equals(format2.getEncoding()) + && format1.getChannels() == format2.getChannels() + && format1.getSampleSizeInBits() == format2.getSampleSizeInBits() + && format1.getFrameSize() == format2.getFrameSize() + && (Math.abs(format1.getSampleRate() - format2.getSampleRate()) < 1.0e-9) + && (Math.abs(format1.getFrameRate() - format2.getFrameRate()) < 1.0e-9); + } + +} + + + +/*** AudioFormats.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/AudioSystemShadow.java b/songdbj/org/tritonus/share/sampled/AudioSystemShadow.java new file mode 100644 index 0000000000..70b4e9ebd7 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/AudioSystemShadow.java @@ -0,0 +1,115 @@ +/* + * AudioSystemShadow.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999, 2000 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled; + +import java.io.File; +import java.io.OutputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; + +import org.tritonus.share.sampled.file.AudioOutputStream; +import org.tritonus.share.sampled.file.TDataOutputStream; +import org.tritonus.share.sampled.file.TSeekableDataOutputStream; +import org.tritonus.share.sampled.file.TNonSeekableDataOutputStream; +import org.tritonus.sampled.file.AiffAudioOutputStream; +import org.tritonus.sampled.file.AuAudioOutputStream; +import org.tritonus.sampled.file.WaveAudioOutputStream; + + + +/** Experminatal area for AudioSystem. + * This class is used to host features that may become part of the + * Java Sound API (In which case they will be moved to AudioSystem). + */ +public class AudioSystemShadow +{ + public static TDataOutputStream getDataOutputStream(File file) + throws IOException + { + return new TSeekableDataOutputStream(file); + } + + + + public static TDataOutputStream getDataOutputStream(OutputStream stream) + throws IOException + { + return new TNonSeekableDataOutputStream(stream); + } + + + + // TODO: lLengthInBytes actually should be lLengthInFrames (design problem of A.O.S.) + public static AudioOutputStream getAudioOutputStream(AudioFileFormat.Type type, AudioFormat audioFormat, long lLengthInBytes, TDataOutputStream dataOutputStream) + { + AudioOutputStream audioOutputStream = null; + + if (type.equals(AudioFileFormat.Type.AIFF) || + type.equals(AudioFileFormat.Type.AIFF)) + { + audioOutputStream = new AiffAudioOutputStream(audioFormat, type, lLengthInBytes, dataOutputStream); + } + else if (type.equals(AudioFileFormat.Type.AU)) + { + audioOutputStream = new AuAudioOutputStream(audioFormat, lLengthInBytes, dataOutputStream); + } + else if (type.equals(AudioFileFormat.Type.WAVE)) + { + audioOutputStream = new WaveAudioOutputStream(audioFormat, lLengthInBytes, dataOutputStream); + } + return audioOutputStream; + } + + + + public static AudioOutputStream getAudioOutputStream(AudioFileFormat.Type type, AudioFormat audioFormat, long lLengthInBytes, File file) + throws IOException + { + TDataOutputStream dataOutputStream = getDataOutputStream(file); + AudioOutputStream audioOutputStream = getAudioOutputStream(type, audioFormat, lLengthInBytes, dataOutputStream); + return audioOutputStream; + } + + + + public static AudioOutputStream getAudioOutputStream(AudioFileFormat.Type type, AudioFormat audioFormat, long lLengthInBytes, OutputStream outputStream) + throws IOException + { + TDataOutputStream dataOutputStream = getDataOutputStream(outputStream); + AudioOutputStream audioOutputStream = getAudioOutputStream(type, audioFormat, lLengthInBytes, dataOutputStream); + return audioOutputStream; + } +} + + +/*** AudioSystemShadow.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/AudioUtils.java b/songdbj/org/tritonus/share/sampled/AudioUtils.java new file mode 100644 index 0000000000..21c838b032 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/AudioUtils.java @@ -0,0 +1,181 @@ +/* + * AudioUtils.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Iterator; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.spi.AudioFileWriter; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.TConversionTool; + + + +public class AudioUtils +{ + public static long getLengthInBytes(AudioInputStream audioInputStream) + { + return getLengthInBytes(audioInputStream.getFormat(), + audioInputStream.getFrameLength()); +/* + long lLengthInFrames = audioInputStream.getFrameLength(); + int nFrameSize = audioInputStream.getFormat().getFrameSize(); + if (lLengthInFrames >= 0 && nFrameSize >= 1) + { + return lLengthInFrames * nFrameSize; + } + else + { + return AudioSystem.NOT_SPECIFIED; + } +*/ + } + + + + /** + * if the passed value for lLength is + * AudioSystem.NOT_SPECIFIED (unknown + * length), the length in bytes becomes + * AudioSystem.NOT_SPECIFIED, too. + */ + public static long getLengthInBytes(AudioFormat audioFormat, + long lLengthInFrames) + { + int nFrameSize = audioFormat.getFrameSize(); + if (lLengthInFrames >= 0 && nFrameSize >= 1) + { + return lLengthInFrames * nFrameSize; + } + else + { + return AudioSystem.NOT_SPECIFIED; + } + } + + + + public static boolean containsFormat(AudioFormat sourceFormat, + Iterator possibleFormats) + { + while (possibleFormats.hasNext()) + { + AudioFormat format = (AudioFormat) possibleFormats.next(); + if (AudioFormats.matches(format, sourceFormat)) + { + return true; + } + } + return false; + } + + /** + * Conversion milliseconds -> bytes + */ + + public static long millis2Bytes(long ms, AudioFormat format) { + return millis2Bytes(ms, format.getFrameRate(), format.getFrameSize()); + } + + public static long millis2Bytes(long ms, float frameRate, int frameSize) { + return (long) (ms*frameRate/1000*frameSize); + } + + /** + * Conversion milliseconds -> bytes (bytes will be frame-aligned) + */ + public static long millis2BytesFrameAligned(long ms, AudioFormat format) { + return millis2BytesFrameAligned(ms, format.getFrameRate(), format.getFrameSize()); + } + + public static long millis2BytesFrameAligned(long ms, float frameRate, int frameSize) { + return ((long) (ms*frameRate/1000))*frameSize; + } + + /** + * Conversion milliseconds -> frames + */ + public static long millis2Frames(long ms, AudioFormat format) { + return millis2Frames(ms, format.getFrameRate()); + } + + public static long millis2Frames(long ms, float frameRate) { + return (long) (ms*frameRate/1000); + } + + /** + * Conversion bytes -> milliseconds + */ + public static long bytes2Millis(long bytes, AudioFormat format) { + return (long) (bytes/format.getFrameRate()*1000/format.getFrameSize()); + } + + /** + * Conversion frames -> milliseconds + */ + public static long frames2Millis(long frames, AudioFormat format) { + return (long) (frames/format.getFrameRate()*1000); + } + + + //$$fb 2000-07-18: added these debugging functions + public static String NS_or_number(int number) { + return (number==AudioSystem.NOT_SPECIFIED)?"NOT_SPECIFIED":String.valueOf(number); + } + public static String NS_or_number(float number) { + return (number==AudioSystem.NOT_SPECIFIED)?"NOT_SPECIFIED":String.valueOf(number); + } + + /** + * For debugging purposes. + */ + public static String format2ShortStr(AudioFormat format) { + return format.getEncoding() + "-" + + NS_or_number(format.getChannels()) + "ch-" + + NS_or_number(format.getSampleSizeInBits()) + "bit-" + + NS_or_number(((int)format.getSampleRate())) + "Hz-"+ + (format.isBigEndian() ? "be" : "le"); + } + +} + + + +/*** AudioUtils.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/Encodings.java b/songdbj/org/tritonus/share/sampled/Encodings.java new file mode 100644 index 0000000000..6b880d24d9 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/Encodings.java @@ -0,0 +1,183 @@ +/* + * Encodings.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Florian Bomers + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +package org.tritonus.share.sampled; + +import java.util.Iterator; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import org.tritonus.share.StringHashedSet; +import org.tritonus.share.TDebug; + +/** + * This class is a proposal for generic handling of encodings. + * The main purpose is to provide a standardized way of + * implementing encoding types. Like this, encodings + * are only identified by their String name, and not, as currently, + * by their object instance. + *

+ * A registry of standard encoding names will + * be maintained by the Tritonus team. + *

+ * In a specification request to JavaSoft, the static method + * getEncoding should be integrated into + * AudioFormat.Encoding(String name) (possibly + * renamed to getInstance(String name).
+ * The static instances of ULAW, ALAW PCM_UNSIGNED and PCM_SIGNED + * encodings in that class should be retrieved using that function, + * too (internally).
+ * At best, the protected constructor of that class + * should also be replaced to be a private constructor. + * Like this it will be prevented that developers create their own + * instances of Encoding, which causes problems with the + * equals method. In fact, the equals method should be redefined anyway + * so that it compares the names and not the objects. + *

+ * Also, a specification request should be made to integrate + * getEncodings() into AudioSystem (this is + * especially annoying as the relevant methods already exist + * in the provider interfaces of file readers, file writers and + * converters). + * + * @author Florian Bomers + */ +public class Encodings extends AudioFormat.Encoding { + + /** contains all known encodings */ + private static StringHashedSet encodings = new StringHashedSet(); + + // initially add the standard encodings + static { + encodings.add(AudioFormat.Encoding.PCM_SIGNED); + encodings.add(AudioFormat.Encoding.PCM_UNSIGNED); + encodings.add(AudioFormat.Encoding.ULAW); + encodings.add(AudioFormat.Encoding.ALAW); + } + + Encodings(String name) { + super(name); + } + + /** + * Use this method for retrieving an instance of + * AudioFormat.Encoding of the specified + * name. A standard registry of encoding names will + * be maintained by the Tritonus team. + *

+ * Every file reader, file writer, and format converter + * provider should exclusively use this method for + * retrieving instances of AudioFormat.Encoding. + */ + /* + MP2000/09/11: + perhaps it is not a good idea to allow user programs the creation of new + encodings. The problem with it is that a plain typo will produce an encoding + object that is not supported. Instead, some indication of an error should be + signaled to the user program. And, there should be a second interface for + service providers allowing them to register encodings supported by themselves. + + $$fb2000/09/26: + The problem is what you see as second issue: it can never be assured + that this class knows all available encodings. So at the moment, there + is no choice than to allow users to create any Encoding they wish. + The encodings database will simplify things. + A problem with an interface to retrieve supported encodings (or API + function in spi.FormatConversionProvider) is that this requires + loading of all providers very early. Hmmm, maybe this is necessary in any + case when the user issues something like AudioSystem.isConversionSupported. + */ + public static AudioFormat.Encoding getEncoding(String name) { + AudioFormat.Encoding res=(AudioFormat.Encoding) encodings.get(name); + if (res==null) { + // it is not already in the string set. Create a new encoding instance. + res=new Encodings(name); + // and save it for the future + encodings.add(res); + } + return res; + } + + /** + * Tests for equality of 2 encodings. They are equal when their strings match. + *

+ * This function should be AudioFormat.Encoding.equals and must + * be considered as a temporary work around until it flows into the + * JavaSound API. + */ + // IDEA: create a special "NOT_SPECIFIED" encoding + // and a AudioFormat.Encoding.matches method. + public static boolean equals(AudioFormat.Encoding e1, AudioFormat.Encoding e2) { + return e2.toString().equals(e1.toString()); + } + + + /** + * Returns all "supported" encodings. + * Supported means that it is possible to read or + * write files with this encoding, or that a converter + * accepts this encoding as source or target format. + *

+ * Currently, this method returns a best guess and + * the search algorithm is far from complete: with standard + * methods of AudioSystem, only the target encodings + * of the converters can be retrieved - neither + * the source encodings of converters nor the encodings + * of file readers and file writers cannot be retrieved. + */ + public static AudioFormat.Encoding[] getEncodings() { + StringHashedSet iteratedSources=new StringHashedSet(); + StringHashedSet retrievedTargets=new StringHashedSet(); + Iterator sourceFormats=encodings.iterator(); + while (sourceFormats.hasNext()) { + AudioFormat.Encoding source=(AudioFormat.Encoding) sourceFormats.next(); + iterateEncodings(source, iteratedSources, retrievedTargets); + } + return (AudioFormat.Encoding[]) retrievedTargets.toArray( + new AudioFormat.Encoding[retrievedTargets.size()]); + } + + + private static void iterateEncodings(AudioFormat.Encoding source, + StringHashedSet iteratedSources, + StringHashedSet retrievedTargets) { + if (!iteratedSources.contains(source)) { + iteratedSources.add(source); + AudioFormat.Encoding[] targets=AudioSystem.getTargetEncodings(source); + for (int i=0; i + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Random; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.spi.AudioFileWriter; + +import org.tritonus.share.TDebug; + +/** + * A class for small buffers of samples in linear, 32-bit + * floating point format. + *

+ * It is supposed to be a replacement of the byte[] stream + * architecture of JavaSound, especially for chains of + * AudioInputStreams. Ideally, all involved AudioInputStreams + * handle reading into a FloatSampleBuffer. + *

+ * Specifications: + *

    + *
  1. Channels are separated, i.e. for stereo there are 2 float arrays + * with the samples for the left and right channel + *
  2. All data is handled in samples, where one sample means + * one float value in each channel + *
  3. All samples are normalized to the interval [-1.0...1.0] + *
+ *

+ * When a cascade of AudioInputStreams use FloatSampleBuffer for + * processing, they may implement the interface FloatSampleInput. + * This signals that this stream may provide float buffers + * for reading. The data is not converted back to bytes, + * but stays in a single buffer that is passed from stream to stream. + * For that serves the read(FloatSampleBuffer) method, which is + * then used as replacement for the byte-based read functions of + * AudioInputStream.
+ * However, backwards compatibility must always be retained, so + * even when an AudioInputStream implements FloatSampleInput, + * it must work the same way when any of the byte-based read methods + * is called.
+ * As an example, consider the following set-up:
+ *

+ * So, what happens when a block of samples is read from pcmAIS2 ? + *
    + *
  1. the read(byte[]) method of pcmAIS2 is called + *
  2. pcmAIS2 always operates on floating point samples, so + * it uses an own instance of FloatSampleBuffer and initializes + * it with the number of samples requested in the read(byte[]) + * method. + *
  3. It queries pcmAIS1 for the FloatSampleInput interface. As it + * implements it, pcmAIS2 calls the read(FloatSampleBuffer) method + * of pcmAIS1. + *
  4. pcmAIS1 notes that its underlying stream does not support floats, + * so it instantiates a byte buffer which can hold the number of + * samples of the FloatSampleBuffer passed to it. It calls the + * read(byte[]) method of auAIS. + *
  5. auAIS fills the buffer with the bytes. + *
  6. pcmAIS1 calls the initFromByteArray method of + * the float buffer to initialize it with the 8 bit data. + *
  7. Then pcmAIS1 processes the data: as the float buffer is + * normalized, it does nothing with the buffer - and returns + * control to pcmAIS2. The SampleSizeInBits field of the + * AudioFormat of pcmAIS1 defines that it should be 16 bits. + *
  8. pcmAIS2 receives the filled buffer from pcmAIS1 and does + * its processing on the buffer - it adds the reverb. + *
  9. As pcmAIS2's read(byte[]) method had been called, pcmAIS2 + * calls the convertToByteArray method of + * the float buffer to fill the byte buffer with the + * resulting samples. + *
+ *

+ * To summarize, here are some advantages when using a FloatSampleBuffer + * for streaming: + *

+ *

+ * Simple benchmarks showed that the processing requirements + * for the conversion to and from float is about the same as + * when converting it to shorts or ints without dithering, + * and significantly higher with dithering. An own implementation + * of a random number generator may improve this. + *

+ * "Lazy" deletion of samples and channels:
+ *

+ * The lazy mechanism can save many array instantiation (and copy-) operations + * for the sake of performance. All relevant methods exist in a second + * version which allows explicitely to disable lazy deletion. + *

+ * Use the reset functions to clear the memory and remove + * hidden samples and channels. + *

+ * Note that the lazy mechanism implies that the arrays returned + * from getChannel(int) may have a greater size + * than getSampleCount(). Consequently, be sure to never rely on the + * length field of the sample arrays. + *

+ * As an example, consider a chain of converters that all act + * on the same instance of FloatSampleBuffer. Some converters + * may decrease the sample count (e.g. sample rate converter) and + * delete channels (e.g. PCM2PCM converter). So, processing of one + * block will decrease both. For the next block, all starts + * from the beginning. With the lazy mechanism, all float arrays + * are only created once for processing all blocks.
+ * Having lazy disabled would require for each chunk that is processed + *

    + *
  1. new instantiation of all channel arrays + * at the converter chain beginning as they have been + * either deleted or decreased in size during processing of the + * previous chunk, and + *
  2. re-instantiation of all channel arrays for + * the reduction of the sample count. + *
+ *

+ * Dithering:
+ * By default, this class uses dithering for reduction + * of sample width (e.g. original data was 16bit, target + * data is 8bit). As dithering may be needed in other cases + * (especially when the float samples are processed using DSP + * algorithms), or it is preferred to switch it off, + * dithering can be explicitely switched on or off with + * the method setDitherMode(int).
+ * For a discussion about dithering, see + * + * here and + * + * here. + * + * @author Florian Bomers + */ + +public class FloatSampleBuffer { + + /** Whether the functions without lazy parameter are lazy or not. */ + private static final boolean LAZY_DEFAULT=true; + + private ArrayList channels = new ArrayList(); // contains for each channel a float array + private int sampleCount=0; + private int channelCount=0; + private float sampleRate=0; + private int originalFormatType=0; + + /** Constant for setDitherMode: dithering will be enabled if sample size is decreased */ + public static final int DITHER_MODE_AUTOMATIC=0; + /** Constant for setDitherMode: dithering will be done */ + public static final int DITHER_MODE_ON=1; + /** Constant for setDitherMode: dithering will not be done */ + public static final int DITHER_MODE_OFF=2; + + private float ditherBits = FloatSampleTools.DEFAULT_DITHER_BITS; + + // e.g. the sample rate converter may want to force dithering + private int ditherMode = DITHER_MODE_AUTOMATIC; + + //////////////////////////////// initialization ///////////////////////////////// + + /** + * Create an instance with initially no channels. + */ + public FloatSampleBuffer() { + this(0,0,1); + } + + /** + * Create an empty FloatSampleBuffer with the specified number of channels, + * samples, and the specified sample rate. + */ + public FloatSampleBuffer(int channelCount, int sampleCount, float sampleRate) { + init(channelCount, sampleCount, sampleRate, LAZY_DEFAULT); + } + + /** + * Creates a new instance of FloatSampleBuffer and initializes + * it with audio data given in the interleaved byte array buffer. + */ + public FloatSampleBuffer(byte[] buffer, int offset, int byteCount, + AudioFormat format) { + this(format.getChannels(), + byteCount/(format.getSampleSizeInBits()/8*format.getChannels()), + format.getSampleRate()); + initFromByteArray(buffer, offset, byteCount, format); + } + + protected void init(int channelCount, int sampleCount, float sampleRate) { + init(channelCount, sampleCount, sampleRate, LAZY_DEFAULT); + } + + protected void init(int channelCount, int sampleCount, float sampleRate, boolean lazy) { + if (channelCount<0 || sampleCount<0) { + throw new IllegalArgumentException( + "invalid parameters in initialization of FloatSampleBuffer."); + } + setSampleRate(sampleRate); + if (getSampleCount()!=sampleCount || getChannelCount()!=channelCount) { + createChannels(channelCount, sampleCount, lazy); + } + } + + private void createChannels(int channelCount, int sampleCount, boolean lazy) { + this.sampleCount=sampleCount; + // lazy delete of all channels. Intentionally lazy ! + this.channelCount=0; + for (int ch=0; ch

If keepOldSamples is true, as much as possible samples are + * retained. If the buffer is enlarged, silence is added at the end. + * If keepOldSamples is false, existing samples are discarded + * and the buffer contains random samples. + */ + public void changeSampleCount(int newSampleCount, boolean keepOldSamples) { + int oldSampleCount=getSampleCount(); + if (oldSampleCount==newSampleCount) { + return; + } + Object[] oldChannels=null; + if (keepOldSamples) { + oldChannels=getAllChannels(); + } + init(getChannelCount(), newSampleCount, getSampleRate()); + if (keepOldSamples) { + // copy old channels and eventually silence out new samples + int copyCount=newSampleCount0) { + makeSilence(0); + for (int ch=1; chindex. + * If LAZY_DEFAULT is true, this is done lazily. + */ + public void insertChannel(int index, boolean silent) { + insertChannel(index, silent, LAZY_DEFAULT); + } + + /** + * Inserts a channel at position index. + *

If silent is true, the new channel will be silent. + * Otherwise it will contain random data. + *

If lazy is true, hidden channels which have at least getSampleCount() + * elements will be examined for reusage as inserted channel.
+ * If lazy is false, still hidden channels are reused, + * but it is assured that the inserted channel has exactly getSampleCount() elements, + * thus not wasting memory. + */ + public void insertChannel(int index, boolean silent, boolean lazy) { + int physSize=channels.size(); + int virtSize=getChannelCount(); + float[] newChannel=null; + if (physSize>virtSize) { + // there are hidden channels. Try to use one. + for (int ch=virtSize; ch=getSampleCount()) + || (!lazy && thisChannel.length==getSampleCount())) { + // we found a matching channel. Use it ! + newChannel=thisChannel; + channels.remove(ch); + break; + } + } + } + if (newChannel==null) { + newChannel=new float[getSampleCount()]; + } + channels.add(index, newChannel); + this.channelCount++; + if (silent) { + makeSilence(index); + } + } + + /** performs a lazy remove of the channel */ + public void removeChannel(int channel) { + removeChannel(channel, LAZY_DEFAULT); + } + + + /** + * Removes a channel. + * If lazy is true, the channel is not physically removed, but only hidden. + * These hidden channels are reused by subsequent calls to addChannel + * or insertChannel. + */ + public void removeChannel(int channel, boolean lazy) { + if (!lazy) { + channels.remove(channel); + } else if (channelbufferCount || destIndex+length>bufferCount + || sourceIndex<0 || destIndex<0 || length<0) { + throw new IndexOutOfBoundsException("parameters exceed buffer size"); + } + System.arraycopy(data, sourceIndex, data, destIndex, length); + } + + /** + * Mix up of 1 channel to n channels.
+ * It copies the first channel to all newly created channels. + * @param targetChannelCount the number of channels that this sample buffer + * will have after expanding. NOT the number of + * channels to add ! + * @exception IllegalArgumentException if this buffer does not have one + * channel before calling this method. + */ + public void expandChannel(int targetChannelCount) { + // even more sanity... + if (getChannelCount()!=1) { + throw new IllegalArgumentException( + "FloatSampleBuffer: can only expand channels for mono signals."); + } + for (int ch=1; ch + * It uses a simple mixdown: all other channels are added to first channel.
+ * The volume is NOT lowered ! + * Be aware, this might cause clipping when converting back + * to integer samples. + */ + public void mixDownChannels() { + float[] firstChannel=getChannel(0); + int sampleCount=getSampleCount(); + int channelCount=getChannelCount(); + for (int ch=channelCount-1; ch>0; ch--) { + float[] thisChannel=getChannel(ch); + for (int i=0; idestOffset. + * This FloatSampleBuffer must be big enough to accomodate the samples. + *

+ * srcBuffer is read from index srcOffset + * to (srcOffset + (lengthInSamples * format.getFrameSize()))input + * @param format input buffer's audio format + * @param floatOffset the offset where to write the float samples + * @param frameCount number of samples to write to this sample buffer + */ + public void setSamplesFromBytes(byte[] input, int inByteOffset, AudioFormat format, + int floatOffset, int frameCount) { + if (floatOffset < 0 || frameCount < 0 || inByteOffset < 0) { + throw new IllegalArgumentException + ("FloatSampleBuffer.setSamplesFromBytes: negative inByteOffset, floatOffset, or frameCount"); + } + if (inByteOffset + (frameCount * format.getFrameSize()) > input.length) { + throw new IllegalArgumentException + ("FloatSampleBuffer.setSamplesFromBytes: input buffer too small."); + } + if (floatOffset + frameCount > getSampleCount()) { + throw new IllegalArgumentException + ("FloatSampleBuffer.setSamplesFromBytes: frameCount too large"); + } + + FloatSampleTools.byte2float(input, inByteOffset, channels, floatOffset, frameCount, format); + } + + //////////////////////////////// properties ///////////////////////////////// + + public int getChannelCount() { + return channelCount; + } + + public int getSampleCount() { + return sampleCount; + } + + public float getSampleRate() { + return sampleRate; + } + + /** + * Sets the sample rate of this buffer. + * NOTE: no conversion is done. The samples are only re-interpreted. + */ + public void setSampleRate(float sampleRate) { + if (sampleRate<=0) { + throw new IllegalArgumentException + ("Invalid samplerate for FloatSampleBuffer."); + } + this.sampleRate=sampleRate; + } + + /** + * NOTE: the returned array may be larger than sampleCount. So in any case, + * sampleCount is to be respected. + */ + public float[] getChannel(int channel) { + if (channel<0 || channel>=getChannelCount()) { + throw new IllegalArgumentException( + "FloatSampleBuffer: invalid channel number."); + } + return (float[]) channels.get(channel); + } + + public Object[] getAllChannels() { + Object[] res=new Object[getChannelCount()]; + for (int ch=0; chNote: this value is only used, when dithering is actually performed. + */ + public void setDitherBits(float ditherBits) { + if (ditherBits<=0) { + throw new IllegalArgumentException("DitherBits must be greater than 0"); + } + this.ditherBits=ditherBits; + } + + public float getDitherBits() { + return ditherBits; + } + + /** + * Sets the mode for dithering. + * This can be one of: + *

+ */ + public void setDitherMode(int mode) { + if (mode!=DITHER_MODE_AUTOMATIC + && mode!=DITHER_MODE_ON + && mode!=DITHER_MODE_OFF) { + throw new IllegalArgumentException("Illegal DitherMode"); + } + this.ditherMode=mode; + } + + public int getDitherMode() { + return ditherMode; + } + + + /** + * @return the ditherBits parameter for the float2byte functions + */ + protected float getConvertDitherBits(int newFormatType) { + // let's see whether dithering is necessary + boolean doDither = false; + switch (ditherMode) { + case DITHER_MODE_AUTOMATIC: + doDither=(originalFormatType & FloatSampleTools.F_SAMPLE_WIDTH_MASK)> + (newFormatType & FloatSampleTools.F_SAMPLE_WIDTH_MASK); + break; + case DITHER_MODE_ON: + doDither=true; + break; + case DITHER_MODE_OFF: + doDither=false; + break; + } + return doDither?ditherBits:0.0f; + } +} diff --git a/songdbj/org/tritonus/share/sampled/FloatSampleTools.java b/songdbj/org/tritonus/share/sampled/FloatSampleTools.java new file mode 100644 index 0000000000..76913ba39e --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/FloatSampleTools.java @@ -0,0 +1,696 @@ +/* + * FloatSampleTools.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000,2004 by Florian Bomers + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled; + +import java.util.*; +import javax.sound.sampled.*; +import org.tritonus.share.TDebug; + +/** + * Utility functions for handling data in normalized float arrays. + * Each sample is linear in the range of [-1.0f, +1.0f]. + *

+ * Currently, the following bit sizes are supported: + *

    + *
  • 8-bit + *
  • 16-bit + *
  • packed 24-bit (stored in 3 bytes) + *
  • 32-bit + *
+ * 8-bit data can be unsigned or signed. All other data is only + * supported in signed encoding. + * + * @see FloatSampleBuffer + * @author Florian Bomers + */ + +public class FloatSampleTools { + + /** default number of bits to be dithered: 0.7f */ + public static final float DEFAULT_DITHER_BITS = 0.7f; + + private static Random random = null; + + // sample width (must be in order !) + static final int F_8=1; + static final int F_16=2; + static final int F_24=3; + static final int F_32=4; + static final int F_SAMPLE_WIDTH_MASK=F_8 | F_16 | F_24 | F_32; + + // format bit-flags + static final int F_SIGNED=8; + static final int F_BIGENDIAN=16; + + // supported formats + static final int CT_8S=F_8 | F_SIGNED; + static final int CT_8U=F_8; + static final int CT_16SB=F_16 | F_SIGNED | F_BIGENDIAN; + static final int CT_16SL=F_16 | F_SIGNED; + static final int CT_24SB=F_24 | F_SIGNED | F_BIGENDIAN; + static final int CT_24SL=F_24 | F_SIGNED; + static final int CT_32SB=F_32 | F_SIGNED | F_BIGENDIAN; + static final int CT_32SL=F_32 | F_SIGNED; + + // ////////////////////////////// initialization /////////////////////////////// // + + /** prevent instanciation */ + private FloatSampleTools() { + } + + + // /////////////////// FORMAT / FORMAT TYPE /////////////////////////////////// // + + /** + * only allow "packed" samples -- currently no support for 18, 20, 24_32 bits. + * @throws IllegalArgumentException + */ + static void checkSupportedSampleSize(int ssib, int channels, int frameSize) { + if ((ssib*channels) != frameSize * 8) { + throw new IllegalArgumentException("unsupported sample size: "+ssib + +" stored in "+(frameSize/channels)+" bytes."); + } + } + + + /** + * Get the formatType code from the given format. + * @throws IllegalArgumentException + */ + static int getFormatType(AudioFormat format) { + boolean signed = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED); + if (!signed && + !format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) { + throw new IllegalArgumentException + ("unsupported encoding: only PCM encoding supported."); + } + if (!signed && format.getSampleSizeInBits() != 8) { + throw new IllegalArgumentException + ("unsupported encoding: only 8-bit can be unsigned"); + } + checkSupportedSampleSize(format.getSampleSizeInBits(), + format.getChannels(), + format.getFrameSize()); + + int formatType = getFormatType(format.getSampleSizeInBits(), + signed, format.isBigEndian()); + return formatType; + } + + /** + * @throws IllegalArgumentException + */ + static int getFormatType(int ssib, boolean signed, boolean bigEndian) { + int bytesPerSample=ssib/8; + int res=0; + if (ssib==8) { + res=F_8; + } else if (ssib==16) { + res=F_16; + } else if (ssib==24) { + res=F_24; + } else if (ssib==32) { + res=F_32; + } + if (res==0) { + throw new IllegalArgumentException + ("FloatSampleBuffer: unsupported sample size of " + +ssib+" bits per sample."); + } + if (!signed && bytesPerSample>1) { + throw new IllegalArgumentException + ("FloatSampleBuffer: unsigned samples larger than " + +"8 bit are not supported"); + } + if (signed) { + res|=F_SIGNED; + } + if (bigEndian && (ssib!=8)) { + res|=F_BIGENDIAN; + } + return res; + } + + static int getSampleSize(int formatType) { + switch (formatType & F_SAMPLE_WIDTH_MASK) { + case F_8: return 1; + case F_16: return 2; + case F_24: return 3; + case F_32: return 4; + } + return 0; + } + + /** + * Return a string representation of this format + */ + static String formatType2Str(int formatType) { + String res=""+formatType+": "; + switch (formatType & F_SAMPLE_WIDTH_MASK) { + case F_8: + res+="8bit"; + break; + case F_16: + res+="16bit"; + break; + case F_24: + res+="24bit"; + break; + case F_32: + res+="32bit"; + break; + } + res+=((formatType & F_SIGNED)==F_SIGNED)?" signed":" unsigned"; + if ((formatType & F_SAMPLE_WIDTH_MASK)!=F_8) { + res+=((formatType & F_BIGENDIAN)==F_BIGENDIAN)? + " big endian":" little endian"; + } + return res; + } + + + // /////////////////// BYTE 2 FLOAT /////////////////////////////////// // + + private static final float twoPower7=128.0f; + private static final float twoPower15=32768.0f; + private static final float twoPower23=8388608.0f; + private static final float twoPower31=2147483648.0f; + + private static final float invTwoPower7=1/twoPower7; + private static final float invTwoPower15=1/twoPower15; + private static final float invTwoPower23=1/twoPower23; + private static final float invTwoPower31=1/twoPower31; + + + /** + * Conversion function to convert an interleaved byte array to + * a List of interleaved float arrays. The float arrays will contain normalized + * samples in the range [-1.0f, +1.0f]. The input array + * provides bytes in the format specified in format. + *

+ * Only PCM formats are accepted. The method will convert all + * byte values from + * input[inByteOffset] to + * input[inByteOffset + (frameCount * format.getFrameSize()) - 1] + * to floats from + * output(n)[outOffset] to + * output(n)[outOffset + frameCount - 1] + * + * @param input the audio data in an byte array + * @param inByteOffset index in input where to start the conversion + * @param output list of float[] arrays which receive the converted audio data. + * if the list does not contain enough elements, or individual float arrays + * are not large enough, they are created. + * @param outOffset the start offset in output + * @param frameCount number of frames to be converted + * @param format the input format. Only packed PCM is allowed + * @throws IllegalArgumentException if one of the parameters is out of bounds + * + * @see #byte2floatInterleaved(byte[],int,float[],int,int,AudioFormat) + */ + public static void byte2float(byte[] input, int inByteOffset, + List output, int outOffset, int frameCount, + //List output, int outOffset, int frameCount, + AudioFormat format) { + for (int channel = 0; channel < format.getChannels(); channel++) { + float[] data; + if (output.size() < channel) { + data = new float[frameCount + outOffset]; + output.add(data); + } else { + data = output.get(channel); + if (data.length < frameCount + outOffset) { + data = new float[frameCount + outOffset]; + output.set(channel, data); + } + } + + byte2floatGeneric(input, inByteOffset, format.getFrameSize(), + data, outOffset, + frameCount, format); + inByteOffset += format.getFrameSize() / format.getChannels(); + } + } + + + /** + * Conversion function to convert an interleaved byte array to + * an interleaved float array. The float array will contain normalized + * samples in the range [-1.0f, +1.0f]. The input array + * provides bytes in the format specified in format. + *

+ * Only PCM formats are accepted. The method will convert all + * byte values from + * input[inByteOffset] to + * input[inByteOffset + (frameCount * format.getFrameSize()) - 1] + * to floats from + * output[outOffset] to + * output[outOffset + (frameCount * format.getChannels()) - 1] + * + * @param input the audio data in an byte array + * @param inByteOffset index in input where to start the conversion + * @param output the float array that receives the converted audio data + * @param outOffset the start offset in output + * @param frameCount number of frames to be converted + * @param format the input format. Only packed PCM is allowed + * @throws IllegalArgumentException if one of the parameters is out of bounds + * + * @see #byte2float(byte[],int,List,int,int,AudioFormat) + */ + public static void byte2floatInterleaved(byte[] input, int inByteOffset, + float[] output, int outOffset, int frameCount, + AudioFormat format) { + + byte2floatGeneric(input, inByteOffset, format.getFrameSize() / format.getChannels(), + output, outOffset, frameCount * format.getChannels(), + format); + } + + + + /** + * Generic conversion function to convert a byte array to + * a float array. + *

+ * Only PCM formats are accepted. The method will convert all + * bytes from + * input[inByteOffset] to + * input[inByteOffset + (sampleCount * (inByteStep - 1)] + * to samples from + * output[outOffset] to + * output[outOffset+sampleCount-1]. + *

+ * The format's channel count is ignored. + *

+ * For mono data, set inByteOffset to format.getFrameSize().
+ * For converting interleaved input data, multiply sampleCount + * by the number of channels and set inByteStep to + * format.getFrameSize() / format.getChannels(). + * + * @param sampleCount number of samples to be written to output + * @param inByteStep how many bytes advance for each output sample in output. + * @throws IllegalArgumentException if one of the parameters is out of bounds + * + * @see #byte2floatInterleaved(byte[],int,float[],int,int,AudioFormat) + * @see #byte2float(byte[],int,List,int,int,AudioFormat) + */ + static void byte2floatGeneric(byte[] input, int inByteOffset, int inByteStep, + float[] output, int outOffset, int sampleCount, + AudioFormat format) { + int formatType = getFormatType(format); + + byte2floatGeneric(input, inByteOffset, inByteStep, + output, outOffset, sampleCount, + formatType); + } + + + /** + * Central conversion function from + * a byte array to a normalized float array. In order to accomodate + * interleaved and non-interleaved + * samples, this method takes inByteStep as parameter which + * can be used to flexibly convert the data. + *

+ * E.g.:
+ * mono->mono: inByteStep=format.getFrameSize()
+ * interleaved_stereo->interleaved_stereo: inByteStep=format.getFrameSize()/2, + * sampleCount*2
+ * stereo->2 mono arrays:
+ * ---inByteOffset=0, outOffset=0, inByteStep=format.getFrameSize()
+ * ---inByteOffset=format.getFrameSize()/2, outOffset=1, inByteStep=format.getFrameSize()
+ */ + static void byte2floatGeneric(byte[] input, int inByteOffset, int inByteStep, + float[] output, int outOffset, int sampleCount, + int formatType) { + //if (TDebug.TraceAudioConverter) { + // TDebug.out("FloatSampleTools.byte2floatGeneric, formatType=" + // +formatType2Str(formatType)); + //} + int endCount = outOffset + sampleCount; + int inIndex = inByteOffset; + for (int outIndex = outOffset; outIndex < endCount; outIndex++, inIndex+=inByteStep) { + // do conversion + switch (formatType) { + case CT_8S: + output[outIndex]= + ((float) input[inIndex])*invTwoPower7; + break; + case CT_8U: + output[outIndex]= + ((float) ((input[inIndex] & 0xFF)-128))*invTwoPower7; + break; + case CT_16SB: + output[outIndex]= + ((float) ((input[inIndex]<<8) + | (input[inIndex+1] & 0xFF)))*invTwoPower15; + break; + case CT_16SL: + output[outIndex]= + ((float) ((input[inIndex+1]<<8) + | (input[inIndex] & 0xFF)))*invTwoPower15; + break; + case CT_24SB: + output[outIndex]= + ((float) ((input[inIndex]<<16) + | ((input[inIndex+1] & 0xFF)<<8) + | (input[inIndex+2] & 0xFF)))*invTwoPower23; + break; + case CT_24SL: + output[outIndex]= + ((float) ((input[inIndex+2]<<16) + | ((input[inIndex+1] & 0xFF)<<8) + | (input[inIndex] & 0xFF)))*invTwoPower23; + break; + case CT_32SB: + output[outIndex]= + ((float) ((input[inIndex]<<24) + | ((input[inIndex+1] & 0xFF)<<16) + | ((input[inIndex+2] & 0xFF)<<8) + | (input[inIndex+3] & 0xFF)))*invTwoPower31; + break; + case CT_32SL: + output[outIndex]= + ((float) ((input[inIndex+3]<<24) + | ((input[inIndex+2] & 0xFF)<<16) + | ((input[inIndex+1] & 0xFF)<<8) + | (input[inIndex] & 0xFF)))*invTwoPower31; + break; + default: + throw new IllegalArgumentException + ("unsupported format="+formatType2Str(formatType)); + } + } + } + + // /////////////////// FLOAT 2 BYTE /////////////////////////////////// // + + private static byte quantize8(float sample, float ditherBits) { + if (ditherBits!=0) { + sample+=random.nextFloat()*ditherBits; + } + if (sample>=127.0f) { + return (byte) 127; + } else if (sample<=-128.0f) { + return (byte) -128; + } else { + return (byte) (sample<0?(sample-0.5f):(sample+0.5f)); + } + } + + private static int quantize16(float sample, float ditherBits) { + if (ditherBits!=0) { + sample+=random.nextFloat()*ditherBits; + } + if (sample>=32767.0f) { + return 32767; + } else if (sample<=-32768.0f) { + return -32768; + } else { + return (int) (sample<0?(sample-0.5f):(sample+0.5f)); + } + } + + private static int quantize24(float sample, float ditherBits) { + if (ditherBits!=0) { + sample+=random.nextFloat()*ditherBits; + } + if (sample>=8388607.0f) { + return 8388607; + } else if (sample<=-8388608.0f) { + return -8388608; + } else { + return (int) (sample<0?(sample-0.5f):(sample+0.5f)); + } + } + + private static int quantize32(float sample, float ditherBits) { + if (ditherBits!=0) { + sample+=random.nextFloat()*ditherBits; + } + if (sample>=2147483647.0f) { + return 2147483647; + } else if (sample<=-2147483648.0f) { + return -2147483648; + } else { + return (int) (sample<0?(sample-0.5f):(sample+0.5f)); + } + } + + + /** + * Conversion function to convert a non-interleaved float audio data to + * an interleaved byte array. The float arrays contains normalized + * samples in the range [-1.0f, +1.0f]. The output array + * will receive bytes in the format specified in format. + * Exactly format.getChannels() channels are converted + * regardless of the number of elements in input. If input + * does not provide enough channels, an IllegalArgumentException is thrown. + *

+ * Only PCM formats are accepted. The method will convert all + * samples from input(n)[inOffset] to + * input(n)[inOffset + frameCount - 1] + * to byte values from output[outByteOffset] to + * output[outByteOffset + (frameCount * format.getFrameSize()) - 1] + *

+ * Dithering should be used when the output resolution is significantly + * lower than the original resolution. This includes if the original + * data was 16-bit and it is now converted to 8-bit, or if the + * data was generated in the float domain. No dithering need to be used + * if the original sample data was in e.g. 8-bit and the resulting output + * data has a higher resolution. If dithering is used, a sensitive value + * is DEFAULT_DITHER_BITS. + * + * @param input a List of float arrays with the input audio data + * @param inOffset index in the input arrays where to start the conversion + * @param output the byte array that receives the converted audio data + * @param outByteOffset the start offset in output + * @param frameCount number of frames to be converted. + * @param format the output format. Only packed PCM is allowed + * @param ditherBits if 0, do not dither. Otherwise the number of bits to be dithered + * @throws IllegalArgumentException if one of the parameters is out of bounds + * + * @see #DEFAULT_DITHER_BITS + * @see #float2byteInterleaved(float[],int,byte[],int,int,AudioFormat,float) + */ + //public static void float2byte(List input, int inOffset, + public static void float2byte(List input, int inOffset, + byte[] output, int outByteOffset, + int frameCount, + AudioFormat format, float ditherBits) { + for (int channel = 0; channel < format.getChannels(); channel++) { + float[] data = (float[]) input.get(channel); + float2byteGeneric(data, inOffset, + output, outByteOffset, format.getFrameSize(), + frameCount, format, ditherBits); + outByteOffset += format.getFrameSize() / format.getChannels(); + } + } + + /** + * Conversion function to convert an interleaved float array to + * an interleaved byte array. The float array contains normalized + * samples in the range [-1.0f, +1.0f]. The output array + * will receive bytes in the format specified in format. + *

+ * Only PCM formats are accepted. The method will convert all + * samples from input[inOffset] to + * input[inOffset + (frameCount * format.getChannels()) - 1] + * to byte values from output[outByteOffset] to + * output[outByteOffset + (frameCount * format.getFrameSize()) - 1] + *

+ * Dithering should be used when the output resolution is significantly + * lower than the original resolution. This includes if the original + * data was 16-bit and it is now converted to 8-bit, or if the + * data was generated in the float domain. No dithering need to be used + * if the original sample data was in e.g. 8-bit and the resulting output + * data has a higher resolution. If dithering is used, a sensitive value + * is DEFAULT_DITHER_BITS. + * + * @param input the audio data in normalized samples + * @param inOffset index in input where to start the conversion + * @param output the byte array that receives the converted audio data + * @param outByteOffset the start offset in output + * @param frameCount number of frames to be converted. + * @param format the output format. Only packed PCM is allowed + * @param ditherBits if 0, do not dither. Otherwise the number of bits to be dithered + * @throws IllegalArgumentException if one of the parameters is out of bounds + * + * @see #DEFAULT_DITHER_BITS + * @see #float2byte(List,int,byte[],int,int,AudioFormat,float) + */ + public static void float2byteInterleaved(float[] input, int inOffset, + byte[] output, int outByteOffset, + int frameCount, + AudioFormat format, float ditherBits) { + float2byteGeneric(input, inOffset, + output, outByteOffset, format.getFrameSize() / format.getChannels(), + frameCount * format.getChannels(), + format, ditherBits); + } + + + + /** + * Generic conversion function to convert a float array to + * a byte array. + *

+ * Only PCM formats are accepted. The method will convert all + * samples from input[inOffset] to + * input[inOffset+sampleCount-1] + * to byte values from output[outByteOffset] to + * output[outByteOffset + (sampleCount * (outByteStep - 1)]. + *

+ * The format's channel count is ignored. + *

+ * For mono data, set outByteOffset to format.getFrameSize().
+ * For converting interleaved input data, multiply sampleCount + * by the number of channels and set outByteStep to + * format.getFrameSize() / format.getChannels(). + * + * @param sampleCount number of samples in input to be converted. + * @param outByteStep how many bytes advance for each input sample in input. + * @throws IllegalArgumentException if one of the parameters is out of bounds + * + * @see #float2byteInterleaved(float[],int,byte[],int,int,AudioFormat,float) + * @see #float2byte(List,int,byte[],int,int,AudioFormat,float) + */ + static void float2byteGeneric(float[] input, int inOffset, + byte[] output, int outByteOffset, int outByteStep, + int sampleCount, + AudioFormat format, float ditherBits) { + int formatType = getFormatType(format); + + float2byteGeneric(input, inOffset, + output, outByteOffset, outByteStep, + sampleCount, + formatType, ditherBits); + } + + + /** + * Central conversion function from normalized float array to + * a byte array. In order to accomodate interleaved and non-interleaved + * samples, this method takes outByteStep as parameter which + * can be used to flexibly convert the data. + *

+ * E.g.:
+ * mono->mono: outByteStep=format.getFrameSize()
+ * interleaved stereo->interleaved stereo: outByteStep=format.getFrameSize()/2, sampleCount*2
+ * 2 mono arrays->stereo:
+ * ---inOffset=0, outByteOffset=0, outByteStep=format.getFrameSize()
+ * ---inOffset=1, outByteOffset=format.getFrameSize()/2, outByteStep=format.getFrameSize()
+ */ + static void float2byteGeneric(float[] input, int inOffset, + byte[] output, int outByteOffset, int outByteStep, + int sampleCount, int formatType, float ditherBits) { + //if (TDebug.TraceAudioConverter) { + // TDebug.out("FloatSampleBuffer.float2byteGeneric, formatType=" + // +"formatType2Str(formatType)); + //} + + if (inOffset < 0 + || inOffset + sampleCount > input.length + || sampleCount < 0) { + throw new IllegalArgumentException("invalid input index: " + +"input.length="+input.length + +" inOffset="+inOffset + +" sampleCount="+sampleCount); + } + if (outByteOffset < 0 + || outByteOffset + (sampleCount * outByteStep) > output.length + || outByteStep < getSampleSize(formatType)) { + throw new IllegalArgumentException("invalid output index: " + +"output.length="+output.length + +" outByteOffset="+outByteOffset + +" sampleCount="+sampleCount + +" format="+formatType2Str(formatType)); + } + + if (ditherBits!=0.0f && random==null) { + // create the random number generator for dithering + random=new Random(); + } + int endSample = inOffset + sampleCount; + int iSample; + int outIndex = outByteOffset; + for (int inIndex = inOffset; + inIndex < endSample; + inIndex++, outIndex+=outByteStep) { + // do conversion + switch (formatType) { + case CT_8S: + output[outIndex]=quantize8(input[inIndex]*twoPower7, ditherBits); + break; + case CT_8U: + output[outIndex]=(byte) (quantize8((input[inIndex]*twoPower7), ditherBits)+128); + break; + case CT_16SB: + iSample=quantize16(input[inIndex]*twoPower15, ditherBits); + output[outIndex]=(byte) (iSample >> 8); + output[outIndex+1]=(byte) (iSample & 0xFF); + break; + case CT_16SL: + iSample=quantize16(input[inIndex]*twoPower15, ditherBits); + output[outIndex+1]=(byte) (iSample >> 8); + output[outIndex]=(byte) (iSample & 0xFF); + break; + case CT_24SB: + iSample=quantize24(input[inIndex]*twoPower23, ditherBits); + output[outIndex]=(byte) (iSample >> 16); + output[outIndex+1]=(byte) ((iSample >>> 8) & 0xFF); + output[outIndex+2]=(byte) (iSample & 0xFF); + break; + case CT_24SL: + iSample=quantize24(input[inIndex]*twoPower23, ditherBits); + output[outIndex+2]=(byte) (iSample >> 16); + output[outIndex+1]=(byte) ((iSample >>> 8) & 0xFF); + output[outIndex]=(byte) (iSample & 0xFF); + break; + case CT_32SB: + iSample=quantize32(input[inIndex]*twoPower31, ditherBits); + output[outIndex]=(byte) (iSample >> 24); + output[outIndex+1]=(byte) ((iSample >>> 16) & 0xFF); + output[outIndex+2]=(byte) ((iSample >>> 8) & 0xFF); + output[outIndex+3]=(byte) (iSample & 0xFF); + break; + case CT_32SL: + iSample=quantize32(input[inIndex]*twoPower31, ditherBits); + output[outIndex+3]=(byte) (iSample >> 24); + output[outIndex+2]=(byte) ((iSample >>> 16) & 0xFF); + output[outIndex+1]=(byte) ((iSample >>> 8) & 0xFF); + output[outIndex]=(byte) (iSample & 0xFF); + break; + default: + throw new IllegalArgumentException + ("unsupported format="+formatType2Str(formatType)); + } + } + } +} diff --git a/songdbj/org/tritonus/share/sampled/TAudioFormat.java b/songdbj/org/tritonus/share/sampled/TAudioFormat.java new file mode 100644 index 0000000000..7911d5e005 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/TAudioFormat.java @@ -0,0 +1,110 @@ +/* + * TAudioFormat.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2003 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.sound.sampled.AudioFormat; + + + +public class TAudioFormat +extends AudioFormat +{ + private Map m_properties; + private Map m_unmodifiableProperties; + + + public TAudioFormat(AudioFormat.Encoding encoding, + float sampleRate, + int sampleSizeInBits, + int channels, + int frameSize, + float frameRate, + boolean bigEndian, + Map properties) + { + super(encoding, + sampleRate, + sampleSizeInBits, + channels, + frameSize, + frameRate, + bigEndian); + initMaps(properties); + } + + + public TAudioFormat(float sampleRate, + int sampleSizeInBits, + int channels, + boolean signed, + boolean bigEndian, + Map properties) + { + super(sampleRate, + sampleSizeInBits, + channels, + signed, + bigEndian); + initMaps(properties); + } + + + + private void initMaps(Map properties) + { + /* Here, we make a shallow copy of the map. It's unclear if this + is sufficient (or if a deep copy should be made). + */ + m_properties = new HashMap(); + m_properties.putAll(properties); + m_unmodifiableProperties = Collections.unmodifiableMap(m_properties); + } + + + + public Map properties() + { + return m_unmodifiableProperties; + } + + + + protected void setProperty(String key, Object value) + { + m_properties.put(key, value); + } +} + + + +/*** TAudioFormat.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/TConversionTool.java b/songdbj/org/tritonus/share/sampled/TConversionTool.java new file mode 100644 index 0000000000..18673edf31 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/TConversionTool.java @@ -0,0 +1,1224 @@ +/* + * TConversionTool.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999,2000 by Florian Bomers + * Copyright (c) 2000 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled; + + +/** + * Useful methods for converting audio data. + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ + +/* +For convenience, a list of available methods is maintained here. +Some hints: +- buffers: always byte arrays +- offsets: always in bytes +- sampleCount: number of SAMPLES to read/write, as opposed to FRAMES or BYTES! +- when in buffer and out buffer are given, the data is copied, + otherwise it is replaced in the same buffer (buffer size is not checked!) +- a number (except "2") gives the number of bits in which format + the samples have to be. +- >8 bits per sample is always treated signed. +- all functions are tried to be optimized - hints welcome ! + + +** "high level" methods ** +changeOrderOrSign(buffer, nOffset, nByteLength, nBytesPerSample) +changeOrderOrSign(inBuffer, nInOffset, outBuffer, nOutOffset, nByteLength, nBytesPerSample) + + +** PCM byte order and sign conversion ** +void convertSign8(buffer, byteOffset, sampleCount) +void swapOrder16(buffer, byteOffset, sampleCount) +void swapOrder24(buffer, byteOffset, sampleCount) +void swapOrder32(buffer, byteOffset, sampleCount) +void convertSign8(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount) +void swapOrder16(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount) +void swapOrder24(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount) +void swapOrder32(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount) + + +** conversion functions for byte arrays ** +** these are for reference to see how to implement these conversions ** +short bytesToShort16(highByte, lowByte) +short bytesToShort16(buffer, byteOffset, bigEndian) +short bytesToInt16(highByte, lowByte) +short bytesToInt16(buffer, byteOffset, bigEndian) +short bytesToInt24(buffer, byteOffset, bigEndian) +short bytesToInt32(buffer, byteOffset, bigEndian) +void shortToBytes16(sample, buffer, byteOffset, bigEndian) +void intToBytes24(sample, buffer, byteOffset, bigEndian) +void intToBytes32(sample, buffer, byteOffset, bigEndian) + + +** ULAW <-> PCM ** +byte linear2ulaw(int sample) +short ulaw2linear(int ulawbyte) +void pcm162ulaw(buffer, byteOffset, sampleCount, bigEndian) +void pcm162ulaw(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount, bigEndian) +void pcm82ulaw(buffer, byteOffset, sampleCount, signed) +void pcm82ulaw(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount, signed) +void ulaw2pcm16(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount, bigEndian) +void ulaw2pcm8(buffer, byteOffset, sampleCount, signed) +void ulaw2pcm8(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount, signed) + + +** ALAW <-> PCM ** +byte linear2alaw(short pcm_val) +short alaw2linear(byte ulawbyte) +void pcm162alaw(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount, bigEndian) +void pcm162alaw(buffer, byteOffset, sampleCount, bigEndian) +void pcm82alaw(buffer, byteOffset, sampleCount, signed) +void pcm82alaw(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount, signed) +void alaw2pcm16(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount, bigEndian) +void alaw2pcm8(buffer, byteOffset, sampleCount, signed) +void alaw2pcm8(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount, signed) + + +** ULAW <-> ALAW ** +byte ulaw2alaw(byte sample) +void ulaw2alaw(buffer, byteOffset, sampleCount) +void ulaw2alaw(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount) +byte alaw2ulaw(byte sample) +void alaw2ulaw(buffer, byteOffset, sampleCount) +void alaw2ulaw(inBuffer, inByteOffset, outBuffer, outByteOffset, sampleCount) + +*/ + +public class TConversionTool { + + ///////////////// sign/byte-order conversion /////////////////////////////////// + + public static void convertSign8(byte[] buffer, int byteOffset, int sampleCount) { + sampleCount+=byteOffset; + for (int i=byteOffset; i0) { + outBuffer[outByteOffset++]=(byte)(inBuffer[inByteOffset++]+128); + sampleCount--; + } + } + + public static void swapOrder16(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, int sampleCount) { + while (sampleCount>0) { + outBuffer[outByteOffset++]=inBuffer[inByteOffset+1]; + outBuffer[outByteOffset++]=inBuffer[inByteOffset++]; + inByteOffset++; + sampleCount--; + } + } + + public static void swapOrder24(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, int sampleCount) { + while (sampleCount>0) { + outBuffer[outByteOffset++]=inBuffer[inByteOffset+2]; + outByteOffset++; + outBuffer[outByteOffset++]=inBuffer[inByteOffset++]; + inByteOffset++; + inByteOffset++; + sampleCount--; + } + } + + public static void swapOrder32(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, int sampleCount) { + while (sampleCount>0) { + outBuffer[outByteOffset++]=inBuffer[inByteOffset+3]; + outBuffer[outByteOffset++]=inBuffer[inByteOffset+2]; + outBuffer[outByteOffset++]=inBuffer[inByteOffset+1]; + outBuffer[outByteOffset++]=inBuffer[inByteOffset++]; + inByteOffset++; + inByteOffset++; + inByteOffset++; + sampleCount--; + } + } + + + ///////////////// conversion functions for byte arrays //////////////////////////// + + + /** + * Converts 2 bytes to a signed sample of type short. + *

This is a reference function. + */ + public static short bytesToShort16(byte highByte, byte lowByte) { + return (short) ((highByte<<8) | (lowByte & 0xFF)); + } + + /** + * Converts 2 successive bytes starting at byteOffset in + * buffer to a signed sample of type short. + *

+ * For little endian, buffer[byteOffset] is interpreted as low byte, + * whereas it is interpreted as high byte in big endian. + *

This is a reference function. + */ + public static short bytesToShort16(byte[] buffer, int byteOffset, boolean bigEndian) { + return bigEndian? + ((short) ((buffer[byteOffset]<<8) | (buffer[byteOffset+1] & 0xFF))): + ((short) ((buffer[byteOffset+1]<<8) | (buffer[byteOffset] & 0xFF))); + } + + /** + * Converts 2 bytes to a signed integer sample with 16bit range. + *

This is a reference function. + */ + public static int bytesToInt16(byte highByte, byte lowByte) { + return (highByte<<8) | (lowByte & 0xFF); + } + + /** + * Converts 2 successive bytes starting at byteOffset in + * buffer to a signed integer sample with 16bit range. + *

+ * For little endian, buffer[byteOffset] is interpreted as low byte, + * whereas it is interpreted as high byte in big endian. + *

This is a reference function. + */ + public static int bytesToInt16(byte[] buffer, int byteOffset, boolean bigEndian) { + return bigEndian? + ((buffer[byteOffset]<<8) | (buffer[byteOffset+1] & 0xFF)): + ((buffer[byteOffset+1]<<8) | (buffer[byteOffset] & 0xFF)); + } + + /** + * Converts 3 successive bytes starting at byteOffset in + * buffer to a signed integer sample with 24bit range. + *

+ * For little endian, buffer[byteOffset] is interpreted as lowest byte, + * whereas it is interpreted as highest byte in big endian. + *

This is a reference function. + */ + public static int bytesToInt24(byte[] buffer, int byteOffset, boolean bigEndian) { + return bigEndian? + ((buffer[byteOffset]<<16) // let Java handle sign-bit + | ((buffer[byteOffset+1] & 0xFF)<<8) // inhibit sign-bit handling + | (buffer[byteOffset+2] & 0xFF)): + ((buffer[byteOffset+2]<<16) // let Java handle sign-bit + | ((buffer[byteOffset+1] & 0xFF)<<8) // inhibit sign-bit handling + | (buffer[byteOffset] & 0xFF)); + } + + /** + * Converts a 4 successive bytes starting at byteOffset in + * buffer to a signed 32bit integer sample. + *

+ * For little endian, buffer[byteOffset] is interpreted as lowest byte, + * whereas it is interpreted as highest byte in big endian. + *

This is a reference function. + */ + public static int bytesToInt32(byte[] buffer, int byteOffset, boolean bigEndian) { + return bigEndian? + ((buffer[byteOffset]<<24) // let Java handle sign-bit + | ((buffer[byteOffset+1] & 0xFF)<<16) // inhibit sign-bit handling + | ((buffer[byteOffset+2] & 0xFF)<<8) // inhibit sign-bit handling + | (buffer[byteOffset+3] & 0xFF)): + ((buffer[byteOffset+3]<<24) // let Java handle sign-bit + | ((buffer[byteOffset+2] & 0xFF)<<16) // inhibit sign-bit handling + | ((buffer[byteOffset+1] & 0xFF)<<8) // inhibit sign-bit handling + | (buffer[byteOffset] & 0xFF)); + } + + + /** + * Converts a sample of type short to 2 bytes in an array. + * sample is interpreted as signed (as Java does). + *

+ * For little endian, buffer[byteOffset] is filled with low byte of sample, + * and buffer[byteOffset+1] is filled with high byte of sample. + *

For big endian, this is reversed. + *

This is a reference function. + */ + public static void shortToBytes16(short sample, byte[] buffer, int byteOffset, boolean bigEndian) { + intToBytes16(sample, buffer, byteOffset, bigEndian); + } + + /** + * Converts a 16 bit sample of type int to 2 bytes in an array. + * sample is interpreted as signed (as Java does). + *

+ * For little endian, buffer[byteOffset] is filled with low byte of sample, + * and buffer[byteOffset+1] is filled with high byte of sample + sign bit. + *

For big endian, this is reversed. + *

Before calling this function, it should be assured that sample + * is in the 16bit range - it will not be clipped. + *

This is a reference function. + */ + public static void intToBytes16(int sample, byte[] buffer, int byteOffset, boolean bigEndian) { + if (bigEndian) { + buffer[byteOffset++]=(byte) (sample >> 8); + buffer[byteOffset]=(byte) (sample & 0xFF); + } else { + buffer[byteOffset++]=(byte) (sample & 0xFF); + buffer[byteOffset]=(byte) (sample >> 8); + } + } + + /** + * Converts a 24 bit sample of type int to 3 bytes in an array. + * sample is interpreted as signed (as Java does). + *

+ * For little endian, buffer[byteOffset] is filled with low byte of sample, + * and buffer[byteOffset+2] is filled with the high byte of sample + sign bit. + *

For big endian, this is reversed. + *

Before calling this function, it should be assured that sample + * is in the 24bit range - it will not be clipped. + *

This is a reference function. + */ + public static void intToBytes24(int sample, byte[] buffer, int byteOffset, boolean bigEndian) { + if (bigEndian) { + buffer[byteOffset++]=(byte) (sample >> 16); + buffer[byteOffset++]=(byte) ((sample >>> 8) & 0xFF); + buffer[byteOffset]=(byte) (sample & 0xFF); + } else { + buffer[byteOffset++]=(byte) (sample & 0xFF); + buffer[byteOffset++]=(byte) ((sample >>> 8) & 0xFF); + buffer[byteOffset]=(byte) (sample >> 16); + } + } + + + /** + * Converts a 32 bit sample of type int to 4 bytes in an array. + * sample is interpreted as signed (as Java does). + *

+ * For little endian, buffer[byteOffset] is filled with lowest byte of sample, + * and buffer[byteOffset+3] is filled with the high byte of sample + sign bit. + *

For big endian, this is reversed. + *

This is a reference function. + */ + public static void intToBytes32(int sample, byte[] buffer, int byteOffset, boolean bigEndian) { + if (bigEndian) { + buffer[byteOffset++]=(byte) (sample >> 24); + buffer[byteOffset++]=(byte) ((sample >>> 16) & 0xFF); + buffer[byteOffset++]=(byte) ((sample >>> 8) & 0xFF); + buffer[byteOffset]=(byte) (sample & 0xFF); + } else { + buffer[byteOffset++]=(byte) (sample & 0xFF); + buffer[byteOffset++]=(byte) ((sample >>> 8) & 0xFF); + buffer[byteOffset++]=(byte) ((sample >>> 16) & 0xFF); + buffer[byteOffset]=(byte) (sample >> 24); + } + } + + + /////////////////////// ULAW /////////////////////////////////////////// + + private static final boolean ZEROTRAP=true; + private static final short BIAS=0x84; + private static final int CLIP=32635; + private static final int exp_lut1[] ={ + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 + }; + + + /** + * Converts a linear signed 16bit sample to a uLaw byte. + * Ported to Java by fb. + *
Originally by:
+ * Craig Reese: IDA/Supercomputing Research Center
+ * Joe Campbell: Department of Defense
+ * 29 September 1989
+ */ + public static byte linear2ulaw(int sample) { + int sign, exponent, mantissa, ulawbyte; + + if (sample>32767) sample=32767; + else if (sample<-32768) sample=-32768; + /* Get the sample into sign-magnitude. */ + sign = (sample >> 8) & 0x80; /* set aside the sign */ + if (sign != 0) sample = -sample; /* get magnitude */ + if (sample > CLIP) sample = CLIP; /* clip the magnitude */ + + /* Convert from 16 bit linear to ulaw. */ + sample = sample + BIAS; + exponent = exp_lut1[(sample >> 7) & 0xFF]; + mantissa = (sample >> (exponent + 3)) & 0x0F; + ulawbyte = ~(sign | (exponent << 4) | mantissa); + if (ZEROTRAP) + if (ulawbyte == 0) ulawbyte = 0x02; /* optional CCITT trap */ + return((byte) ulawbyte); + } + + /* u-law to linear conversion table */ + private static short[] u2l = { + -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, + -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, + -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, + -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 + }; + public static short ulaw2linear(byte ulawbyte) { + return u2l[ulawbyte & 0xFF]; + } + + + + /** + * Converts a buffer of signed 16bit big endian samples to uLaw. + * The uLaw bytes overwrite the original 16 bit values. + * The first byte-offset of the uLaw bytes is byteOffset. + * It will be written sampleCount/2 bytes. + */ + public static void pcm162ulaw(byte[] buffer, int byteOffset, int sampleCount, boolean bigEndian) { + int shortIndex=byteOffset; + int ulawIndex=shortIndex; + if (bigEndian) { + while (sampleCount>0) { + buffer[ulawIndex++]=linear2ulaw + (bytesToInt16(buffer[shortIndex], buffer[shortIndex+1])); + shortIndex++; + shortIndex++; + sampleCount--; + } + } else { + while (sampleCount>0) { + buffer[ulawIndex++]=linear2ulaw + (bytesToInt16(buffer[shortIndex+1], buffer[shortIndex])); + shortIndex++; + shortIndex++; + sampleCount--; + } + } + } + + /** + * Fills outBuffer with ulaw samples. + * reading starts from inBuffer[inByteOffset]. + * writing starts at outBuffer[outByteOffset]. + * There will be sampleCount*2 bytes read from inBuffer; + * There will be sampleCount bytes written to outBuffer. + */ + public static void pcm162ulaw(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, + int sampleCount, boolean bigEndian) { + int shortIndex=inByteOffset; + int ulawIndex=outByteOffset; + if (bigEndian) { + while (sampleCount>0) { + outBuffer[ulawIndex++]=linear2ulaw + (bytesToInt16(inBuffer[shortIndex], inBuffer[shortIndex+1])); + shortIndex++; + shortIndex++; + sampleCount--; + } + } else { + while (sampleCount>0) { + outBuffer[ulawIndex++]=linear2ulaw + (bytesToInt16(inBuffer[shortIndex+1], inBuffer[shortIndex])); + shortIndex++; + shortIndex++; + sampleCount--; + } + } + } + + // TODO: either direct 8bit pcm to ulaw, or better conversion from 8bit to 16bit + /** + * Converts a buffer of 8bit samples to uLaw. + * The uLaw bytes overwrite the original 8 bit values. + * The first byte-offset of the uLaw bytes is byteOffset. + * It will be written sampleCount bytes. + */ + public static void pcm82ulaw(byte[] buffer, int byteOffset, int sampleCount, boolean signed) { + sampleCount+=byteOffset; + if (signed) { + for (int i=byteOffset; ibytes written to outBuffer. + */ + public static void pcm82ulaw(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, int sampleCount, boolean signed) { + int ulawIndex=outByteOffset; + int pcmIndex=inByteOffset; + if (signed) { + while (sampleCount>0) { + outBuffer[ulawIndex++]=linear2ulaw(inBuffer[pcmIndex++] << 8); + sampleCount--; + } + } else { + while (sampleCount>0) { + outBuffer[ulawIndex++]=linear2ulaw(((byte) (inBuffer[pcmIndex++]+128)) << 8); + sampleCount--; + } + } + } + + /** + * Fills outBuffer with pcm signed 16 bit samples. + * reading starts from inBuffer[inByteOffset]. + * writing starts at outBuffer[outByteOffset]. + * There will be sampleCount bytes read from inBuffer; + * There will be sampleCount*2 bytes written to outBuffer. + */ + public static void ulaw2pcm16(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, + int sampleCount, boolean bigEndian) { + int shortIndex=outByteOffset; + int ulawIndex=inByteOffset; + while (sampleCount>0) { + intToBytes16 + (u2l[inBuffer[ulawIndex++] & 0xFF], outBuffer, shortIndex++, bigEndian); + shortIndex++; + sampleCount--; + } + } + + + // TODO: either direct 8bit pcm to ulaw, or better conversion from 8bit to 16bit + /** + * Inplace-conversion of a ulaw buffer to 8bit samples. + * The 8bit bytes overwrite the original ulaw values. + * The first byte-offset of the uLaw bytes is byteOffset. + * It will be written sampleCount bytes. + */ + public static void ulaw2pcm8(byte[] buffer, int byteOffset, int sampleCount, boolean signed) { + sampleCount+=byteOffset; + if (signed) { + for (int i=byteOffset; i> 8) & 0xFF); + } + } else { + for (int i=byteOffset; i>8)+128); + } + } + } + + /** + * Fills outBuffer with ulaw samples. + * reading starts from inBuffer[inByteOffset]. + * writing starts at outBuffer[outByteOffset]. + * There will be sampleCount bytes written to outBuffer. + */ + public static void ulaw2pcm8(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, int sampleCount, boolean signed) { + int ulawIndex=inByteOffset; + int pcmIndex=outByteOffset; + if (signed) { + while (sampleCount>0) { + outBuffer[pcmIndex++]= + (byte) ((u2l[inBuffer[ulawIndex++] & 0xFF] >> 8) & 0xFF); + sampleCount--; + } + } else { + while (sampleCount>0) { + outBuffer[pcmIndex++]= + (byte) ((u2l[inBuffer[ulawIndex++] & 0xFF]>>8)+128); + sampleCount--; + } + } + } + + + //////////////////// ALAW //////////////////////////// + + + /* + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + * + * linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law + * + * linear2alaw() accepts an 16-bit integer and encodes it as A-law data. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ + private static final byte QUANT_MASK = 0xf; /* Quantization field mask. */ + private static final byte SEG_SHIFT = 4; /* Left shift for segment number. */ + private static final short[] seg_end = { + 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF + }; + + public static byte linear2alaw(short pcm_val) /* 2's complement (16-bit range) */ + { + byte mask; + byte seg=8; + byte aval; + + if (pcm_val >= 0) { + mask = (byte) 0xD5; /* sign (7th) bit = 1 */ + } else { + mask = 0x55; /* sign bit = 0 */ + pcm_val = (short) (-pcm_val - 8); + } + + /* Convert the scaled magnitude to segment number. */ + for (int i = 0; i < 8; i++) { + if (pcm_val <= seg_end[i]) { + seg=(byte) i; + break; + } + } + + /* Combine the sign, segment, and quantization bits. */ + if (seg >= 8) /* out of range, return maximum value. */ + return (byte) ((0x7F ^ mask) & 0xFF); + else { + aval = (byte) (seg << SEG_SHIFT); + if (seg < 2) + aval |= (pcm_val >> 4) & QUANT_MASK; + else + aval |= (pcm_val >> (seg + 3)) & QUANT_MASK; + return (byte) ((aval ^ mask) & 0xFF); + } + } + + private static short[] a2l = { + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, + -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, + -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, + -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, + 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, + 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, + 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, + 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, + 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, + 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, + 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, + 344, 328, 376, 360, 280, 264, 312, 296, + 472, 456, 504, 488, 408, 392, 440, 424, + 88, 72, 120, 104, 24, 8, 56, 40, + 216, 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, + 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, + 688, 656, 752, 720, 560, 528, 624, 592, + 944, 912, 1008, 976, 816, 784, 880, 848 + }; + + public static short alaw2linear(byte ulawbyte) { + return a2l[ulawbyte & 0xFF]; + } + + /** + * Converts a buffer of signed 16bit big endian samples to uLaw. + * The uLaw bytes overwrite the original 16 bit values. + * The first byte-offset of the uLaw bytes is byteOffset. + * It will be written sampleCount/2 bytes. + */ + public static void pcm162alaw(byte[] buffer, int byteOffset, int sampleCount, boolean bigEndian) { + int shortIndex=byteOffset; + int alawIndex=shortIndex; + if (bigEndian) { + while (sampleCount>0) { + buffer[alawIndex++]= + linear2alaw(bytesToShort16 + (buffer[shortIndex], buffer[shortIndex+1])); + shortIndex++; + shortIndex++; + sampleCount--; + } + } else { + while (sampleCount>0) { + buffer[alawIndex++]= + linear2alaw(bytesToShort16 + (buffer[shortIndex+1], buffer[shortIndex])); + shortIndex++; + shortIndex++; + sampleCount--; + } + } + } + + /** + * Fills outBuffer with alaw samples. + * reading starts from inBuffer[inByteOffset]. + * writing starts at outBuffer[outByteOffset]. + * There will be sampleCount*2 bytes read from inBuffer; + * There will be sampleCount bytes written to outBuffer. + */ + public static void pcm162alaw(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, int sampleCount, boolean bigEndian) { + int shortIndex=inByteOffset; + int alawIndex=outByteOffset; + if (bigEndian) { + while (sampleCount>0) { + outBuffer[alawIndex++]=linear2alaw + (bytesToShort16(inBuffer[shortIndex], inBuffer[shortIndex+1])); + shortIndex++; + shortIndex++; + sampleCount--; + } + } else { + while (sampleCount>0) { + outBuffer[alawIndex++]=linear2alaw + (bytesToShort16(inBuffer[shortIndex+1], inBuffer[shortIndex])); + shortIndex++; + shortIndex++; + sampleCount--; + } + } + } + + /** + * Converts a buffer of 8bit samples to alaw. + * The alaw bytes overwrite the original 8 bit values. + * The first byte-offset of the aLaw bytes is byteOffset. + * It will be written sampleCount bytes. + */ + public static void pcm82alaw(byte[] buffer, int byteOffset, int sampleCount, boolean signed) { + sampleCount+=byteOffset; + if (signed) { + for (int i=byteOffset; ibytes written to outBuffer. + */ + public static void pcm82alaw(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, int sampleCount, boolean signed) { + int alawIndex=outByteOffset; + int pcmIndex=inByteOffset; + if (signed) { + while (sampleCount>0) { + outBuffer[alawIndex++]= + linear2alaw((short) (inBuffer[pcmIndex++] << 8)); + sampleCount--; + } + } else { + while (sampleCount>0) { + outBuffer[alawIndex++]= + linear2alaw((short) (((byte) (inBuffer[pcmIndex++]+128)) << 8)); + sampleCount--; + } + } + } + + + + /** + * Converts an alaw buffer to 8bit pcm samples + * The 8bit bytes overwrite the original alaw values. + * The first byte-offset of the aLaw bytes is byteOffset. + * It will be written sampleCount bytes. + */ + public static void alaw2pcm8(byte[] buffer, int byteOffset, int sampleCount, boolean signed) { + sampleCount+=byteOffset; + if (signed) { + for (int i=byteOffset; i> 8) & 0xFF); + } + } else { + for (int i=byteOffset; i>8)+128); + } + } + } + + /** + * Fills outBuffer with alaw samples. + * reading starts from inBuffer[inByteOffset]. + * writing starts at outBuffer[outByteOffset]. + * There will be sampleCount bytes written to outBuffer. + */ + public static void alaw2pcm8(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, int sampleCount, boolean signed) { + int alawIndex=inByteOffset; + int pcmIndex=outByteOffset; + if (signed) { + while (sampleCount>0) { + outBuffer[pcmIndex++]= + (byte) ((a2l[inBuffer[alawIndex++] & 0xFF] >> 8) & 0xFF); + sampleCount--; + } + } else { + while (sampleCount>0) { + outBuffer[pcmIndex++]= + (byte) ((a2l[inBuffer[alawIndex++] & 0xFF]>>8)+128); + sampleCount--; + } + } + } + + /** + * Fills outBuffer with pcm signed 16 bit samples. + * reading starts from inBuffer[inByteOffset]. + * writing starts at outBuffer[outByteOffset]. + * There will be sampleCount bytes read from inBuffer; + * There will be sampleCount*2 bytes written to outBuffer. + */ + public static void alaw2pcm16(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, + int sampleCount, boolean bigEndian) { + int shortIndex=outByteOffset; + int alawIndex=inByteOffset; + while (sampleCount>0) { + intToBytes16 + (a2l[inBuffer[alawIndex++] & 0xFF], outBuffer, shortIndex++, bigEndian); + shortIndex++; + sampleCount--; + } + } + + //////////////////////// cross conversion alaw <-> ulaw //////////////////////////////////////// + + private static byte[] u2a = { + -86, -85, -88, -87, -82, -81, -84, -83, -94, -93, -96, -95, -90, -89, -92, -91, + -70, -69, -72, -71, -66, -65, -68, -67, -78, -77, -80, -79, -74, -73, -76, -75, + -118, -117, -120, -119, -114, -113, -116, -115, -126, -125, -128, -127, -122, -121, -124, -123, + -101, -104, -103, -98, -97, -100, -99, -110, -109, -112, -111, -106, -105, -108, -107, -22, + -24, -23, -18, -17, -20, -19, -30, -29, -32, -31, -26, -25, -28, -27, -6, -8, + -2, -1, -4, -3, -14, -13, -16, -15, -10, -9, -12, -11, -53, -55, -49, -51, + -62, -61, -64, -63, -58, -57, -60, -59, -38, -37, -40, -39, -34, -33, -36, -35, + -46, -46, -45, -45, -48, -48, -47, -47, -42, -42, -41, -41, -44, -44, -43, -43, + 42, 43, 40, 41, 46, 47, 44, 45, 34, 35, 32, 33, 38, 39, 36, 37, + 58, 59, 56, 57, 62, 63, 60, 61, 50, 51, 48, 49, 54, 55, 52, 53, + 10, 11, 8, 9, 14, 15, 12, 13, 2, 3, 0, 1, 6, 7, 4, 5, + 27, 24, 25, 30, 31, 28, 29, 18, 19, 16, 17, 22, 23, 20, 21, 106, + 104, 105, 110, 111, 108, 109, 98, 99, 96, 97, 102, 103, 100, 101, 122, 120, + 126, 127, 124, 125, 114, 115, 112, 113, 118, 119, 116, 117, 75, 73, 79, 77, + 66, 67, 64, 65, 70, 71, 68, 69, 90, 91, 88, 89, 94, 95, 92, 93, + 82, 82, 83, 83, 80, 80, 81, 81, 86, 86, 87, 87, 84, 84, 85, 85, + }; + + public static byte ulaw2alaw(byte sample) { + return u2a[sample & 0xFF]; + } + + /** + * Converts a buffer of uLaw samples to aLaw. + */ + public static void ulaw2alaw(byte[] buffer, int byteOffset, int sampleCount) { + sampleCount+=byteOffset; + for (int i=byteOffset; i0) { + outBuffer[alawIndex++]=u2a[inBuffer[ulawIndex++] & 0xFF]; + sampleCount--; + } + } + + private static byte[] a2u = { + -86, -85, -88, -87, -82, -81, -84, -83, -94, -93, -96, -95, -90, -89, -92, -91, + -71, -70, -73, -72, -67, -66, -69, -68, -79, -78, -80, -80, -75, -74, -77, -76, + -118, -117, -120, -119, -114, -113, -116, -115, -126, -125, -128, -127, -122, -121, -124, -123, + -102, -101, -104, -103, -98, -97, -100, -99, -110, -109, -112, -111, -106, -105, -108, -107, + -30, -29, -32, -31, -26, -25, -28, -27, -35, -35, -36, -36, -33, -33, -34, -34, + -12, -10, -16, -14, -4, -2, -8, -6, -22, -21, -24, -23, -18, -17, -20, -19, + -56, -55, -58, -57, -52, -51, -54, -53, -64, -63, -65, -65, -60, -59, -62, -61, + -42, -41, -44, -43, -38, -37, -40, -39, -49, -49, -50, -50, -46, -45, -48, -47, + 42, 43, 40, 41, 46, 47, 44, 45, 34, 35, 32, 33, 38, 39, 36, 37, + 57, 58, 55, 56, 61, 62, 59, 60, 49, 50, 48, 48, 53, 54, 51, 52, + 10, 11, 8, 9, 14, 15, 12, 13, 2, 3, 0, 1, 6, 7, 4, 5, + 26, 27, 24, 25, 30, 31, 28, 29, 18, 19, 16, 17, 22, 23, 20, 21, + 98, 99, 96, 97, 102, 103, 100, 101, 93, 93, 92, 92, 95, 95, 94, 94, + 116, 118, 112, 114, 124, 126, 120, 122, 106, 107, 104, 105, 110, 111, 108, 109, + 72, 73, 70, 71, 76, 77, 74, 75, 64, 65, 63, 63, 68, 69, 66, 67, + 86, 87, 84, 85, 90, 91, 88, 89, 79, 79, 78, 78, 82, 83, 80, 81, + }; + + public static byte alaw2ulaw(byte sample) { + return a2u[sample & 0xFF]; + } + + /** + * Converts a buffer of aLaw samples to uLaw. + * The uLaw bytes overwrite the original aLaw values. + * The first byte-offset of the uLaw bytes is byteOffset. + * It will be written sampleCount bytes. + */ + public static void alaw2ulaw(byte[] buffer, int byteOffset, int sampleCount) { + sampleCount+=byteOffset; + for (int i=byteOffset; ibytes written to outBuffer. + */ + public static void alaw2ulaw(byte[] inBuffer, int inByteOffset, + byte[] outBuffer, int outByteOffset, int sampleCount) { + int ulawIndex=outByteOffset; + int alawIndex=inByteOffset; + while (sampleCount>0) { + outBuffer[ulawIndex++]=a2u[inBuffer[alawIndex++] & 0xFF]; + sampleCount--; + } + } + + + //////////////////////// high level methods ///////////////////////////////////////////////// + + /* + * !! Here, unlike other functions in this class, the length is + * in bytes rather than samples !! + */ + public static void changeOrderOrSign(byte[] buffer, int nOffset, + int nByteLength, int nBytesPerSample) { + switch (nBytesPerSample) { + case 1: + convertSign8(buffer, nOffset, nByteLength); + break; + + case 2: + swapOrder16(buffer, nOffset, nByteLength / 2); + break; + + case 3: + swapOrder24(buffer, nOffset, nByteLength / 3); + break; + + case 4: + swapOrder32(buffer, nOffset, nByteLength / 4); + break; + } + } + + + + /* + * !! Here, unlike other functions in this class, the length is + * in bytes rather than samples !! + */ + public static void changeOrderOrSign( + byte[] inBuffer, int nInOffset, + byte[] outBuffer, int nOutOffset, + int nByteLength, int nBytesPerSample) { + switch (nBytesPerSample) { + case 1: + convertSign8( + inBuffer, nInOffset, + outBuffer, nOutOffset, + nByteLength); + break; + + case 2: + swapOrder16( + inBuffer, nInOffset, + outBuffer, nOutOffset, + nByteLength / 2); + break; + + case 3: + swapOrder24( + inBuffer, nInOffset, + outBuffer, nOutOffset, + nByteLength / 3); + break; + + case 4: + swapOrder32( + inBuffer, nInOffset, + outBuffer, nOutOffset, + nByteLength / 4); + break; + } + } + + + ///////////////// Annexe: how the arrays were created. ////////////////////////////////// + + /* + * Converts a uLaw byte to a linear signed 16bit sample. + * Ported to Java by fb. + *
Originally by:
+ * + * Craig Reese: IDA/Supercomputing Research Center
+ * 29 September 1989
+ * + * References:
+ *

    + *
  1. CCITT Recommendation G.711 (very difficult to follow)
  2. + *
  3. MIL-STD-188-113,"Interoperability and Performance Standards + * for Analog-to_Digital Conversion Techniques," + * 17 February 1987
  4. + *
+ */ + /* + private static final int exp_lut2[] = { + 0,132,396,924,1980,4092,8316,16764 +}; + + public static short _ulaw2linear(int ulawbyte) { + int sign, exponent, mantissa, sample; + + ulawbyte = ~ulawbyte; + sign = (ulawbyte & 0x80); + exponent = (ulawbyte >> 4) & 0x07; + mantissa = ulawbyte & 0x0F; + sample = exp_lut2[exponent] + (mantissa << (exponent + 3)); + if (sign != 0) sample = -sample; + return((short) sample); +}*/ + + + /* u- to A-law conversions: copied from CCITT G.711 specifications */ + /* + private static byte[] _u2a = { + 1, 1, 2, 2, 3, 3, 4, 4, + 5, 5, 6, 6, 7, 7, 8, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 27, 29, 31, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, + 46, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 81, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, (byte) 128}; + */ + + /* u-law to A-law conversion */ + /* + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + */ + /* + public static byte _ulaw2alaw(byte sample) { + sample &= 0xff; + return (byte) (((sample & 0x80)!=0) ? (0xD5 ^ (_u2a[(0x7F ^ sample) & 0x7F] - 1)) : + (0x55 ^ (_u2a[(0x7F ^ sample) & 0x7F] - 1))); +}*/ + + /* A- to u-law conversions */ + /* + private static byte[] _a2u = { + 1, 3, 5, 7, 9, 11, 13, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 32, 33, 33, 34, 34, 35, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 48, 49, 49, + 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 64, + 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}; + */ + + /* + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + */ + /* + public static byte _alaw2ulaw(byte sample) { + sample &= 0xff; + return (byte) (((sample & 0x80)!=0) ? (0xFF ^ _a2u[(sample ^ 0xD5) & 0x7F]) : + (0x7F ^ _a2u[(sample ^ 0x55) & 0x7F])); +} + + public static void print_a2u() { + System.out.println("\tprivate static byte[] a2u = {"); + for (int i=-128; i<128; i++) { + if (((i+128) % 16)==0) { + System.out.print("\t\t"); + } + byte b=(byte) i; + System.out.print(_alaw2ulaw(b)+", "); + if (((i+128) % 16)==15) { + System.out.println(""); + } +} + System.out.println("\t};"); +} + + public static void print_u2a() { + System.out.println("\tprivate static byte[] u2a = {"); + for (int i=-128; i<128; i++) { + if (((i+128) % 16)==0) { + System.out.print("\t\t"); + } + byte b=(byte) i; + System.out.print(_ulaw2alaw(b)+", "); + if (((i+128) % 16)==15) { + System.out.println(""); + } +} + System.out.println("\t};"); +} + */ + +} + + +/*** TConversionTool.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/TVolumeUtils.java b/songdbj/org/tritonus/share/sampled/TVolumeUtils.java new file mode 100644 index 0000000000..0eaf1388da --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/TVolumeUtils.java @@ -0,0 +1,55 @@ +/* + * TVolumeUtils.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled; + + + +public class TVolumeUtils +{ + private static final double FACTOR1 = 20.0 / Math.log(10.0); + private static final double FACTOR2 = 1 / 20.0; + + + + public static double lin2log(double dLinear) + { + return FACTOR1 * Math.log(dLinear); + } + + + + public static double log2lin(double dLogarithmic) + { + return Math.pow(10.0, dLogarithmic * FACTOR2); + } +} + + + +/*** TVolumeUtils.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/convert/TAsynchronousFilteredAudioInputStream.java b/songdbj/org/tritonus/share/sampled/convert/TAsynchronousFilteredAudioInputStream.java new file mode 100644 index 0000000000..83349439eb --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/convert/TAsynchronousFilteredAudioInputStream.java @@ -0,0 +1,256 @@ +/* + * TAsynchronousFilteredAudioInputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999, 2000 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.convert; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; + +import org.tritonus.share.TDebug; +import org.tritonus.share.TCircularBuffer; + + + +/** Base class for asynchronus converters. + This class serves as base class for + converters that do not have a fixed + ratio between the size of a block of input + data and the size of a block of output data. + These types of converters therefore need an + internal buffer, which is realized in this + class. + + @author Matthias Pfisterer +*/ +public abstract class TAsynchronousFilteredAudioInputStream +extends TAudioInputStream +implements TCircularBuffer.Trigger +{ + private static final int DEFAULT_BUFFER_SIZE = 327670; + private static final int DEFAULT_MIN_AVAILABLE = 4096; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + + private TCircularBuffer m_circularBuffer; + private int m_nMinAvailable; + private byte[] m_abSingleByte; + + + + /** Constructor. + This constructor uses the default buffer size and the default + min available amount. + + @param lLength length of this stream in frames. May be + AudioSystem.NOT_SPECIFIED. + */ + public TAsynchronousFilteredAudioInputStream(AudioFormat outputFormat, long lLength) + { + this(outputFormat, lLength, + DEFAULT_BUFFER_SIZE, + DEFAULT_MIN_AVAILABLE); + } + + + + /** Constructor. + With this constructor, the buffer size and the minimum + available amount can be specified as parameters. + + @param lLength length of this stream in frames. May be + AudioSystem.NOT_SPECIFIED. + + @param nBufferSize size of the circular buffer in bytes. + */ + public TAsynchronousFilteredAudioInputStream( + AudioFormat outputFormat, long lLength, + int nBufferSize, + int nMinAvailable) + { + /* The usage of a ByteArrayInputStream is a hack. + * (the infamous "JavaOne hack", because I did it on June + * 6th 2000 in San Francisco, only hours before a + * JavaOne session where I wanted to show mp3 playback + * with Java Sound.) It is necessary because in the FCS + * version of the Sun jdk1.3, the constructor of + * AudioInputStream throws an exception if its first + * argument is null. So we have to pass a dummy non-null + * value. + */ + super(new ByteArrayInputStream(EMPTY_BYTE_ARRAY), + outputFormat, + lLength); + if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.(): begin"); } + m_circularBuffer = new TCircularBuffer( + nBufferSize, + false, // blocking read + true, // blocking write + this); // trigger + m_nMinAvailable = nMinAvailable; + if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.(): end"); } + } + + + /** Returns the circular buffer. + */ + protected TCircularBuffer getCircularBuffer() + { + return m_circularBuffer; + } + + + + /** Check if writing more data to the circular buffer is recommanded. + This checks the available write space in the circular buffer + against the minimum available property. If the available write + space is greater than th minimum available property, more + writing is encouraged, so this method returns true. + Note that this is only a hint to subclasses. However, + it is an important hint. + + @return true if more writing to the circular buffer is + recommanden. Otherwise, false is returned. + */ + protected boolean writeMore() + { + return getCircularBuffer().availableWrite() > m_nMinAvailable; + } + + + + public int read() + throws IOException + { + // if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.read(): begin"); } + int nByte = -1; + if (m_abSingleByte == null) + { + m_abSingleByte = new byte[1]; + } + int nReturn = read(m_abSingleByte); + if (nReturn == -1) + { + nByte = -1; + } + else + { + //$$fb 2001-04-14 nobody really knows that... + nByte = m_abSingleByte[0] & 0xFF; + } + // if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.read(): end"); } + return nByte; + } + + + + public int read(byte[] abData) + throws IOException + { + if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.read(byte[]): begin"); } + int nRead = read(abData, 0, abData.length); + if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.read(byte[]): end"); } + return nRead; + } + + + + public int read(byte[] abData, int nOffset, int nLength) + throws IOException + { + if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.read(byte[], int, int): begin"); } + //$$fb 2001-04-22: this returns at maximum circular buffer + // length. This is not very efficient... + //$$fb 2001-04-25: we should check that we do not exceed getFrameLength() ! + int nRead = m_circularBuffer.read(abData, nOffset, nLength); + if (TDebug.TraceAudioConverter) { TDebug.out("TAsynchronousFilteredAudioInputStream.read(byte[], int, int): end"); } + return nRead; + } + + + + public long skip(long lSkip) + throws IOException + { + // TODO: this is quite inefficient + for (long lSkipped = 0; lSkipped < lSkip; lSkipped++) + { + int nReturn = read(); + if (nReturn == -1) + { + return lSkipped; + } + } + return lSkip; + } + + + + public int available() + throws IOException + { + return m_circularBuffer.availableRead(); + } + + + + public void close() + throws IOException + { + m_circularBuffer.close(); + } + + + + public boolean markSupported() + { + return false; + } + + + + public void mark(int nReadLimit) + { + } + + + + public void reset() + throws IOException + { + throw new IOException("mark not supported"); + } +} + + + +/*** TAsynchronousFilteredAudioInputStream.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/convert/TAudioInputStream.java b/songdbj/org/tritonus/share/sampled/convert/TAudioInputStream.java new file mode 100644 index 0000000000..d84530e115 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/convert/TAudioInputStream.java @@ -0,0 +1,120 @@ +/* + * TAudioInputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2003 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.convert; + +import java.io.InputStream; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; + + +/** AudioInputStream base class. This class implements "dynamic" + properties. "Dynamic" properties are properties that may change + during the life time of the objects. This is typically used to + pass information like the current frame number, volume of subbands + and similar values. "Dynamic" properties are different from + properties in AudioFormat and AudioFileFormat, which are + considered "static", as they aren't allowed to change after + creating of the object, thereby maintaining the immutable + character of these classes. +*/ + +public class TAudioInputStream +extends AudioInputStream +{ + private Map m_properties; + private Map m_unmodifiableProperties; + + + /** Constructor without properties. + Creates an empty properties map. + */ + public TAudioInputStream(InputStream inputStream, + AudioFormat audioFormat, + long lLengthInFrames) + { + super(inputStream, audioFormat, lLengthInFrames); + initMaps(new HashMap()); + } + + + /** Constructor with properties. + The passed properties map is not copied. This allows subclasses + to change values in the map after creation, and the changes are + reflected in the map the application program can obtain. + */ + public TAudioInputStream(InputStream inputStream, + AudioFormat audioFormat, + long lLengthInFrames, + Map properties) + { + super(inputStream, audioFormat, lLengthInFrames); + initMaps(properties); + } + + + private void initMaps(Map properties) + { + /* Here, we make a shallow copy of the map. It's unclear if this + is sufficient (of if a deep copy should be made). + */ + m_properties = properties; + m_unmodifiableProperties = Collections.unmodifiableMap(m_properties); + } + + + /** Obtain a Map containing the properties. This method returns a + Map that cannot be modified by the application program, but + reflects changes to the map made by the implementation. + + @return a map containing the properties. + */ + public Map properties() + { + return m_unmodifiableProperties; + } + + + /** Set a property. Unlike in AudioFormat and AudioFileFormat, + this method may be used anywhere by subclasses - it is not + restricted to be used in the constructor. + */ + protected void setProperty(String key, Object value) + { + m_properties.put(key, value); + } +} + + + +/*** TAudioInputStream.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/convert/TEncodingFormatConversionProvider.java b/songdbj/org/tritonus/share/sampled/convert/TEncodingFormatConversionProvider.java new file mode 100644 index 0000000000..6b83403c43 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/convert/TEncodingFormatConversionProvider.java @@ -0,0 +1,129 @@ +/* + * TEncodingFormatConversionProvider.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Florian Bomers + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.convert; + +import java.util.Collection; +import java.util.Iterator; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; + +import org.tritonus.share.TDebug; +import org.tritonus.share.ArraySet; + + +// this class depends on handling of AudioSystem.NOT_SPECIFIED in AudioFormat.matches() + +/** + * This is a base class for FormatConversionProviders that only + * change the encoding, i.e. they never + *
    + *
  • change the sample size in bits without changing the encoding + *
  • change the sample rate + *
  • change the number of channels + *
+ *

It is assumed that each source format can be encoded to all + * target formats. + *

In the sourceFormats and targetFormats collections that are passed to + * the constructor of this class, fields may be set to AudioSystem.NOT_SPECIFIED. + * This means that it handles all values of that field, but cannot change it. + *

This class prevents that a conversion is done (e.g. for sample rates), + * because the overriding class specified AudioSystem.NOT_SPECIFIED as sample rate, + * meaning it handles all sample rates. + *

Overriding classes must implement at least + * AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream) + * and provide a constructor that calls the protected constructor of this class. + * + * @author Florian Bomers + */ +public abstract class TEncodingFormatConversionProvider +extends TSimpleFormatConversionProvider +{ + protected TEncodingFormatConversionProvider( + Collection sourceFormats, + Collection targetFormats) + { + super(sourceFormats, targetFormats); + } + + + + /** + * This implementation assumes that the converter can convert + * from each of its source formats to each of its target + * formats. If this is not the case, the converter has to + * override this method. + *

When conversion is supported, for every target encoding, + * the fields sample size in bits, channels and sample rate are checked: + *

    + *
  • When a field in both the source and target format is AudioSystem.NOT_SPECIFIED, + * one instance of that targetFormat is returned with this field set to AudioSystem.NOT_SPECIFIED. + *
  • When a field in sourceFormat is set and it is AudioSystem.NOT_SPECIFIED in the target format, + * the value of the field of source format is set in the returned format. + *
  • The same applies for the other way round. + *
+ * For this, replaceNotSpecified(sourceFormat, targetFormat) in the base + * class TSimpleFormatConversionProvider is used - and accordingly, the frameSize + * is recalculated with getFrameSize(...) if a field with AudioSystem.NOT_SPECIFIED + * is replaced. Inheriting classes may wish to override this method if the + * default mode of calculating the frame size is not appropriate. + */ + public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat) { + if (TDebug.TraceAudioConverter) { + TDebug.out(">TEncodingFormatConversionProvider.getTargetFormats(AudioFormat.Encoding, AudioFormat):"); + TDebug.out("checking if conversion possible"); + TDebug.out("from: " + sourceFormat); + TDebug.out("to: " + targetEncoding); + } + if (isConversionSupported(targetEncoding, sourceFormat)) { + // TODO: check that no duplicates may occur... + ArraySet result=new ArraySet(); + Iterator iterator = getCollectionTargetFormats().iterator(); + while (iterator.hasNext()) { + AudioFormat targetFormat = iterator.next(); + targetFormat=replaceNotSpecified(sourceFormat, targetFormat); + result.add(targetFormat); + } + if (TDebug.TraceAudioConverter) { + TDebug.out("< returning "+result.size()+" elements."); + } + return result.toArray(EMPTY_FORMAT_ARRAY); + } else { + if (TDebug.TraceAudioConverter) { + TDebug.out("< returning empty array."); + } + return EMPTY_FORMAT_ARRAY; + } + } + +} + +/*** TEncodingFormatConversionProvider.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/convert/TFormatConversionProvider.java b/songdbj/org/tritonus/share/sampled/convert/TFormatConversionProvider.java new file mode 100644 index 0000000000..eaec65bb06 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/convert/TFormatConversionProvider.java @@ -0,0 +1,170 @@ +/* + * TFormatConversionProvider.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999, 2000 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.convert; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.spi.FormatConversionProvider; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.AudioFormats; + + + +/** Base class for all conversion providers of Tritonus. + * + * @author Matthias Pfisterer + */ +public abstract class TFormatConversionProvider +extends FormatConversionProvider +{ + protected static final AudioFormat.Encoding[] EMPTY_ENCODING_ARRAY = new AudioFormat.Encoding[0]; + protected static final AudioFormat[] EMPTY_FORMAT_ARRAY = new AudioFormat[0]; + + + + // $$fb2000-10-04: use AudioSystem.NOT_SPECIFIED for all fields. + public AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream audioInputStream) + { + AudioFormat sourceFormat = audioInputStream.getFormat(); + AudioFormat targetFormat = new AudioFormat( + targetEncoding, + AudioSystem.NOT_SPECIFIED, // sample rate + AudioSystem.NOT_SPECIFIED, // sample size in bits + AudioSystem.NOT_SPECIFIED, // channels + AudioSystem.NOT_SPECIFIED, // frame size + AudioSystem.NOT_SPECIFIED, // frame rate + sourceFormat.isBigEndian()); // big endian + if (TDebug.TraceAudioConverter) + { + TDebug.out("TFormatConversionProvider.getAudioInputStream(AudioFormat.Encoding, AudioInputStream):"); + TDebug.out("trying to convert to " + targetFormat); + } + return getAudioInputStream(targetFormat, audioInputStream); + } + + + + /** + * WARNING: this method uses getTargetFormats(AudioFormat.Encoding, AudioFormat) + * which may create infinite loops if the latter is overwritten. + *

+ * This method is overwritten here to make use of org.tritonus.share.sampled.AudioFormats.matches + * and is considered temporary until AudioFormat.matches is corrected in the JavaSound API. + */ + /* $$mp: if we decide to use getMatchingFormat(), this method should be + implemented by simply calling getMatchingFormat() and comparing the + result against null. + */ + public boolean isConversionSupported( + AudioFormat targetFormat, + AudioFormat sourceFormat) + { + if (TDebug.TraceAudioConverter) + { + TDebug.out(">TFormatConversionProvider.isConversionSupported(AudioFormat, AudioFormat):"); + TDebug.out("class: "+getClass().getName()); + TDebug.out("checking if conversion possible"); + TDebug.out("from: " + sourceFormat); + TDebug.out("to: " + targetFormat); + } + AudioFormat[] aTargetFormats = getTargetFormats(targetFormat.getEncoding(), sourceFormat); + for (int i = 0; i < aTargetFormats.length; i++) + { + if (TDebug.TraceAudioConverter) + { + TDebug.out("checking against possible target format: " + aTargetFormats[i]); + } + if (aTargetFormats[i] != null + && AudioFormats.matches(aTargetFormats[i], targetFormat)) + { + if (TDebug.TraceAudioConverter) + { + TDebug.out("getTargetFormats(AudioFormat.Encoding, AudioFormat) + * which may create infinite loops if the latter is overwritten. + *

+ * This method is overwritten here to make use of org.tritonus.share.sampled.AudioFormats.matches + * and is considered temporary until AudioFormat.matches is corrected in the JavaSound API. + */ + public AudioFormat getMatchingFormat( + AudioFormat targetFormat, + AudioFormat sourceFormat) + { + if (TDebug.TraceAudioConverter) + { + TDebug.out(">TFormatConversionProvider.isConversionSupported(AudioFormat, AudioFormat):"); + TDebug.out("class: "+getClass().getName()); + TDebug.out("checking if conversion possible"); + TDebug.out("from: " + sourceFormat); + TDebug.out("to: " + targetFormat); + } + AudioFormat[] aTargetFormats = getTargetFormats(targetFormat.getEncoding(), sourceFormat); + for (int i = 0; i < aTargetFormats.length; i++) + { + if (TDebug.TraceAudioConverter) + { + TDebug.out("checking against possible target format: " + aTargetFormats[i]); + } + if (aTargetFormats[i] != null + && AudioFormats.matches(aTargetFormats[i], targetFormat)) + { + if (TDebug.TraceAudioConverter) + { + TDebug.out(" + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +package org.tritonus.share.sampled.convert; + + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.Iterator; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.spi.FormatConversionProvider; + +import org.tritonus.share.sampled.AudioFormats; +import org.tritonus.share.ArraySet; + +/** + * Base class for arbitrary formatConversionProviders. + * + * @author Matthias Pfisterer + */ + + +public abstract class TMatrixFormatConversionProvider + extends TSimpleFormatConversionProvider +{ + /* + * keys: source AudioFormat + * values: collection of possible target encodings + * + * Note that accessing values with get() is not appropriate, + * since the equals() method in AudioFormat is not overloaded. + * The hashtable is just used as a convenient storage + * organization. + */ + private Map m_targetEncodingsFromSourceFormat; + + + /* + * keys: source AudioFormat + * values: a Map that contains a mapping from target encodings + * (keys) to a collection of target formats (values). + * + * Note that accessing values with get() is not appropriate, + * since the equals() method in AudioFormat is not overloaded. + * The hashtable is just used as a convenient storage + * organization. + */ + private Map m_targetFormatsFromSourceFormat; + + + + protected TMatrixFormatConversionProvider( + List sourceFormats, + List targetFormats, + boolean[][] abConversionPossible) + { + super(sourceFormats, + targetFormats); + m_targetEncodingsFromSourceFormat = new HashMap(); + m_targetFormatsFromSourceFormat = new HashMap(); + + for (int nSourceFormat = 0; + nSourceFormat < sourceFormats.size(); + nSourceFormat++) + { + AudioFormat sourceFormat = (AudioFormat) sourceFormats.get(nSourceFormat); + List supportedTargetEncodings = new ArraySet(); + m_targetEncodingsFromSourceFormat.put(sourceFormat, supportedTargetEncodings); + Map targetFormatsFromTargetEncodings = new HashMap(); + m_targetFormatsFromSourceFormat.put(sourceFormat, targetFormatsFromTargetEncodings); + for (int nTargetFormat = 0; + nTargetFormat < targetFormats.size(); + nTargetFormat++) + { + AudioFormat targetFormat = (AudioFormat) targetFormats.get(nTargetFormat); + if (abConversionPossible[nSourceFormat][nTargetFormat]) + { + AudioFormat.Encoding targetEncoding = targetFormat.getEncoding(); + supportedTargetEncodings.add(targetEncoding); + Collection supportedTargetFormats = (Collection) targetFormatsFromTargetEncodings.get(targetEncoding); + if (supportedTargetFormats == null) + { + supportedTargetFormats = new ArraySet(); + targetFormatsFromTargetEncodings.put(targetEncoding, supportedTargetFormats); + } + supportedTargetFormats.add(targetFormat); + } + } + } + } + + + + public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat) + { + Iterator iterator = m_targetEncodingsFromSourceFormat.entrySet().iterator(); + while (iterator.hasNext()) + { + Map.Entry entry = (Map.Entry) iterator.next(); + AudioFormat format = (AudioFormat) entry.getKey(); + if (AudioFormats.matches(format, sourceFormat)) + { + Collection targetEncodings = (Collection) entry.getValue(); + return (AudioFormat.Encoding[]) targetEncodings.toArray(EMPTY_ENCODING_ARRAY); + } + + } + return EMPTY_ENCODING_ARRAY; + } + + + + // TODO: this should work on the array returned by getTargetEncodings(AudioFormat) +/* + public boolean isConversionSupported(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat) + { + return isAllowedSourceFormat(sourceFormat) && + isTargetEncodingSupported(targetEncoding); + } +*/ + + + + public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat) + { + Iterator iterator = m_targetFormatsFromSourceFormat.entrySet().iterator(); + while (iterator.hasNext()) + { + Map.Entry entry = (Map.Entry) iterator.next(); + AudioFormat format = (AudioFormat) entry.getKey(); + if (AudioFormats.matches(format, sourceFormat)) + { + Map targetEncodings = (Map) entry.getValue(); + Collection targetFormats = (Collection) targetEncodings.get(targetEncoding); + if (targetFormats != null) + { + return (AudioFormat[]) targetFormats.toArray(EMPTY_FORMAT_ARRAY); + } + else + { + return EMPTY_FORMAT_ARRAY; + } + } + + } + return EMPTY_FORMAT_ARRAY; + } + + +} + + + +/*** TMatrixFormatConversionProvider.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/convert/TSimpleFormatConversionProvider.java b/songdbj/org/tritonus/share/sampled/convert/TSimpleFormatConversionProvider.java new file mode 100644 index 0000000000..71b055ff79 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/convert/TSimpleFormatConversionProvider.java @@ -0,0 +1,367 @@ +/* + * TSimpleFormatConversionProvider.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 2004 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.convert; + +import java.util.Collection; +import java.util.Iterator; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; + +import org.tritonus.share.sampled.AudioFormats; +import org.tritonus.share.ArraySet; +import org.tritonus.share.TDebug; + + +/** + * This is a base class for FormatConversionProviders that can convert + * from each source encoding/format to each target encoding/format. + * If this is not the case, use TEncodingFormatConversionProvider. + * + *

Overriding classes must + * provide a constructor that calls the protected constructor of this class and override + * AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream). + * The latter method should be able to handle the case that all fields are NOT_SPECIFIED + * and provide appropriate default values. + * + * @author Matthias Pfisterer + */ + +// todo: +// - declare a constant ALL_BUT_SAME_VALUE (==-2) or so that can be used in format lists +// - consistent implementation of replacing NOT_SPECIFIED when not given in conversion + +public abstract class TSimpleFormatConversionProvider +extends TFormatConversionProvider +{ + private Collection m_sourceEncodings; + private Collection m_targetEncodings; + private Collection m_sourceFormats; + private Collection m_targetFormats; + + + + protected TSimpleFormatConversionProvider( + Collection sourceFormats, + Collection targetFormats) + { + m_sourceEncodings = new ArraySet(); + m_targetEncodings = new ArraySet(); + m_sourceFormats = sourceFormats; + m_targetFormats = targetFormats; + collectEncodings(m_sourceFormats, m_sourceEncodings); + collectEncodings(m_targetFormats, m_targetEncodings); + } + + + + /** Disables this FormatConversionProvider. + This may be useful when e.g. native libraries are not present. + TODO: enable method, better implementation + */ + protected void disable() + { + if (TDebug.TraceAudioConverter) { TDebug.out("TSimpleFormatConversionProvider.disable(): disabling " + getClass().getName()); } + m_sourceEncodings = new ArraySet(); + m_targetEncodings = new ArraySet(); + m_sourceFormats = new ArraySet(); + m_targetFormats = new ArraySet(); + } + + + + private static void collectEncodings(Collection formats, + Collection encodings) + { + Iterator iterator = formats.iterator(); + while (iterator.hasNext()) + { + AudioFormat format = iterator.next(); + encodings.add(format.getEncoding()); + } + } + + + + public AudioFormat.Encoding[] getSourceEncodings() + { + return m_sourceEncodings.toArray(EMPTY_ENCODING_ARRAY); + } + + + + public AudioFormat.Encoding[] getTargetEncodings() + { + return m_targetEncodings.toArray(EMPTY_ENCODING_ARRAY); + } + + + + // overwritten of FormatConversionProvider + public boolean isSourceEncodingSupported(AudioFormat.Encoding sourceEncoding) + { + return m_sourceEncodings.contains(sourceEncoding); + } + + + + // overwritten of FormatConversionProvider + public boolean isTargetEncodingSupported(AudioFormat.Encoding targetEncoding) + { + return m_targetEncodings.contains(targetEncoding); + } + + + + /** + * This implementation assumes that the converter can convert + * from each of its source encodings to each of its target + * encodings. If this is not the case, the converter has to + * override this method. + */ + public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat) + { + if (isAllowedSourceFormat(sourceFormat)) + { + return getTargetEncodings(); + } + else + { + return EMPTY_ENCODING_ARRAY; + } + } + + + + /** + * This implementation assumes that the converter can convert + * from each of its source formats to each of its target + * formats. If this is not the case, the converter has to + * override this method. + */ + public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat) + { + if (isConversionSupported(targetEncoding, sourceFormat)) + { + return m_targetFormats.toArray(EMPTY_FORMAT_ARRAY); + } + else + { + return EMPTY_FORMAT_ARRAY; + } + } + + + // TODO: check if necessary + protected boolean isAllowedSourceEncoding(AudioFormat.Encoding sourceEncoding) + { + return m_sourceEncodings.contains(sourceEncoding); + } + + + + protected boolean isAllowedTargetEncoding(AudioFormat.Encoding targetEncoding) + { + return m_targetEncodings.contains(targetEncoding); + } + + + + protected boolean isAllowedSourceFormat(AudioFormat sourceFormat) + { + Iterator iterator = m_sourceFormats.iterator(); + while (iterator.hasNext()) + { + AudioFormat format = iterator.next(); + if (AudioFormats.matches(format, sourceFormat)) + { + return true; + } + } + return false; + } + + + + protected boolean isAllowedTargetFormat(AudioFormat targetFormat) + { + Iterator iterator = m_targetFormats.iterator(); + while (iterator.hasNext()) + { + AudioFormat format = iterator.next(); + if (AudioFormats.matches(format, targetFormat)) + { + return true; + } + } + return false; + } + + // $$fb 2000-04-02 added some convenience methods for overriding classes + protected Collection getCollectionSourceEncodings() + { + return m_sourceEncodings; + } + + protected Collection getCollectionTargetEncodings() + { + return m_targetEncodings; + } + + protected Collection getCollectionSourceFormats() { + return m_sourceFormats; + } + + protected Collection getCollectionTargetFormats() { + return m_targetFormats; + } + + /** + * Utility method to check whether these values match, + * taking into account AudioSystem.NOT_SPECIFIED. + * @return true if any of the values is AudioSystem.NOT_SPECIFIED + * or both values have the same value. + */ + //$$fb 2000-08-16: moved from TEncodingFormatConversionProvider + protected static boolean doMatch(int i1, int i2) { + return i1==AudioSystem.NOT_SPECIFIED + || i2==AudioSystem.NOT_SPECIFIED + || i1==i2; + } + + /** + * @see #doMatch(int,int) + */ + //$$fb 2000-08-16: moved from TEncodingFormatConversionProvider + protected static boolean doMatch(float f1, float f2) { + return f1==AudioSystem.NOT_SPECIFIED + || f2==AudioSystem.NOT_SPECIFIED + || Math.abs(f1 - f2) < 1.0e-9; + } + + /** + * Utility method, replaces all occurences of AudioSystem.NOT_SPECIFIED + * in targetFormat with the corresponding value in sourceFormat. + * If targetFormat does not contain any fields with AudioSystem.NOT_SPECIFIED, + * it is returned unmodified. The endian-ness and encoding remain the same in all cases. + *

+ * If any of the fields is AudioSystem.NOT_SPECIFIED in both sourceFormat and + * targetFormat, it will remain not specified. + *

+ * This method uses getFrameSize(...) (see below) to set the new frameSize, + * if a new AudioFormat instance is created. + *

+ * This method isn't used in TSimpleFormatConversionProvider - it is solely there + * for inheriting classes. + */ + //$$fb 2000-08-16: moved from TEncodingFormatConversionProvider + protected AudioFormat replaceNotSpecified(AudioFormat sourceFormat, AudioFormat targetFormat) { + boolean bSetSampleSize=false; + boolean bSetChannels=false; + boolean bSetSampleRate=false; + boolean bSetFrameRate=false; + if (targetFormat.getSampleSizeInBits()==AudioSystem.NOT_SPECIFIED + && sourceFormat.getSampleSizeInBits()!=AudioSystem.NOT_SPECIFIED) { + bSetSampleSize=true; + } + if (targetFormat.getChannels()==AudioSystem.NOT_SPECIFIED + && sourceFormat.getChannels()!=AudioSystem.NOT_SPECIFIED) { + bSetChannels=true; + } + if (targetFormat.getSampleRate()==AudioSystem.NOT_SPECIFIED + && sourceFormat.getSampleRate()!=AudioSystem.NOT_SPECIFIED) { + bSetSampleRate=true; + } + if (targetFormat.getFrameRate()==AudioSystem.NOT_SPECIFIED + && sourceFormat.getFrameRate()!=AudioSystem.NOT_SPECIFIED) { + bSetFrameRate=true; + } + if (bSetSampleSize || bSetChannels || bSetSampleRate || bSetFrameRate + || (targetFormat.getFrameSize()==AudioSystem.NOT_SPECIFIED + && sourceFormat.getFrameSize()!=AudioSystem.NOT_SPECIFIED)) { + // create new format in place of the original target format + float sampleRate=bSetSampleRate? + sourceFormat.getSampleRate():targetFormat.getSampleRate(); + float frameRate=bSetFrameRate? + sourceFormat.getFrameRate():targetFormat.getFrameRate(); + int sampleSize=bSetSampleSize? + sourceFormat.getSampleSizeInBits():targetFormat.getSampleSizeInBits(); + int channels=bSetChannels? + sourceFormat.getChannels():targetFormat.getChannels(); + int frameSize=getFrameSize( + targetFormat.getEncoding(), + sampleRate, + sampleSize, + channels, + frameRate, + targetFormat.isBigEndian(), + targetFormat.getFrameSize()); + targetFormat= new AudioFormat( + targetFormat.getEncoding(), + sampleRate, + sampleSize, + channels, + frameSize, + frameRate, + targetFormat.isBigEndian()); + } + return targetFormat; + } + + /** + * Calculates the frame size for the given format description. + * The default implementation returns AudioSystem.NOT_SPECIFIED + * if either sampleSize or channels is AudioSystem.NOT_SPECIFIED, + * otherwise sampleSize*channels/8 is returned. + *

+ * If this does not reflect the way to calculate the right frame size, + * inheriting classes should overwrite this method if they use + * replaceNotSpecified(...). It is not used elsewhere in this class. + */ + //$$fb 2000-08-16: added + protected int getFrameSize( + AudioFormat.Encoding encoding, + float sampleRate, + int sampleSize, + int channels, + float frameRate, + boolean bigEndian, + int oldFrameSize) { + if (sampleSize==AudioSystem.NOT_SPECIFIED || channels==AudioSystem.NOT_SPECIFIED) { + return AudioSystem.NOT_SPECIFIED; + } + return sampleSize*channels/8; + } + + + +} + +/*** TSimpleFormatConversionProvider.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/convert/TSynchronousFilteredAudioInputStream.java b/songdbj/org/tritonus/share/sampled/convert/TSynchronousFilteredAudioInputStream.java new file mode 100644 index 0000000000..8a588e5c3e --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/convert/TSynchronousFilteredAudioInputStream.java @@ -0,0 +1,271 @@ +/* + * TSynchronousFilteredAudioInputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999,2000 by Florian Bomers + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.convert; + +import java.io.IOException; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.spi.FormatConversionProvider; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.AudioUtils; + + + +/** + * Base class for types of audio filter/converter that translate one frame to another frame.
+ * It provides all the transformation of frame sizes.
+ * It does NOT handle different sample rates of original stream and this stream ! + * + * @author Florian Bomers + */ +public abstract class TSynchronousFilteredAudioInputStream +extends TAudioInputStream { + + private AudioInputStream originalStream; + private AudioFormat originalFormat; + /** 1 if original format's frame size is NOT_SPECIFIED */ + private int originalFrameSize; + /** 1 if original format's frame size is NOT_SPECIFIED */ + private int newFrameSize; + + /** + * The intermediate buffer used during convert actions + * (if not convertInPlace is used). + * It remains until this audioStream is closed or destroyed + * and grows with the time - it always has the size of the + * largest intermediate buffer ever needed. + */ + protected byte[] buffer=null; + + /** + * For use of the more efficient method convertInPlace. + * it will be set to true when (frameSizeFactor==1) + */ + private boolean m_bConvertInPlace = false; + + public TSynchronousFilteredAudioInputStream(AudioInputStream audioInputStream, AudioFormat newFormat) { + // the super class will do nothing... we override everything + super(audioInputStream, newFormat, audioInputStream.getFrameLength()); + originalStream=audioInputStream; + originalFormat=audioInputStream.getFormat(); + originalFrameSize=(originalFormat.getFrameSize()<=0) ? + 1 : originalFormat.getFrameSize(); + newFrameSize=(getFormat().getFrameSize()<=0) ? + 1 : getFormat().getFrameSize(); + if (TDebug.TraceAudioConverter) { + TDebug.out("TSynchronousFilteredAudioInputStream: original format =" + +AudioUtils.format2ShortStr(originalFormat)); + TDebug.out("TSynchronousFilteredAudioInputStream: converted format=" + +AudioUtils.format2ShortStr(getFormat())); + } + //$$fb 2000-07-17: convert in place has to be enabled explicitly with "enableConvertInPlace" + //if (getFormat().getFrameSize() == originalFormat.getFrameSize()) { + // m_bConvertInPlace = true; + //} + m_bConvertInPlace = false; + } + + protected boolean enableConvertInPlace() { + if (newFrameSize >= originalFrameSize) { + m_bConvertInPlace = true; + } + return m_bConvertInPlace; + } + + + /** + * Override this method to do the actual conversion. + * inBuffer starts always at index 0 (it is an internal buffer) + * You should always override this. + * inFrameCount is the number of frames in inBuffer. These + * frames are of the format originalFormat. + * @return the resulting number of frames converted and put into + * outBuffer. The return value is in the format of this stream. + */ + protected abstract int convert(byte[] inBuffer, byte[] outBuffer, int outByteOffset, int inFrameCount); + + + + /** + * Override this method to provide in-place conversion of samples. + * To use it, call "enableConvertInPlace()". It will only be used when + * input bytes per frame >= output bytes per frame. + * This method must always convert frameCount frames, so no return value is necessary. + */ + protected void convertInPlace(byte[] buffer, int byteOffset, int frameCount) { + throw new RuntimeException("Illegal call to convertInPlace"); + } + + public int read() + throws IOException { + if (newFrameSize != 1) { + throw new IOException("frame size must be 1 to read a single byte"); + } + // very ugly, but efficient. Who uses this method anyway ? + // TODO: use an instance variable + byte[] temp = new byte[1]; + int result = read(temp); + if (result == -1) { + return -1; + } + if (result == 0) { + // what in this case ??? Let's hope it never occurs. + return -1; + } + return temp[0] & 0xFF; + } + + + + private void clearBuffer() { + buffer = null; + } + + public AudioInputStream getOriginalStream() { + return originalStream; + } + + public AudioFormat getOriginalFormat() { + return originalFormat; + } + + /** + * Read nLength bytes that will be the converted samples + * of the original InputStream. + * When nLength is not an integral number of frames, + * this method may read less than nLength bytes. + */ + public int read(byte[] abData, int nOffset, int nLength) + throws IOException { + // number of frames that we have to read from the underlying stream. + int nFrameLength = nLength/newFrameSize; + + // number of bytes that we need to read from underlying stream. + int originalBytes = nFrameLength * originalFrameSize; + + if (TDebug.TraceAudioConverter) { + TDebug.out("> TSynchronousFilteredAIS.read(buffer["+abData.length+"], " + +nOffset+" ,"+nLength+" bytes ^="+nFrameLength+" frames)"); + } + int nFramesConverted = 0; + + // set up buffer to read + byte readBuffer[]; + int readOffset; + if (m_bConvertInPlace) { + readBuffer=abData; + readOffset=nOffset; + } else { + // assert that the buffer fits + if (buffer == null || buffer.length < originalBytes) { + buffer = new byte[originalBytes]; + } + readBuffer=buffer; + readOffset=0; + } + int nBytesRead = originalStream.read(readBuffer, readOffset, originalBytes); + if (nBytesRead == -1) { + // end of stream + clearBuffer(); + return -1; + } + int nFramesRead = nBytesRead / originalFrameSize; + if (TDebug.TraceAudioConverter) { + TDebug.out("original.read returned " + +nBytesRead+" bytes ^="+nFramesRead+" frames"); + } + if (m_bConvertInPlace) { + convertInPlace(abData, nOffset, nFramesRead); + nFramesConverted=nFramesRead; + } else { + nFramesConverted = convert(buffer, abData, nOffset, nFramesRead); + } + if (TDebug.TraceAudioConverter) { + TDebug.out("< converted "+nFramesConverted+" frames"); + } + return nFramesConverted*newFrameSize; + } + + + public long skip(long nSkip) + throws IOException { + // only returns integral frames + long skipFrames = nSkip / newFrameSize; + long originalSkippedBytes = originalStream.skip(skipFrames*originalFrameSize); + long skippedFrames = originalSkippedBytes/originalFrameSize; + return skippedFrames * newFrameSize; + } + + + public int available() + throws IOException { + int origAvailFrames = originalStream.available()/originalFrameSize; + return origAvailFrames*newFrameSize; + } + + + public void close() + throws IOException { + originalStream.close(); + clearBuffer(); + } + + + + public void mark(int readlimit) { + int readLimitFrames=readlimit/newFrameSize; + originalStream.mark(readLimitFrames*originalFrameSize); + } + + + + public void reset() + throws IOException { + originalStream.reset(); + } + + + public boolean markSupported() { + return originalStream.markSupported(); + } + + + private int getFrameSize() { + return getFormat().getFrameSize(); + } + +} + + +/*** TSynchronousFilteredAudioInputStream.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/convert/package.html b/songdbj/org/tritonus/share/sampled/convert/package.html new file mode 100644 index 0000000000..d0cc35c408 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/convert/package.html @@ -0,0 +1,17 @@ + + + + + + +

Base classes for the implementation of FormatConversionProviders. + The classes provided here .

+ + @see javax.sound.sampled.spi.FormatConversionProvider + @see org.tritonus.sampled.convert + @see org.tritonus.sampled.convert.gsm + @see org.tritonus.sampled.convert.jorbis + @see org.tritonus.sampled.convert.lame + @see org.tritonus.sampled.convert.vorbis + + diff --git a/songdbj/org/tritonus/share/sampled/file/AudioOutputStream.java b/songdbj/org/tritonus/share/sampled/file/AudioOutputStream.java new file mode 100644 index 0000000000..d76296cd2d --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/AudioOutputStream.java @@ -0,0 +1,113 @@ +/* + * AudioOutputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.IOException; + +import javax.sound.sampled.AudioFormat; + + + +/** Represents a one-time writing of audio data to a destination (file or output stream). + * + * This interface is the lowest abstraction level of writing audio data. + * Implementations of this interface should, when write() is called, never + * do buffering and they should never do format conversions. However, + * this interface is intended to abstract the file format (how the + * headers and data chunks look like) and the way of writing to the + * destination object. (implementation note [non-normative]: the last + * should be done by using TDataOutputStream implementing classes). + * + * One reasoning behind this interface was to allow direct, unbuffered + * writing of recorded data. + * In JS API 0.90, there was no obvious way for this. + * Data have had to be recorded to a buffer, then written to a file + * from that buffer. + * This gave problems with long recordings, where the main + * memory of the machine is not big enough to hold all data. There are + * two ways so solve this: + * + * a) Having a special AudioInputStream that fetches its data from a + * TargetDataLine. This way, the loop inside the AudioFileWriters reads + * directely from the recording line via the special AudioInputStream. + * This is the solution Sun adopted for JS 1.0. + * + * b) The other way is to expose a direct interface to the writing of the + * audio file with no loop inside it. This is to enable the application + * programmer to write the main loop itself, possibly doing some + * additional processing inside it. This is the more flexible way. + * The drawback is that it requires a new architecture for writing files. + * + * This interface is the central part of a proposal for the second + * solution. + * The idea is now to use the new structure inside the old one to gain + * experience with it before proposing to make it a public interface + * (public in the sense that it is part of the javax.sound.sampled + * package). + * + * @author Matthias Pfisterer + */ +public interface AudioOutputStream +{ + /** + * Retrieves the AufioFormat of this AudioOutputStream. + */ + public AudioFormat getFormat(); + + + /** Gives length of the stream. + * This value is in bytes. It may be AudioSystem.NOT_SPECIFIED + * to express that the length is unknown. + */ + public long getLength(); + + + + /** + * Writes a chunk of audio data to the destination (file or output stream). + */ + // IDEA: use long? + public int write(byte[] abData, int nOffset, int nLength) + throws IOException; + + + + /** Closes the stream. + * This does write remaining buffered data to the destination, + * backpatch the header, if necessary, and closes the destination. + */ + public void close() + throws IOException; +} + + + +/*** AudioOutputStream.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/file/HeaderlessAudioOutputStream.java b/songdbj/org/tritonus/share/sampled/file/HeaderlessAudioOutputStream.java new file mode 100644 index 0000000000..3083fd5b8f --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/HeaderlessAudioOutputStream.java @@ -0,0 +1,58 @@ +/* + * HeaderlessAudioOutputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Florian Bomers + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.IOException; +import javax.sound.sampled.AudioFormat; + + +/** + * AudioOutputStream for files without a header; the input is written as it is. + * + * @author Florian Bomers + */ + +// todo: implement directly AudioOutputStream without using TAudioOutputStream + +public class HeaderlessAudioOutputStream extends TAudioOutputStream { + + public HeaderlessAudioOutputStream(AudioFormat audioFormat, + long lLength, + TDataOutputStream dataOutputStream) { + super(audioFormat, lLength, dataOutputStream, false); + } + + protected void writeHeader() throws IOException + { + } +} + +/*** HeaderlessAudioOutputStream.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/file/TAudioFileFormat.java b/songdbj/org/tritonus/share/sampled/file/TAudioFileFormat.java new file mode 100644 index 0000000000..fd9831e291 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/TAudioFileFormat.java @@ -0,0 +1,113 @@ +/* + * TAudioFileFormat.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; + + + +/** + * This class is just to have a public constructor taking the + * number of bytes of the whole file. The public constructor of + * AudioFileFormat doesn't take this parameter, the one who takes + * it is protected. + * + * @author Matthias Pfisterer + */ +public class TAudioFileFormat +extends AudioFileFormat +{ + private Map m_properties; + private Map m_unmodifiableProperties; + + + /* + * Note that the order of the arguments is different from + * the one in AudioFileFormat. + */ + public TAudioFileFormat(Type type, + AudioFormat audioFormat, + int nLengthInFrames, + int nLengthInBytes) + { + super(type, + nLengthInBytes, + audioFormat, + nLengthInFrames); + } + + + public TAudioFileFormat(Type type, + AudioFormat audioFormat, + int nLengthInFrames, + int nLengthInBytes, + Map properties) + { + super(type, + nLengthInBytes, + audioFormat, + nLengthInFrames); + initMaps(properties); + } + + + private void initMaps(Map properties) + { + /* Here, we make a shallow copy of the map. It's unclear if this + is sufficient (of if a deep copy should be made). + */ + m_properties = new HashMap(); + m_properties.putAll(properties); + m_unmodifiableProperties = Collections.unmodifiableMap(m_properties); + } + + + public Map properties() + { + return m_unmodifiableProperties; + } + + + + protected void setProperty(String key, Object value) + { + m_properties.put(key, value); + } +} + + + +/*** TAudioFileFormat.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/file/TAudioFileReader.java b/songdbj/org/tritonus/share/sampled/file/TAudioFileReader.java new file mode 100644 index 0000000000..ee79becf20 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/TAudioFileReader.java @@ -0,0 +1,510 @@ +/* + * TAudioFileReader.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 by Matthias Pfisterer + * Copyright (c) 2001 by Florian Bomers + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.DataInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.EOFException; + +import java.net.URL; +import java.net.URLConnection; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.sound.sampled.spi.AudioFileReader; + +import org.tritonus.share.TDebug; + + + +/** Base class for audio file readers. + This is Tritonus' base class for classes that provide the facility + of detecting an audio file type and reading its header. + Classes should be derived from this class or one of its subclasses + rather than from javax.sound.sampled.spi.AudioFileReader. + + @author Matthias Pfisterer + @author Florian Bomers +*/ +public abstract class TAudioFileReader +extends AudioFileReader +{ + private int m_nMarkLimit = -1; + private boolean m_bRereading; + + + protected TAudioFileReader(int nMarkLimit) + { + this(nMarkLimit, false); + } + + + + protected TAudioFileReader(int nMarkLimit, boolean bRereading) + { + m_nMarkLimit = nMarkLimit; + m_bRereading = bRereading; + } + + + + private int getMarkLimit() + { + return m_nMarkLimit; + } + + + + private boolean isRereading() + { + return m_bRereading; + } + + + + /** Get an AudioFileFormat object for a File. + This method calls getAudioFileFormat(InputStream, long). + Subclasses should not override this method unless there are + really severe reasons. Normally, it is sufficient to + implement getAudioFileFormat(InputStream, long). + + @param file the file to read from. + @return an AudioFileFormat instance containing + information from the header of the file passed in. + */ + public AudioFileFormat getAudioFileFormat(File file) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioFileFormat(File): begin"); } + long lFileLengthInBytes = file.length(); + InputStream inputStream = new FileInputStream(file); + AudioFileFormat audioFileFormat = null; + try + { + audioFileFormat = getAudioFileFormat(inputStream, lFileLengthInBytes); + } + finally + { + inputStream.close(); + } + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioFileFormat(File): end"); } + return audioFileFormat; + } + + + + /** Get an AudioFileFormat object for a URL. + This method calls getAudioFileFormat(InputStream, long). + Subclasses should not override this method unless there are + really severe reasons. Normally, it is sufficient to + implement getAudioFileFormat(InputStream, long). + + @param url the URL to read from. + @return an AudioFileFormat instance containing + information from the header of the URL passed in. + */ + public AudioFileFormat getAudioFileFormat(URL url) + throws UnsupportedAudioFileException, IOException + + { + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioFileFormat(URL): begin"); } + long lFileLengthInBytes = getDataLength(url); + InputStream inputStream = url.openStream(); + AudioFileFormat audioFileFormat = null; + try + { + audioFileFormat = getAudioFileFormat(inputStream, lFileLengthInBytes); + } + finally + { + inputStream.close(); + } + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioFileFormat(URL): end"); } + return audioFileFormat; + } + + + + /** Get an AudioFileFormat object for an InputStream. + This method calls getAudioFileFormat(InputStream, long). + Subclasses should not override this method unless there are + really severe reasons. Normally, it is sufficient to + implement getAudioFileFormat(InputStream, long). + + @param inputStream the stream to read from. + @return an AudioFileFormat instance containing + information from the header of the stream passed in. + */ + public AudioFileFormat getAudioFileFormat(InputStream inputStream) + throws UnsupportedAudioFileException, IOException + + { + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioFileFormat(InputStream): begin"); } + long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; + inputStream.mark(getMarkLimit()); + AudioFileFormat audioFileFormat = null; + try + { + audioFileFormat = getAudioFileFormat(inputStream, lFileLengthInBytes); + } + finally + { + /* TODO: required semantics is unclear: should reset() + be executed only when there is an exception or + should it be done always? + */ + inputStream.reset(); + } + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioFileFormat(InputStream): end"); } + return audioFileFormat; + } + + + + /** Get an AudioFileFormat (internal implementation). + Subclasses must implement this method in a way specific + to the file format they handle. + + Note that depending on the implementation of this method, + you should or should not override + getAudioInputStream(InputStream, long), too (see comment + there). + + @param inputStream The InputStream to read from. + @param lFileLengthInBytes The size of the originating + file, if known. If it isn't known, AudioSystem.NOT_SPECIFIED + should be passed. This value may be used for byteLength in + AudioFileFormat, if this value can't be derived from the + informmation in the file header. + + @return an AudioFileFormat instance containing + information from the header of the stream passed in as + inputStream. + */ + protected abstract AudioFileFormat getAudioFileFormat( + InputStream inputStream, + long lFileLengthInBytes) + throws UnsupportedAudioFileException, IOException; + + + + /** Get an AudioInputStream object for a file. + This method calls getAudioInputStream(InputStream, long). + Subclasses should not override this method unless there are + really severe reasons. Normally, it is sufficient to + implement getAudioFileFormat(InputStream, long) and perhaps + override getAudioInputStream(InputStream, long). + + @param file the File object to read from. + @return an AudioInputStream instance containing + the audio data from this file. + */ + public AudioInputStream getAudioInputStream(File file) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioInputStream(File): begin"); } + long lFileLengthInBytes = file.length(); + InputStream inputStream = new FileInputStream(file); + AudioInputStream audioInputStream = null; + try + { + audioInputStream = getAudioInputStream(inputStream, lFileLengthInBytes); + } + catch (UnsupportedAudioFileException e) + { + inputStream.close(); + throw e; + } + catch (IOException e) + { + inputStream.close(); + throw e; + } + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioInputStream(File): end"); } + return audioInputStream; + } + + + + /** Get an AudioInputStream object for a URL. + This method calls getAudioInputStream(InputStream, long). + Subclasses should not override this method unless there are + really severe reasons. Normally, it is sufficient to + implement getAudioFileFormat(InputStream, long) and perhaps + override getAudioInputStream(InputStream, long). + + @param url the URL to read from. + @return an AudioInputStream instance containing + the audio data from this URL. + */ + public AudioInputStream getAudioInputStream(URL url) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioInputStream(URL): begin"); } + long lFileLengthInBytes = getDataLength(url); + InputStream inputStream = url.openStream(); + AudioInputStream audioInputStream = null; + try + { + audioInputStream = getAudioInputStream(inputStream, lFileLengthInBytes); + } + catch (UnsupportedAudioFileException e) + { + inputStream.close(); + throw e; + } + catch (IOException e) + { + inputStream.close(); + throw e; + } + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioInputStream(URL): end"); } + return audioInputStream; + } + + + + /** Get an AudioInputStream object for an InputStream. + This method calls getAudioInputStream(InputStream, long). + Subclasses should not override this method unless there are + really severe reasons. Normally, it is sufficient to + implement getAudioFileFormat(InputStream, long) and perhaps + override getAudioInputStream(InputStream, long). + + @param inputStream the stream to read from. + @return an AudioInputStream instance containing + the audio data from this stream. + */ + public AudioInputStream getAudioInputStream(InputStream inputStream) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioInputStream(InputStream): begin"); } + long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; + AudioInputStream audioInputStream = null; + inputStream.mark(getMarkLimit()); + try + { + audioInputStream = getAudioInputStream(inputStream, lFileLengthInBytes); + } + catch (UnsupportedAudioFileException e) + { + inputStream.reset(); + throw e; + } + catch (IOException e) + { + inputStream.reset(); + throw e; + } + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioInputStream(InputStream): end"); } + return audioInputStream; + } + + + + /** Get an AudioInputStream (internal implementation). + This implementation calls getAudioFileFormat() with the + same arguments as passed in here. Then, it constructs + an AudioInputStream instance. This instance takes the passed + inputStream in the state it is left after getAudioFileFormat() + did its work. In other words, the implementation here + assumes that getAudioFileFormat() reads the entire header + up to a position exactely where the audio data starts. + If this can't be realized for a certain format, this method + should be overridden. + + @param inputStream The InputStream to read from. + @param lFileLengthInBytes The size of the originating + file, if known. If it isn't known, AudioSystem.NOT_SPECIFIED + should be passed. This value may be used for byteLength in + AudioFileFormat, if this value can't be derived from the + informmation in the file header. + */ + protected AudioInputStream getAudioInputStream(InputStream inputStream, long lFileLengthInBytes) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioInputStream(InputStream, long): begin"); } + if (isRereading()) + { + inputStream = new BufferedInputStream(inputStream, getMarkLimit()); + inputStream.mark(getMarkLimit()); + } + AudioFileFormat audioFileFormat = getAudioFileFormat(inputStream, lFileLengthInBytes); + if (isRereading()) + { + inputStream.reset(); + } + AudioInputStream audioInputStream = + new AudioInputStream(inputStream, + audioFileFormat.getFormat(), + audioFileFormat.getFrameLength()); + if (TDebug.TraceAudioFileReader) {TDebug.out("TAudioFileReader.getAudioInputStream(InputStream, long): end"); } + return audioInputStream; + } + + + + protected static int calculateFrameSize(int nSampleSize, int nNumChannels) + { + return ((nSampleSize + 7) / 8) * nNumChannels; + } + + + + private static long getDataLength(URL url) + throws IOException + { + long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; + URLConnection connection = url.openConnection(); + connection.connect(); + int nLength = connection.getContentLength(); + if (nLength > 0) + { + lFileLengthInBytes = nLength; + } + return lFileLengthInBytes; + } + + + + public static int readLittleEndianInt(InputStream is) + throws IOException + { + int b0 = is.read(); + int b1 = is.read(); + int b2 = is.read(); + int b3 = is.read(); + if ((b0 | b1 | b2 | b3) < 0) + { + throw new EOFException(); + } + return (b3 << 24) + (b2 << 16) + (b1 << 8) + (b0 << 0); + } + + + + public static short readLittleEndianShort(InputStream is) + throws IOException + { + int b0 = is.read(); + int b1 = is.read(); + if ((b0 | b1) < 0) + { + throw new EOFException(); + } + return (short) ((b1 << 8) + (b0 << 0)); + } + + +/* + * C O N V E R T F R O M I E E E E X T E N D E D + */ + +/* + * Copyright (C) 1988-1991 Apple Computer, Inc. + * All rights reserved. + * + * Machine-independent I/O routines for IEEE floating-point numbers. + * + * NaN's and infinities are converted to HUGE_VAL or HUGE, which + * happens to be infinity on IEEE machines. Unfortunately, it is + * impossible to preserve NaN's in a machine-independent way. + * Infinities are, however, preserved on IEEE machines. + * + * These routines have been tested on the following machines: + * Apple Macintosh, MPW 3.1 C compiler + * Apple Macintosh, THINK C compiler + * Silicon Graphics IRIS, MIPS compiler + * Cray X/MP and Y/MP + * Digital Equipment VAX + * + * + * Implemented by Malcolm Slaney and Ken Turkowski. + * + * Malcolm Slaney contributions during 1988-1990 include big- and little- + * endian file I/O, conversion to and from Motorola's extended 80-bit + * floating-point format, and conversions to and from IEEE single- + * precision floating-point format. + * + * In 1991, Ken Turkowski implemented the conversions to and from + * IEEE double-precision format, added more precision to the extended + * conversions, and accommodated conversions involving +/- infinity, + * NaN's, and denormalized numbers. + */ + + public static double readIeeeExtended(DataInputStream dis) + throws IOException + { + double f = 0.0D; + int expon = 0; + long hiMant = 0L; + long loMant = 0L; + double HUGE = 3.4028234663852886E+038D; + expon = dis.readUnsignedShort(); + long t1 = dis.readUnsignedShort(); + long t2 = dis.readUnsignedShort(); + hiMant = t1 << 16 | t2; + t1 = dis.readUnsignedShort(); + t2 = dis.readUnsignedShort(); + loMant = t1 << 16 | t2; + if(expon == 0 && hiMant == 0L && loMant == 0L) + { + f = 0.0D; + } + else + { + if(expon == 32767) + { + f = HUGE; + } + else + { + expon -= 16383; + expon -= 31; + f = hiMant * Math.pow(2D, expon); + expon -= 32; + f += loMant * Math.pow(2D, expon); + } + } + return f; + } +} + + + +/*** TAudioFileReader.java ***/ + diff --git a/songdbj/org/tritonus/share/sampled/file/TAudioFileWriter.java b/songdbj/org/tritonus/share/sampled/file/TAudioFileWriter.java new file mode 100644 index 0000000000..d9d6ee86ec --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/TAudioFileWriter.java @@ -0,0 +1,484 @@ +/* + * TAudioFileWriter.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999, 2000 by Matthias Pfisterer + * Copyright (c) 1999, 2000 by Florian Bomers + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Iterator; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.spi.AudioFileWriter; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.AudioFormats; +import org.tritonus.share.sampled.AudioUtils; +import org.tritonus.share.sampled.TConversionTool; +import org.tritonus.share.ArraySet; + +/** + * Common base class for implementing classes of AudioFileWriter. + *

It provides often-used functionality and the new architecture using + * an AudioOutputStream. + *

There should be only one set of audio formats supported by any given + * class of TAudioFileWriter. This class assumes implicitely that all + * supported file types have a common set of audio formats they can handle. + * + * @author Matthias Pfisterer + * @author Florian Bomers + */ + +public abstract class TAudioFileWriter +extends AudioFileWriter +{ + protected static final int ALL = AudioSystem.NOT_SPECIFIED; + + public static AudioFormat.Encoding PCM_SIGNED=new AudioFormat.Encoding("PCM_SIGNED"); + public static AudioFormat.Encoding PCM_UNSIGNED=new AudioFormat.Encoding("PCM_UNSIGNED"); + + /** Buffer length for the loop in the write() method. + * This is in bytes. Perhaps it should be in frames to give an + * equal amount of latency. + */ + private static final int BUFFER_LENGTH = 16384; + + // only needed for Collection.toArray() + protected static final AudioFileFormat.Type[] NULL_TYPE_ARRAY = new AudioFileFormat.Type[0]; + + + /** The audio file types (AudioFileFormat.Type) that can be + * handled by the AudioFileWriter. + */ + private Collection m_audioFileTypes; + + + + /** The AudioFormats that can be handled by the + * AudioFileWriter. + */ + // IDEA: implement a special collection that uses matches() to test whether an element is already in + private Collection m_audioFormats; + + + /** + * Inheriting classes should call this constructor + * in order to make use of the functionality of TAudioFileWriter. + */ + protected TAudioFileWriter(Collection fileTypes, + Collection audioFormats) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("TAudioFileWriter.(): begin"); } + m_audioFileTypes = fileTypes; + m_audioFormats = audioFormats; + if (TDebug.TraceAudioFileWriter) { TDebug.out("TAudioFileWriter.(): end"); } + } + + // implementing the interface + public AudioFileFormat.Type[] getAudioFileTypes() + { + return m_audioFileTypes.toArray(NULL_TYPE_ARRAY); + } + + + // implementing the interface + public boolean isFileTypeSupported(AudioFileFormat.Type fileType) + { + return m_audioFileTypes.contains(fileType); + } + + + + // implementing the interface + public AudioFileFormat.Type[] getAudioFileTypes( + AudioInputStream audioInputStream) + { + //$$fb 2000-08-16: rewrote this method. We need to check for *each* + // file type, whether the format is supported ! + AudioFormat format = audioInputStream.getFormat(); + ArraySet res=new ArraySet(); + Iterator it=m_audioFileTypes.iterator(); + while (it.hasNext()) { + AudioFileFormat.Type thisType = it.next(); + if (isAudioFormatSupportedImpl(format, thisType)) { + res.add(thisType); + } + } + return res.toArray(NULL_TYPE_ARRAY); + } + + + + // implementing the interface + public boolean isFileTypeSupported(AudioFileFormat.Type fileType, AudioInputStream audioInputStream) + { + // $$fb 2000-08-16: finally this method works reliably ! + return isFileTypeSupported(fileType) + && (isAudioFormatSupportedImpl(audioInputStream.getFormat(), fileType) + || findConvertableFormat(audioInputStream.getFormat(), fileType)!=null); + // we may soft it up by including the possibility of endian/sign + // changing for PCM formats. + // I prefer to return false if the format is not exactly supported + // but still exectute the write, if only sign/endian changing is necessary. + } + + + + // implementing the interface + public int write(AudioInputStream audioInputStream, + AudioFileFormat.Type fileType, + File file) + throws IOException + { + if (TDebug.TraceAudioFileWriter) + { + TDebug.out(">TAudioFileWriter.write(.., File): called"); + TDebug.out("class: "+getClass().getName()); + } + //$$fb added this check + if (!isFileTypeSupported(fileType)) { + if (TDebug.TraceAudioFileWriter) + { + TDebug.out("< file type is not supported"); + } + throw new IllegalArgumentException("file type is not supported."); + } + + AudioFormat inputFormat = audioInputStream.getFormat(); + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format: " + inputFormat); } + AudioFormat outputFormat = null; + boolean bNeedsConversion = false; + if (isAudioFormatSupportedImpl(inputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format is supported directely"); } + outputFormat = inputFormat; + bNeedsConversion = false; + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format is not supported directely; trying to find a convertable format"); } + outputFormat = findConvertableFormat(inputFormat, fileType); + if (outputFormat != null) + { + bNeedsConversion = true; + // $$fb 2000-08-16 made consistent with new conversion trials + // if 8 bit and only endianness changed, don't convert ! + if (outputFormat.getSampleSizeInBits()==8 + && outputFormat.getEncoding().equals(inputFormat.getEncoding())) { + bNeedsConversion = false; + } + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< input format is not supported and not convertable."); } + throw new IllegalArgumentException("format not supported and not convertable"); + } + } + long lLengthInBytes = AudioUtils.getLengthInBytes(audioInputStream); + TDataOutputStream dataOutputStream = new TSeekableDataOutputStream(file); + AudioOutputStream audioOutputStream = + getAudioOutputStream( + outputFormat, + lLengthInBytes, + fileType, + dataOutputStream); + int written=writeImpl(audioInputStream, + audioOutputStream, + bNeedsConversion); + if (TDebug.TraceAudioFileWriter) + { + TDebug.out("< wrote "+written+" bytes."); + } + return written; + } + + + + // implementing the interface + public int write(AudioInputStream audioInputStream, + AudioFileFormat.Type fileType, + OutputStream outputStream) + throws IOException + { + //$$fb added this check + if (!isFileTypeSupported(fileType)) { + throw new IllegalArgumentException("file type is not supported."); + } + if (TDebug.TraceAudioFileWriter) + { + TDebug.out(">TAudioFileWriter.write(.., OutputStream): called"); + TDebug.out("class: "+getClass().getName()); + } + AudioFormat inputFormat = audioInputStream.getFormat(); + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format: " + inputFormat); } + AudioFormat outputFormat = null; + boolean bNeedsConversion = false; + if (isAudioFormatSupportedImpl(inputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format is supported directely"); } + outputFormat = inputFormat; + bNeedsConversion = false; + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("input format is not supported directely; trying to find a convertable format"); } + outputFormat = findConvertableFormat(inputFormat, fileType); + if (outputFormat != null) + { + bNeedsConversion = true; + // $$fb 2000-08-16 made consistent with new conversion trials + // if 8 bit and only endianness changed, don't convert ! + if (outputFormat.getSampleSizeInBits()==8 + && outputFormat.getEncoding().equals(inputFormat.getEncoding())) { + bNeedsConversion = false; + } + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< format is not supported"); } + throw new IllegalArgumentException("format not supported and not convertable"); + } + } + long lLengthInBytes = AudioUtils.getLengthInBytes(audioInputStream); + TDataOutputStream dataOutputStream = new TNonSeekableDataOutputStream(outputStream); + AudioOutputStream audioOutputStream = + getAudioOutputStream( + outputFormat, + lLengthInBytes, + fileType, + dataOutputStream); + int written=writeImpl(audioInputStream, + audioOutputStream, + bNeedsConversion); + if (TDebug.TraceAudioFileWriter) { TDebug.out("< wrote "+written+" bytes."); } + return written; + } + + + + protected int writeImpl( + AudioInputStream audioInputStream, + AudioOutputStream audioOutputStream, + boolean bNeedsConversion) + throws IOException + { + if (TDebug.TraceAudioFileWriter) + { + TDebug.out(">TAudioFileWriter.writeImpl(): called"); + TDebug.out("class: "+getClass().getName()); + } + int nTotalWritten = 0; + AudioFormat inputFormat = audioInputStream.getFormat(); + AudioFormat outputFormat = audioOutputStream.getFormat(); + + // TODO: handle case when frame size is unknown ? + int nBytesPerSample = outputFormat.getFrameSize() / outputFormat.getChannels(); + + //$$fb 2000-07-18: BUFFER_LENGTH must be a multiple of frame size... + int nBufferSize=((int)BUFFER_LENGTH/outputFormat.getFrameSize())*outputFormat.getFrameSize(); + byte[] abBuffer = new byte[nBufferSize]; + while (true) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying to read (bytes): " + abBuffer.length); } + int nBytesRead = audioInputStream.read(abBuffer); + if (TDebug.TraceAudioFileWriter) { TDebug.out("read (bytes): " + nBytesRead); } + if (nBytesRead == -1) + { + break; + } + if (bNeedsConversion) + { + TConversionTool.changeOrderOrSign(abBuffer, 0, + nBytesRead, nBytesPerSample); + } + int nWritten = audioOutputStream.write(abBuffer, 0, nBytesRead); + nTotalWritten += nWritten; + } + if (TDebug.TraceAudioFileWriter) { TDebug.out(" getSupportedAudioFormats(AudioFileFormat.Type fileType) + { + return m_audioFormats.iterator(); + } + + + /** Checks whether the passed AudioFormat can be handled. + * In this simple implementation, it is only checked if the + * passed AudioFormat matches one of the generally handled + * formats (i.e. the fileType argument is ignored). If the + * handled AudioFormats depend on the file type, this method + * or getSupportedAudioFormats() (on which this method relies) + * has to be overwritten by subclasses. + *

+ * This is the central method for checking if a FORMAT is supported. + * Inheriting classes can overwrite this for performance + * or to exclude/include special type/format combinations. + *

+ * This method is only called when the fileType + * is in the list of supported file types ! Overriding + * classes need not check this. + */ + //$$fb 2000-08-16 changed name, changed documentation. Semantics ! + protected boolean isAudioFormatSupportedImpl( + AudioFormat audioFormat, + AudioFileFormat.Type fileType) + { + if (TDebug.TraceAudioFileWriter) + { + TDebug.out("> TAudioFileWriter.isAudioFormatSupportedImpl(): format to test: " + audioFormat); + TDebug.out("class: "+getClass().getName()); + } + Iterator audioFormats = getSupportedAudioFormats(fileType); + while (audioFormats.hasNext()) + { + AudioFormat handledFormat = (AudioFormat) audioFormats.next(); + if (TDebug.TraceAudioFileWriter) { TDebug.out("matching against format : " + handledFormat); } + if (AudioFormats.matches(handledFormat, audioFormat)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("<...succeeded."); } + return true; + } + } + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... failed"); } + return false; + } + + + + protected abstract AudioOutputStream getAudioOutputStream( + AudioFormat audioFormat, + long lLengthInBytes, + AudioFileFormat.Type fileType, + TDataOutputStream dataOutputStream) + throws IOException; + + private AudioFormat findConvertableFormat( + AudioFormat inputFormat, + AudioFileFormat.Type fileType) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("TAudioFileWriter.findConvertableFormat(): input format: " + inputFormat); } + if (!isFileTypeSupported(fileType)) { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< input file type is not supported."); } + return null; + } + AudioFormat.Encoding inputEncoding = inputFormat.getEncoding(); + if ((inputEncoding.equals(PCM_SIGNED) || inputEncoding.equals(PCM_UNSIGNED)) + && inputFormat.getSampleSizeInBits() == 8) + { + AudioFormat outputFormat = convertFormat(inputFormat, true, false); + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying output format: " + outputFormat); } + if (isAudioFormatSupportedImpl(outputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... succeeded"); } + return outputFormat; + } + //$$fb 2000-08-16: added trial of other endianness for 8bit. We try harder ! + outputFormat = convertFormat(inputFormat, false, true); + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying output format: " + outputFormat); } + if (isAudioFormatSupportedImpl(outputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... succeeded"); } + return outputFormat; + } + outputFormat = convertFormat(inputFormat, true, true); + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying output format: " + outputFormat); } + if (isAudioFormatSupportedImpl(outputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... succeeded"); } + return outputFormat; + } + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... failed"); } + return null; + } + else if (inputEncoding.equals(PCM_SIGNED) && + (inputFormat.getSampleSizeInBits() == 16 || + inputFormat.getSampleSizeInBits() == 24 || + inputFormat.getSampleSizeInBits() == 32) ) + { + // TODO: possible to allow all sample sized > 8 bit? + // $$ fb: don't think that this is necessary. Well, let's talk about that in 5 years :) + AudioFormat outputFormat = convertFormat(inputFormat, false, true); + if (TDebug.TraceAudioFileWriter) { TDebug.out("trying output format: " + outputFormat); } + if (isAudioFormatSupportedImpl(outputFormat, fileType)) + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... succeeded"); } + return outputFormat; + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... failed"); } + return null; + } + } + else + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("< ... failed"); } + return null; + } + } + + // $$fb 2000-08-16: added convenience method + private AudioFormat convertFormat(AudioFormat format, boolean changeSign, boolean changeEndian) { + AudioFormat.Encoding enc=PCM_SIGNED; + if (format.getEncoding().equals(PCM_UNSIGNED)!=changeSign) { + enc=PCM_UNSIGNED; + } + return new AudioFormat( + enc, + format.getSampleRate(), + format.getSampleSizeInBits(), + format.getChannels(), + format.getFrameSize(), + format.getFrameRate(), + format.isBigEndian() ^ changeEndian); + } + +} + + + +/*** TAudioFileWriter.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/file/TAudioOutputStream.java b/songdbj/org/tritonus/share/sampled/file/TAudioOutputStream.java new file mode 100644 index 0000000000..e54316c0a6 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/TAudioOutputStream.java @@ -0,0 +1,197 @@ +/* + * TAudioOutputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.IOException; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; + +import org.tritonus.share.TDebug; + + +/** + * Base class for classes implementing AudioOutputStream. + * + * @author Matthias Pfisterer + */ + +public abstract class TAudioOutputStream +implements AudioOutputStream +{ + private AudioFormat m_audioFormat; + private long m_lLength; // in bytes + private long m_lCalculatedLength; + private TDataOutputStream m_dataOutputStream; + private boolean m_bDoBackPatching; + private boolean m_bHeaderWritten; + + + + protected TAudioOutputStream(AudioFormat audioFormat, + long lLength, + TDataOutputStream dataOutputStream, + boolean bDoBackPatching) + { + m_audioFormat = audioFormat; + m_lLength = lLength; + m_lCalculatedLength = 0; + m_dataOutputStream = dataOutputStream; + m_bDoBackPatching = bDoBackPatching; + m_bHeaderWritten = false; + } + + + + public AudioFormat getFormat() + { + return m_audioFormat; + } + + + + /** Gives length of the stream. + * This value is in bytes. It may be AudioSystem.NOT_SPECIFIED + * to express that the length is unknown. + */ + public long getLength() + { + return m_lLength; + } + + + + /** Gives number of bytes already written. + */ + // IDEA: rename this to BytesWritten or something like that ? + public long getCalculatedLength() + { + return m_lCalculatedLength; + } + + protected TDataOutputStream getDataOutputStream() + { + return m_dataOutputStream; + } + + + /** Writes audio data to the destination (file or output stream). + */ + // IDEA: use long? + public int write(byte[] abData, int nOffset, int nLength) + throws IOException + { + if (TDebug.TraceAudioOutputStream) + { + TDebug.out("TAudioOutputStream.write(): wanted length: " + nLength); + } + if (! m_bHeaderWritten) + { + writeHeader(); + m_bHeaderWritten = true; + } + // $$fb added + // check that total writes do not exceed specified length + long lTotalLength=getLength(); + if (lTotalLength!=AudioSystem.NOT_SPECIFIED && (m_lCalculatedLength+nLength)>lTotalLength) { + if (TDebug.TraceAudioOutputStream) { + TDebug.out("TAudioOutputStream.write(): requested more bytes to write than possible."); + } + nLength=(int) (lTotalLength-m_lCalculatedLength); + // sanity + if (nLength<0) { + nLength=0; + } + } + // TODO: throw an exception if nLength==0 ? (to indicate end of file ?) + if (nLength>0) { + m_dataOutputStream.write(abData, nOffset, nLength); + m_lCalculatedLength += nLength; + } + if (TDebug.TraceAudioOutputStream) + { + TDebug.out("TAudioOutputStream.write(): calculated (total) length: " + m_lCalculatedLength+" bytes = "+(m_lCalculatedLength/getFormat().getFrameSize())+" frames"); + } + return nLength; + } + + + + /** Writes the header of the audio file. + */ + protected abstract void writeHeader() + throws IOException; + + + + /** Closes the stream. + * This does write remaining buffered data to the destination, + * backpatch the header, if necessary, and closes the destination. + */ + public void close() + throws IOException + { + if (TDebug.TraceAudioOutputStream) + { + TDebug.out("TAudioOutputStream.close(): called"); + } + // flush? + if (m_bDoBackPatching) + { + if (TDebug.TraceAudioOutputStream) + { + TDebug.out("TAudioOutputStream.close(): patching header"); + } + patchHeader(); + } + m_dataOutputStream.close(); + } + + + + protected void patchHeader() + throws IOException + { + TDebug.out("TAudioOutputStream.patchHeader(): called"); + // DO NOTHING + } + + + + protected void setLengthFromCalculatedLength() + { + m_lLength = m_lCalculatedLength; + } +} + + + +/*** TAudioOutputStream.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/file/TDataOutputStream.java b/songdbj/org/tritonus/share/sampled/file/TDataOutputStream.java new file mode 100644 index 0000000000..eacc00a2e2 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/TDataOutputStream.java @@ -0,0 +1,79 @@ +/* + * TDataOutputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 by Florian Bomers + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.DataOutput; +import java.io.IOException; +import java.io.InputStream; + + +/** + * Interface for the file writing classes. + *

Like that it is possible to write to a file without knowing + * the length before. + * + * @author Florian Bomers + */ +public interface TDataOutputStream +extends DataOutput +{ + public boolean supportsSeek(); + + + + public void seek(long position) + throws IOException; + + + + public long getFilePointer() + throws IOException; + + + + public long length() + throws IOException; + + + public void writeLittleEndian32(int value) + throws IOException; + + + public void writeLittleEndian16(short value) + throws IOException; + + public void close() + throws IOException; +} + + + +/*** TDataOutputStream.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/file/THeaderlessAudioFileWriter.java b/songdbj/org/tritonus/share/sampled/file/THeaderlessAudioFileWriter.java new file mode 100644 index 0000000000..a9d76de505 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/THeaderlessAudioFileWriter.java @@ -0,0 +1,84 @@ +/* + * THeaderlessAudioFileWriter.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Florian Bomers + * Copyright (c) 2000 - 2002 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.IOException; +import java.util.Collection; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; + +import org.tritonus.share.TDebug; + + + +/** Base class for formats without extra header. + This AudioFileWriter is typically used for compressed formats + where the encoder puts a header into the encoded stream. In this + case, the AudioFileWriter needs not to add a header. This is why + THeaderlessAudioOutputStream is used here. + + @author Florian Bomers + @author Matthias Pfisterer +*/ +public class THeaderlessAudioFileWriter +extends TAudioFileWriter +{ + protected THeaderlessAudioFileWriter(Collection fileTypes, + Collection audioFormats) + { + super(fileTypes, audioFormats); + if (TDebug.TraceAudioFileWriter) { TDebug.out("THeaderlessAudioFileWriter.(): begin"); } + if (TDebug.TraceAudioFileWriter) { TDebug.out("THeaderlessAudioFileWriter.(): end"); } + } + + + + protected AudioOutputStream getAudioOutputStream( + AudioFormat audioFormat, + long lLengthInBytes, + AudioFileFormat.Type fileType, + TDataOutputStream dataOutputStream) + throws IOException + { + if (TDebug.TraceAudioFileWriter) { TDebug.out("THeaderlessAudioFileWriter.getAudioOutputStream(): begin"); } + AudioOutputStream aos = new HeaderlessAudioOutputStream( + audioFormat, + lLengthInBytes, + dataOutputStream); + if (TDebug.TraceAudioFileWriter) { TDebug.out("THeaderlessAudioFileWriter.getAudioOutputStream(): end"); } + return aos; + } + +} + + + +/*** THeaderlessAudioFileWriter.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/file/TNonSeekableDataOutputStream.java b/songdbj/org/tritonus/share/sampled/file/TNonSeekableDataOutputStream.java new file mode 100644 index 0000000000..2e9704ed10 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/TNonSeekableDataOutputStream.java @@ -0,0 +1,109 @@ +/* + * TNonSeekableDataOutputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 by Florian Bomers + * Copyright (c) 2000 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.DataOutputStream; + + + +/** + * A TDataOutputStream that does not allow seeking. + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ +public class TNonSeekableDataOutputStream +extends DataOutputStream +implements TDataOutputStream +{ + public TNonSeekableDataOutputStream(OutputStream outputStream) + { + super(outputStream); + } + + + + public boolean supportsSeek() + { + return false; + } + + + + public void seek(long position) + throws IOException + { + throw new IllegalArgumentException("TNonSeekableDataOutputStream: Call to seek not allowed."); + } + + + + public long getFilePointer() + throws IOException + { + throw new IllegalArgumentException("TNonSeekableDataOutputStream: Call to getFilePointer not allowed."); + } + + + + public long length() + throws IOException + { + throw new IllegalArgumentException("TNonSeekableDataOutputStream: Call to length not allowed."); + } + + + + public void writeLittleEndian32(int value) + throws IOException + { + writeByte(value & 0xFF); + writeByte((value >> 8) & 0xFF); + writeByte((value >> 16) & 0xFF); + writeByte((value >> 24) & 0xFF); + } + + + + public void writeLittleEndian16(short value) + throws IOException + { + writeByte(value & 0xFF); + writeByte((value >> 8) & 0xFF); + } +} + + + +/*** TNonSeekableDataOutputStream.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/file/TSeekableDataOutputStream.java b/songdbj/org/tritonus/share/sampled/file/TSeekableDataOutputStream.java new file mode 100644 index 0000000000..6f688c5b2e --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/TSeekableDataOutputStream.java @@ -0,0 +1,86 @@ +/* + * TSeekableDataOutputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 by Florian Bomers + * Copyright (c) 2000 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.file; + +import java.io.File; +import java.io.RandomAccessFile; +import java.io.IOException; + + + +/** + * A TDataOutputStream that allows seeking. + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ +public class TSeekableDataOutputStream +extends RandomAccessFile +implements TDataOutputStream +{ + public TSeekableDataOutputStream(File file) + throws IOException + { + super(file, "rw"); + } + + + + public boolean supportsSeek() + { + return true; + } + + + + public void writeLittleEndian32(int value) + throws IOException + { + writeByte(value & 0xFF); + writeByte((value >> 8) & 0xFF); + writeByte((value >> 16) & 0xFF); + writeByte((value >> 24) & 0xFF); + } + + + + public void writeLittleEndian16(short value) + throws IOException + { + writeByte(value & 0xFF); + writeByte((value >> 8) & 0xFF); + } +} + + + +/*** TSeekableDataOutputStream.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/file/package.html b/songdbj/org/tritonus/share/sampled/file/package.html new file mode 100644 index 0000000000..a79274048c --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/file/package.html @@ -0,0 +1,18 @@ + + + + + + +

Base classes for the implementation of AudioFileReaders and AudioFileWriters. + The classes provided here .

+ + @see javax.sound.sampled.spi.AudioFileReader + @see javax.sound.sampled.spi.AudioFileWriter + @see org.tritonus.sampled.file + @see org.tritonus.sampled.file.gsm + @see org.tritonus.sampled.file.jorbis + @see org.tritonus.sampled.file.mpeg + @see org.tritonus.sampled.file.vorbis + + diff --git a/songdbj/org/tritonus/share/sampled/mixer/TBaseDataLine.java b/songdbj/org/tritonus/share/sampled/mixer/TBaseDataLine.java new file mode 100644 index 0000000000..e589439838 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TBaseDataLine.java @@ -0,0 +1,107 @@ +/* + * TBaseDataLine.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 2004 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.util.Collection; +import java.util.EventListener; +import java.util.EventObject; +import java.util.HashSet; +import java.util.Set; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Control; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineUnavailableException; + +import org.tritonus.share.TDebug; + + + +/** Base class for implementing SourceDataLine or TargetDataLine. + */ +public abstract class TBaseDataLine +extends TDataLine +{ + public TBaseDataLine(TMixer mixer, + DataLine.Info info) + { + super(mixer, + info); + } + + + + public TBaseDataLine(TMixer mixer, + DataLine.Info info, + Collection controls) + { + super(mixer, + info, + controls); + } + + + + public void open(AudioFormat format, int nBufferSize) + throws LineUnavailableException + { + if (TDebug.TraceDataLine) { TDebug.out("TBaseDataLine.open(AudioFormat, int): called with buffer size: " + nBufferSize); } + setBufferSize(nBufferSize); + open(format); + } + + + + public void open(AudioFormat format) + throws LineUnavailableException + { + if (TDebug.TraceDataLine) { TDebug.out("TBaseDataLine.open(AudioFormat): called"); } + setFormat(format); + open(); + } + + + // IDEA: move to TDataLine or TLine? + // necessary and wise at all? + protected void finalize() + { + if (isOpen()) + { + close(); + } + } +} + + + +/*** TBaseDataLine.java ***/ + diff --git a/songdbj/org/tritonus/share/sampled/mixer/TBooleanControl.java b/songdbj/org/tritonus/share/sampled/mixer/TBooleanControl.java new file mode 100644 index 0000000000..a722edbf31 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TBooleanControl.java @@ -0,0 +1,128 @@ +/* + * TBooleanControl.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2001 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import javax.sound.sampled.BooleanControl; + +import org.tritonus.share.TDebug; + + + + +/** Base class for classes implementing BooleanControl. + */ +public class TBooleanControl +extends BooleanControl +implements TControllable +{ + private TControlController m_controller; + + + + public TBooleanControl(BooleanControl.Type type, + boolean bInitialValue) + { + this(type, bInitialValue, null); + } + + + + public TBooleanControl(BooleanControl.Type type, + boolean bInitialValue, + TCompoundControl parentControl) + { + super(type, bInitialValue); + if (TDebug.TraceControl) + { + TDebug.out("TBooleanControl.: begin"); + } + m_controller = new TControlController(); + if (TDebug.TraceControl) + { + TDebug.out("TBooleanControl.: end"); + } + } + + + + public TBooleanControl(BooleanControl.Type type, + boolean bInitialValue, + String strTrueStateLabel, + String strFalseStateLabel) + { + this(type, bInitialValue, strTrueStateLabel, strFalseStateLabel, null); + } + + + + public TBooleanControl(BooleanControl.Type type, + boolean bInitialValue, + String strTrueStateLabel, + String strFalseStateLabel, + TCompoundControl parentControl) + { + super(type, bInitialValue, strTrueStateLabel, strFalseStateLabel); + if (TDebug.TraceControl) + { + TDebug.out("TBooleanControl.: begin"); + } + m_controller = new TControlController(); + if (TDebug.TraceControl) + { + TDebug.out("TBooleanControl.: end"); + } + } + + + + public void setParentControl(TCompoundControl compoundControl) + { + m_controller.setParentControl(compoundControl); + } + + + + public TCompoundControl getParentControl() + { + return m_controller.getParentControl(); + } + + + + public void commit() + { + m_controller.commit(); + } +} + + + +/*** TBooleanControl.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TClip.java b/songdbj/org/tritonus/share/sampled/mixer/TClip.java new file mode 100644 index 0000000000..e0a8140c37 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TClip.java @@ -0,0 +1,340 @@ +/* + * TClip.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 2004 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.io.IOException; +import java.io.ByteArrayInputStream; +import java.util.Collection; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.Control; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Mixer; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.mixer.TDataLine; + + + +public class TClip +extends TDataLine +implements Clip +{ + private static final Class[] CONTROL_CLASSES = {/*GainControl.class*/}; + private static final int BUFFER_FRAMES = 16384; + + + public TClip(DataLine.Info info) + { + super(null, // TMixer + info); + } + + + + public TClip(DataLine.Info info, + Collection controls) + { + super(null, // TMixer + info, + controls); + } + + + + public void open(AudioFormat audioFormat, + byte[] abData, + int nOffset, + int nLength) + throws LineUnavailableException + { + // int nBufferLength = nNumFrames * audioFormat.getFrameSize(); + // TODO: check if nOffset + nBufferLength <= abData.length + // perhaps truncate automatically + ByteArrayInputStream bais = new ByteArrayInputStream(abData, nOffset, nLength); + AudioInputStream audioInputStream = new AudioInputStream(bais, audioFormat, AudioSystem.NOT_SPECIFIED); + try + { + open(audioInputStream); + } + catch (IOException e) + { + if (TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + throw new LineUnavailableException("IOException occured"); + } + } + + + + public void open(AudioInputStream audioInputStream) + throws LineUnavailableException, IOException + { + AudioFormat audioFormat = audioInputStream.getFormat(); + // TOOD: + DataLine.Info info = new DataLine.Info(Clip.class, + audioFormat, -1/*nBufferSize*/); +/* + setLineInfo(info); + int nFrameSize = audioFormat.getFrameSize(); + long lTotalLength = audioInputStream.getFrameLength() * nFrameSize; + int nFormat = Esd.ESD_STREAM | Esd.ESD_PLAY | EsdUtils.getEsdFormat(audioFormat); + if (TDebug.TraceClip) + { + TDebug.out("format: " + nFormat); + TDebug.out("sample rate: " + audioFormat.getSampleRate()); + } + // m_esdSample.open(nFormat, (int) audioFormat.getSampleRate(), (int) lTotalLength); + if (TDebug.TraceClip) + { + TDebug.out("size in esd: " + audioInputStream.getFrameLength() * nFrameSize); + } + int nBufferLength = BUFFER_FRAMES * nFrameSize; + byte[] abData = new byte[nBufferLength]; + int nBytesRead = 0; + int nTotalBytes = 0; + while (nBytesRead != -1) + { + try + { + nBytesRead = audioInputStream.read(abData, 0, abData.length); + } + catch (IOException e) + { + if (TDebug.TraceClip || TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + } + if (nBytesRead >= 0) + { + nTotalBytes += nBytesRead; + if (TDebug.TraceClip) + { + TDebug.out("TClip.open(): total bytes: " + nTotalBytes); + TDebug.out("TClip.open(): Trying to write: " + nBytesRead); + } + int nBytesWritten = 0; //m_esdSample.write(abData, 0, nBytesRead); + if (TDebug.TraceClip) + { + TDebug.out("TClip.open(): Written: " + nBytesWritten); + } + } + } + // to trigger the events + // open(); + */ + } + + + + public int getFrameLength() + { + // TODO: + return -1; + } + + + + public long getMicrosecondLength() + { + // TODO: + return -1; + } + + + + public void setFramePosition(int nPosition) + { + // TOOD: + } + + + + public void setMicrosecondPosition(long lPosition) + { + // TOOD: + } + + + + public int getFramePosition() + { + // TOOD: + return -1; + } + + + + public long getMicrosecondPosition() + { + // TOOD: + return -1; + } + + + + public void setLoopPoints(int nStart, int nEnd) + { + // TOOD: + } + + + + public void loop(int nCount) + { + if (TDebug.TraceClip) + { + TDebug.out("TClip.loop(int): called; count = " + nCount); + } + if (false/*isStarted()*/) + { + /* + * only allow zero count to stop the looping + * at the end of an iteration. + */ + if (nCount == 0) + { + if (TDebug.TraceClip) + { + TDebug.out("TClip.loop(int): stopping sample"); + } + // m_esdSample.stop(); + } + } + else + { + if (nCount == 0) + { + if (TDebug.TraceClip) + { + TDebug.out("TClip.loop(int): starting sample (once)"); + } + // m_esdSample.play(); + } + else + { + /* + * we're ignoring the count, because esd + * cannot loop for a fixed number of + * times. + */ + // TDebug.out("hallo"); + if (TDebug.TraceClip) + { + TDebug.out("TClip.loop(int): starting sample (forever)"); + } + // m_esdSample.loop(); + } + } + // TOOD: + } + + + + public void flush() + { + // TOOD: + } + + + + public void drain() + { + // TOOD: + } + + + + public void close() + { + // m_esdSample.free(); + // m_esdSample.close(); + // TOOD: + } + + + + + public void open() + { + // TODO: + } + + + + public void start() + { + if (TDebug.TraceClip) + { + TDebug.out("TClip.start(): called"); + } + /* + * This is a hack. What start() really should do is + * start playing at the position playback was stopped. + */ + if (TDebug.TraceClip) + { + TDebug.out("TClip.start(): calling 'loop(0)' [hack]"); + } + loop(0); + } + + + + public void stop() + { + // TODO: + // m_esdSample.kill(); + } + + + + /* + * This method is enforced by DataLine, but doesn't make any + * sense for Clips. + */ + public int available() + { + return -1; + } +} + + + +/*** TClip.java ***/ + diff --git a/songdbj/org/tritonus/share/sampled/mixer/TCompoundControl.java b/songdbj/org/tritonus/share/sampled/mixer/TCompoundControl.java new file mode 100644 index 0000000000..4a370eb86c --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TCompoundControl.java @@ -0,0 +1,90 @@ +/* + * TCompoundControl.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2001 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import javax.sound.sampled.CompoundControl; +import javax.sound.sampled.Control; + +import org.tritonus.share.TDebug; + + + + +/** Base class for classes implementing Line. + */ +public class TCompoundControl +extends CompoundControl +implements TControllable +{ + private TControlController m_controller; + + + + public TCompoundControl(CompoundControl.Type type, + Control[] aMemberControls) + { + super(type, aMemberControls); + if (TDebug.TraceControl) + { + TDebug.out("TCompoundControl.: begin"); + } + m_controller = new TControlController(); + if (TDebug.TraceControl) + { + TDebug.out("TCompoundControl.: end"); + } + } + + + + public void setParentControl(TCompoundControl compoundControl) + { + m_controller.setParentControl(compoundControl); + } + + + + public TCompoundControl getParentControl() + { + return m_controller.getParentControl(); + } + + + + public void commit() + { + m_controller.commit(); + } +} + + + +/*** TCompoundControl.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TCompoundControlType.java b/songdbj/org/tritonus/share/sampled/mixer/TCompoundControlType.java new file mode 100644 index 0000000000..1b90b1a673 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TCompoundControlType.java @@ -0,0 +1,55 @@ +/* + * TCompoundControlType.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2001 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import javax.sound.sampled.CompoundControl; + + + +/** CompoundControl.Type class. + This class is only needed to provide a public constructor. + */ +public class TCompoundControlType +extends CompoundControl.Type +{ + /** Constructor. + Constructs a CompoundControl.Type with the + name given. + + @param strName The name of the control. + */ + public TCompoundControlType(String strName) + { + super(strName); + } +} + + + +/*** TCompoundControlType.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TControlController.java b/songdbj/org/tritonus/share/sampled/mixer/TControlController.java new file mode 100644 index 0000000000..ec17c45b59 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TControlController.java @@ -0,0 +1,98 @@ +/* + * TControlController.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2001 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.util.Collection; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Control; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Port; + +import org.tritonus.share.TDebug; + + + + +/** Base class for classes implementing Line. + */ +public class TControlController +implements TControllable +{ + /** The parent (compound) control. + In case this control is part of a compound control, the parentControl + property is set to a value other than null. + */ + private TCompoundControl m_parentControl; + + + public TControlController() + { + } + + + + public void setParentControl(TCompoundControl compoundControl) + { + m_parentControl = compoundControl; + } + + + public TCompoundControl getParentControl() + { + return m_parentControl; + } + + + public void commit() + { + if (TDebug.TraceControl) + { + TDebug.out("TControlController.commit(): called [" + this.getClass().getName() + "]"); + } + if (getParentControl() != null) + { + getParentControl().commit(); + } + } +} + + + +/*** TControlController.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TControllable.java b/songdbj/org/tritonus/share/sampled/mixer/TControllable.java new file mode 100644 index 0000000000..b89d34a2b3 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TControllable.java @@ -0,0 +1,62 @@ +/* + * TControllable.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2001 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.util.Collection; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Control; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Port; + +import org.tritonus.share.TDebug; + + + + +public interface TControllable +{ + public void setParentControl(TCompoundControl compoundControl); + public TCompoundControl getParentControl(); + public void commit(); +} + + + +/*** TControllable.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TDataLine.java b/songdbj/org/tritonus/share/sampled/mixer/TDataLine.java new file mode 100644 index 0000000000..a493bac6c5 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TDataLine.java @@ -0,0 +1,304 @@ +/* + * TDataLine.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 2004 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.util.Collection; +import java.util.EventListener; +import java.util.EventObject; +import java.util.HashSet; +import java.util.Set; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Control; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.Line; + +import org.tritonus.share.TDebug; + + + +/** Base class for classes implementing DataLine. + */ +public abstract class TDataLine +extends TLine +implements DataLine +{ + private static final int DEFAULT_BUFFER_SIZE = 128000; + + private AudioFormat m_format; + private int m_nBufferSize; + private boolean m_bRunning; + // private boolean m_bActive; + + + + + public TDataLine(TMixer mixer, + DataLine.Info info) + { + super(mixer, + info); + init(info); + } + + + + public TDataLine(TMixer mixer, + DataLine.Info info, + Collection controls) + { + super(mixer, + info, + controls); + init(info); + } + + + + // IDEA: extract format and bufsize from info? + private void init(DataLine.Info info) + { + m_format = null; + m_nBufferSize = AudioSystem.NOT_SPECIFIED; + setRunning(false); + // setActive(false); + } + + + + // not defined here: + // public void drain() + // public void flush() + + + + public void start() + { + if (TDebug.TraceSourceDataLine) + { + TDebug.out("TDataLine.start(): called"); + } + setRunning(true); + } + + + + public void stop() + { + if (TDebug.TraceSourceDataLine) + { + TDebug.out("TDataLine.stop(): called"); + } + setRunning(false); + } + + + + public boolean isRunning() + { + return m_bRunning; + } + + + + // TODO: recheck + protected void setRunning(boolean bRunning) + { + boolean bOldValue = isRunning(); + m_bRunning = bRunning; + if (bOldValue != isRunning()) + { + if (isRunning()) + { + startImpl(); + notifyLineEvent(LineEvent.Type.START); + } + else + { + stopImpl(); + notifyLineEvent(LineEvent.Type.STOP); + } + } + } + + + + protected void startImpl() + { + } + + + + protected void stopImpl() + { + } + + + + /** + * This implementation returns the status of isRunning(). + * Subclasses should overwrite this method if there is more + * precise information about the status of the line available. + */ + public boolean isActive() + { + return isRunning(); + } + + +/* + public boolean isStarted() + { + return m_bStarted; + } +*/ + + // TODO: should only ALLOW engaging in data I/O. + // actual START event should only be sent when line really becomes active +/* + protected void setStarted(boolean bStarted) + { + m_bStarted = bStarted; + if (!isRunning()) + { + setActive(false); + } + } +*/ + + + public AudioFormat getFormat() + { + return m_format; + } + + + + protected void setFormat(AudioFormat format) + { + if (TDebug.TraceDataLine) + { + TDebug.out("TDataLine.setFormat(): setting: " + format); + } + m_format = format; + } + + + + public int getBufferSize() + { + return m_nBufferSize; + } + + + + protected void setBufferSize(int nBufferSize) + { + if (TDebug.TraceDataLine) + { + TDebug.out("TDataLine.setBufferSize(): setting: " + nBufferSize); + } + m_nBufferSize = nBufferSize; + } + + + + // not defined here: + // public int available() + + + + public int getFramePosition() + { + // TODO: + return -1; + } + + + + public long getLongFramePosition() + { + // TODO: + return -1; + } + + + + public long getMicrosecondPosition() + { + return (long) (getFramePosition() * getFormat().getFrameRate() * 1000000); + } + + + + /* + * Has to be overridden to be useful. + */ + public float getLevel() + { + return AudioSystem.NOT_SPECIFIED; + } + + + + protected void checkOpen() + { + if (getFormat() == null) + { + throw new IllegalStateException("format must be specified"); + } + if (getBufferSize() == AudioSystem.NOT_SPECIFIED) + { + setBufferSize(getDefaultBufferSize()); + } + } + + + + protected int getDefaultBufferSize() + { + return DEFAULT_BUFFER_SIZE; + } + + + + protected void notifyLineEvent(LineEvent.Type type) + { + notifyLineEvent(new LineEvent(this, type, getFramePosition())); + } +} + + + +/*** TDataLine.java ***/ + diff --git a/songdbj/org/tritonus/share/sampled/mixer/TEnumControl.java b/songdbj/org/tritonus/share/sampled/mixer/TEnumControl.java new file mode 100644 index 0000000000..2c9132401f --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TEnumControl.java @@ -0,0 +1,92 @@ +/* + * TEnumControl.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2001 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import javax.sound.sampled.EnumControl; + +import org.tritonus.share.TDebug; + + + + +/** Base class for classes implementing Line. + */ +public class TEnumControl +extends EnumControl +implements TControllable +{ + private TControlController m_controller; + + + + public TEnumControl(EnumControl.Type type, + Object[] aValues, + Object value) + { + super(type, + aValues, + value); + if (TDebug.TraceControl) + { + TDebug.out("TEnumControl.: begin"); + } + m_controller = new TControlController(); + if (TDebug.TraceControl) + { + TDebug.out("TEnumControl.: end"); + } + } + + + + public void setParentControl(TCompoundControl compoundControl) + { + m_controller.setParentControl(compoundControl); + } + + + + public TCompoundControl getParentControl() + { + return m_controller.getParentControl(); + } + + + + public void commit() + { + m_controller.commit(); + } +} + + + +/*** TEnumControl.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TFloatControl.java b/songdbj/org/tritonus/share/sampled/mixer/TFloatControl.java new file mode 100644 index 0000000000..8a80016865 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TFloatControl.java @@ -0,0 +1,134 @@ +/* + * TFloatControl.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2001 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import javax.sound.sampled.FloatControl; + +import org.tritonus.share.TDebug; + + + + +/** Base class for classes implementing Line. + */ +public class TFloatControl +extends FloatControl +implements TControllable +{ + private TControlController m_controller; + + + + public TFloatControl(FloatControl.Type type, + float fMinimum, + float fMaximum, + float fPrecision, + int nUpdatePeriod, + float fInitialValue, + String strUnits) + { + super(type, + fMinimum, + fMaximum, + fPrecision, + nUpdatePeriod, + fInitialValue, + strUnits); + if (TDebug.TraceControl) + { + TDebug.out("TFloatControl.: begin"); + } + m_controller = new TControlController(); + if (TDebug.TraceControl) + { + TDebug.out("TFloatControl.: end"); + } + } + + + + public TFloatControl(FloatControl.Type type, + float fMinimum, + float fMaximum, + float fPrecision, + int nUpdatePeriod, + float fInitialValue, + String strUnits, + String strMinLabel, + String strMidLabel, + String strMaxLabel) + { + super(type, + fMinimum, + fMaximum, + fPrecision, + nUpdatePeriod, + fInitialValue, + strUnits, + strMinLabel, + strMidLabel, + strMaxLabel); + if (TDebug.TraceControl) + { + TDebug.out("TFloatControl.: begin"); + } + m_controller = new TControlController(); + if (TDebug.TraceControl) + { + TDebug.out("TFloatControl.: end"); + } + } + + + + public void setParentControl(TCompoundControl compoundControl) + { + m_controller.setParentControl(compoundControl); + } + + + + public TCompoundControl getParentControl() + { + return m_controller.getParentControl(); + } + + + + public void commit() + { + m_controller.commit(); + } +} + + + +/*** TFloatControl.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TLine.java b/songdbj/org/tritonus/share/sampled/mixer/TLine.java new file mode 100644 index 0000000000..89b38099ed --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TLine.java @@ -0,0 +1,362 @@ +/* + * TLine.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 2004 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.util.Collection; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Control; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; + +import org.tritonus.share.TDebug; +import org.tritonus.share.TNotifier; + + + + +/** Base class for classes implementing Line. + */ +public abstract class TLine +implements Line +{ + private static final Control[] EMPTY_CONTROL_ARRAY = new Control[0]; + + private Line.Info m_info; + private boolean m_bOpen; + private List m_controls; + private Set m_lineListeners; + private TMixer m_mixer; + + + + protected TLine(TMixer mixer, + Line.Info info) + { + setLineInfo(info); + setOpen(false); + m_controls = new ArrayList(); + m_lineListeners = new HashSet(); + m_mixer = mixer; + } + + + + protected TLine(TMixer mixer, + Line.Info info, + Collection controls) + { + this (mixer, info); + m_controls.addAll(controls); + } + + + protected TMixer getMixer() + { + return m_mixer; + } + + + public Line.Info getLineInfo() + { + return m_info; + } + + + + protected void setLineInfo(Line.Info info) + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.setLineInfo(): setting: " + info); + } + synchronized (this) + { + m_info = info; + } + } + + + + public void open() + throws LineUnavailableException + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.open(): called"); + } + if (! isOpen()) + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.open(): opening"); + } + openImpl(); + if (getMixer() != null) + { + getMixer().registerOpenLine(this); + } + setOpen(true); + } + else + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.open(): already open"); + } + } + } + + + + /** + * Subclasses should override this method. + */ + protected void openImpl() + throws LineUnavailableException + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.openImpl(): called"); + } + } + + + + public void close() + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.close(): called"); + } + if (isOpen()) + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.close(): closing"); + } + if (getMixer() != null) + { + getMixer().unregisterOpenLine(this); + } + closeImpl(); + setOpen(false); + } + else + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.close(): not open"); + } + } + } + + + + /** + * Subclasses should override this method. + */ + protected void closeImpl() + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.closeImpl(): called"); + } + } + + + + + + public boolean isOpen() + { + return m_bOpen; + } + + + + + protected void setOpen(boolean bOpen) + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.setOpen(): called, value: " + bOpen); + } + boolean bOldValue = isOpen(); + m_bOpen = bOpen; + if (bOldValue != isOpen()) + { + if (isOpen()) + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.setOpen(): opened"); + } + notifyLineEvent(LineEvent.Type.OPEN); + } + else + { + if (TDebug.TraceLine) + { + TDebug.out("TLine.setOpen(): closed"); + } + notifyLineEvent(LineEvent.Type.CLOSE); + } + } + } + + + + protected void addControl(Control control) + { + synchronized (m_controls) + { + m_controls.add(control); + } + } + + + + protected void removeControl(Control control) + { + synchronized (m_controls) + { + m_controls.remove(control); + } + } + + + + public Control[] getControls() + { + synchronized (m_controls) + { + return m_controls.toArray(EMPTY_CONTROL_ARRAY); + } + } + + + + public Control getControl(Control.Type controlType) + { + synchronized (m_controls) + { + Iterator it = m_controls.iterator(); + while (it.hasNext()) + { + Control control = it.next(); + if (control.getType().equals(controlType)) + { + return control; + } + } + throw new IllegalArgumentException("no control of type " + controlType); + } + } + + + + public boolean isControlSupported(Control.Type controlType) + { + // TDebug.out("TLine.isSupportedControl(): called"); + try + { + return getControl(controlType) != null; + } + catch (IllegalArgumentException e) + { + if (TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + // TDebug.out("TLine.isSupportedControl(): returning false"); + return false; + } + } + + + + public void addLineListener(LineListener listener) + { + // TDebug.out("%% TChannel.addListener(): called"); + synchronized (m_lineListeners) + { + m_lineListeners.add(listener); + } + } + + + + public void removeLineListener(LineListener listener) + { + synchronized (m_lineListeners) + { + m_lineListeners.remove(listener); + } + } + + + + private Set getLineListeners() + { + synchronized (m_lineListeners) + { + return new HashSet(m_lineListeners); + } + } + + + // is overridden in TDataLine to provide a position + protected void notifyLineEvent(LineEvent.Type type) + { + notifyLineEvent(new LineEvent(this, type, AudioSystem.NOT_SPECIFIED)); + } + + + + protected void notifyLineEvent(LineEvent event) + { + // TDebug.out("%% TChannel.notifyChannelEvent(): called"); + // Channel.Event event = new Channel.Event(this, type, getPosition()); + TNotifier.notifier.addEntry(event, getLineListeners()); + } +} + + + +/*** TLine.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TMixer.java b/songdbj/org/tritonus/share/sampled/mixer/TMixer.java new file mode 100644 index 0000000000..6a5dc4db72 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TMixer.java @@ -0,0 +1,506 @@ +/* + * TMixer.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 2004 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.Clip; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Mixer; +import javax.sound.sampled.Port; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.TargetDataLine; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.AudioFormats; +import org.tritonus.share.ArraySet; + + + +// TODO: global controls (that use the system mixer) +public abstract class TMixer +extends TLine +implements Mixer +{ + private static Line.Info[] EMPTY_LINE_INFO_ARRAY = new Line.Info[0]; + private static Line[] EMPTY_LINE_ARRAY = new Line[0]; + + private Mixer.Info m_mixerInfo; + private Collection m_supportedSourceFormats; + private Collection m_supportedTargetFormats; + private Collection m_supportedSourceLineInfos; + private Collection m_supportedTargetLineInfos; + private Set m_openSourceDataLines; + private Set m_openTargetDataLines; + + + /** Constructor for mixers that use setSupportInformation(). + */ + protected TMixer(Mixer.Info mixerInfo, + Line.Info lineInfo) + { + this(mixerInfo, + lineInfo, + new ArrayList(), + new ArrayList(), + new ArrayList(), + new ArrayList()); + } + + + + /** Constructor for mixers. + */ + protected TMixer(Mixer.Info mixerInfo, + Line.Info lineInfo, + Collection supportedSourceFormats, + Collection supportedTargetFormats, + Collection supportedSourceLineInfos, + Collection supportedTargetLineInfos) + { + super(null, // TMixer + lineInfo); + if (TDebug.TraceMixer) { TDebug.out("TMixer.(): begin"); } + m_mixerInfo = mixerInfo; + setSupportInformation( + supportedSourceFormats, + supportedTargetFormats, + supportedSourceLineInfos, + supportedTargetLineInfos); + m_openSourceDataLines = new ArraySet(); + m_openTargetDataLines = new ArraySet(); + if (TDebug.TraceMixer) { TDebug.out("TMixer.(): end"); } + } + + + + protected void setSupportInformation( + Collection supportedSourceFormats, + Collection supportedTargetFormats, + Collection supportedSourceLineInfos, + Collection supportedTargetLineInfos) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.setSupportInformation(): begin"); } + m_supportedSourceFormats = supportedSourceFormats; + m_supportedTargetFormats = supportedTargetFormats; + m_supportedSourceLineInfos = supportedSourceLineInfos; + m_supportedTargetLineInfos = supportedTargetLineInfos; + if (TDebug.TraceMixer) { TDebug.out("TMixer.setSupportInformation(): end"); } + } + + + + public Mixer.Info getMixerInfo() + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getMixerInfo(): begin"); } + if (TDebug.TraceMixer) { TDebug.out("TMixer.getMixerInfo(): end"); } + return m_mixerInfo; + } + + + + public Line.Info[] getSourceLineInfo() + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSourceLineInfo(): begin"); } + Line.Info[] infos = (Line.Info[]) m_supportedSourceLineInfos.toArray(EMPTY_LINE_INFO_ARRAY); + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSourceLineInfo(): end"); } + return infos; + } + + + + public Line.Info[] getTargetLineInfo() + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getTargetLineInfo(): begin"); } + Line.Info[] infos = (Line.Info[]) m_supportedTargetLineInfos.toArray(EMPTY_LINE_INFO_ARRAY); + if (TDebug.TraceMixer) { TDebug.out("TMixer.getTargetLineInfo(): end"); } + return infos; + } + + + + public Line.Info[] getSourceLineInfo(Line.Info info) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSourceLineInfo(Line.Info): info to test: " + info); } + // TODO: + return EMPTY_LINE_INFO_ARRAY; + } + + + + public Line.Info[] getTargetLineInfo(Line.Info info) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getTargetLineInfo(Line.Info): info to test: " + info); } + // TODO: + return EMPTY_LINE_INFO_ARRAY; + } + + + + public boolean isLineSupported(Line.Info info) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.isLineSupported(): info to test: " + info); } + Class lineClass = info.getLineClass(); + if (lineClass.equals(SourceDataLine.class)) + { + return isLineSupportedImpl(info, m_supportedSourceLineInfos); + } + else if (lineClass.equals(TargetDataLine.class)) + { + return isLineSupportedImpl(info, m_supportedTargetLineInfos); + } + else if (lineClass.equals(Port.class)) + { + return isLineSupportedImpl(info, m_supportedSourceLineInfos) || isLineSupportedImpl(info, m_supportedTargetLineInfos); + } + else + { + return false; + } + } + + + + private static boolean isLineSupportedImpl(Line.Info info, Collection supportedLineInfos) + { + Iterator iterator = supportedLineInfos.iterator(); + while (iterator.hasNext()) + { + Line.Info info2 = (Line.Info) iterator.next(); + if (info2.matches(info)) + { + return true; + } + } + return false; + } + + + + public Line getLine(Line.Info info) + throws LineUnavailableException + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getLine(): begin"); } + Class lineClass = info.getLineClass(); + DataLine.Info dataLineInfo = null; + Port.Info portInfo = null; + AudioFormat[] aFormats = null; + if (info instanceof DataLine.Info) + { + dataLineInfo = (DataLine.Info) info; + aFormats = dataLineInfo.getFormats(); + } + else if (info instanceof Port.Info) + { + portInfo = (Port.Info) info; + } + AudioFormat format = null; + Line line = null; + if (lineClass == SourceDataLine.class) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getLine(): type: SourceDataLine"); } + if (dataLineInfo == null) + { + throw new IllegalArgumentException("need DataLine.Info for SourceDataLine"); + } + format = getSupportedSourceFormat(aFormats); + line = getSourceDataLine(format, dataLineInfo.getMaxBufferSize()); + } + else if (lineClass == Clip.class) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getLine(): type: Clip"); } + if (dataLineInfo == null) + { + throw new IllegalArgumentException("need DataLine.Info for Clip"); + } + format = getSupportedSourceFormat(aFormats); + line = getClip(format); + } + else if (lineClass == TargetDataLine.class) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getLine(): type: TargetDataLine"); } + if (dataLineInfo == null) + { + throw new IllegalArgumentException("need DataLine.Info for TargetDataLine"); + } + format = getSupportedTargetFormat(aFormats); + line = getTargetDataLine(format, dataLineInfo.getMaxBufferSize()); + } + else if (lineClass == Port.class) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getLine(): type: TargetDataLine"); } + if (portInfo == null) + { + throw new IllegalArgumentException("need Port.Info for Port"); + } + line = getPort(portInfo); + } + else + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getLine(): unknown line type, will throw exception"); } + throw new LineUnavailableException("unknown line class: " + lineClass); + } + if (TDebug.TraceMixer) { TDebug.out("TMixer.getLine(): end"); } + return line; + } + + + + protected SourceDataLine getSourceDataLine(AudioFormat format, int nBufferSize) + throws LineUnavailableException + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSourceDataLine(): begin"); } + throw new IllegalArgumentException("this mixer does not support SourceDataLines"); + } + + + + protected Clip getClip(AudioFormat format) + throws LineUnavailableException + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getClip(): begin"); } + throw new IllegalArgumentException("this mixer does not support Clips"); + } + + + + protected TargetDataLine getTargetDataLine(AudioFormat format, int nBufferSize) + throws LineUnavailableException + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getTargetDataLine(): begin"); } + throw new IllegalArgumentException("this mixer does not support TargetDataLines"); + } + + + + protected Port getPort(Port.Info info) + throws LineUnavailableException + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getTargetDataLine(): begin"); } + throw new IllegalArgumentException("this mixer does not support Ports"); + } + + + + private AudioFormat getSupportedSourceFormat(AudioFormat[] aFormats) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSupportedSourceFormat(): begin"); } + AudioFormat format = null; + for (int i = 0; i < aFormats.length; i++) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSupportedSourceFormat(): checking " + aFormats[i] + "..."); } + if (isSourceFormatSupported(aFormats[i])) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSupportedSourceFormat(): ...supported"); } + format = aFormats[i]; + break; + } + else + { + if (TDebug.TraceMixer) + { + TDebug.out("TMixer.getSupportedSourceFormat(): ...no luck"); + } + } + } + if (format == null) + { + throw new IllegalArgumentException("no line matchine one of the passed formats"); + } + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSupportedSourceFormat(): end"); } + return format; + } + + + + private AudioFormat getSupportedTargetFormat(AudioFormat[] aFormats) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSupportedTargetFormat(): begin"); } + AudioFormat format = null; + for (int i = 0; i < aFormats.length; i++) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSupportedTargetFormat(): checking " + aFormats[i] + " ..."); } + if (isTargetFormatSupported(aFormats[i])) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSupportedTargetFormat(): ...supported"); } + format = aFormats[i]; + break; + } + else + { + if (TDebug.TraceMixer) + { + TDebug.out("TMixer.getSupportedTargetFormat(): ...no luck"); + } + } + } + if (format == null) + { + throw new IllegalArgumentException("no line matchine one of the passed formats"); + } + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSupportedTargetFormat(): end"); } + return format; + } + + + +/* + not implemented here: + getMaxLines(Line.Info) +*/ + + + + public Line[] getSourceLines() + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getSourceLines(): called"); } + return (Line[]) m_openSourceDataLines.toArray(EMPTY_LINE_ARRAY); + } + + + + public Line[] getTargetLines() + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.getTargetLines(): called"); } + return (Line[]) m_openTargetDataLines.toArray(EMPTY_LINE_ARRAY); + } + + + + public void synchronize(Line[] aLines, + boolean bMaintainSync) + { + throw new IllegalArgumentException("synchronization not supported"); + } + + + + public void unsynchronize(Line[] aLines) + { + throw new IllegalArgumentException("synchronization not supported"); + } + + + + public boolean isSynchronizationSupported(Line[] aLines, + boolean bMaintainSync) + { + return false; + } + + + + protected boolean isSourceFormatSupported(AudioFormat format) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.isSourceFormatSupported(): format to test: " + format); } + Iterator iterator = m_supportedSourceFormats.iterator(); + while (iterator.hasNext()) + { + AudioFormat supportedFormat = iterator.next(); + if (AudioFormats.matches(supportedFormat, format)) + { + return true; + } + } + return false; + } + + + + protected boolean isTargetFormatSupported(AudioFormat format) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.isTargetFormatSupported(): format to test: " + format); } + Iterator iterator = m_supportedTargetFormats.iterator(); + while (iterator.hasNext()) + { + AudioFormat supportedFormat = iterator.next(); + if (AudioFormats.matches(supportedFormat, format)) + { + return true; + } + } + return false; + } + + + + /*package*/ void registerOpenLine(Line line) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.registerOpenLine(): line to register: " + line); + } + if (line instanceof SourceDataLine) + { + synchronized (m_openSourceDataLines) + { + m_openSourceDataLines.add((SourceDataLine) line); + } + } + else if (line instanceof TargetDataLine) + { + synchronized (m_openSourceDataLines) + { + m_openTargetDataLines.add((TargetDataLine) line); + } + } + } + + + + /*package*/ void unregisterOpenLine(Line line) + { + if (TDebug.TraceMixer) { TDebug.out("TMixer.unregisterOpenLine(): line to unregister: " + line); } + if (line instanceof SourceDataLine) + { + synchronized (m_openSourceDataLines) + { + m_openSourceDataLines.remove((SourceDataLine) line); + } + } + else if (line instanceof TargetDataLine) + { + synchronized (m_openTargetDataLines) + { + m_openTargetDataLines.remove((TargetDataLine) line); + } + } + } +} + + + +/*** TMixer.java ***/ + diff --git a/songdbj/org/tritonus/share/sampled/mixer/TMixerInfo.java b/songdbj/org/tritonus/share/sampled/mixer/TMixerInfo.java new file mode 100644 index 0000000000..cb4b7cc860 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TMixerInfo.java @@ -0,0 +1,56 @@ +/* + * TMixerInfo.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999, 2000 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import javax.sound.sampled.Mixer; + +import org.tritonus.share.TDebug; + + + + +/* + * This is needed only because Mixer.Info's constructor + * is protected (in the Sun jdk1.3). + */ +public class TMixerInfo +extends Mixer.Info +{ + public TMixerInfo(String a, String b, String c, String d) + { + super(a, b, c, d); + } +} + + + +/*** TMixerInfo.java ***/ + diff --git a/songdbj/org/tritonus/share/sampled/mixer/TMixerProvider.java b/songdbj/org/tritonus/share/sampled/mixer/TMixerProvider.java new file mode 100644 index 0000000000..3116d74dc3 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TMixerProvider.java @@ -0,0 +1,240 @@ +/* + * TMixerProvider.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 2004 by Matthias Pfisterer + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.sound.sampled.Mixer; +import javax.sound.sampled.spi.MixerProvider; + +import org.tritonus.share.TDebug; + + + +public abstract class TMixerProvider +extends MixerProvider +{ + private static final Mixer.Info[] EMPTY_MIXER_INFO_ARRAY = new Mixer.Info[0]; + + private static Map sm_mixerProviderStructs = new HashMap(); + + private boolean m_bDisabled = false; + + + + + public TMixerProvider() + { + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.(): begin"); } + // currently does nothing + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.(): end"); } + } + + + + /* + Override this method if you want a thread-safe static initializaiton. + */ + protected void staticInit() + { + } + + + + private MixerProviderStruct getMixerProviderStruct() + { + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.getMixerProviderStruct(): begin"); } + Class cls = this.getClass(); + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.getMixerProviderStruct(): called from " + cls); } + // Thread.dumpStack(); + synchronized (TMixerProvider.class) + { + MixerProviderStruct struct = sm_mixerProviderStructs.get(cls); + if (struct == null) + { + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.getMixerProviderStruct(): creating new MixerProviderStruct for " + cls); } + struct = new MixerProviderStruct(); + sm_mixerProviderStructs.put(cls, struct); + } + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.getMixerProviderStruct(): end"); } + return struct; + } + } + + + + protected void disable() + { + if (TDebug.TraceMixerProvider) { TDebug.out("disabling " + getClass().getName()); } + m_bDisabled = true; + } + + + protected boolean isDisabled() + { + return m_bDisabled; + } + + + + protected void addMixer(Mixer mixer) + { + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.addMixer(): begin"); } + MixerProviderStruct struct = getMixerProviderStruct(); + synchronized (struct) + { + struct.m_mixers.add(mixer); + if (struct.m_defaultMixer == null) + { + struct.m_defaultMixer = mixer; + } + } + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.addMixer(): end"); } + } + + + + protected void removeMixer(Mixer mixer) + { + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.removeMixer(): begin"); } + MixerProviderStruct struct = getMixerProviderStruct(); + synchronized (struct) + { + struct.m_mixers.remove(mixer); + // TODO: should search for another mixer + if (struct.m_defaultMixer == mixer) + { + struct.m_defaultMixer = null; + } + } + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.removeMixer(): end"); } + } + + + // $$mp 2003/01/11: TODO: this implementation may become obsolete once the overridden method in spi.MixerProvider is implemented in a way documented officially. + public boolean isMixerSupported(Mixer.Info info) + { + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.isMixerSupported(): begin"); } + boolean bIsSupported = false; + Mixer.Info[] infos = getMixerInfo(); + for (int i = 0; i < infos.length; i++) + { + if (infos[i].equals(info)) + { + bIsSupported = true; + break; + } + } + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.isMixerSupported(): end"); } + return bIsSupported; + } + + + + /** + */ + public Mixer getMixer(Mixer.Info info) + { + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.getMixer(): begin"); } + MixerProviderStruct struct = getMixerProviderStruct(); + Mixer mixerResult = null; + synchronized (struct) + { + if (info == null) + { + mixerResult = struct.m_defaultMixer; + } + else + { + Iterator mixers = struct.m_mixers.iterator(); + while (mixers.hasNext()) + { + Mixer mixer = (Mixer) mixers.next(); + if (mixer.getMixerInfo().equals(info)) + { + mixerResult = mixer; + break; + } + } + } + } + if (mixerResult == null) + { + throw new IllegalArgumentException("no mixer available for " + info); + } + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.getMixer(): end"); } + return mixerResult; + } + + + + public Mixer.Info[] getMixerInfo() + { + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.getMixerInfo(): begin"); } + Set mixerInfos = new HashSet(); + MixerProviderStruct struct = getMixerProviderStruct(); + synchronized (struct) + { + Iterator mixers = struct.m_mixers.iterator(); + while (mixers.hasNext()) + { + Mixer mixer = mixers.next(); + mixerInfos.add(mixer.getMixerInfo()); + } + } + if (TDebug.TraceMixerProvider) { TDebug.out("TMixerProvider.getMixerInfo(): end"); } + return mixerInfos.toArray(EMPTY_MIXER_INFO_ARRAY); + } + + + + private class MixerProviderStruct + { + public List m_mixers; + public Mixer m_defaultMixer; + + + + public MixerProviderStruct() + { + m_mixers = new ArrayList(); + m_defaultMixer = null; + } + } +} + + + +/*** TMixerProvider.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TPort.java b/songdbj/org/tritonus/share/sampled/mixer/TPort.java new file mode 100644 index 0000000000..18d5abae00 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TPort.java @@ -0,0 +1,77 @@ +/* + * TPort.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 2004 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.util.Collection; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Control; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Port; + +import org.tritonus.share.TDebug; + + + + +/** Base class for Ports. + */ +public class TPort +extends TLine +implements Port +{ + public TPort(TMixer mixer, + Line.Info info) + { + super(mixer, info); + } + + + + public TPort(TMixer mixer, + Line.Info info, + Collection controls) + { + super(mixer, info, controls); + } +} + + + +/*** TPort.java ***/ diff --git a/songdbj/org/tritonus/share/sampled/mixer/TSoftClip.java b/songdbj/org/tritonus/share/sampled/mixer/TSoftClip.java new file mode 100644 index 0000000000..b5a8aea2c1 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/TSoftClip.java @@ -0,0 +1,318 @@ +/* + * TSoftClip.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 by Matthias Pfisterer + * + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +|<--- this code is formatted to fit into 80 columns --->| +*/ + +package org.tritonus.share.sampled.mixer; + +import java.io.IOException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Mixer; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.mixer.TDataLine; + + + +public class TSoftClip +extends TClip +implements Runnable +{ + private static final Class[] CONTROL_CLASSES = {/*GainControl.class*/}; + private static final int BUFFER_SIZE = 16384; + + + private Mixer m_mixer; + private SourceDataLine m_line; + private byte[] m_abClip; + private int m_nRepeatCount; + private Thread m_thread; + + public TSoftClip(Mixer mixer, AudioFormat format) + throws LineUnavailableException + { + // TODO: info object +/* + DataLine.Info info = new DataLine.Info(Clip.class, + audioFormat, -1); +*/ + super(null); + m_mixer = mixer; + DataLine.Info info = new DataLine.Info( + SourceDataLine.class, + // TODO: should pass a real AudioFormat object that isn't too restrictive + format); + m_line = (SourceDataLine) AudioSystem.getLine(info); + } + + + + public void open(AudioInputStream audioInputStream) + throws LineUnavailableException, IOException + { + AudioFormat audioFormat = audioInputStream.getFormat(); + setFormat(audioFormat); + int nFrameSize = audioFormat.getFrameSize(); + if (nFrameSize < 1) + { + throw new IllegalArgumentException("frame size must be positive"); + } + if (TDebug.TraceClip) + { + TDebug.out("TSoftClip.open(): format: " + audioFormat); + // TDebug.out("sample rate: " + audioFormat.getSampleRate()); + } + byte[] abData = new byte[BUFFER_SIZE]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int nBytesRead = 0; + while (nBytesRead != -1) + { + try + { + nBytesRead = audioInputStream.read(abData, 0, abData.length); + } + catch (IOException e) + { + if (TDebug.TraceClip || TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + } + if (nBytesRead >= 0) + { + if (TDebug.TraceClip) + { + TDebug.out("TSoftClip.open(): Trying to write: " + nBytesRead); + } + baos.write(abData, 0, nBytesRead); + if (TDebug.TraceClip) + { + TDebug.out("TSoftClip.open(): Written: " + nBytesRead); + } + } + } + m_abClip = baos.toByteArray(); + setBufferSize(m_abClip.length); + // open the line + m_line.open(getFormat()); + // to trigger the events + // open(); + } + + + + public int getFrameLength() + { + if (isOpen()) + { + return getBufferSize() / getFormat().getFrameSize(); + } + else + { + return AudioSystem.NOT_SPECIFIED; + } + } + + + + public long getMicrosecondLength() + { + if (isOpen()) + { + return (long) (getFrameLength() * getFormat().getFrameRate() * 1000000); + } + else + { + return AudioSystem.NOT_SPECIFIED; + } + } + + + + public void setFramePosition(int nPosition) + { + // TOOD: + } + + + + public void setMicrosecondPosition(long lPosition) + { + // TOOD: + } + + + + public int getFramePosition() + { + // TOOD: + return -1; + } + + + + public long getMicrosecondPosition() + { + // TOOD: + return -1; + } + + + + public void setLoopPoints(int nStart, int nEnd) + { + // TOOD: + } + + + + public void loop(int nCount) + { + if (TDebug.TraceClip) + { + TDebug.out("TSoftClip.loop(int): called; count = " + nCount); + } + if (false/*isStarted()*/) + { + /* + * only allow zero count to stop the looping + * at the end of an iteration. + */ + if (nCount == 0) + { + if (TDebug.TraceClip) + { + TDebug.out("TSoftClip.loop(int): stopping sample"); + } + // m_esdSample.stop(); + } + } + else + { + m_nRepeatCount = nCount; + m_thread = new Thread(this); + m_thread.start(); + } + // TOOD: + } + + + + public void flush() + { + // TOOD: + } + + + + public void drain() + { + // TOOD: + } + + + + public void close() + { + // m_esdSample.free(); + // m_esdSample.close(); + // TOOD: + } + + + + + public void open() + { + // TODO: + } + + + + public void start() + { + if (TDebug.TraceClip) + { + TDebug.out("TSoftClip.start(): called"); + } + /* + * This is a hack. What start() really should do is + * start playing at the position playback was stopped. + */ + if (TDebug.TraceClip) + { + TDebug.out("TSoftClip.start(): calling 'loop(0)' [hack]"); + } + loop(0); + } + + + + public void stop() + { + // TODO: + // m_esdSample.kill(); + } + + + + /* + * This method is enforced by DataLine, but doesn't make any + * sense for Clips. + */ + public int available() + { + return -1; + } + + + + public void run() + { + while (m_nRepeatCount >= 0) + { + m_line.write(m_abClip, 0, m_abClip.length); + m_nRepeatCount--; + } + } + +} + + + +/*** TSoftClip.java ***/ + diff --git a/songdbj/org/tritonus/share/sampled/mixer/package.html b/songdbj/org/tritonus/share/sampled/mixer/package.html new file mode 100644 index 0000000000..681024bc9d --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/mixer/package.html @@ -0,0 +1,14 @@ + + + + + + +

Base classes for the implementation of MixerProviders. + The classes provided here .

+ + @see javax.sound.sampled.spi.MixerProvider + @see org.tritonus.sampled.mixer.alsa + @see org.tritonus.sampled.mixer.esd + + diff --git a/songdbj/org/tritonus/share/sampled/package.html b/songdbj/org/tritonus/share/sampled/package.html new file mode 100644 index 0000000000..f7a6b56099 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/package.html @@ -0,0 +1,10 @@ + + + + + + +

Helper classes for the implementation of sampled audio stuff. + The classes provided here .

+ + -- cgit v1.2.3