re-organized visualizer setup for future ease of use

This commit is contained in:
2018-02-04 22:36:16 -06:00
parent 7f51ca2c55
commit b9f70e0c9d
6 changed files with 324 additions and 299 deletions

View File

@@ -0,0 +1,268 @@
package zero1hd.rhythmbullet.desktop.audio.visualizer;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import zero1hd.rhythmbullet.audio.MusicManager;
import zero1hd.rhythmbullet.audio.visualizer.MirrorVisualizer;
public class HorizontalVisualizer extends Visualizer {
private Pixmap pixmap;
private Texture barTexture;
private int barWidth;
private int binsPerBar;
private int spaceBetweenBars;
private Sprite[] bars;
private float[] barHeights;
private int smoothRange;
private float barHeightMultiplier;
private float rotation;
private Vector2 angleRot;
private boolean flip;
private Array<MirrorVisualizer> mirrors;
private boolean reverse;
private float maxAvgHeight;
private float currentAvg;
private int barCount;
private float width, height, x, y;
public HorizontalVisualizer() {
super();
mirrors = new Array<>();
pixmap = new Pixmap(2, 2, Format.RGBA8888);
pixmap.setColor(Color.WHITE);
pixmap.fill();
barCount = 70;
width = Gdx.graphics.getWidth();
height = Gdx.graphics.getHeight()/2f;
x = 0;
y = 0;
smoothRange = 3;
angleRot = new Vector2(MathUtils.cosDeg(rotation), MathUtils.sinDeg(rotation));
bars = new Sprite[barCount];
barHeights = new float[barCount];
barTexture = new Texture(pixmap);
for (int i = 0; i < bars.length; i++) {
bars[i] = new Sprite(barTexture);
}
updatePositionInfo();
pixmap.dispose();
}
@Override
public void render(Batch batch, float parentAlpha) {
if (mm != null) {
for (int i = 0; i < bars.length; i++) {
bars[i].draw(batch);
for (int j = 0; j < mirrors.size; j++) {
mirrors.get(j).render(i, batch, parentAlpha, bars);
}
}
}
super.render(batch, parentAlpha);
}
public void update(float delta) {
if (mm != null) {
//Averaging bins together
for (int i = 0; i < barCount; i++) {
float barHeight = 0;
for (int j = 0; j < binsPerBar; j++) {
barHeight += Math.abs(audioPCM[j+i*binsPerBar +1]);
}
barHeight /= binsPerBar;
barHeight *= barHeightMultiplier;
barHeights[i] = barHeight;
}
currentAvg = 0;
for (int i = 0; i < barCount; i++) {
int avg = 0;
//Averaging the bars
for (int range = 0; range < smoothRange; range++) {
if (i+range < barCount) {
avg += barHeights[i+range];
}
if (i-range >= 0) {
avg += barHeights[i-range];
}
}
avg /= smoothRange*2;
barHeights[i] = avg;
currentAvg += barHeights[i];
if (bars[i].getHeight() > barHeights[i]) {
bars[i].setSize(barWidth, bars[i].getHeight() - (15f*delta*(bars[i].getHeight()-barHeights[i])));
} else {
bars[i].setSize(barWidth, bars[i].getHeight() + (20f*delta*(barHeights[i] - bars[i].getHeight())));
}
}
currentAvg /= barHeights.length;
if (currentAvg > maxAvgHeight) {
maxAvgHeight = currentAvg;
}
}
}
@Override
public void setMM(MusicManager mm) {
maxAvgHeight = 0;
currentAvg = 0;
float validBins = (1700/((mm.getSampleRate()/2)/((mm.getReadWindowSize()/2)+1)));
Gdx.app.debug("Visualizer", "valid frequency bins " + validBins);
binsPerBar = MathUtils.round((validBins/barCount));
barHeights = new float[barCount];
super.setMM(mm);
}
@Override
public void dispose() {
barTexture.dispose();
super.dispose();
}
public void updatePositionInfo() {
barHeightMultiplier = height*0.03f;
int barSpace = 0;
angleRot.set(MathUtils.cosDeg(rotation), MathUtils.sinDeg(rotation));
barWidth = MathUtils.round((float) width/(float) barCount);
barWidth -= spaceBetweenBars;
for (int i = 0; i < bars.length; i++) {
barSpace = i*(barWidth+spaceBetweenBars);
if (flip) {
bars[i].setRotation(rotation+180);
} else {
bars[i].setRotation(rotation);
}
if (reverse) {
bars[bars.length-i-1].setPosition(x + barSpace*angleRot.x, y + barSpace*angleRot.y);
} else {
bars[i].setPosition(x + barSpace*angleRot.x, y + barSpace*angleRot.y);
}
for (int mirrorIndex = 0; mirrorIndex < mirrors.size; mirrorIndex++) {
mirrors.get(mirrorIndex).position(i, barWidth, spaceBetweenBars);
}
}
}
public float getActualWidth() {
return (barWidth+spaceBetweenBars)*(barCount - 1);
}
public void setColor(Color color) {
for (int i = 0; i < bars.length; i++) {
bars[i].setColor(color);
}
for (int i = 0; i < mirrors.size; i++) {
mirrors.get(i).setColor(color);
}
}
public void setColor(float r, float g, float b, float a) {
for (int i = 0; i < bars.length; i++) {
bars[i].setColor(r, g, b, a);
}
for (int i = 0; i < mirrors.size; i++) {
mirrors.get(i).setColor(r, g, b, a);
}
}
public void setRotation(float rotation) {
this.rotation = rotation;
}
public void flip() {
flip = !flip;
}
public boolean isFlipped() {
return flip;
}
public void addMirrorVisualizer(MirrorVisualizer mirror) {
updatePositionInfo();
mirror.setup(bars, x, y, rotation);
mirrors.add(mirror);
x = (int) (((width - getActualWidth())/2f));
}
public void removeMirrorVisualizer(MirrorVisualizer mirror) {
mirrors.removeValue(mirror, true);
}
public void setSpaceBetweenBars(int spaceBetweenBars) {
this.spaceBetweenBars = spaceBetweenBars;
}
public int getBarWidth() {
return barWidth;
}
public void reverse() {
reverse = reverse ? false : true;
}
public boolean isReversed() {
return reverse;
}
public Sprite[] getBars() {
return bars;
}
public float getCurrentAvg() {
return currentAvg;
}
public float getMaxAvgHeight() {
return maxAvgHeight;
}
public int getSpaceBetweenBars() {
return spaceBetweenBars;
}
public void setX(float x) {
this.x = x;
}
public float getX() {
return x;
}
public void setY(float y) {
this.y = y;
}
public float getY() {
return y;
}
public void setWidth(float width) {
this.width = width;
}
public float getWidth() {
return width;
}
public void setHeight(float height) {
this.height = height;
}
public float getHeight() {
return height;
}
}

