diff --git a/src/main/java/sh/ball/audio/effect/PerspectiveEffect.java b/src/main/java/sh/ball/audio/effect/PerspectiveEffect.java index 60a9386..ad4d80b 100644 --- a/src/main/java/sh/ball/audio/effect/PerspectiveEffect.java +++ b/src/main/java/sh/ball/audio/effect/PerspectiveEffect.java @@ -1,17 +1,27 @@ package sh.ball.audio.effect; +import org.luaj.vm2.LuaValue; import sh.ball.engine.Vector3; +import sh.ball.parser.lua.LuaExecutor; import sh.ball.shapes.Vector2; +import javax.xml.transform.Result; + // 3D rotation effect public class PerspectiveEffect implements SettableEffect { + private final LuaExecutor executor; + private double zPos = 1.0; private Vector3 baseRotation = new Vector3(Math.PI, Math.PI, 0); private Vector3 currentRotation = new Vector3(); private double rotateSpeed = 0.0; private double effectScale = 1.0; + public PerspectiveEffect(LuaExecutor executor) { + this.executor = executor; + } + @Override public Vector2 apply(int count, Vector2 vector) { currentRotation = currentRotation.add(baseRotation.scale(rotateSpeed)); @@ -19,10 +29,23 @@ public class PerspectiveEffect implements SettableEffect { Vector3 vertex = new Vector3(vector.x, vector.y, 0.0); vertex = vertex.rotate(baseRotation.add(currentRotation)); + executor.setVariable("x", vertex.x); + executor.setVariable("y", vertex.y); + executor.setVariable("z", -zPos); + double x = vertex.x; + double y = vertex.y; + double z = -zPos; + try { + LuaValue result = executor.execute(); + x = result.get(1).checkdouble(); + y = result.get(2).checkdouble(); + z = result.get(3).checkdouble(); + } catch (Exception ignored) {} + double focalLength = 1.0; return new Vector2( - (1 - effectScale) * vector.x + effectScale * (vertex.x * focalLength / (vertex.z - zPos)), - (1 - effectScale) * vector.y + effectScale * (vertex.y * focalLength / (vertex.z - zPos)) + (1 - effectScale) * vector.x + effectScale * (x * focalLength / z), + (1 - effectScale) * vector.y + effectScale * (y * focalLength / z) ); } diff --git a/src/main/java/sh/ball/audio/engine/XtAudioEngine.java b/src/main/java/sh/ball/audio/engine/XtAudioEngine.java index b534b9a..914bba6 100644 --- a/src/main/java/sh/ball/audio/engine/XtAudioEngine.java +++ b/src/main/java/sh/ball/audio/engine/XtAudioEngine.java @@ -1,6 +1,5 @@ package sh.ball.audio.engine; -import com.sun.javafx.PlatformUtil; import sh.ball.shapes.Vector2; import xt.audio.*; @@ -237,13 +236,7 @@ public class XtAudioEngine implements AudioEngine { // connects to an XtAudio XtService in order of lowest latency to highest latency private XtService getService(XtPlatform platform) { - XtService service = null; - if ((PlatformUtil.isLinux() || PlatformUtil.isUnix()) && !PlatformUtil.isMac()) { - service = platform.getService(Enums.XtSystem.JACK); - } - if (service == null) { - service = platform.getService(platform.setupToSystem(Enums.XtSetup.SYSTEM_AUDIO)); - } + XtService service = platform.getService(platform.setupToSystem(Enums.XtSetup.SYSTEM_AUDIO)); if (service == null) { service = platform.getService(platform.setupToSystem(Enums.XtSetup.PRO_AUDIO)); } diff --git a/src/main/java/sh/ball/gui/Gui.java b/src/main/java/sh/ball/gui/Gui.java index 61f0d7b..1e7771d 100644 --- a/src/main/java/sh/ball/gui/Gui.java +++ b/src/main/java/sh/ball/gui/Gui.java @@ -31,6 +31,7 @@ import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.logging.*; public class Gui extends Application { @@ -40,12 +41,11 @@ public class Gui extends Application { public static String LOG_DIR = "./logs/"; public static final Logger logger = Logger.getLogger(Gui.class.getName()); - public static ProjectSelectController projectSelectController; public static ShapeAudioPlayer audioPlayer; public static AudioDevice defaultDevice; - public static CodeEditor editor; - public static Stage editorStage; - public static Scene editorScene; + + private static CodeEditor editor; + private static Stage editorStage; static { try { @@ -79,13 +79,11 @@ public class Gui extends Application { new Thread(midiCommunicator).start(); } - private Stage stage; private Scene scene; private Parent root; @Override public void start(Stage stage) throws Exception { - this.stage = stage; System.setProperty("prism.lcdtext", "false"); System.setProperty("org.luaj.luajc", "true"); @@ -93,7 +91,7 @@ public class Gui extends Application { FXMLLoader projectSelectLoader = new FXMLLoader(getClass().getResource("/fxml/projectSelect.fxml")); Parent projectSelectRoot = projectSelectLoader.load(); - projectSelectController = projectSelectLoader.getController(); + ProjectSelectController projectSelectController = projectSelectLoader.getController(); projectSelectController.setOpenBrowser(url -> getHostServices().showDocument(url)); stage.getIcons().add(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/icons/icon.png")))); @@ -166,12 +164,11 @@ public class Gui extends Application { editor = new CodeEditor(); editor.initialize(); editorStage = new Stage(); - editorScene = new Scene(editor, 900, 600, false, SceneAntialiasing.BALANCED); + Scene editorScene = new Scene(editor, 900, 600, false, SceneAntialiasing.BALANCED); editorStage.setScene(editorScene); editorStage.getIcons().add(new Image(Objects.requireNonNull(Gui.class.getResourceAsStream("/icons/icon.png")))); editor.prefHeightProperty().bind(editorStage.heightProperty()); editor.prefWidthProperty().bind(editorStage.widthProperty()); - editor.setCallback(controller::updateFileData); } public void launchMainApplication(MainController controller, String projectPath, Boolean muted) throws Exception { @@ -194,20 +191,12 @@ public class Gui extends Application { } } - public static void launchCodeEditor(String code, String fileName) { + public static void launchCodeEditor(String code, String fileName, String mimeType, BiConsumer callback) { editor.setCode(code, fileName); + editor.setCallback(callback); editorStage.show(); editorStage.setTitle(fileName); - - if (LuaParser.isLuaFile(fileName)) { - editor.setMode("text/x-lua"); - } else if (ObjParser.isObjFile(fileName)) { - editor.setMode(""); - } else if (SvgParser.isSvgFile(fileName)) { - editor.setMode("text/html"); - } else if (TextParser.isTxtFile(fileName)) { - editor.setMode(""); - } + editor.setMode(mimeType); } public static void closeCodeEditor() { diff --git a/src/main/java/sh/ball/gui/controller/EffectsController.java b/src/main/java/sh/ball/gui/controller/EffectsController.java index 37049c9..5f6d0f7 100644 --- a/src/main/java/sh/ball/gui/controller/EffectsController.java +++ b/src/main/java/sh/ball/gui/controller/EffectsController.java @@ -13,11 +13,15 @@ import org.w3c.dom.Element; import sh.ball.audio.FrequencyAnalyser; import sh.ball.audio.effect.*; import sh.ball.audio.engine.AudioDevice; +import sh.ball.gui.Gui; import sh.ball.gui.components.EffectComponentGroup; +import sh.ball.parser.lua.LuaExecutor; +import sh.ball.parser.lua.LuaParser; import sh.ball.shapes.Shape; import sh.ball.shapes.Vector2; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -29,6 +33,8 @@ public class EffectsController implements Initializable, SubController { private static final int DEFAULT_SAMPLE_RATE = 192000; + private final LuaExecutor executor = new LuaExecutor(LuaExecutor.STANDARD_GLOBALS); + private final WobbleEffect wobbleEffect; private final PerspectiveEffect perspectiveEffect; private final TranslateEffect translateEffect; @@ -88,10 +94,12 @@ public class EffectsController implements Initializable, SubController { private Button resetRotationButton; @FXML private Button resetPerspectiveRotationButton; + @FXML + private Button depthFunctionButton; public EffectsController() { this.wobbleEffect = new WobbleEffect(DEFAULT_SAMPLE_RATE); - this.perspectiveEffect = new PerspectiveEffect(); + this.perspectiveEffect = new PerspectiveEffect(executor); this.translateEffect = new TranslateEffect(DEFAULT_SAMPLE_RATE, 1, new Vector2()); this.rotateEffect = new RotateEffect(DEFAULT_SAMPLE_RATE); } @@ -237,6 +245,14 @@ public class EffectsController implements Initializable, SubController { rotateSpeed3D.controller.slider.setValue(0); perspectiveEffect.resetRotation(); }); + + String script = "return { x, y, z }"; + executor.setScript(script); + depthFunctionButton.setOnAction(e -> Gui.launchCodeEditor(script, "3D Perspective Effect Depth Function", "text/x-lua", this::updateDepthFunction)); + } + + private void updateDepthFunction(byte[] fileData, String fileName) { + executor.setScript(new String(fileData, StandardCharsets.UTF_8)); } private List effects() { diff --git a/src/main/java/sh/ball/gui/controller/MainController.java b/src/main/java/sh/ball/gui/controller/MainController.java index 3bdc5dc..16a082a 100644 --- a/src/main/java/sh/ball/gui/controller/MainController.java +++ b/src/main/java/sh/ball/gui/controller/MainController.java @@ -4,10 +4,15 @@ import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Platform; import javafx.collections.FXCollections; +import javafx.event.Event; import javafx.scene.control.*; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.TextField; +import javafx.scene.control.skin.ComboBoxListViewSkin; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.SVGPath; @@ -61,6 +66,7 @@ import sh.ball.parser.lua.LuaParser; import sh.ball.parser.obj.ObjFrameSettings; import sh.ball.parser.obj.ObjParser; import sh.ball.parser.ParserFactory; +import sh.ball.parser.svg.SvgParser; import sh.ball.parser.txt.FontStyle; import sh.ball.shapes.Shape; import sh.ball.shapes.ShapeFrameSource; @@ -142,6 +148,8 @@ public class MainController implements Initializable, FrequencyListener, MidiLis @FXML private MenuItem openProjectMenuItem; @FXML + private Menu audioMenu; + @FXML private Menu recentProjectMenu; @FXML private MenuItem saveProjectMenuItem; @@ -184,6 +192,8 @@ public class MainController implements Initializable, FrequencyListener, MidiLis @FXML private ComboBox deviceComboBox; @FXML + private CustomMenuItem audioDeviceMenuItem; + @FXML private Slider brightnessSlider; @FXML private CustomMenuItem recordLengthMenuItem; @@ -1397,7 +1407,14 @@ public class MainController implements Initializable, FrequencyListener, MidiLis void openCodeEditor() { String code = new String(openFiles.get(currentFrameSource), StandardCharsets.UTF_8); closeCodeEditor(); - launchCodeEditor(code, frameSourcePaths.get(currentFrameSource)); + String name = frameSourcePaths.get(currentFrameSource); + String mimeType = ""; + if (LuaParser.isLuaFile(name)) { + mimeType = "text/x-lua"; + } else if (SvgParser.isSvgFile(name)) { + mimeType = "text/html"; + } + launchCodeEditor(code, frameSourcePaths.get(currentFrameSource), mimeType, this::updateFileData); } private void updateTitle(String message, String projectName) { diff --git a/src/main/java/sh/ball/parser/lua/LuaExecutor.java b/src/main/java/sh/ball/parser/lua/LuaExecutor.java new file mode 100644 index 0000000..15459da --- /dev/null +++ b/src/main/java/sh/ball/parser/lua/LuaExecutor.java @@ -0,0 +1,200 @@ +package sh.ball.parser.lua; + +import org.luaj.vm2.*; +import org.luaj.vm2.lib.ThreeArgFunction; +import org.luaj.vm2.lib.TwoArgFunction; +import org.luaj.vm2.lib.jse.CoerceJavaToLua; +import org.luaj.vm2.lib.jse.JsePlatform; + +import javax.script.ScriptException; +import java.io.StringReader; +import java.util.*; +import java.util.logging.Level; + +import static sh.ball.gui.Gui.logger; + +public class LuaExecutor { + + public static final Globals STANDARD_GLOBALS = JsePlatform.standardGlobals(); + + static { + org.luaj.vm2.luajc.LuaJC.install(STANDARD_GLOBALS); + } + + private final Globals globals; + private final Map bindings = new HashMap<>(); + private final Set programDefinedVariables = new HashSet<>(); + private LuaFunction lastWorkingFunction; + private String lastWorkingTextScript; + private LuaFunction function; + private String textScript; + private long step = 1; + + public LuaExecutor(Globals globals) { + this.globals = globals; + } + + public void setScript(String script) { + LuaFunction f = LuaExecutor.STANDARD_GLOBALS.load(new StringReader(script), "script").checkfunction(); + setFunction(script, f); + } + + private void setFunction(String textScript, LuaFunction function) { + if (lastWorkingFunction == null) { + lastWorkingFunction = function; + lastWorkingTextScript = textScript; + } + this.function = function; + this.textScript = textScript; + } + + public LuaValue execute() { + try { + boolean updatedFile = false; + + globals.setmetatable(new BindingsMetatable(bindings)); + bindings.put(LuaValue.valueOf("step"), LuaValue.valueOf((double) step)); + + LuaValue result; + try { + result = (LuaValue) LuaExecutor.eval(function, globals); + if (!lastWorkingTextScript.equals(textScript)) { + lastWorkingFunction = function; + lastWorkingTextScript = textScript; + updatedFile = true; + } + } catch (Exception e) { + logger.log(Level.INFO, e.getMessage(), e); + result = (LuaValue) LuaExecutor.eval(lastWorkingFunction, globals); + } + step++; + + if (updatedFile) { + // reset variables + step = 1; + List variablesToRemove = new ArrayList<>(); + bindings.keySet().forEach(name -> { + if (!programDefinedVariables.contains(name)) { + variablesToRemove.add(name); + } + }); + variablesToRemove.forEach(bindings::remove); + } + + return result; + } catch (Exception e) { + logger.log(Level.INFO, e.getMessage(), e); + } + + return LuaValue.NIL; + } + + public void setVariable(String variableName, Object value) { + LuaString name = LuaValue.valueOf(variableName); + programDefinedVariables.add(name); + bindings.put(name, toLua(value)); + } + + public void resetStep() { + step = 1; + } + + /* +MIT License + +Copyright (c) 2007 LuaJ. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit p ersons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------ + +The below functions have all been modified by James Ball to optimise Lua parsing +on machines with poorer performance. + + + */ + + static class BindingsMetatable extends LuaTable { + + BindingsMetatable(Map bindings) { + this.rawset(LuaValue.INDEX, new TwoArgFunction() { + public LuaValue call(LuaValue table, LuaValue key) { + LuaValue value = bindings.get(key.checkstring()); + if (value == null) { + return LuaValue.NIL; + } else { + return value; + } + } + }); + this.rawset(LuaValue.NEWINDEX, new ThreeArgFunction() { + public LuaValue call(LuaValue table, LuaValue key, LuaValue value) { + if (value == null || value.type() == LuaValue.TNIL) { + bindings.remove(key.checkstring()); + } else { + bindings.put(key.checkstring(), value); + } + return LuaValue.NONE; + } + }); + } + } + + static private LuaValue toLua(Object javaValue) { + return javaValue == null? LuaValue.NIL: + javaValue instanceof LuaValue? (LuaValue) javaValue: + CoerceJavaToLua.coerce(javaValue); + } + + static private Object toJava(LuaValue luajValue) { + return switch (luajValue.type()) { + case LuaValue.TNIL -> null; + case LuaValue.TSTRING -> luajValue.tojstring(); + case LuaValue.TUSERDATA -> luajValue.checkuserdata(Object.class); + case LuaValue.TNUMBER -> luajValue.isinttype() ? + (Object) luajValue.toint() : + (Object) luajValue.todouble(); + default -> luajValue; + }; + } + + static private Object toJava(Varargs v) { + final int n = v.narg(); + switch (n) { + case 0: return null; + case 1: return toJava(v.arg1()); + default: + Object[] o = new Object[n]; + for (int i=0; i> { - private static final Globals globals = JsePlatform.standardGlobals(); - - static { - org.luaj.vm2.luajc.LuaJC.install(globals); - } - - private final LuaSampleSource sampleSource = new LuaSampleSource(globals); + private final LuaSampleSource sampleSource = new LuaSampleSource(LuaExecutor.STANDARD_GLOBALS); private String script; @@ -35,10 +25,7 @@ public class LuaParser extends FileParser> { @Override public FrameSource parse() throws Exception { - LuaFunction f = globals.load(new StringReader(script), "script").checkfunction(); - - sampleSource.setFunction(script, f); - + sampleSource.setScript(script); return sampleSource; } diff --git a/src/main/java/sh/ball/parser/lua/LuaSampleSource.java b/src/main/java/sh/ball/parser/lua/LuaSampleSource.java index 57bd62c..c060d91 100644 --- a/src/main/java/sh/ball/parser/lua/LuaSampleSource.java +++ b/src/main/java/sh/ball/parser/lua/LuaSampleSource.java @@ -18,69 +18,21 @@ import static sh.ball.gui.Gui.logger; public class LuaSampleSource implements FrameSource { - private final Globals globals; - private final Map bindings = new HashMap<>(); - private final Set programDefinedVariables = new HashSet<>(); - private LuaFunction lastWorkingFunction; - private String lastWorkingTextScript; - private LuaFunction function; - private String textScript; + private final LuaExecutor executor; private boolean active = true; - private long step = 1; public LuaSampleSource(Globals globals) { - this.globals = globals; + this.executor = new LuaExecutor(globals); } - public void setFunction(String textScript, LuaFunction function) { - if (lastWorkingFunction == null) { - lastWorkingFunction = function; - lastWorkingTextScript = textScript; - } - this.function = function; - this.textScript = textScript; + public void setScript(String textScript) { + executor.setScript(textScript); } @Override public Vector2 next() { - try { - boolean updatedFile = false; - - globals.setmetatable(new BindingsMetatable(bindings)); - bindings.put(LuaValue.valueOf("step"), LuaValue.valueOf((double) step)); - - LuaValue result; - try { - result = (LuaValue) LuaSampleSource.eval(function, globals); - if (!lastWorkingTextScript.equals(textScript)) { - lastWorkingFunction = function; - lastWorkingTextScript = textScript; - updatedFile = true; - } - } catch (Exception e) { - logger.log(Level.INFO, e.getMessage(), e); - result = (LuaValue) LuaSampleSource.eval(lastWorkingFunction, globals); - } - step++; - - if (updatedFile) { - // reset variables - step = 1; - List variablesToRemove = new ArrayList<>(); - bindings.keySet().forEach(name -> { - if (!programDefinedVariables.contains(name)) { - variablesToRemove.add(name); - } - }); - variablesToRemove.forEach(bindings::remove); - } - - return new Vector2(result.get(1).checkdouble(), result.get(2).checkdouble()); - } catch (Exception e) { - logger.log(Level.INFO, e.getMessage(), e); - } - - return new Vector2(); + LuaValue result = executor.execute(); + return new Vector2(result.get(1).checkdouble(), result.get(2).checkdouble()); } @Override @@ -107,110 +59,10 @@ public class LuaSampleSource implements FrameSource { } public void setVariable(String variableName, Object value) { - programDefinedVariables.add(LuaValue.valueOf(variableName)); - bindings.put(LuaValue.valueOf(variableName), toLua(value)); + executor.setVariable(variableName, value); } public void resetStep() { - step = 1; - } - - /* -MIT License - -Copyright (c) 2007 LuaJ. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit p ersons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ------------------------------- - -The below functions have all been modified by James Ball to optimise Lua parsing -on machines with poorer performance. - - - */ - - static class BindingsMetatable extends LuaTable { - - BindingsMetatable(Map bindings) { - this.rawset(LuaValue.INDEX, new TwoArgFunction() { - public LuaValue call(LuaValue table, LuaValue key) { - LuaValue value = bindings.get(key.checkstring()); - if (value == null) { - return LuaValue.NIL; - } else { - return value; - } - } - }); - this.rawset(LuaValue.NEWINDEX, new ThreeArgFunction() { - public LuaValue call(LuaValue table, LuaValue key, LuaValue value) { - if (value == null || value.type() == LuaValue.TNIL) { - bindings.remove(key.checkstring()); - } else { - bindings.put(key.checkstring(), value); - } - return LuaValue.NONE; - } - }); - } - } - - static private LuaValue toLua(Object javaValue) { - return javaValue == null? LuaValue.NIL: - javaValue instanceof LuaValue? (LuaValue) javaValue: - CoerceJavaToLua.coerce(javaValue); - } - - static private Object toJava(LuaValue luajValue) { - return switch (luajValue.type()) { - case LuaValue.TNIL -> null; - case LuaValue.TSTRING -> luajValue.tojstring(); - case LuaValue.TUSERDATA -> luajValue.checkuserdata(Object.class); - case LuaValue.TNUMBER -> luajValue.isinttype() ? - (Object) luajValue.toint() : - (Object) luajValue.todouble(); - default -> luajValue; - }; - } - - static private Object toJava(Varargs v) { - final int n = v.narg(); - switch (n) { - case 0: return null; - case 1: return toJava(v.arg1()); - default: - Object[] o = new Object[n]; - for (int i=0; i - + + +