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 --- .../tritonus/share/sampled/FloatSampleBuffer.java | 734 +++++++++++++++++++++ 1 file changed, 734 insertions(+) create mode 100644 songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java (limited to 'songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java') diff --git a/songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java b/songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java new file mode 100644 index 0000000000..d1fe534613 --- /dev/null +++ b/songdbj/org/tritonus/share/sampled/FloatSampleBuffer.java @@ -0,0 +1,734 @@ +/* + * FloatSampleBuffer.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.ArrayList; +import java.util.Iterator; +import java.util.Random; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.spi.AudioFileWriter; + +import org.tritonus.share.TDebug; + +/** + * A class for small buffers of samples in linear, 32-bit + * floating point format. + *

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

+ * Specifications: + *

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

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

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

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

+ *

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

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

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

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

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

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

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

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

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

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

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

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

+ */ + public void setDitherMode(int mode) { + if (mode!=DITHER_MODE_AUTOMATIC + && mode!=DITHER_MODE_ON + && mode!=DITHER_MODE_OFF) { + throw new IllegalArgumentException("Illegal DitherMode"); + } + this.ditherMode=mode; + } + + public int getDitherMode() { + return ditherMode; + } + + + /** + * @return the ditherBits parameter for the float2byte functions + */ + protected float getConvertDitherBits(int newFormatType) { + // let's see whether dithering is necessary + boolean doDither = false; + switch (ditherMode) { + case DITHER_MODE_AUTOMATIC: + doDither=(originalFormatType & FloatSampleTools.F_SAMPLE_WIDTH_MASK)> + (newFormatType & FloatSampleTools.F_SAMPLE_WIDTH_MASK); + break; + case DITHER_MODE_ON: + doDither=true; + break; + case DITHER_MODE_OFF: + doDither=false; + break; + } + return doDither?ditherBits:0.0f; + } +} -- cgit v1.2.3