View File

@@ -1,218 +1,126 @@
package zero1hd.rhythmbullet.desktop.graphics.ui.components;
import static org.lwjgl.openal.AL10.*;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import org.lwjgl.openal.AL11;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.audio.OpenALMusic;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.ui.Widget;
import com.badlogic.gdx.utils.Disposable;
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.MusicManager;
import zero1hd.rhythmbullet.audio.visualizer.BasicVisualizer;
public class Visualizer extends Widget implements Disposable {
private BasicVisualizer vis;
private boolean updatePositioning = true;
private boolean mmSet;
private MusicManager mm;
private ShortBuffer playingBuffer;
private ShortBuffer compareBuffer;
private ShortBuffer buffer;
private int sourceID;
private float visRefreshRate;
private float timer;
private int readWindowIndex;
public Visualizer() {
vis = new BasicVisualizer();
try {
Field bufferField = ClassReflection.getDeclaredField(OpenALMusic.class, "tempBuffer");
bufferField.setAccessible(true);
buffer = ((ByteBuffer) bufferField.get(null)).asShortBuffer().asReadOnlyBuffer();
} catch (IllegalArgumentException | SecurityException | ReflectionException e) {
e.printStackTrace();
Gdx.app.debug("Visualizer reflection", "Failed attempt at retrieving tempBuffer field.");
Gdx.app.exit();
}
}
@Override
public void draw(Batch batch, float parentAlpha) {
vis.render(batch, parentAlpha);
super.draw(batch, parentAlpha);
}
@Override
public void act(float delta) {
if (mm != null && mm.isFinishedLoading() && !mmSet) {
vis.setMM(mm);
visRefreshRate = mm.getReadWindowSize()/mm.getSampleRate();
mmSet = true;
Gdx.app.debug("Visualizer", "\nsample count: " + mm.getSampleCount()
+ "\nDuration in seconds: " + mm.getDuration() +
"\nSample rate: " + mm.getSampleRate() +
"\nRefresh rate: " + visRefreshRate);
}
vis.update(delta);
updateVisualizerProperties();
if (updatePositioning) {
vis.updatePositionInfo();
vis.setxPos((getWidth() - vis.getActualWidth())/2f);
}
if (mmSet) {
if (timer >= visRefreshRate) {
timer = 0;
calcPCMData();
vis.calculate(delta);
} else {
timer += delta;
}
}
super.act(delta);
}
public void calcPCMData() {
short chanVal;
int bufferPosOffset = 0;
if (mm.playbackIndexUpdate() != readWindowIndex) {
bufferPosOffset = updateBufferPosition();
if (bufferPosOffset < 0 && playingBuffer.limit()+bufferPosOffset > 0) {
playingBuffer.position(playingBuffer.limit()+bufferPosOffset);
}
}
for (int sid = 0; sid < vis.getAudioPCM().length && sid < buffer.remaining(); sid++) {
for (int channel = 0; channel < mm.getChannelCount(); channel ++) {
if (playingBuffer.position() < playingBuffer.limit()) {
if (vis.getAudioPCM()[sid] < (chanVal = playingBuffer.get())) {
vis.getAudioPCM()[sid] = chanVal;
}
} else {
if (vis.getAudioPCM()[sid] < (chanVal = buffer.get())) {
vis.getAudioPCM()[sid] = chanVal;
}
}
}
vis.getAudioPCM()[sid] /= Short.MAX_VALUE+1f;
}
readWindowIndex++;
//Take down original buffer position so we don't need to sync again after...
int originalPos = buffer.position();
//Begin comparison
buffer.rewind();
if (compareBuffer.compareTo(buffer) != 0) {
bufferChanged();
//Begin copying current buffer to the comparison buffer
compareBuffer.clear();
compareBuffer.put(buffer);
compareBuffer.flip();
}
//Reset buffer to proper position.
buffer.position(originalPos);
}
private void bufferChanged() {
playingBuffer.clear();
playingBuffer.put(compareBuffer);
}
private int updateBufferPosition() {
int pos = (int) ((alGetSourcef(sourceID, AL11.AL_SAMPLE_OFFSET))-buffer.limit()-mm.getChannelCount()*mm.getReadWindowSize());
readWindowIndex = mm.getPlaybackIndexPosition()-1;
if (pos < 0) {
buffer.position(0);
} else {
buffer.position(pos);
}
return pos;
}
public void setMM(MusicManager mm) {
try {
Field sourceIDField = ClassReflection.getDeclaredField(OpenALMusic.class, "sourceID");
sourceIDField.setAccessible(true);
sourceID = (int) sourceIDField.get(mm.getMusic());
} catch (ReflectionException e) {
e.printStackTrace();
}
int originalPos = buffer.position();
playingBuffer = ShortBuffer.allocate(buffer.capacity());
buffer.rewind();
playingBuffer.put(buffer);
playingBuffer.flip();
compareBuffer = ShortBuffer.allocate(buffer.capacity());
buffer.rewind();
compareBuffer.put(buffer);
compareBuffer.flip();
buffer.position(originalPos);
this.mm = mm;
mmSet = false;
}
@Override
public void setColor(Color color) {
vis.setColor(color);
super.setColor(color);
}
@Override
public void setColor(float r, float g, float b, float a) {
vis.setColor(r, g, b, a);
super.setColor(r, g, b, a);
}
public void updateVisualizerProperties() {
vis.setHeight(getHeight());
vis.setWidth(getWidth());
vis.setxPos(getX());
vis.setyPos(getY());
vis.setRotation(getRotation());
}
public void setUpdatePositioning(boolean updatePositioning) {
updateVisualPosition();
this.updatePositioning = updatePositioning;
}
public void updateVisualPosition() {
updateVisualizerProperties();
vis.updatePositionInfo();
vis.setxPos(((vis.getWidth() - vis.getActualWidth())/2f));
vis.updatePositionInfo();
}
public boolean isUpdatePositioning() {
return updatePositioning;
}
public BasicVisualizer getVis() {
return vis;
}
@Override
public void dispose() {
vis.dispose();
}
}
package zero1hd.rhythmbullet.desktop.audio.visualizer;
import static org.lwjgl.openal.AL10.alGetSourcef;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import org.lwjgl.openal.AL11;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.audio.OpenALMusic;
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.MusicManager;
import zero1hd.rhythmbullet.audio.visualizer.MusicManagerFFT;
public class Visualizer extends MusicManagerFFT {
private ShortBuffer playingBuffer;
private ShortBuffer compareBuffer;
private ShortBuffer buffer;
private int sourceID;
private int readWindowIndex;
public Visualizer() {
super();
try {
Field bufferField = ClassReflection.getDeclaredField(OpenALMusic.class, "tempBuffer");
bufferField.setAccessible(true);
buffer = ((ByteBuffer) bufferField.get(null)).asShortBuffer().asReadOnlyBuffer();
} catch (IllegalArgumentException | SecurityException | ReflectionException e) {
e.printStackTrace();
Gdx.app.debug("Visualizer reflection", "Failed attempt at retrieving tempBuffer field.");
Gdx.app.exit();
}
}
public void calcPCMData() {
short chanVal;
int bufferPosOffset = 0;
if (mm.playbackIndexUpdate() != readWindowIndex) {
bufferPosOffset = updateBufferPosition();
if (bufferPosOffset < 0 && playingBuffer.limit()+bufferPosOffset > 0) {
playingBuffer.position(playingBuffer.limit()+bufferPosOffset);
}
}
for (int sid = 0; sid < getAudioPCM().length && sid < buffer.remaining(); sid++) {
for (int channel = 0; channel < mm.getChannelCount(); channel ++) {
if (playingBuffer.position() < playingBuffer.limit()) {
if (getAudioPCM()[sid] < (chanVal = playingBuffer.get())) {
getAudioPCM()[sid] = chanVal;
}
} else {
if (getAudioPCM()[sid] < (chanVal = buffer.get())) {
getAudioPCM()[sid] = chanVal;
}
}
}
getAudioPCM()[sid] /= Short.MAX_VALUE+1f;
}
readWindowIndex++;
//Take down original buffer position so we don't need to sync again after...
int originalPos = buffer.position();
//Begin comparison
buffer.rewind();
if (compareBuffer.compareTo(buffer) != 0) {
bufferChanged();
//Begin copying current buffer to the comparison buffer
compareBuffer.clear();
compareBuffer.put(buffer);
compareBuffer.flip();
}
//Reset buffer to proper position.
buffer.position(originalPos);
}
private void bufferChanged() {
playingBuffer.clear();
playingBuffer.put(compareBuffer);
}
private int updateBufferPosition() {
int pos = (int) ((alGetSourcef(sourceID, AL11.AL_SAMPLE_OFFSET))-buffer.limit()-mm.getChannelCount()*mm.getReadWindowSize());
readWindowIndex = mm.getPlaybackIndexPosition()-1;
if (pos < 0) {
buffer.position(0);
} else {
buffer.position(pos);
}
return pos;
}
public void setMM(MusicManager mm) {
try {
Field sourceIDField = ClassReflection.getDeclaredField(OpenALMusic.class, "sourceID");
sourceIDField.setAccessible(true);
sourceID = (int) sourceIDField.get(mm.getMusic());
} catch (ReflectionException e) {
e.printStackTrace();
}
int originalPos = buffer.position();
playingBuffer = ShortBuffer.allocate(buffer.capacity());
buffer.rewind();
playingBuffer.put(buffer);
playingBuffer.flip();
compareBuffer = ShortBuffer.allocate(buffer.capacity());
buffer.rewind();
compareBuffer.put(buffer);
compareBuffer.flip();
buffer.position(originalPos);
this.mm = mm;
super.setMM(mm);
}
}

