mirror of
https://github.com/badvision/lawless-legends.git
synced 2025-01-18 19:31:49 +00:00
Completely rewritten sound engine, fixed listener registration issues, Configuration global actions fixed.
This commit is contained in:
parent
cd5903367b
commit
b5997c3876
55
Platform/Apple/tools/jace/.vscode/launch.json
vendored
Normal file
55
Platform/Apple/tools/jace/.vscode/launch.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
3
Platform/Apple/tools/jace/.vscode/settings.json
vendored
Normal file
3
Platform/Apple/tools/jace/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
@ -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>
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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 ->
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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.");
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -63,6 +63,7 @@ public class LawlessComputer extends Apple2e {
|
||||
if (showBootAnimation && PRODUCTION_MODE) {
|
||||
(new Thread(this::startAnimation)).start();
|
||||
} else {
|
||||
cpu.setPaused(false);
|
||||
finishColdStart();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
35
Platform/Apple/tools/jace/src/test/java/jace/SoundTest.java
Normal file
35
Platform/Apple/tools/jace/src/test/java/jace/SoundTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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(()->{
|
||||
|
Loading…
x
Reference in New Issue
Block a user