/**
* Xtreme Media Player a cross-platform media player.
* Copyright (C) 2005-2010 Besmir Beqiri
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package xtrememp.player.audio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import javax.sound.sampled.AudioInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tritonus.share.sampled.convert.TAsynchronousFilteredAudioInputStream;
/**
* Based on original work of Martin Holtzer, licensed under the GPL.
*
* @author alexs
* @author Besmir Beqiri
*/
public class EqAudioInputStream extends TAsynchronousFilteredAudioInputStream {
private final Logger logger = LoggerFactory.getLogger(EqAudioInputStream.class);
//protected static final int BLOCKSIZE = 128;
//protected static final int BLOCKSIZE = 10*1024;
protected static final int BLOCKSIZE = 17640;
/**
* The maximum upper frequency. The highest band would have an even higher
* upper edge frequency, but it gets reduced to this value.
*/
private static final int MAX_UPPER_FREQ = 19000;
/**
* Number of frequency bands.
*/
private static final int BAND_COUNT = 10;
/**
* Number of filter stages for each band. Each stage resembles one
* second-order stage of the low-shelving prototype, which becomes a
* fourth-order stage after frequency-shifting. The resulting filter order
* per band is thus 4 * STAGE_COUNT.
*/
private static final int STAGE_COUNT = 2;
/**
* The sampling rate used to denormalize frequencies from radian/s to Hz. If
* the audio data uses another sampling-rate, the labels in the UI will be
* wrong.
*/
private static final int SAMPLING_RATE = 44100;
/**
* Center frequency of the lowest band.
*/
private static final double FIRST_CENTER_FREQUENCY = 30;
/**
* Logarithm of the highest normalized frequency to plot.
*/
private static final float UPPER_NORM_FREQ_LOG = 0.4971f;
/**
* Logarithm of the lowest normalized frequency to plot.
*/
private static final float LOWER_NORM_FREQ_LOG = -2.5452f;
/**
* Value of {@link #K} for unity gain.
*/
private double[] KBase = new double[BAND_COUNT];
/**
* Band-width and gain dependent filter coefficient.
*/
private double[] K = new double[BAND_COUNT];
/**
* Gain dependent filter coefficient.
*/
private double[] V = new double[BAND_COUNT];
/**
* Auxiliary filter coefficient.
*/
private double[][] a0recip = new double[BAND_COUNT][STAGE_COUNT];
/**
* Stage dependent filter coefficient.
*/
private double[] c = new double[STAGE_COUNT];
/**
* Center frequency dependent coefficient.
*/
private double[] c0 = new double[BAND_COUNT];
/**
* The filter states. In each stage, states 1 and 3 hold the state of delay
* inside the all-passes and states 0 and 2 resemble the additional delay at
* the all-pass input.
*/
private double[][][] filterstates = new double[BAND_COUNT][STAGE_COUNT][4];
private AudioInputStream sourceStream;
/**
* Audio input buffer.
*/
private ByteBuffer audioDataIn;
/**
* Audio output buffer.
*/
private ByteBuffer audioDataOut;
/**
* The {@link #audioDataIn} buffer accessed as <code>short</code>s.
*/
private ShortBuffer shortDataIn;
/**
* The {@link #audioDataOut} buffer accessed as <code>short</code>s.
*/
private ShortBuffer shortDataOut;
/**
* Number of input channels.
*/
private int nChannels;
// private double skipped = 0;
// private double totalTime = 0;
/**
* The sliders to control the per-band gains.
*/
//private JSlider[] gainSliders = new JSlider[BAND_COUNT];
/**
* The plot of the resulting magnitude transfer function.
*/
//private PlotDisplay transferPlot = new PlotDisplay();
public EqAudioInputStream(AudioInputStream sourceStream) {
super(sourceStream.getFormat(), sourceStream.getFrameLength());
this.sourceStream = sourceStream;
init();
}
@Override
public void close() throws IOException {
super.close();
sourceStream.close();
}
// public static double bytesToSeconds(int bytes, AudioFormat format) {
// int frame = format.getSampleSizeInBits() / 8;
// return (double) bytes / ((double) format.getSampleRate() * (double) format.getChannels() * (double) frame);
// }
@Override
public long skip(long n) throws IOException {
//long sk = super.skip(n);
// need to skip this out of the source
long sk = sourceStream.skip(n);
// skipped += bytesToSeconds((int) sk, sourceStream.getFormat());
return sk;
}
@Override
public void execute() {
//ALEX!!!
double[] inSamples = new double[nChannels * BLOCKSIZE / 2];
double[] outSamples = new double[nChannels * BLOCKSIZE / 2];
int sampleCount;
int nBytesRead = 0;
try {
nBytesRead = sourceStream.read(audioDataIn.array(), 0,
BLOCKSIZE * nChannels);
} catch (Exception e) {
e.printStackTrace();
}
if (nBytesRead == -1) {
getCircularBuffer().close();
return;
}
byte[] trimBuffer = audioDataIn.array();
if (nBytesRead < trimBuffer.length) {
trimBuffer = new byte[nBytesRead];
System.arraycopy(audioDataIn.array(), 0, trimBuffer, 0, nBytesRead);
}
// totalTime += bytesToSeconds(nBytesRead, sourceStream.getFormat());
if (nChannels > 1) {
/*
sampleCount = nBytesRead / 4;
for (int n = 0; n < nBytesRead / 2 - 1; n += 2) {
inSamples[n / 2] = ((double) shortDataIn.get(n) + (double) shortDataIn.get(n + 1)) / 2 / 32768;
}*/
sampleCount = nBytesRead / 2;
for (int n = 0; n < nBytesRead / 2; n++) {
inSamples[n] = (double) shortDataIn.get(n) / 32768;
}
} else {
sampleCount = nBytesRead / 2;
for (int n = 0; n < nBytesRead / 2; n++) {
inSamples[n] = (double) shortDataIn.get(n) / 32768;
}
}
for (int n = 0; n < sampleCount; n++) {
double outValue;
outSamples[n] = processSample(inSamples[n]);
outValue = outSamples[n] * 32768;
if (outValue > 32767) {
outValue = 32767;
}
if (outValue < -32768) {
outValue = -32768;
}
shortDataOut.put(n, (short) outValue);
}
// write the processed block if samples are there
if (nBytesRead >= 0) {
getCircularBuffer().write(audioDataOut.array(), 0, sampleCount * 2);
}
}
/**
* The actual filtering algorithm.
*
* @param u The input sample.
* @return The resulting output sample.
*/
protected double processSample(double u) {
double y = u;
for (int band = 0; band < BAND_COUNT; band++) {
for (int stage = 0; stage < STAGE_COUNT; stage++) {
double x4 = -c0[band] * (filterstates[band][stage][0] - filterstates[band][stage][1]);
double x6 = filterstates[band][stage][1] + x4;
filterstates[band][stage][1] = filterstates[band][stage][0] + x4;
double x8 = -c0[band] * (filterstates[band][stage][2] - filterstates[band][stage][3]);
double x7 = filterstates[band][stage][3] + x8;
filterstates[band][stage][3] = filterstates[band][stage][2] + x8;
double x3 = 2 * x6;
double x2 = x7 + x3;
double x1 = a0recip[band][stage] * (K[band] * u - (x7 - x3 + K[band] * (-2 * c[stage] * x7 + K[band] * x2)));
double x5 = K[band] * (x1 + x2);
filterstates[band][stage][0] = -x1;
filterstates[band][stage][2] = -x6;
y = V[band] * (V[band] * x5 + 2 * (x5 - c[stage] * (x7 - x1))) + u;
u = y;
}
}
return y;
}
/**
* Initializes the filter coefficients and UI components.
*/
public void init() {
nChannels = getFormat().getChannels();
audioDataIn = ByteBuffer.allocate(BLOCKSIZE * nChannels);
audioDataIn.order(ByteOrder.LITTLE_ENDIAN);
shortDataIn = audioDataIn.asShortBuffer();
//audioDataOut = ByteBuffer.allocate(BLOCKSIZE);
// ALEX!!!
audioDataOut = ByteBuffer.allocate(BLOCKSIZE * nChannels);
audioDataOut.order(ByteOrder.LITTLE_ENDIAN);
shortDataOut = audioDataOut.asShortBuffer();
for (int stage = 0; stage < STAGE_COUNT; stage++) {
c[stage] = Math.cos((.5 - (2. * stage + 1) / (4 * STAGE_COUNT)) * Math.PI);
}
for (int band = 0; band < BAND_COUNT; band++) {
double fC = FIRST_CENTER_FREQUENCY * Math.pow(2, band);
double fL = fC / Math.sqrt(2);
double fU = fC * Math.sqrt(2);
if (fU > MAX_UPPER_FREQ) {
fU = MAX_UPPER_FREQ;
}
double fB = fU - fL;
double wB = 2 * Math.PI / SAMPLING_RATE * fB;
double wU = 2 * Math.PI / SAMPLING_RATE * fU;
double wL = 2 * Math.PI / SAMPLING_RATE * fL;
double wM = 2 * Math.atan(Math.sqrt(Math.tan(wU / 2) * Math.tan(wL / 2)));
KBase[band] = Math.tan(wB / 2);
c0[band] = Math.cos(wM);
setGain(band, 1.0);
}
}
public void setGain(int band, int gain) {
if (gain > 12) {
gain = 12;
} else if (gain < -12) {
gain = -12;
}
setGain(band, Math.pow(10, gain / 20.));
}
/**
* Sets the gain for the given band.
*
* @param band The band to set the gain for.
* @param gain The multiplicative (i.e. not dB) gain.
*/
protected void setGain(int band, double gain) {
K[band] = Math.pow(gain, -1. / (4 * STAGE_COUNT)) * KBase[band];
V[band] = Math.sqrt(gain) - 1;
for (int stage = 0; stage < STAGE_COUNT; stage++) {
a0recip[band][stage] = 1 / (1 + 2 * K[band] * c[stage] + K[band] * K[band]);
}
}
}