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 --- songdbj/org/tritonus/file/AiffAudioFileReader.java | 244 ++++ songdbj/org/tritonus/file/AiffAudioFileWriter.java | 104 ++ .../org/tritonus/file/AiffAudioOutputStream.java | 205 ++++ songdbj/org/tritonus/file/AiffTool.java | 82 ++ songdbj/org/tritonus/file/AuAudioFileReader.java | 185 +++ songdbj/org/tritonus/file/AuAudioFileWriter.java | 104 ++ songdbj/org/tritonus/file/AuAudioOutputStream.java | 121 ++ songdbj/org/tritonus/file/AuTool.java | 95 ++ songdbj/org/tritonus/file/WaveAudioFileReader.java | 300 +++++ songdbj/org/tritonus/file/WaveAudioFileWriter.java | 103 ++ .../org/tritonus/file/WaveAudioOutputStream.java | 201 ++++ songdbj/org/tritonus/file/WaveTool.java | 115 ++ .../org/tritonus/file/gsm/GSMAudioFileReader.java | 142 +++ .../org/tritonus/file/gsm/GSMAudioFileWriter.java | 77 ++ songdbj/org/tritonus/file/gsm/package.html | 10 + .../file/jorbis/JorbisAudioFileReader.java | 231 ++++ songdbj/org/tritonus/file/jorbis/package.html | 10 + .../tritonus/file/mpeg/MpegAudioFileWriter.java | 75 ++ songdbj/org/tritonus/file/mpeg/package.html | 10 + songdbj/org/tritonus/file/package.html | 10 + .../file/pvorbis/VorbisAudioFileReader.java | 302 +++++ .../file/pvorbis/VorbisAudioFileWriter.java | 75 ++ songdbj/org/tritonus/file/pvorbis/package.html | 12 + .../file/vorbis/VorbisAudioFileReader.java | 302 +++++ .../file/vorbis/VorbisAudioFileWriter.java | 75 ++ songdbj/org/tritonus/file/vorbis/package.html | 12 + songdbj/org/tritonus/lowlevel/ogg/Buffer.java | 173 +++ songdbj/org/tritonus/lowlevel/ogg/Ogg.java | 104 ++ songdbj/org/tritonus/lowlevel/ogg/Packet.java | 113 ++ songdbj/org/tritonus/lowlevel/ogg/Page.java | 131 +++ songdbj/org/tritonus/lowlevel/ogg/StreamState.java | 143 +++ songdbj/org/tritonus/lowlevel/ogg/SyncState.java | 127 ++ songdbj/org/tritonus/lowlevel/ogg/package.html | 12 + songdbj/org/tritonus/lowlevel/pogg/Buffer.java | 284 +++++ songdbj/org/tritonus/lowlevel/pogg/Ogg.java | 104 ++ songdbj/org/tritonus/lowlevel/pogg/Packet.java | 133 +++ songdbj/org/tritonus/lowlevel/pogg/Page.java | 298 +++++ .../org/tritonus/lowlevel/pogg/StreamState.java | 703 +++++++++++ songdbj/org/tritonus/lowlevel/pogg/SyncState.java | 339 ++++++ songdbj/org/tritonus/lowlevel/pogg/package.html | 12 + songdbj/org/tritonus/share/ArraySet.java | 87 ++ songdbj/org/tritonus/share/GlobalInfo.java | 60 + songdbj/org/tritonus/share/StringHashedSet.java | 112 ++ songdbj/org/tritonus/share/TCircularBuffer.java | 268 +++++ songdbj/org/tritonus/share/TDebug.java | 192 +++ songdbj/org/tritonus/share/TNotifier.java | 140 +++ songdbj/org/tritonus/share/TSettings.java | 75 ++ songdbj/org/tritonus/share/package.html | 10 + .../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 + 96 files changed, 16917 insertions(+) create mode 100644 songdbj/org/tritonus/file/AiffAudioFileReader.java create mode 100644 songdbj/org/tritonus/file/AiffAudioFileWriter.java create mode 100644 songdbj/org/tritonus/file/AiffAudioOutputStream.java create mode 100644 songdbj/org/tritonus/file/AiffTool.java create mode 100644 songdbj/org/tritonus/file/AuAudioFileReader.java create mode 100644 songdbj/org/tritonus/file/AuAudioFileWriter.java create mode 100644 songdbj/org/tritonus/file/AuAudioOutputStream.java create mode 100644 songdbj/org/tritonus/file/AuTool.java create mode 100644 songdbj/org/tritonus/file/WaveAudioFileReader.java create mode 100644 songdbj/org/tritonus/file/WaveAudioFileWriter.java create mode 100644 songdbj/org/tritonus/file/WaveAudioOutputStream.java create mode 100644 songdbj/org/tritonus/file/WaveTool.java create mode 100644 songdbj/org/tritonus/file/gsm/GSMAudioFileReader.java create mode 100644 songdbj/org/tritonus/file/gsm/GSMAudioFileWriter.java create mode 100644 songdbj/org/tritonus/file/gsm/package.html create mode 100644 songdbj/org/tritonus/file/jorbis/JorbisAudioFileReader.java create mode 100644 songdbj/org/tritonus/file/jorbis/package.html create mode 100644 songdbj/org/tritonus/file/mpeg/MpegAudioFileWriter.java create mode 100644 songdbj/org/tritonus/file/mpeg/package.html create mode 100644 songdbj/org/tritonus/file/package.html create mode 100644 songdbj/org/tritonus/file/pvorbis/VorbisAudioFileReader.java create mode 100644 songdbj/org/tritonus/file/pvorbis/VorbisAudioFileWriter.java create mode 100644 songdbj/org/tritonus/file/pvorbis/package.html create mode 100644 songdbj/org/tritonus/file/vorbis/VorbisAudioFileReader.java create mode 100644 songdbj/org/tritonus/file/vorbis/VorbisAudioFileWriter.java create mode 100644 songdbj/org/tritonus/file/vorbis/package.html create mode 100644 songdbj/org/tritonus/lowlevel/ogg/Buffer.java create mode 100644 songdbj/org/tritonus/lowlevel/ogg/Ogg.java create mode 100644 songdbj/org/tritonus/lowlevel/ogg/Packet.java create mode 100644 songdbj/org/tritonus/lowlevel/ogg/Page.java create mode 100644 songdbj/org/tritonus/lowlevel/ogg/StreamState.java create mode 100644 songdbj/org/tritonus/lowlevel/ogg/SyncState.java create mode 100644 songdbj/org/tritonus/lowlevel/ogg/package.html create mode 100644 songdbj/org/tritonus/lowlevel/pogg/Buffer.java create mode 100644 songdbj/org/tritonus/lowlevel/pogg/Ogg.java create mode 100644 songdbj/org/tritonus/lowlevel/pogg/Packet.java create mode 100644 songdbj/org/tritonus/lowlevel/pogg/Page.java create mode 100644 songdbj/org/tritonus/lowlevel/pogg/StreamState.java create mode 100644 songdbj/org/tritonus/lowlevel/pogg/SyncState.java create mode 100644 songdbj/org/tritonus/lowlevel/pogg/package.html create mode 100644 songdbj/org/tritonus/share/ArraySet.java create mode 100644 songdbj/org/tritonus/share/GlobalInfo.java create mode 100644 songdbj/org/tritonus/share/StringHashedSet.java create mode 100644 songdbj/org/tritonus/share/TCircularBuffer.java create mode 100644 songdbj/org/tritonus/share/TDebug.java create mode 100644 songdbj/org/tritonus/share/TNotifier.java create mode 100644 songdbj/org/tritonus/share/TSettings.java create mode 100644 songdbj/org/tritonus/share/package.html 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') diff --git a/songdbj/org/tritonus/file/AiffAudioFileReader.java b/songdbj/org/tritonus/file/AiffAudioFileReader.java new file mode 100644 index 0000000000..139ba05425 --- /dev/null +++ b/songdbj/org/tritonus/file/AiffAudioFileReader.java @@ -0,0 +1,244 @@ +/* + * AiffAudioFileReader.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 by Florian Bomers + * 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.sampled.file; + +import java.io.DataInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + +import org.tritonus.share.sampled.file.TAudioFileFormat; +import org.tritonus.share.sampled.file.TAudioFileReader; +import org.tritonus.share.TDebug; + + +/** Class for reading AIFF and AIFF-C files. + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ +public class AiffAudioFileReader extends TAudioFileReader +{ + private static final int READ_LIMIT = 1000; + + + + public AiffAudioFileReader() + { + super(READ_LIMIT); + } + + + + private void skipChunk(DataInputStream dataInputStream, int chunkLength, int chunkRead) + throws IOException { + chunkLength-=chunkRead; + if (chunkLength>0) { + dataInputStream.skip(chunkLength + (chunkLength % 2)); + } + } + + private AudioFormat readCommChunk(DataInputStream dataInputStream, int chunkLength) + throws IOException, UnsupportedAudioFileException { + + int nNumChannels = dataInputStream.readShort(); + if (nNumChannels <= 0) { + throw new UnsupportedAudioFileException( + "not an AIFF file: number of channels must be positive"); + } + if (TDebug.TraceAudioFileReader) { + TDebug.out("Found "+nNumChannels+" channels."); + } + // ignored: frame count + dataInputStream.readInt(); + int nSampleSize = dataInputStream.readShort(); + float fSampleRate = (float) readIeeeExtended(dataInputStream); + if (fSampleRate <= 0.0) { + throw new UnsupportedAudioFileException( + "not an AIFF file: sample rate must be positive"); + } + if (TDebug.TraceAudioFileReader) { + TDebug.out("Found framerate "+fSampleRate); + } + AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED; + int nRead=18; + if (chunkLength>nRead) { + int nEncoding=dataInputStream.readInt(); + nRead+=4; + if (nEncoding==AiffTool.AIFF_COMM_PCM) { + // PCM - nothing to do + } + else if (nEncoding==AiffTool.AIFF_COMM_ULAW) { + // ULAW + encoding=AudioFormat.Encoding.ULAW; + nSampleSize=8; + } + else if (nEncoding==AiffTool.AIFF_COMM_IMA_ADPCM) { + encoding = new AudioFormat.Encoding("IMA_ADPCM"); + nSampleSize=4; + } + else { + throw new UnsupportedAudioFileException( + "Encoding 0x"+Integer.toHexString(nEncoding) + +" of AIFF file not supported"); + } + } + /* In case of IMA ADPCM, frame size is 0.5 bytes (since it is + always mono). A value of 1 as frame size would be wrong. + Handling of frame size 0 in defined nowhere. So the best + solution is to set the frame size to unspecified (-1). + */ + int nFrameSize = (nSampleSize == 4) ? + AudioSystem.NOT_SPECIFIED : + calculateFrameSize(nSampleSize, nNumChannels); + if (TDebug.TraceAudioFileReader) { TDebug.out("calculated frame size: " + nFrameSize); } + skipChunk(dataInputStream, chunkLength, nRead); + AudioFormat format = new AudioFormat(encoding, + fSampleRate, + nSampleSize, + nNumChannels, + nFrameSize, + fSampleRate, + true); + return format; + } + + private void readVerChunk(DataInputStream dataInputStream, int chunkLength) + throws IOException, UnsupportedAudioFileException { + if (chunkLength<4) { + throw new UnsupportedAudioFileException( + "Corrput AIFF file: FVER chunk too small."); + } + int nVer=dataInputStream.readInt(); + if (nVer!=AiffTool.AIFF_FVER_TIME_STAMP) { + throw new UnsupportedAudioFileException( + "Unsupported AIFF file: version not known."); + } + skipChunk(dataInputStream, chunkLength, 4); + } + + + + protected AudioFileFormat getAudioFileFormat(InputStream inputStream, long lFileSizeInBytes) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) {TDebug.out("AiffAudioFileReader.getAudioFileFormat(InputStream, long): begin"); } + DataInputStream dataInputStream = new DataInputStream(inputStream); + int nMagic = dataInputStream.readInt(); + if (nMagic != AiffTool.AIFF_FORM_MAGIC) { + throw new UnsupportedAudioFileException( + "not an AIFF file: header magic is not FORM"); + } + int nTotalLength = dataInputStream.readInt(); + nMagic = dataInputStream.readInt(); + boolean bIsAifc; + if (nMagic == AiffTool.AIFF_AIFF_MAGIC) { + bIsAifc = false; + } else if (nMagic == AiffTool.AIFF_AIFC_MAGIC) { + bIsAifc = true; + } else { + throw new UnsupportedAudioFileException( + "unsupported IFF file: header magic neither AIFF nor AIFC"); + } + boolean bFVerFound=!bIsAifc; + boolean bCommFound=false; + boolean bSSndFound=false; + AudioFormat format=null; + int nDataChunkLength=0; + + // walk through the chunks + // chunks may be in any order. However, in this implementation, SSND must be last + while (!bFVerFound || !bCommFound || !bSSndFound) { + nMagic = dataInputStream.readInt(); + int nChunkLength = dataInputStream.readInt(); + switch (nMagic) { + case AiffTool.AIFF_COMM_MAGIC: + format=readCommChunk(dataInputStream, nChunkLength); + if (TDebug.TraceAudioFileReader) { + TDebug.out("Read COMM chunk with length "+nChunkLength); + } + bCommFound=true; + break; + case AiffTool.AIFF_FVER_MAGIC: + if (!bFVerFound) { + readVerChunk(dataInputStream, nChunkLength); + if (TDebug.TraceAudioFileReader) { + TDebug.out("Read FVER chunk with length "+nChunkLength); + } + bFVerFound=true; + } else { + skipChunk(dataInputStream, nChunkLength, 0); + } + break; + case AiffTool.AIFF_SSND_MAGIC: + if (!bCommFound || !bFVerFound) { + throw new UnsupportedAudioFileException( + "cannot handle AIFF file: SSND not last chunk"); + } + bSSndFound=true; + nDataChunkLength=nChunkLength-8; + // 8 information bytes of no interest + dataInputStream.skip(8); + if (TDebug.TraceAudioFileReader) { + TDebug.out("Found SSND chunk with length "+nChunkLength); + } + break; + default: + if (TDebug.TraceAudioFileReader) { + TDebug.out("Skipping unknown chunk: " + +Integer.toHexString(nMagic)); + } + skipChunk(dataInputStream, nChunkLength, 0); + break; + } + } + + // TODO: length argument has to be in frames + AudioFileFormat audioFileFormat = new TAudioFileFormat( + bIsAifc ? AudioFileFormat.Type.AIFC : AudioFileFormat.Type.AIFF, + format, + nDataChunkLength / format.getFrameSize(), + nTotalLength + 8); + if (TDebug.TraceAudioFileReader) {TDebug.out("AiffAudioFileReader.getAudioFileFormat(InputStream, long): end"); } + return audioFileFormat; + } +} + + + +/*** AiffAudioFileReader.java ***/ diff --git a/songdbj/org/tritonus/file/AiffAudioFileWriter.java b/songdbj/org/tritonus/file/AiffAudioFileWriter.java new file mode 100644 index 0000000000..cfd6996ae9 --- /dev/null +++ b/songdbj/org/tritonus/file/AiffAudioFileWriter.java @@ -0,0 +1,104 @@ +/* + * AiffAudioFileWriter.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.sampled.file; + +import java.io.IOException; +import java.util.Arrays; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.AudioOutputStream; +import org.tritonus.share.sampled.file.TAudioFileWriter; +import org.tritonus.share.sampled.file.TDataOutputStream; + + +/** + * Class for writing AIFF and AIFF-C files. + * + * @author Florian Bomers + */ + +public class AiffAudioFileWriter extends TAudioFileWriter { + + private static final AudioFileFormat.Type[] FILE_TYPES = + { + AudioFileFormat.Type.AIFF, + AudioFileFormat.Type.AIFC + }; + + private static final int ALL=AudioSystem.NOT_SPECIFIED; + private static final AudioFormat.Encoding PCM_SIGNED = AudioFormat.Encoding.PCM_SIGNED; + private static final AudioFormat.Encoding ULAW = AudioFormat.Encoding.ULAW; + private static final AudioFormat.Encoding IMA_ADPCM = new AudioFormat.Encoding("IMA_ADPCM"); + + // IMPORTANT: this array depends on the AudioFormat.match() algorithm which takes + // AudioSystem.NOT_SPECIFIED into account ! + private static final AudioFormat[] AUDIO_FORMATS = + { + new AudioFormat(PCM_SIGNED, ALL, 8, ALL, ALL, ALL, true), + new AudioFormat(PCM_SIGNED, ALL, 8, ALL, ALL, ALL, false), + new AudioFormat(ULAW, ALL, 8, ALL, ALL, ALL, false), + new AudioFormat(ULAW, ALL, 8, ALL, ALL, ALL, true), + new AudioFormat(PCM_SIGNED, ALL, 16, ALL, ALL, ALL, true), + new AudioFormat(PCM_SIGNED, ALL, 24, ALL, ALL, ALL, true), + new AudioFormat(PCM_SIGNED, ALL, 32, ALL, ALL, ALL, true), + new AudioFormat(IMA_ADPCM, ALL, 4, ALL, ALL, ALL, true), + new AudioFormat(IMA_ADPCM, ALL, 4, ALL, ALL, ALL, false), + }; + + public AiffAudioFileWriter() { + super(Arrays.asList(FILE_TYPES), + Arrays.asList(AUDIO_FORMATS)); + } + + + protected boolean isAudioFormatSupportedImpl(AudioFormat format, + AudioFileFormat.Type fileType) { + return AiffTool.getFormatCode(format)!=AiffTool.AIFF_COMM_UNSPECIFIED; + } + + + protected AudioOutputStream getAudioOutputStream(AudioFormat audioFormat, + long lLengthInBytes, + AudioFileFormat.Type fileType, + TDataOutputStream dataOutputStream) throws IOException { + return new AiffAudioOutputStream(audioFormat, fileType, + lLengthInBytes, + dataOutputStream); + } + +} + +/*** AiffAudioFileWriter.java ***/ diff --git a/songdbj/org/tritonus/file/AiffAudioOutputStream.java b/songdbj/org/tritonus/file/AiffAudioOutputStream.java new file mode 100644 index 0000000000..d0006ebfe0 --- /dev/null +++ b/songdbj/org/tritonus/file/AiffAudioOutputStream.java @@ -0,0 +1,205 @@ +/* + * AiffAudioOutputStream.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.sampled.file; + +import java.io.IOException; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioSystem; +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioOutputStream; +import org.tritonus.share.sampled.file.TDataOutputStream; + + +/** + * AudioOutputStream for AIFF and AIFF-C files. + * + * @author Florian Bomers + */ +public class AiffAudioOutputStream extends TAudioOutputStream { + + // this constant is used for chunk lengths when the length is not known yet + private static final int LENGTH_NOT_KNOWN=-1; + + private AudioFileFormat.Type m_FileType; + + public AiffAudioOutputStream(AudioFormat audioFormat, + AudioFileFormat.Type fileType, + long lLength, + TDataOutputStream dataOutputStream) { + super(audioFormat, + lLength, + dataOutputStream, + lLength == AudioSystem.NOT_SPECIFIED + && dataOutputStream.supportsSeek()); + // AIFF files cannot exceed 2GB + if (lLength != AudioSystem.NOT_SPECIFIED && lLength>0x7FFFFFFFl) { + throw new IllegalArgumentException( + "AIFF files cannot be larger than 2GB."); + } + // IDEA: write AIFF file instead of AIFC when encoding=PCM ? + m_FileType=fileType; + if (!audioFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED) + && !audioFormat.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) { + // only AIFC files can handle non-pcm data + m_FileType=AudioFileFormat.Type.AIFC; + } + } + + protected void writeHeader() + throws IOException { + if (TDebug.TraceAudioOutputStream) { + TDebug.out("AiffAudioOutputStream.writeHeader(): called."); + } + AudioFormat format = getFormat(); + boolean bIsAifc = m_FileType.equals(AudioFileFormat.Type.AIFC); + long lLength = getLength(); + TDataOutputStream dos = getDataOutputStream(); + int nCommChunkSize=18; + int nFormatCode=AiffTool.getFormatCode(format); + if (bIsAifc) { + // encoding takes 4 bytes + // encoding name takes at minimum 2 bytes + nCommChunkSize+=6; + } + int nHeaderSize=4 // magic + +8+nCommChunkSize // COMM chunk + +8; // header of SSND chunk + if (bIsAifc) { + // add length for FVER chunk + nHeaderSize+=12; + } + // if patching the header, and the length has not been known at first + // writing of the header, just truncate the size fields, don't throw an exception + if (lLength != AudioSystem.NOT_SPECIFIED && lLength+nHeaderSize>0x7FFFFFFFl) { + lLength=0x7FFFFFFFl-nHeaderSize; + } + // chunks must be on word-boundaries + long lSSndChunkSize=(lLength!=AudioSystem.NOT_SPECIFIED)? + (lLength+(lLength%2)+8):AudioSystem.NOT_SPECIFIED; + + // write IFF container chunk + dos.writeInt(AiffTool.AIFF_FORM_MAGIC); + dos.writeInt((lLength!=AudioSystem.NOT_SPECIFIED)? + ((int) (lSSndChunkSize+nHeaderSize)):LENGTH_NOT_KNOWN); + if (bIsAifc) { + dos.writeInt(AiffTool.AIFF_AIFC_MAGIC); + // write FVER chunk + dos.writeInt(AiffTool.AIFF_FVER_MAGIC); + dos.writeInt(4); + dos.writeInt(AiffTool.AIFF_FVER_TIME_STAMP); + } else { + dos.writeInt(AiffTool.AIFF_AIFF_MAGIC); + } + + // write COMM chunk + dos.writeInt(AiffTool.AIFF_COMM_MAGIC); + dos.writeInt(nCommChunkSize); + dos.writeShort((short) format.getChannels()); + dos.writeInt((lLength!=AudioSystem.NOT_SPECIFIED)? + ((int) (lLength / format.getFrameSize())):LENGTH_NOT_KNOWN); + if (nFormatCode==AiffTool.AIFF_COMM_ULAW) { + // AIFF ulaw states 16 bits for ulaw data + dos.writeShort(16); + } else { + dos.writeShort((short) format.getSampleSizeInBits()); + } + writeIeeeExtended(dos, format.getSampleRate()); + if (bIsAifc) { + dos.writeInt(nFormatCode); + dos.writeShort(0); // no encoding name + // TODO: write encoding.toString() ?? + } + + // write header of SSND chunk + + + + dos.writeInt(AiffTool.AIFF_SSND_MAGIC); + // don't use lSSndChunkSize here ! + dos.writeInt((lLength!=AudioSystem.NOT_SPECIFIED) + ?((int) (lLength+8)):LENGTH_NOT_KNOWN); + // 8 information bytes of no interest + dos.writeInt(0); // offset + dos.writeInt(0); // blocksize + } + + + + + protected void patchHeader() + throws IOException { + TDataOutputStream tdos = getDataOutputStream(); + tdos.seek(0); + setLengthFromCalculatedLength(); + writeHeader(); + } + + public void close() throws IOException { + long nBytesWritten=getCalculatedLength(); + + if ((nBytesWritten % 2)==1) { + if (TDebug.TraceAudioOutputStream) { + TDebug.out("AiffOutputStream.close(): adding padding byte"); + } + // extra byte for to align on word boundaries + TDataOutputStream tdos = getDataOutputStream(); + tdos.writeByte(0); + // DON'T adjust calculated length ! + } + + + + super.close(); + } + + public void writeIeeeExtended(TDataOutputStream dos, float sampleRate) throws IOException { + // currently, only integer sample rates are written + // TODO: real conversion + // I don't know exactly how much I have to shift left the mantisse for normalisation + // now I do it so that there are any bits set in the first 5 bits + int nSampleRate=(int) sampleRate; + short ieeeExponent=0; + while ((nSampleRate!=0) && (nSampleRate & 0x80000000)==0) { + ieeeExponent++; + nSampleRate<<=1; + } + dos.writeShort(16414-ieeeExponent); // exponent + dos.writeInt(nSampleRate); // mantisse high double word + dos.writeInt(0); // mantisse low double word + } + + + + +} + +/*** AiffAudioOutputStream.java ***/ diff --git a/songdbj/org/tritonus/file/AiffTool.java b/songdbj/org/tritonus/file/AiffTool.java new file mode 100644 index 0000000000..39cdbf0878 --- /dev/null +++ b/songdbj/org/tritonus/file/AiffTool.java @@ -0,0 +1,82 @@ +/* + * AiffTool.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.sampled.file; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioSystem; + + +/** + * Common constants and methods for handling aiff and aiff-c files. + * + * @author Florian Bomers + */ + +public class AiffTool { + + public static final int AIFF_FORM_MAGIC = 0x464F524D; + public static final int AIFF_AIFF_MAGIC = 0x41494646; + public static final int AIFF_AIFC_MAGIC = 0x41494643; + public static final int AIFF_COMM_MAGIC = 0x434F4D4D; + public static final int AIFF_SSND_MAGIC = 0x53534E44; + public static final int AIFF_FVER_MAGIC = 0x46564552; + public static final int AIFF_COMM_UNSPECIFIED = 0x00000000; // "0000" + public static final int AIFF_COMM_PCM = 0x4E4F4E45; // "NONE" + public static final int AIFF_COMM_ULAW = 0x756C6177; // "ulaw" + public static final int AIFF_COMM_IMA_ADPCM = 0x696D6134; // "ima4" + public static final int AIFF_FVER_TIME_STAMP = 0xA2805140; // May 23, 1990, 2:40pm + + public static int getFormatCode(AudioFormat format) { + AudioFormat.Encoding encoding = format.getEncoding(); + int nSampleSize = format.getSampleSizeInBits(); + boolean bigEndian = format.isBigEndian(); + // $$fb 2000-08-16: check the frame size, too. + boolean frameSizeOK=format.getFrameSize()==AudioSystem.NOT_SPECIFIED + || format.getChannels()!=AudioSystem.NOT_SPECIFIED + || format.getFrameSize()==nSampleSize/8*format.getChannels(); + + if ((encoding.equals(AudioFormat.Encoding.PCM_SIGNED)) + && ((bigEndian && nSampleSize>=16 && nSampleSize<=32) || (nSampleSize==8)) + && frameSizeOK) { + return AIFF_COMM_PCM; + } else if (encoding.equals(AudioFormat.Encoding.ULAW) && nSampleSize == 8 && frameSizeOK) { + return AIFF_COMM_ULAW; + } else if (encoding.equals(new AudioFormat.Encoding("IMA_ADPCM")) && nSampleSize == 4) { + return AIFF_COMM_IMA_ADPCM; + } else { + return AIFF_COMM_UNSPECIFIED; + } + } + +} + +/*** AiffTool.java ***/ diff --git a/songdbj/org/tritonus/file/AuAudioFileReader.java b/songdbj/org/tritonus/file/AuAudioFileReader.java new file mode 100644 index 0000000000..b527920118 --- /dev/null +++ b/songdbj/org/tritonus/file/AuAudioFileReader.java @@ -0,0 +1,185 @@ +/* + * AuAudioFileReader.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999,2000,2001 by Florian Bomers + * 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.sampled.file; + +import java.io.DataInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.sound.sampled.spi.AudioFileReader; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioFileFormat; +import org.tritonus.share.sampled.file.TAudioFileReader; + + +/** Class for reading Sun/Next AU files. + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ +public class AuAudioFileReader extends TAudioFileReader +{ + private static final int READ_LIMIT = 1000; + + + + public AuAudioFileReader() + { + super(READ_LIMIT); + } + + + + private static String readDescription(DataInputStream dis, int len) throws IOException { + byte c=-1; + String ret=""; + while (len>0 && (c=dis.readByte())!=0) { + ret=ret+(char) c; + len--; + } + if (len>1 && c==0) { + dis.skip(len-1); + } + return ret; + } + + + + protected AudioFileFormat getAudioFileFormat(InputStream inputStream, long lFileSizeInBytes) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) {TDebug.out("AuAudioFileReader.getAudioFileFormat(InputStream, long): begin"); } + DataInputStream dataInputStream = new DataInputStream(inputStream); + int nMagic = dataInputStream.readInt(); + if (nMagic != AuTool.AU_HEADER_MAGIC) { + throw new UnsupportedAudioFileException( + "not an AU file: wrong header magic"); + } + int nDataOffset = dataInputStream.readInt(); + if (TDebug.TraceAudioFileReader) { + TDebug.out("AuAudioFileReader.getAudioFileFormat(): data offset: " + nDataOffset); + } + if (nDataOffset < AuTool.DATA_OFFSET) { + throw new UnsupportedAudioFileException( + "not an AU file: data offset must be 24 or greater"); + } + int nDataLength = dataInputStream.readInt(); + if (TDebug.TraceAudioFileReader) { + TDebug.out("AuAudioFileReader.getAudioFileFormat(): data length: " + nDataLength); + } + if (nDataLength < 0 && nDataLength!=AuTool.AUDIO_UNKNOWN_SIZE) { + throw new UnsupportedAudioFileException( + "not an AU file: data length must be positive, 0 or -1 for unknown"); + } + AudioFormat.Encoding encoding = null; + int nSampleSize = 0; + int nEncoding = dataInputStream.readInt(); + switch (nEncoding) { + case AuTool.SND_FORMAT_MULAW_8: // 8-bit uLaw G.711 + encoding = AudioFormat.Encoding.ULAW; + nSampleSize = 8; + break; + + case AuTool.SND_FORMAT_LINEAR_8: + encoding = AudioFormat.Encoding.PCM_SIGNED; + nSampleSize = 8; + break; + + case AuTool.SND_FORMAT_LINEAR_16: + encoding = AudioFormat.Encoding.PCM_SIGNED; + nSampleSize = 16; + break; + + case AuTool.SND_FORMAT_LINEAR_24: + encoding = AudioFormat.Encoding.PCM_SIGNED; + nSampleSize = 24; + break; + + case AuTool.SND_FORMAT_LINEAR_32: + encoding = AudioFormat.Encoding.PCM_SIGNED; + nSampleSize = 32; + break; + + case AuTool.SND_FORMAT_ALAW_8: // 8-bit aLaw G.711 + encoding = AudioFormat.Encoding.ALAW; + nSampleSize = 8; + break; + } + if (nSampleSize == 0) { + throw new UnsupportedAudioFileException( + "unsupported AU file: unknown encoding " + nEncoding); + } + int nSampleRate = dataInputStream.readInt(); + if (nSampleRate <= 0) { + throw new UnsupportedAudioFileException( + "corrupt AU file: sample rate must be positive"); + } + int nNumChannels = dataInputStream.readInt(); + if (nNumChannels <= 0) { + throw new UnsupportedAudioFileException( + "corrupt AU file: number of channels must be positive"); + } + // skip header information field + inputStream.skip(nDataOffset - AuTool.DATA_OFFSET); + // read header info field + //String desc=readDescription(dataInputStream, nDataOffset - AuTool.DATA_OFFSET); + + AudioFormat format = new AudioFormat(encoding, + (float) nSampleRate, + nSampleSize, + nNumChannels, + calculateFrameSize(nSampleSize, nNumChannels), + (float) nSampleRate, + true); + AudioFileFormat audioFileFormat = new TAudioFileFormat( + AudioFileFormat.Type.AU, + format, + (nDataLength==AuTool.AUDIO_UNKNOWN_SIZE)? + AudioSystem.NOT_SPECIFIED:(nDataLength / format.getFrameSize()), + (nDataLength==AuTool.AUDIO_UNKNOWN_SIZE)? + AudioSystem.NOT_SPECIFIED:(nDataLength + nDataOffset)); + if (TDebug.TraceAudioFileReader) { TDebug.out("AuAudioFileReader.getAudioFileFormat(InputStream, long): begin"); } + return audioFileFormat; + } +} + + + +/*** AuAudioFileReader.java ***/ + diff --git a/songdbj/org/tritonus/file/AuAudioFileWriter.java b/songdbj/org/tritonus/file/AuAudioFileWriter.java new file mode 100644 index 0000000000..5ac80a8c8a --- /dev/null +++ b/songdbj/org/tritonus/file/AuAudioFileWriter.java @@ -0,0 +1,104 @@ +/* + * AuAudioFileWriter.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999,2000,2001 by Florian Bomers + * 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.sampled.file; + +import java.io.IOException; +import java.util.Arrays; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.AudioOutputStream; +import org.tritonus.share.sampled.file.TAudioFileWriter; +import org.tritonus.share.sampled.file.TDataOutputStream; + + +/** + * AudioFileWriter for Sun/Next AU files. + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ +public class AuAudioFileWriter extends TAudioFileWriter { + + private static final AudioFileFormat.Type[] FILE_TYPES = + { + AudioFileFormat.Type.AU + }; + + private static final int ALL=AudioSystem.NOT_SPECIFIED; + + // IMPORTANT: this array depends on the AudioFormat.match() algorithm which takes + // AudioSystem.NOT_SPECIFIED into account ! + private static final AudioFormat[] AUDIO_FORMATS = + { + new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, ALL, 8, ALL, ALL, ALL, true), + new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, ALL, 8, ALL, ALL, ALL, false), + + new AudioFormat(AudioFormat.Encoding.ULAW, ALL, 8, ALL, ALL, ALL, false), + new AudioFormat(AudioFormat.Encoding.ULAW, ALL, 8, ALL, ALL, ALL, true), + + new AudioFormat(AudioFormat.Encoding.ALAW, ALL, 8, ALL, ALL, ALL, false), + new AudioFormat(AudioFormat.Encoding.ALAW, ALL, 8, ALL, ALL, ALL, true), + + new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, ALL, 16, ALL, ALL, ALL, true), + + new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, ALL, 24, ALL, ALL, ALL, true), + + new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, ALL, 32, ALL, ALL, ALL, true), + }; + + public AuAudioFileWriter() { + super(Arrays.asList(FILE_TYPES), + Arrays.asList(AUDIO_FORMATS)); + } + + + protected boolean isAudioFormatSupportedImpl(AudioFormat format, + AudioFileFormat.Type fileType) { + return AuTool.getFormatCode(format)!=AuTool.SND_FORMAT_UNSPECIFIED; + } + protected AudioOutputStream getAudioOutputStream(AudioFormat audioFormat, + long lLengthInBytes, + AudioFileFormat.Type fileType, + TDataOutputStream dataOutputStream) throws IOException { + return new AuAudioOutputStream(audioFormat, + lLengthInBytes, + dataOutputStream); + } + +} + +/*** AuAudioFileWriter.java ***/ diff --git a/songdbj/org/tritonus/file/AuAudioOutputStream.java b/songdbj/org/tritonus/file/AuAudioOutputStream.java new file mode 100644 index 0000000000..9f33e7a2aa --- /dev/null +++ b/songdbj/org/tritonus/file/AuAudioOutputStream.java @@ -0,0 +1,121 @@ +/* + * AuAudioOutputStream.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000,2001 by Florian Bomers + * 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.sampled.file; + +import java.io.IOException; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioSystem; +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioOutputStream; +import org.tritonus.share.sampled.file.TDataOutputStream; + + + +/** + * AudioOutputStream for AU files. + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ + +public class AuAudioOutputStream extends TAudioOutputStream { + + private static String description="Created by Tritonus"; + + /** + * Writes a null-terminated ascii string s to f. + * The total number of bytes written is aligned on a 2byte boundary. + * @exception IOException Write error. + */ + protected static void writeText(TDataOutputStream dos, String s) throws IOException { + if (s.length()>0) { + dos.writeBytes(s); + dos.writeByte(0); // pour terminer le texte + if ((s.length() % 2)==0) { + // ajout d'un zero pour faire la longeur pair + dos.writeByte(0); + } + } + } + + /** + * Returns number of bytes that have to written for string s (with alignment) + */ + protected static int getTextLength(String s) { + if (s.length()==0) { + return 0; + } else { + return (s.length()+2) & 0xFFFFFFFE; + } + } + + public AuAudioOutputStream(AudioFormat audioFormat, + long lLength, + TDataOutputStream dataOutputStream) { + // if length exceeds 2GB, set the length field to NOT_SPECIFIED + super(audioFormat, + lLength>0x7FFFFFFFl?AudioSystem.NOT_SPECIFIED:lLength, + dataOutputStream, + lLength == AudioSystem.NOT_SPECIFIED && dataOutputStream.supportsSeek()); + } + + protected void writeHeader() throws IOException { + if (TDebug.TraceAudioOutputStream) { + TDebug.out("AuAudioOutputStream.writeHeader(): called."); + } + AudioFormat format = getFormat(); + long lLength = getLength(); + TDataOutputStream dos = getDataOutputStream(); + if (TDebug.TraceAudioOutputStream) { + TDebug.out("AuAudioOutputStream.writeHeader(): AudioFormat: " + format); + TDebug.out("AuAudioOutputStream.writeHeader(): length: " + lLength); + } + + dos.writeInt(AuTool.AU_HEADER_MAGIC); + dos.writeInt(AuTool.DATA_OFFSET+getTextLength(description)); + dos.writeInt((lLength!=AudioSystem.NOT_SPECIFIED)?((int) lLength):AuTool.AUDIO_UNKNOWN_SIZE); + dos.writeInt(AuTool.getFormatCode(format)); + dos.writeInt((int) format.getSampleRate()); + dos.writeInt(format.getChannels()); + writeText(dos, description); + } + + protected void patchHeader() throws IOException { + TDataOutputStream tdos = getDataOutputStream(); + tdos.seek(0); + setLengthFromCalculatedLength(); + writeHeader(); + } +} + +/*** AuAudioOutputStream.java ***/ diff --git a/songdbj/org/tritonus/file/AuTool.java b/songdbj/org/tritonus/file/AuTool.java new file mode 100644 index 0000000000..bcdc62f86c --- /dev/null +++ b/songdbj/org/tritonus/file/AuTool.java @@ -0,0 +1,95 @@ +/* + * AuTool.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000,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.sampled.file; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioSystem; + + +/** Common constants and methods for handling au files. + * + * @author Florian Bomers + */ + +public class AuTool { + + public static final int AU_HEADER_MAGIC = 0x2e736e64; + public static final int AUDIO_UNKNOWN_SIZE = -1; + + // length of header in bytes + public static final int DATA_OFFSET = 24; + + public static final int SND_FORMAT_UNSPECIFIED = 0; + public static final int SND_FORMAT_MULAW_8 = 1; + public static final int SND_FORMAT_LINEAR_8 = 2; + public static final int SND_FORMAT_LINEAR_16 = 3; + public static final int SND_FORMAT_LINEAR_24 = 4; + public static final int SND_FORMAT_LINEAR_32 = 5; + public static final int SND_FORMAT_FLOAT = 6; + public static final int SND_FORMAT_DOUBLE = 7; + public static final int SND_FORMAT_ADPCM_G721 = 23; + public static final int SND_FORMAT_ADPCM_G722 = 24; + public static final int SND_FORMAT_ADPCM_G723_3 = 25; + public static final int SND_FORMAT_ADPCM_G723_5 = 26; + public static final int SND_FORMAT_ALAW_8 = 27; + + public static int getFormatCode(AudioFormat format) { + AudioFormat.Encoding encoding = format.getEncoding(); + int nSampleSize = format.getSampleSizeInBits(); + // must be big endian for >8 bit formats + boolean bigEndian = format.isBigEndian(); + // $$fb 2000-08-16: check the frame size, too. + boolean frameSizeOK=( + format.getFrameSize()==AudioSystem.NOT_SPECIFIED + || format.getChannels()!=AudioSystem.NOT_SPECIFIED + || format.getFrameSize()==nSampleSize/8*format.getChannels()); + + if (encoding.equals(AudioFormat.Encoding.ULAW) && nSampleSize == 8 && frameSizeOK) { + return SND_FORMAT_MULAW_8; + } else if (encoding.equals(AudioFormat.Encoding.PCM_SIGNED) && frameSizeOK) { + if (nSampleSize == 8) { + return SND_FORMAT_LINEAR_8; + } else if (nSampleSize == 16 && bigEndian) { + return SND_FORMAT_LINEAR_16; + } else if (nSampleSize == 24 && bigEndian) { + return SND_FORMAT_LINEAR_24; + } else if (nSampleSize == 32 && bigEndian) { + return SND_FORMAT_LINEAR_32; + } + } else if (encoding.equals(AudioFormat.Encoding.ALAW) && nSampleSize == 8 && frameSizeOK) { + return SND_FORMAT_ALAW_8; + } + return SND_FORMAT_UNSPECIFIED; + } +} + +/*** AuTool.java ***/ diff --git a/songdbj/org/tritonus/file/WaveAudioFileReader.java b/songdbj/org/tritonus/file/WaveAudioFileReader.java new file mode 100644 index 0000000000..62d3f1a9ea --- /dev/null +++ b/songdbj/org/tritonus/file/WaveAudioFileReader.java @@ -0,0 +1,300 @@ +/* + * WaveAudioFileReader.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999,2000 by Florian Bomers + * 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.sampled.file; + +import java.io.DataInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.sound.sampled.spi.AudioFileReader; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioFileFormat; +import org.tritonus.share.sampled.file.TAudioFileReader; + + +/** + * Class for reading wave files. + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ + +public class WaveAudioFileReader extends TAudioFileReader +{ + private static final int READ_LIMIT = 1000; + + + + public WaveAudioFileReader() + { + super(READ_LIMIT); + } + + + + protected void advanceChunk(DataInputStream dis, long prevLength, long prevRead) + throws IOException { + if (prevLength>0) { + dis.skip(((prevLength+1) & 0xFFFFFFFE)-prevRead); + } + } + + + protected long findChunk(DataInputStream dis, int key) + throws UnsupportedAudioFileException, IOException { + // $$fb 1999-12-18: we should take care that we don't exceed + // the mark of this stream. When we exceeded the mark and + // we notice that we don't support this wave file, + // other potential wave file readers have no chance. + int thisKey; + long chunkLength=0; + do { + advanceChunk(dis, chunkLength, 0); + try { + thisKey = dis.readInt(); + } catch (IOException e) + { + if (TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + // $$fb: when we come here, we skipped past the end of the wave file + // without finding the chunk. + // IMHO, this is not an IOException, as there are incarnations + // of WAVE files which store data in different chunks. + // maybe we can find a nice description of the "required chunk" ? + throw new UnsupportedAudioFileException( + "unsupported WAVE file: required chunk not found."); + } + chunkLength = readLittleEndianInt(dis) & 0xFFFFFFFF; // unsigned + } + while (thisKey != key); + return chunkLength; + } + + protected AudioFormat readFormatChunk(DataInputStream dis, + long chunkLength) throws UnsupportedAudioFileException, IOException { + String debugAdd=""; + + int read=WaveTool.MIN_FMT_CHUNK_LENGTH; + + if (chunkLength0xFFFFFFFFl) { + lLength=0xFFFFFFFFl-dataOffset; + } + + // chunks must be on word-boundaries + long lDataChunkSize=lLength+(lLength%2); + TDataOutputStream dos = getDataOutputStream(); + + // write RIFF container chunk + dos.writeInt(WaveTool.WAVE_RIFF_MAGIC); + dos.writeLittleEndian32((int) ((lDataChunkSize+dataOffset-WaveTool.CHUNK_HEADER_SIZE) + & 0xFFFFFFFF)); + dos.writeInt(WaveTool.WAVE_WAVE_MAGIC); + + // write fmt_ chunk + int formatChunkSize=WaveTool.FMT_CHUNK_SIZE+formatChunkAdd; + short sampleSizeInBits=(short) format.getSampleSizeInBits(); + int decodedSamplesPerBlock=1; + + if (formatCode==WaveTool.WAVE_FORMAT_GSM610) { + if (format.getFrameSize()==33) { + decodedSamplesPerBlock=320; + } else if (format.getFrameSize()==65) { + decodedSamplesPerBlock=320; + } else { + // how to retrieve this value here ? + decodedSamplesPerBlock=(int) (format.getFrameSize()*(320.0f/65.0f)); + } + sampleSizeInBits=0; // MS standard + } + + + int avgBytesPerSec=((int) format.getSampleRate())/decodedSamplesPerBlock*format.getFrameSize(); + dos.writeInt(WaveTool.WAVE_FMT_MAGIC); + dos.writeLittleEndian32(formatChunkSize); + dos.writeLittleEndian16((short) formatCode); // wFormatTag + dos.writeLittleEndian16((short) format.getChannels()); // nChannels + dos.writeLittleEndian32((int) format.getSampleRate()); // nSamplesPerSec + dos.writeLittleEndian32(avgBytesPerSec); // nAvgBytesPerSec + dos.writeLittleEndian16((short) format.getFrameSize()); // nBlockalign + dos.writeLittleEndian16(sampleSizeInBits); // wBitsPerSample + dos.writeLittleEndian16((short) formatChunkAdd); // cbSize + + if (formatCode==WaveTool.WAVE_FORMAT_GSM610) { + dos.writeLittleEndian16((short) decodedSamplesPerBlock); // wSamplesPerBlock + } + + // write fact chunk + + + if (formatCode!=WaveTool.WAVE_FORMAT_PCM) { + // write "fact" chunk: number of samples + // todo: add this as an attribute or property + // in AudioOutputStream or AudioInputStream + long samples=0; + if (lLength!=AudioSystem.NOT_SPECIFIED) { + samples=lLength/format.getFrameSize()*decodedSamplesPerBlock; + } + // saturate sample count + if (samples>0xFFFFFFFFl) { + samples=(0xFFFFFFFFl/decodedSamplesPerBlock)*decodedSamplesPerBlock; + } + dos.writeInt(WaveTool.WAVE_FACT_MAGIC); + dos.writeLittleEndian32(4); + dos.writeLittleEndian32((int) (samples & 0xFFFFFFFF)); + } + + // write header of data chunk + dos.writeInt(WaveTool.WAVE_DATA_MAGIC); + dos.writeLittleEndian32((lLength!=AudioSystem.NOT_SPECIFIED)?((int) lLength):LENGTH_NOT_KNOWN); + } + + protected void patchHeader() + throws IOException { + TDataOutputStream tdos = getDataOutputStream(); + tdos.seek(0); + setLengthFromCalculatedLength(); + writeHeader(); + } + + public void close() throws IOException { + long nBytesWritten=getCalculatedLength(); + + if ((nBytesWritten % 2)==1) { + if (TDebug.TraceAudioOutputStream) { + TDebug.out("WaveOutputStream.close(): adding padding byte"); + } + // extra byte for to align on word boundaries + TDataOutputStream tdos = getDataOutputStream(); + tdos.writeByte(0); + // DON'T adjust calculated length ! + } + + + super.close(); + } + +} + +/*** WaveAudioOutputStream.java ***/ diff --git a/songdbj/org/tritonus/file/WaveTool.java b/songdbj/org/tritonus/file/WaveTool.java new file mode 100644 index 0000000000..3487557088 --- /dev/null +++ b/songdbj/org/tritonus/file/WaveTool.java @@ -0,0 +1,115 @@ +/* + * WaveTool.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.sampled.file; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; + + +/** + * Common constants and methods for handling wave files. + * + * @author Florian Bomers + */ + +public class WaveTool { + + public static final int WAVE_RIFF_MAGIC = 0x52494646; // "RIFF" + public static final int WAVE_WAVE_MAGIC = 0x57415645; // "WAVE" + public static final int WAVE_FMT_MAGIC = 0x666D7420; // "fmt " + public static final int WAVE_DATA_MAGIC = 0x64617461; // "DATA" + public static final int WAVE_FACT_MAGIC = 0x66616374; // "fact" + + public static final short WAVE_FORMAT_UNSPECIFIED = 0; + public static final short WAVE_FORMAT_PCM = 1; + public static final short WAVE_FORMAT_MS_ADPCM = 2; + public static final short WAVE_FORMAT_ALAW = 6; + public static final short WAVE_FORMAT_ULAW = 7; + public static final short WAVE_FORMAT_IMA_ADPCM = 17; // same as DVI_ADPCM + public static final short WAVE_FORMAT_G723_ADPCM = 20; + public static final short WAVE_FORMAT_GSM610 = 49; + public static final short WAVE_FORMAT_G721_ADPCM = 64; + public static final short WAVE_FORMAT_MPEG = 80; + + public static final int MIN_FMT_CHUNK_LENGTH=14; + public static final int MIN_DATA_OFFSET=12+8+MIN_FMT_CHUNK_LENGTH+8; + public static final int MIN_FACT_CHUNK_LENGTH = 4; + + // we always write the sample size in bits and the length of extra bytes. + // There are programs (CoolEdit) that rely on the + // additional entry for sample size in bits. + public static final int FMT_CHUNK_SIZE=18; + public static final int RIFF_CONTAINER_CHUNK_SIZE=12; + public static final int CHUNK_HEADER_SIZE=8; + public static final int DATA_OFFSET=RIFF_CONTAINER_CHUNK_SIZE + +CHUNK_HEADER_SIZE+FMT_CHUNK_SIZE+CHUNK_HEADER_SIZE; + + public static AudioFormat.Encoding GSM0610 = new AudioFormat.Encoding("GSM0610"); + public static AudioFormat.Encoding IMA_ADPCM = new AudioFormat.Encoding("IMA_ADPCM"); + + public static short getFormatCode(AudioFormat format) { + AudioFormat.Encoding encoding = format.getEncoding(); + int nSampleSize = format.getSampleSizeInBits(); + boolean littleEndian = !format.isBigEndian(); + boolean frameSizeOK=format.getFrameSize()==AudioSystem.NOT_SPECIFIED + || format.getChannels()!=AudioSystem.NOT_SPECIFIED + || format.getFrameSize()==nSampleSize/8*format.getChannels(); + + if (nSampleSize==8 && frameSizeOK + && (encoding.equals(AudioFormat.Encoding.PCM_SIGNED) + || encoding.equals(AudioFormat.Encoding.PCM_UNSIGNED))) { + return WAVE_FORMAT_PCM; + } else if (nSampleSize>8 && frameSizeOK && littleEndian + && encoding.equals(AudioFormat.Encoding.PCM_SIGNED)) { + return WAVE_FORMAT_PCM; + } else if (encoding.equals(AudioFormat.Encoding.ULAW) + && (nSampleSize==AudioSystem.NOT_SPECIFIED || nSampleSize == 8) + && frameSizeOK) { + return WAVE_FORMAT_ULAW; + } else if (encoding.equals(AudioFormat.Encoding.ALAW) + && (nSampleSize==AudioSystem.NOT_SPECIFIED || nSampleSize == 8) + && frameSizeOK) { + return WAVE_FORMAT_ALAW; + } else if (encoding.equals(new AudioFormat.Encoding("IMA_ADPCM")) + && nSampleSize == 4) + { + return WAVE_FORMAT_IMA_ADPCM; + } + else if (encoding.equals(GSM0610)) { + return WAVE_FORMAT_GSM610; + } + return WAVE_FORMAT_UNSPECIFIED; + } + +} + +/*** WaveTool.java ***/ diff --git a/songdbj/org/tritonus/file/gsm/GSMAudioFileReader.java b/songdbj/org/tritonus/file/gsm/GSMAudioFileReader.java new file mode 100644 index 0000000000..26859ddf24 --- /dev/null +++ b/songdbj/org/tritonus/file/gsm/GSMAudioFileReader.java @@ -0,0 +1,142 @@ +/* + * GSMAudioFileReader.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 2004 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.sampled.file.gsm; + +import java.io.InputStream; +import java.io.IOException; +import java.io.EOFException; + +import java.util.HashMap; +import java.util.Map; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.sound.sampled.spi.AudioFileReader; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioFileFormat; +import org.tritonus.share.sampled.file.TAudioFileReader; + + + +/** AudioFileReader class for GSM 06.10 data. + @author Matthias Pfisterer + */ +public class GSMAudioFileReader +extends TAudioFileReader +{ + private static final int GSM_MAGIC = 0xD0; + private static final int GSM_MAGIC_MASK = 0xF0; + + private static final int MARK_LIMIT = 1; + + + + public GSMAudioFileReader() + { + super(MARK_LIMIT, true); + } + + + + protected AudioFileFormat getAudioFileFormat(InputStream inputStream, long lFileSizeInBytes) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) { TDebug.out("GSMAudioFileReader.getAudioFileFormat(): begin"); } + int b0 = inputStream.read(); + if (b0 < 0) + { + throw new EOFException(); + } + + /* + * Check for magic number. + */ + if ((b0 & GSM_MAGIC_MASK) != GSM_MAGIC) + { + throw new UnsupportedAudioFileException("not a GSM stream: wrong magic number"); + } + + + /* + If the file size is known, we derive the number of frames + ('frame size') from it. + If the values don't fit into integers, we leave them at + NOT_SPECIFIED. 'Unknown' is considered less incorrect than + a wrong value. + */ + // [fb] not specifying it causes Sun's Wave file writer to write rubbish + int nByteSize = AudioSystem.NOT_SPECIFIED; + int nFrameSize = AudioSystem.NOT_SPECIFIED; + Map properties = new HashMap(); + if (lFileSizeInBytes != AudioSystem.NOT_SPECIFIED) + { + // the number of GSM frames + long lFrameSize = lFileSizeInBytes / 33; + // duration in microseconds + long lDuration = lFrameSize * 20000; + properties.put("duration", lDuration); + if (lFileSizeInBytes <= Integer.MAX_VALUE) + { + nByteSize = (int) lFileSizeInBytes; + nFrameSize = (int) (lFileSizeInBytes / 33); + } + } + + Map afProperties = new HashMap(); + afProperties.put("bitrate", 13200L); + AudioFormat format = new AudioFormat( + new AudioFormat.Encoding("GSM0610"), + 8000.0F, + AudioSystem.NOT_SPECIFIED /* ??? [sample size in bits] */, + 1, + 33, + 50.0F, + true, // this value is chosen arbitrarily + afProperties); + AudioFileFormat audioFileFormat = + new TAudioFileFormat( + new AudioFileFormat.Type("GSM","gsm"), + format, + nFrameSize, + nByteSize, + properties); + if (TDebug.TraceAudioFileReader) { TDebug.out("GSMAudioFileReader.getAudioFileFormat(): end"); } + return audioFileFormat; + } +} + + + +/*** GSMAudioFileReader.java ***/ + diff --git a/songdbj/org/tritonus/file/gsm/GSMAudioFileWriter.java b/songdbj/org/tritonus/file/gsm/GSMAudioFileWriter.java new file mode 100644 index 0000000000..eb3d1f3f16 --- /dev/null +++ b/songdbj/org/tritonus/file/gsm/GSMAudioFileWriter.java @@ -0,0 +1,77 @@ +/* + * GSMAudioFileWriter.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 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.sampled.file.gsm; + +import java.util.Arrays; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.THeaderlessAudioFileWriter; + + + +/** Class for writing GSM streams + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ +public class GSMAudioFileWriter +extends THeaderlessAudioFileWriter +{ + private static final AudioFileFormat.Type[] FILE_TYPES = + { + new AudioFileFormat.Type("GSM", "gsm") + }; + + private static final AudioFormat[] AUDIO_FORMATS = + { + new AudioFormat(new AudioFormat.Encoding("GSM0610"), 8000.0F, ALL, 1, 33, 50.0F, false), + new AudioFormat(new AudioFormat.Encoding("GSM0610"), 8000.0F, ALL, 1, 33, 50.0F, true), + }; + + + + public GSMAudioFileWriter() + { + super(Arrays.asList(FILE_TYPES), + Arrays.asList(AUDIO_FORMATS)); + if (TDebug.TraceAudioFileWriter) { TDebug.out("GSMAudioFileWriter.(): begin"); } + if (TDebug.TraceAudioFileWriter) { TDebug.out("GSMAudioFileWriter.(): end"); } + } +} + + + +/*** GSMAudioFileWriter.java ***/ diff --git a/songdbj/org/tritonus/file/gsm/package.html b/songdbj/org/tritonus/file/gsm/package.html new file mode 100644 index 0000000000..763851dda0 --- /dev/null +++ b/songdbj/org/tritonus/file/gsm/package.html @@ -0,0 +1,10 @@ + + + + + + +

