diff --git a/Platform/Apple/tools/jace/.vscode/launch.json b/Platform/Apple/tools/jace/.vscode/launch.json new file mode 100644 index 00000000..fd194888 --- /dev/null +++ b/Platform/Apple/tools/jace/.vscode/launch.json @@ -0,0 +1,55 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "request": "attach", + "name": "Attach by Process ID", + "processId": "${command:PickJavaProcess}" + }, + { + "type": "java", + "name": "Current File", + "request": "launch", + "mainClass": "${file}" + }, + { + "type": "java", + "name": "ConvertDiskImage", + "request": "launch", + "mainClass": "lawlesslegends/jace.ConvertDiskImage", + "projectName": "lawlesslegends" + }, + { + "type": "java", + "name": "LawlessLegends", + "request": "launch", + "mainClass": "lawlesslegends/jace.LawlessLegends", + "projectName": "lawlesslegends" + }, + { + "type": "java", + "name": "ApplesoftProgram", + "request": "launch", + "mainClass": "lawlesslegends/jace.applesoft.ApplesoftProgram", + "projectName": "lawlesslegends" + }, + { + "type": "java", + "name": "AcmeCrossAssembler", + "request": "launch", + "mainClass": "lawlesslegends/jace.assembly.AcmeCrossAssembler", + "projectName": "lawlesslegends" + }, + { + "type": "java", + "name": "Library", + "request": "launch", + "mainClass": "lawlesslegends/jace.ui.Library", + "projectName": "lawlesslegends" + } + ] +} \ No newline at end of file diff --git a/Platform/Apple/tools/jace/.vscode/settings.json b/Platform/Apple/tools/jace/.vscode/settings.json new file mode 100644 index 00000000..e0f15db2 --- /dev/null +++ b/Platform/Apple/tools/jace/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/Platform/Apple/tools/jace/pom.xml b/Platform/Apple/tools/jace/pom.xml index ee9ce579..d79876a9 100644 --- a/Platform/Apple/tools/jace/pom.xml +++ b/Platform/Apple/tools/jace/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 org.8bitbunch @@ -13,6 +15,7 @@ UTF-8 jace.LawlessLegends apache20 + 3.3.3 @@ -43,7 +46,7 @@ com.gluonhq gluonfx-maven-plugin - 1.0.19 + 1.0.21 jace.LawlessLegends @@ -55,44 +58,44 @@ lawlesslegends/jace.LawlessLegends - - - - default-cli - - - - - debug - - - - - - - - - ide-debug - - - - - - - - - ide-profile - - - - - - - - - - - + + + + default-cli + + + + + debug + + + + + + + + + ide-debug + + + + + + + + + ide-profile + + + + + + + + + + + @@ -116,8 +119,8 @@ module nestedvm { - exports org.ibex.nestedvm; - exports org.ibex.nestedvm.util; + exports org.ibex.nestedvm; + exports org.ibex.nestedvm.util; } @@ -130,13 +133,25 @@ + + + + org.lwjgl + lwjgl-bom + ${lwjgl.version} + import + pom + + + + junit junit 4.13.2 test - + org.xerial.thirdparty nestedvm @@ -175,18 +190,69 @@ org.openjfx javafx-swing - 18 + 20 jar org.lwjgl - lwjgl-openal - 3.3.2 + lwjgl - javazoom + org.lwjgl + lwjgl-openal + + + org.lwjgl + lwjgl + ${lwjgl.natives} + + + org.lwjgl + lwjgl-openal + ${lwjgl.natives} + + + com.badlogicgames.jlayer jlayer - 1.0.1 + 1.0.2-gdx - + + + lwjgl-natives-linux-amd64 + + + unix + amd64 + + + + natives-linux + + + + lwjgl-natives-macos-x86_64 + + + mac + x86_64 + + + + natives-macos + + + + lwjgl-natives-windows-amd64 + + + windows + amd64 + + + + natives-windows + + + + \ No newline at end of file diff --git a/Platform/Apple/tools/jace/src/main/java/jace/Emulator.java b/Platform/Apple/tools/jace/src/main/java/jace/Emulator.java index 5bf4ff07..c5220d1c 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/Emulator.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/Emulator.java @@ -35,7 +35,7 @@ import jace.lawless.LawlessComputer; public class Emulator { public static Emulator instance; - public static EmulatorUILogic logic = new EmulatorUILogic(); + private static EmulatorUILogic logic; public static Thread mainThread; // public static void main(String... args) { @@ -44,6 +44,13 @@ public class Emulator { // } private final LawlessComputer computer; + + public static EmulatorUILogic getUILogic() { + if (logic == null) { + logic = new EmulatorUILogic(); + } + return logic; + } public static Emulator getInstance(List args) { Emulator i = getInstance(); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/EmulatorUILogic.java b/Platform/Apple/tools/jace/src/main/java/jace/EmulatorUILogic.java index 57944d3d..f83ef650 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/EmulatorUILogic.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/EmulatorUILogic.java @@ -18,6 +18,8 @@ */ package jace; +import static jace.core.Utility.gripe; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -44,7 +46,6 @@ import jace.config.Reconfigurable; import jace.core.Debugger; import jace.core.RAM; import jace.core.RAMListener; -import static jace.core.Utility.gripe; import jace.ide.IdeController; import javafx.application.Platform; import javafx.event.EventHandler; @@ -90,7 +91,7 @@ public class EmulatorUILogic implements Reconfigurable { category = "General", name = "Show Drives" ) - public boolean showDrives = false; + public boolean showDrives = Emulator.withComputer(c->!c.PRODUCTION_MODE, false); public static void updateCPURegisters(MOS65C02 cpu) { // DebuggerPanel debuggerPanel = Emulator.getFrame().getDebuggerPanel(); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/JaceUIController.java b/Platform/Apple/tools/jace/src/main/java/jace/JaceUIController.java index a14b28d6..ce188004 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/JaceUIController.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/JaceUIController.java @@ -246,9 +246,9 @@ public class JaceUIController { }); speedSlider.valueProperty().addListener((val, oldValue, newValue) -> setSpeed(newValue.doubleValue())); Platform.runLater(() -> { - speedSlider.setValue(Emulator.logic.speedSetting); + speedSlider.setValue(Emulator.getUILogic().speedSetting); // Kind of redundant but make sure speed is properly set as if the user did it - setSpeed(Emulator.logic.speedSetting); + setSpeed(Emulator.getUILogic().speedSetting); }); musicSelection.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> Emulator.withComputer(computer -> @@ -268,7 +268,7 @@ public class JaceUIController { } public void setSpeed(double speed) { - Emulator.logic.speedSetting = (int) speed; + Emulator.getUILogic().speedSetting = (int) speed; double speedRatio = convertSpeedToRatio(speed); if (speedSlider.getValue() != speed) { Platform.runLater(()->speedSlider.setValue(speed)); @@ -404,7 +404,7 @@ public class JaceUIController { private List getMediaConsumers() { List consumers = new ArrayList<>(); Emulator.withComputer(c -> consumers.add(((LawlessComputer) c).getUpgradeHandler())); - if (Emulator.logic.showDrives) { + if (Emulator.getUILogic().showDrives) { Emulator.withMemory(m -> { for (Optional card : m.getAllCards()) { card.filter(c -> c instanceof MediaConsumerParent).ifPresent(parent -> diff --git a/Platform/Apple/tools/jace/src/main/java/jace/LawlessLegends.java b/Platform/Apple/tools/jace/src/main/java/jace/LawlessLegends.java index de3d4289..28952342 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/LawlessLegends.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/LawlessLegends.java @@ -138,7 +138,7 @@ public class LawlessLegends extends Application { new Thread(()->{ Logger.getLogger(getClass().getName()).log(Level.WARNING, "Booting with watchdog"); RAMListener startListener = c.getMemory(). - observe(RAMEvent.TYPE.EXECUTE, 0x2000, (e) -> { + observe("Lawless Legends watchdog", RAMEvent.TYPE.EXECUTE, 0x2000, (e) -> { Logger.getLogger(getClass().getName()).log(Level.WARNING, "Boot was detected, watchdog terminated."); romStarted = true; }); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Apple2e.java b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Apple2e.java index 98b7917b..6758f149 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Apple2e.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Apple2e.java @@ -76,9 +76,9 @@ public class Apple2e extends Computer { @ConfigurableField(name = "Slot 5", shortName = "s5card") public DeviceSelection card5 = new DeviceSelection<>(Cards.class, null); @ConfigurableField(name = "Slot 6", shortName = "s6card") - public DeviceSelection card6 = new DeviceSelection<>(Cards.class, Cards.DiskIIDrive); + public DeviceSelection card6 = new DeviceSelection<>(Cards.class, Cards.DiskIIDrive, true); @ConfigurableField(name = "Slot 7", shortName = "s7card") - public DeviceSelection card7 = new DeviceSelection<>(Cards.class, Cards.MassStorage); + public DeviceSelection card7 = new DeviceSelection<>(Cards.class, Cards.MassStorage, true); @ConfigurableField(name = "Debug rom", shortName = "debugRom", description = "Use debugger //e rom") public boolean useDebugRom = false; @ConfigurableField(name = "Helpful hints", shortName = "hints") @@ -434,7 +434,7 @@ public class Apple2e extends Computer { private void enableHints() { if (hints.isEmpty()) { - hints.add(getMemory().observe(RAMEvent.TYPE.EXECUTE, 0x0FB63, (e)->{ + hints.add(getMemory().observe("Helpful hints", RAMEvent.TYPE.EXECUTE, 0x0FB63, (e)->{ animationTimer.schedule(drawHints, 1, TimeUnit.SECONDS); animationSchedule = animationTimer.scheduleAtFixedRate(doAnimation, 1250, 100, TimeUnit.MILLISECONDS); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/RAM128k.java b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/RAM128k.java index 940b1b44..9c01dafb 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/RAM128k.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/RAM128k.java @@ -53,9 +53,9 @@ abstract public class RAM128k extends RAM { Function factory; String name; - Class clazz; + Class clazz; - RamCards(String name, Class clazz, Function factory) { + RamCards(String name, Class clazz, Function factory) { this.factory = factory; this.name = name; this.clazz = clazz; @@ -409,7 +409,7 @@ abstract public class RAM128k extends RAM { * */ @Override - public synchronized void configureActiveMemory() { + public void configureActiveMemory() { String auxZpConfiguration = getAuxZPConfiguration(); String readConfiguration = getReadConfiguration() + auxZpConfiguration; String writeConfiguration = getWriteConfiguration() + auxZpConfiguration; diff --git a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Speaker.java b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Speaker.java index d78f6c77..eb70f6c4 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Speaker.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Speaker.java @@ -24,6 +24,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Timer; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; @@ -37,6 +38,7 @@ import jace.core.RAMEvent; import jace.core.RAMListener; import jace.core.SoundGeneratorDevice; import jace.core.SoundMixer; +import jace.core.SoundMixer.SoundBuffer; import javafx.stage.FileChooser; /** @@ -108,23 +110,15 @@ public class Speaker extends SoundGeneratorDevice { * Manifestation of the apple speaker softswitch */ private boolean speakerBit = false; - // - /** - * Locking semaphore to prevent race conditions when working with buffer or - * related variables - */ - private final Object bufferLock = new Object(); /** * Double-buffer used for playing processed sound -- as one is played the * other fills up. */ - private byte[] primaryBuffer; - private byte[] secondaryBuffer; - private int bufferPos = 0; private Timer playbackTimer; private double TICKS_PER_SAMPLE = ((double) Motherboard.DEFAULT_SPEED) / SoundMixer.RATE; private double TICKS_PER_SAMPLE_FLOOR = Math.floor(TICKS_PER_SAMPLE); private RAMListener listener = null; + private SoundBuffer buffer = null; /** * Creates a new instance of Speaker @@ -148,11 +142,14 @@ public class Speaker extends SoundGeneratorDevice { playbackTimer = null; } speakerBit = false; - if (sdl != null && sdl.isOpen()) { - sdl.stop(); - sdl.close(); + if (buffer != null) { + try { + buffer.shutdown(); + } catch (InterruptedException | ExecutionException e) { + // Ignore + } } - sdl = null; + buffer = null; return result; } @@ -162,19 +159,17 @@ public class Speaker extends SoundGeneratorDevice { */ @Override public void resume() { - if (sdl == null || !sdl.isOpen()) { - sdl = computer.mixer.getLine(); - if (sdl != null) { - sdl.start(); - counter = 0; - idleCycles = 0; - level = 0; - bufferPos = 0; - } else { - Logger.getLogger(getClass().getName()).severe("Unable to get audio line for speaker!"); - detach(); - return; - } + if (buffer == null || !buffer.isAlive()) { + buffer = SoundMixer.createBuffer(false); + } + if (buffer != null) { + counter = 0; + idleCycles = 0; + level = 0; + } else { + Logger.getLogger(getClass().getName()).severe("Unable to get audio buffer for speaker!"); + detach(); + return; } if (force1mhz) { @@ -187,27 +182,13 @@ public class Speaker extends SoundGeneratorDevice { setRun(true); } - public void playCurrentBuffer() { - byte[] buffer; - int len; - synchronized (bufferLock) { - len = bufferPos; - buffer = primaryBuffer; - primaryBuffer = secondaryBuffer; - bufferPos = 0; - } - secondaryBuffer = buffer; - if (sdl != null && len > 0) { - sdl.write(buffer, 0, len); - } - } - /** * Reset idle counter whenever sound playback occurs */ public void resetIdle() { idleCycles = 0; if (!isRunning()) { + speakerBit = false; resume(); } } @@ -237,28 +218,32 @@ public class Speaker extends SoundGeneratorDevice { } private void toggleSpeaker(RAMEvent e) { - if (e.getType() == RAMEvent.TYPE.WRITE) { - level += 2; - } + // if (e.getType() == RAMEvent.TYPE.WRITE) { + // level += 2; + // } speakerBit = !speakerBit; resetIdle(); } private void playSample(int sample) { - if (sdl == null || !sdl.isOpen()) { - resume(); + if (buffer == null || !buffer.isAlive()) { + Logger.getLogger(getClass().getName()).severe("Audio buffer not initalized properly!"); + buffer = SoundMixer.createBuffer(false); + } + try { + buffer.playSample((short) sample); + } catch (InterruptedException | ExecutionException e) { + // TODO: Do we need to really worry about this? + e.printStackTrace(); } - int bytes = SoundMixer.BITS >> 3; - // Prepare sound output in little endian format - for (int i = 0; i < bytes; i++) { - primaryBuffer[i] = primaryBuffer[i+bytes] = (byte) (sample & 0x0ff); - sample >>= 8; - } - sdl.write(primaryBuffer, 0, bytes*2); if (fileOutputActive) { + byte[] bytes = new byte[2]; + bytes[0] = (byte) (sample & 0x0ff); + bytes[1] = (byte) ((sample >> 8) & 0x0ff); + try { - out.write(primaryBuffer, 0, bytes*2); + out.write(bytes, 0, 2); } catch (IOException ex) { Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Error recording sound", ex); toggleFileOutput(); @@ -271,7 +256,7 @@ public class Speaker extends SoundGeneratorDevice { * Add a memory event listener for C03x for capturing speaker events */ private void configureListener() { - listener = computer.getMemory().observe(RAMEvent.TYPE.ANY, 0x0c030, 0x0c03f, this::toggleSpeaker); + listener = computer.getMemory().observe("Speaker", RAMEvent.TYPE.ANY, 0x0c030, 0x0c03f, this::toggleSpeaker); } private void removeListener() { @@ -295,14 +280,7 @@ public class Speaker extends SoundGeneratorDevice { @Override public final void reconfigure() { - super.reconfigure(); - - if (primaryBuffer != null && secondaryBuffer != null) { - return; - } - BUFFER_SIZE = 20000 * (SoundMixer.BITS >> 3); - primaryBuffer = new byte[BUFFER_SIZE]; - secondaryBuffer = new byte[BUFFER_SIZE]; + super.reconfigure(); } @Override diff --git a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/VideoDHGR.java b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/VideoDHGR.java index 367107ed..29fb722a 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/VideoDHGR.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/VideoDHGR.java @@ -719,8 +719,8 @@ public class VideoDHGR extends Video { } private void registerDirtyFlagChecks() { - computer.getMemory().observe(RAMEvent.TYPE.WRITE, 0x0400, 0x0bff, this::registerTextDirtyFlag); - computer.getMemory().observe(RAMEvent.TYPE.WRITE, 0x02000, 0x05fff, this::registerHiresDirtyFlag); + computer.getMemory().observe("Check for text changes", RAMEvent.TYPE.WRITE, 0x0400, 0x0bff, this::registerTextDirtyFlag); + computer.getMemory().observe("Check for graphics changes", RAMEvent.TYPE.WRITE, 0x02000, 0x05fff, this::registerHiresDirtyFlag); } @Override diff --git a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/VideoNTSC.java b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/VideoNTSC.java index 18a5c225..c2701a40 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/VideoNTSC.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/VideoNTSC.java @@ -423,11 +423,11 @@ public class VideoNTSC extends VideoDHGR { return; } RAM memory = computer.getMemory(); - rgbStateListeners.add(memory.observe(RAMEvent.TYPE.ANY, 0x0c05e, (e) -> { + rgbStateListeners.add(memory.observe("NTSC: AN3 state change", RAMEvent.TYPE.ANY, 0x0c05e, (e) -> { an3 = false; rgbStateChange(); })); - rgbStateListeners.add(memory.observe(RAMEvent.TYPE.ANY, 0x0c05f, (e) -> { + rgbStateListeners.add(memory.observe("NTSC: 80COL state change", RAMEvent.TYPE.ANY, 0x0c05f, (e) -> { if (!an3) { f2 = f1; f1 = SoftSwitches._80COL.getState(); @@ -435,7 +435,7 @@ public class VideoNTSC extends VideoDHGR { an3 = true; rgbStateChange(); })); - rgbStateListeners.add(memory.observe(RAMEvent.TYPE.EXECUTE, 0x0fa62, (e) -> { + rgbStateListeners.add(memory.observe("NTSC: Reset hook for reverting RGB mode", RAMEvent.TYPE.EXECUTE, 0x0fa62, (e) -> { // When reset hook is called, reset the graphics mode // This is useful in case a program is running that // is totally clueless how to set the RGB state correctly. diff --git a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/softswitch/IntC8SoftSwitch.java b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/softswitch/IntC8SoftSwitch.java index edf81787..63a4e026 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/softswitch/IntC8SoftSwitch.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/softswitch/IntC8SoftSwitch.java @@ -40,7 +40,7 @@ public class IntC8SoftSwitch extends SoftSwitch { super("InternalC8Rom", false); // INTC8Rom should activate whenever C3xx memory is accessed and SLOTC3ROM is off addListener( - new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { + new RAMListener("Softswitch " + getName() + " on", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(0x0C300); @@ -57,7 +57,7 @@ public class IntC8SoftSwitch extends SoftSwitch { // INTCXRom shoud deactivate whenever CFFF is accessed addListener( - new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { + new RAMListener("Softswitch " + getName() + " off", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(0x0CFFF); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/applesoft/ApplesoftProgram.java b/Platform/Apple/tools/jace/src/main/java/jace/applesoft/ApplesoftProgram.java index 5303385d..6e74982d 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/applesoft/ApplesoftProgram.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/applesoft/ApplesoftProgram.java @@ -202,7 +202,7 @@ public class ApplesoftProgram { */ private void whenReady(Runnable r) { Emulator.withMemory(memory->{ - memory.addListener(new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { + memory.addListener(new RAMListener("Applesoft: Trap GOTO command", RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(GOTO_CMD); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/cheat/Cheats.java b/Platform/Apple/tools/jace/src/main/java/jace/cheat/Cheats.java index 3dedc143..ab908dbd 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/cheat/Cheats.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/cheat/Cheats.java @@ -87,36 +87,36 @@ public abstract class Cheats extends Device { } } - public RAMListener bypassCode(int address, int addressEnd) { + public RAMListener bypassCode(String name, int address, int addressEnd) { int noOperation = MOS65C02.COMMAND.NOP.ordinal(); - return addCheat(RAMEvent.TYPE.READ, (e) -> e.setNewValue(noOperation), address, addressEnd); + return addCheat(name, RAMEvent.TYPE.READ, (e) -> e.setNewValue(noOperation), address, addressEnd); } - public RAMListener forceValue(int value, int... address) { - return addCheat(RAMEvent.TYPE.ANY, (e) -> e.setNewValue(value), address); + public RAMListener forceValue(String name, int value, int... address) { + return addCheat(name, RAMEvent.TYPE.ANY, (e) -> e.setNewValue(value), address); } - public RAMListener forceValue(int value, Boolean auxFlag, int... address) { - return addCheat(RAMEvent.TYPE.ANY, auxFlag, (e) -> e.setNewValue(value), address); + public RAMListener forceValue(String name, int value, Boolean auxFlag, int... address) { + return addCheat(name, RAMEvent.TYPE.ANY, auxFlag, (e) -> e.setNewValue(value), address); } - public RAMListener addCheat(RAMEvent.TYPE type, RAMEvent.RAMEventHandler handler, int... address) { + public RAMListener addCheat(String name, RAMEvent.TYPE type, RAMEvent.RAMEventHandler handler, int... address) { RAMListener listener; if (address.length == 1) { - listener = computer.getMemory().observe(type, address[0], handler); + listener = computer.getMemory().observe(getName() + ": " + name, type, address[0], handler); } else { - listener = computer.getMemory().observe(type, address[0], address[1], handler); + listener = computer.getMemory().observe(getName() + ": " + name, type, address[0], address[1], handler); } listeners.add(listener); return listener; } - public RAMListener addCheat(RAMEvent.TYPE type, Boolean auxFlag, RAMEvent.RAMEventHandler handler, int... address) { + public RAMListener addCheat(String name, RAMEvent.TYPE type, Boolean auxFlag, RAMEvent.RAMEventHandler handler, int... address) { RAMListener listener; if (address.length == 1) { - listener = computer.getMemory().observe(type, address[0], auxFlag, handler); + listener = computer.getMemory().observe(getName() + ": " + name, type, address[0], auxFlag, handler); } else { - listener = computer.getMemory().observe(type, address[0], address[1], auxFlag, handler); + listener = computer.getMemory().observe(getName() + ": " + name, type, address[0], address[1], auxFlag, handler); } listeners.add(listener); return listener; diff --git a/Platform/Apple/tools/jace/src/main/java/jace/cheat/DynamicCheat.java b/Platform/Apple/tools/jace/src/main/java/jace/cheat/DynamicCheat.java index 1f1a1be3..68910800 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/cheat/DynamicCheat.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/cheat/DynamicCheat.java @@ -1,5 +1,7 @@ package jace.cheat; +import javax.script.ScriptException; + import jace.core.RAMEvent; import jace.core.RAMListener; import javafx.beans.property.BooleanProperty; @@ -9,7 +11,6 @@ import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.util.Callback; -import javax.script.ScriptException; /** * @@ -21,10 +22,11 @@ public class DynamicCheat extends RAMListener { StringProperty expression; BooleanProperty active; StringProperty name; + String cheatName; Callback expressionCallback; - public DynamicCheat(int address, String expr) { - super(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY); + public DynamicCheat(String cheatName, int address, String expr) { + super(cheatName, RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY); id = (int) (Math.random() * 10000000); addr = new SimpleIntegerProperty(address); expression = new SimpleStringProperty(expr); @@ -106,18 +108,19 @@ public class DynamicCheat extends RAMListener { public static final String DELIMITER = ";"; public String serialize() { - return escape(name.get()) + DELIMITER + return escape(cheatName) + DELIMITER + escape(name.get()) + DELIMITER + escape("$"+Integer.toHexString(addr.get())) + DELIMITER + escape(expression.get()); } static public DynamicCheat deserialize(String in) { String[] parts = in.split(DELIMITER); - String name = unescape(parts[0]); - Integer addr = Integer.parseInt(parts[1].substring(1), 16); - String expr = unescape(parts[2]); + String cheatName = unescape(parts[0]); + String name = unescape(parts[1]); + Integer addr = Integer.parseInt(parts[2].substring(1), 16); + String expr = unescape(parts[3]); - DynamicCheat out = new DynamicCheat(addr, expr); + DynamicCheat out = new DynamicCheat(cheatName, addr, expr); out.name.set(name); return out; } diff --git a/Platform/Apple/tools/jace/src/main/java/jace/cheat/MetaCheat.java b/Platform/Apple/tools/jace/src/main/java/jace/cheat/MetaCheat.java index cc201efc..b353944d 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/cheat/MetaCheat.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/cheat/MetaCheat.java @@ -302,7 +302,7 @@ public class MetaCheat extends Cheats { } } if (memoryViewListener == null) { - memoryViewListener = memory.observe(RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent); + memoryViewListener = memory.observe("Metacheat memory viewer", RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent); listeners.add(memoryViewListener); } }); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/cheat/MontezumasRevengeCheats.java b/Platform/Apple/tools/jace/src/main/java/jace/cheat/MontezumasRevengeCheats.java index b30e4d5e..2dd0e3c0 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/cheat/MontezumasRevengeCheats.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/cheat/MontezumasRevengeCheats.java @@ -71,21 +71,21 @@ public class MontezumasRevengeCheats extends Cheats { @Override public void registerListeners() { if (repulsiveHack) { - addCheat(RAMEvent.TYPE.WRITE, this::repulsiveBehavior, 0x1508, 0x1518); + addCheat("Repulsive", RAMEvent.TYPE.WRITE, this::repulsiveBehavior, 0x1508, 0x1518); } if (featherFall) { - addCheat(RAMEvent.TYPE.WRITE, this::featherFallBehavior, PLAYER_Y); + addCheat("Feather fall", RAMEvent.TYPE.WRITE, this::featherFallBehavior, PLAYER_Y); // Bypass the part that realizes you should die when you hit the floor - bypassCode(0x6bb3, 0x6bb4); + bypassCode("Feather fall code hack", 0x6bb3, 0x6bb4); } if (moonJump) { - addCheat(RAMEvent.TYPE.WRITE, this::moonJumpBehavior, Y_VELOCITY); + addCheat("Moon jump", RAMEvent.TYPE.WRITE, this::moonJumpBehavior, Y_VELOCITY); } if (infiniteLives) { - forceValue(11, LIVES); + forceValue("Infinite lives", 11, LIVES); } if (safePassage) { @@ -103,22 +103,22 @@ public class MontezumasRevengeCheats extends Cheats { memory.write(0x0f51, (byte) 0b00001000, false, false); memory.write(0x0f52, (byte) 0b10000100, false, false); memory.write(0x0f53, (byte) 0b11010101, false, false); - forceValue(32, FLOOR_TIMER); - forceValue(32, HAZARD_TIMER); - forceValue(1, HAZARD_FLAG); + forceValue("Hack floor timer", 32, FLOOR_TIMER); + forceValue("Hack hazard timer", 32, HAZARD_TIMER); + forceValue("Hack hazard flag", 1, HAZARD_FLAG); }); } if (scoreHack) { // Score: 900913 - forceValue(0x90, SCORE); - forceValue(0x09, SCORE + 1); - forceValue(0x13, SCORE + 2); + forceValue("Hack score 1", 0x90, SCORE); + forceValue("Hack score 2", 0x09, SCORE + 1); + forceValue("Hack score 3", 0x13, SCORE + 2); } if (snakeCharmer) { // Skip the code that determines you're touching an enemy - bypassCode(0x07963, 0x07964); + bypassCode("Snake charmer", 0x07963, 0x07964); } if (mouseHack) { EmulatorUILogic.addMouseListener(listener); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/cheat/PrinceOfPersiaCheats.java b/Platform/Apple/tools/jace/src/main/java/jace/cheat/PrinceOfPersiaCheats.java index d7c4f408..fd7b164e 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/cheat/PrinceOfPersiaCheats.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/cheat/PrinceOfPersiaCheats.java @@ -172,19 +172,19 @@ public class PrinceOfPersiaCheats extends Cheats { @Override public void registerListeners() { if (velocityHack) { - addCheat(RAMEvent.TYPE.READ_DATA, true, this::velocityHackBehavior, CharYVel); + addCheat("Hack velocity", RAMEvent.TYPE.READ_DATA, true, this::velocityHackBehavior, CharYVel); } if (invincibilityHack) { - forceValue(3, true, KidStrength); + forceValue("Hack invincibility", 3, true, KidStrength); } if (sleepHack) { - forceValue(0, true, EnemyAlert); + forceValue("Go to sleep!", 0, true, EnemyAlert); } if (swordHack) { - forceValue(1, true, hasSword); + forceValue("Can haz sword", 1, true, hasSword); } if (timeHack) { - forceValue(0x69, true, MinLeft); + forceValue("Hack time", 0x69, true, MinLeft); } if (mouseHack) { EmulatorUILogic.addMouseListener(listener); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/config/Configuration.java b/Platform/Apple/tools/jace/src/main/java/jace/config/Configuration.java index d86b7561..03de6947 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/config/Configuration.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/config/Configuration.java @@ -62,6 +62,11 @@ import javafx.scene.image.ImageView; * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class Configuration implements Reconfigurable { + public EmulatorUILogic ui; + + public Configuration() { + ui = Emulator.getUILogic(); + } static ConfigurableField getConfigurableFieldInfo(Reconfigurable subject, String settingName) { Field f; @@ -262,13 +267,12 @@ public class Configuration implements Reconfigurable { } } public static ConfigNode BASE; - public static EmulatorUILogic ui = Emulator.logic; @ConfigurableField(name = "Autosave Changes", description = "If unchecked, changes are only saved when the Save button is pressed.") public static boolean saveAutomatically = false; public static void buildTree() { BASE = new ConfigNode(new Configuration()); - Set visited = new LinkedHashSet(); + Set visited = new LinkedHashSet<>(); buildTree(BASE, visited); Emulator.withComputer(c->{ ConfigNode computer = new ConfigNode(BASE, c); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/core/Card.java b/Platform/Apple/tools/jace/src/main/java/jace/core/Card.java index 80b5cb13..d0c1cc7a 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/core/Card.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/core/Card.java @@ -18,7 +18,6 @@ */ package jace.core; -import jace.Emulator; import jace.apple2e.SoftSwitches; /** @@ -103,10 +102,10 @@ public abstract class Card extends Device { @Override public void reconfigure() { //super.reconfigure(); - Emulator.whileSuspended(c-> { + // Emulator.whileSuspended(c-> { unregisterListeners(); registerListeners(); - }); + // }); } public void notifyVBLStateChanged(boolean state) { @@ -121,12 +120,12 @@ public abstract class Card extends Device { RAM memory = computer.getMemory(); int baseIO = 0x0c080 + slot * 16; int baseRom = 0x0c000 + slot * 256; - ioListener = memory.observe(RAMEvent.TYPE.ANY, baseIO, baseIO + 15, (e) -> { + ioListener = memory.observe("Slot " + getSlot() + " " + getDeviceName() + " IO access", RAMEvent.TYPE.ANY, baseIO, baseIO + 15, (e) -> { int address = e.getAddress() & 0x0f; handleIOAccess(address, e.getType(), e.getNewValue(), e); }); - firmwareListener = memory.observe(RAMEvent.TYPE.ANY, baseRom, baseRom + 255, (e) -> { + firmwareListener = memory.observe("Slot " + getSlot() + " " + getDeviceName() + " CX Firmware access", RAMEvent.TYPE.ANY, baseRom, baseRom + 255, (e) -> { computer.getMemory().setActiveCard(slot); // Sather 6-4: Writes will still go through even when CXROM inhibits slot ROM if (SoftSwitches.CXROM.isOff() || !e.getType().isRead()) { @@ -134,7 +133,7 @@ public abstract class Card extends Device { } }); - c8firmwareListener = memory.observe(RAMEvent.TYPE.ANY, 0xc800, 0xcfff, (e) -> { + c8firmwareListener = memory.observe("Slot " + getSlot() + " " + getDeviceName() + " C8 Firmware access", RAMEvent.TYPE.ANY, 0xc800, 0xcfff, (e) -> { if (SoftSwitches.CXROM.isOff() && SoftSwitches.INTC8ROM.isOff() && computer.getMemory().getActiveSlot() == slot) { handleC8FirmwareAccess(e.getAddress() - 0x0c800, e.getType(), e.getNewValue(), e); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/core/Motherboard.java b/Platform/Apple/tools/jace/src/main/java/jace/core/Motherboard.java index dc087741..73f50852 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/core/Motherboard.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/core/Motherboard.java @@ -135,6 +135,7 @@ public class Motherboard extends TimedDevice { addChildDevice(speaker); } catch (Throwable t) { System.out.println("Unable to initalize sound -- deactivating speaker out"); + t.printStackTrace(); } } else { System.out.println("Speaker not enabled, leaving it off."); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/core/RAM.java b/Platform/Apple/tools/jace/src/main/java/jace/core/RAM.java index a5542dd7..3fc14e20 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/core/RAM.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/core/RAM.java @@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentSkipListSet; import java.util.function.Consumer; import java.util.function.Supplier; +import jace.Emulator; import jace.apple2e.SoftSwitches; import jace.config.Reconfigurable; @@ -56,6 +57,7 @@ public abstract class RAM implements Reconfigurable { * * @param computer */ + @SuppressWarnings("unchecked") public RAM(Computer computer) { this.computer = computer; listeners = new ConcurrentSkipListSet<>(); @@ -95,11 +97,10 @@ public abstract class RAM implements Reconfigurable { cards[slot] = Optional.of(c); c.setSlot(slot); c.attach(); + configureActiveMemory(); } public void removeCard(Card c) { - c.suspend(); - c.detach(); removeCard(c.getSlot()); } @@ -198,14 +199,15 @@ public abstract class RAM implements Reconfigurable { } } + @SuppressWarnings("unchecked") private void refreshListenerMap() { listenerMap = (Set[]) new Set[256]; ioListenerMap = (Set[]) new Set[256]; listeners.forEach(this::addListenerRange); - } + } - public RAMListener observe(RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) { - return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { + public RAMListener observe(String observerationName, RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) { + return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(address); @@ -218,8 +220,8 @@ public abstract class RAM implements Reconfigurable { }); } - public RAMListener observe(RAMEvent.TYPE type, int address, Boolean auxFlag, RAMEvent.RAMEventHandler handler) { - return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { + public RAMListener observe(String observerationName, RAMEvent.TYPE type, int address, Boolean auxFlag, RAMEvent.RAMEventHandler handler) { + return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(address); @@ -234,8 +236,8 @@ public abstract class RAM implements Reconfigurable { }); } - public RAMListener observe(RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) { - return addListener(new RAMListener(type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { + public RAMListener observe(String observerationName, RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) { + return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(addressStart); @@ -249,8 +251,8 @@ public abstract class RAM implements Reconfigurable { }); } - public RAMListener observe(RAMEvent.TYPE type, int addressStart, int addressEnd, Boolean auxFlag, RAMEvent.RAMEventHandler handler) { - return addListener(new RAMListener(type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { + public RAMListener observe(String observerationName, RAMEvent.TYPE type, int addressStart, int addressEnd, Boolean auxFlag, RAMEvent.RAMEventHandler handler) { + return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(addressStart); @@ -280,14 +282,12 @@ public abstract class RAM implements Reconfigurable { return l; } listeners.add(l); - computer.cpu.whileSuspended(()->{ - addListenerRange(l); - }); + Emulator.whileSuspended((c)->addListenerRange(l)); return l; } - public RAMListener addExecutionTrap(int address, Consumer handler) { - RAMListener listener = new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { + public RAMListener addExecutionTrap(String observerationName, int address, Consumer handler) { + RAMListener listener = new RAMListener(observerationName, RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(address); @@ -307,9 +307,7 @@ public abstract class RAM implements Reconfigurable { return; } listeners.remove(l); - computer.cpu.whileSuspended(()->{ - refreshListenerMap(); - }); + Emulator.whileSuspended(c->refreshListenerMap()); } private byte _callListener(RAMEvent.TYPE t, int address, int oldValue, int newValue) { diff --git a/Platform/Apple/tools/jace/src/main/java/jace/core/RAMListener.java b/Platform/Apple/tools/jace/src/main/java/jace/core/RAMListener.java index 5eb7c3f1..ad239dd4 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/core/RAMListener.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/core/RAMListener.java @@ -39,14 +39,17 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler, Comparabl private int valueStart; private int valueEnd; private int valueAmount; + private String name; /** * Creates a new instance of RAMListener + * @param name * @param t * @param s * @param v */ - public RAMListener(RAMEvent.TYPE t, RAMEvent.SCOPE s, RAMEvent.VALUE v) { + public RAMListener(String name, RAMEvent.TYPE t, RAMEvent.SCOPE s, RAMEvent.VALUE v) { + setName(name); setType(t); setScope(s); setValue(v); @@ -77,6 +80,14 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler, Comparabl this.value = value; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public int getScopeStart() { return scopeStart; } @@ -161,18 +172,23 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler, Comparabl @Override public int compareTo(RAMListener o) { - if (o.scopeStart == scopeStart) { - if (o.scopeEnd == scopeEnd) { - if (o.type == type) { - return Integer.compare(o.hashCode(), hashCode()); + if (o.name == name) { + if (o.scopeStart == scopeStart) { + if (o.scopeEnd == scopeEnd) { + if (o.type == type) { + // Ignore hash codes -- combination of name, address range and type should identify similar listeners. + return (int) 0; + } else { + return Integer.compare(o.type.ordinal(), type.ordinal()); + } } else { - return Integer.compare(o.type.ordinal(), type.ordinal()); + return Integer.compare(o.scopeEnd, scopeEnd); } } else { - return Integer.compare(o.scopeEnd, scopeEnd); + return Integer.compare(o.scopeStart, scopeStart); } } else { - return Integer.compare(o.scopeStart, scopeStart); + return o.name.compareTo(name); } } } diff --git a/Platform/Apple/tools/jace/src/main/java/jace/core/SoftSwitch.java b/Platform/Apple/tools/jace/src/main/java/jace/core/SoftSwitch.java index aba74d74..f289e7f2 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/core/SoftSwitch.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/core/SoftSwitch.java @@ -106,7 +106,7 @@ public abstract class SoftSwitch { exclusionActivate.add(i); } } - RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { + RAMListener l = new RAMListener("Softswitch toggle " + name, changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(beginAddr); @@ -136,7 +136,7 @@ public abstract class SoftSwitch { exclusionActivate.add(i); } } - RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { + RAMListener l = new RAMListener("Softswitch on " + name, changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(beginAddr); @@ -169,7 +169,7 @@ public abstract class SoftSwitch { exclusionDeactivate.add(i); } } - RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { + RAMListener l = new RAMListener("Softswitch off " + name, changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(beginAddr); @@ -201,7 +201,7 @@ public abstract class SoftSwitch { } } // RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { - RAMListener l = new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { + RAMListener l = new RAMListener("Softswitch read state " + name, RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(beginAddr); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/core/SoundMixer.java b/Platform/Apple/tools/jace/src/main/java/jace/core/SoundMixer.java index 6823347d..e45cac3f 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/core/SoundMixer.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/core/SoundMixer.java @@ -18,14 +18,25 @@ */ package jace.core; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.logging.Logger; +import org.lwjgl.BufferUtils; import org.lwjgl.openal.AL; +import org.lwjgl.openal.AL10; import org.lwjgl.openal.ALC; import org.lwjgl.openal.ALC10; import org.lwjgl.openal.ALCCapabilities; import org.lwjgl.openal.ALCapabilities; +import jace.Emulator; import jace.config.ConfigurableField; /** @@ -41,8 +52,9 @@ public class SoundMixer extends Device { /** * Bits per sample + * Making this configurable requires too much effort and not a lot of benefit */ - @ConfigurableField(name = "Bits per sample", shortName = "bits") + // @ConfigurableField(name = "Bits per sample", shortName = "bits") public static int BITS = 16; /** * Sample playback rate @@ -51,38 +63,206 @@ public class SoundMixer extends Device { public static int RATE = 48000; @ConfigurableField(name = "Mute", shortName = "mute") public static boolean MUTE = false; + + @ConfigurableField(name = "Buffer size", shortName = "buffer") + public static int BUFFER_SIZE = 512; + + public static boolean PLAYBACK_ENABLED = false; + // Innocent until proven guilty by a failed initialization + public static boolean PLAYBACK_DRIVER_DETECTED = true; + public static boolean PLAYBACK_INITIALIZED = false; - private final String defaultDeviceName; - private long audioDevice; - private long audioContext; - private ALCCapabilities audioCapabilities; - private ALCapabilities audioLibCapabilities; + private static String defaultDeviceName; + private static long audioDevice = -1; + private static long audioContext = -1; + private static ALCCapabilities audioCapabilities; + private static ALCapabilities audioLibCapabilities; + // In case the OpenAL implementation wants to be run in a single thread, use a single thread executor + protected static ExecutorService soundThreadExecutor = Executors.newSingleThreadExecutor(); public SoundMixer(Computer computer) { super(computer); - defaultDeviceName = ALC10.alcGetString(0, ALC10.ALC_DEFAULT_DEVICE_SPECIFIER); + defaultDeviceName = ALC10.alcGetString(0, ALC10.ALC_DEFAULT_DEVICE_SPECIFIER); + } + + public static T performSoundFunction(Callable operation) { + return performSoundFunction(operation, false); + } + + public static T performSoundFunction(Callable operation, boolean ignoreError) { + Future result = soundThreadExecutor.submit(operation); + try { + if (!ignoreError) { + Future error = soundThreadExecutor.submit(AL10::alGetError); + int err; + err = error.get(); + if (err != AL10.AL_NO_ERROR) { + throw new RuntimeException(AL10.alGetString(err)); + } + } + return result.get(); + } catch (InterruptedException | ExecutionException e) { + System.out.println("Error when executing sound action: " + e.getMessage()); + e.printStackTrace(); + } + return null; + } + + public static void performSoundOperation(Runnable operation) { + performSoundOperation(operation, false); + } + + public static void performSoundOperation(Runnable operation, boolean ignoreError) { + performSoundFunction(()->{ + operation.run(); + return null; + }, ignoreError); + } + + public static void performSoundOperationAsync(Runnable operation) { + soundThreadExecutor.submit(operation); + } + + protected static void initSound() { + performSoundOperation(()->{ + if (!PLAYBACK_INITIALIZED) { + audioDevice = ALC10.alcOpenDevice(defaultDeviceName); + audioContext = ALC10.alcCreateContext(audioDevice, new int[]{0}); + ALC10.alcMakeContextCurrent(audioContext); + audioCapabilities = ALC.createCapabilities(audioDevice); + audioLibCapabilities = AL.createCapabilities(audioCapabilities); + if (!audioLibCapabilities.OpenAL10) { + PLAYBACK_DRIVER_DETECTED = false; + Logger.getLogger(SoundMixer.class.getName()).warning("OpenAL 1.0 not supported"); + Emulator.withComputer(c->c.mixer.detach()); + } + PLAYBACK_INITIALIZED = true; + } else { + ALC10.alcMakeContextCurrent(audioContext); + } + }); } // Lots of inspiration from https://www.youtube.com/watch?v=dLrqBTeipwg @Override public void attach() { + if (!PLAYBACK_DRIVER_DETECTED) { + Logger.getLogger(SoundMixer.class.getName()).warning("Sound driver not detected"); + return; + } super.attach(); - audioDevice = ALC10.alcOpenDevice(defaultDeviceName); - // TODO: Other attributes? - audioContext = ALC10.alcCreateContext(audioDevice, new int[]{0}); - ALC10.alcMakeContextCurrent(audioContext); - audioCapabilities = ALC.createCapabilities(audioDevice); - audioLibCapabilities = AL.createCapabilities(audioCapabilities); - if (!audioLibCapabilities.OpenAL10) { - Logger.getLogger(SoundMixer.class.getName()).warning("OpenAL 1.0 not supported"); - detach(); + initSound(); + PLAYBACK_ENABLED = true; + } + + private static List buffers = new ArrayList<>(); + public static SoundBuffer createBuffer(boolean stereo) { + if (!PLAYBACK_ENABLED) { + return null; + } + SoundBuffer buffer = new SoundBuffer(stereo); + buffers.add(buffer); + return buffer; + } + + public static class SoundBuffer { + public static int MAX_BUFFER_ID; + private ShortBuffer currentBuffer; + private ShortBuffer alternateBuffer; + private int audioFormat; + private int currentBufferId; + private int alternateBufferId; + private int sourceId; + private boolean isAlive; + private int buffersGenerated = 0; + + public SoundBuffer(boolean stereo) { + initSound(); + currentBuffer = BufferUtils.createShortBuffer(BUFFER_SIZE * (stereo ? 2 : 1)); + alternateBuffer = BufferUtils.createShortBuffer(BUFFER_SIZE * (stereo ? 2 : 1)); + currentBufferId = performSoundFunction(AL10::alGenBuffers); + alternateBufferId = performSoundFunction(AL10::alGenBuffers); + sourceId = performSoundFunction(AL10::alGenSources); + audioFormat = stereo ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16; + isAlive = true; + } + + public boolean isAlive() { + return isAlive; + } + + /* If stereo, call this once for left and then again for right sample */ + public void playSample(short sample) throws InterruptedException, ExecutionException { + if (!isAlive) { + Logger.getLogger(SoundMixer.class.getName()).warning("Playback attempted on stopped buffer!"); + return; + } + currentBuffer.put(sample); + if (!currentBuffer.hasRemaining()) { + buffersGenerated++; + currentBuffer.flip(); + if (buffersGenerated > 2) { + int[] unqueueBuffers = new int[]{currentBufferId}; + performSoundOperation(()->{ + int buffersProcessed = AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED); + while (buffersProcessed < 1) { + Thread.onSpinWait(); + buffersProcessed = AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED); + } + }); + performSoundOperation(()->{ + AL10.alSourceUnqueueBuffers(sourceId, unqueueBuffers); + }); + } + performSoundOperation(()->AL10.alBufferData(currentBufferId, audioFormat, currentBuffer, RATE)); + performSoundOperation(()->AL10.alSourceQueueBuffers(sourceId, currentBufferId)); + performSoundOperationAsync(()->{ + if (AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING) { + AL10.alSourcePlay(sourceId); + } + }); + + // Swap AL buffers + int tempId = currentBufferId; + currentBufferId = alternateBufferId; + alternateBufferId = tempId; + // Swap Java buffers + ShortBuffer tempBuffer = currentBuffer; + currentBuffer = alternateBuffer; + alternateBuffer = tempBuffer; + } + } + + public void shutdown() throws InterruptedException, ExecutionException { + if (!isAlive) { + return; + } + isAlive = false; + performSoundOperation(()->AL10.alSourceStop(sourceId)); + performSoundOperation(()->AL10.alDeleteSources(sourceId)); + performSoundOperation(()->AL10.alDeleteBuffers(alternateBufferId)); + performSoundOperation(()->AL10.alDeleteBuffers(currentBufferId)); } } @Override public void detach() { - ALC10.alcDestroyContext(audioContext); - ALC10.alcCloseDevice(audioDevice); + if (!PLAYBACK_ENABLED) { + return; + } MUTE = true; + PLAYBACK_ENABLED = false; + + for (SoundBuffer buffer : buffers) { + try { + buffer.shutdown(); + } catch (InterruptedException | ExecutionException e) { + Logger.getLogger(SoundMixer.class.getName()).warning("Error when detaching sound mixer: " + e.getMessage()); + } + } + buffers.clear(); + PLAYBACK_INITIALIZED = false; + performSoundOperation(()->ALC10.alcDestroyContext(audioContext), true); + performSoundOperation(()->ALC10.alcCloseDevice(audioDevice), true); super.detach(); } @@ -100,10 +280,11 @@ public class SoundMixer extends Device { @Override public synchronized void reconfigure() { - if (MUTE) { - detach(); - } else { + PLAYBACK_ENABLED = PLAYBACK_DRIVER_DETECTED && !MUTE; + if (PLAYBACK_ENABLED) { attach(); + } else { + detach(); } } diff --git a/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardDiskII.java b/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardDiskII.java index fe9dbb9a..ffd4c983 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardDiskII.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardDiskII.java @@ -27,7 +27,6 @@ import java.util.logging.Logger; import jace.EmulatorUILogic; import jace.config.ConfigurableField; import jace.config.Name; -import jace.config.Reconfigurable; import jace.core.Card; import jace.core.Computer; import jace.core.RAMEvent; @@ -46,7 +45,7 @@ import jace.library.MediaConsumerParent; * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ @Name("Disk ][ Controller") -public class CardDiskII extends Card implements Reconfigurable, MediaConsumerParent { +public class CardDiskII extends Card implements MediaConsumerParent { DiskIIDrive currentDrive; DiskIIDrive drive1 = new DiskIIDrive(computer); @@ -128,7 +127,8 @@ public class CardDiskII extends Card implements Reconfigurable, MediaConsumerPar case 0xC: // read/write latch currentDrive.write(); - e.setNewValue(currentDrive.readLatch()); + int latch = currentDrive.readLatch(); + e.setNewValue(latch); break; case 0xF: // write mode diff --git a/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardMockingboard.java b/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardMockingboard.java index 24297d64..bb5cff7c 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardMockingboard.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardMockingboard.java @@ -348,7 +348,7 @@ public class CardMockingboard extends Card implements Runnable { public void run() { SourceDataLine out = null; try { - out = computer.mixer.getLine(); + // out = computer.mixer.getLine(); if (out == null) { setRun(false); return; diff --git a/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardRamworks.java b/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardRamworks.java index 0ca31752..9227cf1d 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardRamworks.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/hardware/CardRamworks.java @@ -140,7 +140,7 @@ public class CardRamworks extends RAM128k { private RAMListener bankSelectListener; @Override public void attach() { - bankSelectListener = observe(RAMEvent.TYPE.WRITE, BANK_SELECT, (e) -> { + bankSelectListener = observe("Ramworks bank select", RAMEvent.TYPE.WRITE, BANK_SELECT, (e) -> { currentBank = e.getNewValue(); configureActiveMemory(); }); diff --git a/Platform/Apple/tools/jace/src/main/java/jace/hardware/DiskIIDrive.java b/Platform/Apple/tools/jace/src/main/java/jace/hardware/DiskIIDrive.java index f461100c..5f8dbfd9 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/hardware/DiskIIDrive.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/hardware/DiskIIDrive.java @@ -18,6 +18,14 @@ */ package jace.hardware; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; + import jace.EmulatorUILogic; import jace.core.Computer; import jace.library.MediaConsumer; @@ -25,12 +33,6 @@ import jace.library.MediaEntry; import jace.library.MediaEntry.MediaFile; import jace.state.StateManager; import jace.state.Stateful; -import java.io.File; -import java.io.IOException; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.locks.LockSupport; import javafx.scene.control.Label; /** @@ -50,11 +52,13 @@ public class DiskIIDrive implements MediaConsumer { this.computer = computer; } + public boolean DEBUG = true; + FloppyDisk disk; // Number of milliseconds to wait between last write and update to disk image public static long WRITE_UPDATE_DELAY = 1000; // Flag to halt if any writes to floopy occur when updating physical disk image - boolean diskUpdatePending = false; + AtomicBoolean diskUpdatePending = new AtomicBoolean(); // Last time of write operation long lastWriteTime; // Managed thread to update disk image in background @@ -86,7 +90,7 @@ public class DiskIIDrive implements MediaConsumer { driveOn = false; magnets = 0; dirtyTracks = new HashSet<>(); - diskUpdatePending = false; + diskUpdatePending.set(false); } void step(int register) { @@ -113,13 +117,18 @@ public class DiskIIDrive implements MediaConsumer { } nibbleOffset = 0; - //System.out.printf("new half track %d\n", currentHalfTrack); + if (DEBUG) { + System.out.printf("step %d, new half track %d\n", register, halfTrack); + } } } } } void setOn(boolean b) { + if (DEBUG) { + System.out.println("Drive setOn: "+b); + } driveOn = b; } @@ -155,17 +164,20 @@ public class DiskIIDrive implements MediaConsumer { void write() { if (writeMode) { - while (diskUpdatePending) { + while (diskUpdatePending.get()) { // If another thread requested writes to block (e.g. because of disk activity), wait for it to finish! - LockSupport.parkNanos(1000); + Thread.onSpinWait(); } - if (disk != null) { - // Do nothing if write-protection is enabled! - if (getMediaEntry() == null || !getMediaEntry().writeProtected) { - dirtyTracks.add(trackStartOffset / FloppyDisk.TRACK_NIBBLE_LENGTH); - disk.nibbles[trackStartOffset + nibbleOffset++] = latch; - triggerDiskUpdate(); - StateManager.markDirtyValue(disk.nibbles, computer); + // Holding the lock should block any other threads from writing to disk + synchronized (diskUpdatePending) { + if (disk != null) { + // Do nothing if write-protection is enabled! + if (getMediaEntry() == null || !getMediaEntry().writeProtected) { + dirtyTracks.add(trackStartOffset / FloppyDisk.TRACK_NIBBLE_LENGTH); + disk.nibbles[trackStartOffset + nibbleOffset++] = latch; + triggerDiskUpdate(); + StateManager.markDirtyValue(disk.nibbles, computer); + } } } @@ -192,19 +204,20 @@ public class DiskIIDrive implements MediaConsumer { } private void updateDisk() { - // Signal disk update is underway - diskUpdatePending = true; - // Update all tracks as necessary - if (disk != null) { - dirtyTracks.stream().forEach((track) -> { - disk.updateTrack(track); - }); + synchronized (diskUpdatePending) { + diskUpdatePending.set(true); + // Update all tracks as necessary + if (disk != null) { + dirtyTracks.stream().forEach((track) -> { + disk.updateTrack(track); + }); + } + // Empty out dirty list + dirtyTracks.clear(); } - // Empty out dirty list - dirtyTracks.clear(); // Signal disk update is completed - diskUpdatePending = false; + diskUpdatePending.set(false); } private void triggerDiskUpdate() { @@ -226,6 +239,9 @@ public class DiskIIDrive implements MediaConsumer { } void insertDisk(File diskPath) throws IOException { + if (DEBUG) { + System.out.println("inserting disk " + diskPath.getAbsolutePath() + " into drive"); + } disk = new FloppyDisk(diskPath, computer); dirtyTracks = new HashSet<>(); // Emulator state has changed significantly, reset state manager @@ -300,14 +316,16 @@ public class DiskIIDrive implements MediaConsumer { @Override public boolean isAccepted(MediaEntry e, MediaFile f) { if (f == null) return false; -// System.out.println("Type is accepted: "+f.path+"; "+e.type.toString()+": "+e.type.is140kb); + if (DEBUG) { + System.out.println("Type is accepted: "+f.path+"; "+e.type.toString()+": "+e.type.is140kb); + } return e.type.is140kb; } private void waitForPendingWrites() { - while (diskUpdatePending || !dirtyTracks.isEmpty()) { + while (diskUpdatePending.get()) { // If the current disk has unsaved changes, wait!!! - LockSupport.parkNanos(1000); + Thread.onSpinWait(); } } } \ No newline at end of file diff --git a/Platform/Apple/tools/jace/src/main/java/jace/hardware/FloppyDisk.java b/Platform/Apple/tools/jace/src/main/java/jace/hardware/FloppyDisk.java index aa43ed62..f921bde1 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/hardware/FloppyDisk.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/hardware/FloppyDisk.java @@ -18,9 +18,6 @@ */ package jace.hardware; -import jace.core.Computer; -import jace.state.StateManager; -import jace.state.Stateful; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -32,6 +29,10 @@ import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; +import jace.core.Computer; +import jace.state.StateManager; +import jace.state.Stateful; + /** * Representation of a 140kb floppy disk image. This also performs conversions * as needed. Internally, the emulator will always use a "nibblized" disk @@ -180,6 +181,17 @@ public class FloppyDisk { writeJunkBytes(output, 38 - gap2); } } + // Write output to stdout for debugging purposes + if (DEBUG) { + System.out.println("Nibblized disk:"); + for (int i = 0; i < output.size(); i++) { + System.out.print(Integer.toString(output.toByteArray()[i] & 0x0ff, 16) + " "); + if (i % 16 == 255) { + System.out.println(); + } + } + System.out.println(); + } return output.toByteArray(); } @@ -298,17 +310,23 @@ public class FloppyDisk { byte[] trackNibbles = new byte[TRACK_NIBBLE_LENGTH]; byte[] trackData = new byte[SECTOR_COUNT * 256]; // Copy track into temporary buffer -// System.out.println("Nibblized track "+track); -// System.out.printf("%04d:",0); - for (int i = 0, pos = track * TRACK_NIBBLE_LENGTH; i < TRACK_NIBBLE_LENGTH; i++, pos++) { - trackNibbles[i] = nibbles[pos]; -// System.out.print(Integer.toString(nibbles[pos] & 0x0ff, 16)+" "); -// if (i % 16 == 15) { -// System.out.println(); -// System.out.printf("%04d:",i+1); -// } + if (DEBUG) { + System.out.println("Nibblized track "+track); + System.out.printf("%04d:",0); + } + for (int i = 0, pos = track * TRACK_NIBBLE_LENGTH; i < TRACK_NIBBLE_LENGTH; i++, pos++) { + trackNibbles[i] = nibbles[pos]; + if (DEBUG) { + System.out.print(Integer.toString(nibbles[pos] & 0x0ff, 16)+" "); + if (i % 16 == 15) { + System.out.println(); + System.out.printf("%04d:",i+1); + } + } + } + if (DEBUG) { + System.out.println(); } -// System.out.println(); int pos = 0; for (int i = 0; i < SECTOR_COUNT; i++) { @@ -318,7 +336,9 @@ public class FloppyDisk { int trackVerify = decodeOddEven(trackNibbles[pos + 5], trackNibbles[pos + 6]); // Locate sector number int sector = decodeOddEven(trackNibbles[pos + 7], trackNibbles[pos + 8]); -// System.out.println("Writing track " + track + ", getting address block for T" + trackVerify + ".S" + sector + " found at NIB offset "+pos); + if (DEBUG) { + System.out.println("Writing track " + track + ", getting address block for T" + trackVerify + ".S" + sector + " found at NIB offset "+pos); + } // Skip to end of address block pos = locatePattern(pos, trackNibbles, 0x0de, 0x0aa /*, 0x0eb this is sometimes being written as FF??*/); // Locate start of sector data @@ -326,7 +346,9 @@ public class FloppyDisk { // Determine offset in output data for sector //int offset = reverseLoopkup(currentSectorOrder, sector) * 256; int offset = currentSectorOrder[sector] * 256; -// System.out.println("Sector "+sector+" maps to physical sector "+reverseLoopkup(currentSectorOrder, sector)); + if (DEBUG) { + System.out.println("Sector "+sector+" maps to physical sector "+reverseLoopkup(currentSectorOrder, sector)); + } // Decode sector data denibblizeSector(trackNibbles, pos + 3, trackData, offset); // Skip to end of sector diff --git a/Platform/Apple/tools/jace/src/main/java/jace/hardware/Joystick.java b/Platform/Apple/tools/jace/src/main/java/jace/hardware/Joystick.java index aabf9356..d9dc60a3 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/hardware/Joystick.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/hardware/Joystick.java @@ -201,7 +201,7 @@ public class Joystick extends Device { RAMListener listener; private void registerListeners() { - listener = computer.getMemory().observe(RAMEvent.TYPE.ANY, 0x0c070, 0x0c07f, this::initJoystickRead); + listener = computer.getMemory().observe("Joystick I/O", RAMEvent.TYPE.ANY, 0x0c070, 0x0c07f, this::initJoystickRead); } private void removeListeners() { diff --git a/Platform/Apple/tools/jace/src/main/java/jace/hardware/NoSlotClock.java b/Platform/Apple/tools/jace/src/main/java/jace/hardware/NoSlotClock.java index 111c492c..bfb907b7 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/hardware/NoSlotClock.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/hardware/NoSlotClock.java @@ -1,5 +1,8 @@ package jace.hardware; +import java.util.Calendar; +import java.util.Optional; + import jace.EmulatorUILogic; import jace.apple2e.SoftSwitches; import jace.config.ConfigurableField; @@ -8,8 +11,6 @@ import jace.core.Device; import jace.core.RAMEvent; import jace.core.RAMListener; import jace.core.Utility; -import java.util.Calendar; -import java.util.Optional; import javafx.scene.control.Label; /** @@ -31,7 +32,7 @@ public class NoSlotClock extends Device { public boolean patchProdosClock = false; Optional