Completely rewritten sound engine, fixed listener registration issues, Configuration global actions fixed.

This commit is contained in:
Brendan Robert 2023-10-23 00:28:55 -05:00
parent cd5903367b
commit b5997c3876
41 changed files with 706 additions and 315 deletions

View File

@ -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"
}
]
}

View File

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.8bitbunch</groupId>
@ -13,6 +15,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mainClass>jace.LawlessLegends</mainClass>
<netbeans.hint.license>apache20</netbeans.hint.license>
<lwjgl.version>3.3.3</lwjgl.version>
</properties>
<organization>
@ -43,7 +46,7 @@
<plugin>
<groupId>com.gluonhq</groupId>
<artifactId>gluonfx-maven-plugin</artifactId>
<version>1.0.19</version>
<version>1.0.21</version>
<configuration>
<mainClass>jace.LawlessLegends</mainClass>
</configuration>
@ -55,44 +58,44 @@
<configuration>
<mainClass>lawlesslegends/jace.LawlessLegends</mainClass>
<executions>
<execution>
<!-- Default configuration for running -->
<!-- Usage: mvn clean javafx:run -->
<id>default-cli</id>
</execution>
<execution>
<!-- Configuration for manual attach debugging -->
<!-- Usage: mvn clean javafx:run@debug -->
<id>debug</id>
<configuration>
<options>
<option>-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:8000</option>
</options>
</configuration>
</execution>
<execution>
<!-- Configuration for automatic IDE debugging -->
<id>ide-debug</id>
<configuration>
<options>
<option>-agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address}</option>
</options>
</configuration>
</execution>
<execution>
<!-- Configuration for automatic IDE profiling -->
<id>ide-profile</id>
<configuration>
<options>
<option>${profiler.jvmargs.arg1}</option>
<option>${profiler.jvmargs.arg2}</option>
<option>${profiler.jvmargs.arg3}</option>
<option>${profiler.jvmargs.arg4}</option>
<option>${profiler.jvmargs.arg5}</option>
</options>
</configuration>
</execution>
</executions>
<execution>
<!-- Default configuration for running -->
<!-- Usage: mvn clean javafx:run -->
<id>default-cli</id>
</execution>
<execution>
<!-- Configuration for manual attach debugging -->
<!-- Usage: mvn clean javafx:run@debug -->
<id>debug</id>
<configuration>
<options>
<option>-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8000</option>
</options>
</configuration>
</execution>
<execution>
<!-- Configuration for automatic IDE debugging -->
<id>ide-debug</id>
<configuration>
<options>
<option>-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8000</option>
</options>
</configuration>
</execution>
<execution>
<!-- Configuration for automatic IDE profiling -->
<id>ide-profile</id>
<configuration>
<options>
<option>${profiler.jvmargs.arg1}</option>
<option>${profiler.jvmargs.arg2}</option>
<option>${profiler.jvmargs.arg3}</option>
<option>${profiler.jvmargs.arg4}</option>
<option>${profiler.jvmargs.arg5}</option>
</options>
</configuration>
</execution>
</executions>
</configuration>
</plugin>
<plugin>
@ -116,8 +119,8 @@
</artifact>
<moduleInfoSource>
module nestedvm {
exports org.ibex.nestedvm;
exports org.ibex.nestedvm.util;
exports org.ibex.nestedvm;
exports org.ibex.nestedvm.util;
}
</moduleInfoSource>
</module>
@ -130,13 +133,25 @@
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-bom</artifactId>
<version>${lwjgl.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependency>
<dependency>
<groupId>org.xerial.thirdparty</groupId>
<artifactId>nestedvm</artifactId>
@ -175,18 +190,69 @@
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>18</version>
<version>20</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-openal</artifactId>
<version>3.3.2</version>
<artifactId>lwjgl</artifactId>
</dependency>
<dependency>
<groupId>javazoom</groupId>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-openal</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-openal</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>com.badlogicgames.jlayer</groupId>
<artifactId>jlayer</artifactId>
<version>1.0.1</version>
<version>1.0.2-gdx</version>
</dependency>
</dependencies>
</project>
<profiles>
<profile>
<id>lwjgl-natives-linux-amd64</id>
<activation>
<os>
<family>unix</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-linux</lwjgl.natives>
</properties>
</profile>
<profile>
<id>lwjgl-natives-macos-x86_64</id>
<activation>
<os>
<family>mac</family>
<arch>x86_64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-macos</lwjgl.natives>
</properties>
</profile>
<profile>
<id>lwjgl-natives-windows-amd64</id>
<activation>
<os>
<family>windows</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-windows</lwjgl.natives>
</properties>
</profile>
</profiles>
</project>

View File

@ -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<String> args) {
Emulator i = getInstance();

View File

@ -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();

View File

@ -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<MediaConsumer> getMediaConsumers() {
List<MediaConsumer> 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> card : m.getAllCards()) {
card.filter(c -> c instanceof MediaConsumerParent).ifPresent(parent ->

View File

@ -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;
});

View File

@ -76,9 +76,9 @@ public class Apple2e extends Computer {
@ConfigurableField(name = "Slot 5", shortName = "s5card")
public DeviceSelection<Cards> card5 = new DeviceSelection<>(Cards.class, null);
@ConfigurableField(name = "Slot 6", shortName = "s6card")
public DeviceSelection<Cards> card6 = new DeviceSelection<>(Cards.class, Cards.DiskIIDrive);
public DeviceSelection<Cards> card6 = new DeviceSelection<>(Cards.class, Cards.DiskIIDrive, true);
@ConfigurableField(name = "Slot 7", shortName = "s7card")
public DeviceSelection<Cards> card7 = new DeviceSelection<>(Cards.class, Cards.MassStorage);
public DeviceSelection<Cards> 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);

View File

@ -53,9 +53,9 @@ abstract public class RAM128k extends RAM {
Function<Computer, ? extends RAM128k> factory;
String name;
Class clazz;
Class<? extends RAM128k> clazz;
RamCards(String name, Class clazz, Function<Computer, ? extends RAM128k> factory) {
RamCards(String name, Class<? extends RAM128k> clazz, Function<Computer, ? extends RAM128k> 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;

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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<RAMEvent, Integer> 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;
}

View File

@ -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);
}
});

