made more visualizer code accessible for other platforms by using interface

This commit is contained in:
2018-03-21 23:26:09 -05:00
parent 2aa498e292
commit bfcad74529
10 changed files with 106 additions and 47 deletions

View File

@@ -0,0 +1,190 @@
package zero1hd.rhythmbullet.audio.visualizer;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.RhythmBullet;
public class CircularVisualizer implements Disposable {
private int circlePointIndex;
private int centerX, centerY;
private int r;
private int componentCount = 2 + 1;
private float[][] circlePoints;
private float[] vertComponents;
private float color;
private Mesh mesh;
private ShaderProgram shader;
private Visualizer visualizer;
private int barCount = 180;
private float barHeightMultiplier = 1.5f;
private float[] audioSpectrum;
private Camera camera;
public CircularVisualizer(Visualizer visualizer) {
shader = new ShaderProgram(Gdx.files.internal("shaders/mesh.vsh"), Gdx.files.internal("shaders/mesh.fsh"));
if (!shader.isCompiled() || shader.getLog().length() != 0) {
Gdx.app.debug("Circular visualizer shader", shader.getLog());
Gdx.app.exit();
}
r = RhythmBullet.pixels_per_unit*RhythmBullet.SPAWN_CIRCLE_RADIUS;
this.visualizer = visualizer;
}
/**
* Only should be called when all changes have been done.
*/
public void applyPositionChanges() {
int vertsReq = calculateVerticeCount(centerX, centerY, r);
vertComponents = new float[vertsReq * componentCount];
circlePoints = new float[vertsReq][2];
Gdx.app.debug("Circular Visualizer", "Using " + circlePoints.length + " points to create a circle.");
if (mesh != null) {
mesh.dispose();
}
createCircleVertices(centerX, centerY, r);
mesh = new Mesh(true, circlePoints.length, 0, new VertexAttribute(Usage.Position, 2, "a_position"), new VertexAttribute(Usage.ColorPacked, 4, "a_color"));
}
public void setCenter(int x, int y) {
this.centerX = x;
this.centerY = y;
}
private void addCircleVertex(int x, int y) {
circlePoints[circlePointIndex][0] = x;
circlePoints[circlePointIndex][1] = y;
circlePointIndex++;
}
public void drawVisualizer() {
for (int circlePointIndex = 0; circlePointIndex < circlePoints.length; circlePointIndex++) {
for (int componentIndex = 0; componentIndex < componentCount; componentIndex++) {
int index = circlePointIndex*componentCount + componentIndex;
if (componentIndex == 2) {
vertComponents[index] = color;
} else {
vertComponents[index] = circlePoints[circlePointIndex][componentIndex];
}
}
}
flush();
}
private void flush() {
mesh.setVertices(vertComponents);
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
int vertexCount = circlePoints.length;
((OrthographicCamera) camera).setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
shader.begin();
shader.setUniformMatrix("u_projTrans", camera.combined);
mesh.render(shader, GL20.GL_TRIANGLE_FAN, 0, vertexCount);
shader.end();
Gdx.gl.glDepthMask(true);
}
private void createCircleVertices(int centerX, int centerY, int radius) {
int x = radius - 1;
int y = 0;
int dx = 1;
int dy = 1;
int err = dx - (radius << 1);
circlePointIndex = 0;
while (x >= y)
{
addCircleVertex(centerX + x, centerY + y);
addCircleVertex(centerX + y, centerY + x);
addCircleVertex(centerX - y, centerY + x);
addCircleVertex(centerX - x, centerY + y);
addCircleVertex(centerX - x, centerY - y);
addCircleVertex(centerX - y, centerY - x);
addCircleVertex(centerX + y, centerY - x);
addCircleVertex(centerX + x, centerY - y);
if (err <= 0) {
y++;
err += dy;
dy += 2;
} else {
x--;
dx += 2;
err += dx - (radius << 1);
}
}
}
private int calculateVerticeCount(int centerX, int centerY, int radius) {
int x = radius - 1;
int y = 0;
int dx = 1;
int dy = 1;
int err = dx - (radius << 1);
int vertCount = 0;
while (x >= y)
{
vertCount += 8;
if (err <= 0) {
y++;
err += dy;
dy += 2;
} else {
x--;
dx += 2;
err += dx - (radius << 1);
}
}
return vertCount;
}
public void setColor(float color) {
this.color = color;
}
@Override
public void dispose() {
mesh.dispose();
shader.dispose();
}
/**
* set the maximum radius
* @param r
*/
public void setR(int r) {
this.r = r;
}
/**
* get the maximum radius
* @return
*/
public int getR() {
return r;
}
public void setCamera(Camera camera) {
this.camera = camera;
}
public Camera getCamera() {
return camera;
}
}

View File

@@ -0,0 +1,281 @@
package zero1hd.rhythmbullet.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 com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.audio.MusicManager;
public class HorizontalVisualizer implements Disposable {
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;
private Visualizer visualizer;
public HorizontalVisualizer(Visualizer visualizer) {
super();
mirrors = new Array<>();
pixmap = new Pixmap(2, 2, Format.RGBA8888);
pixmap.setColor(Color.WHITE);
pixmap.fill();
barCount = 68;
width = Gdx.graphics.getWidth();
height = Gdx.graphics.getHeight()/2f;
x = 0;
y = 0;
this.visualizer = visualizer;
smoothRange = 2;
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();
}
public void render(Batch batch, float parentAlpha) {
if (visualizer.getMM() != 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);
}
}
}
}
public void update(float delta) {
if (visualizer.getMM() != null) {
//Averaging bins together
for (int i = 0; i < barCount; i++) {
float barHeight = 0;
for (int j = 0; j < binsPerBar; j++) {
barHeight += Math.abs(visualizer.getAudioPCMData()[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;
}
}
}
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];
visualizer.setMM(mm);
}
public void dispose() {
barTexture.dispose();
visualizer.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;
}
public void calcPCMData() {
visualizer.calcPCMData();
}
public void calculate() {
visualizer.fft();
}
public Visualizer getVisualizer() {
return visualizer;
}
}

View File

@@ -2,7 +2,6 @@ package zero1hd.rhythmbullet.audio.visualizer;
import java.util.concurrent.locks.ReentrantLock;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.utils.Disposable;
import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D;
@@ -19,7 +18,7 @@ public class MusicManagerFFT implements Disposable {
lock = new ReentrantLock();
}
public void calculate(float delta) {
public void calculate() {
if (mm != null && calc && mm.isPlaying()) {
lock.lock();
fft.realForward(audioPCM);
@@ -39,12 +38,6 @@ public class MusicManagerFFT implements Disposable {
lock.unlock();
}
public void render(Batch batch, float parentAlpha) {
}
public void update(float delta) {
}
@Override
public void dispose() {
}

View File

@@ -0,0 +1,21 @@
package zero1hd.rhythmbullet.audio.visualizer;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.utils.Disposable;
import zero1hd.rhythmbullet.audio.MusicManager;
public interface Visualizer extends Disposable {
void calcPCMData();
void setMM(MusicManager mm);
MusicManager getMM();
void render(Batch batch, float delta);
float[] getAudioPCMData();
void fft();
}