Initial commit

This commit is contained in:
2017-04-18 18:25:45 -05:00
commit fa87f5e8cf
176 changed files with 7413 additions and 0 deletions

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

View 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();
}

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

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