cleaning up and preparing for reflection visuals and music synchronization

This commit is contained in:
Harrison Deng 2018-01-03 22:39:43 -06:00
parent 2b1b8ba620
commit f292d6f81c
5 changed files with 183 additions and 185 deletions

View File

@ -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);

View File

@ -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()];

View File

@ -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();
}

View File

@ -1,70 +1,98 @@
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 Music playbackMusic;
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 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 {
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) {
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 {
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);
}
@Override
public void dispose() {
music.dispose();
try {
ais.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
playbackMusic = Gdx.audio.newMusic(audioFile);
}
@Override
public void playbackIndexUpdate() {
playbackIndex = (int) ((getPositionInSeconds()*getSampleRate())/getReadWindowSize());
playbackIndex = (int) (playbackMusic.getPosition() * sampleRate / readWindowSize);
}
@Override
@ -72,143 +100,170 @@ public class Mp3Manager implements MusicManager {
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 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() {
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;
return file;
}
public void seek(long position) {
closeStream();
reInitStream();
skip(position);
}
private void closeStream() {
if (ais != null) {
@Override
public void dispose() {
Gdx.app.debug("MP3Manager", "Disposing...");
playbackMusic.stop();
playbackMusic.dispose();
exec.shutdown();
try {
ais.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void reInitStream() {
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 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;
public int framesRead() {
return readIndex;
}
}
@Override
public void skipReadWindow() {
skip(readWindowSize*af.getFrameSize());
readIndex++;
}
}

View File

@ -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++;
}
}