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;
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) {
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);
}

View File

@ -29,7 +29,7 @@ public class VisualizableMusic implements Music {
e.printStackTrace();
}
} 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) {
rat = new RhythmBulletAudioThread(windowSize, this);
}
rat.setOnCompletionListener(ocl);
rat.play();
rat.start();
}
@Override
@ -105,6 +107,9 @@ public class VisualizableMusic implements Music {
@Override
public void setOnCompletionListener(OnCompletionListener listener) {
this.ocl = listener;
if (rat != null) {
rat.setOnCompletionListener(listener);
}
}
protected SampleProcessor getSampleProcessor() {

View File

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

View File

@ -1,6 +1,15 @@
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
*/
@ -12,29 +21,30 @@ public interface SampleProcessor {
public int getSampleRate();
/**
* <b>Should be thread-safe!</b>
* <b>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
* @param syncObj the object that this object should use to synchronize multiple threads.
* @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.
* @param volume the volume to set
*/
public void setVolume(float volume);
/**
* <b>Should be thread-safe!</b>
* <b>Thread safe</b>
* @return the volume ranging from 0 to 1
*/
public float getVolume();
/**
* <b>Should be thread-safe!</b>
* <b>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.
@ -43,7 +53,7 @@ public interface SampleProcessor {
public void setPan(float pan);
/**
* <b>Should be thread-safe!</b>
* <b>Thread safe</b>
* Returns the pan value from -1 to 1.
* see {@link #setPan(float)} for more information.
* @return the pan value.

View File

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