Initial commit
This commit is contained in:
291
core/src/zero1hd/polyjet/audio/AudioAnalyzer.java
Executable file
291
core/src/zero1hd/polyjet/audio/AudioAnalyzer.java
Executable file
@@ -0,0 +1,291 @@
|
||||
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;
|
||||
}
|
||||
}
|
55
core/src/zero1hd/polyjet/audio/AudioData.java
Executable file
55
core/src/zero1hd/polyjet/audio/AudioData.java
Executable file
@@ -0,0 +1,55 @@
|
||||
package zero1hd.polyjet.audio;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
import com.badlogic.gdx.audio.Music;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
|
||||
public interface AudioData {
|
||||
/**
|
||||
* sets a integer variable to the current window of audio data the playback is at.
|
||||
* Useful for efficiency because we compute once for that frame then get the values everytime it is required instead of calculating every time we get the index.
|
||||
*/
|
||||
public void readIndexUpdate();
|
||||
|
||||
/**
|
||||
* Gets the current position in seconds
|
||||
* @return the current frame of audio.
|
||||
*/
|
||||
public int getReadIndex();
|
||||
|
||||
/**
|
||||
* Sets the current audio file
|
||||
*/
|
||||
public void setAudioFile(FileHandle setAudio);
|
||||
|
||||
/**
|
||||
* Completely resets the current audio data. Think of pooling, except, only one instance which is reused instead of multiple.
|
||||
*/
|
||||
public void reset();
|
||||
|
||||
/**
|
||||
* returns the read window size.
|
||||
* @return
|
||||
*/
|
||||
public int getReadWindowSize();
|
||||
|
||||
/**
|
||||
* Gets the object to play the music. Contains playback data.
|
||||
* @return
|
||||
*/
|
||||
public Music getPlaybackMusic();
|
||||
|
||||
/**
|
||||
* read in samples and fills the array.
|
||||
* @param samples the array that should contain said samples
|
||||
* @return amount read. Will return 0 if end of stream.
|
||||
*/
|
||||
public int readSamples(float[] samples);
|
||||
|
||||
/**
|
||||
* Returns properly setup AudioFormat object.
|
||||
* @return
|
||||
*/
|
||||
public AudioFormat getFormat();
|
||||
}
|
113
core/src/zero1hd/polyjet/audio/Mp3AudioData.java
Executable file
113
core/src/zero1hd/polyjet/audio/Mp3AudioData.java
Executable file
@@ -0,0 +1,113 @@
|
||||
package zero1hd.polyjet.audio;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.audio.Music;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
|
||||
import javazoom.jl.decoder.Obuffer;
|
||||
|
||||
public class Mp3AudioData implements AudioData {
|
||||
private int readWindowSize = 1024;
|
||||
|
||||
private AudioInputStream audStream;
|
||||
private AudioInputStream faudStream;
|
||||
|
||||
private AudioFormat audioFormat;
|
||||
private Music playbackMusic;
|
||||
private int readIndex;
|
||||
|
||||
@Override
|
||||
public void readIndexUpdate() {
|
||||
readIndex = (int) (playbackMusic.getPosition() * audioFormat.getSampleRate() / readWindowSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReadIndex() {
|
||||
return readIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAudioFile(FileHandle setAudio) {
|
||||
reset();
|
||||
try {
|
||||
audStream = AudioSystem.getAudioInputStream(setAudio.file());
|
||||
AudioFormat bFormat = audStream.getFormat();
|
||||
audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, bFormat.getSampleRate(), 16, bFormat.getChannels(), bFormat.getChannels()*2, bFormat.getSampleRate(), false);
|
||||
faudStream = AudioSystem.getAudioInputStream(audioFormat, audStream);
|
||||
} catch (UnsupportedAudioFileException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
playbackMusic = Gdx.audio.newMusic(setAudio);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
if (playbackMusic != null) {
|
||||
playbackMusic.stop();
|
||||
playbackMusic.dispose();
|
||||
playbackMusic = null;
|
||||
}
|
||||
|
||||
if (audStream != null) {
|
||||
try {
|
||||
audStream.close();
|
||||
faudStream.close();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReadWindowSize() {
|
||||
return readWindowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Music getPlaybackMusic() {
|
||||
return playbackMusic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readSamples(float[] samples) {
|
||||
int samplesRead = 0;
|
||||
int sampleAverage = 0;
|
||||
for (int currentSample = 0; currentSample < samples.length; currentSample++) {
|
||||
for (int channel = 0; channel < audioFormat.getChannels(); channel++) {
|
||||
try {
|
||||
sampleAverage += audStream.read();
|
||||
if (sampleAverage == -1) {
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
sampleAverage /= audioFormat.getChannels()*Short.MAX_VALUE+1;
|
||||
samples[currentSample] = sampleAverage;
|
||||
samplesRead++;
|
||||
if (sampleAverage == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return samplesRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioFormat getFormat() {
|
||||
return audioFormat;
|
||||
}
|
||||
|
||||
}
|
83
core/src/zero1hd/polyjet/audio/WavAudioData.java
Executable file
83
core/src/zero1hd/polyjet/audio/WavAudioData.java
Executable file
@@ -0,0 +1,83 @@
|
||||
package zero1hd.polyjet.audio;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.audio.Music;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
|
||||
import zero1hd.wavedecoder.WavDecoder;
|
||||
|
||||
public class WavAudioData implements AudioData {
|
||||
private int readWindowSize = 1024;
|
||||
private InputStream inStream;
|
||||
private InputStream bufferedAudioIn;
|
||||
private AudioFormat format;
|
||||
int readIndex;
|
||||
Music playbackMusic;
|
||||
WavDecoder decoder;
|
||||
|
||||
@Override
|
||||
public void readIndexUpdate() {
|
||||
readIndex = (int) (playbackMusic.getPosition() * decoder.getSampleRate() / readWindowSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReadIndex() {
|
||||
return readIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAudioFile(FileHandle audioFileHandler) {
|
||||
reset();
|
||||
|
||||
inStream = audioFileHandler.read();
|
||||
bufferedAudioIn = new BufferedInputStream(inStream);
|
||||
decoder.setAudioFile(audioFileHandler.file());
|
||||
playbackMusic = Gdx.audio.newMusic(audioFileHandler);
|
||||
format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, (float) decoder.getSampleRate(), 16, decoder.getChannels(), decoder.getChannels()*2, (float)decoder.getSampleRate(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
if (playbackMusic != null) {
|
||||
playbackMusic.stop();
|
||||
playbackMusic.dispose();
|
||||
playbackMusic = null;
|
||||
}
|
||||
|
||||
if (inStream != null) {
|
||||
try {
|
||||
bufferedAudioIn.close();
|
||||
inStream.close();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReadWindowSize() {
|
||||
return readWindowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Music getPlaybackMusic() {
|
||||
return playbackMusic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readSamples(float[] samples) {
|
||||
return decoder.readSamples(samples);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user