From 5067c4747e03b4b8a9779f204387120086649fcd Mon Sep 17 00:00:00 2001 From: James Ball Date: Wed, 30 Sep 2020 20:24:26 +0100 Subject: [PATCH 1/3] Implement pre-rendering --- src/audio/AudioClient.java | 34 ++++++++++++++++--------- src/audio/AudioPlayer.java | 52 +++++++++++++++++++------------------- src/shapes/Shapes.java | 4 +-- 3 files changed, 50 insertions(+), 40 deletions(-) diff --git a/src/audio/AudioClient.java b/src/audio/AudioClient.java index aa60783..5fe258b 100644 --- a/src/audio/AudioClient.java +++ b/src/audio/AudioClient.java @@ -3,30 +3,40 @@ package audio; import engine.Camera; import engine.Vector3; import engine.WorldObject; +import java.util.ArrayList; +import java.util.List; +import shapes.Shape; import shapes.Shapes; +import shapes.Vector2; public class AudioClient { - private static final int SAMPLE_RATE = 192000; - private static final double TARGET_FRAMERATE = 30; + public static final int SAMPLE_RATE = 192000; + public static final double TARGET_FRAMERATE = 30; - public static void main(String[] args) throws InterruptedException { + public static void main(String[] args) { // TODO: Calculate weight of lines using depth. // Reduce weight of lines drawn multiple times. // Find intersections of lines to (possibly) improve line cleanup. // Improve performance of line cleanup with a heuristic. + // Pre-render in parallel. - AudioPlayer player = new AudioPlayer(SAMPLE_RATE, 440); - - Camera camera = new Camera(0.6, new Vector3(0, 0, -3)); - WorldObject cube = new WorldObject(args[0], new Vector3(0, 0, 0), new Vector3()); + Camera camera = new Camera(0.8, new Vector3(0, 0, -3)); + WorldObject object = new WorldObject(args[0], new Vector3(0, 0, 0), new Vector3()); Vector3 rotation = new Vector3(0,Math.PI / 100,Math.PI / 100); + List> preRenderedFrames = new ArrayList<>(); - player.start(); + int numFrames = (int) (Float.parseFloat(args[1]) * TARGET_FRAMERATE); - while (true) { - AudioPlayer.updateFrame(Shapes.sortLines(camera.draw(cube))); - cube.rotate(rotation); - Thread.sleep((long) (1000 / TARGET_FRAMERATE)); + + + for (int i = 0; i < numFrames; i++) { + preRenderedFrames.add(Shapes.sortLines(camera.draw(object))); + object.rotate(rotation); } + + AudioPlayer player = new AudioPlayer(SAMPLE_RATE, preRenderedFrames); + //AudioPlayer.setRotateSpeed(1); + //AudioPlayer.setTranslation(1, new Vector2(1, 1)); + player.start(); } } \ No newline at end of file diff --git a/src/audio/AudioPlayer.java b/src/audio/AudioPlayer.java index 347eb97..97cb051 100644 --- a/src/audio/AudioPlayer.java +++ b/src/audio/AudioPlayer.java @@ -7,17 +7,17 @@ import shapes.Vector2; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; public class AudioPlayer extends Thread { private static double[] phases = new double[2]; private static XtFormat FORMAT; private static List shapes = new ArrayList<>(); - private static Lock lock = new ReentrantLock(); + private static List> frames = new ArrayList<>(); + private static int currentFrame = 0; private static int currentShape = 0; - private static int framesDrawn = 0; + private static long timeOfLastFrame; + private static int audioFramesDrawn = 0; private static double TRANSLATE_SPEED = 0; private static Vector2 TRANSLATE_VECTOR; @@ -27,16 +27,17 @@ public class AudioPlayer extends Thread { private static double SCALE = 1; private static double WEIGHT = 100; - private boolean stopped; + private volatile boolean stopped; - public AudioPlayer(int sampleRate, double frequency) { + public AudioPlayer(int sampleRate, List> frames) { AudioPlayer.FORMAT = new XtFormat(new XtMix(sampleRate, XtSample.FLOAT32), 0, 0, 2, 0); + AudioPlayer.frames = frames; + AudioPlayer.timeOfLastFrame = System.currentTimeMillis(); } - static void render(XtStream stream, Object input, Object output, int frames, + static void render(XtStream stream, Object input, Object output, int audioFrames, double time, long position, boolean timeValid, long error, Object user) { - lock.lock(); - for (int f = 0; f < frames; f++) { + for (int f = 0; f < audioFrames; f++) { Shape shape = getCurrentShape(); shape = shape.setWeight(WEIGHT); @@ -44,22 +45,27 @@ public class AudioPlayer extends Thread { shape = rotate(shape, FORMAT.mix.rate); shape = translate(shape, FORMAT.mix.rate); - double framesToDraw = shape.getWeight() * shape.getLength(); - double drawingProgress = framesToDraw == 0 ? 1 : framesDrawn / framesToDraw; + double totalAudioFrames = shape.getWeight() * shape.getLength(); + double drawingProgress = totalAudioFrames == 0 ? 1 : audioFramesDrawn / totalAudioFrames; for (int c = 0; c < FORMAT.outputs; c++) { - ((float[]) output)[f * FORMAT.outputs] = (float) shape.nextX(drawingProgress); - ((float[]) output)[f * FORMAT.outputs + 1] = (float) shape.nextY(drawingProgress); + ((float[]) output)[f * FORMAT.outputs] = shape.nextX(drawingProgress); + ((float[]) output)[f * FORMAT.outputs + 1] = shape.nextY(drawingProgress); } - framesDrawn++; + audioFramesDrawn++; - if (framesDrawn > framesToDraw) { - framesDrawn = 0; - currentShape++; + if (audioFramesDrawn > totalAudioFrames) { + audioFramesDrawn = 0; + currentShape = ++currentShape % AudioPlayer.frames.get(currentFrame).size(); + } + + if (System.currentTimeMillis() - timeOfLastFrame > (float) 1000 / AudioClient.TARGET_FRAMERATE) { + currentShape = 0; + currentFrame = ++currentFrame % AudioPlayer.frames.size(); + timeOfLastFrame = System.currentTimeMillis(); } } - lock.unlock(); } private static Shape rotate(Shape shape, double sampleRate) { @@ -122,25 +128,19 @@ public class AudioPlayer extends Thread { } public static void updateFrame(List frame) { - lock.lock(); currentShape = 0; shapes = new ArrayList<>(); shapes.addAll(frame); // Arbitrary function for changing weights based on frame draw-time. AudioPlayer.WEIGHT = 200 * Math.exp(-0.017 * Shapes.totalLength(frame)); - lock.unlock(); } private static Shape getCurrentShape() { - if (shapes.size() == 0) { + if (frames.size() == 0) { return new Vector2(0, 0); } - if (currentShape >= shapes.size()) { - currentShape -= shapes.size(); - } - - return shapes.get(currentShape); + return frames.get(currentFrame).get(currentShape); } @Override diff --git a/src/shapes/Shapes.java b/src/shapes/Shapes.java index 79ad7db..cd15bfc 100644 --- a/src/shapes/Shapes.java +++ b/src/shapes/Shapes.java @@ -65,7 +65,7 @@ public class Shapes { .orElse(0d); } - public static List sortLines(List lines) { + public static List sortLines(List lines) { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); for (Line line : lines) { @@ -79,7 +79,7 @@ public class Shapes { ConnectivityInspector inspector = new ConnectivityInspector<>(graph); - List sortedLines = new ArrayList<>(); + List sortedLines = new ArrayList<>(); for (Set vertices : inspector.connectedSets()) { AsSubgraph subgraph = new AsSubgraph<>(graph, vertices); From 0f2be2cee67f4ecff21c5017d8bf5db0eb71c048 Mon Sep 17 00:00:00 2001 From: James Ball Date: Wed, 30 Sep 2020 21:00:33 +0100 Subject: [PATCH 2/3] Add parallel pre-rendering --- src/audio/AudioClient.java | 16 ++++++++++++---- src/audio/AudioPlayer.java | 2 +- src/engine/Vector3.java | 4 ++++ src/engine/WorldObject.java | 27 +++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/audio/AudioClient.java b/src/audio/AudioClient.java index 5fe258b..4ba2ddf 100644 --- a/src/audio/AudioClient.java +++ b/src/audio/AudioClient.java @@ -5,6 +5,7 @@ import engine.Vector3; import engine.WorldObject; import java.util.ArrayList; import java.util.List; +import java.util.stream.IntStream; import shapes.Shape; import shapes.Shapes; import shapes.Vector2; @@ -20,20 +21,27 @@ public class AudioClient { // Improve performance of line cleanup with a heuristic. // Pre-render in parallel. - Camera camera = new Camera(0.8, new Vector3(0, 0, -3)); + Camera camera = new Camera(0.6, new Vector3(0, 0, -0.08)); WorldObject object = new WorldObject(args[0], new Vector3(0, 0, 0), new Vector3()); Vector3 rotation = new Vector3(0,Math.PI / 100,Math.PI / 100); List> preRenderedFrames = new ArrayList<>(); int numFrames = (int) (Float.parseFloat(args[1]) * TARGET_FRAMERATE); - + long start = System.currentTimeMillis(); for (int i = 0; i < numFrames; i++) { - preRenderedFrames.add(Shapes.sortLines(camera.draw(object))); - object.rotate(rotation); + preRenderedFrames.add(new ArrayList<>()); } + IntStream.range(0, numFrames).parallel().forEach((frameNum) -> { + WorldObject clone = object.clone(); + clone.rotate(rotation.scale(frameNum)); + preRenderedFrames.set(frameNum, Shapes.sortLines(camera.draw(clone))); + }); + + System.out.println(System.currentTimeMillis() - start); + AudioPlayer player = new AudioPlayer(SAMPLE_RATE, preRenderedFrames); //AudioPlayer.setRotateSpeed(1); //AudioPlayer.setTranslation(1, new Vector2(1, 1)); diff --git a/src/audio/AudioPlayer.java b/src/audio/AudioPlayer.java index 97cb051..b7f1db7 100644 --- a/src/audio/AudioPlayer.java +++ b/src/audio/AudioPlayer.java @@ -136,7 +136,7 @@ public class AudioPlayer extends Thread { } private static Shape getCurrentShape() { - if (frames.size() == 0) { + if (frames.size() == 0 || frames.get(currentFrame).size() == 0) { return new Vector2(0, 0); } diff --git a/src/engine/Vector3.java b/src/engine/Vector3.java index 4fa5b30..6af045d 100644 --- a/src/engine/Vector3.java +++ b/src/engine/Vector3.java @@ -84,4 +84,8 @@ public final class Vector3 { return mean.scale(1f / (points.size())); } + + public Vector3 clone() { + return new Vector3(x, y, z); + } } diff --git a/src/engine/WorldObject.java b/src/engine/WorldObject.java index fa2c507..90e2a25 100644 --- a/src/engine/WorldObject.java +++ b/src/engine/WorldObject.java @@ -23,10 +23,21 @@ public class WorldObject { loadFromFile(filename); } + public WorldObject(List vertices, List edgeData, Vector3 position, Vector3 rotation) { + this.vertices = vertices; + this.edgeData = edgeData; + this.position = position; + this.rotation = rotation; + } + public void rotate(Vector3 theta) { rotation = rotation.add(theta); } + public void resetRotation() { + rotation = new Vector3(); + } + public List getVertices() { List newVertices = new ArrayList<>(); @@ -67,4 +78,20 @@ public class WorldObject { throw new IllegalArgumentException("Cannot load mesh data from: " + filename); } } + + public WorldObject clone() { + List newVertices = new ArrayList<>(); + + for (Vector3 vertex : vertices) { + newVertices.add(vertex.clone()); + } + + List newEdgeData = new ArrayList<>(); + + for (int edge : edgeData) { + newEdgeData.add(edge); + } + + return new WorldObject(newVertices, newEdgeData, position.clone(), rotation.clone()); + } } From 05bdd38216375f924f46214cc919d20b80f4ce1f Mon Sep 17 00:00:00 2001 From: James Ball Date: Wed, 30 Sep 2020 21:01:36 +0100 Subject: [PATCH 3/3] Remove TODO --- src/audio/AudioClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/audio/AudioClient.java b/src/audio/AudioClient.java index 4ba2ddf..4b7d80c 100644 --- a/src/audio/AudioClient.java +++ b/src/audio/AudioClient.java @@ -19,7 +19,6 @@ public class AudioClient { // Reduce weight of lines drawn multiple times. // Find intersections of lines to (possibly) improve line cleanup. // Improve performance of line cleanup with a heuristic. - // Pre-render in parallel. Camera camera = new Camera(0.6, new Vector3(0, 0, -0.08)); WorldObject object = new WorldObject(args[0], new Vector3(0, 0, 0), new Vector3());