/*
* MixingFloatAudioInputStream
*
* This file is part of jsresources.org
*/
/*
* Copyright (c) 2006 by Florian Bomers
* Copyright (c) 1999 - 2001 by Matthias Pfisterer
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
|<--- this code is formatted to fit into 80 columns --->|
*/
import java.io.*;
import java.util.*;
import javax.sound.sampled.*;
/*
* This is a class of Tritonus, you need to have tritonus_share.jar in the
* classpath. Get it from http://www.tritonus.org .
*/
import org.tritonus.share.sampled.FloatSampleBuffer;
/**
* Mixing of multiple AudioInputStreams to one AudioInputStream. This class
* takes a collection of AudioInputStreams and mixes them together. Being a
* subclass of AudioInputStream itself, reading from instances of this class
* behaves as if the mixdown result of the input streams is read.
* <p>
* This class uses the FloatSampleBuffer for easy conversion using normalized
* samples in the buffers.
*
* @author Florian Bomers
* @author Matthias Pfisterer
*/
public class MixingFloatAudioInputStream extends AudioInputStream {
private List audioInputStreamList;
/**
* Attenuate the stream by how many dB per mixed stream. For example, if
* attenuationPerStream is 2dB, and 3 streams are mixed together, the mixed
* stream will be attenuated by 6dB. Set to 0 to not attenuate the signal,
* which will usually give good results if only 2 streams are mixed
* together.
*/
private float attenuationPerStream = 0.1f;
/**
* The linear factor to apply to all samples (derived from
* attenuationPerStream). This is a factor in the range of 0..1 (depending
* on attenuationPerStream and the number of streams).
*/
private float attenuationFactor = 1.0f;
private FloatSampleBuffer mixBuffer;
private FloatSampleBuffer readBuffer;
/**
* A buffer for byte to float conversion.
*/
private byte[] tempBuffer;
public MixingFloatAudioInputStream(AudioFormat audioFormat,
Collection audioInputStreams) {
super(new ByteArrayInputStream(new byte[0]), audioFormat,
AudioSystem.NOT_SPECIFIED);
audioInputStreamList = new ArrayList(audioInputStreams);
// set up the static mix buffer with initially no samples. Note that
// using a static mix buffer prevents that this class can be used at
// once from different threads, but that wouldn't be useful anyway. But
// by re-using this buffer we save a lot of garbage collection.
mixBuffer = new FloatSampleBuffer(audioFormat.getChannels(), 0,
audioFormat.getSampleRate());
// ditto for the read buffer. It is used for reading samples from the
// underlying streams.
readBuffer = new FloatSampleBuffer();
// calculate the linear attenuation factor
attenuationFactor = decibel2linear(-1.0f * attenuationPerStream
* audioInputStreamList.size());
}
/**
* The maximum of the frame length of the input stream is calculated and
* returned. If at least one of the input streams has length
* <code>AudioInputStream.NOT_SPECIFIED</code>, this value is returned.
*/
public long getFrameLength() {
long lLengthInFrames = 0;
Iterator streamIterator = audioInputStreamList.iterator();
while (streamIterator.hasNext()) {
AudioInputStream stream = (AudioInputStream) streamIterator.next();
long lLength = stream.getFrameLength();
if (lLength == AudioSystem.NOT_SPECIFIED) {
return AudioSystem.NOT_SPECIFIED;
} else {
lLengthInFrames = Math.max(lLengthInFrames, lLength);
}
}
return lLengthInFrames;
}
public int read() throws IOException {
byte[] samples = new byte[1];
int ret = read(samples);
if (ret != 1) {
return -1;
}
return samples[0];
}
public int read(byte[] abData, int nOffset, int nLength) throws IOException {
// set up the mix buffer with the requested size
mixBuffer.changeSampleCount(nLength / getFormat().getFrameSize(), false);
// initialize the mixBuffer with silence
mixBuffer.makeSilence();
// remember the maximum number of samples actually mixed
int maxMixed = 0;
Iterator streamIterator = audioInputStreamList.iterator();
while (streamIterator.hasNext()) {
AudioInputStream stream = (AudioInputStream) streamIterator.next();
// calculate how many bytes we need to read from this stream
int needRead = mixBuffer.getSampleCount()
* stream.getFormat().getFrameSize();
// set up the temporary byte buffer
if (tempBuffer == null || tempBuffer.length < needRead) {
tempBuffer = new byte[needRead];
}
// read from the source stream
int bytesRead = stream.read(tempBuffer, 0, needRead);
if (bytesRead == -1) {
// end of stream: remove it from the list of streams.
streamIterator.remove();
continue;
}
// now convert this buffer to float samples
readBuffer.initFromByteArray(tempBuffer, 0, bytesRead,
stream.getFormat());
if (maxMixed < readBuffer.getSampleCount()) {
maxMixed = readBuffer.getSampleCount();
}
// the actual mixing routine: add readBuffer to mixBuffer
// can only mix together as many channels as available
int maxChannels = Math.min(mixBuffer.getChannelCount(),
readBuffer.getChannelCount());
for (int channel = 0; channel < maxChannels; channel++) {
// get the arrays of the normalized float samples
float[] readSamples = readBuffer.getChannel(channel);
float[] mixSamples = mixBuffer.getChannel(channel);
// Never use readSamples.length or mixSamples.length: the length
// of the array may be longer than the actual buffer ("lazy"
// deletion).
int maxSamples = Math.min(mixBuffer.getSampleCount(),
readBuffer.getSampleCount());
// in a loop, add each "read" sample to the mix buffer
// can only mix as many samples as available. Also apply the
// attenuation factor.
// Note1: the attenuation factor could also be applied only once
// in a separate loop after mixing all the streams together,
// saving processor time in case of many mixed streams.
// Note2: adding everything together here will not cause
// clipping, because all samples are in float format.
for (int sample = 0; sample < maxSamples; sample++) {
mixSamples[sample] += attenuationFactor
* readSamples[sample];
}
}
} // loop over streams
if (maxMixed == 0) {
// nothing written to the mixBuffer
if (audioInputStreamList.size() == 0) {
// nothing mixed, no more streams available: end of stream
return -1;
}
// nothing written, but still streams to read from
return 0;
}
// finally convert the mix Buffer to the requested byte array.
// This routine will handle clipping, i.e. if there are samples > 1.0f
// in the mix buffer, they will be clipped to 1.0f and converted to the
// specified audioFormat's sample format.
mixBuffer.convertToByteArray(0, maxMixed, abData, nOffset, getFormat());
return maxMixed * getFormat().getFrameSize();
}
/**
* calls skip() on all input streams. There is no way to assure that the
* number of bytes really skipped is the same for all input streams. Due to
* that, this method always returns the passed value. In other words: the
* return value is useless (better ideas appreciated).
*/
public long skip(long lLength) throws IOException {
Iterator streamIterator = audioInputStreamList.iterator();
while (streamIterator.hasNext()) {
AudioInputStream stream = (AudioInputStream) streamIterator.next();
stream.skip(lLength);
}
return lLength;
}
/**
* The minimum of available() of all input stream is calculated and
* returned.
*/
public int available() throws IOException {
int nAvailable = 0;
Iterator streamIterator = audioInputStreamList.iterator();
while (streamIterator.hasNext()) {
AudioInputStream stream = (AudioInputStream) streamIterator.next();
nAvailable = Math.min(nAvailable, stream.available());
}
return nAvailable;
}
public void close() throws IOException {
// TODO: should we close all streams in the list?
}
/**
* Calls mark() on all input streams.
*/
public void mark(int nReadLimit) {
Iterator streamIterator = audioInputStreamList.iterator();
while (streamIterator.hasNext()) {
AudioInputStream stream = (AudioInputStream) streamIterator.next();
stream.mark(nReadLimit);
}
}
/**
* Calls reset() on all input streams.
*/
public void reset() throws IOException {
Iterator streamIterator = audioInputStreamList.iterator();
while (streamIterator.hasNext()) {
AudioInputStream stream = (AudioInputStream) streamIterator.next();
stream.reset();
}
}
/**
* returns true if all input stream return true for markSupported().
*/
public boolean markSupported() {
Iterator streamIterator = audioInputStreamList.iterator();
while (streamIterator.hasNext()) {
AudioInputStream stream = (AudioInputStream) streamIterator.next();
if (!stream.markSupported()) {
return false;
}
}
return true;
}
public static float decibel2linear(float decibels) {
return (float) Math.pow(10.0, decibels / 20.0);
}
}
/** * MixingFloatAudioInputStream.java ** */