reworked the reading system as previous system was not working; not yet

tested;
This commit is contained in:
Harrison Deng 2017-12-15 12:28:11 -06:00
parent 25142029af
commit 677ada45b6
6 changed files with 205 additions and 308 deletions

View File

@ -73,6 +73,7 @@ project(":core") {
compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
compile group: 'com.badlogicgames.jlayer', name: 'jlayer', version: '1.0.1-gdx' compile group: 'com.badlogicgames.jlayer', name: 'jlayer', version: '1.0.1-gdx'
compile group: 'com.googlecode.soundlibs', name: 'mp3spi', version: '1.9.5-1'
compile "org.apache.commons:commons-math3:3.2" compile "org.apache.commons:commons-math3:3.2"
compile "com.github.rwl:jtransforms:2.4.0" compile "com.github.rwl:jtransforms:2.4.0"

View File

@ -1,121 +1,69 @@
package zero1hd.rhythmbullet.audio; package zero1hd.rhythmbullet.audio;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.util.Map;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.security.InvalidParameterException;
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 org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; import javax.sound.sampled.AudioFormat;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; import javax.sound.sampled.AudioInputStream;
import org.jaudiotagger.audio.mp3.MP3File; import javax.sound.sampled.AudioSystem;
import org.jaudiotagger.tag.TagException; import javax.sound.sampled.UnsupportedAudioFileException;
import org.tritonus.share.sampled.TAudioFormat;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Music.OnCompletionListener; import com.badlogic.gdx.audio.Music.OnCompletionListener;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.GdxRuntimeException;
import javazoom.jl.decoder.Bitstream; import zero1hd.rhythmbullet.audio.wavedecoder.AudioSampleReader;
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;
public class Mp3Manager implements MusicManager { public class Mp3Manager implements MusicManager {
private int readWindowSize = 1024; private int readWindowSize = 1024;
private Music music;
private int readWindowIndex;
private Music playbackMusic;
private int playbackIndex; private int playbackIndex;
private FileHandle fileHandle;
private AudioInputStream ais;
private AudioFormat af;
private AudioSampleReader d;
private Map<String, Object> properties;
private long bitrate; public Mp3Manager(FileHandle file) {
private int sampleRate; this.fileHandle = file;
private long sampleCount;
private double durationInSeconds;
private byte channels;
Bitstream bitstream;
MP3Decoder decoder;
OutputBuffer sampleBuffer;
InputStream inputStream;
private byte[] currentByteSet;
private byte[] workset;
private int indexHead = -1;
private RandomAccessFile raf;
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();
bitrate = mp3File.getAudioHeader().getBitRateAsNumber();
} catch (IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace();
}
lock.unlock();
});
try { try {
raf = new RandomAccessFile(audioFile.file(), "r"); AudioInputStream in;
bitstream = new Bitstream(inputStream = Channels.newInputStream(raf.getChannel())); in = AudioSystem.getAudioInputStream(file.file());
decoder = new MP3Decoder(); AudioFormat baseFormat = in.getFormat();
} catch (FileNotFoundException e2) { properties = ((TAudioFormat)baseFormat).properties();
e2.printStackTrace(); af = new AudioFormat(
throw new InvalidParameterException("Error with creating RandomAccessFile."); AudioFormat.Encoding.PCM_SIGNED,
} baseFormat.getSampleRate(),
try { 16, baseFormat.getChannels(),
Header header = bitstream.readFrame(); baseFormat.getChannels()*2,
if (header == null) throw new GdxRuntimeException("Empty MP3"); baseFormat.getSampleRate(), false);
channels = (byte) (header.mode() == Header.SINGLE_CHANNEL ? 1 : 2); ais = AudioSystem.getAudioInputStream(baseFormat, in);
sampleBuffer = new OutputBuffer(channels, false); in.close();
decoder.setOutputBuffer(sampleBuffer); d = new AudioSampleReader(ais);
workset = new byte[channels*2]; music = Gdx.audio.newMusic(file);
} catch (BitstreamException e) { } catch (UnsupportedAudioFileException | IOException e) {
throw new GdxRuntimeException("error while preloading mp3", e);
}
playbackMusic = Gdx.audio.newMusic(audioFile);
}
@Override
public void setReadIndexToPlaybackIndex() {
try {
long pointer = (long) (getPositionInSeconds()*(bitrate*sampleRate/8f));
raf.seek(pointer);
System.out.println(pointer);
readWindowIndex = playbackIndex;
currentByteSet = null;
} catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override
public void dispose() {
music.dispose();
try {
ais.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override @Override
public void playbackIndexUpdate() { public void playbackIndexUpdate() {
playbackIndex = (int) (playbackMusic.getPosition() * sampleRate / readWindowSize); playbackIndex = (int) ((getDuration()*getSampleRate())/getReadWindowSize());
} }
@Override @Override
@ -128,172 +76,122 @@ public class Mp3Manager implements MusicManager {
return readWindowSize; return readWindowSize;
} }
@Override
public int readSamples(float[] samples) {
try {
return d.readSamples(samples);
} catch (IOException e) {
e.printStackTrace();
};
return 0;
}
@Override @Override
public long getSampleCount() { public long getSampleCount() {
return sampleCount; return ais.getFrameLength();
} }
@Override @Override
public float getDuration() { public float getDuration() {
return Float.valueOf(String.valueOf(durationInSeconds)); return ((long) properties.get("duration"))/1000000f;
} }
@Override
public int readSamples(float[] samples) {
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;
}
}
readWindowIndex++;
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 @Override
public float getSampleRate() { public float getSampleRate() {
return sampleRate; return af.getSampleRate();
}
@Override
public long getBitrate() {
return bitrate;
}
@Override
public int getreadWindowIndex() {
return readWindowIndex;
} }
@Override @Override
public void pause() { public void pause() {
Gdx.app.debug("MP3Manager", "Pausing..."); music.pause();
playbackMusic.pause();
} }
@Override @Override
public void play() { public void play() {
Gdx.app.debug("Mp3Manager", "MP3 Playing..."); music.play();
playbackMusic.play();
} }
@Override @Override
public boolean isPlaying() { public boolean isPlaying() {
return playbackMusic.isPlaying(); return music.isPlaying();
} }
@Override @Override
public float getPositionInSeconds() { public float getPositionInSeconds() {
return playbackMusic.getPosition();
return music.getPosition();
} }
@Override @Override
public void setPosition(float position) { public void setPosition(float position) {
playbackMusic.setPosition(position); music.setPosition(position);
} }
@Override @Override
public void setOnCompletionListener(OnCompletionListener listener) { public void setOnCompletionListener(OnCompletionListener listener) {
playbackMusic.setOnCompletionListener(listener); music.setOnCompletionListener(listener);
} }
@Override @Override
public void setVolume(float percent) { public void setVolume(float percent) {
playbackMusic.setVolume(percent); music.setVolume(percent);
} }
@Override @Override
public boolean isFinishedLoading() { public boolean isFinishedLoading() {
if (lock.isHeldByCurrentThread()) { return true;
return true;
} else {
try {
if (lock.tryLock(0, TimeUnit.SECONDS)) {
return true;
} else {
return false;
}
} catch (InterruptedException e) {
return false;
}
}
} }
@Override @Override
public String getBasicSongName() { public String getBasicSongName() {
return basicSongName; return fileHandle.nameWithoutExtension();
} }
@Override @Override
public FileHandle getMusicFile() { public FileHandle getMusicFile() {
return file; return fileHandle;
}
@Override
public void seek(long position) {
long totalSkipped = 0;
long skipped = 0;
closeStream();
reInitStream();
while (totalSkipped < position) {
try {
skipped = ais.skip(position-totalSkipped);
} catch (IOException e) {
e.printStackTrace();
}
if (skipped == 0) break;
totalSkipped += skipped;
}
} }
@Override private void closeStream() {
public void dispose() { if (ais != null) {
Gdx.app.debug("MP3Manager", "Disposing..."); try {
playbackMusic.stop(); ais.close();
playbackMusic.dispose(); } catch (IOException e) {
exec.shutdown(); e.printStackTrace();
}
}
}
private void reInitStream() {
try { try {
bitstream.close(); AudioInputStream in = AudioSystem.getAudioInputStream(fileHandle.file());
bitstream = null; ais = AudioSystem.getAudioInputStream(af, in);
inputStream.close(); in.close();
} catch (BitstreamException | IOException e) { } catch (UnsupportedAudioFileException | IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override
public void synchronize() {
seek(MathUtils.round(((Integer) properties.get("audio.length.bytes")).intValue() * (getPositionInSeconds()/getDuration())));
}
} }

View File

@ -50,16 +50,6 @@ public interface MusicManager extends Disposable {
*/ */
public float getSampleRate(); public float getSampleRate();
/**
* @return the bitrate of the audio.
*/
public long getBitrate();
/**
* The current numerical value of the last window read
* @return
*/
public int getreadWindowIndex();
public void pause(); public void pause();
public void play(); public void play();
@ -93,7 +83,13 @@ public interface MusicManager extends Disposable {
public FileHandle getMusicFile(); public FileHandle getMusicFile();
/** /**
* Synchronizes reading to current playback. * sets the reading position to the given position;
* @param position the byte to skip to.
*/ */
public void setReadIndexToPlaybackIndex(); public void seek(long position);
/**
* Synchronizes the playback and read threads
*/
public void synchronize();
} }

View File

@ -1,167 +1,177 @@
package zero1hd.rhythmbullet.audio; package zero1hd.rhythmbullet.audio;
import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.security.InvalidParameterException;
import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Music.OnCompletionListener; import com.badlogic.gdx.audio.Music.OnCompletionListener;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.math.MathUtils;
import zero1hd.rhythmbullet.audio.wavedecoder.WavDecoder; import zero1hd.rhythmbullet.audio.wavedecoder.AudioSampleReader;
public class WAVManager implements MusicManager { public class WAVManager implements MusicManager {
private int readWindowSize = 1024; private int readWindowSize = 1024;
private AudioFormat format;
private int playbackIndex; private int playbackIndex;
private int readWindowIndex; private Music music;
private Music playbackMusic; private FileHandle fileHandle;
private WavDecoder decoder; private AudioInputStream ais;
private FileHandle file; private AudioFormat af;
private String basicSongName; private AudioSampleReader d;
private RandomAccessFile raf;
private int sampleSizeInBits;
public WAVManager(FileHandle file) { public WAVManager(FileHandle file) {
this.file = file; this.fileHandle = file;
basicSongName = file.name();
try { try {
raf = new RandomAccessFile(file.file(), "r"); ais = AudioSystem.getAudioInputStream(file.file());
decoder = new WavDecoder(new BufferedInputStream(Channels.newInputStream(raf.getChannel()))); d = new AudioSampleReader(ais);
} catch (InvalidParameterException | IOException e) { af = ais.getFormat();
// TODO Auto-generated catch block } catch (UnsupportedAudioFileException | IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
playbackMusic = Gdx.audio.newMusic(file); music = Gdx.audio.newMusic(file);
format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, (float) decoder.getSampleRate(), decoder.getSampleSizeInBits(), decoder.getChannels(), decoder.getChannels()*2, (float) decoder.getSampleRate(), false);
sampleSizeInBits = decoder.getSampleSizeInBits();
} }
@Override @Override
public void playbackIndexUpdate() { public void dispose() {
playbackIndex = (int) (playbackMusic.getPosition() * decoder.getSampleRate() / readWindowSize); music.dispose();
try {
ais.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
@Override
public void playbackIndexUpdate() {
playbackIndex = (int) ((getSampleRate() * getDuration())/getReadWindowSize());
}
@Override @Override
public int getPlaybackIndexPosition() { public int getPlaybackIndexPosition() {
return playbackIndex; return playbackIndex;
} }
@Override @Override
public int getReadWindowSize() { public int getReadWindowSize() {
return readWindowSize; return readWindowSize;
} }
@Override
public void setReadIndexToPlaybackIndex() {
try {
long pointer = (long) (getPositionInSeconds()*(getBitrate()/8f));
raf.seek(pointer);
readWindowIndex = playbackIndex;
} catch (IOException e) {
e.printStackTrace();
}
}
@Override @Override
public int readSamples(float[] samples) { public int readSamples(float[] samples) {
int samplesRead = 0;
try { try {
samplesRead = decoder.readSamples(samples); return d.readSamples(samples);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
return 0;
if (samplesRead != 0) {
readWindowIndex++;
}
return samplesRead;
} }
@Override @Override
public long getSampleCount() { public long getSampleCount() {
return decoder.getFrameLength(); return ais.getFrameLength();
}
@Override
public void dispose() {
playbackMusic.stop();
playbackMusic.dispose();
decoder.cleanAndClose();
} }
@Override @Override
public float getDuration() { public float getDuration() {
return decoder.getDurationInSeconds(); return ais.getFrameLength()/af.getFrameRate();
} }
@Override @Override
public float getSampleRate() { public float getSampleRate() {
return format.getSampleRate(); return af.getSampleRate();
}
@Override
public int getreadWindowIndex() {
return readWindowIndex;
} }
@Override @Override
public void pause() { public void pause() {
playbackMusic.pause(); music.pause();
} }
@Override @Override
public void play() { public void play() {
playbackMusic.play(); music.play();
} }
@Override @Override
public boolean isPlaying() { public boolean isPlaying() {
return playbackMusic.isPlaying(); return music.isPlaying();
} }
@Override @Override
public float getPositionInSeconds() { public float getPositionInSeconds() {
return playbackMusic.getPosition(); return music.getPosition();
} }
@Override @Override
public void setPosition(float position) { public void setPosition(float position) {
playbackMusic.setPosition(position); music.setPosition(position);
} }
@Override @Override
public void setOnCompletionListener(OnCompletionListener listener) { public void setOnCompletionListener(OnCompletionListener listener) {
playbackMusic.setOnCompletionListener(listener); music.setOnCompletionListener(listener);
} }
@Override @Override
public void setVolume(float percent) { public void setVolume(float percent) {
playbackMusic.setVolume(percent); music.setVolume(percent);
} }
@Override @Override
public boolean isFinishedLoading() { public boolean isFinishedLoading() {
return true; return true;
} }
@Override @Override
public String getBasicSongName() { public String getBasicSongName() {
return basicSongName; return fileHandle.nameWithoutExtension();
} }
@Override @Override
public FileHandle getMusicFile() { public FileHandle getMusicFile() {
return file; return fileHandle;
} }
@Override @Override
public long getBitrate() { public void seek(long position) {
return (long) (getSampleRate()*sampleSizeInBits); long totalSkipped = 0;
long skipped = 0;
restartStream();
while (totalSkipped < position) {
try {
skipped = ais.skip(position-totalSkipped);
} catch (IOException e) {
e.printStackTrace();
}
if (skipped == 0) break;
totalSkipped += skipped;
}
}
private void restartStream() {
try {
ais.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
AudioInputStream in = AudioSystem.getAudioInputStream(fileHandle.file());
ais = AudioSystem.getAudioInputStream(af, in);
in.close();
} catch (UnsupportedAudioFileException | IOException e) {
e.printStackTrace();
}
}
@Override
public void synchronize() {
seek(MathUtils.round(getPositionInSeconds()*getSampleRate()));
} }
} }

View File

@ -1,30 +1,23 @@
package zero1hd.rhythmbullet.audio.wavedecoder; package zero1hd.rhythmbullet.audio.wavedecoder;
import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
public class WavDecoder { public class AudioSampleReader {
private int channels; private int channels;
private double sampleRate; private double sampleRate;
private byte[] buffer; private byte[] buffer;
private int sampleSize; private int sampleSize;
private AudioInputStream audioInputStream; private AudioInputStream audioInputStream;
public WavDecoder(BufferedInputStream inputStream) throws IOException { public AudioSampleReader(AudioInputStream ais) throws IOException {
try { audioInputStream = ais;
audioInputStream = AudioSystem.getAudioInputStream(inputStream); buffer = new byte[audioInputStream.getFormat().getFrameSize()];
buffer = new byte[audioInputStream.getFormat().getFrameSize()];
channels = audioInputStream.getFormat().getChannels();
channels = audioInputStream.getFormat().getChannels(); sampleRate = audioInputStream.getFormat().getSampleRate();
sampleRate = audioInputStream.getFormat().getSampleRate(); sampleSize = audioInputStream.getFormat().getSampleSizeInBits();
sampleSize = audioInputStream.getFormat().getSampleSizeInBits();
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
}
} }
public int getChannels() { public int getChannels() {

View File

@ -29,8 +29,7 @@ public class Visualizer extends Widget implements Disposable {
if (mm != null && mm.isFinishedLoading() && !mmSet) { if (mm != null && mm.isFinishedLoading() && !mmSet) {
Gdx.app.debug("Visualizer", "\nsample count: " + mm.getSampleCount() Gdx.app.debug("Visualizer", "\nsample count: " + mm.getSampleCount()
+ "\nDuration in seconds: " + mm.getDuration() + + "\nDuration in seconds: " + mm.getDuration() +
"\nSample rate: " + mm.getSampleRate() "\nSample rate: " + mm.getSampleRate());
+ "\nBit rate: " + mm.getBitrate());
vis.setMM(mm); vis.setMM(mm);
mmSet = true; mmSet = true;
} }