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; } }