diff --git a/core/src/zero1hd/rhythmbullet/audio/visualizer/VisualizerCore.java b/core/src/zero1hd/rhythmbullet/audio/visualizer/VisualizerCore.java index de26aca..83586cb 100755 --- a/core/src/zero1hd/rhythmbullet/audio/visualizer/VisualizerCore.java +++ b/core/src/zero1hd/rhythmbullet/audio/visualizer/VisualizerCore.java @@ -32,11 +32,7 @@ public class VisualizerCore implements Disposable { if (mm != null && calc && mm.isPlaying()) { updateTimer += delta; if (updateTimer >= updateRate) { - mm.playbackIndexUpdate(); - System.out.println(mm.getPlaybackIndexPosition() + "vs" + mm.getReadIndex()); - while (mm.getPlaybackIndexPosition() > mm.getReadIndex()) { - mm.skipReadWindow(); - } + //TODO use current buffer being played lock.lock(); mm.readSamples(audioPCM); fft.realForward(audioPCM); diff --git a/core/src/zero1hd/rhythmbullet/audio/wavedecoder/AudioSampleReader.java b/core/src/zero1hd/rhythmbullet/audio/wavedecoder/WAVSampleReader.java similarity index 95% rename from core/src/zero1hd/rhythmbullet/audio/wavedecoder/AudioSampleReader.java rename to core/src/zero1hd/rhythmbullet/audio/wavedecoder/WAVSampleReader.java index 9589214..1bb0ed4 100755 --- a/core/src/zero1hd/rhythmbullet/audio/wavedecoder/AudioSampleReader.java +++ b/core/src/zero1hd/rhythmbullet/audio/wavedecoder/WAVSampleReader.java @@ -4,14 +4,14 @@ import java.io.IOException; import javax.sound.sampled.AudioInputStream; -public class AudioSampleReader { +public class WAVSampleReader { private int channels; private double sampleRate; private byte[] buffer; private AudioInputStream audioInputStream; private boolean mergeChannels; - public AudioSampleReader(AudioInputStream ais) throws IOException { + public WAVSampleReader(AudioInputStream ais) throws IOException { audioInputStream = ais; buffer = new byte[audioInputStream.getFormat().getFrameSize()]; diff --git a/core/src/zero1hd/rhythmbullet/util/MusicManager.java b/core/src/zero1hd/rhythmbullet/util/MusicManager.java index a00bd3a..fae1438 100755 --- a/core/src/zero1hd/rhythmbullet/util/MusicManager.java +++ b/core/src/zero1hd/rhythmbullet/util/MusicManager.java @@ -83,14 +83,8 @@ public interface MusicManager extends Disposable { public FileHandle getMusicFile(); /** - * Synchronizes the playback and read threads + * @return the amount of windows that have been read. */ - public void synchronize(); + public int framesRead(); - /** - * @return the window that the current read index is on - */ - public int getReadIndex(); - - public void skipReadWindow(); } diff --git a/desktop/src/zero1hd/rhythmbullet/desktop/audio/Mp3Manager.java b/desktop/src/zero1hd/rhythmbullet/desktop/audio/Mp3Manager.java index 26f7b76..a90d668 100755 --- a/desktop/src/zero1hd/rhythmbullet/desktop/audio/Mp3Manager.java +++ b/desktop/src/zero1hd/rhythmbullet/desktop/audio/Mp3Manager.java @@ -1,214 +1,269 @@ package zero1hd.rhythmbullet.desktop.audio; import java.io.IOException; -import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; -import javax.sound.sampled.AudioFileFormat; -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; - -import org.jaudiotagger.audio.AudioFile; +import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; +import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; +import org.jaudiotagger.audio.mp3.MP3File; +import org.jaudiotagger.tag.TagException; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.audio.Music.OnCompletionListener; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.GdxRuntimeException; -import zero1hd.rhythmbullet.audio.wavedecoder.AudioSampleReader; +import javazoom.jl.decoder.Bitstream; +import javazoom.jl.decoder.BitstreamException; +import javazoom.jl.decoder.DecoderException; +import javazoom.jl.decoder.Header; +import javazoom.jl.decoder.MP3Decoder; +import javazoom.jl.decoder.OutputBuffer; import zero1hd.rhythmbullet.util.MusicManager; public class Mp3Manager implements MusicManager { private int readWindowSize = 1024; - private Music music; - private int playbackIndex, readIndex; - private FileHandle fileHandle; - private AudioInputStream in; - private AudioInputStream ais; - private AudioFormat af; - private AudioSampleReader d; - public Mp3Manager(FileHandle file) { - this.fileHandle = file; + private Music playbackMusic; + private int playbackIndex, readIndex; + + private int sampleRate; + private long sampleCount; + private double durationInSeconds; + private byte channels; + Bitstream bitstream; + MP3Decoder decoder; + OutputBuffer sampleBuffer; + private byte[] currentByteSet; + private byte[] workset; + private int indexHead = -1; + + private FileHandle file; + ReentrantLock lock = new ReentrantLock(); + + private ExecutorService exec; + + private String basicSongName; + public Mp3Manager(FileHandle audioFile) { + this.file = audioFile; + lock = new ReentrantLock(); + this.basicSongName = audioFile.name(); + exec = Executors.newSingleThreadExecutor(); + exec.submit(() -> { + lock.lock(); + Logger.getLogger("org.jaudiotagger").setLevel(Level.OFF); + try { + MP3File mp3File = new MP3File(audioFile.file()); + sampleCount = MathUtils.round(Float.valueOf((float) (mp3File.getAudioHeader().getSampleRateAsNumber()*mp3File.getMP3AudioHeader().getPreciseTrackLength()))); + sampleRate = mp3File.getMP3AudioHeader().getSampleRateAsNumber(); + durationInSeconds = mp3File.getMP3AudioHeader().getPreciseTrackLength(); + + } catch (IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { + e.printStackTrace(); + } + lock.unlock(); + }); + + bitstream = new Bitstream(audioFile.read()); + decoder = new MP3Decoder(); try { - in = AudioSystem.getAudioInputStream(file.file()); - AudioFormat baseFormat = in.getFormat(); - af = new AudioFormat( - AudioFormat.Encoding.PCM_SIGNED, - baseFormat.getSampleRate(), - 16, baseFormat.getChannels(), - baseFormat.getChannels()*2, - baseFormat.getSampleRate(), false); - ais = AudioSystem.getAudioInputStream(af, in); - d = new AudioSampleReader(ais); - music = Gdx.audio.newMusic(file); - } catch (IOException | UnsupportedAudioFileException e) { - e.printStackTrace(); - } - } - - @Override - public void dispose() { - music.dispose(); - try { - ais.close(); - in.close(); - } catch (IOException e) { - e.printStackTrace(); + Header header = bitstream.readFrame(); + if (header == null) throw new GdxRuntimeException("Empty MP3"); + channels = (byte) (header.mode() == Header.SINGLE_CHANNEL ? 1 : 2); + sampleBuffer = new OutputBuffer(channels, false); + decoder.setOutputBuffer(sampleBuffer); + workset = new byte[channels*2]; + } catch (BitstreamException e) { + throw new GdxRuntimeException("error while preloading mp3", e); } + + playbackMusic = Gdx.audio.newMusic(audioFile); + } @Override public void playbackIndexUpdate() { - playbackIndex = (int) ((getPositionInSeconds()*getSampleRate())/getReadWindowSize()); + playbackIndex = (int) (playbackMusic.getPosition() * sampleRate / readWindowSize); } @Override public int getPlaybackIndexPosition() { return playbackIndex; } - - @Override - public int getReadIndex() { - return readIndex; - } - + @Override public int getReadWindowSize() { return readWindowSize; } - @Override - public int readSamples(float[] samples) { - try { - readIndex++; - return d.readSamples(samples); - } catch (IOException e) { - e.printStackTrace(); - }; - return 0; - } - @Override public long getSampleCount() { - return ais.getFrameLength(); + return sampleCount; } @Override public float getDuration() { - return 0; + return Float.valueOf(String.valueOf(durationInSeconds)); } + @Override + public int readSamples(float[] samples) { + readIndex++; + int framesRead = 0; + for (int sid = 0; sid < samples.length; sid++) { + for (int wsid = 0; wsid < workset.length; wsid++) { + workset[wsid] = nextByte(); + } + if (currentByteSet != null) { + samples[sid] += (workset[1] << 8) + (workset[0] & 0x00ff); + if (channels > 1) { + short altChan = (short) ((workset[3] << 8) + (workset[2] & 0x00ff)); + + samples[sid] = altChan > samples[sid] ? altChan : samples[sid]; + } + framesRead ++; + samples[sid] /= Short.MAX_VALUE+1; + } + } + return framesRead; + } + + public byte nextByte() { + indexHead++; + if (currentByteSet == null || indexHead >= currentByteSet.length) { + loadNextBuffer(); + if (currentByteSet == null) { + return 0; + } + indexHead = 0; + } + + return currentByteSet[indexHead]; + } + + public int loadNextBuffer() { + if (bitstream != null) { + int bytesRead = 0; + try { + Header header = bitstream.readFrame(); + if (header != null) { + + try { + decoder.decodeFrame(header, bitstream); + } catch (ArrayIndexOutOfBoundsException | DecoderException e) { + System.out.println(e); + } + + bitstream.closeFrame(); + bytesRead = sampleBuffer.reset(); + + currentByteSet = sampleBuffer.getBuffer(); + } else { + currentByteSet = null; + } + } catch (BitstreamException e1) { + e1.printStackTrace(); + } + return bytesRead; + } else { + return 0; + } + } + @Override public float getSampleRate() { - return af.getSampleRate(); + return sampleRate; } @Override public void pause() { - music.pause(); + Gdx.app.debug("MP3Manager", "Pausing..."); + playbackMusic.pause(); } @Override public void play() { - music.play(); + Gdx.app.debug("Mp3Manager", "MP3 Playing..."); + playbackMusic.play(); } @Override public boolean isPlaying() { - return music.isPlaying(); + return playbackMusic.isPlaying(); } @Override public float getPositionInSeconds() { - - return music.getPosition(); + return playbackMusic.getPosition(); } @Override public void setPosition(float position) { - music.setPosition(position); + playbackMusic.setPosition(position); } @Override public void setOnCompletionListener(OnCompletionListener listener) { - music.setOnCompletionListener(listener); + playbackMusic.setOnCompletionListener(listener); } @Override public void setVolume(float percent) { - music.setVolume(percent); + playbackMusic.setVolume(percent); } @Override public boolean isFinishedLoading() { - return true; + if (lock.isHeldByCurrentThread()) { + return true; + } else { + try { + if (lock.tryLock(0, TimeUnit.SECONDS)) { + return true; + } else { + return false; + } + } catch (InterruptedException e) { + return false; + } + } } @Override public String getBasicSongName() { - return fileHandle.nameWithoutExtension(); + return basicSongName; } @Override public FileHandle getMusicFile() { - return fileHandle; - } - - public void seek(long position) { - closeStream(); - reInitStream(); - skip(position); + return file; } - private void closeStream() { - if (ais != null) { - try { - ais.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private void reInitStream() { + @Override + public void dispose() { + Gdx.app.debug("MP3Manager", "Disposing..."); + playbackMusic.stop(); + playbackMusic.dispose(); + exec.shutdown(); try { - AudioInputStream in = AudioSystem.getAudioInputStream(fileHandle.file()); - ais = AudioSystem.getAudioInputStream(af, in); - in.close(); - } catch (UnsupportedAudioFileException | IOException e) { + bitstream.close(); + bitstream = null; + } catch (BitstreamException e) { e.printStackTrace(); } } @Override - public void synchronize() { - seek(MathUtils.round(((Integer) properties.get("audio.length.bytes")).intValue() * (getPositionInSeconds()/getDuration()))); + public int framesRead() { + return readIndex; } - - public void skip(long bytes) { - long totalSkipped = 0; - long skipped = 0; - while (totalSkipped < bytes) { - try { - skipped = ais.skip(bytes-totalSkipped); - } catch (IOException e) { - e.printStackTrace(); - } - - if (skipped == 0) break; - totalSkipped += skipped; - } - } - - @Override - public void skipReadWindow() { - skip(readWindowSize*af.getFrameSize()); - readIndex++; - } - } diff --git a/desktop/src/zero1hd/rhythmbullet/desktop/audio/WAVManager.java b/desktop/src/zero1hd/rhythmbullet/desktop/audio/WAVManager.java index b4d1799..e0eaa8a 100755 --- a/desktop/src/zero1hd/rhythmbullet/desktop/audio/WAVManager.java +++ b/desktop/src/zero1hd/rhythmbullet/desktop/audio/WAVManager.java @@ -11,9 +11,8 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.audio.Music.OnCompletionListener; import com.badlogic.gdx.files.FileHandle; -import com.badlogic.gdx.math.MathUtils; -import zero1hd.rhythmbullet.audio.wavedecoder.AudioSampleReader; +import zero1hd.rhythmbullet.audio.wavedecoder.WAVSampleReader; import zero1hd.rhythmbullet.util.MusicManager; public class WAVManager implements MusicManager { @@ -23,13 +22,13 @@ public class WAVManager implements MusicManager { private FileHandle fileHandle; private AudioInputStream ais; private AudioFormat af; - private AudioSampleReader d; + private WAVSampleReader d; public WAVManager(FileHandle file) { this.fileHandle = file; try { ais = AudioSystem.getAudioInputStream(file.file()); - d = new AudioSampleReader(ais); + d = new WAVSampleReader(ais); af = ais.getFormat(); } catch (UnsupportedAudioFileException | IOException e) { e.printStackTrace(); @@ -138,54 +137,8 @@ public class WAVManager implements MusicManager { return fileHandle; } - public void seek(long position) { - restartStream(); - skip(position); - } - - private void restartStream() { - try { - ais.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - try { - ais = AudioSystem.getAudioInputStream(fileHandle.file()); - } catch (UnsupportedAudioFileException | IOException e) { - e.printStackTrace(); - } - } - @Override - public void synchronize() { - seek(MathUtils.round(getPositionInSeconds()*getSampleRate())); - readIndex = playbackIndex; - } - - @Override - public int getReadIndex() { + public int framesRead() { return readIndex; } - - public void skip(long bytes) { - long totalSkipped = 0; - long skipped = 0; - while (totalSkipped < bytes) { - try { - skipped = ais.skip(bytes-totalSkipped); - } catch (IOException e) { - e.printStackTrace(); - } - - if (skipped == 0) break; - totalSkipped += skipped; - } - } - - @Override - public void skipReadWindow() { - skip(af.getFrameSize()*readWindowSize); - readIndex++; - } }