Minor restructuring done; furthered functionality of the audio system;

further work on thread-safety of the classes.
This commit is contained in:
Harrison Deng 2018-06-14 00:48:29 -05:00
parent 31b2278e9b
commit 78324045f0
7 changed files with 275 additions and 186 deletions

View File

@ -1,29 +0,0 @@
package zero1hd.rhythmbullet.audio;
import com.badlogic.gdx.utils.Array;
import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D;
import zero1hd.rhythmbullet.audio.processor.SampleProcessor;
class AudioFileReadingThread extends Thread {
private boolean function = true;
private Array<SampleProcessor> sps;
SampleProcessor sp;
FloatFFT_1D fft;
@Override
public void run() {
while (function) {
if (sps.size > 0) {
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
super.run();
}
}

View File

@ -0,0 +1,75 @@
package zero1hd.rhythmbullet.audio;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.AudioDevice;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Music.OnCompletionListener;
import zero1hd.rhythmbullet.audio.processor.SampleProcessor;
public class RhythmBulletAudioThread extends Thread {
private AudioDevice ad;
private SampleProcessor sp;
private Music music;
private volatile OnCompletionListener ocl;
private short[] pcm;
private volatile boolean terminated;
private volatile boolean playing;
public RhythmBulletAudioThread(int windowSize, VisualizableMusic vm) {
music = vm;
this.sp = vm.getSampleProcessor();
pcm = new short[sp.getChannels()*windowSize];
this.ad = Gdx.audio.newAudioDevice(sp.getSampleRate(), (sp.getChannels() > 1 ? false : true));
}
@Override
public void run() {
while (!terminated && sp.readSamples(pcm) > 0) {
ad.writeSamples(pcm, 0, pcm.length);
}
if (ocl != null) {
ocl.onCompletion(music);
}
super.run();
}
public synchronized void play() {
playing = true;
notify();
}
public synchronized void pause() {
playing = false;
while (!playing) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized short[] getPcm() {
return pcm;
}
public void terminate() {
terminated = true;
}
public boolean isTerminated() {
return terminated;
}
public boolean isPlaying() {
return playing;
}
public void setOnCompletionListener(OnCompletionListener ocl) {
this.ocl = ocl;
}
}

View File

@ -0,0 +1,113 @@
package zero1hd.rhythmbullet.audio;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.UnsupportedAudioFileException;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.files.FileHandle;
import zero1hd.rhythmbullet.audio.processor.MP3SampleProcessor;
import zero1hd.rhythmbullet.audio.processor.SampleProcessor;
import zero1hd.rhythmbullet.audio.processor.WAVSampleProcessor;
public class VisualizableMusic implements Music {
private int windowSize = 1024;
private OnCompletionListener ocl;
private boolean looping;
private RhythmBulletAudioThread rat;
private File musicFile;
private SampleProcessor sampleProcessor;
public VisualizableMusic(FileHandle file) {
musicFile = file.file();
if (musicFile.getName().toLowerCase().endsWith("wav")) {
try {
sampleProcessor = new WAVSampleProcessor(musicFile, windowSize);
} catch (IOException | UnsupportedAudioFileException e) {
e.printStackTrace();
}
} else if (musicFile.getName().toLowerCase().endsWith("mp3")) {
sampleProcessor = new MP3SampleProcessor();
}
}
@Override
public void play() {
if (rat == null) {
rat = new RhythmBulletAudioThread(windowSize, this);
}
rat.setOnCompletionListener(ocl);
rat.play();
}
@Override
public void pause() {
rat.pause();
}
@Override
public void stop() {
rat.terminate();
rat = null;
}
@Override
public boolean isPlaying() {
return rat.isPlaying();
}
@Override
public void setLooping(boolean isLooping) {
looping = isLooping;
}
@Override
public boolean isLooping() {
return looping;
}
@Override
public void setVolume(float volume) {
sampleProcessor.setVolume(volume);
}
@Override
public float getVolume() {
return sampleProcessor.getVolume();
}
@Override
public void setPan(float pan, float volume) {
sampleProcessor.setPan(pan);
sampleProcessor.setVolume(volume);
}
@Override
public void setPosition(float position) {
// TODO Auto-generated method stub
}
@Override
public float getPosition() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void dispose() {
// TODO Auto-generated method stub
}
@Override
public void setOnCompletionListener(OnCompletionListener listener) {
this.ocl = listener;
}
protected SampleProcessor getSampleProcessor() {
return sampleProcessor;
}
}

View File

@ -20,5 +20,27 @@ public class MP3SampleProcessor implements SampleProcessor {
return 0;
}
@Override
public void setVolume(float volume) {
// TODO Auto-generated method stub
}
@Override
public float getVolume() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void setPan(float pan) {
// TODO Auto-generated method stub
}
@Override
public float getPan() {
// TODO Auto-generated method stub
return 0;
}
}

View File

@ -1,7 +1,52 @@
package zero1hd.rhythmbullet.audio.processor;
public interface SampleProcessor {
/**
* @return number of channels
*/
public int getChannels();
/**
* @return sample rate
*/
public int getSampleRate();
/**
* <b>Should be thread-safe!</b>
* Reads samples (NOT FRAMES) with interwoven data for stereo.
* stored in 16 bit format (first 8 are the first byte of data while the second 8 are the second byte of data that composes a short value)
* @param pcm the array the samples should fill
* @return the amount of samples read.
*/
public int readSamples(short[] pcm);
/**
* <b>Should be thread-safe!</b>
* ranges from 0 to 1.
* @param volume the volume to set
*/
public void setVolume(float volume);
/**
* <b>Should be thread-safe!</b>
* @return the volume ranging from 0 to 1
*/
public float getVolume();
/**
* <b>Should be thread-safe!</b>
* sets the pan from the left channel to the right.
* ranges from -1 to 1.
* Default is 0, -1 is left, 1 is right.
* @param pan
*/
public void setPan(float pan);
/**
* <b>Should be thread-safe!</b>
* Returns the pan value from -1 to 1.
* see {@link #setPan(float)} for more information.
* @return the pan value.
*/
public float getPan();
}

View File

@ -1,144 +0,0 @@
package zero1hd.rhythmbullet.audio.processor;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.UnsupportedAudioFileException;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.AudioDevice;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.files.FileHandle;
public class VisualizableMusic implements Music {
private int windowSize = 1024;
private OnCompletionListener ocl;
private volatile boolean playing, updatedPCMData;
private boolean looping;
private volatile short[] pcm;
private volatile float[] normalized;
private volatile SampleProcessor sp;
private volatile float volume = 1f;
public VisualizableMusic(FileHandle file) {
}
@Override
public void play() {
playing = true;
}
@Override
public void pause() {
playing = false;
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public boolean isPlaying() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setLooping(boolean isLooping) {
looping = isLooping;
}
@Override
public boolean isLooping() {
return looping;
}
@Override
public void setVolume(float volume) {
this.volume = volume;
}
@Override
public float getVolume() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void setPan(float pan, float volume) {
// TODO Auto-generated method stub
}
@Override
public void setPosition(float position) {
// TODO Auto-generated method stub
}
@Override
public float getPosition() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void dispose() {
// TODO Auto-generated method stub
}
@Override
public void setOnCompletionListener(OnCompletionListener listener) {
this.ocl = listener;
}
public void normalize(short[] pcm, float[] normalized) {
int currentFrame = 0;
for (int sampleID = 0; sampleID < pcm.length; sampleID++) {
for (int chan = 0; chan < sp.getChannels(); chan++) {
if (normalized[currentFrame] > pcm[sampleID]) {
normalized[currentFrame] = pcm[sampleID];
}
sampleID++;
}
normalized[currentFrame] /= Short.MAX_VALUE+1;
currentFrame++;
}
}
class RhythmBulletAudioThread extends Thread {
private AudioDevice ad;
public RhythmBulletAudioThread(int windowSize, File file) {
if (file.getName().toLowerCase().endsWith("wav")) {
try {
sp = new WAVSampleProcessor(file, windowSize);
} catch (IOException | UnsupportedAudioFileException e) {
e.printStackTrace();
}
} else if (file.getName().toLowerCase().endsWith("mp3")) {
sp = new MP3SampleProcessor();
}
pcm = new short[sp.getChannels()*windowSize];
normalized = new float[windowSize];
this.ad = Gdx.audio.newAudioDevice(sp.getSampleRate(), (sp.getChannels() > 1 ? true : false));
}
@Override
public void run() {
while (playing && sp.readSamples(pcm) > 0) {
updatedPCMData = true;
ad.writeSamples(pcm, 0, pcm.length);
}
super.run();
}
}
}

View File

@ -12,8 +12,8 @@ public class WAVSampleProcessor implements SampleProcessor {
private int sampleRate;
private byte[] buffer;
private AudioInputStream audioInputStream;
private boolean mergeChannels;
private float volume, pan;
private volatile float volume, pan;
public WAVSampleProcessor(File musicFile, int windowSize) throws IOException, UnsupportedAudioFileException {
audioInputStream = AudioSystem.getAudioInputStream(musicFile);
buffer = new byte[audioInputStream.getFormat().getFrameSize()];
@ -32,7 +32,7 @@ public class WAVSampleProcessor implements SampleProcessor {
}
@Override
public int readSamples(short[] pcm) {
public synchronized int readSamples(short[] pcm) {
int framesRead = 0;
for (int sampleID = 0; sampleID < pcm.length; sampleID++) {
try {
@ -55,16 +55,23 @@ public class WAVSampleProcessor implements SampleProcessor {
return framesRead;
}
public AudioInputStream getAudioInputStream() {
return audioInputStream;
@Override
public void setVolume(float volume) {
this.volume = volume;
}
public void setMergeChannels(boolean mergeChannels) {
this.mergeChannels = mergeChannels;
@Override
public float getVolume() {
return volume;
}
public boolean isMergeChannels() {
return mergeChannels;
@Override
public void setPan(float pan) {
this.pan = pan;
}
@Override
public float getPan() {
return pan;
}
}