diff --git a/pom.xml b/pom.xml index e8d0a4a..3588876 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ sh.ball osci-render - 1.1.1 + 1.1.2 osci-render diff --git a/src/main/java/sh/ball/audio/AudioPlayer.java b/src/main/java/sh/ball/audio/AudioPlayer.java index c73e47b..6a16bfa 100644 --- a/src/main/java/sh/ball/audio/AudioPlayer.java +++ b/src/main/java/sh/ball/audio/AudioPlayer.java @@ -1,6 +1,7 @@ package sh.ball.audio; import sh.ball.audio.effect.Effect; +import xt.audio.*; import xt.audio.Enums.XtSample; import xt.audio.Enums.XtSetup; import xt.audio.Enums.XtSystem; @@ -11,12 +12,6 @@ import xt.audio.Structs.XtDeviceStreamParams; import xt.audio.Structs.XtFormat; import xt.audio.Structs.XtMix; import xt.audio.Structs.XtStreamParams; -import xt.audio.XtAudio; -import xt.audio.XtDevice; -import xt.audio.XtPlatform; -import xt.audio.XtSafeBuffer; -import xt.audio.XtService; -import xt.audio.XtStream; import java.util.HashMap; import java.util.Map; @@ -30,38 +25,24 @@ import java.util.List; public class AudioPlayer implements Renderer> { - private static final int SAMPLE_RATE = 192000; private static final int BUFFER_SIZE = 20; - private final XtMix MIX = new XtMix(SAMPLE_RATE, XtSample.FLOAT32); - private final XtChannels CHANNELS = new XtChannels(0, 0, 2, 0); - private final XtFormat FORMAT = new XtFormat(MIX, CHANNELS); + private final XtFormat format; private final BlockingQueue> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE); + private final Map effects = new HashMap<>(); - private Map effects = new HashMap<>(); private List frame; private int currentShape = 0; private int audioFramesDrawn = 0; - private double translateSpeed = 0; - private Vector2 translateVector = new Vector2(); - private final Phase translatePhase = new Phase(); - private double rotateSpeed = 0; - private final Phase rotatePhase = new Phase(); - private double scale = 1; private double weight = Shape.DEFAULT_WEIGHT; private volatile boolean stopped; - public AudioPlayer() { - } - - public AudioPlayer(double rotateSpeed, double translateSpeed, Vector2 translateVector, double scale, double weight) { - setRotationSpeed(rotateSpeed); - setTranslationSpeed(translateSpeed); - setTranslation(translateVector); - setScale(scale); - setQuality(weight); + public AudioPlayer(int sampleRate) { + XtMix mix = new XtMix(sampleRate, XtSample.FLOAT32); + XtChannels channels = new XtChannels(0, 0, 2, 0); + this.format = new XtFormat(mix, channels); } private int render(XtStream stream, XtBuffer buffer, Object user) throws InterruptedException { @@ -73,16 +54,13 @@ public class AudioPlayer implements Renderer> { Shape shape = getCurrentShape(); shape = shape.setWeight(weight); - shape = scale(shape); - shape = rotate(shape, FORMAT.mix.rate); - shape = translate(shape, FORMAT.mix.rate); double totalAudioFrames = shape.getWeight() * shape.getLength(); double drawingProgress = totalAudioFrames == 0 ? 1 : audioFramesDrawn / totalAudioFrames; Vector2 nextVector = applyEffects(f, shape.nextVector(drawingProgress)); - output[f * FORMAT.channels.outputs] = (float) nextVector.getX(); - output[f * FORMAT.channels.outputs + 1] = (float) nextVector.getY(); + output[f * format.channels.outputs] = (float) nextVector.getX(); + output[f * format.channels.outputs + 1] = (float) nextVector.getY(); audioFramesDrawn++; @@ -107,60 +85,6 @@ public class AudioPlayer implements Renderer> { return vector; } - private Shape rotate(Shape shape, double sampleRate) { - if (rotateSpeed != 0) { - shape = shape.rotate( - nextTheta(sampleRate, rotateSpeed, translatePhase) - ); - } - - return shape; - } - - private Shape translate(Shape shape, double sampleRate) { - if (translateSpeed != 0 && !translateVector.equals(new Vector2())) { - return shape.translate(translateVector.scale( - Math.sin(nextTheta(sampleRate, translateSpeed, rotatePhase)) - )); - } - - return shape; - } - - private double nextTheta(double sampleRate, double frequency, Phase phase) { - phase.value += frequency / sampleRate; - - if (phase.value >= 1.0) { - phase.value = -1.0; - } - - return phase.value * Math.PI; - } - - private Shape scale(Shape shape) { - if (scale != 1) { - return shape.scale(scale); - } - - return shape; - } - - public void setRotationSpeed(double speed) { - this.rotateSpeed = speed; - } - - public void setTranslation(Vector2 translation) { - this.translateVector = translation; - } - - public void setTranslationSpeed(double speed) { - translateSpeed = speed; - } - - public void setScale(double scale) { - this.scale = scale; - } - @Override public void setQuality(double quality) { this.weight = quality; @@ -191,11 +115,11 @@ public class AudioPlayer implements Renderer> { if (defaultOutput == null) return; try (XtDevice device = service.openDevice(defaultOutput)) { - if (device.supportsFormat(FORMAT)) { + if (device.supportsFormat(format)) { - XtBufferSize size = device.getBufferSize(FORMAT); + XtBufferSize size = device.getBufferSize(format); XtStreamParams streamParams = new XtStreamParams(true, this::render, null, null); - XtDeviceStreamParams deviceParams = new XtDeviceStreamParams(streamParams, FORMAT, size.current); + XtDeviceStreamParams deviceParams = new XtDeviceStreamParams(streamParams, format, size.current); try (XtStream stream = device.openStream(deviceParams, null); XtSafeBuffer safe = XtSafeBuffer.register(stream, true)) { stream.start(); @@ -239,9 +163,4 @@ public class AudioPlayer implements Renderer> { effects.remove(identifier); } - private static final class Phase { - - private double value = 0; - } } - diff --git a/src/main/java/sh/ball/audio/effect/EffectType.java b/src/main/java/sh/ball/audio/effect/EffectType.java index 7bcec4c..642d76f 100644 --- a/src/main/java/sh/ball/audio/effect/EffectType.java +++ b/src/main/java/sh/ball/audio/effect/EffectType.java @@ -2,5 +2,8 @@ package sh.ball.audio.effect; public enum EffectType { VECTOR_CANCELLING, - BIT_CRUSH + BIT_CRUSH, + SCALE, + ROTATE, + TRANSLATE } diff --git a/src/main/java/sh/ball/audio/effect/PhaseEffect.java b/src/main/java/sh/ball/audio/effect/PhaseEffect.java new file mode 100644 index 0000000..d3d30cf --- /dev/null +++ b/src/main/java/sh/ball/audio/effect/PhaseEffect.java @@ -0,0 +1,28 @@ +package sh.ball.audio.effect; + +public abstract class PhaseEffect implements Effect { + + protected final int sampleRate; + + protected double speed; + private double phase; + + protected PhaseEffect(int sampleRate, double speed) { + this.sampleRate = sampleRate; + this.speed = speed; + } + + protected double nextTheta() { + phase += speed / sampleRate; + + if (phase >= 1.0) { + phase = -1.0; + } + + return phase * Math.PI; + } + + public void setSpeed(double speed) { + this.speed = speed; + } +} diff --git a/src/main/java/sh/ball/audio/effect/RotateEffect.java b/src/main/java/sh/ball/audio/effect/RotateEffect.java new file mode 100644 index 0000000..5be9abb --- /dev/null +++ b/src/main/java/sh/ball/audio/effect/RotateEffect.java @@ -0,0 +1,23 @@ +package sh.ball.audio.effect; + +import sh.ball.shapes.Vector2; + +public class RotateEffect extends PhaseEffect { + + public RotateEffect(int sampleRate, double speed) { + super(sampleRate, speed); + } + + public RotateEffect(int sampleRate) { + this(sampleRate, 0); + } + + @Override + public Vector2 apply(int count, Vector2 vector) { + if (speed != 0) { + return vector.rotate(nextTheta()); + } + + return vector; + } +} diff --git a/src/main/java/sh/ball/audio/effect/ScaleEffect.java b/src/main/java/sh/ball/audio/effect/ScaleEffect.java new file mode 100644 index 0000000..2794cd6 --- /dev/null +++ b/src/main/java/sh/ball/audio/effect/ScaleEffect.java @@ -0,0 +1,25 @@ +package sh.ball.audio.effect; + +import sh.ball.shapes.Vector2; + +public class ScaleEffect implements Effect { + + private double scale; + + public ScaleEffect(double scale) { + this.scale = scale; + } + + public ScaleEffect() { + this(1); + } + + public void setScale(double scale) { + this.scale = scale; + } + + @Override + public Vector2 apply(int count, Vector2 vector) { + return vector.scale(scale); + } +} diff --git a/src/main/java/sh/ball/audio/effect/TranslateEffect.java b/src/main/java/sh/ball/audio/effect/TranslateEffect.java new file mode 100644 index 0000000..b5ded48 --- /dev/null +++ b/src/main/java/sh/ball/audio/effect/TranslateEffect.java @@ -0,0 +1,30 @@ +package sh.ball.audio.effect; + +import sh.ball.shapes.Vector2; + +public class TranslateEffect extends PhaseEffect { + + private Vector2 translation; + + public TranslateEffect(int sampleRate, double speed, Vector2 translation) { + super(sampleRate, speed); + this.translation = translation; + } + + public TranslateEffect(int sampleRate) { + this(sampleRate, 0, new Vector2()); + } + + @Override + public Vector2 apply(int count, Vector2 vector) { + if (speed != 0 && !translation.equals(new Vector2())) { + return vector.translate(translation.scale(Math.sin(nextTheta()))); + } + + return vector; + } + + public void setTranslation(Vector2 translation) { + this.translation = translation; + } +} diff --git a/src/main/java/sh/ball/gui/Controller.java b/src/main/java/sh/ball/gui/Controller.java index 3488e07..3864ed7 100644 --- a/src/main/java/sh/ball/gui/Controller.java +++ b/src/main/java/sh/ball/gui/Controller.java @@ -1,8 +1,8 @@ package sh.ball.gui; import javafx.scene.control.*; -import sh.ball.audio.AudioPlayer; -import sh.ball.audio.effect.Effect; +import sh.ball.audio.Renderer; +import sh.ball.audio.effect.*; import sh.ball.audio.FrameProducer; import java.io.File; @@ -24,8 +24,6 @@ import javafx.stage.Stage; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; -import sh.ball.audio.effect.EffectType; -import sh.ball.audio.effect.EventFactory; import sh.ball.engine.Vector3; import sh.ball.parser.obj.ObjFrameSettings; import sh.ball.parser.obj.ObjParser; @@ -35,17 +33,18 @@ import sh.ball.shapes.Vector2; public class Controller implements Initializable { + private static final int SAMPLE_RATE = 192000; private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj"); private final FileChooser fileChooser = new FileChooser(); - // TODO: Reduce coupling on AudioPlayer - private final AudioPlayer renderer = new AudioPlayer(); + private final Renderer> renderer; private final ExecutorService executor = Executors.newSingleThreadExecutor(); - private FrameProducer> producer = new FrameProducer<>( - renderer, - new ObjParser(DEFAULT_OBJ).parse() - ); + private final RotateEffect rotateEffect = new RotateEffect(SAMPLE_RATE); + private final TranslateEffect translateEffect = new TranslateEffect(SAMPLE_RATE); + private final ScaleEffect scaleEffect = new ScaleEffect(); + + private FrameProducer> producer; private Stage stage; @@ -94,7 +93,12 @@ public class Controller implements Initializable { @FXML private Slider bitCrushSlider; - public Controller() throws IOException { + public Controller(Renderer> renderer) throws IOException { + this.renderer = renderer; + this.producer = new FrameProducer<>( + renderer, + new ObjParser(DEFAULT_OBJ).parse() + ); } private Map> initializeSliderMap() { @@ -102,11 +106,11 @@ public class Controller implements Initializable { weightSlider, new SliderUpdater<>(weightLabel::setText, renderer::setQuality), rotateSpeedSlider, - new SliderUpdater<>(rotateSpeedLabel::setText, renderer::setRotationSpeed), + new SliderUpdater<>(rotateSpeedLabel::setText, rotateEffect::setSpeed), translationSpeedSlider, - new SliderUpdater<>(translationSpeedLabel::setText, renderer::setTranslationSpeed), + new SliderUpdater<>(translationSpeedLabel::setText, translateEffect::setSpeed), scaleSlider, - new SliderUpdater<>(scaleLabel::setText, renderer::setScale), + new SliderUpdater<>(scaleLabel::setText, scaleEffect::setScale), focalLengthSlider, new SliderUpdater<>(focalLengthLabel::setText, this::setFocalLength) ); @@ -135,7 +139,7 @@ public class Controller implements Initializable { } InvalidationListener translationUpdate = observable -> - renderer.setTranslation(new Vector2( + translateEffect.setTranslation(new Vector2( tryParse(translationXTextField.getText()), tryParse(translationYTextField.getText()) )); @@ -150,7 +154,6 @@ public class Controller implements Initializable { tryParse(cameraZTextField.getText()) ))); - cameraXTextField.textProperty().addListener(cameraPosUpdate); cameraYTextField.textProperty().addListener(cameraPosUpdate); cameraZTextField.textProperty().addListener(cameraPosUpdate); @@ -175,6 +178,10 @@ public class Controller implements Initializable { } }); + renderer.addEffect(EffectType.SCALE, scaleEffect); + renderer.addEffect(EffectType.ROTATE, rotateEffect); + renderer.addEffect(EffectType.TRANSLATE, translateEffect); + executor.submit(producer); new Thread(renderer).start(); } diff --git a/src/main/java/sh/ball/gui/Gui.java b/src/main/java/sh/ball/gui/Gui.java index cb30077..f5cd95a 100644 --- a/src/main/java/sh/ball/gui/Gui.java +++ b/src/main/java/sh/ball/gui/Gui.java @@ -7,17 +7,20 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; +import sh.ball.audio.AudioPlayer; import java.util.Objects; public class Gui extends Application { + private static final int SAMPLE_RATE = 192000; + @Override public void start(Stage stage) throws Exception { FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml")); + Controller controller = new Controller(new AudioPlayer(SAMPLE_RATE)); + loader.setController(controller); Parent root = loader.load(); - Controller controller = loader.getController(); - controller.setStage(stage); stage.getIcons().add(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/icons/icon.png")))); stage.setTitle("osci-render"); diff --git a/src/main/resources/fxml/osci-render.fxml b/src/main/resources/fxml/osci-render.fxml index 5026a23..4741300 100644 --- a/src/main/resources/fxml/osci-render.fxml +++ b/src/main/resources/fxml/osci-render.fxml @@ -12,7 +12,7 @@ - + @@ -26,9 +26,9 @@