/*
* Copyright (C) 2012 Gyver
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.gyver.matrixmover.core.audio;
import edu.emory.mathcs.jtransforms.fft.DoubleFFT_1D;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.TargetDataLine;
/**
*
* @author Gyver
*/
public class AudioCapture {
private final int CHANNEL_BUFFER_LENGTH = 512;
private final int listBuffer = 2;
// set the audio format
// private final float SAMPLE_RATE = 44100F;
private final float SAMPLE_RATE = 32000F;
private final int SAMPLE_SIZE_IN_BITS = 16;
private final int CHANNELS = 2;
private final boolean SIGNED = true;
private final boolean BIG_ENDIAN = false;
private static final Object stackLock = new Object();
private byte[] buffer = null;
private float[] leftChanel = null;
private float[] rightChanel = null;
private double[] monoChanel = null;
private LinkedList<double[]> fftStack = null;
private DoubleFFT_1D fft = null;
private AudioFormat format = null;
private Mixer.Info[] mixerInfo = null;
private TargetDataLine line = null;
public AudioCapture() {
// load all supported Mixers into mixerinfo
Line.Info lineInfo = new Line.Info(TargetDataLine.class);
mixerInfo = AudioSystem.getMixerInfo();
ArrayList<Mixer.Info> supportedMixers = new ArrayList<Mixer.Info>();
for (Mixer.Info mixerInfoItem : mixerInfo) {
Mixer testMixer = AudioSystem.getMixer(mixerInfoItem);
if (testMixer.isLineSupported(lineInfo)) {
supportedMixers.add(mixerInfoItem);
System.out.println("\n" + mixerInfoItem.getName() + "\n" + mixerInfoItem.getDescription() + "\n");
}
}
mixerInfo = supportedMixers.toArray(new Mixer.Info[supportedMixers.size()]);
format = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE_IN_BITS, CHANNELS, SIGNED, BIG_ENDIAN);
buffer = new byte[CHANNEL_BUFFER_LENGTH * 4];
leftChanel = new float[CHANNEL_BUFFER_LENGTH];
rightChanel = new float[CHANNEL_BUFFER_LENGTH];
monoChanel = new double[CHANNEL_BUFFER_LENGTH];
fftStack = new LinkedList<double[]>();
fft = new DoubleFFT_1D(CHANNEL_BUFFER_LENGTH);
}
public void startAudio(Mixer.Info mixerInfo) {
AudioSystem.getMixer(mixerInfo);
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
line = null;
try {
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();
} catch (LineUnavailableException ex) {
Logger.getLogger(AudioCapture.class.getName()).log(Level.SEVERE, null, ex);
}
}
public Mixer.Info[] getAvalibalMixer() {
return mixerInfo;
}
public void captureAudio() {
if (line == null) {
return;
}
line.read(buffer, 0, buffer.length);
//now split the two signals
int n = 0;
for (int i = 0; i < leftChanel.length; n += 4, i++) {
leftChanel[i] = (((buffer[(n + 1)] << 8) + buffer[n]) / 32767.0F);
rightChanel[i] = (((buffer[(n + 3)] << 8) + buffer[(n + 2)]) / 32767.0F);
monoChanel[i] = (leftChanel[i] + rightChanel[i]) / 2.0;
}
//calculate fft
fft.realForward(monoChanel);
//extract amplitudes
double[] amplitude = computeAmplitude(monoChanel);
//stack result until its read.
addToStack(amplitude);
}
public float[] getLevel() {
double leftChan = calculateRMSLevel(leftChanel);
double rightChan = calculateRMSLevel(rightChanel);
float[] level = new float[2];
level[0] = (float) (Math.log((double) ((leftChan == 0.0) ? 0.0001 : leftChan)) / Math.log(10.0) * 20.0);
level[1] = (float) (Math.log((double) ((rightChan == 0.0) ? 0.0001 : rightChan)) / Math.log(10.0) * 20.0);
return level;
}
public float[] getFftOutput(int bands) {
// save stack to local copy and clear it
double[] a = getStack();
float[] spectrum = stripToBands(a, bands);
for (int i = 0; i < spectrum.length; i++) {
// to logarithmic scale
spectrum[i] = (float) (Math.log10(spectrum[i]) + 0.99999F);
// bring all frequencies to same level
spectrum[i] = (float) (spectrum[i] / 2.8);
}
return spectrum;
}
private double calculateRMSLevel(float[] data) {
//calculade the rms
double sum = 0;
for (int i = 0; i < data.length; i++) {
sum = sum + data[i];
}
double avg = sum / data.length;
double sumMeanSquare = 0d;
for (int j = 0; j < data.length; j++) {
sumMeanSquare = sumMeanSquare + Math.pow(data[j] - avg, 2d);
}
double avgMeanSquare = sumMeanSquare / data.length;
return Math.pow(avgMeanSquare, 0.5d) * 100d;
}
private double[] computeAmplitude(double[] a) {
double subFactorForNoise = 0.1;
double[] amplitude = new double[a.length / 2];
amplitude[amplitude.length - 1] = (double) Math.abs(a[1]);
amplitude[amplitude.length - 1] -= subFactorForNoise;
if (amplitude[amplitude.length - 1] < 0) {
amplitude[amplitude.length - 1] = 0;
}
for (int k = 1; k < amplitude.length; k++) {
amplitude[k - 1] = Math.sqrt((a[2 * k] * a[2 * k]) + (a[2 * k + 1] * a[2 * k + 1]));
amplitude[k - 1] -= subFactorForNoise;
if (amplitude[k - 1] < 0) {
amplitude[k - 1] = 0;
}
}
return amplitude;
}
private float[] stripToBands(double[] amplitude, int bands) {
double band_elong = 1;
double x = Math.pow(Math.sqrt(2 * amplitude.length - 1.75), 1 / ((double) bands * band_elong - 1));
float[] spectrum = new float[bands];
double freqPerBand[] = new double[bands];
for (int i = 0; i < bands; i++) {
freqPerBand[i] = Math.pow(x, (double) i);
if (freqPerBand[i] < 1) {
freqPerBand[i] = 1;
}
}
int aplitudeIndex = 0;
double runningSum = 0;
for (int i = 0; i < bands; i++) {
runningSum += freqPerBand[i];
while (runningSum > aplitudeIndex) {
aplitudeIndex++;
spectrum[i] += amplitude[aplitudeIndex]; //skip first amplitude
}
}
return spectrum;
}
private void addToStack(double[] amplitude) {
synchronized (stackLock) {
fftStack.add(amplitude);
if (fftStack.size() > listBuffer) {
fftStack.remove();
}
}
}
private double[] getStack() {
if (fftStack.size() == 0) {
return new double[CHANNEL_BUFFER_LENGTH];
}
synchronized (stackLock) {
double[] ret = new double[fftStack.get(0).length];
for (double[] elem : fftStack) {
for (int i = 0; i < elem.length; i++) {
ret[i] += elem[i];
}
}
for (int i = 0; i < ret.length; i++) {
ret[i] /= fftStack.size();
}
return ret;
}
}
}