View File

@@ -0,0 +1,110 @@
package zero1hd.rhythmbullet.desktop.graphics.ui.components;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.ui.Widget;
import com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.audio.MusicManager;
import zero1hd.rhythmbullet.desktop.audio.visualizer.HorizontalVisualizer;
public class HorizontalVisualizerWidget extends Widget implements Disposable {
private HorizontalVisualizer vis;
private boolean updatePositioning = true;
private MusicManager mm;
private boolean initialLoad;
private float visRefreshRate;
private float timer;
public HorizontalVisualizerWidget() {
vis = new HorizontalVisualizer();
}
@Override
public void draw(Batch batch, float parentAlpha) {
vis.render(batch, parentAlpha);
super.draw(batch, parentAlpha);
}
@Override
public void act(float delta) {
if (mm != null && mm.isFinishedLoading() && !initialLoad) {
visRefreshRate = mm.getReadWindowSize()/mm.getSampleRate();
Gdx.app.debug("Visualizer", "\nsample count: " + mm.getSampleCount()
+ "\nDuration in seconds: " + mm.getDuration() +
"\nSample rate: " + mm.getSampleRate() +
"\nRefresh rate: " + visRefreshRate);
vis.setMM(mm);
initialLoad = true;
}
if (updatePositioning) {
vis.updatePositionInfo();
vis.setX((getWidth() - vis.getActualWidth())/2f);
}
vis.update(delta);
updateVisualizerProperties();
if (mm != null && initialLoad) {
if (timer >= visRefreshRate) {
timer = 0;
vis.calcPCMData();
vis.calculate(delta);
} else {
timer += delta;
}
}
super.act(delta);
}
public void setMM(MusicManager mm) {
initialLoad = false;
this.mm = mm;
}
@Override
public void setColor(Color color) {
vis.setColor(color);
super.setColor(color);
}
@Override
public void setColor(float r, float g, float b, float a) {
vis.setColor(r, g, b, a);
super.setColor(r, g, b, a);
}
public void updateVisualizerProperties() {
vis.setHeight(getHeight());
vis.setWidth(getWidth());
vis.setX(getX());
vis.setY(getY());
vis.setRotation(getRotation());
}
public void setUpdatePositioning(boolean updatePositioning) {
updateVisualPosition();
this.updatePositioning = updatePositioning;
}
public void updateVisualPosition() {
updateVisualizerProperties();
vis.updatePositionInfo();
vis.setX(((vis.getWidth() - vis.getActualWidth())/2f));
vis.updatePositionInfo();
}
public boolean isUpdatePositioning() {
return updatePositioning;
}
public HorizontalVisualizer getVis() {
return vis;
}
@Override
public void dispose() {
vis.dispose();
}
}

View File

@@ -19,7 +19,7 @@ import com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.audio.visualizer.MirrorVisualizer;
public class TitleBarVisualizer extends Group implements Disposable {
private Visualizer visual;
private HorizontalVisualizerWidget visual;
private MirrorVisualizer visual2;
private Texture bgTexture;
private Image bg;
@@ -32,7 +32,7 @@ public class TitleBarVisualizer extends Group implements Disposable {
private float particleLimitTime;
public TitleBarVisualizer(AssetManager assets) {
if (assets == null) throw new NullPointerException("TitleBarVisualizer requires assets manager... ITS NULL YOU FOOL");
visual = new Visualizer();
visual = new HorizontalVisualizerWidget();
visual.getVis().setSpaceBetweenBars(visual.getVis().getBarWidth()- 2);
addActor(visual);
@@ -110,7 +110,7 @@ public class TitleBarVisualizer extends Group implements Disposable {
super.draw(batch, parentAlpha);
}
public Visualizer getHvisual() {
public HorizontalVisualizerWidget getHvisual() {
return visual;
}