Finished basic visualizer and mass refactoring on the audio analyzer to
be compatible with the structure
This commit is contained in:
@@ -3,11 +3,12 @@ package zero1hd.rhythmbullet.desktop.audio.processor;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
|
||||
import zero1hd.rhythmbullet.audio.AudioProcessorFactory;
|
||||
import zero1hd.rhythmbullet.audio.MinimalAudioHeader;
|
||||
import zero1hd.rhythmbullet.audio.processor.AudioProcessor;
|
||||
|
||||
public class DesktopAudioProcessorFactory implements AudioProcessorFactory {
|
||||
@Override
|
||||
public AudioProcessor newMP3AudioProcessor(FileHandle fileHandle) {
|
||||
return new MP3AudioProcessor(fileHandle);
|
||||
return new MP3AudioProcessor(fileHandle, new MinimalAudioHeader(fileHandle));
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package zero1hd.rhythmbullet.desktop.audio.processor;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
import com.badlogic.gdx.utils.GdxRuntimeException;
|
||||
|
||||
import javazoom.jl.decoder.Bitstream;
|
||||
import javazoom.jl.decoder.BitstreamException;
|
||||
@@ -10,12 +9,14 @@ import javazoom.jl.decoder.DecoderException;
|
||||
import javazoom.jl.decoder.Header;
|
||||
import javazoom.jl.decoder.MP3Decoder;
|
||||
import javazoom.jl.decoder.OutputBuffer;
|
||||
import zero1hd.rhythmbullet.audio.MinimalAudioHeader;
|
||||
import zero1hd.rhythmbullet.audio.processor.AudioProcessor;
|
||||
|
||||
|
||||
public class MP3AudioProcessor implements AudioProcessor {
|
||||
private boolean stereo;
|
||||
private int sampleRate;
|
||||
private long sampleFrames;
|
||||
private FileHandle fileHandle;
|
||||
private byte[] currentByteSet;
|
||||
private byte[] workset;
|
||||
@@ -25,30 +26,16 @@ public class MP3AudioProcessor implements AudioProcessor {
|
||||
private int indexHead = -1;
|
||||
|
||||
|
||||
public MP3AudioProcessor(FileHandle fileHandle) {
|
||||
public MP3AudioProcessor(FileHandle fileHandle, MinimalAudioHeader minimalAudioHeader) {
|
||||
this.fileHandle = fileHandle;
|
||||
|
||||
bitstream = new Bitstream(fileHandle.read());
|
||||
|
||||
try {
|
||||
Header header = bitstream.readFrame();
|
||||
if (header == null) throw new GdxRuntimeException("Empty MP3");
|
||||
stereo = header.mode() == Header.DUAL_CHANNEL;
|
||||
sampleRate = header.getSampleRate();
|
||||
} catch (BitstreamException e) {
|
||||
throw new GdxRuntimeException("error while preloading mp3", e);
|
||||
}
|
||||
|
||||
try {
|
||||
bitstream.close();
|
||||
} catch (BitstreamException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initiate() {
|
||||
bitstream = new Bitstream(fileHandle.read());
|
||||
|
||||
stereo = minimalAudioHeader.getChannelCount() == 1 ? false : true;
|
||||
sampleRate = minimalAudioHeader.getSampleRate();
|
||||
sampleFrames = minimalAudioHeader.estimateSampleFrames();
|
||||
|
||||
decoder = new MP3Decoder();
|
||||
sampleBuffer = new OutputBuffer(stereo ? 2 : 1, false);
|
||||
decoder.setOutputBuffer(sampleBuffer);
|
||||
@@ -67,8 +54,8 @@ public class MP3AudioProcessor implements AudioProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readSamples(short[] pcm, Object syncObj) {
|
||||
int framesRead = 0;
|
||||
public int readSamples(short[] pcm) {
|
||||
int samplesRead = 0;
|
||||
for (int sid = 0; sid < pcm.length; sid++) {
|
||||
for (int wsid = 0; wsid < workset.length; wsid++) {
|
||||
workset[wsid] = nextByte();
|
||||
@@ -78,9 +65,28 @@ public class MP3AudioProcessor implements AudioProcessor {
|
||||
if (stereo) {
|
||||
short altChan = (short) ((workset[3] << 8) + (workset[2] & 0x00ff));
|
||||
sid++;
|
||||
pcm[sid] = altChan;
|
||||
}
|
||||
samplesRead ++;
|
||||
}
|
||||
}
|
||||
return samplesRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readFrames(float[] pcm) {
|
||||
int framesRead = 0;
|
||||
for (int sid = 0; sid < pcm.length; sid++) {
|
||||
for (int wsid = 0; wsid < workset.length; wsid++) {
|
||||
workset[wsid] = nextByte();
|
||||
}
|
||||
if (currentByteSet != null) {
|
||||
pcm[sid] += (workset[1] << 8) + (workset[0] & 0x00ff);
|
||||
if (stereo) {
|
||||
short altChan = (short) ((workset[3] << 8) + (workset[2] & 0x00ff));
|
||||
pcm[sid] = altChan > pcm[sid] ? altChan : pcm[sid];
|
||||
}
|
||||
framesRead ++;
|
||||
framesRead++;
|
||||
pcm[sid] /= Short.MAX_VALUE+1;
|
||||
}
|
||||
}
|
||||
@@ -129,6 +135,16 @@ public class MP3AudioProcessor implements AudioProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileHandle getMusicFileHandle() {
|
||||
return fileHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSampleFrames() {
|
||||
return sampleFrames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
Gdx.app.debug("MP3Manager", "Disposing...");
|
||||
|
@@ -0,0 +1,112 @@
|
||||
package zero1hd.rhythmbullet.desktop.audio.visualizer;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.Batch;
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
|
||||
|
||||
import zero1hd.rhythmbullet.audio.MusicController;
|
||||
|
||||
public class DoubleHorizontalVisualizer {
|
||||
private int width, height, barCount, barWidth, spaceBetweenBars;
|
||||
private int x, y;
|
||||
private float barRate = 0.75f;
|
||||
private ShapeRenderer shapeRenderer;
|
||||
private PCMMachine pcm;
|
||||
private int smoothRange;
|
||||
private int multiplier = 300;
|
||||
private int[] amplitudes;
|
||||
private int[] barHeights;
|
||||
private int binsPerBar;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param barCount amount of bars this visualizer should have.
|
||||
* @param width the width of the visualizer.
|
||||
* @param spacePercentage the percentage of a bar that should be space.
|
||||
*/
|
||||
public DoubleHorizontalVisualizer(int barCount, int width, float spacePercentage, int height, MusicController musicController) {
|
||||
this.barCount = barCount;
|
||||
this.barWidth = width/barCount;
|
||||
this.spaceBetweenBars = (int) (barWidth * spacePercentage);
|
||||
this.barWidth -= spaceBetweenBars;
|
||||
if (barWidth < 1) throw new IllegalArgumentException("The arguments you passed caused the bar width to be 0.");
|
||||
binsPerBar = (pcm.getWindowSize()/barCount);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
pcm = new PCMMachine(musicController);
|
||||
amplitudes = new int[barCount];
|
||||
barHeights = new int[barCount];
|
||||
shapeRenderer = new ShapeRenderer();
|
||||
shapeRenderer.set(ShapeType.Filled);
|
||||
}
|
||||
|
||||
public void act(float delta) {
|
||||
for (int bar = 0; bar < amplitudes.length; bar++) {
|
||||
float normalizedAmplitude = 0;
|
||||
for (int freq = bar*binsPerBar; freq < bar*binsPerBar + binsPerBar; freq++) {
|
||||
normalizedAmplitude += Math.abs(pcm.getFrequencyBins()[freq]);
|
||||
}
|
||||
amplitudes[bar] = (int) (normalizedAmplitude*multiplier);
|
||||
amplitudes[bar] /= binsPerBar;
|
||||
|
||||
float cappedDelta = Math.max(0, delta);
|
||||
cappedDelta = Math.min(1f, delta);
|
||||
|
||||
barHeights[bar] += Math.max(0, (amplitudes[bar] - barHeights[bar]) * barRate * cappedDelta);
|
||||
|
||||
}
|
||||
for (int bar = 1; bar <= barHeights.length; bar++) {
|
||||
int smoothCount = 1;
|
||||
for (int range = 0; range < smoothRange; range++) {
|
||||
if (bar+range < amplitudes.length) {
|
||||
barHeights[bar] += amplitudes[bar+range];
|
||||
smoothCount++;
|
||||
}
|
||||
if (bar-range > 0) {
|
||||
barHeights[bar] += amplitudes[bar-range];
|
||||
smoothCount++;
|
||||
}
|
||||
}
|
||||
barHeights[bar] /= smoothCount;
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(Batch batch, float parentAlpha) {
|
||||
shapeRenderer.begin();
|
||||
int beginX = x + spaceBetweenBars/2, beginY = y;
|
||||
for (int bar = 0; bar < barCount; bar++) {
|
||||
shapeRenderer.rect(beginX + spaceBetweenBars*bar, beginY+height, beginX+barWidth, beginY+barHeights[bar]+height);
|
||||
shapeRenderer.rect(beginX + spaceBetweenBars*bar, beginY, beginX+barWidth, beginY+barHeights[barHeights.length - 1 - bar]);
|
||||
}
|
||||
shapeRenderer.end();
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setX(int x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public void setY(int y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void setPosition(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
}
|
@@ -11,17 +11,20 @@ import org.lwjgl.openal.AL11;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.backends.lwjgl.audio.OpenALMusic;
|
||||
import com.badlogic.gdx.utils.Disposable;
|
||||
import com.badlogic.gdx.utils.TimeUtils;
|
||||
import com.badlogic.gdx.utils.reflect.ClassReflection;
|
||||
import com.badlogic.gdx.utils.reflect.Field;
|
||||
import com.badlogic.gdx.utils.reflect.ReflectionException;
|
||||
|
||||
import zero1hd.rhythmbullet.audio.MinimalAudioHeader;
|
||||
import zero1hd.rhythmbullet.audio.MusicController;
|
||||
import zero1hd.rhythmbullet.audio.visualizer.BasicFFT;
|
||||
|
||||
public class DesktopVisualizer implements Observer {
|
||||
public class PCMMachine implements Observer, Disposable {
|
||||
private int windowSize = 1024;
|
||||
private float[] PCM = new float[windowSize];
|
||||
private float[] frequencyBins = new float[windowSize/2];
|
||||
private BasicFFT fft = new BasicFFT(windowSize);
|
||||
private ShortBuffer playingBuffer;
|
||||
private ShortBuffer compareBuffer;
|
||||
private ShortBuffer buffer;
|
||||
@@ -32,8 +35,11 @@ public class DesktopVisualizer implements Observer {
|
||||
private BufferStreamReadThread streamReadThread;
|
||||
private int windowsRead;
|
||||
private int currentPlaybackWindow;
|
||||
|
||||
public DesktopVisualizer() {
|
||||
private volatile boolean updated;
|
||||
|
||||
public PCMMachine(MusicController musicController) {
|
||||
this.mc = musicController;
|
||||
mc.addObserver(this);
|
||||
try {
|
||||
Field bufferField = ClassReflection.getDeclaredField(OpenALMusic.class, "tempBuffer");
|
||||
bufferField.setAccessible(true);
|
||||
@@ -109,7 +115,7 @@ public class DesktopVisualizer implements Observer {
|
||||
windowsRead = (int) ((mc.getCurrentPosition()*sampleRate)/windowSize);
|
||||
}
|
||||
|
||||
public void setMusic(MinimalAudioHeader header) {
|
||||
private void setMusic() {
|
||||
try {
|
||||
Field sourceIDField = ClassReflection.getDeclaredField(OpenALMusic.class, "sourceID");
|
||||
sourceIDField.setAccessible(true);
|
||||
@@ -117,8 +123,9 @@ public class DesktopVisualizer implements Observer {
|
||||
} catch (ReflectionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
channelCount = header.getChannelCount();
|
||||
sampleRate = header.getSampleRate();
|
||||
|
||||
channelCount = mc.getCurrentMusicHeader().getChannelCount();
|
||||
sampleRate = mc.getCurrentMusicHeader().getSampleRate();
|
||||
|
||||
playingBuffer = ShortBuffer.allocate(buffer.capacity()*2);
|
||||
buffer.rewind();
|
||||
@@ -133,17 +140,28 @@ public class DesktopVisualizer implements Observer {
|
||||
buffer.rewind();
|
||||
}
|
||||
|
||||
public synchronized float[] getAudioPCM() {
|
||||
return PCM;
|
||||
public float[] getFrequencyBins() {
|
||||
if (updated) {
|
||||
synchronized (PCM) {
|
||||
fft.fft(PCM);
|
||||
System.arraycopy(PCM, 0, frequencyBins, 0, frequencyBins.length);
|
||||
}
|
||||
}
|
||||
return frequencyBins;
|
||||
}
|
||||
|
||||
public class BufferStreamReadThread implements Runnable {
|
||||
public int getWindowSize() {
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
private class BufferStreamReadThread implements Runnable {
|
||||
private String name = "PCM-Audio-Processing";
|
||||
private Thread thread;
|
||||
private boolean run, paused;
|
||||
private volatile long timeOfLastRead;
|
||||
private volatile boolean run;
|
||||
private boolean paused;
|
||||
private long timeOfLastRead;
|
||||
private int waitTime;
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (run) {
|
||||
@@ -155,10 +173,10 @@ public class DesktopVisualizer implements Observer {
|
||||
waitTime = sampleRate/windowSize/Gdx.graphics.getFramesPerSecond();
|
||||
if (TimeUtils.timeSinceMillis(timeOfLastRead) >= waitTime) {
|
||||
calcPCMData();
|
||||
updated = true;
|
||||
windowsRead++;
|
||||
timeOfLastRead = TimeUtils.millis();
|
||||
|
||||
|
||||
currentPlaybackWindow = (int) ((mc.getCurrentPosition()*sampleRate)/windowSize);
|
||||
if (windowsRead != currentPlaybackWindow) {
|
||||
synchronizeBufferWithPlayback();
|
||||
@@ -177,14 +195,20 @@ public class DesktopVisualizer implements Observer {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void start() {
|
||||
public void start() {
|
||||
if (thread == null && !thread.isAlive()) {
|
||||
thread = new Thread(this, name);
|
||||
thread.start();
|
||||
} else {
|
||||
notify();
|
||||
synchronized (this) {
|
||||
notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
run = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,7 +216,7 @@ public class DesktopVisualizer implements Observer {
|
||||
if (o == mc) {
|
||||
switch ((MusicController.States) arg) {
|
||||
case Loaded:
|
||||
setMusic(mc.getCurrentMusicHeader());
|
||||
setMusic();
|
||||
break;
|
||||
case Playing:
|
||||
streamReadThread.start();
|
||||
@@ -202,4 +226,9 @@ public class DesktopVisualizer implements Observer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
streamReadThread.stop();
|
||||
}
|
||||
}
|
@@ -8,7 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Widget;
|
||||
import com.badlogic.gdx.utils.Disposable;
|
||||
|
||||
import zero1hd.rhythmbullet.audio.visualizer.HorizontalVisualizer;
|
||||
import zero1hd.rhythmbullet.desktop.audio.visualizer.DesktopVisualizer;
|
||||
import zero1hd.rhythmbullet.desktop.audio.visualizer.PCMMachine;
|
||||
|
||||
public class HorizontalVisualizerWidget extends Widget implements Disposable {
|
||||
private HorizontalVisualizer vis;
|
||||
@@ -18,7 +18,7 @@ public class HorizontalVisualizerWidget extends Widget implements Disposable {
|
||||
private float visRefreshRate;
|
||||
private float timer;
|
||||
public HorizontalVisualizerWidget() {
|
||||
vis = new HorizontalVisualizer(new DesktopVisualizer());
|
||||
vis = new HorizontalVisualizer(new PCMMachine());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -11,7 +11,7 @@ import com.badlogic.gdx.utils.viewport.ExtendViewport;
|
||||
|
||||
import zero1hd.rhythmbullet.RhythmBullet;
|
||||
import zero1hd.rhythmbullet.audio.visualizer.CircularVisualizer;
|
||||
import zero1hd.rhythmbullet.desktop.audio.visualizer.DesktopVisualizer;
|
||||
import zero1hd.rhythmbullet.desktop.audio.visualizer.PCMMachine;
|
||||
import zero1hd.rhythmbullet.game.GameController;
|
||||
|
||||
public class GameScreen extends ScreenAdapter {
|
||||
@@ -25,7 +25,7 @@ public class GameScreen extends ScreenAdapter {
|
||||
this.assets = assets;
|
||||
batch = new SpriteBatch();
|
||||
viewport = new ExtendViewport(RhythmBullet.WORLD_WIDTH, RhythmBullet.WORLD_HEIGHT);
|
||||
circleVisualizer = new CircularVisualizer(new DesktopVisualizer());
|
||||
circleVisualizer = new CircularVisualizer(new PCMMachine());
|
||||
circleVisualizer.setCenter(Gdx.graphics.getWidth()/2, Gdx.graphics.getHeight()/2);
|
||||
circleVisualizer.setCamera(viewport.getCamera());
|
||||
circleVisualizer.setColor(Color.CYAN.toFloatBits());
|
||||
|
Reference in New Issue
Block a user