GSM 06.10 audio file reader and writer. + The classes provided here .

+ + diff --git a/songdbj/org/tritonus/file/jorbis/JorbisAudioFileReader.java b/songdbj/org/tritonus/file/jorbis/JorbisAudioFileReader.java new file mode 100644 index 0000000000..5b33534b21 --- /dev/null +++ b/songdbj/org/tritonus/file/jorbis/JorbisAudioFileReader.java @@ -0,0 +1,231 @@ +/* + * JorbisAudioFileReader.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.sampled.file.jorbis; + +import java.io.InputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.UnsupportedAudioFileException; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioFileFormat; +import org.tritonus.share.sampled.file.TAudioFileReader; + +import com.jcraft.jogg.Buffer; +import com.jcraft.jogg.SyncState; +import com.jcraft.jogg.StreamState; +import com.jcraft.jogg.Page; +import com.jcraft.jogg.Packet; + + + +/** + * @author Matthias Pfisterer + */ +public class JorbisAudioFileReader +extends TAudioFileReader +{ + private static final int INITAL_READ_LENGTH = 4096; + private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1; + + + + public JorbisAudioFileReader() + { + super(MARK_LIMIT, true); + } + + + + protected AudioFileFormat getAudioFileFormat(InputStream inputStream, long lFileSizeInBytes) + throws UnsupportedAudioFileException, IOException + { + // sync and verify incoming physical bitstream + SyncState oggSyncState = new SyncState(); + + // take physical pages, weld into a logical stream of packets + StreamState oggStreamState = new StreamState(); + + // one Ogg bitstream page. Vorbis packets are inside + Page oggPage = new Page(); + + // one raw packet of data for decode + Packet oggPacket = new Packet(); + + int bytes = 0; + + // Decode setup + + oggSyncState.init(); // Now we can read pages + + // grab some data at the head of the stream. We want the first page + // (which is guaranteed to be small and only contain the Vorbis + // stream initial header) We need the first page to get the stream + // serialno. + + // submit a 4k block to libvorbis' Ogg layer + int index = oggSyncState.buffer(INITAL_READ_LENGTH); + bytes = inputStream.read(oggSyncState.data, index, INITAL_READ_LENGTH); + oggSyncState.wrote(bytes); + + // Get the first page. + if (oggSyncState.pageout(oggPage) != 1) + { + // have we simply run out of data? If so, we're done. + if (bytes < 4096) + { + // IDEA: throw EOFException? + throw new UnsupportedAudioFileException("not a Vorbis stream: ended prematurely"); + } + throw new UnsupportedAudioFileException("not a Vorbis stream: not in Ogg bitstream format"); + } + + // Get the serial number and set up the rest of decode. + // serialno first; use it to set up a logical stream + oggStreamState.init(oggPage.serialno()); + + // extract the initial header from the first page and verify that the + // Ogg bitstream is in fact Vorbis data + + // I handle the initial header first instead of just having the code + // read all three Vorbis headers at once because reading the initial + // header is an easy way to identify a Vorbis bitstream and it's + // useful to see that functionality seperated out. + + if (oggStreamState.pagein(oggPage) < 0) + { + // error; stream version mismatch perhaps + throw new UnsupportedAudioFileException("not a Vorbis stream: can't read first page of Ogg bitstream data"); + } + + if (oggStreamState.packetout(oggPacket) != 1) + { + // no page? must not be vorbis + throw new UnsupportedAudioFileException("not a Vorbis stream: can't read initial header packet"); + } + + Buffer oggPacketBuffer = new Buffer(); + oggPacketBuffer.readinit(oggPacket.packet_base, oggPacket.packet, oggPacket.bytes); + + int nPacketType = oggPacketBuffer.read(8); + byte[] buf = new byte[6]; + oggPacketBuffer.read(buf, 6); + if(buf[0]!='v' || buf[1]!='o' || buf[2]!='r' || + buf[3]!='b' || buf[4]!='i' || buf[5]!='s') + { + throw new UnsupportedAudioFileException("not a Vorbis stream: not a vorbis header packet"); + } + if (nPacketType != 1) + { + throw new UnsupportedAudioFileException("not a Vorbis stream: first packet is not the identification header"); + } + if(oggPacket.b_o_s == 0) + { + throw new UnsupportedAudioFileException("not a Vorbis stream: initial packet not marked as beginning of stream"); + } + int nVersion = oggPacketBuffer.read(32); + if (nVersion != 0) + { + throw new UnsupportedAudioFileException("not a Vorbis stream: wrong vorbis version"); + } + int nChannels = oggPacketBuffer.read(8); + float fSampleRate = oggPacketBuffer.read(32); + + // These are only used for error checking. + int bitrate_upper = oggPacketBuffer.read(32); + int bitrate_nominal = oggPacketBuffer.read(32); + int bitrate_lower = oggPacketBuffer.read(32); + + int[] blocksizes = new int[2]; + blocksizes[0] = 1 << oggPacketBuffer.read(4); + blocksizes[1] = 1 << oggPacketBuffer.read(4); + + if (fSampleRate < 1.0F || + nChannels < 1 || + blocksizes[0] < 8|| + blocksizes[1] < blocksizes[0] || + oggPacketBuffer.read(1) != 1) + { + throw new UnsupportedAudioFileException("not a Vorbis stream: illegal values in initial header"); + } + + + if (TDebug.TraceAudioFileReader) { TDebug.out("JorbisAudioFileReader.getAudioFileFormat(): channels: " + nChannels); } + if (TDebug.TraceAudioFileReader) { TDebug.out("JorbisAudioFileReader.getAudioFileFormat(): rate: " + fSampleRate); } + + /* + If the file size is known, we derive the number of frames + ('frame size') from it. + If the values don't fit into integers, we leave them at + NOT_SPECIFIED. 'Unknown' is considered less incorrect than + a wrong value. + */ + // [fb] not specifying it causes Sun's Wave file writer to write rubbish + int nByteSize = AudioSystem.NOT_SPECIFIED; + if (lFileSizeInBytes != AudioSystem.NOT_SPECIFIED + && lFileSizeInBytes <= Integer.MAX_VALUE) + { + nByteSize = (int) lFileSizeInBytes; + } + int nFrameSize = AudioSystem.NOT_SPECIFIED; + /* Can we calculate a useful size? + Peeking into ogginfo gives the insight that the only + way seems to be reading through the file. This is + something we do not want, at least not by default. + */ + // nFrameSize = (int) (lFileSizeInBytes / ...; + + AudioFormat format = new AudioFormat( + new AudioFormat.Encoding("VORBIS"), + fSampleRate, + AudioSystem.NOT_SPECIFIED, + nChannels, + AudioSystem.NOT_SPECIFIED, + AudioSystem.NOT_SPECIFIED, + true); // this value is chosen arbitrarily + if (TDebug.TraceAudioFileReader) { TDebug.out("JorbisAudioFileReader.getAudioFileFormat(): AudioFormat: " + format); } + AudioFileFormat.Type type = new AudioFileFormat.Type("Ogg","ogg"); + AudioFileFormat audioFileFormat = + new TAudioFileFormat( + type, + format, + nFrameSize, + nByteSize); + if (TDebug.TraceAudioFileReader) { TDebug.out("JorbisAudioFileReader.getAudioFileFormat(): AudioFileFormat: " + audioFileFormat); } + return audioFileFormat; + } +} + + + +/*** JorbisAudioFileReader.java ***/ + diff --git a/songdbj/org/tritonus/file/jorbis/package.html b/songdbj/org/tritonus/file/jorbis/package.html new file mode 100644 index 0000000000..e5b5599d8c --- /dev/null +++ b/songdbj/org/tritonus/file/jorbis/package.html @@ -0,0 +1,10 @@ + + + + + + +

Ogg vorbis audio file reader based on the jorbis library. + The classes provided here .

+ + diff --git a/songdbj/org/tritonus/file/mpeg/MpegAudioFileWriter.java b/songdbj/org/tritonus/file/mpeg/MpegAudioFileWriter.java new file mode 100644 index 0000000000..d958fecf0d --- /dev/null +++ b/songdbj/org/tritonus/file/mpeg/MpegAudioFileWriter.java @@ -0,0 +1,75 @@ +/* + * MpegAudioFileWriter.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.sampled.file.mpeg; + +import java.util.Arrays; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.THeaderlessAudioFileWriter; + + + +/** Class for writing mpeg files + * + * @author Florian Bomers + */ +public class MpegAudioFileWriter extends THeaderlessAudioFileWriter { + + private static final AudioFileFormat.Type[] FILE_TYPES = { + //new AudioFileFormat.Type("MPEG", "mpeg"), + // workaround for the fixed extension problem in AudioFileFormat.Type + // see org.tritonus.share.sampled.AudioFileTypes.java + new AudioFileFormat.Type("MP3", "mp3") + }; + + public static AudioFormat.Encoding MPEG1L3=new AudioFormat.Encoding("MPEG1L3"); + + private static final AudioFormat[] AUDIO_FORMATS = { + new AudioFormat(MPEG1L3, ALL, ALL, 1, ALL, ALL, false), + new AudioFormat(MPEG1L3, ALL, ALL, 1, ALL, ALL, true), + new AudioFormat(MPEG1L3, ALL, ALL, 2, ALL, ALL, false), + new AudioFormat(MPEG1L3, ALL, ALL, 2, ALL, ALL, true), + }; + + public MpegAudioFileWriter() + { + super(Arrays.asList(FILE_TYPES), + Arrays.asList(AUDIO_FORMATS)); + if (TDebug.TraceAudioFileWriter) { TDebug.out("MpegAudioFileWriter.(): begin"); } + if (TDebug.TraceAudioFileWriter) { TDebug.out("MpegAudioFileWriter.(): end"); } + } +} + + +/*** MpegAudioFileWriter.java ***/ diff --git a/songdbj/org/tritonus/file/mpeg/package.html b/songdbj/org/tritonus/file/mpeg/package.html new file mode 100644 index 0000000000..f24ded00ec --- /dev/null +++ b/songdbj/org/tritonus/file/mpeg/package.html @@ -0,0 +1,10 @@ + + + + + + +

Mp3 audio file reader and writer. + The classes provided here .

+ + diff --git a/songdbj/org/tritonus/file/package.html b/songdbj/org/tritonus/file/package.html new file mode 100644 index 0000000000..a3ba1632a8 --- /dev/null +++ b/songdbj/org/tritonus/file/package.html @@ -0,0 +1,10 @@ + + + + + + +

Standard audio file readers and writers (.aiff, .au, .wav). + The classes provided here .

+ + diff --git a/songdbj/org/tritonus/file/pvorbis/VorbisAudioFileReader.java b/songdbj/org/tritonus/file/pvorbis/VorbisAudioFileReader.java new file mode 100644 index 0000000000..a882f11c3a --- /dev/null +++ b/songdbj/org/tritonus/file/pvorbis/VorbisAudioFileReader.java @@ -0,0 +1,302 @@ +/* + * VorbisAudioFileReader.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2001 - 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.sampled.file.pvorbis; + +import java.io.InputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.UnsupportedAudioFileException; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioFileFormat; +import org.tritonus.share.sampled.file.TAudioFileReader; + +import org.tritonus.lowlevel.pogg.Buffer; +import org.tritonus.lowlevel.pogg.Page; +import org.tritonus.lowlevel.pogg.Packet; +import org.tritonus.lowlevel.pogg.SyncState; +import org.tritonus.lowlevel.pogg.StreamState; + + + +/** + * @author Matthias Pfisterer + */ +public class VorbisAudioFileReader +extends TAudioFileReader +{ + private static final int INITAL_READ_LENGTH = 4096; + private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1; + + + + public VorbisAudioFileReader() + { + super(MARK_LIMIT, true); + } + + + + protected AudioFileFormat getAudioFileFormat(InputStream inputStream, + long lFileSizeInBytes) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) { TDebug.out(">VorbisAudioFileReader.getAudioFileFormat(): begin"); } + SyncState oggSyncState = new SyncState(); + StreamState oggStreamState = new StreamState(); + Page oggPage = new Page(); + Packet oggPacket = new Packet(); + + int bytes = 0; + + // Decode setup + + oggSyncState.init(); // Now we can read pages + + // grab some data at the head of the stream. We want the first page + // (which is guaranteed to be small and only contain the Vorbis + // stream initial header) We need the first page to get the stream + // serialno. + + // submit a 4k block to libvorbis' Ogg layer + byte[] abBuffer = new byte[INITAL_READ_LENGTH]; + bytes = inputStream.read(abBuffer); + if (TDebug.TraceAudioFileReader) { TDebug.out("read bytes from input stream: " + bytes); } + int nResult = oggSyncState.write(abBuffer, bytes); + if (TDebug.TraceAudioFileReader) { TDebug.out("SyncState.write() returned " + nResult); } + + // Get the first page. + if (oggSyncState.pageOut(oggPage) != 1) + { + // have we simply run out of data? If so, we're done. + if (bytes < INITAL_READ_LENGTH) + { + if (TDebug.TraceAudioFileReader) { TDebug.out("stream ended prematurely"); } + if (TDebug.TraceAudioFileReader) { TDebug.out(">> 4) & 0xF); + if (TDebug.TraceAudioFileReader) { TDebug.out("blocksizes[0]: " + blocksizes[0]); } + if (TDebug.TraceAudioFileReader) { TDebug.out("blocksizes[1]: " + blocksizes[1]); } + + if (fSampleRate < 1.0F || + nChannels < 1 || + blocksizes[0] < 8|| + blocksizes[1] < blocksizes[0] || + (abData[29] & 0x1) != 1) + { + if (TDebug.TraceAudioFileReader) { TDebug.out("illegal values in initial header"); } + if (TDebug.TraceAudioFileReader) { TDebug.out(" + * Copyright (c) 2000 -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.sampled.file.pvorbis; + +import java.util.Arrays; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.THeaderlessAudioFileWriter; + + + +/** Class for writing Vorbis streams + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ +public class VorbisAudioFileWriter +extends THeaderlessAudioFileWriter +{ + private static final AudioFileFormat.Type[] FILE_TYPES = + { + new AudioFileFormat.Type("Vorbis", "ogg") + }; + + private static final AudioFormat[] AUDIO_FORMATS = + { + new AudioFormat(new AudioFormat.Encoding("VORBIS"), ALL, ALL, ALL, ALL, ALL, false), + new AudioFormat(new AudioFormat.Encoding("VORBIS"), ALL, ALL, ALL, ALL, ALL, true), + }; + + + + public VorbisAudioFileWriter() + { + super(Arrays.asList(FILE_TYPES), + Arrays.asList(AUDIO_FORMATS)); + if (TDebug.TraceAudioFileWriter) { TDebug.out("VorbisAudioFileWriter.(): begin"); } + if (TDebug.TraceAudioFileWriter) { TDebug.out("VorbisAudioFileWriter.(): end"); } + } +} + + + +/*** VorbisAudioFileWriter.java ***/ diff --git a/songdbj/org/tritonus/file/pvorbis/package.html b/songdbj/org/tritonus/file/pvorbis/package.html new file mode 100644 index 0000000000..a5238aaca0 --- /dev/null +++ b/songdbj/org/tritonus/file/pvorbis/package.html @@ -0,0 +1,12 @@ + + + + + + +

Ogg vorbis audio file reader and writer based on the pure java libraries. + The classes provided here .

+ + @see org.tritonus.lowlevel.ogg + + diff --git a/songdbj/org/tritonus/file/vorbis/VorbisAudioFileReader.java b/songdbj/org/tritonus/file/vorbis/VorbisAudioFileReader.java new file mode 100644 index 0000000000..f8b34d5411 --- /dev/null +++ b/songdbj/org/tritonus/file/vorbis/VorbisAudioFileReader.java @@ -0,0 +1,302 @@ +/* + * VorbisAudioFileReader.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.sampled.file.vorbis; + +import java.io.InputStream; +import java.io.IOException; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.UnsupportedAudioFileException; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.TAudioFileFormat; +import org.tritonus.share.sampled.file.TAudioFileReader; + +import org.tritonus.lowlevel.ogg.Buffer; +import org.tritonus.lowlevel.ogg.Page; +import org.tritonus.lowlevel.ogg.Packet; +import org.tritonus.lowlevel.ogg.SyncState; +import org.tritonus.lowlevel.ogg.StreamState; + + + +/** + * @author Matthias Pfisterer + */ +public class VorbisAudioFileReader +extends TAudioFileReader +{ + private static final int INITAL_READ_LENGTH = 4096; + private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1; + + + + public VorbisAudioFileReader() + { + super(MARK_LIMIT, true); + } + + + + protected AudioFileFormat getAudioFileFormat(InputStream inputStream, + long lFileSizeInBytes) + throws UnsupportedAudioFileException, IOException + { + if (TDebug.TraceAudioFileReader) { TDebug.out(">VorbisAudioFileReader.getAudioFileFormat(): begin"); } + SyncState oggSyncState = new SyncState(); + StreamState oggStreamState = new StreamState(); + Page oggPage = new Page(); + Packet oggPacket = new Packet(); + + int bytes = 0; + + // Decode setup + + oggSyncState.init(); // Now we can read pages + + // grab some data at the head of the stream. We want the first page + // (which is guaranteed to be small and only contain the Vorbis + // stream initial header) We need the first page to get the stream + // serialno. + + // submit a 4k block to libvorbis' Ogg layer + byte[] abBuffer = new byte[INITAL_READ_LENGTH]; + bytes = inputStream.read(abBuffer); + if (TDebug.TraceAudioFileReader) { TDebug.out("read bytes from input stream: " + bytes); } + int nResult = oggSyncState.write(abBuffer, bytes); + if (TDebug.TraceAudioFileReader) { TDebug.out("SyncState.write() returned " + nResult); } + + // Get the first page. + if (oggSyncState.pageOut(oggPage) != 1) + { + // have we simply run out of data? If so, we're done. + if (bytes < INITAL_READ_LENGTH) + { + if (TDebug.TraceAudioFileReader) { TDebug.out("stream ended prematurely"); } + if (TDebug.TraceAudioFileReader) { TDebug.out(">> 4) & 0xF); + if (TDebug.TraceAudioFileReader) { TDebug.out("blocksizes[0]: " + blocksizes[0]); } + if (TDebug.TraceAudioFileReader) { TDebug.out("blocksizes[1]: " + blocksizes[1]); } + + if (fSampleRate < 1.0F || + nChannels < 1 || + blocksizes[0] < 8|| + blocksizes[1] < blocksizes[0] || + (abData[29] & 0x1) != 1) + { + if (TDebug.TraceAudioFileReader) { TDebug.out("illegal values in initial header"); } + if (TDebug.TraceAudioFileReader) { TDebug.out(" + * 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.sampled.file.vorbis; + +import java.util.Arrays; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; + +import org.tritonus.share.TDebug; +import org.tritonus.share.sampled.file.THeaderlessAudioFileWriter; + + + +/** Class for writing Vorbis streams + * + * @author Florian Bomers + * @author Matthias Pfisterer + */ +public class VorbisAudioFileWriter +extends THeaderlessAudioFileWriter +{ + private static final AudioFileFormat.Type[] FILE_TYPES = + { + new AudioFileFormat.Type("Vorbis", "ogg") + }; + + private static final AudioFormat[] AUDIO_FORMATS = + { + new AudioFormat(new AudioFormat.Encoding("VORBIS"), ALL, ALL, ALL, ALL, ALL, false), + new AudioFormat(new AudioFormat.Encoding("VORBIS"), ALL, ALL, ALL, ALL, ALL, true), + }; + + + + public VorbisAudioFileWriter() + { + super(Arrays.asList(FILE_TYPES), + Arrays.asList(AUDIO_FORMATS)); + if (TDebug.TraceAudioFileWriter) { TDebug.out("VorbisAudioFileWriter.(): begin"); } + if (TDebug.TraceAudioFileWriter) { TDebug.out("VorbisAudioFileWriter.(): end"); } + } +} + + + +/*** VorbisAudioFileWriter.java ***/ diff --git a/songdbj/org/tritonus/file/vorbis/package.html b/songdbj/org/tritonus/file/vorbis/package.html new file mode 100644 index 0000000000..5d6c328b7d --- /dev/null +++ b/songdbj/org/tritonus/file/vorbis/package.html @@ -0,0 +1,12 @@ + + + + + + +

Ogg vorbis audio file reader and writer based on native libraries. + The classes provided here .

+ + @see org.tritonus.lowlevel.ogg + + diff --git a/songdbj/org/tritonus/lowlevel/ogg/Buffer.java b/songdbj/org/tritonus/lowlevel/ogg/Buffer.java new file mode 100644 index 0000000000..2903f0e17e --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/ogg/Buffer.java @@ -0,0 +1,173 @@ +/* + * Buffer.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 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.lowlevel.ogg; + +import org.tritonus.share.TDebug; + + +/** Wrapper for oggpack_buffer. + */ +public class Buffer +{ + static + { + Ogg.loadNativeLibrary(); + if (TDebug.TraceOggNative) + { + setTrace(true); + } + } + + + /** + * Holds the pointer to oggpack_buffer + * for the native code. + * This must be long to be 64bit-clean. + */ + private long m_lNativeHandle; + + + + public Buffer() + { + if (TDebug.TraceOggNative) { TDebug.out("Buffer.(): begin"); } + int nReturn = malloc(); + if (nReturn < 0) + { + throw new RuntimeException("malloc of ogg_page failed"); + } + if (TDebug.TraceOggNative) { TDebug.out("Buffer.(): end"); } + } + + + + public void finalize() + { + // TODO: call free() + // call super.finalize() first or last? + // and introduce a flag if free() has already been called? + } + + + + private native int malloc(); + public native void free(); + + + /** Calls oggpack_writeinit(). + */ + public native void writeInit(); + + + /** Calls oggpack_writetrunc(). + */ + public native void writeTrunc(int nBits); + + + /** Calls oggpack_writealign(). + */ + public native void writeAlign(); + + + /** Calls oggpack_writecopy(). + */ + public native void writeCopy(byte[] abSource, int nBits); + + + /** Calls oggpack_reset(). + */ + public native void reset(); + + + /** Calls oggpack_writeclear(). + */ + public native void writeClear(); + + + /** Calls oggpack_readinit(). + */ + public native void readInit(byte[] abBuffer, int nBytes); + + + /** Calls oggpack_write(). + */ + public native void write(int nValue, int nBits); + + + /** Calls oggpack_look(). + */ + public native int look(int nBits); + + + /** Calls oggpack_look1(). + */ + public native int look1(); + + + /** Calls oggpack_adv(). + */ + public native void adv(int nBits); + + + /** Calls oggpack_adv1(). + */ + public native void adv1(); + + + /** Calls oggpack_read(). + */ + public native int read(int nBits); + + + /** Calls oggpack_read1(). + */ + public native int read1(); + + + /** Calls oggpack_bytes(). + */ + public native int bytes(); + + + /** Calls oggpack_bits(). + */ + public native int bits(); + + + /** Calls oggpack_get_buffer(). + */ + public native byte[] getBuffer(); + + + private static native void setTrace(boolean bTrace); +} + + + +/*** Buffer.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/ogg/Ogg.java b/songdbj/org/tritonus/lowlevel/ogg/Ogg.java new file mode 100644 index 0000000000..1ad6bde789 --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/ogg/Ogg.java @@ -0,0 +1,104 @@ +/* + * Ogg.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 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.lowlevel.ogg; + +import org.tritonus.share.TDebug; + + +/** libogg loading. + */ +public class Ogg +{ + private static boolean sm_bIsLibraryAvailable = false; + + + + static + { + Ogg.loadNativeLibrary(); + } + + + + public static void loadNativeLibrary() + { + if (TDebug.TraceOggNative) { TDebug.out("Ogg.loadNativeLibrary(): begin"); } + + if (! isLibraryAvailable()) + { + loadNativeLibraryImpl(); + } + if (TDebug.TraceOggNative) { TDebug.out("Ogg.loadNativeLibrary(): end"); } + } + + + + /** Load the native library for ogg vorbis. + + This method actually does the loading of the library. Unlike + {@link loadNativeLibrary() loadNativeLibrary()}, it does not + check if the library is already loaded. + + */ + private static void loadNativeLibraryImpl() + { + if (TDebug.TraceOggNative) { TDebug.out("Ogg.loadNativeLibraryImpl(): loading native library tritonusvorbis"); } + try + { + System.loadLibrary("tritonusvorbis"); + // only reached if no exception occures + sm_bIsLibraryAvailable = true; + } + catch (Error e) + { + if (TDebug.TraceOggNative || + TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + // throw e; + } + if (TDebug.TraceOggNative) { TDebug.out("Ogg.loadNativeLibraryImpl(): loaded"); } + } + + + + /** Returns whether the libraries are installed correctly. + */ + public static boolean isLibraryAvailable() + { + return sm_bIsLibraryAvailable; + } +} + + + +/*** Ogg.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/ogg/Packet.java b/songdbj/org/tritonus/lowlevel/ogg/Packet.java new file mode 100644 index 0000000000..a5b3f6e7e2 --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/ogg/Packet.java @@ -0,0 +1,113 @@ +/* + * Packet.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 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.lowlevel.ogg; + +import org.tritonus.share.TDebug; + + + +/** Wrapper for ogg_packet. + */ +public class Packet +{ + static + { + Ogg.loadNativeLibrary(); + if (TDebug.TraceOggNative) + { + setTrace(true); + } + } + + + /** + * Holds the pointer to ogg_packet + * for the native code. + * This must be long to be 64bit-clean. + */ + private long m_lNativeHandle; + + + + public Packet() + { + if (TDebug.TraceOggNative) { TDebug.out("Packet.(): begin"); } + int nReturn = malloc(); + if (nReturn < 0) + { + throw new RuntimeException("malloc of ogg_packet failed"); + } + if (TDebug.TraceOggNative) { TDebug.out("Packet.(): end"); } + } + + + + public void finalize() + { + // TODO: call free() + // call super.finalize() first or last? + // and introduce a flag if free() has already been called? + } + + + + private native int malloc(); + public native void free(); + + + + /** Calls ogg_packet_clear(). + */ + public native void clear(); + + + + /** Accesses packet and bytes. + */ + public native byte[] getData(); + + + /** Accesses b_o_s. + */ + public native boolean isBos(); + + + /** Accesses e_o_s. + */ + public native boolean isEos(); + + + private static native void setTrace(boolean bTrace); +} + + + + + +/*** Packet.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/ogg/Page.java b/songdbj/org/tritonus/lowlevel/ogg/Page.java new file mode 100644 index 0000000000..ae30f210d4 --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/ogg/Page.java @@ -0,0 +1,131 @@ +/* + * Page.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 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.lowlevel.ogg; + +import org.tritonus.share.TDebug; + + + +/** Wrapper for ogg_page. + */ +public class Page +{ + static + { + Ogg.loadNativeLibrary(); + if (TDebug.TraceOggNative) + { + setTrace(true); + } + } + /** + * Holds the pointer to ogg_page + * for the native code. + * This must be long to be 64bit-clean. + */ + private long m_lNativeHandle; + + + + public Page() + { + if (TDebug.TraceOggNative) { TDebug.out("Page.(): begin"); } + int nReturn = malloc(); + if (nReturn < 0) + { + throw new RuntimeException("malloc of ogg_page failed"); + } + if (TDebug.TraceOggNative) { TDebug.out("Page.(): end"); } + } + + + + public void finalize() + { + // TODO: call free() + // call super.finalize() first or last? + // and introduce a flag if free() has already been called? + } + + + + private native int malloc(); + public native void free(); + + + /** Calls ogg_page_version(). + */ + public native int getVersion(); + + /** Calls ogg_page_continued(). + */ + public native boolean isContinued(); + + /** Calls ogg_page_packets(). + */ + public native int getPackets(); + + /** Calls ogg_page_bos(). + */ + public native boolean isBos(); + + /** Calls ogg_page_eos(). + */ + public native boolean isEos(); + + /** Calls ogg_page_granulepos(). + */ + public native long getGranulePos(); + + /** Calls ogg_page_serialno(). + */ + public native int getSerialNo(); + + /** Calls ogg_page_pageno(). + */ + public native int getPageNo(); + + /** Calls ogg_page_checksum_set(). + */ + public native void setChecksum(); + + + public native byte[] getHeader(); + + public native byte[] getBody(); + + + private static native void setTrace(boolean bTrace); +} + + + + + +/*** Page.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/ogg/StreamState.java b/songdbj/org/tritonus/lowlevel/ogg/StreamState.java new file mode 100644 index 0000000000..34b970c5e2 --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/ogg/StreamState.java @@ -0,0 +1,143 @@ +/* + * StreamState.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 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.lowlevel.ogg; + +import org.tritonus.share.TDebug; + + +/** Wrapper for ogg_stream_state. + */ +public class StreamState +{ + static + { + Ogg.loadNativeLibrary(); + if (TDebug.TraceOggNative) + { + setTrace(true); + } + } + + + /** + * Holds the pointer to ogg_stream_state + * for the native code. + * This must be long to be 64bit-clean. + */ + private long m_lNativeHandle; + + + + public StreamState() + { + if (TDebug.TraceOggNative) { TDebug.out("StreamState.(): begin"); } + int nReturn = malloc(); + if (nReturn < 0) + { + throw new RuntimeException("malloc of ogg_stream_state failed"); + } + if (TDebug.TraceOggNative) { TDebug.out("StreamState.(): end"); } + } + + + + public void finalize() + { + // TODO: call free() + // call super.finalize() first or last? + // and introduce a flag if free() has already been called? + } + + + + private native int malloc(); + public native void free(); + + + + /** Calls ogg_stream_init(). + */ + public native int init(int nSerialNo); + + /** Calls ogg_stream_clear(). + */ + public native int clear(); + + /** Calls ogg_stream_reset(). + */ + public native int reset(); + + /** Calls ogg_stream_destroy(). + */ + public native int destroy(); + + /** Calls ogg_stream_eos(). + */ + public native boolean isEOSReached(); + + + + /** Calls ogg_stream_packetin(). + */ + public native int packetIn(Packet packet); + + + /** Calls ogg_stream_pageout(). + */ + public native int pageOut(Page page); + + + /** Calls ogg_stream_flush(). + */ + public native int flush(Page page); + + + /** Calls ogg_stream_pagein(). + */ + public native int pageIn(Page page); + + + /** Calls ogg_stream_packetout(). + */ + public native int packetOut(Packet packet); + + + /** Calls ogg_stream_packetpeek(). + */ + public native int packetPeek(Packet packet); + + + private static native void setTrace(boolean bTrace); +} + + + + + +/*** StreamState.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/ogg/SyncState.java b/songdbj/org/tritonus/lowlevel/ogg/SyncState.java new file mode 100644 index 0000000000..3b3b535fbe --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/ogg/SyncState.java @@ -0,0 +1,127 @@ +/* + * SyncState.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 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.lowlevel.ogg; + +import org.tritonus.share.TDebug; + + +/** Wrapper for ogg_sync_state. + */ +public class SyncState +{ + static + { + Ogg.loadNativeLibrary(); + if (TDebug.TraceOggNative) + { + setTrace(true); + } + } + + + /** + * Holds the pointer to ogg_sync_state + * for the native code. + * This must be long to be 64bit-clean. + */ + private long m_lNativeHandle; + + + + public SyncState() + { + if (TDebug.TraceOggNative) { TDebug.out("SyncState.(): begin"); } + int nReturn = malloc(); + if (nReturn < 0) + { + throw new RuntimeException("malloc of ogg_sync_state failed"); + } + if (TDebug.TraceOggNative) { TDebug.out("SyncState.(): end"); } + } + + + + public void finalize() + { + // TODO: call free() + // call super.finalize() first or last? + // and introduce a flag if free() has already been called? + } + + + + private native int malloc(); + public native void free(); + + + + /** Calls ogg_sync_init(). + */ + public native void init(); + + + /** Calls ogg_sync_clear(). + */ + public native void clear(); + + + /** Calls ogg_sync_reset(). + */ + public native void reset(); + + + /** Calls ogg_sync_destroy(). + */ + public native void destroy(); + + + /** Calls ogg_sync_buffer() + and ogg_sync_wrote(). + */ + public native int write(byte[] abBuffer, int nBytes); + + + /** Calls ogg_sync_pageseek(). + */ + public native int pageseek(Page page); + + + /** Calls ogg_sync_pageout(). + */ + public native int pageOut(Page page); + + + private static native void setTrace(boolean bTrace); +} + + + + + +/*** SyncState.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/ogg/package.html b/songdbj/org/tritonus/lowlevel/ogg/package.html new file mode 100644 index 0000000000..4202aaebac --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/ogg/package.html @@ -0,0 +1,12 @@ + + + + + + +

Access to the native ogg library. + The classes provided here .

+ + @see org.tritonus.sampled.convert.vorbis + + diff --git a/songdbj/org/tritonus/lowlevel/pogg/Buffer.java b/songdbj/org/tritonus/lowlevel/pogg/Buffer.java new file mode 100644 index 0000000000..6d94c37740 --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/pogg/Buffer.java @@ -0,0 +1,284 @@ +/* + * Buffer.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 2005 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.lowlevel.pogg; + +import java.io.UnsupportedEncodingException; + +import org.tritonus.share.TDebug; + + +/** Wrapper for oggpack_buffer. + */ +public class Buffer +{ + static + { + Ogg.loadNativeLibrary(); + if (TDebug.TraceOggNative) + { + setTrace(true); + } + } + + + /** + * Holds the pointer to oggpack_buffer + * for the native code. + * This must be long to be 64bit-clean. + */ + private long m_lNativeHandle; + + + + public Buffer() + { + if (TDebug.TraceOggNative) { TDebug.out("Buffer.(): begin"); } + int nReturn = malloc(); + if (nReturn < 0) + { + throw new RuntimeException("malloc of ogg_page failed"); + } + if (TDebug.TraceOggNative) { TDebug.out("Buffer.(): end"); } + } + + + + public void finalize() + { + // TODO: call free() + // call super.finalize() first or last? + // and introduce a flag if free() has already been called? + } + + + + private native int malloc(); + public native void free(); + + + /** Calls oggpack_writeinit(). + */ + public native void writeInit(); + + + /** Calls oggpack_writetrunc(). + */ + public native void writeTrunc(int nBits); + + + /** Calls oggpack_writealign(). + */ + public native void writeAlign(); + + + /** Calls oggpack_writecopy(). + */ + public native void writeCopy(byte[] abSource, int nBits); + + + /** Calls oggpack_reset(). + */ + public native void reset(); + + + /** Calls oggpack_writeclear(). + */ + public native void writeClear(); + + + /** Calls oggpack_readinit(). + */ + public native void readInit(byte[] abBuffer, int nBytes); + + + /** Calls oggpack_write(). + */ + public native void write(int nValue, int nBits); + + + /** Calls oggpack_look(). + */ + public native int look(int nBits); + + + /** Calls oggpack_look1(). + */ + public native int look1(); + + + /** Calls oggpack_adv(). + */ + public native void adv(int nBits); + + + /** Calls oggpack_adv1(). + */ + public native void adv1(); + + + /** Calls oggpack_read(). + */ + public native int read(int nBits); + + + /** Calls oggpack_read1(). + */ + public native int read1(); + + + /** Calls oggpack_bytes(). + */ + public native int bytes(); + + + /** Calls oggpack_bits(). + */ + public native int bits(); + + + /** Calls oggpack_get_buffer(). + */ + public native byte[] getBuffer(); + + + /** Writes a string as UTF-8. + Note: no length coding and no end byte are written, + just the pure string! + */ + public void write(String str) + { + write(str, false); + } + + + /** Writes a string as UTF-8, including a length coding. + In front of the string, the length in bytes is written + as a 32 bit integer. No end byte is written. + */ + public void writeWithLength(String str) + { + write(str, true); + } + + + /** Writes a string as UTF-8, with or without a length coding. + If a length coding is requested, the length in (UTF8-)bytes is written + as a 32 bit integer in front of the string. + No end byte is written. + */ + private void write(String str, boolean bWithLength) + { + byte[] aBytes = null; + try + { + aBytes = str.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) + { + if (TDebug.TraceAllExceptions) TDebug.out(e); + } + if (bWithLength) + { + write(aBytes.length, 32); + } + for (int i = 0; i < aBytes.length; i++) + { + write(aBytes[i], 8); + } + } + + + /** Reads a UTF-8 coded string with length coding. + It is expected that at the current read position, + there is a 32 bit integer containing the length in (UTF8-)bytes, + followed by the specified number of bytes in UTF-8 coding. + + @return the string read from the buffer or null if an error occurs. + */ + public String readString() + { + int length = read(32); + if (length < 0) + { + return null; + } + return readString(length); + } + + + /** Reads a UTF-8 coded string without length coding. + It is expected that at the current read position, + there is string in UTF-8 coding. + + @return the string read from the buffer or null if an error occurs. + */ + public String readString(int nLength) + { + byte[] aBytes = new byte[nLength]; + for (int i = 0; i < nLength; i++) + { + aBytes[i] = (byte) read(8); + } + String s = null; + try + { + s = new String(aBytes, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + if (TDebug.TraceAllExceptions) TDebug.out(e); + } + return s; + } + + + /** Reads a single bit. + */ + public boolean readFlag() + { + return (read(1) != 0); + } + + private static native void setTrace(boolean bTrace); + + // for debugging + public static void outBuffer(byte[] buffer) + { + String s = ""; + for (int i = 0; i < buffer.length; i++) + { + s += "" + buffer[i] + ", "; + } + TDebug.out("buffer: " + s); + } +} + + + +/*** Buffer.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/pogg/Ogg.java b/songdbj/org/tritonus/lowlevel/pogg/Ogg.java new file mode 100644 index 0000000000..086dd0f001 --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/pogg/Ogg.java @@ -0,0 +1,104 @@ +/* + * Ogg.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 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.lowlevel.pogg; + +import org.tritonus.share.TDebug; + + +/** libogg loading. + */ +public class Ogg +{ + private static boolean sm_bIsLibraryAvailable = false; + + + + static + { + Ogg.loadNativeLibrary(); + } + + + + public static void loadNativeLibrary() + { + if (TDebug.TraceOggNative) { TDebug.out("Ogg.loadNativeLibrary(): begin"); } + + if (! isLibraryAvailable()) + { + loadNativeLibraryImpl(); + } + if (TDebug.TraceOggNative) { TDebug.out("Ogg.loadNativeLibrary(): end"); } + } + + + + /** Load the native library for ogg vorbis. + + This method actually does the loading of the library. Unlike + {@link loadNativeLibrary() loadNativeLibrary()}, it does not + check if the library is already loaded. + + */ + private static void loadNativeLibraryImpl() + { + if (TDebug.TraceOggNative) { TDebug.out("Ogg.loadNativeLibraryImpl(): loading native library tritonuspvorbis"); } + try + { + System.loadLibrary("tritonuspvorbis"); + // only reached if no exception occures + sm_bIsLibraryAvailable = true; + } + catch (Error e) + { + if (TDebug.TraceOggNative || + TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + // throw e; + } + if (TDebug.TraceOggNative) { TDebug.out("Ogg.loadNativeLibraryImpl(): loaded"); } + } + + + + /** Returns whether the libraries are installed correctly. + */ + public static boolean isLibraryAvailable() + { + return sm_bIsLibraryAvailable; + } +} + + + +/*** Ogg.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/pogg/Packet.java b/songdbj/org/tritonus/lowlevel/pogg/Packet.java new file mode 100644 index 0000000000..15c5d9a66e --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/pogg/Packet.java @@ -0,0 +1,133 @@ +/* + * Packet.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 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.lowlevel.pogg; + +import org.tritonus.share.TDebug; + + + +/** Wrapper for ogg_packet. + */ +public class Packet +{ + static + { + Ogg.loadNativeLibrary(); + if (TDebug.TraceOggNative) + { + setTrace(true); + } + } + + + /** + * Holds the pointer to ogg_packet + * for the native code. + * This must be long to be 64bit-clean. + */ + private long m_lNativeHandle; + + + + public Packet() + { + if (TDebug.TraceOggNative) { TDebug.out("Packet.(): begin"); } + int nReturn = malloc(); + if (nReturn < 0) + { + throw new RuntimeException("malloc of ogg_packet failed"); + } + if (TDebug.TraceOggNative) { TDebug.out("Packet.(): end"); } + } + + + + public void finalize() + { + // TODO: call free() + // call super.finalize() first or last? + // and introduce a flag if free() has already been called? + } + + + + private native int malloc(); + public native void free(); + + + + /** Calls ogg_packet_clear(). + */ + public native void clear(); + + + + /** Accesses packet and bytes. + */ + public native byte[] getData(); + + + /** Accesses b_o_s. + */ + public native boolean isBos(); + + + /** Accesses e_o_s. + */ + public native boolean isEos(); + + + public native long getGranulePos(); + + + public native long getPacketNo(); + + + /** Sets the data in the packet. + */ + public native void setData(byte[] abData, int nOffset, int nLength); + + + public native void setFlags(boolean bBos, boolean bEos, long lGranulePos, + long lPacketNo); + + public void setFlags(boolean bBos, boolean bEos, long lGranulePos) + { + setFlags(bBos, bEos, lGranulePos, 0); + } + + + private static native void setTrace(boolean bTrace); +} + + + + + +/*** Packet.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/pogg/Page.java b/songdbj/org/tritonus/lowlevel/pogg/Page.java new file mode 100644 index 0000000000..3f89d7166e --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/pogg/Page.java @@ -0,0 +1,298 @@ +/* + * Page.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 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.lowlevel.pogg; + +import org.tritonus.share.TDebug; + + + +/** Wrapper for ogg_page. + */ +public class Page +{ + private static final int[] crc_lookup = + { + 0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9, + 0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005, + 0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61, + 0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd, + 0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9, + 0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75, + 0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011, + 0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd, + 0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039, + 0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5, + 0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81, + 0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d, + 0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49, + 0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95, + 0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1, + 0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d, + 0x34867077,0x30476dc0,0x3d044b19,0x39c556ae, + 0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072, + 0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16, + 0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca, + 0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde, + 0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02, + 0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066, + 0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba, + 0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e, + 0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692, + 0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6, + 0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a, + 0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e, + 0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2, + 0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686, + 0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a, + 0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637, + 0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb, + 0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f, + 0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53, + 0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47, + 0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b, + 0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff, + 0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623, + 0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7, + 0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b, + 0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f, + 0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3, + 0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7, + 0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b, + 0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f, + 0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3, + 0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640, + 0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c, + 0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8, + 0x68860bfd,0x6c47164a,0x61043093,0x65c52d24, + 0x119b4be9,0x155a565e,0x18197087,0x1cd86d30, + 0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec, + 0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088, + 0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654, + 0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0, + 0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c, + 0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18, + 0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4, + 0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0, + 0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c, + 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668, + 0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4 + }; + + + private byte[] m_abHeader; + private int m_nHeaderLength; + private byte[] m_abBody; + private int m_nBodyLength; + + + public Page() + { + if (TDebug.TraceOggNative) { TDebug.out("Page.(): begin"); } + if (TDebug.TraceOggNative) { TDebug.out("Page.(): end"); } + } + + + + private native int malloc(); + + // TODO: remove calls to this method + public void free() + { + } + + + /** Calls ogg_page_version(). + */ + public int getVersion() + { + return m_abHeader[4] & 0xFF; + } + + + /** Calls ogg_page_continued(). + */ + public boolean isContinued() + { + return (m_abHeader[5] & 0x01) != 0; + } + + + + /** Calls ogg_page_packets(). + */ +/* returns the number of packets that are completed on this page (if + the leading packet is begun on a previous page, but ends on this + page, it's counted */ + +/* NOTE: +If a page consists of a packet begun on a previous page, and a new +packet begun (but not completed) on this page, the return will be: + ogg_page_packets(page) ==1, + ogg_page_continued(page) !=0 + +If a page happens to be a single packet that was begun on a +previous page, and spans to the next page (in the case of a three or +more page packet), the return will be: + ogg_page_packets(page) ==0, + ogg_page_continued(page) !=0 +*/ + public int getPackets() + { + int n = m_abHeader[26] & 0xFF; + int count = 0; + for (int i = 0; i < n; i++) + if ((m_abHeader[27 + i] & 0xFF) < 255) + count++; + return count; + } + + + + /** Calls ogg_page_bos(). + */ + public boolean isBos() + { + return (m_abHeader[5] & 0x02) != 0; + } + + + + /** Calls ogg_page_eos(). + */ + public boolean isEos() + { + return (m_abHeader[5] & 0x04) != 0; + } + + + + /** Calls ogg_page_granulepos(). + */ + public long getGranulePos() + { + long granulepos = m_abHeader[13]&(0xff); + granulepos = (granulepos<<8)|(m_abHeader[12] & 0xFF); + granulepos = (granulepos<<8)|(m_abHeader[11] & 0xFF); + granulepos = (granulepos<<8)|(m_abHeader[10] & 0xFF); + granulepos = (granulepos<<8)|(m_abHeader[9] & 0xFF); + granulepos = (granulepos<<8)|(m_abHeader[8] & 0xFF); + granulepos = (granulepos<<8)|(m_abHeader[7] & 0xFF); + granulepos = (granulepos<<8)|(m_abHeader[6] & 0xFF); + return granulepos; + } + + + + /** Calls ogg_page_serialno(). + */ + public int getSerialNo() + { + return m_abHeader[14] | + (m_abHeader[15] << 8) | + (m_abHeader[16] << 16) | + (m_abHeader[17] << 24); + } + + + + /** Calls ogg_page_pageno(). + */ + public int getPageNo() + { + return m_abHeader[18] | + (m_abHeader[19] << 8) | + (m_abHeader[20] << 16) | + (m_abHeader[21] << 24); + } + + + + /** Calls ogg_page_checksum_set(). + */ + public void setChecksum() + { + int crc_reg = 0; + + /* safety; needed for API behavior, but not framing code */ + m_abHeader[22]=0; + m_abHeader[23]=0; + m_abHeader[24]=0; + m_abHeader[25]=0; + + for(int i = 0; i < m_nHeaderLength; i++) + crc_reg = (crc_reg << 8) ^ crc_lookup[((crc_reg >>> 24) & 0xff) ^ (m_abHeader[i] & 0xFF)]; + for(int i = 0; i < m_nBodyLength; i++) + crc_reg = (crc_reg << 8) ^ crc_lookup[((crc_reg >>> 24) & 0xff) ^ (m_abBody[i] & 0xFF)]; + + m_abHeader[22] = (byte) (crc_reg & 0xff); + m_abHeader[23] = (byte) ((crc_reg >> 8) & 0xff); + m_abHeader[24] = (byte) ((crc_reg >> 16) & 0xff); + m_abHeader[25] = (byte) ((crc_reg >> 24) & 0xff); + } + + + + + public byte[] getHeader() + { + byte[] abHeader = new byte[m_nHeaderLength]; + System.arraycopy(m_abHeader, 0, abHeader, 0, m_nHeaderLength); + return abHeader; + } + + + + public byte[] getBody() + { + byte[] abBody = new byte[m_nBodyLength]; + System.arraycopy(m_abBody, 0, abBody, 0, m_nBodyLength); + return abBody; + } + + + + public void setData(byte[] abHeader, int nHeaderOffset, + int nHeaderLength, + byte[] abBody, int nBodyOffset, + int nBodyLength) + { + m_abHeader = new byte[nHeaderLength]; + System.arraycopy(abHeader, nHeaderOffset, m_abHeader, 0, nHeaderLength); + m_nHeaderLength = nHeaderLength; + m_abBody = new byte[nBodyLength]; + System.arraycopy(abBody, nBodyOffset, m_abBody, 0, nBodyLength); + m_nBodyLength = nBodyLength; + } +} + + + + + +/*** Page.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/pogg/StreamState.java b/songdbj/org/tritonus/lowlevel/pogg/StreamState.java new file mode 100644 index 0000000000..3fde33de8f --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/pogg/StreamState.java @@ -0,0 +1,703 @@ +/* + * StreamState.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 2005 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.lowlevel.pogg; + +import org.tritonus.share.TDebug; + + +/** Wrapper for ogg_stream_state. + */ +public class StreamState +{ + private static final int INITIAL_BODY_DATA_SIZE = 16 * 1024; + private static final int INITIAL_LACING_VALUES_SIZE = 1024; + + /** The serial number of the stream. + This is set by init(). + */ + private int m_nSerialNo; + + /** Storage for packet bodies. + */ + private byte[] m_abBodyData; + + /** Number of bytes used in te body storage. + */ + private int m_nBodyFill; + + /** Number of bytes aready returned (as pages) from the body storage. + */ + private int m_nBodyReturned; + + /** Lacing values. Bit 0 to 7 contain the lacing value (mask + 0xFF). Bit 8 is set if the segment belongs to the first + packet of the stream (mask 0x100). Bit 9 is set ig the + segment belongs to the last packet of the stream (mask 0x200). + */ + private int[] m_anLacingValues; + + /** Granule values. + */ + private long[] m_alGranuleValues; + + /** Number of elements used in m_anLacingValues and m_alGranuleValues. + The elements with the index m_nLacingFill is the first free element. + */ + private int m_nLacingFill; + + /** Pointer to the index in m_anLacingValues where the lacing values + of the last decoded packet start (??) + */ + private int m_nLacingPacket; + + /** + */ + private int m_nLacingReturned; + + private byte[] m_abHeader; + + private int m_nHeaderFill; + + private boolean m_bBos; + private boolean m_bEos; + private int m_nPageNo; + private long m_lPacketNo; + private long m_lGranulePos; + + + + public StreamState() + { + if (TDebug.TraceOggNative) { TDebug.out("StreamState.(): begin"); } + if (TDebug.TraceOggNative) { TDebug.out("StreamState.(): end"); } + } + + + + public void free() + { + } + + + + /** Calls ogg_stream_init(). + */ + public int init(int nSerialNo) + { + m_nSerialNo = nSerialNo; + m_abBodyData = new byte[INITIAL_BODY_DATA_SIZE]; + m_nBodyFill = 0; + m_nBodyReturned = 0; + m_anLacingValues = new int[INITIAL_LACING_VALUES_SIZE]; + m_alGranuleValues = new long[INITIAL_LACING_VALUES_SIZE]; + m_nLacingFill = 0; + m_nLacingPacket = 0; + m_nLacingReturned = 0; + + m_abHeader = new byte[282]; + m_nHeaderFill = 0; + + m_bBos = false; + m_bEos = false; + m_nPageNo = 0; + m_lPacketNo = 0; + m_lGranulePos = 0; + + // TODO: necessary? + for (int i = 0; i < m_abBodyData.length; i++) + m_abBodyData[i] = 0; + for (int i = 0; i < m_anLacingValues.length; i++) + m_anLacingValues[i] = 0; + for (int i = 0; i < m_alGranuleValues.length; i++) + m_alGranuleValues[i] = 0; + + // TODO: remove return value + return 0; + } + + /** Calls ogg_stream_clear(). + */ + public int clear() + { + m_nSerialNo = 0; + m_abBodyData = null; + m_nBodyFill = 0; + m_nBodyReturned = 0; + m_anLacingValues = null; + m_alGranuleValues = null; + m_nLacingFill = 0; + m_nLacingPacket = 0; + m_nLacingReturned = 0; + + m_abHeader = null; + m_nHeaderFill = 0; + + m_bBos = false; + m_bEos = false; + m_nPageNo = 0; + m_lPacketNo = 0; + m_lGranulePos = 0; + + // TODO: remove return value + return 0; + } + + /** Calls ogg_stream_reset(). + */ + public int reset() + { + m_nBodyFill = 0; + m_nBodyReturned = 0; + + m_nLacingFill = 0; + m_nLacingPacket = 0; + m_nLacingReturned = 0; + + m_nHeaderFill = 0; + + m_bBos = false; + m_bEos = false; + m_nPageNo = -1; + m_lPacketNo = 0; + m_lGranulePos = 0; + + // TODO: remove return value + return 0; + } + + /** Calls ogg_stream_eos(). + */ + public boolean isEOSReached() + { + return m_bEos; + } + + /** Calls ogg_stream_packetin(). + */ + /* submit data to the internal buffer of the framing engine */ + public int packetIn(Packet packet) + { + int i; + byte[] abPacketData = packet.getData(); + int lacing_vals = abPacketData.length / 255 + 1; + + if (m_nBodyReturned > 0) + { + /* advance packet data according to the body_returned pointer. We + had to keep it around to return a pointer into the buffer last + call */ + m_nBodyFill -= m_nBodyReturned; + if (m_nBodyFill > 0) + { + System.arraycopy(m_abBodyData, m_nBodyReturned, + m_abBodyData, 0, m_nBodyFill); + } + m_nBodyReturned = 0; + } + + /* make sure we have the buffer storage */ + assureBodyDataCapacity(abPacketData.length); + assureLacingValuesCapacity(lacing_vals); + + /* Copy in the submitted packet. Yes, the copy is a waste; + this is the liability of overly clean abstraction for the + time being. It will actually be fairly easy to eliminate + the extra copy in the future */ + System.arraycopy(abPacketData, 0, m_abBodyData, m_nBodyFill, + abPacketData.length); + m_nBodyFill += abPacketData.length; + + /* Store lacing vals for this packet */ + for (i = 0; i < lacing_vals - 1; i++) + { + m_anLacingValues[m_nLacingFill + i] = 255; + m_alGranuleValues[m_nLacingFill + i] = m_lGranulePos; + } + m_anLacingValues[m_nLacingFill + i] = abPacketData.length % 255; + m_alGranuleValues[m_nLacingFill + i] = packet.getGranulePos(); + m_lGranulePos = packet.getGranulePos(); + + /* flag the first segment as the beginning of the packet */ + m_anLacingValues[m_nLacingFill] |= 0x100; + + m_nLacingFill += lacing_vals; + + /* for the sake of completeness */ + m_lPacketNo++; + + if (packet.isEos()) + m_bEos = true; + return 0; + } + + + + /** Calls ogg_stream_pageout(). + */ +/* This constructs pages from buffered packet segments. The pointers + returned are to static buffers; do not free. The returned buffers are + good only until the next call (using the same ogg_stream_state) */ + public int pageOut(Page page) + { + if ((m_bEos && m_nLacingFill > 0) || /* 'were done, now flush' */ + m_nBodyFill - m_nBodyReturned > 4096 || /* 'page nominal size' */ + m_nLacingFill >= 255 || /* 'segment table full' */ + (m_nLacingFill > 0 && ! m_bBos)) /* 'initial header page' */ + { + return flush(page); + } + /* not enough data to construct a page and not end of stream */ + return 0; + } + + + + /** Calls ogg_stream_flush(). + */ +/* This will flush remaining packets into a page (returning nonzero), + even if there is not enough data to trigger a flush normally + (undersized page). If there are no packets or partial packets to + flush, ogg_stream_flush returns 0. Note that ogg_stream_flush will + try to flush a normal sized page like ogg_stream_pageout; a call to + ogg_stream_flush does not guarantee that all packets have flushed. + Only a return value of 0 from ogg_stream_flush indicates all packet + data is flushed into pages. + + since ogg_stream_flush will flush the last page in a stream even if + it's undersized, you almost certainly want to use ogg_stream_pageout + (and *not* ogg_stream_flush) unless you specifically need to flush + an page regardless of size in the middle of a stream. +*/ + public int flush(Page page) + { + int i; + int vals = 0; + int maxvals = Math.min(m_nLacingFill, 255); + int bytes = 0; + int acc = 0; + long granule_pos = m_alGranuleValues[0]; + + if (maxvals == 0) + { + return 0; + } + + /* construct a page */ + /* decide how many segments to include */ + + /* If this is the initial header case, the first page must + only include the initial header packet */ + if (! m_bBos) + { /* 'initial header page' case */ + granule_pos = 0; + for (vals = 0; vals < maxvals; vals++) + { + if ((m_anLacingValues[vals] & 0x0FF) < 255) + { + vals++; + break; + } + } + } + else + { + for (vals = 0; vals < maxvals; vals++) + { + if (acc > 4096) + break; + acc += (m_anLacingValues[vals] & 0x0FF); + granule_pos = m_alGranuleValues[vals]; + } + } + + /* construct the header in temp storage */ + m_abHeader[0] = (byte) 'O'; + m_abHeader[1] = (byte) 'g'; + m_abHeader[2] = (byte) 'g'; + m_abHeader[3] = (byte) 'S'; + + /* stream structure version */ + m_abHeader[4] = 0; + + m_abHeader[5] = 0x00; + /* continued packet flag? */ + if ((m_anLacingValues[0] & 0x100) == 0) + m_abHeader[5] |= 0x01; + /* first page flag? */ + if (! m_bBos) + m_abHeader[5] |= 0x02; + /* last page flag? */ + if (m_bEos && m_nLacingFill == vals) + m_abHeader[5] |= 0x04; + m_bBos = true; + + /* 64 bits of PCM position */ + for (i = 6; i < 14; i++) + { + m_abHeader[i] = (byte) (granule_pos & 0xFF); + granule_pos >>>= 8; + } + + /* 32 bits of stream serial number */ + int serialno = m_nSerialNo; + for (i = 14; i < 18; i++) + { + m_abHeader[i] = (byte) (serialno & 0xFF); + serialno >>>= 8; + } + + /* 32 bits of page counter (we have both counter and page header + because this val can roll over) */ + if (m_nPageNo == -1) + { + m_nPageNo = 0; /* because someone called + stream_reset; this would be a + strange thing to do in an + encode stream, but it has + plausible uses */ + } + int pageno = m_nPageNo++; + for (i = 18; i < 22; i++) + { + m_abHeader[i] = (byte) (pageno & 0xFF); + pageno >>>= 8; + } + + /* zero for computation; filled in later */ + m_abHeader[22] = 0; + m_abHeader[23] = 0; + m_abHeader[24] = 0; + m_abHeader[25] = 0; + + /* segment table */ + m_abHeader[26] = (byte) (vals & 0xFF); + for (i = 0; i < vals; i++) + { + m_abHeader[i + 27] = (byte) (m_anLacingValues[i] & 0xFF); + bytes += (m_anLacingValues[i] & 0xFF); + } + + /* set pointers in the ogg_page struct */ + page.setData(m_abHeader, 0, vals + 27, + m_abBodyData, m_nBodyReturned, bytes); + m_nHeaderFill = vals + 27; + + /* advance the lacing data and set the body_returned pointer */ + + m_nLacingFill -= vals; + System.arraycopy(m_anLacingValues, vals, m_anLacingValues, 0, + m_nLacingFill); + System.arraycopy(m_alGranuleValues, vals, m_alGranuleValues, 0, + m_nLacingFill); + m_nBodyReturned += bytes; + + /* calculate the checksum */ + + page.setChecksum(); + + /* done */ + return 1; + } + + + + /** add the incoming page to the stream state; we decompose the + page into packet segments here as well. + + @return 0 on success, -1 if the stream serial number stored in + the page does not match the one stored in the stream state or + if the protocol version stored in the page is greater than 0. + */ + public int pageIn(Page page) + { + byte[] header = page.getHeader(); + byte[] body = page.getBody(); + int nBodyOffset = 0; + int bodysize = body.length; + int segptr = 0; + + int version = page.getVersion(); + boolean continued = page.isContinued(); + boolean bos = page.isBos(); + boolean eos = page.isEos(); + long granulepos = page.getGranulePos(); + int serialno = page.getSerialNo(); + int pageno = page.getPageNo(); + int segments = header[26] & 0xFF; + + /* clean up 'returned data' */ + int lr = m_nLacingReturned; + int br = m_nBodyReturned; + + /* body data */ + if (br > 0) + { + m_nBodyFill -= br; + if (m_nBodyFill > 0) + { + System.arraycopy(m_abBodyData, br, m_abBodyData, 0, + m_nBodyFill); + } + m_nBodyReturned = 0; + } + + if (lr > 0) + { + /* segment table */ + if (m_nLacingFill - lr > 0) + { + System.arraycopy(m_anLacingValues, lr, m_anLacingValues, 0, + m_nLacingFill - lr); + System.arraycopy(m_alGranuleValues, lr, m_alGranuleValues, 0, + m_nLacingFill - lr); + } + m_nLacingFill -= lr; + m_nLacingPacket -= lr; + m_nLacingReturned = 0; + } + + /* check the serial number */ + if (serialno != m_nSerialNo) + return -1; + if (version > 0) + return -1; + + assureLacingValuesCapacity(segments + 1); + + /* are we in sequence? */ + if (pageno != m_nPageNo) + { + int i; + + /* unroll previous partial packet (if any) */ + for (i = m_nLacingPacket; i < m_nLacingFill; i++) + m_nBodyFill -= (m_anLacingValues[i] & 0xFF); + m_nLacingFill = m_nLacingPacket; + + /* make a note of dropped data in segment table */ + if (m_nPageNo != -1) + { + m_anLacingValues[m_nLacingFill] = 0x400; + m_nLacingFill++; + m_nLacingPacket++; + } + + /* are we a 'continued packet' page? If so, we'll need to skip + some segments */ + if (continued) + { + bos = false; + for (; segptr < segments; segptr++) + { + int val = header[27 + segptr] & 0xFF; + nBodyOffset += val; + bodysize -= val; + if (val < 255) + { + segptr++; + break; + } + } + } + } + + if (bodysize > 0) + { + assureBodyDataCapacity(bodysize); + System.arraycopy(body, nBodyOffset, m_abBodyData, m_nBodyFill, + bodysize); + m_nBodyFill += bodysize; + } + + int saved = -1; + while (segptr < segments) + { + int val = header[27 + segptr] & 0xFF; + m_anLacingValues[m_nLacingFill] = val; + m_alGranuleValues[m_nLacingFill] = -1; + + if (bos) + { + m_anLacingValues[m_nLacingFill] |= 0x100; + bos = false; + } + + if (val < 255) + saved = m_nLacingFill; + + m_nLacingFill++; + segptr++; + + if (val < 255) + m_nLacingPacket = m_nLacingFill; + } + + /* set the granulepos on the last granuleval of the last full packet */ + if (saved != -1) + { + m_alGranuleValues[saved] = granulepos; + } + + if (eos) + { + m_bEos = true; + if (m_nLacingFill > 0) + m_anLacingValues[m_nLacingFill - 1] |= 0x200; + } + + m_nPageNo = pageno + 1; + + return 0; + } + + + /** Calls ogg_stream_packetout(). + */ + public int packetOut(Packet packet) + { + return packetOutInternal(packet, true); + } + + + /** Calls ogg_stream_packetpeek(). + */ + public int packetPeek(Packet packet) + { + return packetOutInternal(packet, false); + } + + + /** Retrieves a packet from the internal storage for emission. + This method is called by packetOut and packetPeek. + + @param packet the Packet object to store the retrieved packet + data in. May be null if bAdvance is false. + + @param bAdvance should the internal pointers to the packet + data storage be advanced to the next packet after retrieving + this one? Called with a value of true for ordinary packet out + and with a value of false for packet peek. + + @return + */ + private int packetOutInternal(Packet packet, boolean bAdvance) + { + /* The last part of decode. We have the stream broken into + packet segments. Now we need to group them into packets + (or return the out of sync markers) */ + + int ptr = m_nLacingReturned; + + if (m_nLacingPacket <= ptr) + return 0; + + if ((m_anLacingValues[ptr] & 0x400) != 0) + { + /* we need to tell the codec there's a gap; it might need + to handle previous packet dependencies. */ + m_nLacingReturned++; + m_lPacketNo++; + return -1; + } + + if (packet == null && ! bAdvance) + return 1; /* just using peek as an inexpensive way + to ask if there's a whole packet + waiting */ + + /* Gather the whole packet. We'll have no holes or a partial + * packet */ + int size = m_anLacingValues[ptr] & 0xFF; + int bytes = size; + /* last packet of the stream? */ + boolean eos = (m_anLacingValues[ptr] & 0x200) != 0; + /* first packet of the stream? */ + boolean bos = (m_anLacingValues[ptr] & 0x100) != 0; + + while (size == 255) + { + int val = m_anLacingValues[++ptr]; + size = val & 0xff; + if ((val & 0x200) != 0) + eos = true; + bytes += size; + } + + if (packet != null) + { + packet.setData(m_abBodyData, m_nBodyReturned, bytes); + packet.setFlags(bos, eos, m_alGranuleValues[ptr], m_lPacketNo); + } + + if (bAdvance) + { + m_nBodyReturned += bytes; + m_nLacingReturned = ptr + 1; + m_lPacketNo++; + } + return 1; + } + + + private void assureBodyDataCapacity(int needed) + { + if (m_abBodyData.length <= m_nBodyFill + needed) + { + int nNewSize = m_abBodyData.length + needed + 1024; + byte[] abNewBodyData = new byte[nNewSize]; + System.arraycopy(m_abBodyData, 0, abNewBodyData, 0, + m_abBodyData.length); + m_abBodyData = abNewBodyData; + } + } + + + + private void assureLacingValuesCapacity(int needed) + { + if (m_anLacingValues.length <= m_nLacingFill + needed) + { + int nNewSize = m_anLacingValues.length + needed + 32; + int[] anNewLacingValues = new int[nNewSize]; + System.arraycopy(m_anLacingValues, 0, anNewLacingValues, 0, + m_anLacingValues.length); + m_anLacingValues = anNewLacingValues; + long[] alNewGranuleValues = new long[nNewSize]; + System.arraycopy(m_alGranuleValues, 0, alNewGranuleValues, 0, + m_alGranuleValues.length); + m_alGranuleValues = alNewGranuleValues; + } + } +} + + + + + +/*** StreamState.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/pogg/SyncState.java b/songdbj/org/tritonus/lowlevel/pogg/SyncState.java new file mode 100644 index 0000000000..4246808bd3 --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/pogg/SyncState.java @@ -0,0 +1,339 @@ +/* + * SyncState.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 2000 - 2005 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.lowlevel.pogg; + +import org.tritonus.share.TDebug; + + +/** Wrapper for ogg_sync_state. + */ +public class SyncState +{ + /** The stream buffer. + This points to a buffer that holds the incoming data from a stream + until the data is emitted in form of a page. + */ + private byte[] m_abData; + + /** The number of bytes in the stream buffer that are used. This + always counts from the beginning of the buffer. So + m_abData[m_nFill] is the first free byte in the buffer. + */ + private int m_nFill; + + /** Number of bytes that have been used to construct a returned + page. This is counted from the beginning of the + buffer. m_abData[m_nReturned] is the first valid byte in the + buffer. + */ + private int m_nReturned; + + /** + */ + private boolean m_bUnsynced; + + /** + */ + private int m_nHeaderBytes; + + /** + */ + private int m_nBodyBytes; + + /** Page object for re-calculating the checksum. + */ + private Page m_tmpPage; + + /** Storage for the checksum saved from the page. + */ + private byte[] m_abChecksum; + + + public SyncState() + { + if (TDebug.TraceOggNative) { TDebug.out("SyncState.(): begin"); } + m_tmpPage = new Page(); + m_abChecksum = new byte[4]; + if (TDebug.TraceOggNative) { TDebug.out("SyncState.(): end"); } + } + + + // TODO: remove calls to this method + public void free() + { + } + + + + /** Calls ogg_sync_init(). + */ + public void init() + { + m_abData = null; + m_nFill = 0; + m_nReturned = 0; + m_bUnsynced = false; + m_nHeaderBytes = 0; + m_nBodyBytes = 0; + } + + + /** Calls ogg_sync_clear(). + */ + public void clear() + { + init(); + } + + + /** Calls ogg_sync_reset(). + */ + public void reset() + { + m_nFill = 0; + m_nReturned = 0; + m_bUnsynced = false; + m_nHeaderBytes = 0; + m_nBodyBytes = 0; + } + + + /** Writes to the stream buffer. + */ + public int write(byte[] abBuffer, int nBytes) + { + /* Clear out space that has been previously returned. */ + if (m_nReturned > 0) + { + m_nFill -= m_nReturned; + if (m_nFill > 0) + { + System.arraycopy(m_abData, m_nReturned, + m_abData, 0, + m_nFill); + } + m_nReturned = 0; + } + + /* Check for enough space in the stream buffer and if it is + * allocated at all. If there isn't sufficient space, extend + * the buffer. */ + if (m_abData == null || nBytes > m_abData.length - m_nFill) + { + int nNewSize = nBytes + m_nFill + 4096; + byte[] abOldBuffer = m_abData; + m_abData = new byte[nNewSize]; + if (abOldBuffer != null) + { + System.arraycopy(abOldBuffer, 0, m_abData, 0, m_nFill); + } + } + + /* Now finally fill with the new data. */ + System.arraycopy(abBuffer, 0, m_abData, m_nFill, nBytes); + m_nFill += nBytes; + return 0; + } + + + /** Synchronizes the stream. This method looks if there is a + complete, valid page in the stream buffer. If a page is found, + it is returned in the passed Page object. + + @param page The Page to store the result of the page search + in. The content is only changed if the return value is > 0. + + @return if a page has been found at the current location, the + length of the page in bytes is returned. If not enough data + for a page is available in the stream buffer, 0 is + returned. If data in the stream buffer has been skipped + because there is no page at the current position, the skip + amount in bytes is returned as a negative number. + */ + public int pageseek(Page page) + { + int nPage = m_nReturned; + int nBytes = m_nFill - m_nReturned; + + if (m_nHeaderBytes == 0) + { + if (nBytes < 27) + { + /* Not enough data for a header. */ + return 0; + } + /* Verify capture pattern. */ + if (m_abData[nPage] != (byte) 'O' || + m_abData[nPage + 1] != (byte) 'g' || + m_abData[nPage + 2] != (byte) 'g' || + m_abData[nPage + 3] != (byte) 'S') + { + TDebug.out("wrong capture pattern"); + return syncFailure(); + } + int nHeaderBytes = (m_abData[nPage + 26] & 0xFF) + 27; + if (nBytes < nHeaderBytes) + { + /* Not enough data for header + segment table. */ + return 0; + } + /* Count up body length in the segment table. */ + for (int i = 0; i < (m_abData[nPage + 26] & 0xFF); i++) + { + m_nBodyBytes += (m_abData[nPage + 27 + i] & 0xFF); + } + m_nHeaderBytes = nHeaderBytes; + } + + if (m_nBodyBytes + m_nHeaderBytes > nBytes) + { + /* Not enough data for the whole packet. */ + return 0; + } + + /* Save the original checksum, set it to zero and recalculate it. */ + System.arraycopy(m_abData, nPage + 22, m_abChecksum, 0, 4); + m_abData[nPage + 22] = 0; + m_abData[nPage + 23] = 0; + m_abData[nPage + 24] = 0; + m_abData[nPage + 25] = 0; + + m_tmpPage.setData(m_abData, nPage, m_nHeaderBytes, + m_abData, nPage + m_nHeaderBytes, m_nBodyBytes); +// TDebug.out("temporary page:"); +// byte[] abHeader = m_tmpPage.getHeader(); +// TDebug.out("H0:" + m_abData[nPage + 0] + " - " + abHeader[0]); +// TDebug.out("H1:" + m_abData[nPage + 1] + " - " + abHeader[1]); +// TDebug.out("H2:" + m_abData[nPage + 2] + " - " + abHeader[2]); +// TDebug.out("H3:" + m_abData[nPage + 3] + " - " + abHeader[3]); +// TDebug.out("" + m_abChecksum[0] + " - " + abHeader[22]); +// TDebug.out("" + m_abChecksum[1] + " - " + abHeader[23]); +// TDebug.out("" + m_abChecksum[2] + " - " + abHeader[24]); +// TDebug.out("" + m_abChecksum[3] + " - " + abHeader[25]); + m_tmpPage.setChecksum(); + byte[] abHeader = m_tmpPage.getHeader(); + //m_tmpPage.free(); + if (abHeader[22] != m_abChecksum[0] || + abHeader[23] != m_abChecksum[1] || + abHeader[24] != m_abChecksum[2] || + abHeader[25] != m_abChecksum[3]) + { + TDebug.out("wrong checksum"); + TDebug.out("" + m_abChecksum[0] + " - " + abHeader[22]); + TDebug.out("" + m_abChecksum[1] + " - " + abHeader[23]); + TDebug.out("" + m_abChecksum[2] + " - " + abHeader[24]); + TDebug.out("" + m_abChecksum[3] + " - " + abHeader[25]); + /* Copy back the saved checksum. */ + System.arraycopy(m_abChecksum, 0, m_abData, nPage + 22, 4); + return syncFailure(); + } + + /* Ok, we have a correct page to emit. */ + page.setData(m_abData, nPage, m_nHeaderBytes, + m_abData, nPage + m_nHeaderBytes, m_nBodyBytes); + m_bUnsynced = false; + nBytes = m_nHeaderBytes + m_nBodyBytes; + m_nReturned += nBytes; + m_nHeaderBytes = 0; + m_nBodyBytes = 0; + return nBytes; + } + + + /** Auxiliary method for pageseek(). + */ + private int syncFailure() + { + int nPage = m_nReturned; + int nBytes = m_nFill - m_nReturned; + m_nHeaderBytes = 0; + m_nBodyBytes = 0; + int nNext = -1; + for (int i = 0; i < nBytes - 1; i++) + { + if (m_abData[nPage + 1 + i] == (byte) 'O') + { + nNext = nPage + 1 + i; + break; + } + } + if (nNext == -1) + { + nNext = m_nFill; + } + m_nReturned = nNext; + return -(nNext - nPage); + } + + + + + /** Returns a page from the stream buffer, if possible. This + method searches the current data in the stream buffer for the + beginning of a page. If there is one, it is returned in the + passed Page object. A synchronization error is signaled by a + return value of -1. However, only the first synchronization + error is reported. Consecutive sync errors are suppressed. + + @param page The Page to store the result of the page search + in. The content is only changed if the return value is 1. + + @return 1 if a page has been found, 0 if there is not enough + data, -1 if a synchronization error occured. + */ + public int pageOut(Page page) + { + while (true) + { + int nReturn = pageseek(page); + if (nReturn > 0) + { + return 1; + } + else if (nReturn == 0) + { + return 0; + } + else // nReturn < 0 + { + if (! m_bUnsynced) + { + m_bUnsynced = true; + return -1; + } + } + } + } +} + + + + + +/*** SyncState.java ***/ diff --git a/songdbj/org/tritonus/lowlevel/pogg/package.html b/songdbj/org/tritonus/lowlevel/pogg/package.html new file mode 100644 index 0000000000..57b0e50763 --- /dev/null +++ b/songdbj/org/tritonus/lowlevel/pogg/package.html @@ -0,0 +1,12 @@ + + + + + + +

Alternative pure java implementation of the ogg library. + The classes provided here .

+ + @see org.tritonus.sampled.convert.pvorbis + + diff --git a/songdbj/org/tritonus/share/ArraySet.java b/songdbj/org/tritonus/share/ArraySet.java new file mode 100644 index 0000000000..5aa677364f --- /dev/null +++ b/songdbj/org/tritonus/share/ArraySet.java @@ -0,0 +1,87 @@ +/* + * ArraySet.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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; + + + +public class ArraySet +extends ArrayList +implements Set +{ + public ArraySet() + { + super(); + } + + + + public ArraySet(Collection c) + { + this(); + addAll(c); + } + + + + public boolean add(E element) + { + if (!contains(element)) + { + super.add(element); + return true; + } + else + { + return false; + } + } + + + + public void add(int index, E element) + { + throw new UnsupportedOperationException("ArraySet.add(int index, Object element) unsupported"); + } + + public E set(int index, E element) + { + throw new UnsupportedOperationException("ArraySet.set(int index, Object element) unsupported"); + } + +} + + + +/*** ArraySet.java ***/ diff --git a/songdbj/org/tritonus/share/GlobalInfo.java b/songdbj/org/tritonus/share/GlobalInfo.java new file mode 100644 index 0000000000..9a44c9c870 --- /dev/null +++ b/songdbj/org/tritonus/share/GlobalInfo.java @@ -0,0 +1,60 @@ +/* + * GlobalInfo.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; + +import org.tritonus.share.TDebug; + + + +public class GlobalInfo +{ + private static final String VENDOR = "Tritonus is free software. See http://www.tritonus.org/"; + private static final String VERSION = "0.3.1"; + + + + public static String getVendor() + { + return VENDOR; + } + + + + public static String getVersion() + { + return VERSION; + } +} + + + +/*** GlobalInfo.java ***/ + diff --git a/songdbj/org/tritonus/share/StringHashedSet.java b/songdbj/org/tritonus/share/StringHashedSet.java new file mode 100644 index 0000000000..8e244665fb --- /dev/null +++ b/songdbj/org/tritonus/share/StringHashedSet.java @@ -0,0 +1,112 @@ +/* + * StringHashedSet.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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import org.tritonus.share.ArraySet; + + +/** + * A set where the elements are uniquely referenced by their + * string representation as given by the objects toString() + * method. No 2 objects with the same toString() can + * be in the set. + *

+ * The contains(Object elem) and get(Object elem) + * methods can be called with Strings as elem parameter. + * For get(Object elem), the object that has been added + * is returned, and not its String representation. + *

+ * Though it's possible to store + * Strings as objects in this class, it doesn't make sense + * as you could use ArraySet for that equally well. + *

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

+ * It is not possible to add null elements. + */ + +public class StringHashedSet extends ArraySet +{ + public StringHashedSet() + { + super(); + } + + public StringHashedSet(Collection c) + { + super(c); + } + + public boolean add(E elem) + { + if (elem==null) { + return false; + } + return super.add(elem); + } + + public boolean contains(Object elem) + { + if (elem==null) { + return false; + } + String comp=elem.toString(); + Iterator it=iterator(); + while (it.hasNext()) { + if (comp.equals(it.next().toString())) { + return true; + } + } + return false; + } + + public E get(E elem) { + if (elem==null) { + return null; + } + String comp=elem.toString(); + Iterator it=iterator(); + while (it.hasNext()) { + E thisElem=it.next(); + if (comp.equals(thisElem.toString())) { + return thisElem; + } + } + return null; + } +} + +/*** StringHashedSet.java ***/ diff --git a/songdbj/org/tritonus/share/TCircularBuffer.java b/songdbj/org/tritonus/share/TCircularBuffer.java new file mode 100644 index 0000000000..11ebc0af01 --- /dev/null +++ b/songdbj/org/tritonus/share/TCircularBuffer.java @@ -0,0 +1,268 @@ +/* + * TCircularBuffer.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; + +import org.tritonus.share.TDebug; + + + +public class TCircularBuffer +{ + private boolean m_bBlockingRead; + private boolean m_bBlockingWrite; + private byte[] m_abData; + private int m_nSize; + private long m_lReadPos; + private long m_lWritePos; + private Trigger m_trigger; + private boolean m_bOpen; + + + + public TCircularBuffer(int nSize, boolean bBlockingRead, boolean bBlockingWrite, Trigger trigger) + { + m_bBlockingRead = bBlockingRead; + m_bBlockingWrite = bBlockingWrite; + m_nSize = nSize; + m_abData = new byte[m_nSize]; + m_lReadPos = 0; + m_lWritePos = 0; + m_trigger = trigger; + m_bOpen = true; + } + + + + public void close() + { + m_bOpen = false; + // TODO: call notify() ? + } + + + + private boolean isOpen() + { + return m_bOpen; + } + + + public int availableRead() + { + return (int) (m_lWritePos - m_lReadPos); + } + + + + public int availableWrite() + { + return m_nSize - availableRead(); + } + + + + private int getReadPos() + { + return (int) (m_lReadPos % m_nSize); + } + + + + private int getWritePos() + { + return (int) (m_lWritePos % m_nSize); + } + + + + public int read(byte[] abData) + { + return read(abData, 0, abData.length); + } + + + + public int read(byte[] abData, int nOffset, int nLength) + { + if (TDebug.TraceCircularBuffer) + { + TDebug.out(">TCircularBuffer.read(): called."); + dumpInternalState(); + } + if (! isOpen()) + { + if (availableRead() > 0) + { + nLength = Math.min(nLength, availableRead()); + if (TDebug.TraceCircularBuffer) { TDebug.out("reading rest in closed buffer, length: " + nLength); } + } + else + { + if (TDebug.TraceCircularBuffer) { TDebug.out("< not open. returning -1."); } + return -1; + } + } + synchronized (this) + { + if (m_trigger != null && availableRead() < nLength) + { + if (TDebug.TraceCircularBuffer) { TDebug.out("executing trigger."); } + m_trigger.execute(); + } + if (!m_bBlockingRead) + { + nLength = Math.min(availableRead(), nLength); + } + int nRemainingBytes = nLength; + while (nRemainingBytes > 0) + { + while (availableRead() == 0) + { + try + { + wait(); + } + catch (InterruptedException e) + { + if (TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + } + } + int nAvailable = Math.min(availableRead(), nRemainingBytes); + while (nAvailable > 0) + { + int nToRead = Math.min(nAvailable, m_nSize - getReadPos()); + System.arraycopy(m_abData, getReadPos(), abData, nOffset, nToRead); + m_lReadPos += nToRead; + nOffset += nToRead; + nAvailable -= nToRead; + nRemainingBytes -= nToRead; + } + notifyAll(); + } + if (TDebug.TraceCircularBuffer) + { + TDebug.out("After read:"); + dumpInternalState(); + TDebug.out("< completed. Read " + nLength + " bytes"); + } + return nLength; + } + } + + + public int write(byte[] abData) + { + return write(abData, 0, abData.length); + } + + + + public int write(byte[] abData, int nOffset, int nLength) + { + if (TDebug.TraceCircularBuffer) + { + TDebug.out(">TCircularBuffer.write(): called; nLength: " + nLength); + dumpInternalState(); + } + synchronized (this) + { + if (TDebug.TraceCircularBuffer) { TDebug.out("entered synchronized block."); } + if (!m_bBlockingWrite) + { + nLength = Math.min(availableWrite(), nLength); + } + int nRemainingBytes = nLength; + while (nRemainingBytes > 0) + { + while (availableWrite() == 0) + { + try + { + wait(); + } + catch (InterruptedException e) + { + if (TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + } + } + int nAvailable = Math.min(availableWrite(), nRemainingBytes); + while (nAvailable > 0) + { + int nToWrite = Math.min(nAvailable, m_nSize - getWritePos()); + //TDebug.out("src buf size= " + abData.length + ", offset = " + nOffset + ", dst buf size=" + m_abData.length + " write pos=" + getWritePos() + " len=" + nToWrite); + System.arraycopy(abData, nOffset, m_abData, getWritePos(), nToWrite); + m_lWritePos += nToWrite; + nOffset += nToWrite; + nAvailable -= nToWrite; + nRemainingBytes -= nToWrite; + } + notifyAll(); + } + if (TDebug.TraceCircularBuffer) + { + TDebug.out("After write:"); + dumpInternalState(); + TDebug.out("< completed. Wrote "+nLength+" bytes"); + } + return nLength; + } + } + + + + private void dumpInternalState() + { + TDebug.out("m_lReadPos = " + m_lReadPos + " ^= "+getReadPos()); + TDebug.out("m_lWritePos = " + m_lWritePos + " ^= "+getWritePos()); + TDebug.out("availableRead() = " + availableRead()); + TDebug.out("availableWrite() = " + availableWrite()); + } + + + + public static interface Trigger + { + public void execute(); + } + + +} + + + +/*** TCircularBuffer.java ***/ + diff --git a/songdbj/org/tritonus/share/TDebug.java b/songdbj/org/tritonus/share/TDebug.java new file mode 100644 index 0000000000..5969d91a72 --- /dev/null +++ b/songdbj/org/tritonus/share/TDebug.java @@ -0,0 +1,192 @@ +/* + * TDebug.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 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; + +import java.io.PrintStream; +import java.util.StringTokenizer; +import java.security.AccessControlException; + + + +public class TDebug +{ + public static boolean SHOW_ACCESS_CONTROL_EXCEPTIONS = false; + private static final String PROPERTY_PREFIX = "tritonus."; + // The stream we output to + public static PrintStream m_printStream = System.out; + + private static String indent=""; + + // meta-general + public static boolean TraceAllExceptions = getBooleanProperty("TraceAllExceptions"); + public static boolean TraceAllWarnings = getBooleanProperty("TraceAllWarnings"); + + // general + public static boolean TraceInit = getBooleanProperty("TraceInit"); + public static boolean TraceCircularBuffer = getBooleanProperty("TraceCircularBuffer"); + public static boolean TraceService = getBooleanProperty("TraceService"); + + // sampled common implementation + public static boolean TraceAudioSystem = getBooleanProperty("TraceAudioSystem"); + public static boolean TraceAudioConfig = getBooleanProperty("TraceAudioConfig"); + public static boolean TraceAudioInputStream = getBooleanProperty("TraceAudioInputStream"); + public static boolean TraceMixerProvider = getBooleanProperty("TraceMixerProvider"); + public static boolean TraceControl = getBooleanProperty("TraceControl"); + public static boolean TraceLine = getBooleanProperty("TraceLine"); + public static boolean TraceDataLine = getBooleanProperty("TraceDataLine"); + public static boolean TraceMixer = getBooleanProperty("TraceMixer"); + public static boolean TraceSourceDataLine = getBooleanProperty("TraceSourceDataLine"); + public static boolean TraceTargetDataLine = getBooleanProperty("TraceTargetDataLine"); + public static boolean TraceClip = getBooleanProperty("TraceClip"); + public static boolean TraceAudioFileReader = getBooleanProperty("TraceAudioFileReader"); + public static boolean TraceAudioFileWriter = getBooleanProperty("TraceAudioFileWriter"); + public static boolean TraceAudioConverter = getBooleanProperty("TraceAudioConverter"); + public static boolean TraceAudioOutputStream = getBooleanProperty("TraceAudioOutputStream"); + + // sampled specific implementation + public static boolean TraceEsdNative = getBooleanProperty("TraceEsdNative"); + public static boolean TraceEsdStreamNative = getBooleanProperty("TraceEsdStreamNative"); + public static boolean TraceEsdRecordingStreamNative = getBooleanProperty("TraceEsdRecordingStreamNative"); + public static boolean TraceAlsaNative = getBooleanProperty("TraceAlsaNative"); + public static boolean TraceAlsaMixerNative = getBooleanProperty("TraceAlsaMixerNative"); + public static boolean TraceAlsaPcmNative = getBooleanProperty("TraceAlsaPcmNative"); + public static boolean TraceMixingAudioInputStream = getBooleanProperty("TraceMixingAudioInputStream"); + public static boolean TraceOggNative = getBooleanProperty("TraceOggNative"); + public static boolean TraceVorbisNative = getBooleanProperty("TraceVorbisNative"); + + // midi common implementation + public static boolean TraceMidiSystem = getBooleanProperty("TraceMidiSystem"); + public static boolean TraceMidiConfig = getBooleanProperty("TraceMidiConfig"); + public static boolean TraceMidiDeviceProvider = getBooleanProperty("TraceMidiDeviceProvider"); + public static boolean TraceSequencer = getBooleanProperty("TraceSequencer"); + public static boolean TraceMidiDevice = getBooleanProperty("TraceMidiDevice"); + + // midi specific implementation + public static boolean TraceAlsaSeq = getBooleanProperty("TraceAlsaSeq"); + public static boolean TraceAlsaSeqDetails = getBooleanProperty("TraceAlsaSeqDetails"); + public static boolean TraceAlsaSeqNative = getBooleanProperty("TraceAlsaSeqNative"); + public static boolean TracePortScan = getBooleanProperty("TracePortScan"); + public static boolean TraceAlsaMidiIn = getBooleanProperty("TraceAlsaMidiIn"); + public static boolean TraceAlsaMidiOut = getBooleanProperty("TraceAlsaMidiOut"); + public static boolean TraceAlsaMidiChannel = getBooleanProperty("TraceAlsaMidiChannel"); + + // misc + public static boolean TraceAlsaCtlNative = getBooleanProperty("TraceAlsaCtlNative"); + public static boolean TraceCdda = getBooleanProperty("TraceCdda"); + public static boolean TraceCddaNative = getBooleanProperty("TraceCddaNative"); + + + + // make this method configurable to write to file, write to stderr,... + public static void out(String strMessage) + { + if (strMessage.length()>0 && strMessage.charAt(0)=='<') { + if (indent.length()>2) { + indent=indent.substring(2); + } else { + indent=""; + } + } + String newMsg=null; + if (indent!="" && strMessage.indexOf("\n")>=0) { + newMsg=""; + StringTokenizer tokenizer=new StringTokenizer(strMessage, "\n"); + while (tokenizer.hasMoreTokens()) { + newMsg+=indent+tokenizer.nextToken()+"\n"; + } + } else { + newMsg=indent+strMessage; + } + m_printStream.println(newMsg); + if (strMessage.length()>0 && strMessage.charAt(0)=='>') { + indent+=" "; + } + } + + + + public static void out(Throwable throwable) + { + throwable.printStackTrace(m_printStream); + } + + + + public static void assertion(boolean bAssertion) + { + if (!bAssertion) + { + throw new AssertException(); + } + } + + + public static class AssertException + extends RuntimeException + { + public AssertException() + { + } + + + + public AssertException(String sMessage) + { + super(sMessage); + } + } + + + + private static boolean getBooleanProperty(String strName) + { + String strPropertyName = PROPERTY_PREFIX + strName; + String strValue = "false"; + try + { + strValue = System.getProperty(strPropertyName, "false"); + } + catch (AccessControlException e) + { + if (SHOW_ACCESS_CONTROL_EXCEPTIONS) + { + out(e); + } + } + // TDebug.out("property: " + strPropertyName + "=" + strValue); + boolean bValue = strValue.toLowerCase().equals("true"); + // TDebug.out("bValue: " + bValue); + return bValue; + } +} + + + +/*** TDebug.java ***/ + diff --git a/songdbj/org/tritonus/share/TNotifier.java b/songdbj/org/tritonus/share/TNotifier.java new file mode 100644 index 0000000000..822b30c305 --- /dev/null +++ b/songdbj/org/tritonus/share/TNotifier.java @@ -0,0 +1,140 @@ +/* + * TNotifier.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; + +import java.util.EventObject; +import java.util.Collection; +import java.util.ArrayList; +import java.util.List; +import java.util.Iterator; + +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineEvent; + + + +public class TNotifier +extends Thread +{ + public static class NotifyEntry + { + private EventObject m_event; + private List m_listeners; + + + + public NotifyEntry(EventObject event, Collection listeners) + { + m_event = event; + m_listeners = new ArrayList(listeners); + } + + + public void deliver() + { + // TDebug.out("%% TNotifier.NotifyEntry.deliver(): called."); + Iterator iterator = m_listeners.iterator(); + while (iterator.hasNext()) + { + LineListener listener = iterator.next(); + listener.update((LineEvent) m_event); + } + } + } + + + public static TNotifier notifier = null; + + static + { + notifier = new TNotifier(); + notifier.setDaemon(true); + notifier.start(); + } + + + + /** The queue of events to deliver. + * The entries are of class NotifyEntry. + */ + private List m_entries; + + + public TNotifier() + { + super("Tritonus Notifier"); + m_entries = new ArrayList(); + } + + + + public void addEntry(EventObject event, Collection listeners) + { + // TDebug.out("%% TNotifier.addEntry(): called."); + synchronized (m_entries) + { + m_entries.add(new NotifyEntry(event, listeners)); + m_entries.notifyAll(); + } + // TDebug.out("%% TNotifier.addEntry(): completed."); + } + + + public void run() + { + while (true) + { + NotifyEntry entry = null; + synchronized (m_entries) + { + while (m_entries.size() == 0) + { + try + { + m_entries.wait(); + } + catch (InterruptedException e) + { + if (TDebug.TraceAllExceptions) + { + TDebug.out(e); + } + } + } + entry = m_entries.remove(0); + } + entry.deliver(); + } + } +} + + +/*** TNotifier.java ***/ diff --git a/songdbj/org/tritonus/share/TSettings.java b/songdbj/org/tritonus/share/TSettings.java new file mode 100644 index 0000000000..ae1a315e0e --- /dev/null +++ b/songdbj/org/tritonus/share/TSettings.java @@ -0,0 +1,75 @@ +/* + * TSettings.java + * + * This file is part of Tritonus: http://www.tritonus.org/ + */ + +/* + * Copyright (c) 1999 - 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; + +import java.io.PrintStream; +import java.util.StringTokenizer; +import java.security.AccessControlException; + +import org.tritonus.share.TDebug; + + +public class TSettings +{ + public static boolean SHOW_ACCESS_CONTROL_EXCEPTIONS = false; + private static final String PROPERTY_PREFIX = "tritonus."; + + + public static boolean AlsaUsePlughw = getBooleanProperty("AlsaUsePlughw"); + + + + private static boolean getBooleanProperty(String strName) + { + String strPropertyName = PROPERTY_PREFIX + strName; + String strValue = "false"; + try + { + strValue = System.getProperty(strPropertyName, "false"); + } + catch (AccessControlException e) + { + if (SHOW_ACCESS_CONTROL_EXCEPTIONS) + { + TDebug.out(e); + } + } + // TDebug.out("property: " + strPropertyName + "=" + strValue); + boolean bValue = strValue.toLowerCase().equals("true"); + // TDebug.out("bValue: " + bValue); + return bValue; + } +} + + + +/*** TSettings.java ***/ + diff --git a/songdbj/org/tritonus/share/package.html b/songdbj/org/tritonus/share/package.html new file mode 100644 index 0000000000..200904dc60 --- /dev/null +++ b/songdbj/org/tritonus/share/package.html @@ -0,0 +1,10 @@ + + + + + + +

Misc helper classes. + The classes provided here .

+ + 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: + *

    + *
  • Encoding must always be equal for a match. + *
  • For a match, endianness must be equal if SampleSizeInBits is not + * AudioSystem.NOT_SPECIFIED and greater than 8bit in both formats.
    + * In other words: If SampleSizeInBits is AudioSystem.NOT_SPECIFIED + * in either format or both formats have a SampleSizeInBits<8, + * endianness does not matter. + *
+ * 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:
+ *

    + *
  • auAIS is an AudioInputStream (AIS) that reads from an AU file + * in 8bit pcm at 8000Hz. It does not implement FloatSampleInput. + *
  • pcmAIS1 is an AIS that reads from auAIS and converts the data + * to PCM 16bit. This stream implements FloatSampleInput, i.e. it + * can generate float audio data from the ulaw samples. + *
  • pcmAIS2 reads from pcmAIS1 and adds a reverb. + * It operates entirely on floating point samples. + *
  • The method that reads from pcmAIS2 (i.e. AudioSystem.write) does + * not handle floating point samples. + *
+ * 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: + *

    + *
  • no conversions from/to bytes need to be done during processing + *
  • the sample size in bits is irrelevant - normalized range + *
  • higher quality for processing + *
  • separated channels (easy process/remove/add channels) + *
  • potentially less copying of audio data, as processing + * the float samples is generally done in-place. The same + * instance of a FloatSampleBuffer may be used from the original data source + * to the final data sink. + *
+ *

+ * 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:
+ *

    + *
  • When the sample count is reduced, the arrays are not resized, but + * only the member variable sampleCount is reduced. A subsequent + * increase of the sample count (which will occur frequently), will check + * that and eventually reuse the existing array. + *
  • When a channel is deleted, it is not removed from memory but only + * hidden. Subsequent insertions of a channel will check whether a hidden channel + * can be reused. + *
+ * 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: + *

  • DITHER_MODE_AUTOMATIC: it is decided automatically, + * whether dithering is necessary - in general when sample size is + * decreased. + *
  • DITHER_MODE_ON: dithering will be forced + *
  • DITHER_MODE_OFF: dithering will not be done. + *
+ */ + 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