org.tritonus.share.sampled.FloatSampleBuffer Maven / Gradle / Ivy
/*
* FloatSampleBuffer.java
*
* This file is part of Tritonus: http://www.tritonus.org/
*/
/*
* Copyright (c) 2000-2006 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;
/**
* 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:
*
* - Channels are separated, i.e. for stereo there are 2 float arrays with
* the samples for the left and right channel
*
- All data is handled in samples, where one sample means one float value
* in each channel
*
- 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 ?
*
* - the read(byte[]) method of pcmAIS2 is called
*
- 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.
*
- It queries pcmAIS1 for the FloatSampleInput interface. As it implements
* it, pcmAIS2 calls the read(FloatSampleBuffer) method of pcmAIS1.
*
- 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.
*
- auAIS fills the buffer with the bytes.
*
- pcmAIS1 calls the
initFromByteArray
method of the float
* buffer to initialize it with the 8 bit data.
* - 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.
*
- pcmAIS2 receives the filled buffer from pcmAIS1 and does its processing
* on the buffer - it adds the reverb.
*
- 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
*
* - 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
*
- 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;
// one float array for each channel
private Object[] channels = new Object[2];
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);
}
/**
* Initialize this sample buffer to have the specified channels, sample
* count, and sample rate. If LAZY_DEFAULT is true, as much as possible will
* existing arrays be reused. Otherwise, any hidden channels are freed.
*
* @param newChannelCount
* @param newSampleCount
* @param newSampleRate
* @throws IllegalArgumentException if newChannelCount or newSampleCount are
* negative, or newSampleRate is not positive.
*/
public void init(int newChannelCount, int newSampleCount,
float newSampleRate) {
init(newChannelCount, newSampleCount, newSampleRate, LAZY_DEFAULT);
}
/**
* Initialize this sample buffer to have the specified channels, sample
* count, and sample rate. If lazy is true, as much as possible will
* existing arrays be reused. Otherwise, any hidden channels are freed.
*
* @param newChannelCount
* @param newSampleCount
* @param newSampleRate
* @param lazy
* @throws IllegalArgumentException if newChannelCount or newSampleCount are
* negative, or newSampleRate is not positive.
*/
public void init(int newChannelCount, int newSampleCount,
float newSampleRate, boolean lazy) {
if (newChannelCount < 0 || newSampleCount < 0 || newSampleRate <= 0.0f) {
throw new IllegalArgumentException(
"invalid parameters in initialization of FloatSampleBuffer.");
}
setSampleRate(newSampleRate);
if (this.sampleCount != newSampleCount
|| this.channelCount != newChannelCount) {
createChannels(newChannelCount, newSampleCount, lazy);
}
}
/**
* Verify that the specified AudioFormat can be converted to and from. If
* the format is not supported, an IllegalArgumentException is thrown.
*
* @throws IllegalArgumentException if the format is not supported
*/
public static void checkFormatSupported(AudioFormat format) {
FloatSampleTools.getFormatType(format);
}
/**
* Grow the channels array to allow at least channelCount elements. If
* !lazy, then channels will be resized to be exactly channelCount elements.
* The new elements will be null.
*
* @param newChannelCount
* @param lazy
*/
private final void grow(int newChannelCount, boolean lazy) {
if (channels.length < newChannelCount || !lazy) {
Object[] newChannels = new Object[newChannelCount];
System.arraycopy(channels, 0, newChannels, 0,
(channelCount < newChannelCount) ? channelCount
: newChannelCount);
this.channels = newChannels;
}
}
private final void createChannels(int newChannelCount, int newSampleCount,
boolean lazy) {
// shortcut
if (lazy && newChannelCount <= channelCount
&& newSampleCount <= this.sampleCount) {
setSampleCountImpl(newSampleCount);
setChannelCountImpl(newChannelCount);
return;
}
setSampleCountImpl(newSampleCount);
// grow the array, if necessary. Intentionally lazy here!
grow(newChannelCount, true);
// lazy delete of all channels. Intentionally lazy !
setChannelCountImpl(0);
for (int ch = 0; ch < newChannelCount; ch++) {
insertChannel(ch, false, lazy);
}
// if not lazy, remove hidden channels
grow(newChannelCount, lazy);
}
/**
* Resets this buffer with the audio data specified in the arguments. This
* FloatSampleBuffer's sample count will be set to
* byteCount / format.getFrameSize()
. If LAZY_DEFAULT is
* true, it will use lazy deletion.
*
* @throws IllegalArgumentException
*/
public void initFromByteArray(byte[] buffer, int offset, int byteCount,
AudioFormat format) {
initFromByteArray(buffer, offset, byteCount, format, LAZY_DEFAULT);
}
/**
* Resets this buffer with the audio data specified in the arguments. This
* FloatSampleBuffer's sample count will be set to
* byteCount / format.getFrameSize()
.
*
* @param lazy if true, then existing channels will be tried to be re-used
* to minimize garbage collection.
* @throws IllegalArgumentException
*/
public void initFromByteArray(byte[] buffer, int offset, int byteCount,
AudioFormat format, boolean lazy) {
if (offset + byteCount > buffer.length) {
throw new IllegalArgumentException(
"FloatSampleBuffer.initFromByteArray: buffer too small.");
}
int thisSampleCount = byteCount / format.getFrameSize();
init(format.getChannels(), thisSampleCount, format.getSampleRate(),
lazy);
// save format for automatic dithering mode
originalFormatType = FloatSampleTools.getFormatType(format);
FloatSampleTools.byte2float(buffer, offset, channels, 0, sampleCount,
format);
}
/**
* Resets this sample buffer with the data in source
.
*/
public void initFromFloatSampleBuffer(FloatSampleBuffer source) {
init(source.getChannelCount(), source.getSampleCount(),
source.getSampleRate());
for (int ch = 0; ch < getChannelCount(); ch++) {
System.arraycopy(source.getChannel(ch), 0, getChannel(ch), 0,
sampleCount);
}
}
/**
* Write the contents of the byte array to this buffer, overwriting existing
* data. If the byte array has fewer channels than this float buffer, only
* the first channels are written. Vice versa, if the byte buffer has more
* channels than this float buffer, only the first channels of the byte
* buffer are written to this buffer.
*
* The format and the number of samples of this float buffer are not
* changed, so if the byte array has more samples than fit into this float
* buffer, it is not expanded.
*
* @param buffer the byte buffer to write to this float buffer
* @param srcByteOffset the offset in bytes in buffer where to start reading
* @param format the audio format of the bytes in buffer
* @param dstSampleOffset the offset in samples where to start writing the
* converted float data into this float buffer
* @param aSampleCount the number of samples to write
* @return the number of samples actually written
*/
public int writeByteBuffer(byte[] buffer, int srcByteOffset,
AudioFormat format, int dstSampleOffset, int aSampleCount) {
if (dstSampleOffset + aSampleCount > getSampleCount()) {
aSampleCount = getSampleCount() - dstSampleOffset;
}
int lChannels = format.getChannels();
if (lChannels > getChannelCount()) {
lChannels = getChannelCount();
}
if (lChannels > format.getChannels()) {
lChannels = format.getChannels();
}
for (int channel = 0; channel < lChannels; channel++) {
float[] data = getChannel(channel);
FloatSampleTools.byte2floatGeneric(buffer, srcByteOffset,
format.getFrameSize(), data, dstSampleOffset, aSampleCount,
format);
srcByteOffset += format.getFrameSize() / format.getChannels();
}
return aSampleCount;
}
/**
* Deletes all channels, frees memory... This also removes hidden channels
* by lazy remove.
*/
public void reset() {
init(0, 0, 1, false);
}
/**
* Destroys any existing data and creates new channels. It also destroys
* lazy removed channels and samples. Channels will not be silenced, though.
*/
public void reset(int newChannels, int newSampleCount, float newSampleRate) {
init(newChannels, newSampleCount, newSampleRate, false);
}
// //////////////////////// conversion back to bytes ///////////////////
/**
* @return the required size of the buffer for calling
* convertToByteArray(..) is called
*/
public int getByteArrayBufferSize(AudioFormat format) {
return getByteArrayBufferSize(format, getSampleCount());
}
/**
* @param lenInSamples how many samples to be considered
* @return the required size of the buffer for the given number of samples
* for calling convertToByteArray(..)
*/
public int getByteArrayBufferSize(AudioFormat format, int lenInSamples) {
// make sure this format is supported
checkFormatSupported(format);
return format.getFrameSize() * lenInSamples;
}
/**
* Writes this sample buffer's audio data to buffer
as an
* interleaved byte array. buffer
must be large enough to
* hold all data.
*
* @throws IllegalArgumentException when buffer is too small or
* format
doesn't match
* @return number of bytes written to buffer
*/
public int convertToByteArray(byte[] buffer, int offset, AudioFormat format) {
return convertToByteArray(0, getSampleCount(), buffer, offset, format);
}
// cache for performance
private AudioFormat lastConvertToByteArrayFormat = null;
private int lastConvertToByteArrayFormatCode = 0;
/**
* Writes this sample buffer's audio data to buffer
as an
* interleaved byte array. buffer
must be large enough to
* hold all data.
*
* @param readOffset the sample offset from where samples are read from this
* FloatSampleBuffer
* @param lenInSamples how many samples are converted
* @param buffer the byte buffer written to
* @param writeOffset the byte offset in buffer
* @throws IllegalArgumentException when buffer is too small or
* format
doesn't match
* @return number of bytes written to buffer
*/
public int convertToByteArray(int readOffset, int lenInSamples,
byte[] buffer, int writeOffset, AudioFormat format) {
int byteCount = format.getFrameSize() * lenInSamples;
if (writeOffset + byteCount > buffer.length) {
throw new IllegalArgumentException(
"FloatSampleBuffer.convertToByteArray: buffer too small.");
}
if (format != lastConvertToByteArrayFormat) {
if (format.getSampleRate() != getSampleRate()) {
throw new IllegalArgumentException(
"FloatSampleBuffer.convertToByteArray: different samplerates.");
}
if (format.getChannels() != getChannelCount()) {
throw new IllegalArgumentException(
"FloatSampleBuffer.convertToByteArray: different channel count.");
}
lastConvertToByteArrayFormat = format;
lastConvertToByteArrayFormatCode = FloatSampleTools.getFormatType(format);
}
FloatSampleTools.float2byte(channels, readOffset, buffer, writeOffset,
lenInSamples, lastConvertToByteArrayFormatCode,
format.getChannels(), format.getFrameSize(),
getConvertDitherBits(lastConvertToByteArrayFormatCode));
return byteCount;
}
/**
* Creates a new byte[] buffer, fills it with the audio data, and returns
* it.
*
* @throws IllegalArgumentException when sample rate or channels do not
* match
* @see #convertToByteArray(byte[], int, AudioFormat)
*/
public byte[] convertToByteArray(AudioFormat format) {
// throws exception when sampleRate doesn't match
// creates a new byte[] buffer and returns it
byte[] res = new byte[getByteArrayBufferSize(format)];
convertToByteArray(res, 0, format);
return res;
}
// ////////////////////////////// actions /////////////////////////////////
/**
* Resizes this buffer.
*
* 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 may get
* discarded, the buffer may then contain random samples.
*/
public void changeSampleCount(int newSampleCount, boolean keepOldSamples) {
int oldSampleCount = getSampleCount();
// shortcut: if we just make this buffer smaller, just set new
// sampleCount
if (oldSampleCount >= newSampleCount) {
setSampleCountImpl(newSampleCount);
return;
}
// shortcut for one or 2 channels
if (channelCount == 1 || channelCount == 2) {
float[] ch = getChannel(0);
if (ch.length < newSampleCount) {
float[] newCh = new float[newSampleCount];
if (keepOldSamples && oldSampleCount > 0) {
// copy old samples
System.arraycopy(ch, 0, newCh, 0, oldSampleCount);
}
channels[0] = newCh;
} else if (keepOldSamples) {
// silence out excess samples (according to the specification)
for (int i = oldSampleCount; i < newSampleCount; i++) {
ch[i] = 0.0f;
}
}
if (channelCount == 2) {
ch = getChannel(1);
if (ch.length < newSampleCount) {
float[] newCh = new float[newSampleCount];
if (keepOldSamples && oldSampleCount > 0) {
// copy old samples
System.arraycopy(ch, 0, newCh, 0, oldSampleCount);
}
channels[1] = newCh;
} else if (keepOldSamples) {
// silence out excess samples (according to the
// specification)
for (int i = oldSampleCount; i < newSampleCount; i++) {
ch[i] = 0.0f;
}
}
}
setSampleCountImpl(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 = newSampleCount < oldSampleCount ? newSampleCount
: oldSampleCount;
for (int ch = 0; ch < this.channelCount; ch++) {
float[] oldSamples = (float[]) oldChannels[ch];
float[] newSamples = (float[]) channels[ch];
if (oldSamples != newSamples) {
// if this sample array was not object of lazy delete
System.arraycopy(oldSamples, 0, newSamples, 0, copyCount);
}
if (oldSampleCount < newSampleCount) {
// silence out new samples
for (int i = oldSampleCount; i < newSampleCount; i++) {
newSamples[i] = 0.0f;
}
}
}
}
}
/**
* Silence the entire audio buffer.
*/
public void makeSilence() {
makeSilence(0, getSampleCount());
}
/**
* Silence the entire buffer in the specified range on all channels.
*/
public void makeSilence(int offset, int count) {
if (offset < 0 || (count + offset) > getSampleCount() || count < 0) {
throw new IllegalArgumentException(
"offset and/or sampleCount out of bounds");
}
// silence all channels
int localChannelCount = getChannelCount();
for (int ch = 0; ch < localChannelCount; ch++) {
makeSilence(getChannel(ch), offset, count);
}
}
/**
* Silence the specified channel
*/
public void makeSilence(int channel) {
makeSilence(channel, 0, getSampleCount());
}
/**
* Silence the specified channel in the specified range
*/
public void makeSilence(int channel, int offset, int count) {
if (offset < 0 || (count + offset) > getSampleCount() || count < 0) {
throw new IllegalArgumentException(
"offset and/or sampleCount out of bounds");
}
makeSilence(getChannel(channel), offset, count);
}
private void makeSilence(float[] samples, int offset, int count) {
count += offset;
for (int i = offset; i < count; i++) {
samples[i] = 0.0f;
}
}
/**
* Fade the volume level of this buffer from the given start volume to the end volume.
* E.g. to implement a fade in, use startVol=0 and endVol=1.
*
* @param startVol the start volume as a linear factor [0..1]
* @param endVol the end volume as a linear factor [0..1]
*/
public void linearFade(float startVol, float endVol) {
linearFade(startVol, endVol, 0, getSampleCount());
}
/**
* Fade the volume level of this buffer from the given start volume to the end volume.
* The fade will start at the offset, and will have reached endVol after count samples.
* E.g. to implement a fade in, use startVol=0 and endVol=1.
*
* @param startVol the start volume as a linear factor [0..1]
* @param endVol the end volume as a linear factor [0..1]
* @param offset the offset in this buffer where to start the fade (in samples)
* @param count the number of samples to fade
*/
public void linearFade(float startVol, float endVol, int offset, int count) {
for (int channel = 0; channel < getChannelCount(); channel++) {
linearFade(channel, startVol, endVol, offset, count);
}
}
/**
* Fade the volume level of the specified channel from the given start volume to
* the end volume.
* The fade will start at the offset, and will have reached endVol after count
* samples.
* E.g. to implement a fade in, use startVol=0 and endVol=1.
*
* @param channel the channel to do the fade
* @param startVol the start volume as a linear factor [0..1]
* @param endVol the end volume as a linear factor [0..1]
* @param offset the offset in this buffer where to start the fade (in samples)
* @param count the number of samples to fade
*/
public void linearFade(int channel, float startVol, float endVol, int offset, int count) {
if (count <= 0) return;
float end = count+offset;
float inc = (endVol - startVol) / count;
float[] samples = getChannel(channel);
float curr = startVol;
for (int i = offset; i < end; i++) {
samples[i] *= curr;
curr += inc;
}
}
/**
* Add a channel to this buffer, e.g. adding a channel to a mono buffer will make it a stereo buffer.
*
* @param silent if true, the channel is explicitly silenced. Otherwise the new channel may contain random data.
*/
public void addChannel(boolean silent) {
// creates new, silent channel
insertChannel(getChannelCount(), silent);
}
/**
* Insert a (silent) channel at position index
. 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) {
// first grow the array of channels, if necessary. Intentionally lazy
grow(this.channelCount + 1, true);
int physSize = channels.length;
int virtSize = this.channelCount;
float[] newChannel = null;
if (physSize > virtSize) {
// there are hidden channels. Try to use one.
for (int ch = virtSize; ch < physSize; ch++) {
float[] thisChannel = (float[]) channels[ch];
if (thisChannel != null
&& ((lazy && thisChannel.length >= getSampleCount()) || (!lazy && thisChannel.length == getSampleCount()))) {
// we found a matching channel. Use it !
newChannel = thisChannel;
channels[ch] = null;
break;
}
}
}
if (newChannel == null) {
newChannel = new float[getSampleCount()];
}
// move channels after index
for (int i = index; i < virtSize; i++) {
channels[i + 1] = channels[i];
}
channels[index] = newChannel;
setChannelCountImpl(this.channelCount + 1);
if (silent) {
makeSilence(index);
}
// if not lazy, remove old channels
grow(this.channelCount, lazy);
}
/** 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) {
float[] toBeDeleted = (float[]) channels[channel];
// move all channels after it
for (int i = channel; i < this.channelCount - 1; i++) {
channels[i] = channels[i + 1];
}
if (!lazy) {
grow(this.channelCount - 1, true);
} else {
// if not already, insert this channel at the end
channels[this.channelCount - 1] = toBeDeleted;
}
setChannelCountImpl(channelCount - 1);
}
/**
* Copy sourceChannel's audio data to targetChannel, identified by their
* indices in the channel list. Both source and target channel have to
* exist. targetChannel will be overwritten
*/
public void copyChannel(int sourceChannel, int targetChannel) {
float[] source = getChannel(sourceChannel);
float[] target = getChannel(targetChannel);
System.arraycopy(source, 0, target, 0, getSampleCount());
}
/**
* Copy sampleCount samples from sourceChannel at position srcOffset to
* targetChannel at position targetOffset. sourceChannel and targetChannel
* are indices in the channel list. Both source and target channel have to
* exist. targetChannel will be overwritten
*/
public void copyChannel(int sourceChannel, int sourceOffset,
int targetChannel, int targetOffset, int aSampleCount) {
float[] source = getChannel(sourceChannel);
float[] target = getChannel(targetChannel);
System.arraycopy(source, sourceOffset, target, targetOffset,
aSampleCount);
}
/**
* Copies data inside all channel. When the 2 regions overlap, the behavior
* is not specified.
*/
public void copy(int sourceIndex, int destIndex, int length) {
int count = getChannelCount();
for (int i = 0; i < count; i++) {
copy(i, sourceIndex, destIndex, length);
}
}
/**
* Copies data inside a channel. When the 2 regions overlap, the behavior is
* not specified.
*/
public void copy(int channel, int sourceIndex, int destIndex, int length) {
float[] data = getChannel(channel);
int bufferCount = getSampleCount();
if (sourceIndex + length > bufferCount
|| 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 < targetChannelCount; ch++) {
addChannel(false);
copyChannel(0, ch);
}
}
/**
* Mix down of n channels to one channel.
* 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 localSampleCount = getSampleCount();
for (int ch = getChannelCount() - 1; ch > 0; ch--) {
float[] thisChannel = getChannel(ch);
for (int i = 0; i < localSampleCount; i++) {
firstChannel[i] += thisChannel[i];
}
removeChannel(ch);
}
}
/**
* Mixes source
to this buffer by adding all samples. At
* most, source
's number of samples, number of channels are
* mixed. None of the sample count, channel count or sample rate of either
* buffer are changed. In particular, the caller needs to assure that the
* sample rate of the buffers match.
*
* @param source the buffer to be mixed to this buffer
*/
public void mix(FloatSampleBuffer source) {
int count = getSampleCount();
if (count > source.getSampleCount()) {
count = source.getSampleCount();
}
int localChannelCount = getChannelCount();
if (localChannelCount > source.getChannelCount()) {
localChannelCount = source.getChannelCount();
}
for (int ch = 0; ch < localChannelCount; ch++) {
float[] thisChannel = getChannel(ch);
float[] otherChannel = source.getChannel(ch);
for (int i = 0; i < count; i++) {
thisChannel[i] += otherChannel[i];
}
}
}
/**
* Mixes source
samples to this buffer by adding the sample values.
* None of the sample count, channel count or sample rate of either
* buffer are changed. In particular, the caller needs to assure that the
* sample rate of the buffers match.
*
* This method is not error tolerant, in particular, runtime exceptions
* will be thrown if the channel counts do not match, or if the
* offsets and count exceed the buffer's capacity.
*
* @param source the source buffer from where to take samples and mix to this one
* @param sourceOffset offset in source where to start reading samples
* @param thisOffset offset in this buffer from where to start mixing samples
* @param count number of samples to mix
*/
public void mix(FloatSampleBuffer source, int sourceOffset, int thisOffset, int count) {
int localChannelCount = getChannelCount();
for (int ch = 0; ch < localChannelCount; ch++) {
float[] thisChannel = getChannel(ch);
float[] otherChannel = source.getChannel(ch);
for (int i = 0; i < count; i++) {
thisChannel[i+thisOffset] += otherChannel[i+sourceOffset];
}
}
}
/**
* Copies the contents of this buffer to the destination buffer at the
* destOffset. At most, dest
's number of samples, number of
* channels are copied. None of the sample count, channel count or sample
* rate of either buffer are changed. In particular, the caller needs to
* assure that the sample rate of the buffers match.
*
* @param dest the buffer to write to
* @param destOffset the position in dest
where to start
* writing the samples of this buffer
* @param count the number of samples to be copied
* @return the number of samples copied
*/
public int copyTo(FloatSampleBuffer dest, int destOffset, int count) {
return copyTo(0, dest, destOffset, count);
}
/**
* Copies the specified part of this buffer to the destination buffer.
* At most, dest
's number of samples, number of
* channels are copied. None of the sample count, channel count or sample
* rate of either buffer are changed. In particular, the caller needs to
* assure that the sample rate of the buffers match.
*
* @param srcOffset the start position in this buffer, where to start reading samples
* @param dest the buffer to write to
* @param destOffset the position in dest
where to start
* writing the samples
* @param count the number of samples to be copied
* @return the number of samples copied
*/
public int copyTo(int srcOffset, FloatSampleBuffer dest, int destOffset, int count) {
if (srcOffset + count > getSampleCount()) {
count = getSampleCount() - srcOffset;
}
if (count + destOffset > dest.getSampleCount()) {
count = dest.getSampleCount() - destOffset;
}
int localChannelCount = getChannelCount();
if (localChannelCount > dest.getChannelCount()) {
localChannelCount = dest.getChannelCount();
}
for (int ch = 0; ch < localChannelCount; ch++) {
System.arraycopy(getChannel(ch), srcOffset, dest.getChannel(ch),
destOffset, count);
}
return count;
}
/**
* Initializes audio data from the provided byte array. The float samples
* are written at destOffset
. 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, false);
}
// ////////////////////////////// properties /////////////////////////////
public int getChannelCount() {
return channelCount;
}
public int getSampleCount() {
return sampleCount;
}
public float getSampleRate() {
return sampleRate;
}
/**
* internal setter for channel count, just change the variable. From
* outside, use addChannel, insertChannel, removeChannel
*/
protected void setChannelCountImpl(int newChannelCount) {
if (channelCount != newChannelCount) {
channelCount = newChannelCount;
// remove cache
this.lastConvertToByteArrayFormat = null;
}
}
/**
* internal setter for sample count, just change the variable. From outside,
* use changeSampleCount
*/
protected void setSampleCountImpl(int newSampleCount) {
if (sampleCount != newSampleCount) {
sampleCount = newSampleCount;
}
}
/**
* Alias for changeSampleCount
*
* @param newSampleCount the new number of samples for this buffer
* @param keepOldSamples if true, the new buffer will keep the current
* samples in the arrays
* @see #changeSampleCount(int, boolean)
*/
public void setSampleCount(int newSampleCount, boolean keepOldSamples) {
changeSampleCount(newSampleCount, keepOldSamples);
}
/**
* 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.");
}
if (this.sampleRate != sampleRate) {
this.sampleRate = sampleRate;
// remove cache
lastConvertToByteArrayFormat = null;
}
}
/**
* Get the actual audio data of one channel.
* Modifying this array will modify the audio samples of this
* FloatSampleBuffer.
* NOTE: the returned array may be larger than sampleCount. So in any case,
* sampleCount is to be respected.
* @throws IllegalArgumentException if channel is out of bounds
*/
public float[] getChannel(int channel) {
if (channel >= this.channelCount) {
throw new IllegalArgumentException(
"FloatSampleBuffer: invalid channel number.");
}
return (float[]) channels[channel];
}
/**
* Low-level method to directly set the array for the given channel.
* Normally, you do not need this method, as you can conveniently
* resize the array with changeSampleCount()
. This method
* may be useful for advanced optimization techniques.
* @param channel the channel to replace
* @param data the audio sample array
* @return the audio data array that was replaced
* @throws IllegalArgumentException if channel is out of bounds or data is null
* @see #changeSampleCount(int, boolean)
*/
public float[] setRawChannel(int channel, float[] data) {
if (data == null) {
throw new IllegalArgumentException(
"cannot set a channel to a null array");
}
float[] ret = getChannel(channel);
channels[channel] = data;
return ret;
}
/**
* Get an array of all channels.
* @return all channels as array
*/
public Object[] getAllChannels() {
Object[] res = new Object[getChannelCount()];
for (int ch = 0; ch < getChannelCount(); ch++) {
res[ch] = getChannel(ch);
}
return res;
}
/**
* Set the number of bits for dithering. Typically, a value between 0.2 and
* 0.9 gives best results.
*
* Note: 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;
}
}