rhythmbullet/core/src/zero1hd/polyjet/audio/AudioAnalyzer.java
2017-04-18 18:25:45 -05:00

292 lines
8.3 KiB
Java
Executable File

package zero1hd.polyjet.audio;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.FloatArray;
import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D;
public class AudioAnalyzer {
public boolean containsData;
FloatFFT_1D fft;
public AudioData audiofile;
float[] audioPCM;
float[] spectrum;
float[] lastSpectrum;
float[] fftData;
Thread analyticalThread;
Runnable analysisAlgorithm;
int bassBinBegin;
int bassBinEnd;
private FloatArray bassSpectralFlux = new FloatArray();
private FloatArray bassThreshold = new FloatArray();
private FloatArray bassPrunned = new FloatArray();
private FloatArray bassPeaks = new FloatArray();
private float bassMaxValue;
int UMBinBegin;
int UMBinEnd;
private FloatArray UMSpectralFlux = new FloatArray();
private FloatArray UMThreshold = new FloatArray();
private FloatArray UMPrunned = new FloatArray();
private FloatArray UMPeaks = new FloatArray();
private float UMMaxValue;
private FloatArray overlappedBeats = new FloatArray();
float bassThresholdMultiplier;
float UMThresholdMultiplier;
int UMThresholdCalcRange;
int bassThresholdCalcRange;
public AudioAnalyzer() {
analysisAlgorithm = new Runnable() {
@Override
public void run() {
bassThresholdMultiplier = 1.5f;
UMThresholdMultiplier = 2f;
bassBinBegin = binCalculator(60);
bassBinEnd = binCalculator(800);
UMBinBegin = binCalculator(1500);
UMBinEnd = binCalculator(3000);
UMThresholdCalcRange = thresholdRangeCalc(0.5f);
bassThresholdCalcRange = thresholdRangeCalc(0.7f);
Gdx.app.debug("Read freq", String.valueOf(audiofile.getFormat().getSampleRate()));
Gdx.app.debug("Using following bin ranges", "\nBass freq begin: " + bassBinBegin + "\nBass freq end: " + bassBinEnd + "\nMain freq begin: " + UMBinBegin + "\nMain freq end: " + UMBinEnd);
Gdx.app.debug("Threshold Calc Range UM", String.valueOf(UMThresholdCalcRange));
Gdx.app.debug("Threshold Calc Range Bass", String.valueOf(bassThresholdCalcRange));
fft = new FloatFFT_1D(audiofile.getReadWindowSize());
while (audiofile.readSamples(audioPCM) > 0) {
fft.realForward(audioPCM);
System.arraycopy(spectrum, 0, lastSpectrum, 0, spectrum.length);
System.arraycopy(audioPCM, 0, spectrum, 0, spectrum.length);
float fluxVal = 0;
//bass detection
fluxVal = 0;
for (int i = bassBinBegin; i < bassBinEnd; i++) {
fluxVal += ((int) (spectrum[i] - lastSpectrum[i])) > 0
? (int) (spectrum[i] - lastSpectrum[i]) : 0;
}
bassSpectralFlux.add(fluxVal);
//main detection
fluxVal = 0;
for (int i = UMBinBegin; i < UMBinEnd; i++) {
fluxVal += ((int) (spectrum[i] - lastSpectrum[i])) > 0
? (int) (spectrum[i] - lastSpectrum[i]) : 0;
}
UMSpectralFlux.add(fluxVal);
}
Gdx.app.debug("Audio Analyzer", "Done getting spectral flux.");
//threshold calculation
for (int i = 0; i < UMSpectralFlux.size; i++) {
int UMStart = Math.max(0, i - UMThresholdCalcRange/2);
int UMEnd = Math.min(UMSpectralFlux.size - 1, i + UMThresholdCalcRange/2);
int bassStart = Math.max(0, i - bassThresholdCalcRange/2);
int bassEnd = Math.min(UMSpectralFlux.size - 1, i + bassThresholdCalcRange/2);
float average = 0;
for (int j = bassStart; j <= bassEnd; j++) {
average += bassSpectralFlux.get(j);
}
average /= (bassEnd - bassStart);
bassThreshold.add(average * bassThresholdMultiplier);
average = 0;
for (int j = UMStart; j <= UMEnd; j++) {
average+= UMSpectralFlux.get(j);
}
average /= (UMEnd - UMStart);
UMThreshold.add(average*UMThresholdMultiplier);
}
Gdx.app.debug("Audio Analyzer", "Threshold calculated.");
//pruning data
float prunnedCurrentVal;
for (int i = 0; i < UMSpectralFlux.size; i++) {
prunnedCurrentVal = bassSpectralFlux.get(i) - bassThreshold.get(i);
if (prunnedCurrentVal >= 0) {
bassPrunned.add(prunnedCurrentVal);
} else {
bassPrunned.add(0);
}
prunnedCurrentVal = UMSpectralFlux.get(i) - UMThreshold.get(i);
if (prunnedCurrentVal >= 0 ) {
UMPrunned.add(prunnedCurrentVal);
} else {
UMPrunned.add(0);
}
}
Gdx.app.debug("Audio Analyzer", "Data prunned.");
//peak detection
for (int i = 0; i < UMPrunned.size-1; i++) {
bassPeaks.add((bassPrunned.get(i) > bassPrunned.get(i+1) ? bassPrunned.get(i) : 0));
if (bassPeaks.get(i) > bassMaxValue) {
bassMaxValue = bassPeaks.get(i);
}
UMPeaks.add((UMPrunned.get(i) > UMPrunned.get(i+1) ? UMPrunned.get(i) : 0));
if (UMPeaks.get(i) > UMMaxValue) {
UMMaxValue = UMPeaks.get(i);
}
}
Gdx.app.debug("Audio Analyzer", "Found all peaks.");
//overlapping beats
for (int i = 0; i < UMPeaks.size; i++) {
if (bassPeaks.get(i) != 0 && UMPeaks.get(i) != 0) {
overlappedBeats.add(bassPeaks.get(i)+UMPeaks.get(i)/2);
} else {
overlappedBeats.add(0);
}
}
Gdx.app.debug("Audio Analyzer", "overlapped beats checked.");
shrinkData();
containsData = true;
}
};
}
public void resetVars() {
bassSpectralFlux.clear();
bassThreshold.clear();
bassPrunned.clear();
bassPeaks.clear();
UMSpectralFlux.clear();
UMThreshold.clear();
UMPrunned.clear();
UMPeaks.clear();
overlappedBeats.clear();
containsData = false;
}
public void shrinkData() {
bassSpectralFlux.shrink();
bassThreshold.shrink();
bassPrunned.shrink();
bassPeaks.shrink();
UMSpectralFlux.shrink();
UMThreshold.shrink();
UMPrunned.shrink();
UMPeaks.shrink();
overlappedBeats.shrink();
}
public void startAnalyticalThread(final AudioData audiofile) {
fftData = new float[audiofile.getReadWindowSize()];
spectrum = new float[(audiofile.getReadWindowSize()/2)+1];
lastSpectrum = new float[(audiofile.getReadWindowSize()/2)+1];
this.audiofile = audiofile;
analyticalThread = new Thread(analysisAlgorithm);
analyticalThread.start();
}
public float[][] splitChannelData(float[] samples, int channelCount) {
int byteIndex = 0;
float[][] splitSamples = new float[channelCount][samples.length / (channelCount * 2)];
for (int currentByte = 0; currentByte < samples.length;) {
for (int channel = 0; channel < channelCount; channel++) {
int low = (int) samples[currentByte];
currentByte++;
int high = (int) samples[currentByte];
currentByte++;
splitSamples[channel][byteIndex] = (high << 8) + (low & 0x00ff);
}
byteIndex++;
}
return splitSamples;
}
public float[] convertPCM(float[] leftChannel, float[] rightChannel) {
float[] result = new float[leftChannel.length];
for (int i = 0; i < leftChannel.length; i++)
result[i] = (leftChannel[i] + rightChannel[i]) / 2 / 32768.0f;
return result;
}
public float[] byteToFloat(byte[] byteArray) {
float[] floatArray = new float[byteArray.length];
for (int i = 0; i < byteArray.length; i++) {
floatArray[i] = byteArray[i];
}
return floatArray;
}
public FloatArray getBassPeaks() {
return bassPeaks;
}
public FloatArray getUMPeaks() {
return UMPeaks;
}
private int binCalculator(int frequency) {
float totalBins = audiofile.getReadWindowSize()/2 +1;
float frequencyRange = audiofile.getFormat().getSampleRate()/2;
//Formula derived from: (22050b/513)+(22050/513/2) = F
//let b be the bin
//let F be the frequency.
return (int) (totalBins*(frequency - (frequencyRange/totalBins*2))/frequencyRange);
}
private int thresholdRangeCalc(float durationOfRange) {
float timePerWindow = (float)audiofile.getReadWindowSize()/audiofile.getFormat().getSampleRate();
return (int) (durationOfRange/timePerWindow);
}
public float getBassMaxValue() {
return bassMaxValue;
}
public float getUMMaxValue() {
return UMMaxValue;
}
public int getReadIndex() {
if (audiofile.getReadIndex() < UMPeaks.size) {
return audiofile.getReadIndex();
} else {
return 0;
}
}
public boolean containsData() {
return containsData;
}
}