292 lines
8.3 KiB
Java
Executable File
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;
|
|
}
|
|
}
|