/*
* Copyright 2007 Sun Microsystems, Inc.
*
* This file is part of jVoiceBridge.
*
* jVoiceBridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation and distributed hereunder
* to you.
*
* jVoiceBridge 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, see <http://www.gnu.org/licenses/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied this
* code.
*/
package com.sun.voip;
public class LowPassFilter {
private String id;
private int channels;
private static int nAvg = 3;
private static double lpfVolumeAdjustment = .05D;
private int lpfCount;
private long totalLpfTime;
private int[] previousSamples;
public LowPassFilter(String id, int sampleRate, int channels) {
this.id = id;
this.channels = channels;
}
public void reset() {
previousSamples = null;
}
public void printStatistics() {
if (lpfCount == 0) {
return;
}
String s = "";
if (id != null) {
s += id + ": ";
}
double avg = (double) totalLpfTime / lpfCount;
avg = (avg / CurrentTime.getTimeUnitsPerSecond()) * 1000;
Logger.writeFile(s + avg + "ms avg low pass filter time");
}
private static final int MAX_NAVG = 50;
public static void setNAvg(int nAvg) {
if (nAvg > MAX_NAVG) {
nAvg = MAX_NAVG;
}
LowPassFilter.nAvg = nAvg;
Logger.println("nAvg set to " + nAvg);
double x = Math.exp(-2 * Math.PI * (nAvg / 500D));
a0 = 1 - x;
b1 = x;
Logger.println("SP: cutoff = " + (nAvg / 500D) + " a0 " + a0
+ " b1 " + b1);
}
public static int getNAvg() {
return nAvg;
}
public static void setLpfVolumeAdjustment(double lpfVolumeAdjustment) {
LowPassFilter.lpfVolumeAdjustment = lpfVolumeAdjustment;
}
public static double getLpfVolumeAdjustment() {
return lpfVolumeAdjustment;
}
/*
* Low pass filter using moving average
*/
public byte[] lpf(byte[] inSamples) {
int length = inSamples.length & ~1; // round down
int[] ints = new int[length / 2];
AudioConversion.bytesToInts(inSamples, 0, length, ints);
ints = lpf(ints);
byte[] bytes = new byte[ints.length * 2];
AudioConversion.intsToBytes(ints, bytes, 0);
return bytes;
}
public int[] lpf(int[] inSamples) {
long start = CurrentTime.getTime();
if (nAvg > inSamples.length / channels) {
nAvg = inSamples.length / channels;
}
if (nAvg < 2) {
return inSamples;
}
if (previousSamples == null ||
previousSamples.length != (nAvg -1) * channels) {
previousSamples = new int[(nAvg - 1) * channels];
}
int[] outSamples = new int[inSamples.length];
/*
* Save next set of previous samples
*/
int[] p = new int[previousSamples.length];
System.arraycopy(inSamples, inSamples.length - p.length,
p, 0, p.length);
if (debug) {
Util.dump("previous", previousSamples, 0, previousSamples.length);
Util.dump("inSamples", inSamples, 0, inSamples.length);
}
/*
* Calculate volume level adjustment. Averaging reduces the
* volume level and we try to compensate for that.
*/
double volumeAdjustment = 1.0D + (lpfVolumeAdjustment * nAvg);
/*
* Calculate sum of nAvg samples using the previous samples
* plus the first sample of inSamples.
*/
int sum1 = 0;
int sum2 = 0;
int ix = 0;
for (int i = 0; i < nAvg - 1; i++) {
sum1 += previousSamples[ix];
ix++;
if (channels == 2) {
sum2 += previousSamples[ix];
ix++;
}
}
if (debug) {
Logger.println("First sum " + sum1);
}
/*
* Handle first nAvg - 1 samples special because
* they involve previous samples.
* After that, just the inSamples are used.
*/
ix = 0;
for (int i = 0; i < nAvg - 1; i++) {
sum1 += inSamples[ix];
if (debug) {
Logger.println("Adding ix " + ix + " " + inSamples[ix]
+ " sum1 " + sum1);
}
/*
* Set sample to be the average of the last nAvg samples.
*/
outSamples[ix] = AudioConversion.clip(
(int) (volumeAdjustment * sum1 / nAvg));
sum1 -= previousSamples[ix];
if (debug) {
Logger.println("subtracting previous ix " + ix + " "
+ previousSamples[ix] + " sum1 " + sum1
+ " avg " + (sum1 / nAvg));
}
ix++;
if (channels == 2) {
sum2 += inSamples[ix];
outSamples[ix] = AudioConversion.clip(
(int) (volumeAdjustment * sum2 / nAvg));
sum2 -= previousSamples[ix];
ix++;
}
}
int indexToSubtract = 0;
while (ix < inSamples.length) {
sum1 += inSamples[ix];
if (debug) {
Logger.println("Adding ix " + ix + " " + inSamples[ix]
+ " sum1 " + sum1);
}
/*
* Set sample to be the average of the last nAvg samples.
*/
outSamples[ix] = AudioConversion.clip(
(int) (volumeAdjustment * sum1 / nAvg));
sum1 -= inSamples[indexToSubtract];
if (debug) {
Logger.println("subtracting ix " + ix + " "
+ inSamples[ix] + " sum1 " + sum1
+ " avg " + (sum1 / nAvg));
}
ix++;
indexToSubtract++;
if (channels == 2) {
sum2 += inSamples[ix];
outSamples[ix] = AudioConversion.clip(
(int) (volumeAdjustment * sum2 / nAvg));
sum2 -= inSamples[indexToSubtract];
ix++;
indexToSubtract++;
}
}
//verify(inSamples, outSamples);
previousSamples = p;
totalLpfTime += (CurrentTime.getTime() - start);
lpfCount++;
if (debug) {
Util.dump("outSamples", outSamples, 0, outSamples.length);
}
return outSamples;
}
/*
* Low pass filter obtained from Ole Lond, lond@GET2NET.DK in
* a public email sent to JAVASOUND-INTEREST on 5/16/2006.
* An implementation of http://www.musicdsp.org/archive.php?classid=3#185
*/
private static double c;
private static double r;
private static double x;
public static void setX(double x) {
setParams(x, y);
}
private static double y;
public static void setY(double y) {
setParams(x, y);
}
public static void setParams(double x, double y) {
LowPassFilter.x = x;
LowPassFilter.y = y;
if (x == 0 && y == 0) {
Logger.println("Disabling Low Pass RC filter");
return;
}
double xx = Math.exp(-2 * Math.PI * (x / 2));
a0 = 1 - xx;
b1 = xx;
Logger.println("SP: cutoff = " + (x / 2) + " a0 " + a0
+ " b1 " + b1);
int cutoff = (int) (x * 127);
int resonance = (int) (y * 127);
c = Math.pow(0.5, (128 - cutoff) / 16.0);
r = Math.pow(0.5, (resonance + 24) / 16.0);
Logger.println("setParams: x = " + x + " y = " + y
+ " cutoff = " + cutoff + " resonance = " + resonance
+ " c = " + c + " r = " + r);
}
public byte[] lpfRC(byte[] inSamples) {
if (x == 0 && y == 0) {
return inSamples;
}
int length = inSamples.length & ~1; // round down
int[] ints = new int[length / 2];
AudioConversion.bytesToInts(inSamples, 0, length, ints);
ints = lpfRC(ints);
byte[] bytes = new byte[ints.length * 2];
AudioConversion.intsToBytes(ints, bytes, 0);
return bytes;
}
public int[] lpfRC(int[] inSamples) {
if (x == 0 && y == 0) {
return inSamples;
}
int[] outSamples = new int[inSamples.length];
double v0 = 0;
double v1 = 0;
double v2 = 0;
double v3 = 0;
int i = 0;
while (i < inSamples.length) {
v0 = (double)
((1 - r * c) * v0 - c * v1 + c * ((double)inSamples[i]));
v1 = (double) ((1 - r * c) * v1 + c * v0);
outSamples[i] = (int) (v1);
i++;
if (channels == 2) {
v2 = (double)
((1 - r * c) * v2 - c * v3 + c * ((double)inSamples[i]));
v3 = (double) ((1 - r * c) * v3 + c * v2);
outSamples[i] = (int) (v3);
i++;
}
}
return outSamples;
}
private static double a0;
private static double b1;
public byte[] lpfSP(byte[] inSamples) {
if (a0 == 0) {
return inSamples;
}
int length = inSamples.length & ~1; // round down
int[] ints = new int[length / 2];
AudioConversion.bytesToInts(inSamples, 0, length, ints);
ints = lpfSP(ints);
byte[] bytes = new byte[ints.length * 2];
AudioConversion.intsToBytes(ints, bytes, 0);
return bytes;
}
int[] lastOutSamples;
public int[] lpfSP(int[] inSamples) {
if (a0 == 0) {
return inSamples;
}
if (Logger.logLevel == -123) {
Logger.println("a0 " + a0 + " b1 " + b1);
Logger.logLevel = 3;
}
int[] outSamples = new int[inSamples.length];
if (lastOutSamples != null) {
outSamples[0] = (int) (
(a0 * inSamples[0]) + (b1 * lastOutSamples[0]));
if (channels == 2) {
outSamples[1] = (int) (
(a0 * inSamples[1]) + (b1 * lastOutSamples[1]));
}
} else {
lastOutSamples = new int[channels];
outSamples[0] = inSamples[0];
if (channels == 2) {
outSamples[1] = inSamples[1];
}
}
int i = channels;
while (i < inSamples.length) {
outSamples[i] = (int) (
(a0 * inSamples[i]) + (b1 * outSamples[i - channels]));
i++;
if (channels == 2) {
outSamples[i] = (int)
((a0 * inSamples[i])
+ (b1 * outSamples[i - channels]));
i++;
}
}
lastOutSamples[0] = outSamples[inSamples.length - channels];
if (channels == 2) {
lastOutSamples[1] = outSamples[inSamples.length - channels + 1];
}
return outSamples;
}
private void verify(int[] inSamples, int[] outSamples) {
for (int i = nAvg; i < inSamples.length - nAvg; i++) {
int sum = 0;
for (int j = 0; j < nAvg; j++) {
sum += inSamples[i + j];
}
if ((sum / nAvg) != outSamples[i + nAvg - 1]) {
Util.dump("bad avg at " + i, inSamples, 0, 8);
Util.dump("out ", outSamples, 0, 8);
break;
}
}
}
/*
* sin(x) / x
*/
private static final double[] filterKernel = {
0D,
0.10929240478705181D,
0.23387232094715982D,
0.3678830105717742D,
0.5045511524271046D,
0.6366197723675814D,
0.756826728640657D,
0.8583936913341398D,
0.935489283788639D,
0.983631643083466D,
1D,
0.983631643083466D,
0.935489283788639D,
0.8583936913341398D,
0.756826728640657D,
0.6366197723675814D,
0.5045511524271046D,
0.3678830105717742D,
0.23387232094715982D,
0.10929240478705181D,
0D,
};
public int[] lpfxxx(int[] inSamples) {
long start = CurrentTime.getTime();
if (previousSamples == null ||
previousSamples.length != (nAvg -1) * channels) {
previousSamples = new int[(nAvg - 1) * channels];
}
/*
* Save next set of previous samples
*/
int[] p = new int[previousSamples.length];
System.arraycopy(inSamples, inSamples.length - p.length,
p, 0, p.length);
int[] in = new int[previousSamples.length + inSamples.length];
for (int i = 0; i < previousSamples.length; i++) {
in[i] = previousSamples[i];
}
for (int i = 0; i < inSamples.length; i++) {
in[previousSamples.length + i] = inSamples[i];
}
previousSamples = p;
int[] out = new int[in.length + filterKernel.length - 1];
/*
* Do the convolution.
*/
for (int i = 0; i < in.length; i++) {
for (int j = 0; j < filterKernel.length; j++) {
out[i + j] += (int) (in[i] * filterKernel[j]);
}
}
int[] outSamples = new int[inSamples.length];
System.arraycopy(out, ((filterKernel.length - 1) / 2),
outSamples, 0, inSamples.length);
totalLpfTime += (CurrentTime.getTime() - start);
lpfCount++;
if (debug) {
Util.dump("outSamples", outSamples, 0, outSamples.length);
}
return outSamples;
}
private static boolean debug = false;
public static void main(String[] args) {
LowPassFilter lpf = new LowPassFilter("test", 8000, 1);
debug = true;
byte[] buf = new byte[320];
for (int i = 0; i < buf.length; i += 2) {
buf[i] = (byte) (((i / 2) >> 8) & 0xff);
buf[i + 1] = (byte) ((i / 2) & 0xff);
}
LowPassFilter.setNAvg(3);
LowPassFilter.setLpfVolumeAdjustment(0.0);
lpf.lpf(buf);
}
}