diff --git a/src/audio/AudioArgs.java b/src/audio/AudioArgs.java new file mode 100644 index 0000000..4184072 --- /dev/null +++ b/src/audio/AudioArgs.java @@ -0,0 +1,59 @@ +package audio; + +// Helper class for AudioClient that deals with optional program arguments. +final class AudioArgs { + + final String objFilePath; + final float[] optionalArgs; + + AudioArgs(String[] args) throws IllegalAudioArgumentException { + if (args.length < 1 || args.length > 6) { + throw new IllegalAudioArgumentException(); + } + + objFilePath = args[0]; + optionalArgs = new float[args.length - 1]; + + for (int i = 0; i < optionalArgs.length; i++) { + optionalArgs[i] = Float.parseFloat(args[i + 1]); + } + } + + String objFilePath() { + return objFilePath; + } + + float rotateSpeed() { + return getArg(0, 0); + } + + float cameraX() { + return getArg(1, 0); + } + + float cameraY() { + return getArg(2, 0); + } + + float cameraZ() { + return getArg(3, 0); + } + + float focalLength() { + return getArg(4, 1); + } + + private float getArg(int n, float defaultVal) { + return optionalArgs.length > n ? optionalArgs[n] : defaultVal; + } + + private class IllegalAudioArgumentException extends IllegalArgumentException { + + private static final String USAGE = "Incorrect usage.\nUsage: osci-render objFilePath " + + "[rotateSpeed] [cameraX] [cameraY] [cameraZ] [focalLength]"; + + public IllegalAudioArgumentException() { + super(USAGE); + } + } +} diff --git a/src/audio/AudioClient.java b/src/audio/AudioClient.java index 825c157..f99ded4 100644 --- a/src/audio/AudioClient.java +++ b/src/audio/AudioClient.java @@ -12,6 +12,7 @@ import shapes.Shapes; import shapes.Vector2; public class AudioClient { + private static final int SAMPLE_RATE = 192000; private static double OBJ_ROTATE_SPEED = Math.PI / 1000; private static final float ROTATE_SPEED = 0; @@ -30,32 +31,38 @@ public class AudioClient { // // example: // osci-render models/cube.obj 1 0 0 -3 10 - public static void main(String[] args) { + public static void main(String[] programArgs) { // 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. - String objFilePath = args[0]; - float focalLength = Float.parseFloat(args[1]); - float cameraX = Float.parseFloat(args[2]); - float cameraY = Float.parseFloat(args[3]); - float cameraZ = Float.parseFloat(args[4]); + AudioArgs args = new AudioArgs(programArgs); - OBJ_ROTATE_SPEED *= Double.parseDouble(args[5]); + OBJ_ROTATE_SPEED *= args.rotateSpeed(); - int numFrames = (int) (2 * Math.PI / OBJ_ROTATE_SPEED); - - Camera camera = new Camera(focalLength, new Vector3(cameraX, cameraY, cameraZ)); - WorldObject object = new WorldObject(objFilePath); + Vector3 cameraPos = new Vector3(args.cameraX(), args.cameraY(), args.cameraZ()); + Camera camera = new Camera(args.focalLength(), cameraPos); + WorldObject object = new WorldObject(args.objFilePath()); Vector3 rotation = new Vector3(0, OBJ_ROTATE_SPEED, OBJ_ROTATE_SPEED); + + System.out.println("Begin pre-render..."); + List> frames = preRender(object, rotation, camera); + System.out.println("Finish pre-render"); + System.out.println("Connecting to audio player"); + AudioPlayer player = new AudioPlayer(SAMPLE_RATE, frames, ROTATE_SPEED, TRANSLATION_SPEED, TRANSLATION, SCALE, WEIGHT); + System.out.println("Starting audio stream"); + player.play(); + } + + private static List> preRender(WorldObject object, Vector3 rotation, Camera camera) { List> preRenderedFrames = new ArrayList<>(); + int numFrames = (int) (2 * Math.PI / OBJ_ROTATE_SPEED); for (int i = 0; i < numFrames; i++) { preRenderedFrames.add(new ArrayList<>()); } - System.out.println("Begin pre-render..."); AtomicInteger renderedFrames = new AtomicInteger(); // pre-renders the WorldObject in parallel @@ -70,16 +77,6 @@ public class AudioClient { } }); - System.out.println("Finish pre-render"); - - System.out.println("Connecting to audio player"); - AudioPlayer player = new AudioPlayer(SAMPLE_RATE, preRenderedFrames); - player.setRotateSpeed(ROTATE_SPEED); - player.setTranslation(TRANSLATION_SPEED, TRANSLATION); - player.setScale(SCALE); - player.setWeight(WEIGHT); - - System.out.println("Starting audio stream"); - player.start(); + return preRenderedFrames; } } \ No newline at end of file diff --git a/src/audio/AudioPlayer.java b/src/audio/AudioPlayer.java index ac20d5a..a04d9c9 100644 --- a/src/audio/AudioPlayer.java +++ b/src/audio/AudioPlayer.java @@ -1,14 +1,12 @@ package audio; import com.xtaudio.xt.*; -import java.time.Duration; -import java.time.Instant; import shapes.Shape; import shapes.Vector2; import java.util.List; -public class AudioPlayer extends Thread { +public class AudioPlayer { private final XtFormat FORMAT; private final List> frames; @@ -31,6 +29,14 @@ public class AudioPlayer extends Thread { this.frames = frames; } + public AudioPlayer(int sampleRate, List> frames, float rotateSpeed, float translateSpeed, Vector2 translateVector, float scale, float weight) { + this(sampleRate, frames); + setRotateSpeed(rotateSpeed); + setTranslation(translateSpeed, translateVector); + setScale(scale); + setWeight(weight); + } + private void render(XtStream stream, Object input, Object output, int audioFrames, double time, long position, boolean timeValid, long error, Object user) { for (int f = 0; f < audioFrames; f++) { @@ -126,14 +132,13 @@ public class AudioPlayer extends Thread { return frames.get(currentFrame).get(currentShape); } - @Override - public void run() { + public void play() { try (XtAudio audio = new XtAudio(null, null, null, null)) { XtService service = XtAudio.getServiceBySetup(XtSetup.CONSUMER_AUDIO); try (XtDevice device = service.openDefaultDevice(true)) { if (device != null && device.supportsFormat(FORMAT)) { - XtBuffer buffer = device.getBuffer(FORMAT); + try (XtStream stream = device.openStream(FORMAT, true, false, buffer.current, this::render, null, null)) { stream.start();