From 55cac998ed433941d747f95a4f815d6da4648f14 Mon Sep 17 00:00:00 2001 From: Recrown Date: Thu, 14 Jun 2018 20:01:23 -0500 Subject: [PATCH] proper synchronization for reading samples; change to sample processor interface to avoid creating unnessecary io streams; minor cleanup; --- .../audio/RhythmBulletAudioThread.java | 7 +- .../rhythmbullet/audio/VisualizableMusic.java | 7 +- .../audio/processor/MP3SampleProcessor.java | 22 +++++- .../audio/processor/SampleProcessor.java | 24 +++++-- .../audio/processor/WAVSampleProcessor.java | 69 +++++++++++++------ 5 files changed, 95 insertions(+), 34 deletions(-) diff --git a/core/src/zero1hd/rhythmbullet/audio/RhythmBulletAudioThread.java b/core/src/zero1hd/rhythmbullet/audio/RhythmBulletAudioThread.java index ad744c6..fd24013 100755 --- a/core/src/zero1hd/rhythmbullet/audio/RhythmBulletAudioThread.java +++ b/core/src/zero1hd/rhythmbullet/audio/RhythmBulletAudioThread.java @@ -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); } diff --git a/core/src/zero1hd/rhythmbullet/audio/VisualizableMusic.java b/core/src/zero1hd/rhythmbullet/audio/VisualizableMusic.java index 06bd956..f1fa4a8 100755 --- a/core/src/zero1hd/rhythmbullet/audio/VisualizableMusic.java +++ b/core/src/zero1hd/rhythmbullet/audio/VisualizableMusic.java @@ -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() { diff --git a/core/src/zero1hd/rhythmbullet/audio/processor/MP3SampleProcessor.java b/core/src/zero1hd/rhythmbullet/audio/processor/MP3SampleProcessor.java index 119a0d1..e3d8ceb 100755 --- a/core/src/zero1hd/rhythmbullet/audio/processor/MP3SampleProcessor.java +++ b/core/src/zero1hd/rhythmbullet/audio/processor/MP3SampleProcessor.java @@ -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; } + } diff --git a/core/src/zero1hd/rhythmbullet/audio/processor/SampleProcessor.java b/core/src/zero1hd/rhythmbullet/audio/processor/SampleProcessor.java index 40ef69d..983d90d 100755 --- a/core/src/zero1hd/rhythmbullet/audio/processor/SampleProcessor.java +++ b/core/src/zero1hd/rhythmbullet/audio/processor/SampleProcessor.java @@ -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(); /** - * Should be thread-safe! + * Thread safe * 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); /** - * Should be thread-safe! + * Thread safe * ranges from 0 to 1. * @param volume the volume to set */ public void setVolume(float volume); /** - * Should be thread-safe! + * Thread safe * @return the volume ranging from 0 to 1 */ public float getVolume(); /** - * Should be thread-safe! + * Thread safe * 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); /** - * Should be thread-safe! + * Thread safe * Returns the pan value from -1 to 1. * see {@link #setPan(float)} for more information. * @return the pan value. diff --git a/core/src/zero1hd/rhythmbullet/audio/processor/WAVSampleProcessor.java b/core/src/zero1hd/rhythmbullet/audio/processor/WAVSampleProcessor.java index 6923e4d..2c92ae4 100755 --- a/core/src/zero1hd/rhythmbullet/audio/processor/WAVSampleProcessor.java +++ b/core/src/zero1hd/rhythmbullet/audio/processor/WAVSampleProcessor.java @@ -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 + + } } \ No newline at end of file