View File

@ -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);

View File

@ -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);

View File

@ -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<ConfigNode> visited = new LinkedHashSet<>();
buildTree(BASE, visited);
Emulator.withComputer(c->{
ConfigNode computer = new ConfigNode(BASE, c);

View File

@ -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);

View File

@ -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.");

View File

@ -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<RAMListener>[]) new Set[256];
ioListenerMap = (Set<RAMListener>[]) 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<RAMEvent> handler) {
RAMListener listener = new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
public RAMListener addExecutionTrap(String observerationName, int address, Consumer<RAMEvent> 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) {

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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> T performSoundFunction(Callable<T> operation) {
return performSoundFunction(operation, false);
}
public static <T> T performSoundFunction(Callable<T> operation, boolean ignoreError) {
Future<T> result = soundThreadExecutor.submit(operation);
try {
if (!ignoreError) {
Future<Integer> 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<SoundBuffer> 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();
}
}

View File

@ -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

View File

@ -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;

View File

@ -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();
});

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -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() {

View File

@ -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<Label> clockIcon;
private final RAMListener listener = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
private final RAMListener listener = new RAMListener("No slot clock read", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x0C100);

View File

@ -52,8 +52,8 @@ public class ZipWarpAccelerator extends Device {
public ZipWarpAccelerator(Computer computer) {
super(computer);
zipListener = computer.getMemory().observe(RAMEvent.TYPE.ANY, ENABLE_ADDR, SET_SPEED, this::handleZipChipEvent);
transwarpListener = computer.getMemory().observe(RAMEvent.TYPE.ANY, TRANSWARP, this::handleTranswarpEvent);
zipListener = computer.getMemory().observe("Zip chip access", RAMEvent.TYPE.ANY, ENABLE_ADDR, SET_SPEED, this::handleZipChipEvent);
transwarpListener = computer.getMemory().observe("Transwarp access", RAMEvent.TYPE.ANY, TRANSWARP, this::handleTranswarpEvent);
}
private void handleZipChipEvent(RAMEvent e) {
@ -190,7 +190,7 @@ public class ZipWarpAccelerator extends Device {
private void turnOffAcceleration() {
// The UI Logic retains the user's desired normal speed, reset to that
Emulator.logic.reconfigure();
Emulator.getUILogic().reconfigure();
}
@Override

View File

@ -63,6 +63,7 @@ public class LawlessComputer extends Apple2e {
if (showBootAnimation && PRODUCTION_MODE) {
(new Thread(this::startAnimation)).start();
} else {
cpu.setPaused(false);
finishColdStart();
}
}

View File

@ -47,14 +47,14 @@ public class LawlessHacks extends Cheats {
@Override
public void registerListeners() {
// Observe graphics changes
addCheat(RAMEvent.TYPE.ANY, (e) -> {
addCheat("Lawless Legends Graphics Modes", RAMEvent.TYPE.ANY, (e) -> {
int addr = e.getAddress();
if (addr >= MODE_SOFTSWITCH_MIN && e.getAddress() <= MODE_SOFTSWITCH_MAX) {
// System.out.println("Trapped " + e.getType().toString() + " to $" + Integer.toHexString(e.getAddress()));
setEngineByOrdinal(e.getAddress() - MODE_SOFTSWITCH_MIN);
}
}, MODE_SOFTSWITCH_MIN, MODE_SOFTSWITCH_MAX);
addCheat(RAMEvent.TYPE.WRITE, (e) -> {
addCheat("Lawless Legends Music Commands", RAMEvent.TYPE.WRITE, (e) -> {
// System.out.println(Integer.toHexString(e.getAddress()) + " => " + Integer.toHexString(e.getNewValue() & 0x0ff));
playSound(e.getNewValue());
}, SFX_TRIGGER);

View File

@ -157,7 +157,7 @@ public class MetacheatUI {
@FXML
void addCheat(ActionEvent event) {
cheatEngine.addCheat(new DynamicCheat(0, "?"));
cheatEngine.addCheat(new DynamicCheat(event.toString(), 0, "?"));
}
@FXML
@ -412,7 +412,7 @@ public class MetacheatUI {
Label addCheat = new Label("Cheat >>");
addCheat.setOnMouseClicked((mouseEvent) -> {
Platform.runLater(() -> addCheat(addr, watch.getValue()));
Platform.runLater(() -> addCheat("Memory View " + Integer.toHexString(addr), addr, watch.getValue()));
});
watch.getChildren().add(addCheat);
@ -539,7 +539,7 @@ public class MetacheatUI {
Label addCheat = new Label("Cheat >>");
addCheat.setOnMouseClicked((mouseEvent) -> {
addCheat(addr, watch.getValue());
addCheat("Metacheat " + Integer.toHexString(addr), addr, watch.getValue());
});
addCheat.setTextFill(Color.WHITE);
watch.getChildren().add(addCheat);
@ -556,8 +556,8 @@ public class MetacheatUI {
return watch;
}
private void addCheat(int addr, int val) {
cheatEngine.addCheat(new DynamicCheat(addr, String.valueOf(val)));
private void addCheat(String name, int addr, int val) {
cheatEngine.addCheat(new DynamicCheat(name, addr, String.valueOf(val)));
}
int currentlyInspecting = 0;

View File

@ -118,7 +118,7 @@ class Watch extends VBox {
holdListener = null;
} else {
value = Emulator.withComputer(c->c.getMemory().readRaw(address) & 0x0ff, 0);
holdListener = outer.cheatEngine.forceValue(value, address);
holdListener = outer.cheatEngine.forceValue("Watch force value", value, address);
}
}

View File

@ -0,0 +1,35 @@
package jace;
import java.util.concurrent.ExecutionException;
import jace.core.SoundMixer;
import jace.core.SoundMixer.SoundBuffer;
public class SoundTest {
// @Test
public void soundGenerationTest() {
try {
System.out.println("Performing sound test...");
System.out.println("Create mixer");
SoundMixer mixer = new SoundMixer(null);
System.out.println("Attach mixer");
mixer.attach();
System.out.println("Allocate buffer");
SoundBuffer buffer = SoundMixer.createBuffer(false);
System.out.println("Generate sound");
for (int i = 0; i < 100000; i++) {
// Gerate a sin wave with a frequency sweep so we can tell if the buffer is being fully processed
double x = Math.sin(i*i * 0.0001);
buffer.playSample((short) (Short.MAX_VALUE * x));
}
System.out.println("Closing buffer");
buffer.shutdown();
System.out.println("Deactivating sound");
mixer.detach();
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

View File

@ -14,17 +14,18 @@
* limitations under the License.
*/
package jace.core;
import java.util.concurrent.atomic.AtomicInteger;
import static jace.TestUtils.initComputer;
import static jace.TestUtils.runAssemblyCode;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.BeforeClass;
import org.junit.Test;
import jace.Emulator;
import static jace.TestUtils.initComputer;
import static jace.TestUtils.runAssemblyCode;
import jace.apple2e.MOS65C02;
import jace.apple2e.RAM128k;
@ -50,7 +51,7 @@ public class MemoryListenerTest {
@Test
public void testListenerRelevance() {
AtomicInteger anyEventCaught = new AtomicInteger();
RAMListener anyListener = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
RAMListener anyListener = new RAMListener("Execution test", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x0100);
@ -63,7 +64,7 @@ public class MemoryListenerTest {
};
AtomicInteger readAnyEventCaught = new AtomicInteger();
RAMListener readAnyListener = new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
RAMListener readAnyListener = new RAMListener("Execution test 1", RAMEvent.TYPE.READ, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x0100);
@ -76,7 +77,7 @@ public class MemoryListenerTest {
};
AtomicInteger writeEventCaught = new AtomicInteger();
RAMListener writeListener = new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
RAMListener writeListener = new RAMListener("Execution test 2", RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x0100);
@ -89,7 +90,7 @@ public class MemoryListenerTest {
};
AtomicInteger executeEventCaught = new AtomicInteger();
RAMListener executeListener = new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
RAMListener executeListener = new RAMListener("Execution test 3", RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x0100);

View File

@ -15,17 +15,18 @@
*/
package jace.cpu;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import org.junit.BeforeClass;
import org.junit.Test;
import jace.Emulator;
import static jace.TestUtils.assemble;
import static jace.TestUtils.createSimpleDevice;
import static jace.TestUtils.initComputer;
import static jace.TestUtils.runAssemblyCode;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.BeforeClass;
import org.junit.Test;
import jace.Emulator;
import jace.apple2e.MOS65C02;
import jace.apple2e.RAM128k;
import jace.core.Computer;
@ -90,7 +91,7 @@ WAIT3 sbc #$01
AtomicInteger breakpointEncountered = new AtomicInteger();
AtomicInteger cycleCount = new AtomicInteger();
// This listener will increment our breakpoint counter if it reaches our desired stoppoing point in time
RAMListener l = ram.addExecutionTrap(0x01000, e -> breakpointEncountered.incrementAndGet());
RAMListener l = ram.addExecutionTrap("Halting test", 0x01000, e -> breakpointEncountered.incrementAndGet());
// This faux device counts the number of cycles executed
cpu.addChildDevice(createSimpleDevice(()->{
if (breakpointEncountered.get() == 0) {
@ -116,7 +117,7 @@ WAIT3 sbc #$01
AtomicInteger breakpointEncountered = new AtomicInteger();
AtomicInteger cycleCount = new AtomicInteger();
// This listener will increment our breakpoint counter if it reaches our desired stoppoing point in time
RAMListener l = ram.addExecutionTrap(0x01000, e -> breakpointEncountered.incrementAndGet());
RAMListener l = ram.addExecutionTrap("Halting test", 0x01000, e -> breakpointEncountered.incrementAndGet());
// This faux device counts the number of cycles executed
cpu.addChildDevice(createSimpleDevice(()->{