proper synchronization for reading samples; change to sample processor

interface to avoid creating unnessecary io streams; minor cleanup;
This commit is contained in:
Harrison Deng 2018-06-14 20:01:23 -05:00
parent 78324045f0
commit 55cac998ed
5 changed files with 95 additions and 34 deletions

View File

@ -20,13 +20,14 @@ public class RhythmBulletAudioThread extends Thread {
music = vm; music = vm;
this.sp = vm.getSampleProcessor(); this.sp = vm.getSampleProcessor();
pcm = new short[sp.getChannels()*windowSize]; pcm = new short[sp.getChannels()*windowSize];
this.ad = Gdx.audio.newAudioDevice(sp.getSampleRate(), (sp.getChannels() > 1 ? false : true));
} }
@Override @Override
public void run() { public void run() {
while (!terminated && sp.readSamples(pcm) > 0) { sp.initiate();
this.ad = Gdx.audio.newAudioDevice(sp.getSampleRate(), (sp.getChannels() > 1 ? false : true));
while (!terminated && sp.readSamples(pcm, this) > 0) {
ad.writeSamples(pcm, 0, pcm.length); ad.writeSamples(pcm, 0, pcm.length);
} }

View File

@ -29,7 +29,7 @@ public class VisualizableMusic implements Music {
e.printStackTrace(); e.printStackTrace();
} }
} else if (musicFile.getName().toLowerCase().endsWith("mp3")) { } else if (musicFile.getName().toLowerCase().endsWith("mp3")) {
sampleProcessor = new MP3SampleProcessor(); sampleProcessor = new MP3SampleProcessor(musicFile, windowSize);
} }
} }
@ -38,8 +38,10 @@ public class VisualizableMusic implements Music {
if (rat == null) { if (rat == null) {
rat = new RhythmBulletAudioThread(windowSize, this); rat = new RhythmBulletAudioThread(windowSize, this);
} }
rat.setOnCompletionListener(ocl); rat.setOnCompletionListener(ocl);
rat.play(); rat.play();
rat.start();
} }
@Override @Override
@ -105,6 +107,9 @@ public class VisualizableMusic implements Music {
@Override @Override
public void setOnCompletionListener(OnCompletionListener listener) { public void setOnCompletionListener(OnCompletionListener listener) {
this.ocl = listener; this.ocl = listener;
if (rat != null) {
rat.setOnCompletionListener(listener);
}
} }
protected SampleProcessor getSampleProcessor() { protected SampleProcessor getSampleProcessor() {

View File

@ -1,6 +1,25 @@
package zero1hd.rhythmbullet.audio.processor; package zero1hd.rhythmbullet.audio.processor;
import java.io.File;
public class MP3SampleProcessor implements SampleProcessor { public class MP3SampleProcessor implements SampleProcessor {
private int channels;
public MP3SampleProcessor(File musicFile, int windowSize) {
}
@Override
public void dispose() {
// TODO Auto-generated method stub
}
@Override
public void initiate() {
// TODO Auto-generated method stub
}
@Override @Override
public int getChannels() { public int getChannels() {
@ -15,7 +34,7 @@ public class MP3SampleProcessor implements SampleProcessor {
} }
@Override @Override
public int readSamples(short[] pcm) { public int readSamples(short[] pcm, Object syncObj) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
return 0; return 0;
} }
@ -43,4 +62,5 @@ public class MP3SampleProcessor implements SampleProcessor {
// TODO Auto-generated method stub // TODO Auto-generated method stub
return 0; return 0;
} }
} }

View File

@ -1,6 +1,15 @@
package zero1hd.rhythmbullet.audio.processor; package zero1hd.rhythmbullet.audio.processor;
public interface SampleProcessor { import com.badlogic.gdx.utils.Disposable;
public interface SampleProcessor extends Disposable {
/**
* Called once, contains the initiation to the stream, only called when play-back begins.
* Not thread safe as it should be the first thing to be called during read process.
*/
public void initiate();
/** /**
* @return number of channels * @return number of channels
*/ */
@ -12,29 +21,30 @@ public interface SampleProcessor {
public int getSampleRate(); public int getSampleRate();
/** /**
* <b>Should be thread-safe!</b> * <b>Thread safe</b>
* Reads samples (NOT FRAMES) with interwoven data for stereo. * 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) * 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 * @param pcm the array the samples should fill
* @param syncObj the object that this object should use to synchronize multiple threads.
* @return the amount of samples read. * @return the amount of samples read.
*/ */
public int readSamples(short[] pcm); public int readSamples(short[] pcm, Object syncObj);
/** /**
* <b>Should be thread-safe!</b> * <b>Thread safe</b>
* ranges from 0 to 1. * ranges from 0 to 1.
* @param volume the volume to set * @param volume the volume to set
*/ */
public void setVolume(float volume); public void setVolume(float volume);
/** /**
* <b>Should be thread-safe!</b> * <b>Thread safe</b>
* @return the volume ranging from 0 to 1 * @return the volume ranging from 0 to 1
*/ */
public float getVolume(); public float getVolume();
/** /**
* <b>Should be thread-safe!</b> * <b>Thread safe</b>
* sets the pan from the left channel to the right. * sets the pan from the left channel to the right.
* ranges from -1 to 1. * ranges from -1 to 1.
* Default is 0, -1 is left, 1 is right. * Default is 0, -1 is left, 1 is right.
@ -43,7 +53,7 @@ public interface SampleProcessor {
public void setPan(float pan); public void setPan(float pan);
/** /**
* <b>Should be thread-safe!</b> * <b>Thread safe</b>
* Returns the pan value from -1 to 1. * Returns the pan value from -1 to 1.
* see {@link #setPan(float)} for more information. * see {@link #setPan(float)} for more information.
* @return the pan value. * @return the pan value.

View File

@ -3,6 +3,7 @@ package zero1hd.rhythmbullet.audio.processor;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem; import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException; import javax.sound.sampled.UnsupportedAudioFileException;
@ -11,18 +12,30 @@ public class WAVSampleProcessor implements SampleProcessor {
private int channels; private int channels;
private int sampleRate; private int sampleRate;
private byte[] buffer; private byte[] buffer;
private File file;
private AudioInputStream audioInputStream; private AudioInputStream audioInputStream;
private volatile float volume, pan; private volatile float volume, pan;
private volatile boolean initiated;
public WAVSampleProcessor(File musicFile, int windowSize) throws IOException, UnsupportedAudioFileException { public WAVSampleProcessor(File musicFile, int windowSize) throws IOException, UnsupportedAudioFileException {
audioInputStream = AudioSystem.getAudioInputStream(musicFile); this.file = musicFile;
buffer = new byte[audioInputStream.getFormat().getFrameSize()]; AudioFormat format = AudioSystem.getAudioFileFormat(file).getFormat();
channels = format.getChannels();
channels = audioInputStream.getFormat().getChannels(); sampleRate = (int) format.getSampleRate();
sampleRate = (int) audioInputStream.getFormat().getSampleRate();
volume = 1f; volume = 1f;
} }
@Override
public void initiate() {
try {
audioInputStream = AudioSystem.getAudioInputStream(file);
} catch (UnsupportedAudioFileException | IOException e) {
e.printStackTrace();
}
buffer = new byte[audioInputStream.getFormat().getFrameSize()];
initiated = true;
}
public int getChannels() { public int getChannels() {
return channels; return channels;
} }
@ -32,7 +45,9 @@ public class WAVSampleProcessor implements SampleProcessor {
} }
@Override @Override
public synchronized int readSamples(short[] pcm) { public int readSamples(short[] pcm, Object syncObj) {
if (initiated) {
synchronized (syncObj) {
int framesRead = 0; int framesRead = 0;
for (int sampleID = 0; sampleID < pcm.length; sampleID++) { for (int sampleID = 0; sampleID < pcm.length; sampleID++) {
try { try {
@ -54,6 +69,10 @@ public class WAVSampleProcessor implements SampleProcessor {
} }
return framesRead; return framesRead;
} }
} else {
throw new IllegalStateException("Stream has not been initialized.");
}
}
@Override @Override
public void setVolume(float volume) { public void setVolume(float volume) {
@ -74,4 +93,10 @@ public class WAVSampleProcessor implements SampleProcessor {
public float getPan() { public float getPan() {
return pan; return pan;
} }
@Override
public void dispose() {
// TODO Auto-generated method stub
}
} }