diff --git a/core/src/zero1hd/rhythmbullet/RhythmBullet.java b/core/src/zero1hd/rhythmbullet/RhythmBullet.java index b8d9db8..a9496f2 100755 --- a/core/src/zero1hd/rhythmbullet/RhythmBullet.java +++ b/core/src/zero1hd/rhythmbullet/RhythmBullet.java @@ -203,4 +203,5 @@ public class RhythmBullet extends Game { } super.dispose(); } + } diff --git a/core/src/zero1hd/rhythmbullet/audio/visualizer/BasicFFT.java b/core/src/zero1hd/rhythmbullet/audio/visualizer/BasicFFT.java deleted file mode 100755 index c899731..0000000 --- a/core/src/zero1hd/rhythmbullet/audio/visualizer/BasicFFT.java +++ /dev/null @@ -1,15 +0,0 @@ -package zero1hd.rhythmbullet.audio.visualizer; - -import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D; - -public class BasicFFT { - private FloatFFT_1D fft; - - public BasicFFT(int window) { - fft = new FloatFFT_1D(window); - } - - public void fft(float[] PCM) { - fft.realForward(PCM); - } -} diff --git a/core/src/zero1hd/rhythmbullet/audio/visualizer/DoubleHorizontalVisualizer.java b/core/src/zero1hd/rhythmbullet/audio/visualizer/DoubleHorizontalVisualizer.java old mode 100644 new mode 100755 index b8175f9..7e8bc0a --- a/core/src/zero1hd/rhythmbullet/audio/visualizer/DoubleHorizontalVisualizer.java +++ b/core/src/zero1hd/rhythmbullet/audio/visualizer/DoubleHorizontalVisualizer.java @@ -3,21 +3,22 @@ package zero1hd.rhythmbullet.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 com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.Disposable; import zero1hd.rhythmbullet.audio.MusicController; -public class DoubleHorizontalVisualizer { +public class DoubleHorizontalVisualizer implements Disposable { private int width, height, barCount, barWidth, spaceBetweenBars; private int x, y; - private float barRate = 0.75f; private ShapeRenderer shapeRenderer; private PCMSystem pcm; - private int smoothRange; - private int multiplier = 300; - private int[] amplitudes; + private float[] amplitudes; private int[] barHeights; private int binsPerBar; - + private float normalFactor = 20; + private float barChangeRate = 4f; + private int smoothRange = 1; /** * * @param barCount amount of bars this visualizer should have. @@ -27,14 +28,14 @@ public class DoubleHorizontalVisualizer { public DoubleHorizontalVisualizer(int barCount, int width, int height, float spacePercentage, MusicController musicController, PCMSystem PCMSystem) { this.barCount = barCount; this.barWidth = width/barCount; - this.spaceBetweenBars = (int) (barWidth * spacePercentage); + this.spaceBetweenBars = MathUtils.round(barWidth * spacePercentage); this.barWidth -= spaceBetweenBars; pcm = PCMSystem; if (barWidth < 1) throw new IllegalArgumentException("The arguments you passed caused the bar width to be 0."); binsPerBar = (pcm.getFrequencyBins().length/barCount); this.width = width; this.height = height; - amplitudes = new int[barCount]; + amplitudes = new float[barCount]; barHeights = new int[barCount]; shapeRenderer = new ShapeRenderer(); } @@ -45,26 +46,29 @@ public class DoubleHorizontalVisualizer { for (int freq = bar*binsPerBar; freq < (bar*binsPerBar) + binsPerBar; freq++) { normalizedAmplitude += Math.abs(pcm.getFrequencyBins()[freq]); } - amplitudes[bar] = (int) (normalizedAmplitude*multiplier); + amplitudes[bar] += normalizedAmplitude * normalFactor; amplitudes[bar] /= binsPerBar; - - barHeights[bar] += Math.max(0, (amplitudes[bar] - barHeights[bar]) * barRate * delta); - if (barHeights[bar] > amplitudes[bar]) barHeights[bar] = amplitudes[bar]; - } for (int bar = 0; bar < barHeights.length; bar++) { int smoothCount = 1; for (int range = 0; range < smoothRange; range++) { if (bar+range < amplitudes.length) { - barHeights[bar] += amplitudes[bar+range]; + amplitudes[bar] += amplitudes[bar+range]; smoothCount++; } if (bar-range > 0) { - barHeights[bar] += amplitudes[bar-range]; + amplitudes[bar] += amplitudes[bar-range]; smoothCount++; } } - barHeights[bar] /= smoothCount; + amplitudes[bar] /= smoothCount; + + int pixelsMoved = MathUtils.round(amplitudes[bar] - barHeights[bar]); + pixelsMoved = MathUtils.floor(pixelsMoved*delta*barChangeRate); + barHeights[bar] += pixelsMoved; + + if (barHeights[bar] < 0) barHeights[bar] = 0; + if (barHeights[bar] > height) barHeights[bar] = height; } } @@ -113,4 +117,9 @@ public class DoubleHorizontalVisualizer { public int getY() { return y; } + + @Override + public void dispose() { + pcm.dispose(); + } } diff --git a/core/src/zero1hd/rhythmbullet/audio/visualizer/PCMSystem.java b/core/src/zero1hd/rhythmbullet/audio/visualizer/PCMSystem.java index 8cdf0d7..125d981 100755 --- a/core/src/zero1hd/rhythmbullet/audio/visualizer/PCMSystem.java +++ b/core/src/zero1hd/rhythmbullet/audio/visualizer/PCMSystem.java @@ -1,6 +1,8 @@ package zero1hd.rhythmbullet.audio.visualizer; -public interface PCMSystem { +import com.badlogic.gdx.utils.Disposable; + +public interface PCMSystem extends Disposable { float[] getFrequencyBins(); diff --git a/desktop/src/zero1hd/rhythmbullet/desktop/audio/PCMObtainer.java b/desktop/src/zero1hd/rhythmbullet/desktop/audio/PCMObtainer.java index 438cf50..ee5558c 100755 --- a/desktop/src/zero1hd/rhythmbullet/desktop/audio/PCMObtainer.java +++ b/desktop/src/zero1hd/rhythmbullet/desktop/audio/PCMObtainer.java @@ -2,8 +2,10 @@ package zero1hd.rhythmbullet.desktop.audio; import static org.lwjgl.openal.AL10.alGetSourcef; +import java.math.RoundingMode; import java.nio.ByteBuffer; import java.nio.ShortBuffer; +import java.text.DecimalFormat; import java.util.Observable; import java.util.Observer; @@ -11,33 +13,35 @@ 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.math.MathUtils; 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 edu.emory.mathcs.jtransforms.fft.FloatFFT_1D; import zero1hd.rhythmbullet.audio.MusicController; -import zero1hd.rhythmbullet.audio.visualizer.BasicFFT; import zero1hd.rhythmbullet.audio.visualizer.PCMSystem; -public class PCMObtainer implements Observer, Disposable, PCMSystem { +public class PCMObtainer implements Observer, PCMSystem { private int windowSize = 1024; + private int sampleRate; + private long millisPerWindow; + private DecimalFormat df; private float[] PCM = new float[windowSize]; private float[] frequencyBins = new float[windowSize / 2]; - private BasicFFT fft = new BasicFFT(windowSize); + private FloatFFT_1D fft = new FloatFFT_1D(windowSize); private ShortBuffer playingBuffer; private ShortBuffer compareBuffer; private ShortBuffer buffer; private int sourceID; private int channelCount; - private int sampleRate; private MusicController mc; private BufferStreamReadThread streamReadThread; private int windowsRead; private int currentPlaybackWindow; private volatile boolean updated; - + public PCMObtainer(MusicController musicController) { this.mc = musicController; mc.addObserver(this); @@ -51,6 +55,9 @@ public class PCMObtainer implements Observer, Disposable, PCMSystem { } streamReadThread = new BufferStreamReadThread(); + + df = new DecimalFormat("#.###"); + df.setRoundingMode(RoundingMode.HALF_EVEN); } private synchronized void calcPCMData() { @@ -95,7 +102,7 @@ public class PCMObtainer implements Observer, Disposable, PCMSystem { } // put the new buffer into the remaining portion. playingBuffer.put(compareBuffer); - + synchronizeBufferWithPlayback(); } @@ -116,7 +123,8 @@ public class PCMObtainer implements Observer, Disposable, PCMSystem { channelCount = mc.getCurrentMusicHeader().getChannelCount(); sampleRate = mc.getCurrentMusicHeader().getSampleRate(); - + String millisPerWindowF = df.format(windowSize/(float) sampleRate); + millisPerWindow = (long) (Float.valueOf(millisPerWindowF)*1000); playingBuffer = ShortBuffer.allocate(buffer.capacity() * 2); buffer.rewind(); playingBuffer.put(buffer); @@ -134,7 +142,6 @@ public class PCMObtainer implements Observer, Disposable, PCMSystem { public float[] getFrequencyBins() { if (updated) { synchronized (this) { - fft.fft(PCM); System.arraycopy(PCM, 1, frequencyBins, 0, frequencyBins.length); } } @@ -150,34 +157,50 @@ public class PCMObtainer implements Observer, Disposable, PCMSystem { private String name = "PCM-Audio-Processing"; private Thread thread; private volatile boolean run = true; - private boolean paused; private long timeOfLastRead; - private int waitTime; - + private long waitTime; + private int syncC, normC; + private float syncPercentage; @Override public void run() { while (run) { if (mc.isPlaying()) { - if (paused) { - timeOfLastRead = TimeUtils.millis(); - paused = false; - } - waitTime = sampleRate / windowSize / Gdx.graphics.getFramesPerSecond(); - if (TimeUtils.timeSinceMillis(timeOfLastRead) >= waitTime) { - calcPCMData(); - updated = true; - windowsRead++; - timeOfLastRead = TimeUtils.millis(); + //record time of read + timeOfLastRead = TimeUtils.millis(); + + //calculate current pcm data and notify that there is new data + calcPCMData(); + fft.realForward(PCM); + updated = true; + windowsRead++; - currentPlaybackWindow = (int) ((mc.getCurrentPosition() * sampleRate) / windowSize); - if (windowsRead != currentPlaybackWindow) { - synchronizeBufferWithPlayback(); + //contemplate synchronization + try { + currentPlaybackWindow = MathUtils.round((mc.getCurrentPosition() * sampleRate) / windowSize); + } catch (UnsatisfiedLinkError ule) { + if (run) { + ule.printStackTrace(); } } + if (windowsRead != currentPlaybackWindow) { + synchronizeBufferWithPlayback(); + syncC++; + } else { + normC++; + } + syncPercentage = (syncC*100)/(float)(normC+syncC); + System.out.printf("%.2f", syncPercentage); + System.out.print("%\n"); + //wait for a bit before reading again depending on the speed at which the system does playback. + waitTime = Math.max(0, millisPerWindow - TimeUtils.timeSinceMillis(timeOfLastRead)); + try { + Thread.sleep(waitTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } } else { synchronized (this) { try { - paused = true; wait(); } catch (InterruptedException e) { e.printStackTrace(); @@ -199,6 +222,7 @@ public class PCMObtainer implements Observer, Disposable, PCMSystem { } public void stop() { + Gdx.app.debug("PCMObtainer", "stopping " + thread.getName()); run = false; } } diff --git a/desktop/src/zero1hd/rhythmbullet/desktop/graphics/ui/components/GraphicsOptions.java b/desktop/src/zero1hd/rhythmbullet/desktop/graphics/ui/components/GraphicsOptions.java index 2b54b6f..f89ec64 100755 --- a/desktop/src/zero1hd/rhythmbullet/desktop/graphics/ui/components/GraphicsOptions.java +++ b/desktop/src/zero1hd/rhythmbullet/desktop/graphics/ui/components/GraphicsOptions.java @@ -91,7 +91,7 @@ public class GraphicsOptions extends Table { pack(); } - public void save() { + public void saveOptions() { Gdx.app.debug("Preferences", "Saved shading values values."); prefs.putInteger("glow shader", (int) glowShaderLevel.getValue()); } diff --git a/desktop/src/zero1hd/rhythmbullet/desktop/graphics/ui/pages/Page.java b/desktop/src/zero1hd/rhythmbullet/desktop/graphics/ui/pages/Page.java index b5336a9..7400e03 100755 --- a/desktop/src/zero1hd/rhythmbullet/desktop/graphics/ui/pages/Page.java +++ b/desktop/src/zero1hd/rhythmbullet/desktop/graphics/ui/pages/Page.java @@ -34,7 +34,10 @@ public class Page extends Group implements Disposable { } @Override - protected void setStage(Stage stage) { + public void setStage(Stage stage) { + if (stage == null && !hasParent()) { + dispose(); + } if (!(stage.getViewport() instanceof ScreenViewport)) { throw new IllegalArgumentException("Pages are explicitly for GUIs, and thus should have a 1:1 ratio between pixel and texture size for maximum clarity. This means that the stage should be using a ScreenViewport."); } @@ -50,7 +53,16 @@ public class Page extends Group implements Disposable { setPosition(baseXPos*getWidth(), baseYPos*getHeight()); } + @Override + public void setParent(Group parent) { + if (parent == null && getStage() == null) { + dispose(); + } + super.setParent(parent); + } + @Override public void dispose() { + Gdx.app.debug(getClass().getSimpleName(), "Disposing..."); } } diff --git a/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/GraphicsPage.java b/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/GraphicsPage.java index 614c114..026678f 100755 --- a/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/GraphicsPage.java +++ b/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/GraphicsPage.java @@ -41,8 +41,8 @@ public class GraphicsPage extends Page { super.act(delta); } - public void save() { - graphicsTable.save(); + public void saveOptions() { + graphicsTable.saveOptions(); } public int getBloomLevel() { diff --git a/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/MainPage.java b/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/MainPage.java index a9fff6b..23ec0a9 100755 --- a/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/MainPage.java +++ b/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/MainPage.java @@ -111,9 +111,10 @@ public class MainPage extends Page implements Observer { @Override public void dispose() { + dhv.dispose(); super.dispose(); } - + @Override public void update(Observable o, Object arg) { if (o == mc) { diff --git a/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/MainScreen.java b/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/MainScreen.java index c7ffbf8..570deb8 100755 --- a/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/MainScreen.java +++ b/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/MainScreen.java @@ -99,11 +99,6 @@ public class MainScreen extends ScreenAdapter implements ResizeReadyScreen { @Override public void preAssetLoad() { stage.clear(); - mainPage.dispose(); - optionsPage.dispose(); - creditsPage.dispose(); - keybindPage.dispose(); - musicSelectionPage.dispose(); bloomShader.dispose(); bloomShader = null; background = null; @@ -187,7 +182,8 @@ public class MainScreen extends ScreenAdapter implements ResizeReadyScreen { public void saveAll() { if (optionsPage != null) { - optionsPage.saveOptions(rhythmBullet.getPreferences()); + optionsPage.saveOptions(); + graphicsPage.saveOptions(); rhythmBullet.getPreferences().flush(); } } @@ -203,6 +199,7 @@ public class MainScreen extends ScreenAdapter implements ResizeReadyScreen { @Override public void dispose() { + Gdx.app.debug("Mainscreen", "disposing..."); stage.dispose(); musicMetadataController.dispose(); screenBatch.dispose(); diff --git a/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/OptionsPage.java b/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/OptionsPage.java index 3db7463..d6ddfe2 100755 --- a/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/OptionsPage.java +++ b/desktop/src/zero1hd/rhythmbullet/desktop/screens/main/OptionsPage.java @@ -22,9 +22,11 @@ public class OptionsPage extends Page { private ProgressBar fxVolSlider; private TextField directoryField; private float musicSearchTimer; + private Preferences prefs; public OptionsPage(MusicController musicController, Skin skin, Preferences preferences, ChangeListener backButtonListener, ChangeListener graphicsButtonListener, ChangeListener controlsButtonListener) { super(-1, 0, "General", skin); + this.prefs = preferences; //Back button TextButton backButton = new TextButton("Back", skin); @@ -140,13 +142,14 @@ public class OptionsPage extends Page { optionsTable.add(usageLabel).colspan(2); } - public void saveOptions(Preferences prefs) { + public void saveOptions() { prefs.putString("music dir", directoryField.getText()); Gdx.app.debug("Preferences", "Saved all basic options page values."); } @Override public void dispose() { + saveOptions(); super.dispose(); } }