346 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Java
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			9.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;
 | 
						|
import zero1hd.polyjet.util.MiniEvents;
 | 
						|
import zero1hd.polyjet.util.MiniSender;
 | 
						|
 | 
						|
public class AudioAnalyzer {
 | 
						|
	private boolean containsData;
 | 
						|
	private boolean finalized;
 | 
						|
	FloatFFT_1D fft;
 | 
						|
	public AudioData audioData;
 | 
						|
 | 
						|
	float[] audioPCM;
 | 
						|
	float[] spectrum;
 | 
						|
	float[] lastSpectrum;
 | 
						|
	
 | 
						|
	Runnable analysisAlgorithm;
 | 
						|
	Runnable thresholdCalculator;
 | 
						|
	
 | 
						|
	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 overlappedPeaks = new FloatArray();
 | 
						|
	
 | 
						|
	float bassThresholdMultiplier;
 | 
						|
	float UMThresholdMultiplier;
 | 
						|
	int UMThresholdCalcRange;
 | 
						|
	int bassThresholdCalcRange;
 | 
						|
	
 | 
						|
	public volatile MiniSender sender;
 | 
						|
	
 | 
						|
	private float avgBPS;
 | 
						|
	
 | 
						|
	int PUID;
 | 
						|
	boolean work;
 | 
						|
	private volatile int progress;
 | 
						|
	private float secondsPerFrame;
 | 
						|
	public AudioAnalyzer() {
 | 
						|
		sender = new MiniSender();
 | 
						|
		
 | 
						|
		
 | 
						|
		analysisAlgorithm = new Runnable() {
 | 
						|
 | 
						|
			@Override
 | 
						|
			public void run() {
 | 
						|
				progress = 0;
 | 
						|
				int tasksDone = 0;
 | 
						|
				int totalTasks = audioData.getSampleCount()/audioData.getReadWindowSize();
 | 
						|
				
 | 
						|
				bassThresholdMultiplier = 1.5f;
 | 
						|
				UMThresholdMultiplier = 2f;
 | 
						|
				
 | 
						|
				bassBinBegin = 1;
 | 
						|
				bassBinEnd = 15;
 | 
						|
				
 | 
						|
				UMBinBegin = 300;
 | 
						|
				UMBinEnd = 450;
 | 
						|
				
 | 
						|
				UMThresholdCalcRange = thresholdRangeCalc(0.5f);
 | 
						|
				bassThresholdCalcRange = thresholdRangeCalc(0.7f);
 | 
						|
				
 | 
						|
				Gdx.app.debug("Read freq", String.valueOf(audioData.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(audioData.getReadWindowSize());
 | 
						|
				int seedDigit = 0;
 | 
						|
				while (audioData.readSamples(audioPCM) > 0 && work) {
 | 
						|
					
 | 
						|
					fft.realForward(audioPCM);
 | 
						|
					
 | 
						|
					//Building a PUID (Pseudo unique ID)
 | 
						|
					if (tasksDone == (seedDigit*totalTasks/9)) {
 | 
						|
						float avg = 0;
 | 
						|
						for (int frame = 0; frame < spectrum.length; frame++) {
 | 
						|
							avg += spectrum[frame];
 | 
						|
						}
 | 
						|
						avg /= spectrum.length;
 | 
						|
						if (avg < 0) {
 | 
						|
							avg *= -1f;
 | 
						|
						}
 | 
						|
						PUID +=(int) Math.pow(10, 9-seedDigit) * ((int)(avg*1000f)-(int)(avg*100f)*10);
 | 
						|
						seedDigit ++;
 | 
						|
					}
 | 
						|
					
 | 
						|
					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 && work; i++) {
 | 
						|
						fluxVal += ((spectrum[i] - lastSpectrum[i])) > 0
 | 
						|
								? (spectrum[i] - lastSpectrum[i]) : 0;
 | 
						|
					}
 | 
						|
					bassSpectralFlux.add(fluxVal);
 | 
						|
					
 | 
						|
					//main detection
 | 
						|
					fluxVal = 0;
 | 
						|
					for (int i = UMBinBegin; i < UMBinEnd && work; i++) {
 | 
						|
						fluxVal += ((spectrum[i] - lastSpectrum[i])) > 0
 | 
						|
								? (spectrum[i] - lastSpectrum[i]) : 0;
 | 
						|
					}
 | 
						|
					UMSpectralFlux.add(fluxVal);
 | 
						|
					tasksDone++;
 | 
						|
					progress = (int) (100f*tasksDone/totalTasks);
 | 
						|
					sender.send(MiniEvents.ANALYZER_ITERATED);
 | 
						|
				}
 | 
						|
				
 | 
						|
				if (work) {
 | 
						|
					Gdx.app.debug("Audio Analyzer", "Done getting spectral flux.");
 | 
						|
 | 
						|
					shrinkData();
 | 
						|
					containsData = true;
 | 
						|
					Gdx.app.debug("Audio Analyzer", "USING SEED: " + PUID);
 | 
						|
					sender.send(MiniEvents.SPECTRAL_FLUX_DONE);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		};
 | 
						|
		
 | 
						|
		thresholdCalculator = new Runnable() {
 | 
						|
			@Override
 | 
						|
			public void run() {
 | 
						|
				
 | 
						|
				//threshold calculation
 | 
						|
				for (int i = 0; i < UMSpectralFlux.size && work; 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 && work; 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.");
 | 
						|
 | 
						|
				secondsPerFrame = audioData.getReadWindowSize()/audioData.getFormat().getSampleRate();
 | 
						|
				//peak detection
 | 
						|
				
 | 
						|
				int lastID = 0;
 | 
						|
				for (int i = 0; i < UMPrunned.size-1 && work; 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);
 | 
						|
					}
 | 
						|
					
 | 
						|
					//overlapping beats
 | 
						|
					if (bassPeaks.get(i) != 0 && UMPeaks.get(i) != 0) {
 | 
						|
						overlappedPeaks.add(bassPeaks.get(i)+UMPeaks.get(i)/2);
 | 
						|
					} else {
 | 
						|
						overlappedPeaks.add(0);
 | 
						|
					}
 | 
						|
					
 | 
						|
					avgBPS = -1f;
 | 
						|
					float beats = 0;
 | 
						|
					if (avgBPS == -1 && bassPeaks.get(i) != 0) {
 | 
						|
						//this should actually equal to 1;
 | 
						|
						avgBPS = 0;
 | 
						|
					} else if (avgBPS == 0 && bassPeaks.get(i) == 0) {
 | 
						|
						avgBPS ++;
 | 
						|
					} else {
 | 
						|
						beats ++;
 | 
						|
						lastID = i;
 | 
						|
					}
 | 
						|
					//then we minus one from the beats so it actually works out
 | 
						|
					avgBPS -= UMPrunned.size-lastID;
 | 
						|
					avgBPS *= secondsPerFrame;
 | 
						|
					avgBPS = beats/avgBPS;
 | 
						|
				}
 | 
						|
				
 | 
						|
				
 | 
						|
				if (work) {
 | 
						|
					Gdx.app.debug("Audio Analyzer", "overlapped beats checked.");
 | 
						|
					
 | 
						|
					finalized = true;
 | 
						|
					
 | 
						|
					sender.send(MiniEvents.MUSIC_DATA_CLEANED);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		};
 | 
						|
		
 | 
						|
	}
 | 
						|
	
 | 
						|
	public void shrinkData() {
 | 
						|
		bassSpectralFlux.shrink();
 | 
						|
		bassThreshold.shrink();
 | 
						|
		bassPrunned.shrink();
 | 
						|
		bassPeaks.shrink();
 | 
						|
		
 | 
						|
		UMSpectralFlux.shrink();
 | 
						|
		UMThreshold.shrink();
 | 
						|
		UMPrunned.shrink();
 | 
						|
		UMPeaks.shrink();
 | 
						|
		
 | 
						|
		overlappedPeaks.shrink();
 | 
						|
	}
 | 
						|
	
 | 
						|
	public void startAnalyticalThread(AudioData audiofile) {
 | 
						|
		audioPCM = new float[audiofile.getReadWindowSize()];
 | 
						|
		spectrum = new float[(audiofile.getReadWindowSize()/2)+1];
 | 
						|
		lastSpectrum = new float[(audiofile.getReadWindowSize()/2)+1];
 | 
						|
		this.audioData = audiofile;
 | 
						|
		work = true;
 | 
						|
		Thread analyticalThread = new Thread(analysisAlgorithm);
 | 
						|
		analyticalThread.start();
 | 
						|
	}
 | 
						|
	
 | 
						|
	public void runThresholdCleaning(float baseThresholdMultiplier, float UMThresholdMultiplier) {
 | 
						|
		this.bassThresholdMultiplier = baseThresholdMultiplier;
 | 
						|
		this.UMThresholdMultiplier = UMThresholdMultiplier;
 | 
						|
		work = true;
 | 
						|
		Thread thresholdClean = new Thread(thresholdCalculator);
 | 
						|
		thresholdClean.start();
 | 
						|
	}
 | 
						|
	
 | 
						|
	public void runThresholdCleaning() {
 | 
						|
		Thread thresholdClean = new Thread(thresholdCalculator);
 | 
						|
		thresholdClean.start();
 | 
						|
	}
 | 
						|
	
 | 
						|
	public FloatArray getBassPeaks() {
 | 
						|
		return bassPeaks;
 | 
						|
	}
 | 
						|
	public FloatArray getUMPeaks() {
 | 
						|
		return UMPeaks;
 | 
						|
	}
 | 
						|
	
 | 
						|
	private int thresholdRangeCalc(float durationOfRange) {
 | 
						|
		float timePerWindow = (float)audioData.getReadWindowSize()/audioData.getFormat().getSampleRate();
 | 
						|
		return (int) (durationOfRange/timePerWindow);
 | 
						|
	}
 | 
						|
	
 | 
						|
	public float getBassMaxValue() {
 | 
						|
		return bassMaxValue;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public float getUMMaxValue() {
 | 
						|
		return UMMaxValue;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public int getReadIndex() {
 | 
						|
		if (audioData.getReadIndex() < UMPeaks.size) {
 | 
						|
			return audioData.getReadIndex();
 | 
						|
		} else {
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	
 | 
						|
	public boolean containsData() {
 | 
						|
		return containsData;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public synchronized int getProgress() {
 | 
						|
		return progress;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public boolean isFinalized() {
 | 
						|
		return finalized;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public void stop() {
 | 
						|
		work = false;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public int getPUID() {
 | 
						|
		return PUID;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public FloatArray getOverlappedPeaks() {
 | 
						|
		return overlappedPeaks;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public AudioData getAudioData() {
 | 
						|
		return audioData;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public float getAvgBPS() {
 | 
						|
		return avgBPS;
 | 
						|
	}
 | 
						|
	
 | 
						|
	public float getsecondsPerFrame() {
 | 
						|
		return secondsPerFrame;
 | 
						|
	}
 | 
						|
}
 |