mirror of
https://github.com/badvision/lawless-legends.git
synced 2024-06-09 23:29:37 +00:00
Lots of test coverage, tweaks and timing fixes
This commit is contained in:
parent
2651818ca2
commit
dcf4638e1e
|
@ -226,12 +226,6 @@
|
||||||
<version>21.0.2</version>
|
<version>21.0.2</version>
|
||||||
<type>jar</type>
|
<type>jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- <dependency>
|
|
||||||
<groupId>org.openjfx</groupId>
|
|
||||||
<artifactId>javafx-media</artifactId>
|
|
||||||
<version>21.0.2</version>
|
|
||||||
<type>jar</type>
|
|
||||||
</dependency> -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-graphics</artifactId>
|
<artifactId>javafx-graphics</artifactId>
|
||||||
|
@ -271,11 +265,6 @@
|
||||||
<artifactId>lwjgl-stb</artifactId>
|
<artifactId>lwjgl-stb</artifactId>
|
||||||
<classifier>${lwjgl.natives}</classifier>
|
<classifier>${lwjgl.natives}</classifier>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- <dependency>
|
|
||||||
<groupId>com.badlogicgames.jlayer</groupId>
|
|
||||||
<artifactId>jlayer</artifactId>
|
|
||||||
<version>1.0.2-gdx</version>
|
|
||||||
</dependency> -->
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
|
|
|
@ -145,8 +145,8 @@ public class Emulator {
|
||||||
instance = this;
|
instance = this;
|
||||||
computer = new LawlessComputer();
|
computer = new LawlessComputer();
|
||||||
Configuration.buildTree();
|
Configuration.buildTree();
|
||||||
computer.getMotherboard().suspend();
|
|
||||||
Configuration.loadSettings();
|
Configuration.loadSettings();
|
||||||
|
Configuration.applySettings(Configuration.BASE);
|
||||||
mainThread = Thread.currentThread();
|
mainThread = Thread.currentThread();
|
||||||
// EmulatorUILogic.registerDebugger();
|
// EmulatorUILogic.registerDebugger();
|
||||||
// computer.coldStart();
|
// computer.coldStart();
|
||||||
|
|
|
@ -29,7 +29,6 @@ import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
@ -79,12 +78,6 @@ public class EmulatorUILogic implements Reconfigurable {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConfigurableField(
|
|
||||||
category = "General",
|
|
||||||
name = "Speed Setting"
|
|
||||||
)
|
|
||||||
public int speedSetting = 3;
|
|
||||||
|
|
||||||
@ConfigurableField(
|
@ConfigurableField(
|
||||||
category = "General",
|
category = "General",
|
||||||
name = "Show Drives"
|
name = "Show Drives"
|
||||||
|
@ -564,8 +557,5 @@ public class EmulatorUILogic implements Reconfigurable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reconfigure() {
|
public void reconfigure() {
|
||||||
// Null-safe so there are no errors in unit tests
|
|
||||||
Optional.ofNullable(LawlessLegends.getApplication())
|
|
||||||
.ifPresent(app->app.controller.setSpeed(speedSetting));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import jace.core.Card;
|
import jace.core.Card;
|
||||||
import jace.core.Motherboard;
|
|
||||||
import jace.core.Utility;
|
import jace.core.Utility;
|
||||||
import jace.core.Video;
|
import jace.core.Video;
|
||||||
import jace.lawless.LawlessComputer;
|
import jace.lawless.LawlessComputer;
|
||||||
|
@ -97,6 +96,9 @@ public class JaceUIController {
|
||||||
|
|
||||||
private final BooleanProperty aspectRatioCorrectionEnabled = new SimpleBooleanProperty(false);
|
private final BooleanProperty aspectRatioCorrectionEnabled = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
|
public static final double MIN_SPEED = 0.5;
|
||||||
|
public static final double MAX_SPEED = 5.0;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert rootPane != null : "fx:id=\"rootPane\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
assert rootPane != null : "fx:id=\"rootPane\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||||
|
@ -191,15 +193,10 @@ public class JaceUIController {
|
||||||
return 0.5;
|
return 0.5;
|
||||||
} else if (setting == 1.0) {
|
} else if (setting == 1.0) {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
} else if (setting >= 10) {
|
} else if (setting >= 5) {
|
||||||
return Double.MAX_VALUE;
|
return Double.MAX_VALUE;
|
||||||
} else {
|
} else {
|
||||||
double val = Math.pow(2.0, (setting - 1.0) / 1.5);
|
return setting;
|
||||||
val = Math.floor(val * 2.0) / 2.0;
|
|
||||||
if (val > 2.0) {
|
|
||||||
val = Math.floor(val);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,14 +218,16 @@ public class JaceUIController {
|
||||||
primaryStage = ps;
|
primaryStage = ps;
|
||||||
|
|
||||||
connectButtons(controlOverlay);
|
connectButtons(controlOverlay);
|
||||||
speedSlider.setMinorTickCount(0);
|
speedSlider.setMinorTickCount(3);
|
||||||
speedSlider.setMajorTickUnit(1);
|
speedSlider.setMajorTickUnit(1);
|
||||||
|
speedSlider.setMax(MAX_SPEED);
|
||||||
|
speedSlider.setMin(MIN_SPEED);
|
||||||
speedSlider.setLabelFormatter(new StringConverter<Double>() {
|
speedSlider.setLabelFormatter(new StringConverter<Double>() {
|
||||||
@Override
|
@Override
|
||||||
public String toString(Double val) {
|
public String toString(Double val) {
|
||||||
if (val < 1.0) {
|
if (val <= MIN_SPEED) {
|
||||||
return "Half";
|
return "Half";
|
||||||
} else if (val >= 10.0) {
|
} else if (val >= MAX_SPEED) {
|
||||||
return "∞";
|
return "∞";
|
||||||
}
|
}
|
||||||
double v = convertSpeedToRatio(val);
|
double v = convertSpeedToRatio(val);
|
||||||
|
@ -244,11 +243,10 @@ public class JaceUIController {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
speedSlider.valueProperty().addListener((val, oldValue, newValue) -> setSpeed(newValue.doubleValue()));
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
speedSlider.setValue(Emulator.getUILogic().speedSetting);
|
double currentSpeed = (double) Emulator.withComputer(c->c.getMotherboard().getSpeedRatio(), 100) / 100.0;
|
||||||
// Kind of redundant but make sure speed is properly set as if the user did it
|
speedSlider.valueProperty().set(currentSpeed);
|
||||||
setSpeed(Emulator.getUILogic().speedSetting);
|
speedSlider.valueProperty().addListener((val, oldValue, newValue) -> setSpeed(newValue.doubleValue()));
|
||||||
});
|
});
|
||||||
musicSelection.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) ->
|
musicSelection.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) ->
|
||||||
Emulator.withComputer(computer ->
|
Emulator.withComputer(computer ->
|
||||||
|
@ -268,29 +266,20 @@ public class JaceUIController {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSpeed(double speed) {
|
public void setSpeed(double speed) {
|
||||||
Emulator.getUILogic().speedSetting = (int) speed;
|
double newSpeed = Math.max(speed, MIN_SPEED);
|
||||||
double speedRatio = convertSpeedToRatio(speed);
|
|
||||||
if (speedSlider.getValue() != speed) {
|
if (speedSlider.getValue() != speed) {
|
||||||
Platform.runLater(()->speedSlider.setValue(speed));
|
Platform.runLater(()->speedSlider.setValue(newSpeed));
|
||||||
}
|
}
|
||||||
if (speedRatio >= 100.0) {
|
if (newSpeed >= MAX_SPEED) {
|
||||||
Emulator.withComputer(c -> {
|
Emulator.withComputer(c -> {
|
||||||
c.getMotherboard().setSpeedInPercentage(20000);
|
|
||||||
c.getMotherboard().setMaxSpeed(true);
|
c.getMotherboard().setMaxSpeed(true);
|
||||||
});
|
});
|
||||||
// Motherboard.cpuPerClock = 10;
|
|
||||||
} else {
|
} else {
|
||||||
if (speedRatio > 1000) {
|
|
||||||
Motherboard.cpuPerClock = 2;
|
|
||||||
} else {
|
|
||||||
Motherboard.cpuPerClock = 1;
|
|
||||||
}
|
|
||||||
Emulator.withComputer(c -> {
|
Emulator.withComputer(c -> {
|
||||||
c.getMotherboard().setMaxSpeed(false);
|
c.getMotherboard().setMaxSpeed(false);
|
||||||
c.getMotherboard().setSpeedInPercentage((int) (speedRatio * 100));
|
c.getMotherboard().setSpeedInPercentage((int) (newSpeed * 100));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Emulator.withComputer(c -> c.getMotherboard().reconfigure());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleAspectRatio() {
|
public void toggleAspectRatio() {
|
||||||
|
|
|
@ -141,7 +141,7 @@ public class LawlessLegends extends Application {
|
||||||
romStarted.set(true);
|
romStarted.set(true);
|
||||||
});
|
});
|
||||||
romStarted.set(false);
|
romStarted.set(false);
|
||||||
c.invokeColdStart();
|
c.coldStart();
|
||||||
try {
|
try {
|
||||||
Thread.sleep(watchdogDelay);
|
Thread.sleep(watchdogDelay);
|
||||||
if (!romStarted.get() || !c.isRunning() || c.getCpu().getProgramCounter() == 0xc700 || c.getCpu().getProgramCounter() == 0) {
|
if (!romStarted.get() || !c.isRunning() || c.getCpu().getProgramCounter() == 0xc700 || c.getCpu().getProgramCounter() == 0) {
|
||||||
|
@ -162,12 +162,10 @@ public class LawlessLegends extends Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetEmulator() {
|
public void resetEmulator() {
|
||||||
// Reset the emulator memory and reconfigure
|
// Reset the emulator memory and restart
|
||||||
Emulator.withComputer(c -> {
|
Emulator.withComputer(c -> {
|
||||||
c.pause();
|
|
||||||
c.getMemory().resetState();
|
c.getMemory().resetState();
|
||||||
c.reconfigure();
|
c.warmStart();
|
||||||
c.resume();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,8 +61,10 @@ import jace.state.Stateful;
|
||||||
*/
|
*/
|
||||||
@Stateful
|
@Stateful
|
||||||
public class Apple2e extends Computer {
|
public class Apple2e extends Computer {
|
||||||
|
|
||||||
static int IRQ_VECTOR = 0x003F2;
|
static int IRQ_VECTOR = 0x003F2;
|
||||||
|
|
||||||
|
@ConfigurableField(name = "Production mode", shortName = "production")
|
||||||
|
public boolean PRODUCTION_MODE = false;
|
||||||
@ConfigurableField(name = "Slot 1", shortName = "s1card")
|
@ConfigurableField(name = "Slot 1", shortName = "s1card")
|
||||||
public DeviceSelection<Cards> card1 = new DeviceSelection<>(Cards.class, null);
|
public DeviceSelection<Cards> card1 = new DeviceSelection<>(Cards.class, null);
|
||||||
@ConfigurableField(name = "Slot 2", shortName = "s2card")
|
@ConfigurableField(name = "Slot 2", shortName = "s2card")
|
||||||
|
@ -92,9 +94,7 @@ public class Apple2e extends Computer {
|
||||||
@ConfigurableField(name = "No-Slot Clock Enabled", shortName = "clock", description = "If checked, no-slot clock will be enabled", enablesDevice = true)
|
@ConfigurableField(name = "No-Slot Clock Enabled", shortName = "clock", description = "If checked, no-slot clock will be enabled", enablesDevice = true)
|
||||||
public boolean clockEnabled = true;
|
public boolean clockEnabled = true;
|
||||||
@ConfigurableField(name = "Accelerator Enabled", shortName = "zip", description = "If checked, add support for Zip/Transwarp", enablesDevice = true)
|
@ConfigurableField(name = "Accelerator Enabled", shortName = "zip", description = "If checked, add support for Zip/Transwarp", enablesDevice = true)
|
||||||
public boolean acceleratorEnabled = true;
|
public boolean acceleratorEnabled = PRODUCTION_MODE;
|
||||||
@ConfigurableField(name = "Production mode", shortName = "production")
|
|
||||||
public boolean PRODUCTION_MODE = true;
|
|
||||||
|
|
||||||
public Joystick joystick1;
|
public Joystick joystick1;
|
||||||
public Joystick joystick2;
|
public Joystick joystick2;
|
||||||
|
@ -105,7 +105,7 @@ public class Apple2e extends Computer {
|
||||||
public ZipWarpAccelerator accelerator;
|
public ZipWarpAccelerator accelerator;
|
||||||
FPSMonitorDevice fpsCounters;
|
FPSMonitorDevice fpsCounters;
|
||||||
@ConfigurableField(name = "Show speed monitors", shortName = "showFps")
|
@ConfigurableField(name = "Show speed monitors", shortName = "showFps")
|
||||||
public boolean showSpeedMonitors = false;
|
public boolean showSpeedMonitors = !PRODUCTION_MODE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of Apple2e
|
* Creates a new instance of Apple2e
|
||||||
|
@ -130,46 +130,42 @@ public class Apple2e extends Computer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void coldStart() {
|
public void coldStart() {
|
||||||
getMotherboard().whileSuspended(()->{
|
RAM128k r = (RAM128k) getMemory();
|
||||||
System.err.println("Cold starting computer: RESETTING SOFT SWITCHES");
|
System.err.println("Cold starting computer: RESETTING SOFT SWITCHES");
|
||||||
for (SoftSwitches s : SoftSwitches.values()) {
|
r.resetState();
|
||||||
|
for (SoftSwitches s : SoftSwitches.values()) {
|
||||||
|
if ((s.getSwitch() instanceof VideoSoftSwitch)) {
|
||||||
s.getSwitch().reset();
|
s.getSwitch().reset();
|
||||||
}
|
}
|
||||||
reconfigure();
|
}
|
||||||
getMemory().configureActiveMemory();
|
// This isn't really authentic behavior but sometimes games like memory to have a consistent state when booting.
|
||||||
getVideo().configureVideoMode();
|
r.zeroAllRam();
|
||||||
for (Optional<Card> c : getMemory().getAllCards()) {
|
// Sather 4-15:
|
||||||
c.ifPresent(Card::reset);
|
// An open Apple (left Apple) reset causes meaningless values to be stored in two locations
|
||||||
}
|
// of every memory page from Page $01 through Page $BF before the power-up byte is checked.
|
||||||
});
|
int offset = IRQ_VECTOR & 0x0ff;
|
||||||
reboot();
|
byte garbage = (byte) (Math.random() * 256.0);
|
||||||
}
|
for (int page=1; page < 0xc0; page++) {
|
||||||
|
r.write(page << 8 + offset, garbage, false, true);
|
||||||
public void reboot() {
|
r.write(page << 8 + 1 + offset, garbage, false, true);
|
||||||
RAM r = getMemory();
|
}
|
||||||
r.write(IRQ_VECTOR, (byte) 0x00, false, true);
|
|
||||||
r.write(IRQ_VECTOR + 1, (byte) 0x00, false, true);
|
|
||||||
r.write(IRQ_VECTOR + 2, (byte) 0x00, false, true);
|
|
||||||
warmStart();
|
warmStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void warmStart() {
|
public void warmStart() {
|
||||||
getMotherboard().whileSuspended(()->{
|
for (SoftSwitches s : SoftSwitches.values()) {
|
||||||
// This isn't really authentic behavior but sometimes games like memory to have a consistent state when booting.
|
if (! (s.getSwitch() instanceof VideoSoftSwitch)) {
|
||||||
for (SoftSwitches s : SoftSwitches.values()) {
|
s.getSwitch().reset();
|
||||||
if (! (s.getSwitch() instanceof VideoSoftSwitch)) {
|
|
||||||
s.getSwitch().reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
((RAM128k)getMemory()).zeroAllRam();
|
}
|
||||||
getMemory().configureActiveMemory();
|
getMemory().configureActiveMemory();
|
||||||
getVideo().configureVideoMode();
|
getVideo().configureVideoMode();
|
||||||
getCpu().reset();
|
getCpu().reset();
|
||||||
for (Optional<Card> c : getMemory().getAllCards()) {
|
for (Optional<Card> c : getMemory().getAllCards()) {
|
||||||
c.ifPresent(Card::reset);
|
c.ifPresent(Card::reset);
|
||||||
}
|
}
|
||||||
});
|
motherboard.disableTempMaxSpeed();
|
||||||
resume();
|
resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,15 +372,9 @@ public class Apple2e extends Computer {
|
||||||
if (getMotherboard() == null) {
|
if (getMotherboard() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getMotherboard().resume();
|
getMotherboard().resumeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// public boolean isRunning() {
|
|
||||||
// if (motherboard == null) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// return motherboard.isRunning() && !motherboard.isPaused;
|
|
||||||
// }
|
|
||||||
private final List<RAMListener> hints = new ArrayList<>();
|
private final List<RAMListener> hints = new ArrayList<>();
|
||||||
|
|
||||||
ScheduledExecutorService animationTimer = new ScheduledThreadPoolExecutor(1);
|
ScheduledExecutorService animationTimer = new ScheduledThreadPoolExecutor(1);
|
||||||
|
|
|
@ -182,7 +182,6 @@ abstract public class RAM128k extends RAM {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void zeroAllRam() {
|
public final void zeroAllRam() {
|
||||||
// Format memory with FF FF 00 00 pattern
|
|
||||||
for (int i = 0; i < 0x0100; i++) {
|
for (int i = 0; i < 0x0100; i++) {
|
||||||
blank.get(0)[i] = (byte) 0x0FF;
|
blank.get(0)[i] = (byte) 0x0FF;
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ public class Speaker extends Device {
|
||||||
* Playback volume (should be < 1423)
|
* Playback volume (should be < 1423)
|
||||||
*/
|
*/
|
||||||
@ConfigurableField(name = "Speaker Volume", shortName = "vol", description = "Should be under 1400")
|
@ConfigurableField(name = "Speaker Volume", shortName = "vol", description = "Should be under 1400")
|
||||||
public static int VOLUME = 600;
|
public static int VOLUME = 400;
|
||||||
/**
|
/**
|
||||||
* Number of idle cycles until speaker playback is deactivated
|
* Number of idle cycles until speaker playback is deactivated
|
||||||
*/
|
*/
|
||||||
|
@ -118,6 +118,7 @@ public class Speaker extends Device {
|
||||||
public boolean suspend() {
|
public boolean suspend() {
|
||||||
boolean result = super.suspend();
|
boolean result = super.suspend();
|
||||||
speakerBit = false;
|
speakerBit = false;
|
||||||
|
Emulator.withComputer(c->c.getMotherboard().cancelSpeedRequest(this));
|
||||||
if (buffer != null) {
|
if (buffer != null) {
|
||||||
try {
|
try {
|
||||||
buffer.shutdown();
|
buffer.shutdown();
|
||||||
|
@ -164,8 +165,7 @@ public class Speaker extends Device {
|
||||||
TICKS_PER_SAMPLE = Emulator.withComputer(c-> ((double) c.getMotherboard().getSpeedInHz()) / SoundMixer.RATE, 0.0);
|
TICKS_PER_SAMPLE = Emulator.withComputer(c-> ((double) c.getMotherboard().getSpeedInHz()) / SoundMixer.RATE, 0.0);
|
||||||
}
|
}
|
||||||
TICKS_PER_SAMPLE_FLOOR = Math.floor(TICKS_PER_SAMPLE);
|
TICKS_PER_SAMPLE_FLOOR = Math.floor(TICKS_PER_SAMPLE);
|
||||||
|
super.resume();
|
||||||
setRun(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -195,6 +195,7 @@ public class Speaker extends Device {
|
||||||
counter += 1.0d;
|
counter += 1.0d;
|
||||||
if (counter >= TICKS_PER_SAMPLE) {
|
if (counter >= TICKS_PER_SAMPLE) {
|
||||||
playSample(level * VOLUME);
|
playSample(level * VOLUME);
|
||||||
|
Emulator.withComputer(c->c.getMotherboard().requestSpeed(this));
|
||||||
|
|
||||||
// Set level back to 0
|
// Set level back to 0
|
||||||
level = 0;
|
level = 0;
|
||||||
|
@ -280,13 +281,11 @@ public class Speaker extends Device {
|
||||||
@Override
|
@Override
|
||||||
public void attach() {
|
public void attach() {
|
||||||
configureListener();
|
configureListener();
|
||||||
resume();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void detach() {
|
public void detach() {
|
||||||
removeListener();
|
removeListener();
|
||||||
suspend();
|
|
||||||
super.detach();
|
super.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,9 @@
|
||||||
|
|
||||||
package jace.applesoft;
|
package jace.applesoft;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jace.Emulator;
|
import jace.Emulator;
|
||||||
|
@ -53,22 +47,7 @@ public class ApplesoftProgram {
|
||||||
public static final int RUNNING_FLAG = 0x076;
|
public static final int RUNNING_FLAG = 0x076;
|
||||||
public static final int NOT_RUNNING = 0x0FF;
|
public static final int NOT_RUNNING = 0x0FF;
|
||||||
public static final int GOTO_CMD = 0x0D944; //actually starts at D93E
|
public static final int GOTO_CMD = 0x0D944; //actually starts at D93E
|
||||||
int startingAddress = 0x0801;
|
public static final int START_ADDRESS = 0x0801;
|
||||||
|
|
||||||
public static void main(String... args) {
|
|
||||||
byte[] source = null;
|
|
||||||
File f = new File("/home/brobert/Documents/Personal/a2gameserver/lib/data/games/LEMONADE#fc0801");
|
|
||||||
try (FileInputStream in = new FileInputStream(f)) {
|
|
||||||
source = new byte[(int) f.length()];
|
|
||||||
in.read(source);
|
|
||||||
} catch (FileNotFoundException ex) {
|
|
||||||
Logger.getLogger(ApplesoftProgram.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Logger.getLogger(ApplesoftProgram.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
ApplesoftProgram test = ApplesoftProgram.fromBinary(Arrays.asList(toObjects(source)));
|
|
||||||
System.out.println(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Byte[] toObjects(byte[] bytesPrim) {
|
public static Byte[] toObjects(byte[] bytesPrim) {
|
||||||
Byte[] bytes = new Byte[bytesPrim.length];
|
Byte[] bytes = new Byte[bytesPrim.length];
|
||||||
|
@ -91,7 +70,7 @@ public class ApplesoftProgram {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApplesoftProgram fromBinary(List<Byte> binary) {
|
public static ApplesoftProgram fromBinary(List<Byte> binary) {
|
||||||
return fromBinary(binary, 0x0801);
|
return fromBinary(binary, START_ADDRESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApplesoftProgram fromBinary(List<Byte> binary, int startAddress) {
|
public static ApplesoftProgram fromBinary(List<Byte> binary, int startAddress) {
|
||||||
|
|
|
@ -643,22 +643,22 @@ public class Configuration implements Reconfigurable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void printTree(ConfigNode n, String prefix, int i) {
|
// private static void printTree(ConfigNode n, String prefix, int i) {
|
||||||
n.getAllSettingNames().stream().forEach((setting) -> {
|
// n.getAllSettingNames().stream().forEach((setting) -> {
|
||||||
for (int j = 0; j < i; j++) {
|
// for (int j = 0; j < i; j++) {
|
||||||
System.out.print(" ");
|
// System.out.print(" ");
|
||||||
}
|
// }
|
||||||
ConfigurableField f = null;
|
// ConfigurableField f = null;
|
||||||
try {
|
// try {
|
||||||
f = n.subject.getClass().getField(setting).getAnnotation(ConfigurableField.class);
|
// f = n.subject.getClass().getField(setting).getAnnotation(ConfigurableField.class);
|
||||||
} catch (NoSuchFieldException | SecurityException ex) {
|
// } catch (NoSuchFieldException | SecurityException ex) {
|
||||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
// Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
// }
|
||||||
String sn = (f != null && !f.shortName().equals("")) ? f.shortName() : setting;
|
// String sn = (f != null && !f.shortName().equals("")) ? f.shortName() : setting;
|
||||||
System.out.println(prefix + ">>" + setting + " (" + n.subject.getShortName() + "." + sn + ")");
|
// System.out.println(prefix + ">>" + setting + " (" + n.subject.getShortName() + "." + sn + ")");
|
||||||
});
|
// });
|
||||||
n.getChildren().stream().forEach((c) -> {
|
// n.getChildren().stream().forEach((c) -> {
|
||||||
printTree(c, prefix + "." + c, i + 1);
|
// printTree(c, prefix + "." + c, i + 1);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,18 +198,10 @@ public class InvokableActionRegistryImpl extends InvokableActionRegistry {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
annotation = createInvokableAction("Cold boot", "general", "Process startup sequence from power-up", "Full reset;reset emulator", true, false, new String[]{"Ctrl+Shift+Backspace", "Ctrl+Shift+Delete"});
|
annotation = createInvokableAction("Reset", "general", "Process user-initatiated reboot (ctrl+apple+reset)", "reboot;reset;three-finger-salute;restart", true, false, new String[]{"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"});
|
||||||
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
|
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
|
||||||
try {
|
try {
|
||||||
((jace.core.Computer) o).invokeColdStart();
|
((jace.core.Computer) o).invokeReset();
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.invokeColdStart", ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
annotation = createInvokableAction("Warm boot", "general", "Process user-initatiated reboot (ctrl+apple+reset)", "reboot;reset;three-finger-salute;restart", true, false, new String[]{"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"});
|
|
||||||
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
|
|
||||||
try {
|
|
||||||
((jace.core.Computer) o).invokeWarmStart();
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.invokeWarmStart", ex);
|
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.invokeWarmStart", ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,11 @@ package jace.core;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import jace.LawlessLegends;
|
import jace.LawlessLegends;
|
||||||
|
import jace.apple2e.SoftSwitches;
|
||||||
import jace.config.ConfigurableField;
|
import jace.config.ConfigurableField;
|
||||||
|
import jace.config.Configuration;
|
||||||
import jace.config.InvokableAction;
|
import jace.config.InvokableAction;
|
||||||
import jace.config.Reconfigurable;
|
import jace.config.Reconfigurable;
|
||||||
import jace.state.StateManager;
|
import jace.state.StateManager;
|
||||||
|
@ -161,34 +161,37 @@ public abstract class Computer implements Reconfigurable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user wants a full reset, use the coldStart method.
|
||||||
|
* This ensures a more consistent state of the machine.
|
||||||
|
* Some games make bad assumptions about the initial state of the machine
|
||||||
|
* and that fails to work if the machine is not reset to a known state first.
|
||||||
|
*/
|
||||||
@InvokableAction(
|
@InvokableAction(
|
||||||
name = "Cold boot",
|
name = "Reset",
|
||||||
description = "Process startup sequence from power-up",
|
|
||||||
category = "general",
|
|
||||||
alternatives = "Full reset;reset emulator",
|
|
||||||
defaultKeyMapping = {"Ctrl+Shift+Backspace", "Ctrl+Shift+Delete"})
|
|
||||||
public void invokeColdStart() {
|
|
||||||
try {
|
|
||||||
loadRom(false);
|
|
||||||
memory.resetState();
|
|
||||||
coldStart();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logger.getLogger(Computer.class.getName()).log(Level.SEVERE, "Failed to load system rom ROMs", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void coldStart();
|
|
||||||
|
|
||||||
@InvokableAction(
|
|
||||||
name = "Warm boot",
|
|
||||||
description = "Process user-initatiated reboot (ctrl+apple+reset)",
|
description = "Process user-initatiated reboot (ctrl+apple+reset)",
|
||||||
category = "general",
|
category = "general",
|
||||||
alternatives = "reboot;reset;three-finger-salute;restart",
|
alternatives = "reboot;reset;three-finger-salute;restart",
|
||||||
defaultKeyMapping = {"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"})
|
defaultKeyMapping = {"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"})
|
||||||
public void invokeWarmStart() {
|
public void invokeReset() {
|
||||||
warmStart();
|
if (SoftSwitches.PDL0.isOn()) {
|
||||||
|
coldStart();
|
||||||
|
} else {
|
||||||
|
warmStart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In a cold start, memory is reset (either two bytes per page as per Sather 4-15) or full-wipe
|
||||||
|
* Also video softswitches are reset
|
||||||
|
* Otherwise it does the same as warm start
|
||||||
|
**/
|
||||||
|
public abstract void coldStart();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In a warm start, memory is not reset, but the CPU and cards are reset
|
||||||
|
* All but video softswitches are reset, putting the MMU in a known state
|
||||||
|
*/
|
||||||
public abstract void warmStart();
|
public abstract void warmStart();
|
||||||
|
|
||||||
public Keyboard getKeyboard() {
|
public Keyboard getKeyboard() {
|
||||||
|
@ -225,5 +228,6 @@ public abstract class Computer implements Reconfigurable {
|
||||||
stateManager = null;
|
stateManager = null;
|
||||||
StateManager.getInstance(this).invalidate();
|
StateManager.getInstance(this).invalidate();
|
||||||
}
|
}
|
||||||
|
Configuration.registerKeyHandlers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,8 @@ public abstract class Device implements Reconfigurable {
|
||||||
|
|
||||||
|
|
||||||
public boolean suspend() {
|
public boolean suspend() {
|
||||||
children.forEach(Device::suspend);
|
// Suspending the parent device means the children are not going to run
|
||||||
|
// children.forEach(Device::suspend);
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
setRun(false);
|
setRun(false);
|
||||||
return true;
|
return true;
|
||||||
|
@ -230,10 +231,17 @@ public abstract class Device implements Reconfigurable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void resumeAll() {
|
||||||
|
resume();
|
||||||
|
children.forEach(Device::resumeAll);
|
||||||
|
}
|
||||||
|
|
||||||
public void resume() {
|
public void resume() {
|
||||||
children.forEach(Device::resume);
|
// Resuming children pre-emptively might lead to unexpected behavior
|
||||||
|
// Don't do that unless we really mean to (such as cold-starting the computer)
|
||||||
|
// children.forEach(Device::resume);
|
||||||
if (!isRunning()) {
|
if (!isRunning()) {
|
||||||
setRun(true);
|
setRun(true);
|
||||||
waitCycles = 0;
|
waitCycles = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,6 @@ public abstract class IndependentTimedDevice extends TimedDevice {
|
||||||
// The actual worker that the device runs as
|
// The actual worker that the device runs as
|
||||||
public Thread worker;
|
public Thread worker;
|
||||||
public boolean hasStopped = true;
|
public boolean hasStopped = true;
|
||||||
// From the holy word of Sather 3:5 (Table 3.1) :-)
|
|
||||||
// This average speed averages in the "long" cycles
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
/* We really don't want to suspect the worker thread if we're running in it.
|
/* We really don't want to suspect the worker thread if we're running in it.
|
||||||
|
@ -67,7 +64,7 @@ public abstract class IndependentTimedDevice extends TimedDevice {
|
||||||
|
|
||||||
|
|
||||||
public boolean isDeviceThread() {
|
public boolean isDeviceThread() {
|
||||||
return worker != null && worker.isAlive() && Thread.currentThread() == worker;
|
return Thread.currentThread() == worker;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,14 +81,14 @@ public abstract class IndependentTimedDevice extends TimedDevice {
|
||||||
public boolean suspend() {
|
public boolean suspend() {
|
||||||
boolean result = super.suspend();
|
boolean result = super.suspend();
|
||||||
Thread w = worker;
|
Thread w = worker;
|
||||||
|
worker = null;
|
||||||
if (w != null && w.isAlive()) {
|
if (w != null && w.isAlive()) {
|
||||||
try {
|
try {
|
||||||
w.interrupt();
|
w.interrupt();
|
||||||
w.join(1000);
|
w.join(100);
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
worker = null;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,9 +109,9 @@ public abstract class IndependentTimedDevice extends TimedDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resume() {
|
public synchronized void resume() {
|
||||||
super.resume();
|
super.resume();
|
||||||
if (worker != null && worker.isAlive()) {
|
if (worker != null && worker.isAlive()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Thread newWorker = new Thread(() -> {
|
Thread newWorker = new Thread(() -> {
|
||||||
|
|
|
@ -304,20 +304,12 @@ public class Keyboard implements Reconfigurable {
|
||||||
|
|
||||||
@InvokableAction(name = "Open Apple Key", alternatives = "OA", category = "Keyboard", notifyOnRelease = true, defaultKeyMapping = "Alt", consumeKeyEvent = false)
|
@InvokableAction(name = "Open Apple Key", alternatives = "OA", category = "Keyboard", notifyOnRelease = true, defaultKeyMapping = "Alt", consumeKeyEvent = false)
|
||||||
public void openApple(boolean pressed) {
|
public void openApple(boolean pressed) {
|
||||||
// boolean isRunning = computer.pause();
|
|
||||||
SoftSwitches.PB0.getSwitch().setState(pressed);
|
SoftSwitches.PB0.getSwitch().setState(pressed);
|
||||||
// if (isRunning) {
|
|
||||||
// computer.resume();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@InvokableAction(name = "Closed Apple Key", alternatives = "CA", category = "Keyboard", notifyOnRelease = true, defaultKeyMapping = {"Shortcut","Meta","Command"}, consumeKeyEvent = false)
|
@InvokableAction(name = "Closed Apple Key", alternatives = "CA", category = "Keyboard", notifyOnRelease = true, defaultKeyMapping = {"Shortcut","Meta","Command"}, consumeKeyEvent = false)
|
||||||
public void solidApple(boolean pressed) {
|
public void solidApple(boolean pressed) {
|
||||||
// boolean isRunning = computer.pause();
|
|
||||||
SoftSwitches.PB1.getSwitch().setState(pressed);
|
SoftSwitches.PB1.getSwitch().setState(pressed);
|
||||||
// if (isRunning) {
|
|
||||||
// computer.resume();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void pasteFromString(String text) {
|
public static void pasteFromString(String text) {
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class Motherboard extends IndependentTimedDevice {
|
||||||
|
|
||||||
@ConfigurableField(name = "Enable Speaker", shortName = "speaker", defaultValue = "true")
|
@ConfigurableField(name = "Enable Speaker", shortName = "speaker", defaultValue = "true")
|
||||||
public static boolean enableSpeaker = true;
|
public static boolean enableSpeaker = true;
|
||||||
private Speaker speaker;
|
public Speaker speaker;
|
||||||
|
|
||||||
void vblankEnd() {
|
void vblankEnd() {
|
||||||
SoftSwitches.VBL.getSwitch().setState(true);
|
SoftSwitches.VBL.getSwitch().setState(true);
|
||||||
|
@ -74,9 +74,6 @@ public class Motherboard extends IndependentTimedDevice {
|
||||||
public String getShortName() {
|
public String getShortName() {
|
||||||
return "mb";
|
return "mb";
|
||||||
}
|
}
|
||||||
@ConfigurableField(category = "advanced", shortName = "cpuPerClock", name = "CPU per clock", defaultValue = "1", description = "Number of extra CPU cycles per clock cycle (normal = 1)")
|
|
||||||
public static int cpuPerClock = 0;
|
|
||||||
public int clockCounter = 1;
|
|
||||||
|
|
||||||
private CPU _cpu = null;
|
private CPU _cpu = null;
|
||||||
public CPU getCpu() {
|
public CPU getCpu() {
|
||||||
|
@ -88,45 +85,32 @@ public class Motherboard extends IndependentTimedDevice {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
// Extra CPU cycles requested, other devices are called by the TimedDevice abstraction
|
|
||||||
for (int i=1; i < cpuPerClock; i++) {
|
|
||||||
getCpu().doTick();
|
|
||||||
if (Speaker.force1mhz) {
|
|
||||||
speaker.tick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public long defaultCyclesPerSecond() {
|
|
||||||
return NTSC_1MHZ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void reconfigure() {
|
public synchronized void reconfigure() {
|
||||||
_cpu = null;
|
_cpu = null;
|
||||||
whileSuspended(() -> {
|
accelorationRequestors.clear();
|
||||||
accelorationRequestors.clear();
|
disableTempMaxSpeed();
|
||||||
disableTempMaxSpeed();
|
super.reconfigure();
|
||||||
super.reconfigure();
|
|
||||||
|
|
||||||
// Now create devices as needed, e.g. sound
|
// Now create devices as needed, e.g. sound
|
||||||
|
|
||||||
if (enableSpeaker) {
|
if (enableSpeaker) {
|
||||||
try {
|
try {
|
||||||
if (speaker == null) {
|
if (speaker == null) {
|
||||||
speaker = new Speaker();
|
speaker = new Speaker();
|
||||||
speaker.attach();
|
speaker.attach();
|
||||||
}
|
|
||||||
speaker.reconfigure();
|
|
||||||
addChildDevice(speaker);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
System.out.println("Unable to initalize sound -- deactivating speaker out");
|
|
||||||
t.printStackTrace();
|
|
||||||
}
|
}
|
||||||
} else {
|
speaker.reconfigure();
|
||||||
System.out.println("Speaker not enabled, leaving it off.");
|
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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
HashSet<Object> accelorationRequestors = new HashSet<>();
|
HashSet<Object> accelorationRequestors = new HashSet<>();
|
||||||
|
|
||||||
|
@ -136,8 +120,7 @@ public class Motherboard extends IndependentTimedDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelSpeedRequest(Object requester) {
|
public void cancelSpeedRequest(Object requester) {
|
||||||
accelorationRequestors.remove(requester);
|
if (accelorationRequestors.remove(requester) && accelorationRequestors.isEmpty()) {
|
||||||
if (accelorationRequestors.isEmpty()) {
|
|
||||||
disableTempMaxSpeed();
|
disableTempMaxSpeed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,10 +41,13 @@ public abstract class RAM implements Reconfigurable {
|
||||||
|
|
||||||
public PagedMemory activeRead;
|
public PagedMemory activeRead;
|
||||||
public PagedMemory activeWrite;
|
public PagedMemory activeWrite;
|
||||||
private final Set<RAMListener> listeners;
|
private final Set<RAMListener> listeners = new ConcurrentSkipListSet<>();
|
||||||
private final Set<RAMListener>[] listenerMap;
|
@SuppressWarnings("unchecked")
|
||||||
private final Set<RAMListener>[] ioListenerMap;
|
private final Set<RAMListener>[] listenerMap = (Set<RAMListener>[]) new Set[256];
|
||||||
public Optional<Card>[] cards;
|
@SuppressWarnings("unchecked")
|
||||||
|
private final Set<RAMListener>[] ioListenerMap = (Set<RAMListener>[]) new Set[256];
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Optional<Card>[] cards = (Optional<Card>[]) new Optional[8];
|
||||||
// card 0 = 80 column card firmware / system rom
|
// card 0 = 80 column card firmware / system rom
|
||||||
public int activeSlot = 0;
|
public int activeSlot = 0;
|
||||||
|
|
||||||
|
@ -53,12 +56,7 @@ public abstract class RAM implements Reconfigurable {
|
||||||
*
|
*
|
||||||
* @param computer
|
* @param computer
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public RAM() {
|
public RAM() {
|
||||||
listeners = new ConcurrentSkipListSet<>();
|
|
||||||
listenerMap = (Set<RAMListener>[]) new Set[256];
|
|
||||||
ioListenerMap = (Set<RAMListener>[]) new Set[256];
|
|
||||||
cards = (Optional<Card>[]) new Optional[8];
|
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
cards[i] = Optional.empty();
|
cards[i] = Optional.empty();
|
||||||
}
|
}
|
||||||
|
@ -298,7 +296,7 @@ public abstract class RAM implements Reconfigurable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RAMListener addListener(final RAMListener l) {
|
public RAMListener addListener(final RAMListener l) {
|
||||||
if (listeners.contains(l)) {
|
if (l == null || listeners.contains(l)) {
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
listeners.add(l);
|
listeners.add(l);
|
||||||
|
@ -323,6 +321,9 @@ public abstract class RAM implements Reconfigurable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeListener(final RAMListener l) {
|
public void removeListener(final RAMListener l) {
|
||||||
|
if (l == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!listeners.contains(l)) {
|
if (!listeners.contains(l)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,11 @@ import jace.config.ConfigurableField;
|
||||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||||
*/
|
*/
|
||||||
public abstract class TimedDevice extends Device {
|
public abstract class TimedDevice extends Device {
|
||||||
|
|
||||||
// From the holy word of Sather 3:5 (Table 3.1) :-)
|
// From the holy word of Sather 3:5 (Table 3.1) :-)
|
||||||
// This average speed averages in the "long" cycles
|
// This average speed averages in the "long" cycles
|
||||||
public static final long NTSC_1MHZ = 1020484L;
|
public static final long NTSC_1MHZ = 1020484L;
|
||||||
public static final long PAL_1MHZ = 1015625L;
|
public static final long PAL_1MHZ = 1015625L;
|
||||||
public static final long SYNC_FREQ_HZ = 30; // Check sync every 2 frames
|
public static final long SYNC_FREQ_HZ = 60;
|
||||||
public static final double NANOS_PER_SECOND = 1000000000.0;
|
public static final double NANOS_PER_SECOND = 1000000000.0;
|
||||||
public static final long NANOS_PER_MILLISECOND = 1000000L;
|
public static final long NANOS_PER_MILLISECOND = 1000000L;
|
||||||
public static final long SYNC_SLOP = NANOS_PER_MILLISECOND * 10L; // 10ms slop for synchronization
|
public static final long SYNC_SLOP = NANOS_PER_MILLISECOND * 10L; // 10ms slop for synchronization
|
||||||
|
@ -41,6 +40,7 @@ public abstract class TimedDevice extends Device {
|
||||||
@ConfigurableField(name = "Speed", description = "(Percentage)")
|
@ConfigurableField(name = "Speed", description = "(Percentage)")
|
||||||
public int speedRatio = 100;
|
public int speedRatio = 100;
|
||||||
@ConfigurableField(name = "Max speed")
|
@ConfigurableField(name = "Max speed")
|
||||||
|
public boolean forceMaxspeed = false;
|
||||||
public boolean maxspeed = false;
|
public boolean maxspeed = false;
|
||||||
private long cyclesPerSecond = defaultCyclesPerSecond();
|
private long cyclesPerSecond = defaultCyclesPerSecond();
|
||||||
private int cycleTimer = 0;
|
private int cycleTimer = 0;
|
||||||
|
@ -120,29 +120,30 @@ public abstract class TimedDevice extends Device {
|
||||||
public final void setMaxSpeed(boolean enabled) {
|
public final void setMaxSpeed(boolean enabled) {
|
||||||
maxspeed = enabled;
|
maxspeed = enabled;
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
disableTempMaxSpeed();
|
resetSyncTimer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isMaxSpeed() {
|
public final boolean isMaxSpeed() {
|
||||||
return maxspeed;
|
return forceMaxspeed || maxspeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final long getSpeedInHz() {
|
public final long getSpeedInHz() {
|
||||||
return cyclesPerInterval * SYNC_FREQ_HZ;
|
return cyclesPerSecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setSpeedInHz(long cyclesPerSecond) {
|
public final void setSpeedInHz(long newSpeed) {
|
||||||
// System.out.println("Raw set speed for " + getName() + " to " + cyclesPerSecond + "hz");
|
// System.out.println("Raw set speed for " + getName() + " to " + cyclesPerSecond + "hz");
|
||||||
|
// Thread.dumpStack();
|
||||||
|
cyclesPerSecond = newSpeed;
|
||||||
speedRatio = (int) Math.round(cyclesPerSecond * 100.0 / defaultCyclesPerSecond());
|
speedRatio = (int) Math.round(cyclesPerSecond * 100.0 / defaultCyclesPerSecond());
|
||||||
cyclesPerInterval = cyclesPerSecond / SYNC_FREQ_HZ;
|
cyclesPerInterval = cyclesPerSecond / SYNC_FREQ_HZ;
|
||||||
nanosPerInterval = (long) (cyclesPerInterval * NANOS_PER_SECOND / cyclesPerSecond);
|
nanosPerInterval = (long) (cyclesPerInterval * NANOS_PER_SECOND / cyclesPerSecond);
|
||||||
// System.out.println("Will pause " + nanosPerInterval + " nanos every " + cyclesPerInterval + " cycles");
|
// System.out.println("Will pause " + nanosPerInterval + " nanos every " + cyclesPerInterval + " cycles");
|
||||||
resetSyncTimer();
|
resetSyncTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setSpeedInPercentage(int ratio) {
|
public final void setSpeedInPercentage(int ratio) {
|
||||||
// System.out.println("Setting " + getName() + " speed ratio to " + speedRatio);
|
|
||||||
cyclesPerSecond = defaultCyclesPerSecond() * ratio / 100;
|
cyclesPerSecond = defaultCyclesPerSecond() * ratio / 100;
|
||||||
if (cyclesPerSecond == 0) {
|
if (cyclesPerSecond == 0) {
|
||||||
cyclesPerSecond = defaultCyclesPerSecond();
|
cyclesPerSecond = defaultCyclesPerSecond();
|
||||||
|
@ -169,21 +170,17 @@ public abstract class TimedDevice extends Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Long calculateResyncDelay() {
|
protected Long calculateResyncDelay() {
|
||||||
if (!maxspeed && ++cycleTimer >= cyclesPerInterval) {
|
if (!isMaxSpeed() && ++cycleTimer >= cyclesPerInterval) {
|
||||||
cycleTimer = 0;
|
cycleTimer = 0;
|
||||||
if (tempSpeedDuration > 0) {
|
if (tempSpeedDuration > 0) {
|
||||||
tempSpeedDuration -= cyclesPerInterval;
|
tempSpeedDuration -= cyclesPerInterval;
|
||||||
if (tempSpeedDuration <= 0) {
|
if (tempSpeedDuration <= 0) {
|
||||||
disableTempMaxSpeed();
|
disableTempMaxSpeed();
|
||||||
}
|
}
|
||||||
} else if (nextSync < System.nanoTime()) {
|
|
||||||
// We're outside the expected range of timing so don't bother trying to sync
|
|
||||||
nextSync = System.nanoTime() + nanosPerInterval;
|
|
||||||
} else {
|
} else {
|
||||||
// Return the number of nanoseconds we're ahead (subtract the slop allowed for synchronization deltas)
|
long retVal = nextSync;
|
||||||
long returnVal = nextSync-SYNC_SLOP;
|
nextSync = Math.min(nextSync + nanosPerInterval, System.nanoTime() + nanosPerInterval * 2); // Avoid drift (but not too much!
|
||||||
nextSync += nanosPerInterval;
|
return retVal;
|
||||||
return returnVal;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -149,6 +149,10 @@ public class Utility {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
InputStream stream = Utility.class.getResourceAsStream("/jace/data/" + filename);
|
InputStream stream = Utility.class.getResourceAsStream("/jace/data/" + filename);
|
||||||
|
if (stream == null) {
|
||||||
|
System.err.println("Could not load icon: " + filename);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
return Optional.of(new Image(stream));
|
return Optional.of(new Image(stream));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,45 +282,6 @@ public class Utility {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void printStackTrace() {
|
|
||||||
System.out.println("START OF STACK TRACE:");
|
|
||||||
int skip = 2;
|
|
||||||
for (StackTraceElement s : Thread.currentThread().getStackTrace()) {
|
|
||||||
if (skip-- > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (s.getClassName().startsWith("com.sun.javafx.event")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
System.out.println(" " + s.getClassName() + "." + s.getMethodName() + " (line " + s.getLineNumber() + ") " + (s.isNativeMethod() ? "NATIVE" : ""));
|
|
||||||
}
|
|
||||||
System.out.println("END OF STACK TRACE");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int parseHexInt(Object s) {
|
|
||||||
if (s == null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (s instanceof Integer integer) {
|
|
||||||
return integer;
|
|
||||||
}
|
|
||||||
String val = String.valueOf(s).trim();
|
|
||||||
int base = 10;
|
|
||||||
if (val.startsWith("$")) {
|
|
||||||
base = 16;
|
|
||||||
val = val.contains(" ") ? val.substring(1, val.indexOf(' ')) : val.substring(1);
|
|
||||||
} else if (val.startsWith("0x")) {
|
|
||||||
base = 16;
|
|
||||||
val = val.contains(" ") ? val.substring(2, val.indexOf(' ')) : val.substring(2);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(val, base);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
gripe("This isn't a valid number: " + val + ". If you put a $ in front of that then I'll know you meant it to be a hex number.");
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void gripe(final String message) {
|
public static void gripe(final String message) {
|
||||||
gripe(message, false, null);
|
gripe(message, false, null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,10 +125,8 @@ public abstract class Video extends TimedDevice {
|
||||||
|
|
||||||
Runnable redrawScreen = () -> {
|
Runnable redrawScreen = () -> {
|
||||||
if (visible != null && video != null) {
|
if (visible != null && video != null) {
|
||||||
// if (computer.getRunningProperty().get()) {
|
screenDirty = false;
|
||||||
screenDirty = false;
|
visible.getPixelWriter().setPixels(0, 0, 560, 192, video.getPixelReader(), 0, 0);
|
||||||
visible.getPixelWriter().setPixels(0, 0, 560, 192, video.getPixelReader(), 0, 0);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ public class CardAppleMouse extends Card {
|
||||||
@Stateful
|
@Stateful
|
||||||
public int statusByte;
|
public int statusByte;
|
||||||
@Stateful
|
@Stateful
|
||||||
public Point2D lastMouseLocation;
|
public Point2D lastMouseLocation = new Point2D(0, 0);
|
||||||
@Stateful
|
@Stateful
|
||||||
public Rectangle2D clampWindow = new Rectangle2D(0, 0, 0x03ff, 0x03ff);
|
public Rectangle2D clampWindow = new Rectangle2D(0, 0, 0x03ff, 0x03ff);
|
||||||
// By default, update 60 times a second -- roughly every VBL period (in theory)
|
// By default, update 60 times a second -- roughly every VBL period (in theory)
|
||||||
|
@ -107,8 +107,16 @@ public class CardAppleMouse extends Card {
|
||||||
|
|
||||||
private void processMouseEvent(MouseEvent event) {
|
private void processMouseEvent(MouseEvent event) {
|
||||||
if (event.getEventType() == MouseEvent.MOUSE_MOVED || event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
|
if (event.getEventType() == MouseEvent.MOUSE_MOVED || event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
|
||||||
Node source = (Node) event.getSource();
|
double x = 0.0;
|
||||||
updateLocation(event.getSceneX(), event.getSceneY(), source.getBoundsInLocal());
|
double y = 0.0;
|
||||||
|
if (event.getSource() != null && event.getSource() instanceof Node) {
|
||||||
|
// This is a bit of a hack to get the mouse position in the local coordinate system of the source (the emulator screen
|
||||||
|
Node source = (Node) event.getSource();
|
||||||
|
Bounds bounds = source.getBoundsInLocal();
|
||||||
|
x=event.getSceneX() / bounds.getWidth();
|
||||||
|
y=event.getSceneY() / bounds.getHeight();
|
||||||
|
}
|
||||||
|
updateLocation(x, y);
|
||||||
event.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
if (event.getEventType() == MouseEvent.MOUSE_PRESSED || event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
|
if (event.getEventType() == MouseEvent.MOUSE_PRESSED || event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
|
||||||
|
@ -120,10 +128,8 @@ public class CardAppleMouse extends Card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLocation(double x, double y, Bounds bounds) {
|
private void updateLocation(double x, double y) {
|
||||||
double scaledX = x / bounds.getWidth();
|
lastMouseLocation = new Point2D(x, y);
|
||||||
double scaledY = y / bounds.getHeight();
|
|
||||||
lastMouseLocation = new Point2D(scaledX, scaledY);
|
|
||||||
movedSinceLastTick = true;
|
movedSinceLastTick = true;
|
||||||
movedSinceLastRead = true;
|
movedSinceLastRead = true;
|
||||||
}
|
}
|
||||||
|
@ -210,6 +216,7 @@ public class CardAppleMouse extends Card {
|
||||||
case 0x08:
|
case 0x08:
|
||||||
// Pascal signature byte
|
// Pascal signature byte
|
||||||
e.setNewValue(0x001);
|
e.setNewValue(0x001);
|
||||||
|
break;
|
||||||
case 0x011:
|
case 0x011:
|
||||||
e.setNewValue(0x000);
|
e.setNewValue(0x000);
|
||||||
break;
|
break;
|
||||||
|
@ -376,7 +383,9 @@ public class CardAppleMouse extends Card {
|
||||||
* Screen holes are updated
|
* Screen holes are updated
|
||||||
*/
|
*/
|
||||||
private void initMouse() {
|
private void initMouse() {
|
||||||
mouseActive.setText("Active");
|
if (mouseActive != null) {
|
||||||
|
mouseActive.setText("Active");
|
||||||
|
}
|
||||||
EmulatorUILogic.addIndicator(this, mouseActive, 2000);
|
EmulatorUILogic.addIndicator(this, mouseActive, 2000);
|
||||||
setClampWindowX(0, 0x3ff);
|
setClampWindowX(0, 0x3ff);
|
||||||
setClampWindowY(0, 0x3ff);
|
setClampWindowY(0, 0x3ff);
|
||||||
|
|
|
@ -84,18 +84,24 @@ public class CardDiskII extends Card implements MediaConsumerParent {
|
||||||
// Motherboard.cancelSpeedRequest(this);
|
// Motherboard.cancelSpeedRequest(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("fallthrough")
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleIOAccess(int register, RAMEvent.TYPE type, int value, RAMEvent e) {
|
protected void handleIOAccess(int register, RAMEvent.TYPE type, int value, RAMEvent e) {
|
||||||
// handle Disk ][ registers
|
// handle Disk ][ registers
|
||||||
switch (register) {
|
switch (register) {
|
||||||
case 0x0:
|
case 0x0:
|
||||||
|
// Fall-through
|
||||||
case 0x1:
|
case 0x1:
|
||||||
|
// Fall-through
|
||||||
case 0x2:
|
case 0x2:
|
||||||
|
// Fall-through
|
||||||
case 0x3:
|
case 0x3:
|
||||||
|
// Fall-through
|
||||||
case 0x4:
|
case 0x4:
|
||||||
|
// Fall-through
|
||||||
case 0x5:
|
case 0x5:
|
||||||
|
// Fall-through
|
||||||
case 0x6:
|
case 0x6:
|
||||||
|
// Fall-through
|
||||||
case 0x7:
|
case 0x7:
|
||||||
currentDrive.step(register);
|
currentDrive.step(register);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -45,6 +45,9 @@ import jace.hardware.mockingboard.R6522;
|
||||||
public class CardMockingboard extends Card {
|
public class CardMockingboard extends Card {
|
||||||
// If true, emulation will cover 4 AY chips. Otherwise, only 2 AY chips
|
// If true, emulation will cover 4 AY chips. Otherwise, only 2 AY chips
|
||||||
|
|
||||||
|
@ConfigurableField(name = "Debug", category = "Sound", description = "Enable debug output")
|
||||||
|
public static boolean DEBUG = false;
|
||||||
|
|
||||||
@ConfigurableField(name = "Volume", shortName = "vol",
|
@ConfigurableField(name = "Volume", shortName = "vol",
|
||||||
category = "Sound",
|
category = "Sound",
|
||||||
description = "Mockingboard volume, 100=max, 0=silent")
|
description = "Mockingboard volume, 100=max, 0=silent")
|
||||||
|
@ -68,7 +71,6 @@ public class CardMockingboard extends Card {
|
||||||
double ticksBetweenPlayback = 24.0;
|
double ticksBetweenPlayback = 24.0;
|
||||||
int MAX_IDLE_TICKS = 1000000;
|
int MAX_IDLE_TICKS = 1000000;
|
||||||
boolean activatedAfterReset = false;
|
boolean activatedAfterReset = false;
|
||||||
boolean debug = false;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDeviceName() {
|
public String getDeviceName() {
|
||||||
|
@ -127,6 +129,11 @@ public class CardMockingboard extends Card {
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
activatedAfterReset = false;
|
activatedAfterReset = false;
|
||||||
|
if (chips != null) {
|
||||||
|
for (PSG p : chips) {
|
||||||
|
p.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
suspend();
|
suspend();
|
||||||
}
|
}
|
||||||
RAMListener mainListener = null;
|
RAMListener mainListener = null;
|
||||||
|
@ -145,7 +152,9 @@ public class CardMockingboard extends Card {
|
||||||
chip++;
|
chip++;
|
||||||
}
|
}
|
||||||
if (chip >= 2) {
|
if (chip >= 2) {
|
||||||
System.err.println("Could not determine which PSG to communicate to for access to regsiter + " + Integer.toHexString(register));
|
if (DEBUG) {
|
||||||
|
System.err.println("Could not determine which PSG to communicate to for access to regsiter + " + Integer.toHexString(register));
|
||||||
|
}
|
||||||
Emulator.withVideo(v->e.setNewValue(v.getFloatingBus()));
|
Emulator.withVideo(v->e.setNewValue(v.getFloatingBus()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -153,24 +162,27 @@ public class CardMockingboard extends Card {
|
||||||
if (e.getType().isRead()) {
|
if (e.getType().isRead()) {
|
||||||
int val = controller.readRegister(register & 0x0f);
|
int val = controller.readRegister(register & 0x0f);
|
||||||
e.setNewValue(val);
|
e.setNewValue(val);
|
||||||
if (debug) System.out.println("Chip " + chip + " Read "+Integer.toHexString(register & 0x0f)+" == "+val);
|
if (DEBUG) System.out.println("Chip " + chip + " Read "+Integer.toHexString(register & 0x0f)+" == "+val);
|
||||||
} else {
|
} else {
|
||||||
controller.writeRegister(register & 0x0f, e.getNewValue());
|
controller.writeRegister(register & 0x0f, e.getNewValue());
|
||||||
if (debug) System.out.println("Chip " + chip + " Write "+Integer.toHexString(register & 0x0f)+" == "+e.getNewValue());
|
if (DEBUG) System.out.println("Chip " + chip + " Write "+Integer.toHexString(register & 0x0f)+" == "+e.getNewValue());
|
||||||
}
|
}
|
||||||
// Any firmware access will reset the idle counter and wake up the card, this allows the timers to start running again
|
// Any firmware access will reset the idle counter and wake up the card, this allows the timers to start running again
|
||||||
// Games such as "Skyfox" use the timer to detect if the card is present.
|
// Games such as "Skyfox" use the timer to detect if the card is present.
|
||||||
idleTicks = 0;
|
idleTicks = 0;
|
||||||
if (!isRunning() || isPaused()) {
|
if (!isRunning() || isPaused()) {
|
||||||
activatedAfterReset = true;
|
activatedAfterReset = true;
|
||||||
resume();
|
// ResumeAll is important so that the 6522's can start their timers
|
||||||
|
resumeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||||
// Oddly, all IO is done at the firmware address bank. It's a strange card.
|
// Oddly, all IO is done at the firmware address bank. It's a strange card.
|
||||||
// System.out.println("MB I/O Access "+type.name()+" "+register+":"+value);
|
if (DEBUG) {
|
||||||
|
System.out.println("MB I/O Access "+type.name()+" "+register+":"+value);
|
||||||
|
}
|
||||||
Emulator.withVideo(v->e.setNewValue(v.getFloatingBus()));
|
Emulator.withVideo(v->e.setNewValue(v.getFloatingBus()));
|
||||||
}
|
}
|
||||||
double ticksSinceLastPlayback = 0;
|
double ticksSinceLastPlayback = 0;
|
||||||
|
@ -200,20 +212,16 @@ public class CardMockingboard extends Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reconfigure() {
|
public void reconfigure() {
|
||||||
boolean isActive = isRunning();
|
if (DEBUG) {
|
||||||
initPSG();
|
System.out.println("Reconfiguring Mockingboard");
|
||||||
for (PSG chip : chips) {
|
|
||||||
chip.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SoundMixer.RATE);
|
|
||||||
chip.reset();
|
|
||||||
}
|
}
|
||||||
ticksBetweenPlayback = (double) CLOCK_SPEED / (double) SoundMixer.RATE;
|
ticksBetweenPlayback = (double) CLOCK_SPEED / (double) SoundMixer.RATE;
|
||||||
buildMixerTable();
|
initPSG();
|
||||||
|
|
||||||
super.reconfigure();
|
super.reconfigure();
|
||||||
if (isActive) {
|
if (DEBUG) {
|
||||||
resume();
|
System.out.println("Reconfiguring Mockingboard completed");
|
||||||
}
|
}
|
||||||
super.reconfigure();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////
|
||||||
|
@ -222,6 +230,10 @@ public class CardMockingboard extends Card {
|
||||||
AtomicInteger left = new AtomicInteger(0);
|
AtomicInteger left = new AtomicInteger(0);
|
||||||
AtomicInteger right = new AtomicInteger(0);
|
AtomicInteger right = new AtomicInteger(0);
|
||||||
public boolean playSound() throws InterruptedException, ExecutionException, SoundError {
|
public boolean playSound() throws InterruptedException, ExecutionException, SoundError {
|
||||||
|
if (phasorMode && chips.length != 4) {
|
||||||
|
System.err.println("Wrong number of chips for phasor mode, correcting this");
|
||||||
|
initPSG();
|
||||||
|
}
|
||||||
chips[0].update(left, true, left, false, left, false);
|
chips[0].update(left, true, left, false, left, false);
|
||||||
chips[1].update(right, true, right, false, right, false);
|
chips[1].update(right, true, right, false, right, false);
|
||||||
if (phasorMode) {
|
if (phasorMode) {
|
||||||
|
@ -262,11 +274,22 @@ public class CardMockingboard extends Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resume() {
|
public void resume() {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Resuming Mockingboard");
|
||||||
|
Thread.dumpStack();
|
||||||
|
}
|
||||||
if (!activatedAfterReset) {
|
if (!activatedAfterReset) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Resuming Mockingboard: not activated after reset, not resuming");
|
||||||
|
}
|
||||||
// Do not re-activate until firmware access was made
|
// Do not re-activate until firmware access was made
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
initPSG();
|
||||||
if (buffer == null || !buffer.isAlive()) {
|
if (buffer == null || !buffer.isAlive()) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Resuming Mockingboard: creating sound buffer");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
buffer = SoundMixer.createBuffer(true);
|
buffer = SoundMixer.createBuffer(true);
|
||||||
} catch (InterruptedException | ExecutionException | SoundError e) {
|
} catch (InterruptedException | ExecutionException | SoundError e) {
|
||||||
|
@ -275,21 +298,18 @@ public class CardMockingboard extends Card {
|
||||||
suspend();
|
suspend();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chips == null) {
|
|
||||||
initPSG();
|
|
||||||
for (PSG psg : chips) {
|
|
||||||
psg.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SoundMixer.RATE);
|
|
||||||
psg.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
idleTicks = 0;
|
idleTicks = 0;
|
||||||
setPaused(false);
|
|
||||||
reconfigure();
|
|
||||||
super.resume();
|
super.resume();
|
||||||
|
System.out.println("Resuming Mockingboard: resume completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean suspend() {
|
public boolean suspend() {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Suspending Mockingboard");
|
||||||
|
Thread.dumpStack();
|
||||||
|
}
|
||||||
|
|
||||||
if (buffer != null) {
|
if (buffer != null) {
|
||||||
try {
|
try {
|
||||||
buffer.shutdown();
|
buffer.shutdown();
|
||||||
|
@ -300,21 +320,28 @@ public class CardMockingboard extends Card {
|
||||||
buffer = null;
|
buffer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (R6522 c : controllers) {
|
||||||
|
c.suspend();
|
||||||
|
}
|
||||||
return super.suspend();
|
return super.suspend();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPSG() {
|
private void initPSG() {
|
||||||
if (phasorMode) {
|
if (phasorMode && (chips == null || chips.length < 4)) {
|
||||||
chips = new PSG[4];
|
chips = new PSG[4];
|
||||||
chips[0] = new PSG(0x10, CLOCK_SPEED * 2, SoundMixer.RATE, "AY1", 8);
|
chips[0] = new PSG(0x10, CLOCK_SPEED * 2, SoundMixer.RATE, "AY1", 8);
|
||||||
chips[1] = new PSG(0x80, CLOCK_SPEED * 2, SoundMixer.RATE, "AY2", 8);
|
chips[1] = new PSG(0x80, CLOCK_SPEED * 2, SoundMixer.RATE, "AY2", 8);
|
||||||
chips[2] = new PSG(0x10, CLOCK_SPEED * 2, SoundMixer.RATE, "AY3", 16);
|
chips[2] = new PSG(0x10, CLOCK_SPEED * 2, SoundMixer.RATE, "AY3", 16);
|
||||||
chips[3] = new PSG(0x80, CLOCK_SPEED * 2, SoundMixer.RATE, "AY4", 16);
|
chips[3] = new PSG(0x80, CLOCK_SPEED * 2, SoundMixer.RATE, "AY4", 16);
|
||||||
} else {
|
} else if (chips == null || chips.length != 2) {
|
||||||
chips = new PSG[2];
|
chips = new PSG[2];
|
||||||
chips[0] = new PSG(0, CLOCK_SPEED, SoundMixer.RATE, "AY1", 255);
|
chips[0] = new PSG(0, CLOCK_SPEED, SoundMixer.RATE, "AY1", 255);
|
||||||
chips[1] = new PSG(0x80, CLOCK_SPEED, SoundMixer.RATE, "AY2", 255);
|
chips[1] = new PSG(0x80, CLOCK_SPEED, SoundMixer.RATE, "AY2", 255);
|
||||||
}
|
}
|
||||||
|
for (PSG psg : chips) {
|
||||||
|
psg.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SoundMixer.RATE);
|
||||||
|
}
|
||||||
|
buildMixerTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -54,8 +54,6 @@ public class CardSSC extends Card {
|
||||||
protected Thread listenThread;
|
protected Thread listenThread;
|
||||||
private int lastInputByte = 0;
|
private int lastInputByte = 0;
|
||||||
private boolean FULL_ECHO = true;
|
private boolean FULL_ECHO = true;
|
||||||
private final boolean RECV_ACTIVE = true;
|
|
||||||
private boolean TRANS_ACTIVE = true;
|
|
||||||
// private boolean RECV_STRIP_LF = true;
|
// private boolean RECV_STRIP_LF = true;
|
||||||
// private boolean TRANS_ADD_LF = true;
|
// private boolean TRANS_ADD_LF = true;
|
||||||
@ConfigurableField(category = "Advanced", name = "Liveness check interval", description = "How often the connection is polled for signs of life when idle (in milliseconds)")
|
@ConfigurableField(category = "Advanced", name = "Liveness check interval", description = "How often the connection is polled for signs of life when idle (in milliseconds)")
|
||||||
|
@ -65,7 +63,7 @@ public class CardSSC extends Card {
|
||||||
@ConfigurableField(name = "Add LF (send)", shortName = "addLF", defaultValue = "false", description = "Append linefeeds after outgoing carriage returns")
|
@ConfigurableField(name = "Add LF (send)", shortName = "addLF", defaultValue = "false", description = "Append linefeeds after outgoing carriage returns")
|
||||||
public boolean TRANS_ADD_LF = false;
|
public boolean TRANS_ADD_LF = false;
|
||||||
private boolean DTR = true;
|
private boolean DTR = true;
|
||||||
public int SW1 = 0x01; // Read = Jumper block SW1
|
public static int SW1 = 0x01; // Read = Jumper block SW1
|
||||||
//Bit 0 = !SW1-6
|
//Bit 0 = !SW1-6
|
||||||
//Bit 1 = !SW1-5
|
//Bit 1 = !SW1-5
|
||||||
//Bit 4 = !SW1-4
|
//Bit 4 = !SW1-4
|
||||||
|
@ -75,7 +73,7 @@ public class CardSSC extends Card {
|
||||||
// 19200 baud (SW1-1,2,3,4 off)
|
// 19200 baud (SW1-1,2,3,4 off)
|
||||||
// Communications mode (SW1-5,6 on)
|
// Communications mode (SW1-5,6 on)
|
||||||
public int SW1_SETTING = 0x0F0;
|
public int SW1_SETTING = 0x0F0;
|
||||||
public int SW2_CTS = 0x02; // Read = Jumper block SW2 and CTS
|
public static int SW2_CTS = 0x02; // Read = Jumper block SW2 and CTS
|
||||||
//Bit 0 = !CTS
|
//Bit 0 = !CTS
|
||||||
//SW2-6 = Allow interrupts (disable in ][, ][+)
|
//SW2-6 = Allow interrupts (disable in ][, ][+)
|
||||||
//Bit 1 = !SW2-5 -- Generate LF after CR
|
//Bit 1 = !SW2-5 -- Generate LF after CR
|
||||||
|
@ -87,10 +85,10 @@ public class CardSSC extends Card {
|
||||||
// 8 data bits (SW2-2 on)
|
// 8 data bits (SW2-2 on)
|
||||||
// No parity (SW2-3 don't care, SW2-4 off)
|
// No parity (SW2-3 don't care, SW2-4 off)
|
||||||
private final int SW2_SETTING = 0x04;
|
private final int SW2_SETTING = 0x04;
|
||||||
public int ACIA_Data = 0x08; // Read=Receive / Write=transmit
|
public static int ACIA_Data = 0x08; // Read=Receive / Write=transmit
|
||||||
public int ACIA_Status = 0x09; // Read=Status / Write=Reset
|
public static int ACIA_Status = 0x09; // Read=Status / Write=Reset
|
||||||
public int ACIA_Command = 0x0A;
|
public static int ACIA_Command = 0x0A;
|
||||||
public int ACIA_Control = 0x0B;
|
public static int ACIA_Control = 0x0B;
|
||||||
public boolean PORT_CONNECTED = false;
|
public boolean PORT_CONNECTED = false;
|
||||||
public boolean RECV_IRQ_ENABLED = false;
|
public boolean RECV_IRQ_ENABLED = false;
|
||||||
public boolean TRANS_IRQ_ENABLED = false;
|
public boolean TRANS_IRQ_ENABLED = false;
|
||||||
|
@ -296,19 +294,15 @@ public class CardSSC extends Card {
|
||||||
switch ((value >> 2) & 3) {
|
switch ((value >> 2) & 3) {
|
||||||
case 0:
|
case 0:
|
||||||
TRANS_IRQ_ENABLED = false;
|
TRANS_IRQ_ENABLED = false;
|
||||||
TRANS_ACTIVE = false;
|
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
TRANS_IRQ_ENABLED = true;
|
TRANS_IRQ_ENABLED = true;
|
||||||
TRANS_ACTIVE = true;
|
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
TRANS_IRQ_ENABLED = false;
|
TRANS_IRQ_ENABLED = false;
|
||||||
TRANS_ACTIVE = true;
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
TRANS_IRQ_ENABLED = false;
|
TRANS_IRQ_ENABLED = false;
|
||||||
TRANS_ACTIVE = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// 4 = Normal mode 0, or Echo mode 1 (bits 2 and 3 must be 0)
|
// 4 = Normal mode 0, or Echo mode 1 (bits 2 and 3 must be 0)
|
||||||
|
@ -397,16 +391,17 @@ public class CardSSC extends Card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCTS(boolean b) throws InterruptedException {
|
// CTS isn't used here -- it's assumed that we're always clear-to-send
|
||||||
PORT_CONNECTED = b;
|
// private void setCTS(boolean b) throws InterruptedException {
|
||||||
if (b == false) {
|
// PORT_CONNECTED = b;
|
||||||
reset();
|
// if (b == false) {
|
||||||
}
|
// reset();
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
private boolean getCTS() throws InterruptedException {
|
// private boolean getCTS() throws InterruptedException {
|
||||||
return PORT_CONNECTED;
|
// return PORT_CONNECTED;
|
||||||
}
|
// }
|
||||||
|
|
||||||
private void triggerIRQ() {
|
private void triggerIRQ() {
|
||||||
IRQ_TRIGGERED = true;
|
IRQ_TRIGGERED = true;
|
||||||
|
|
|
@ -7,13 +7,14 @@ import jace.core.Card;
|
||||||
import jace.hardware.massStorage.CardMassStorage;
|
import jace.hardware.massStorage.CardMassStorage;
|
||||||
|
|
||||||
public enum Cards implements DeviceEnum<Card> {
|
public enum Cards implements DeviceEnum<Card> {
|
||||||
DiskIIDrive("Disk II Floppy Controller", CardDiskII.class, CardDiskII::new),
|
|
||||||
MassStorage("Mass Storage", CardMassStorage.class, CardMassStorage::new),
|
|
||||||
AppleMouse("Apple Mouse", CardAppleMouse.class, CardAppleMouse::new),
|
AppleMouse("Apple Mouse", CardAppleMouse.class, CardAppleMouse::new),
|
||||||
|
DiskIIDrive("Disk II Floppy Controller", CardDiskII.class, CardDiskII::new),
|
||||||
HayesMicroModem("Hayes MicroModem", CardHayesMicromodem.class, CardHayesMicromodem::new),
|
HayesMicroModem("Hayes MicroModem", CardHayesMicromodem.class, CardHayesMicromodem::new),
|
||||||
|
MassStorage("Mass Storage", CardMassStorage.class, CardMassStorage::new),
|
||||||
Mockingboard("Mockingboard", CardMockingboard.class, CardMockingboard::new),
|
Mockingboard("Mockingboard", CardMockingboard.class, CardMockingboard::new),
|
||||||
SuperSerialCard("Super Serial Card", CardSSC.class, CardSSC::new),
|
PassportMidi("Passport MIDI", PassportMidiInterface.class, PassportMidiInterface::new),
|
||||||
RamFactor("RamFactor", CardRamFactor.class, CardRamFactor::new),
|
RamFactor("RamFactor", CardRamFactor.class, CardRamFactor::new),
|
||||||
|
SuperSerialCard("Super Serial Card", CardSSC.class, CardSSC::new),
|
||||||
Thunderclock("Thunderclock", CardThunderclock.class, CardThunderclock::new);
|
Thunderclock("Thunderclock", CardThunderclock.class, CardThunderclock::new);
|
||||||
|
|
||||||
Supplier<Card> factory;
|
Supplier<Card> factory;
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
/**
|
/**
|
||||||
* Copyright 2024 Brendan Robert
|
* Copyright 2024 Brendan Robert
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
* */
|
||||||
|
|
||||||
package jace.hardware;
|
package jace.hardware;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -42,7 +41,7 @@ import jace.core.RAMEvent.TYPE;
|
||||||
* operational notes taken from the Passport MIDI interface manual
|
* operational notes taken from the Passport MIDI interface manual
|
||||||
* ftp://ftp.apple.asimov.net/pub/apple_II/documentation/hardware/misc/passport_midi.pdf
|
* ftp://ftp.apple.asimov.net/pub/apple_II/documentation/hardware/misc/passport_midi.pdf
|
||||||
*
|
*
|
||||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||||
*/
|
*/
|
||||||
@Name(value = "Passport Midi Interface", description = "MIDI sound card")
|
@Name(value = "Passport Midi Interface", description = "MIDI sound card")
|
||||||
public class PassportMidiInterface extends Card {
|
public class PassportMidiInterface extends Card {
|
||||||
|
@ -78,8 +77,9 @@ public class PassportMidiInterface extends Card {
|
||||||
for (MidiDevice.Info dev : devices) {
|
for (MidiDevice.Info dev : devices) {
|
||||||
try {
|
try {
|
||||||
MidiDevice device = MidiSystem.getMidiDevice(dev);
|
MidiDevice device = MidiSystem.getMidiDevice(dev);
|
||||||
if (device.getMaxReceivers() > 0 || dev instanceof Synthesizer)
|
if (device.getMaxReceivers() > 0 || dev instanceof Synthesizer) {
|
||||||
System.out.println("MIDI Device found: " + dev);
|
System.out.println("MIDI Device found: " + dev);
|
||||||
|
}
|
||||||
out.put(dev.getName(), dev.getName());
|
out.put(dev.getName(), dev.getName());
|
||||||
} catch (MidiUnavailableException ex) {
|
} catch (MidiUnavailableException ex) {
|
||||||
Logger.getLogger(PassportMidiInterface.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(PassportMidiInterface.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
@ -202,13 +202,11 @@ public class PassportMidiInterface extends Card {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
// TODO: Deactivate card
|
|
||||||
suspend();
|
suspend();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean suspend() {
|
public boolean suspend() {
|
||||||
// TODO: Deactivate card
|
|
||||||
suspendACIA();
|
suspendACIA();
|
||||||
return super.suspend();
|
return super.suspend();
|
||||||
}
|
}
|
||||||
|
@ -221,77 +219,70 @@ public class PassportMidiInterface extends Card {
|
||||||
@Override
|
@Override
|
||||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case READ_DATA:
|
case READ_DATA -> {
|
||||||
int returnValue = 0;
|
int returnValue = 0;
|
||||||
switch (register) {
|
switch (register) {
|
||||||
case ACIA_STATUS:
|
case ACIA_STATUS ->
|
||||||
returnValue = getACIAStatus();
|
returnValue = getACIAStatus();
|
||||||
break;
|
case ACIA_RECV ->
|
||||||
case ACIA_RECV:
|
|
||||||
returnValue = getACIARecieve();
|
returnValue = getACIARecieve();
|
||||||
break;
|
case TIMER_CONTROL_1 -> // Technically it's not supposed to return anything...
|
||||||
//TODO: Implement PTM registers
|
|
||||||
case TIMER_CONTROL_1:
|
|
||||||
// Technically it's not supposed to return anything...
|
|
||||||
returnValue = getPTMStatus();
|
returnValue = getPTMStatus();
|
||||||
break;
|
case TIMER_CONTROL_2 ->
|
||||||
case TIMER_CONTROL_2:
|
|
||||||
returnValue = getPTMStatus();
|
returnValue = getPTMStatus();
|
||||||
break;
|
case TIMER1_LSB -> {
|
||||||
case TIMER1_LSB:
|
|
||||||
returnValue = (int) (ptmTimer[0].value & 0x0ff);
|
returnValue = (int) (ptmTimer[0].value & 0x0ff);
|
||||||
if (ptmStatusReadSinceIRQ) {
|
if (ptmStatusReadSinceIRQ) {
|
||||||
ptmTimer[0].irqRequested = false;
|
ptmTimer[0].irqRequested = false;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TIMER1_MSB:
|
case TIMER1_MSB -> {
|
||||||
returnValue = (int) (ptmTimer[0].value >> 8) & 0x0ff;
|
returnValue = (int) (ptmTimer[0].value >> 8) & 0x0ff;
|
||||||
if (ptmStatusReadSinceIRQ) {
|
if (ptmStatusReadSinceIRQ) {
|
||||||
ptmTimer[0].irqRequested = false;
|
ptmTimer[0].irqRequested = false;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TIMER2_LSB:
|
case TIMER2_LSB -> {
|
||||||
returnValue = (int) (ptmTimer[1].value & 0x0ff);
|
returnValue = (int) (ptmTimer[1].value & 0x0ff);
|
||||||
if (ptmStatusReadSinceIRQ) {
|
if (ptmStatusReadSinceIRQ) {
|
||||||
ptmTimer[1].irqRequested = false;
|
ptmTimer[1].irqRequested = false;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TIMER2_MSB:
|
case TIMER2_MSB -> {
|
||||||
returnValue = (int) (ptmTimer[1].value >> 8) & 0x0ff;
|
returnValue = (int) (ptmTimer[1].value >> 8) & 0x0ff;
|
||||||
if (ptmStatusReadSinceIRQ) {
|
if (ptmStatusReadSinceIRQ) {
|
||||||
ptmTimer[1].irqRequested = false;
|
ptmTimer[1].irqRequested = false;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TIMER3_LSB:
|
case TIMER3_LSB -> {
|
||||||
returnValue = (int) (ptmTimer[2].value & 0x0ff);
|
returnValue = (int) (ptmTimer[2].value & 0x0ff);
|
||||||
if (ptmStatusReadSinceIRQ) {
|
if (ptmStatusReadSinceIRQ) {
|
||||||
ptmTimer[2].irqRequested = false;
|
ptmTimer[2].irqRequested = false;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TIMER3_MSB:
|
case TIMER3_MSB -> {
|
||||||
returnValue = (int) (ptmTimer[2].value >> 8) & 0x0ff;
|
returnValue = (int) (ptmTimer[2].value >> 8) & 0x0ff;
|
||||||
if (ptmStatusReadSinceIRQ) {
|
if (ptmStatusReadSinceIRQ) {
|
||||||
ptmTimer[2].irqRequested = false;
|
ptmTimer[2].irqRequested = false;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
default:
|
default ->
|
||||||
System.out.println("Passport midi read unrecognized, port " + register);
|
System.out.println("Passport midi read unrecognized, port " + register);
|
||||||
}
|
}
|
||||||
|
//TODO: Implement PTM registers
|
||||||
e.setNewValue(returnValue);
|
e.setNewValue(returnValue);
|
||||||
// System.out.println("Passport I/O read register " + register + " == " + returnValue);
|
// System.out.println("Passport I/O read register " + register + " == " + returnValue);
|
||||||
break;
|
}
|
||||||
case WRITE:
|
|
||||||
|
case WRITE -> {
|
||||||
int v = e.getNewValue() & 0x0ff;
|
int v = e.getNewValue() & 0x0ff;
|
||||||
// System.out.println("Passport I/O write register " + register + " == " + v);
|
// System.out.println("Passport I/O write register " + register + " == " + v);
|
||||||
switch (register) {
|
switch (register) {
|
||||||
case ACIA_CONTROL:
|
case ACIA_CONTROL ->
|
||||||
processACIAControl(v);
|
processACIAControl(v);
|
||||||
break;
|
case ACIA_SEND ->
|
||||||
case ACIA_SEND:
|
|
||||||
processACIASend(v);
|
processACIASend(v);
|
||||||
break;
|
case TIMER_CONTROL_1 -> {
|
||||||
case TIMER_CONTROL_1:
|
|
||||||
if (ptmTimer3Selected) {
|
if (ptmTimer3Selected) {
|
||||||
// System.out.println("Configuring timer 3");
|
// System.out.println("Configuring timer 3");
|
||||||
ptmTimer[2].prescaledTimer = ((v & TIMER3_PRESCALED) != 0);
|
ptmTimer[2].prescaledTimer = ((v & TIMER3_PRESCALED) != 0);
|
||||||
|
@ -305,38 +296,32 @@ public class PassportMidiInterface extends Card {
|
||||||
}
|
}
|
||||||
processPTMConfiguration(ptmTimer[0], v);
|
processPTMConfiguration(ptmTimer[0], v);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case TIMER_CONTROL_2:
|
case TIMER_CONTROL_2 -> {
|
||||||
// System.out.println("Configuring timer 2");
|
// System.out.println("Configuring timer 2");
|
||||||
ptmTimer3Selected = ((v & PTM_SELECT_REG_1) == 0);
|
ptmTimer3Selected = ((v & PTM_SELECT_REG_1) == 0);
|
||||||
processPTMConfiguration(ptmTimer[1], v);
|
processPTMConfiguration(ptmTimer[1], v);
|
||||||
break;
|
}
|
||||||
case TIMER1_LSB:
|
case TIMER1_LSB ->
|
||||||
ptmTimer[0].duration = (ptmTimer[0].duration & 0x0ff00) | v;
|
ptmTimer[0].duration = (ptmTimer[0].duration & 0x0ff00) | v;
|
||||||
break;
|
case TIMER1_MSB ->
|
||||||
case TIMER1_MSB:
|
|
||||||
ptmTimer[0].duration = (ptmTimer[0].duration & 0x0ff) | (v << 8);
|
ptmTimer[0].duration = (ptmTimer[0].duration & 0x0ff) | (v << 8);
|
||||||
break;
|
case TIMER2_LSB ->
|
||||||
case TIMER2_LSB:
|
|
||||||
ptmTimer[1].duration = (ptmTimer[1].duration & 0x0ff00) | v;
|
ptmTimer[1].duration = (ptmTimer[1].duration & 0x0ff00) | v;
|
||||||
break;
|
case TIMER2_MSB ->
|
||||||
case TIMER2_MSB:
|
|
||||||
ptmTimer[1].duration = (ptmTimer[1].duration & 0x0ff) | (v << 8);
|
ptmTimer[1].duration = (ptmTimer[1].duration & 0x0ff) | (v << 8);
|
||||||
break;
|
case TIMER3_LSB ->
|
||||||
case TIMER3_LSB:
|
|
||||||
ptmTimer[2].duration = (ptmTimer[2].duration & 0x0ff00) | v;
|
ptmTimer[2].duration = (ptmTimer[2].duration & 0x0ff00) | v;
|
||||||
break;
|
case TIMER3_MSB ->
|
||||||
case TIMER3_MSB:
|
|
||||||
ptmTimer[2].duration = (ptmTimer[2].duration & 0x0ff00) | (v << 8);
|
ptmTimer[2].duration = (ptmTimer[2].duration & 0x0ff00) | (v << 8);
|
||||||
break;
|
default ->
|
||||||
default:
|
|
||||||
System.out.println("Passport midi write unrecognized, port " + register);
|
System.out.println("Passport midi write unrecognized, port " + register);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
default -> {
|
||||||
default:
|
}
|
||||||
// Nothing
|
|
||||||
}
|
}
|
||||||
|
// Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -352,7 +337,7 @@ public class PassportMidiInterface extends Card {
|
||||||
if (t.irqEnabled) {
|
if (t.irqEnabled) {
|
||||||
// System.out.println("Timer generating interrupt!");
|
// System.out.println("Timer generating interrupt!");
|
||||||
t.irqRequested = true;
|
t.irqRequested = true;
|
||||||
Emulator.withComputer(c->c.getCpu().generateInterrupt());
|
Emulator.withComputer(c -> c.getCpu().generateInterrupt());
|
||||||
ptmStatusReadSinceIRQ = false;
|
ptmStatusReadSinceIRQ = false;
|
||||||
}
|
}
|
||||||
if (t.mode == TIMER_MODE.CONTINUOUS || t.mode == TIMER_MODE.FREQ_COMPARISON) {
|
if (t.mode == TIMER_MODE.CONTINUOUS || t.mode == TIMER_MODE.FREQ_COMPARISON) {
|
||||||
|
@ -425,6 +410,7 @@ public class PassportMidiInterface extends Card {
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------ ACIA
|
//------------------------------------------------------ ACIA
|
||||||
/*
|
/*
|
||||||
ACIA status register
|
ACIA status register
|
||||||
|
@ -554,9 +540,8 @@ public class PassportMidiInterface extends Card {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
System.out.println("MIDI Device found: " + dev);
|
System.out.println("MIDI Device found: " + dev);
|
||||||
if ((preferredMidiDevice.getValue() == null && dev.getName().contains("Java Sound") && dev instanceof Synthesizer) ||
|
if ((preferredMidiDevice.getValue() == null && dev.getName().contains("Java Sound") && dev instanceof Synthesizer)
|
||||||
preferredMidiDevice.getValue().equalsIgnoreCase(dev.getName())
|
|| preferredMidiDevice.getValue().equalsIgnoreCase(dev.getName())) {
|
||||||
) {
|
|
||||||
selectedDevice = MidiSystem.getMidiDevice(dev);
|
selectedDevice = MidiSystem.getMidiDevice(dev);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -575,7 +560,6 @@ public class PassportMidiInterface extends Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void suspendACIA() {
|
private void suspendACIA() {
|
||||||
// TODO: Stop ACIA thread...
|
|
||||||
if (midiOut != null) {
|
if (midiOut != null) {
|
||||||
currentMessage = new ShortMessage();
|
currentMessage = new ShortMessage();
|
||||||
// Send a note-off on every channel
|
// Send a note-off on every channel
|
||||||
|
@ -583,10 +567,10 @@ public class PassportMidiInterface extends Card {
|
||||||
try {
|
try {
|
||||||
// All Notes Off
|
// All Notes Off
|
||||||
currentMessage.setMessage(0x0B0 | channel, 123, 0);
|
currentMessage.setMessage(0x0B0 | channel, 123, 0);
|
||||||
midiOut.send(currentMessage, 0);
|
midiOut.send(currentMessage, 0);
|
||||||
// All Oscillators Off
|
// All Oscillators Off
|
||||||
currentMessage.setMessage(0x0B0 | channel, 120, 0);
|
currentMessage.setMessage(0x0B0 | channel, 120, 0);
|
||||||
midiOut.send(currentMessage, 0);
|
midiOut.send(currentMessage, 0);
|
||||||
} catch (InvalidMidiDataException ex) {
|
} catch (InvalidMidiDataException ex) {
|
||||||
Logger.getLogger(PassportMidiInterface.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(PassportMidiInterface.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ package jace.hardware;
|
||||||
import jace.Emulator;
|
import jace.Emulator;
|
||||||
import jace.config.ConfigurableField;
|
import jace.config.ConfigurableField;
|
||||||
import jace.core.Device;
|
import jace.core.Device;
|
||||||
import jace.core.Motherboard;
|
|
||||||
import jace.core.RAMEvent;
|
import jace.core.RAMEvent;
|
||||||
import jace.core.RAMListener;
|
import jace.core.RAMListener;
|
||||||
|
|
||||||
|
@ -175,14 +174,12 @@ public class ZipWarpAccelerator extends Device {
|
||||||
private void setSpeed(SPEED speed) {
|
private void setSpeed(SPEED speed) {
|
||||||
speedValue = speed.val;
|
speedValue = speed.val;
|
||||||
Emulator.withComputer(c -> {
|
Emulator.withComputer(c -> {
|
||||||
// if (speed.max) {
|
if (speed.max) {
|
||||||
// c.getMotherboard().setMaxSpeed(true);
|
c.getMotherboard().setMaxSpeed(true);
|
||||||
// Motherboard.cpuPerClock = 3;
|
} else {
|
||||||
// } else {
|
|
||||||
c.getMotherboard().setMaxSpeed(false);
|
c.getMotherboard().setMaxSpeed(false);
|
||||||
c.getMotherboard().setSpeedInPercentage((int) (speed.ratio * 100));
|
c.getMotherboard().setSpeedInPercentage((int) (speed.ratio * 100));
|
||||||
Motherboard.cpuPerClock = 1;
|
}
|
||||||
// }
|
|
||||||
c.getMotherboard().reconfigure();
|
c.getMotherboard().reconfigure();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,16 +99,12 @@ public class PSG {
|
||||||
List<SoundGenerator> channels;
|
List<SoundGenerator> channels;
|
||||||
EnvelopeGenerator envelopeGenerator;
|
EnvelopeGenerator envelopeGenerator;
|
||||||
NoiseGenerator noiseGenerator;
|
NoiseGenerator noiseGenerator;
|
||||||
int CLOCK;
|
|
||||||
int SAMPLE_RATE;
|
|
||||||
public int bus;
|
public int bus;
|
||||||
int selectedReg;
|
int selectedReg;
|
||||||
String name;
|
|
||||||
Map<Reg, Integer> regValues;
|
Map<Reg, Integer> regValues;
|
||||||
public int mask;
|
public int mask;
|
||||||
|
|
||||||
public PSG(int base, int clock, int sample_rate, String name, int DDR_Mask) {
|
public PSG(int base, int clock, int sample_rate, String name, int DDR_Mask) {
|
||||||
this.name = name;
|
|
||||||
this.mask = DDR_Mask;
|
this.mask = DDR_Mask;
|
||||||
baseReg = base;
|
baseReg = base;
|
||||||
channels = new ArrayList<>();
|
channels = new ArrayList<>();
|
||||||
|
@ -117,7 +113,7 @@ public class PSG {
|
||||||
}
|
}
|
||||||
envelopeGenerator = new EnvelopeGenerator(clock, sample_rate);
|
envelopeGenerator = new EnvelopeGenerator(clock, sample_rate);
|
||||||
noiseGenerator = new NoiseGenerator(clock, sample_rate);
|
noiseGenerator = new NoiseGenerator(clock, sample_rate);
|
||||||
regValues = Collections.synchronizedMap(new EnumMap<Reg, Integer>(Reg.class));
|
regValues = Collections.synchronizedMap(new EnumMap<>(Reg.class));
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,20 +128,20 @@ public class PSG {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case inactive:
|
case inactive -> {
|
||||||
break;
|
}
|
||||||
case latch:
|
case latch -> {
|
||||||
if (debug) System.out.println("PSG latched register "+selectedReg);
|
if (debug) System.out.println("PSG latched register "+selectedReg);
|
||||||
selectedReg = bus & 0x0f;
|
selectedReg = bus & 0x0f;
|
||||||
break;
|
}
|
||||||
case read:
|
case read -> {
|
||||||
bus = getReg(Reg.get(selectedReg));
|
bus = getReg(Reg.get(selectedReg));
|
||||||
if (debug) System.out.println("PSG read register "+selectedReg + " == "+bus);
|
if (debug) System.out.println("PSG read register "+selectedReg + " == "+bus);
|
||||||
break;
|
}
|
||||||
case write:
|
case write -> {
|
||||||
if (debug) System.out.println("PSG wrote register "+selectedReg + " == "+bus);
|
if (debug) System.out.println("PSG wrote register "+selectedReg + " == "+bus);
|
||||||
setReg(Reg.get(selectedReg), bus);
|
setReg(Reg.get(selectedReg), bus);
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,14 +150,11 @@ public class PSG {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRate(int clock, int sample_rate) {
|
public void setRate(int clock, int sample_rate) {
|
||||||
CLOCK = clock;
|
|
||||||
SAMPLE_RATE = sample_rate;
|
|
||||||
channels.stream().forEach((c) -> {
|
channels.stream().forEach((c) -> {
|
||||||
c.setRate(clock, sample_rate);
|
c.setRate(clock, sample_rate);
|
||||||
});
|
});
|
||||||
envelopeGenerator.setRate(clock, sample_rate);
|
envelopeGenerator.setRate(clock, sample_rate);
|
||||||
noiseGenerator.setRate(clock, sample_rate);
|
noiseGenerator.setRate(clock, sample_rate);
|
||||||
reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void reset() {
|
public final void reset() {
|
||||||
|
@ -208,50 +201,29 @@ public class PSG {
|
||||||
/* period. In that case, period = 0 is half as period = 1. */
|
/* period. In that case, period = 0 is half as period = 1. */
|
||||||
value = value & 0x0ff;
|
value = value & 0x0ff;
|
||||||
switch (r) {
|
switch (r) {
|
||||||
case ACoarse:
|
case ACoarse, AFine -> channels.get(0).setPeriod(getReg(Reg.AFine) + (getReg(Reg.ACoarse) << 8));
|
||||||
case AFine:
|
case BCoarse, BFine -> channels.get(1).setPeriod(getReg(Reg.BFine) + (getReg(Reg.BCoarse) << 8));
|
||||||
channels.get(0).setPeriod(getReg(Reg.AFine) + (getReg(Reg.ACoarse) << 8));
|
case CCoarse, CFine -> channels.get(2).setPeriod(getReg(Reg.CFine) + (getReg(Reg.CCoarse) << 8));
|
||||||
break;
|
case NoisePeriod -> {
|
||||||
case BCoarse:
|
|
||||||
case BFine:
|
|
||||||
channels.get(1).setPeriod(getReg(Reg.BFine) + (getReg(Reg.BCoarse) << 8));
|
|
||||||
break;
|
|
||||||
case CCoarse:
|
|
||||||
case CFine:
|
|
||||||
channels.get(2).setPeriod(getReg(Reg.CFine) + (getReg(Reg.CCoarse) << 8));
|
|
||||||
break;
|
|
||||||
case NoisePeriod:
|
|
||||||
if (value == 0) value = 32;
|
if (value == 0) value = 32;
|
||||||
noiseGenerator.setPeriod(value+16);
|
noiseGenerator.setPeriod(value+16);
|
||||||
noiseGenerator.counter = 0;
|
noiseGenerator.counter = 0;
|
||||||
break;
|
}
|
||||||
case Enable:
|
case Enable -> {
|
||||||
channels.get(0).setActive((value & 1) == 0);
|
channels.get(0).setActive((value & 1) == 0);
|
||||||
channels.get(0).setNoiseActive((value & 8) == 0);
|
channels.get(0).setNoiseActive((value & 8) == 0);
|
||||||
channels.get(1).setActive((value & 2) == 0);
|
channels.get(1).setActive((value & 2) == 0);
|
||||||
channels.get(1).setNoiseActive((value & 16) == 0);
|
channels.get(1).setNoiseActive((value & 16) == 0);
|
||||||
channels.get(2).setActive((value & 4) == 0);
|
channels.get(2).setActive((value & 4) == 0);
|
||||||
channels.get(2).setNoiseActive((value & 32) == 0);
|
channels.get(2).setNoiseActive((value & 32) == 0);
|
||||||
break;
|
}
|
||||||
case AVol:
|
case AVol -> channels.get(0).setAmplitude(value);
|
||||||
channels.get(0).setAmplitude(value);
|
case BVol -> channels.get(1).setAmplitude(value);
|
||||||
break;
|
case CVol -> channels.get(2).setAmplitude(value);
|
||||||
case BVol:
|
case EnvFine, EnvCoarse -> envelopeGenerator.setPeriod(getReg(Reg.EnvFine) + 256 * getReg(Reg.EnvCoarse));
|
||||||
channels.get(1).setAmplitude(value);
|
case EnvShape -> envelopeGenerator.setShape(value);
|
||||||
break;
|
case PortA, PortB -> {
|
||||||
case CVol:
|
}
|
||||||
channels.get(2).setAmplitude(value);
|
|
||||||
break;
|
|
||||||
case EnvFine:
|
|
||||||
case EnvCoarse:
|
|
||||||
envelopeGenerator.setPeriod(getReg(Reg.EnvFine) + 256 * getReg(Reg.EnvCoarse));
|
|
||||||
break;
|
|
||||||
case EnvShape:
|
|
||||||
envelopeGenerator.setShape(value);
|
|
||||||
break;
|
|
||||||
case PortA:
|
|
||||||
case PortB:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,15 +33,8 @@ public abstract class R6522 extends Device {
|
||||||
timer1running = true;
|
timer1running = true;
|
||||||
timer1latch = 0x1fff;
|
timer1latch = 0x1fff;
|
||||||
timer1interruptEnabled = false;
|
timer1interruptEnabled = false;
|
||||||
// setSpeedInHz(SPEED);
|
|
||||||
// setRun(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public long defaultCyclesPerSecond() {
|
|
||||||
// return SPEED;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 6522 VIA
|
// 6522 VIA
|
||||||
// http://www.applevault.com/twiki/Main/Mockingboard/6522.pdf
|
// http://www.applevault.com/twiki/Main/Mockingboard/6522.pdf
|
||||||
// I/O registers
|
// I/O registers
|
||||||
|
@ -148,56 +141,49 @@ public abstract class R6522 extends Device {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
// if (!unclocked) {
|
if (timer1running) {
|
||||||
if (timer1running) {
|
timer1counter--;
|
||||||
timer1counter--;
|
if (debug && timer1counter % 1000 == 0)
|
||||||
if (debug && timer1counter % 1000 == 0)
|
System.out.println(getShortName() + " Timer 1 counter: "+timer1counter+" Timer 1 interrupt enabled: "+timer1interruptEnabled);
|
||||||
System.out.println(getShortName() + " Timer 1 counter: "+timer1counter+" Timer 1 interrupt enabled: "+timer1interruptEnabled);
|
if (timer1counter < 0) {
|
||||||
if (timer1counter < 0) {
|
timer1counter = timer1latch;
|
||||||
timer1counter = timer1latch;
|
if (!timer1freerun) {
|
||||||
if (!timer1freerun) {
|
timer1running = false;
|
||||||
timer1running = false;
|
}
|
||||||
}
|
if (timer1interruptEnabled) {
|
||||||
if (timer1interruptEnabled) {
|
if (debug) System.out.println("Timer 1 generated interrupt");
|
||||||
if (debug) System.out.println("Timer 1 generated interrupt");
|
timer1IRQ = true;
|
||||||
timer1IRQ = true;
|
Emulator.withComputer(c->c.getCpu().generateInterrupt());
|
||||||
Emulator.withComputer(c->c.getCpu().generateInterrupt());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (timer2running) {
|
}
|
||||||
timer2counter--;
|
if (timer2running) {
|
||||||
if (debug && timer2counter % 1000 == 0)
|
timer2counter--;
|
||||||
System.out.println(getShortName() + " Timer 2 counter: "+timer2counter+" Timer 2 interrupt enabled: "+timer2interruptEnabled);
|
if (debug && timer2counter % 1000 == 0)
|
||||||
if (timer2counter < 0) {
|
System.out.println(getShortName() + " Timer 2 counter: "+timer2counter+" Timer 2 interrupt enabled: "+timer2interruptEnabled);
|
||||||
timer2running = false;
|
if (timer2counter < 0) {
|
||||||
timer2counter = timer2latch;
|
timer2running = false;
|
||||||
if (timer2interruptEnabled) {
|
timer2counter = timer2latch;
|
||||||
if (debug) System.out.println("Timer 2 generated interrupt");
|
if (timer2interruptEnabled) {
|
||||||
timer2IRQ = true;
|
if (debug) System.out.println("Timer 2 generated interrupt");
|
||||||
Emulator.withComputer(c->c.getCpu().generateInterrupt());
|
timer2IRQ = true;
|
||||||
}
|
Emulator.withComputer(c->c.getCpu().generateInterrupt());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!timer1running && !timer2running) {
|
}
|
||||||
if (debug) System.out.println("No timers active, suspending");
|
if (!timer1running && !timer2running) {
|
||||||
suspend();
|
if (debug) System.out.println("No timers active, suspending");
|
||||||
}
|
suspend();
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUnclocked(boolean unclocked) {
|
public void setUnclocked(boolean unclocked) {
|
||||||
this.unclocked = unclocked;
|
this.unclocked = unclocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void attach() {
|
|
||||||
// Start chip
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reconfigure() {
|
public void reconfigure() {
|
||||||
// Reset
|
// Nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeRegister(int reg, int val) {
|
public void writeRegister(int reg, int val) {
|
||||||
|
@ -205,70 +191,59 @@ public abstract class R6522 extends Device {
|
||||||
Register r = Register.fromInt(reg);
|
Register r = Register.fromInt(reg);
|
||||||
if (debug) System.out.println(getShortName() + " Writing "+Integer.toHexString(value&0x0ff)+" to register "+r.toString());
|
if (debug) System.out.println(getShortName() + " Writing "+Integer.toHexString(value&0x0ff)+" to register "+r.toString());
|
||||||
switch (r) {
|
switch (r) {
|
||||||
case ORB:
|
case ORB -> {
|
||||||
if (dataDirectionB == 0) {
|
if (dataDirectionB == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sendOutputB(value & dataDirectionB);
|
sendOutputB(value & dataDirectionB);
|
||||||
break;
|
}
|
||||||
case ORA:
|
case ORA -> {
|
||||||
// case ORAH:
|
// case ORAH:
|
||||||
if (dataDirectionA == 0) {
|
if (dataDirectionA == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sendOutputA(value & dataDirectionA);
|
sendOutputA(value & dataDirectionA);
|
||||||
break;
|
}
|
||||||
case DDRB:
|
case DDRB -> dataDirectionB = value;
|
||||||
dataDirectionB = value;
|
case DDRA -> dataDirectionA = value;
|
||||||
break;
|
case T1CL, T1LL -> timer1latch = (timer1latch & 0x0ff00) | value;
|
||||||
case DDRA:
|
case T1CH -> {
|
||||||
dataDirectionA = value;
|
|
||||||
break;
|
|
||||||
case T1CL:
|
|
||||||
case T1LL:
|
|
||||||
timer1latch = (timer1latch & 0x0ff00) | value;
|
|
||||||
break;
|
|
||||||
case T1CH:
|
|
||||||
timer1latch = (timer1latch & 0x0ff) | (value << 8);
|
timer1latch = (timer1latch & 0x0ff) | (value << 8);
|
||||||
timer1IRQ = false;
|
timer1IRQ = false;
|
||||||
timer1counter = timer1latch;
|
timer1counter = timer1latch;
|
||||||
timer1running = true;
|
timer1running = true;
|
||||||
break;
|
}
|
||||||
case T1LH:
|
case T1LH -> {
|
||||||
timer1latch = (timer1latch & 0x0ff) | (value << 8);
|
timer1latch = (timer1latch & 0x0ff) | (value << 8);
|
||||||
timer1IRQ = false;
|
timer1IRQ = false;
|
||||||
break;
|
}
|
||||||
case T2CL:
|
case T2CL -> timer2latch = (timer2latch & 0x0ff00) | value;
|
||||||
timer2latch = (timer2latch & 0x0ff00) | value;
|
case T2CH -> {
|
||||||
break;
|
|
||||||
case T2CH:
|
|
||||||
timer2latch = (timer2latch & 0x0ff) | (value << 8);
|
timer2latch = (timer2latch & 0x0ff) | (value << 8);
|
||||||
timer2IRQ = false;
|
timer2IRQ = false;
|
||||||
timer2counter = timer2latch;
|
timer2counter = timer2latch;
|
||||||
timer2running = true;
|
timer2running = true;
|
||||||
break;
|
}
|
||||||
case SR:
|
case SR -> {
|
||||||
// SHIFT REGISTER NOT IMPLEMENTED
|
}
|
||||||
break;
|
case ACR -> {
|
||||||
case ACR:
|
|
||||||
// SHIFT REGISTER NOT IMPLEMENTED
|
// SHIFT REGISTER NOT IMPLEMENTED
|
||||||
timer1freerun = (value & 64) != 0;
|
timer1freerun = (value & 64) != 0;
|
||||||
if (timer1freerun) {
|
if (timer1freerun) {
|
||||||
timer1running = true;
|
timer1running = true;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case PCR:
|
case PCR -> {
|
||||||
// TODO: Implement if Votrax (SSI) is to be supported
|
}
|
||||||
break;
|
case IFR -> {
|
||||||
case IFR:
|
|
||||||
if ((value & 64) != 0) {
|
if ((value & 64) != 0) {
|
||||||
timer1IRQ = false;
|
timer1IRQ = false;
|
||||||
}
|
}
|
||||||
if ((value & 32) != 0) {
|
if ((value & 32) != 0) {
|
||||||
timer2IRQ = false;
|
timer2IRQ = false;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case IER:
|
case IER -> {
|
||||||
boolean enable = (value & 128) != 0;
|
boolean enable = (value & 128) != 0;
|
||||||
if ((value & 64) != 0) {
|
if ((value & 64) != 0) {
|
||||||
timer1interruptEnabled = enable;
|
timer1interruptEnabled = enable;
|
||||||
|
@ -276,9 +251,12 @@ public abstract class R6522 extends Device {
|
||||||
if ((value & 32) != 0) {
|
if ((value & 32) != 0) {
|
||||||
timer2interruptEnabled = enable;
|
timer2interruptEnabled = enable;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
default:
|
default -> {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// SHIFT REGISTER NOT IMPLEMENTED
|
||||||
|
// TODO: Implement if Votrax (SSI) is to be supported
|
||||||
if (timer1running || timer2running) {
|
if (timer1running || timer2running) {
|
||||||
if (debug) System.out.println("One or more timers active, resuming");
|
if (debug) System.out.println("One or more timers active, resuming");
|
||||||
resume();
|
resume();
|
||||||
|
|
|
@ -4,7 +4,6 @@ import jace.LawlessLegends;
|
||||||
import jace.apple2e.SoftSwitches;
|
import jace.apple2e.SoftSwitches;
|
||||||
import jace.apple2e.softswitch.VideoSoftSwitch;
|
import jace.apple2e.softswitch.VideoSoftSwitch;
|
||||||
import jace.core.Device;
|
import jace.core.Device;
|
||||||
import jace.core.Motherboard;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
@ -65,7 +64,6 @@ public class FPSMonitorDevice extends Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateIcon() {
|
void updateIcon() {
|
||||||
cpuPerClock = Math.max(Motherboard.cpuPerClock, 1);
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long ellapsed = now - lastUpdate;
|
long ellapsed = now - lastUpdate;
|
||||||
if (ellapsed < UPDATE_INTERVAL) {
|
if (ellapsed < UPDATE_INTERVAL) {
|
||||||
|
|
|
@ -28,19 +28,20 @@ public class LawlessComputer extends Apple2e {
|
||||||
boolean performedBootAnimation = false;
|
boolean performedBootAnimation = false;
|
||||||
LawlessImageTool gameDiskHandler = new LawlessImageTool();
|
LawlessImageTool gameDiskHandler = new LawlessImageTool();
|
||||||
@ConfigurableField(name = "Boot Animation")
|
@ConfigurableField(name = "Boot Animation")
|
||||||
public boolean showBootAnimation = true;
|
public boolean showBootAnimation = PRODUCTION_MODE;
|
||||||
|
|
||||||
public LawlessComputer() {
|
public LawlessComputer() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initLawlessLegendsConfiguration() {
|
public void initLawlessLegendsConfiguration() {
|
||||||
this.cheatEngine.setValue(Cheats.Cheat.LawlessHacks);
|
if (PRODUCTION_MODE) {
|
||||||
|
this.cheatEngine.setValue(Cheats.Cheat.LawlessHacks);
|
||||||
|
}
|
||||||
// this.activeCheatEngine = new LawlessHacks(this);
|
// this.activeCheatEngine = new LawlessHacks(this);
|
||||||
// this.activeCheatEngine.attach();
|
// this.activeCheatEngine.attach();
|
||||||
blankTextPage1();
|
blankTextPage1();
|
||||||
reconfigure();
|
reconfigure();
|
||||||
Configuration.registerKeyHandlers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void blankTextPage1() {
|
private void blankTextPage1() {
|
||||||
|
@ -60,7 +61,7 @@ public class LawlessComputer extends Apple2e {
|
||||||
s.getSwitch().reset();
|
s.getSwitch().reset();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (showBootAnimation && PRODUCTION_MODE) {
|
if (showBootAnimation) {
|
||||||
(new Thread(this::startAnimation)).start();
|
(new Thread(this::startAnimation)).start();
|
||||||
} else {
|
} else {
|
||||||
getCpu().setPaused(false);
|
getCpu().setPaused(false);
|
||||||
|
@ -187,7 +188,7 @@ public class LawlessComputer extends Apple2e {
|
||||||
public void finishColdStart() {
|
public void finishColdStart() {
|
||||||
try {
|
try {
|
||||||
waitForVBL();
|
waitForVBL();
|
||||||
reboot();
|
warmStart();
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
Logger.getLogger(LawlessComputer.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(LawlessComputer.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,12 @@ import org.junit.BeforeClass;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|
||||||
public abstract class AbstractFXTest {
|
public abstract class AbstractFXTest {
|
||||||
|
public static boolean fxInitialized = false;
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void initJfxRuntime() {
|
public static void initJfxRuntime() {
|
||||||
Platform.startup(() -> {});
|
if (!fxInitialized) {
|
||||||
|
fxInitialized = true;
|
||||||
|
Platform.startup(() -> {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import jace.apple2e.Full65C02Test;
|
||||||
import jace.apple2e.MOS65C02;
|
import jace.apple2e.MOS65C02;
|
||||||
import jace.core.Computer;
|
import jace.core.Computer;
|
||||||
import jace.cpu.Full65C02Test;
|
|
||||||
|
|
||||||
public class TestProgram {
|
public class TestProgram {
|
||||||
// Tests could be run in any order so it is really important that all registers/flags are preserved!
|
// Tests could be run in any order so it is really important that all registers/flags are preserved!
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package jace.cpu;
|
package jace.apple2e;
|
||||||
|
|
||||||
import static jace.TestUtils.initComputer;
|
import static jace.TestUtils.initComputer;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package jace.cpu;
|
package jace.apple2e;
|
||||||
|
|
||||||
import static jace.TestProgram.Flag.CARRY_CLEAR;
|
import static jace.TestProgram.Flag.CARRY_CLEAR;
|
||||||
import static jace.TestProgram.Flag.CARRY_SET;
|
import static jace.TestProgram.Flag.CARRY_SET;
|
||||||
|
@ -43,8 +43,6 @@ import jace.Emulator;
|
||||||
import jace.ProgramException;
|
import jace.ProgramException;
|
||||||
import jace.TestProgram;
|
import jace.TestProgram;
|
||||||
import jace.TestUtils;
|
import jace.TestUtils;
|
||||||
import jace.apple2e.MOS65C02;
|
|
||||||
import jace.apple2e.RAM128k;
|
|
||||||
import jace.core.Computer;
|
import jace.core.Computer;
|
||||||
import jace.core.RAMEvent.TYPE;
|
import jace.core.RAMEvent.TYPE;
|
||||||
import jace.core.SoundMixer;
|
import jace.core.SoundMixer;
|
|
@ -0,0 +1,80 @@
|
||||||
|
package jace.apple2e;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
|
|
||||||
|
// This is mostly to provide execution coverage to catch null pointer or index out of range exceptions
|
||||||
|
public class VideoDHGRTest extends AbstractFXTest {
|
||||||
|
WritableImage image = new WritableImage(560, 192);
|
||||||
|
|
||||||
|
private VideoDHGR video;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
video = new VideoDHGR();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitHgrDhgrTables() {
|
||||||
|
// Test the initialization of HGR_TO_DHGR and HGR_TO_DHGR_BW tables
|
||||||
|
assertNotNull(video.HGR_TO_DHGR);
|
||||||
|
assertNotNull(video.HGR_TO_DHGR_BW);
|
||||||
|
// Add more assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitCharMap() {
|
||||||
|
// Test the initialization of CHAR_MAP1, CHAR_MAP2, and CHAR_MAP3 arrays
|
||||||
|
assertNotNull(video.CHAR_MAP1);
|
||||||
|
assertNotNull(video.CHAR_MAP2);
|
||||||
|
assertNotNull(video.CHAR_MAP3);
|
||||||
|
// Add more assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeToScreen() {
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 0, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 4, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 190, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, -1, 0, 0, 0);
|
||||||
|
video.getCurrentWriter().actualWriter().displayByte(image, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetYOffset() {
|
||||||
|
// Run through all possible combinations of soft switches to ensure the correct Y offset is returned each time
|
||||||
|
SoftSwitches[] switches = {SoftSwitches.HIRES, SoftSwitches.TEXT, SoftSwitches.PAGE2, SoftSwitches._80COL, SoftSwitches.DHIRES, SoftSwitches.MIXED};
|
||||||
|
for (int i=0; i < Math.pow(2.0, switches.length); i++) {
|
||||||
|
String state = "";
|
||||||
|
for (int j=0; j < switches.length; j++) {
|
||||||
|
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||||
|
state += switches[j].getSwitch().getName() + "=" + (switches[j].getSwitch().getState() ? "1" : "0") + " ";
|
||||||
|
}
|
||||||
|
video.configureVideoMode();
|
||||||
|
int address = video.getCurrentWriter().getYOffset(0);
|
||||||
|
int expected = SoftSwitches.TEXT.isOn() || SoftSwitches.HIRES.isOff() ? (SoftSwitches.PAGE2.isOn() ? 0x0800 : 0x0400)
|
||||||
|
: (SoftSwitches.PAGE2.isOn() ? 0x04000 : 0x02000);
|
||||||
|
assertEquals("Address for mode not correct: " + state, expected, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisplayByte() {
|
||||||
|
// Run through all possible combinations of soft switches to ensure the video writer executes without error
|
||||||
|
SoftSwitches[] switches = {SoftSwitches.HIRES, SoftSwitches.TEXT, SoftSwitches.PAGE2, SoftSwitches._80COL, SoftSwitches.DHIRES, SoftSwitches.MIXED};
|
||||||
|
for (int i=0; i < Math.pow(2.0, switches.length); i++) {
|
||||||
|
for (int j=0; j < switches.length; j++) {
|
||||||
|
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||||
|
}
|
||||||
|
video.configureVideoMode();
|
||||||
|
writeToScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add more test cases for other methods in the VideoDHGR class
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package jace.apple2e;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import javafx.scene.image.WritableImage;
|
||||||
|
|
||||||
|
// This is mostly to provide execution coverage to catch null pointer or index out of range exceptions
|
||||||
|
public class VideoNTSCTest extends AbstractFXTest {
|
||||||
|
WritableImage image = new WritableImage(560, 192);
|
||||||
|
|
||||||
|
private VideoNTSC video;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
video = new VideoNTSC();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitHgrDhgrTables() {
|
||||||
|
// Test the initialization of HGR_TO_DHGR and HGR_TO_DHGR_BW tables
|
||||||
|
assertNotNull(video.HGR_TO_DHGR);
|
||||||
|
assertNotNull(video.HGR_TO_DHGR_BW);
|
||||||
|
// Add more assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitCharMap() {
|
||||||
|
// Test the initialization of CHAR_MAP1, CHAR_MAP2, and CHAR_MAP3 arrays
|
||||||
|
assertNotNull(video.CHAR_MAP1);
|
||||||
|
assertNotNull(video.CHAR_MAP2);
|
||||||
|
assertNotNull(video.CHAR_MAP3);
|
||||||
|
// Add more assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeToScreen() {
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 0, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 4, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, 0, 190, 0, 0);
|
||||||
|
video.getCurrentWriter().displayByte(image, -1, 0, 0, 0);
|
||||||
|
video.getCurrentWriter().actualWriter().displayByte(image, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetYOffset() {
|
||||||
|
// Run through all possible combinations of soft switches to ensure the correct Y offset is returned each time
|
||||||
|
SoftSwitches[] switches = {SoftSwitches.HIRES, SoftSwitches.TEXT, SoftSwitches.PAGE2, SoftSwitches._80COL, SoftSwitches.DHIRES, SoftSwitches.MIXED};
|
||||||
|
for (int i=0; i < Math.pow(2.0, switches.length); i++) {
|
||||||
|
String state = "";
|
||||||
|
for (int j=0; j < switches.length; j++) {
|
||||||
|
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||||
|
state += switches[j].getSwitch().getName() + "=" + (switches[j].getSwitch().getState() ? "1" : "0") + " ";
|
||||||
|
}
|
||||||
|
video.configureVideoMode();
|
||||||
|
int address = video.getCurrentWriter().getYOffset(0);
|
||||||
|
int expected = SoftSwitches.TEXT.isOn() || SoftSwitches.HIRES.isOff() ? (SoftSwitches.PAGE2.isOn() ? 0x0800 : 0x0400)
|
||||||
|
: (SoftSwitches.PAGE2.isOn() ? 0x04000 : 0x02000);
|
||||||
|
assertEquals("Address for mode not correct: " + state, expected, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisplayByte() {
|
||||||
|
// Run through all possible combinations of soft switches to ensure the video writer executes without error
|
||||||
|
SoftSwitches[] switches = {SoftSwitches.HIRES, SoftSwitches.TEXT, SoftSwitches.PAGE2, SoftSwitches._80COL, SoftSwitches.DHIRES, SoftSwitches.MIXED};
|
||||||
|
for (int i=0; i < Math.pow(2.0, switches.length); i++) {
|
||||||
|
for (int j=0; j < switches.length; j++) {
|
||||||
|
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||||
|
}
|
||||||
|
video.configureVideoMode();
|
||||||
|
writeToScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisplayModes() {
|
||||||
|
for (VideoNTSC.VideoMode mode : VideoNTSC.VideoMode.values()) {
|
||||||
|
VideoNTSC.setVideoMode(mode, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package jace.applesoft;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ApplesoftTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fromStringTest() {
|
||||||
|
String programSource = "10 PRINT \"Hello, World!\"\n20 PRINT \"Goodbye!\"";
|
||||||
|
ApplesoftProgram program = ApplesoftProgram.fromString(programSource);
|
||||||
|
assertNotNull(program);
|
||||||
|
assertEquals(2, program.lines.size());
|
||||||
|
Line line1 = program.lines.get(0);
|
||||||
|
assertEquals(10, line1.getNumber());
|
||||||
|
assertEquals(1, line1.getCommands().size());
|
||||||
|
Command command1 = line1.getCommands().get(0);
|
||||||
|
assertEquals(0xBA, command1.parts.get(0).getByte() & 0x0ff);
|
||||||
|
String match = "";
|
||||||
|
for (int idx=1; idx < command1.parts.size(); idx++) {
|
||||||
|
match += command1.parts.get(idx).toString();
|
||||||
|
}
|
||||||
|
assertEquals("\"Hello, World!\"", match);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toStringTest() {
|
||||||
|
Line line1 = Line.fromString("10 print \"Hello, world!\"");
|
||||||
|
Line line2 = Line.fromString("20 print \"Goodbye!\"");
|
||||||
|
ApplesoftProgram program = new ApplesoftProgram();
|
||||||
|
program.lines.add(line1);
|
||||||
|
program.lines.add(line2);
|
||||||
|
String programSource = program.toString();
|
||||||
|
assertEquals("10 PRINT \"Hello, world!\"\n20 PRINT \"Goodbye!\"\n", programSource);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,12 @@ package jace.core;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import jace.AbstractFXTest;
|
import jace.AbstractFXTest;
|
||||||
import jace.core.SoundMixer.SoundBuffer;
|
import jace.core.SoundMixer.SoundBuffer;
|
||||||
import jace.core.SoundMixer.SoundError;
|
import jace.core.SoundMixer.SoundError;
|
||||||
|
@ -12,6 +15,18 @@ import jace.lawless.LawlessHacks;
|
||||||
import jace.lawless.Media;
|
import jace.lawless.Media;
|
||||||
|
|
||||||
public class SoundTest extends AbstractFXTest {
|
public class SoundTest extends AbstractFXTest {
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
System.out.println("Init sound");
|
||||||
|
Utility.setHeadlessMode(false);
|
||||||
|
SoundMixer.initSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
Utility.setHeadlessMode(true);
|
||||||
|
}
|
||||||
|
|
||||||
// @Test
|
// @Test
|
||||||
public void musicDecodeTest() {
|
public void musicDecodeTest() {
|
||||||
// For every song in the music folder, decode it and print out the duration
|
// For every song in the music folder, decode it and print out the duration
|
||||||
|
@ -39,19 +54,19 @@ public class SoundTest extends AbstractFXTest {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
@Test
|
||||||
//(Only use this to ensure the sound engine produces audible output, it's otherwise annoying to hear all the time)
|
//(Only use this to ensure the sound engine produces audible output, it's otherwise annoying to hear all the time)
|
||||||
public void soundGenerationTest() throws SoundError {
|
public void soundGenerationTest() throws SoundError {
|
||||||
try {
|
try {
|
||||||
System.out.println("Performing sound test...");
|
System.out.println("Performing sound test...");
|
||||||
System.out.println("Create mixer");
|
|
||||||
SoundMixer mixer = new SoundMixer();
|
SoundMixer mixer = new SoundMixer();
|
||||||
System.out.println("Attach mixer");
|
System.out.println("Attach mixer");
|
||||||
mixer.attach();
|
mixer.attach();
|
||||||
System.out.println("Allocate buffer");
|
System.out.println("Allocate buffer");
|
||||||
SoundBuffer buffer = SoundMixer.createBuffer(false);
|
SoundBuffer buffer = SoundMixer.createBuffer(false);
|
||||||
System.out.println("Generate sound");
|
System.out.println("Generate sound");
|
||||||
for (int i = 0; i < 100000; i++) {
|
// for (int i = 0; i < 100000; i++) {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
// Gerate a sin wave with a frequency sweep so we can tell if the buffer is being fully processed
|
// 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);
|
double x = Math.sin(i*i * 0.0001);
|
||||||
buffer.playSample((short) (Short.MAX_VALUE * x));
|
buffer.playSample((short) (Short.MAX_VALUE * x));
|
||||||
|
@ -65,7 +80,7 @@ public class SoundTest extends AbstractFXTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
@Test
|
||||||
// Commented out because it's annoying to hear all the time, but it worked without issues
|
// Commented out because it's annoying to hear all the time, but it worked without issues
|
||||||
public void mixerTortureTest() throws SoundError, InterruptedException, ExecutionException {
|
public void mixerTortureTest() throws SoundError, InterruptedException, ExecutionException {
|
||||||
System.out.println("Performing speaker tick test...");
|
System.out.println("Performing speaker tick test...");
|
||||||
|
@ -75,8 +90,9 @@ public class SoundTest extends AbstractFXTest {
|
||||||
System.out.println("Attach mixer");
|
System.out.println("Attach mixer");
|
||||||
mixer.attach();
|
mixer.attach();
|
||||||
// We want to create and destroy lots of buffers to make sure we don't have any memory leaks
|
// We want to create and destroy lots of buffers to make sure we don't have any memory leaks
|
||||||
for (int i = 0; i < 10000; i++) {
|
// for (int i = 0; i < 10000; i++) {
|
||||||
// Print status every 1000 iterations
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
// Print status every 1000 iterations
|
||||||
if (i % 1000 == 0) {
|
if (i % 1000 == 0) {
|
||||||
System.out.println("Iteration %d".formatted(i));
|
System.out.println("Iteration %d".formatted(i));
|
||||||
}
|
}
|
||||||
|
@ -100,14 +116,13 @@ public class SoundTest extends AbstractFXTest {
|
||||||
* Runs through 500 iterations of playing a random song for 1 second and switching songs
|
* Runs through 500 iterations of playing a random song for 1 second and switching songs
|
||||||
*/
|
*/
|
||||||
public void musicPlaybackTortureTest() throws InterruptedException {
|
public void musicPlaybackTortureTest() throws InterruptedException {
|
||||||
SoundMixer.initSound();
|
|
||||||
System.out.println("Create mixer");
|
System.out.println("Create mixer");
|
||||||
SoundMixer mixer = new SoundMixer();
|
SoundMixer mixer = new SoundMixer();
|
||||||
System.out.println("Attach mixer");
|
System.out.println("Attach mixer");
|
||||||
mixer.attach();
|
mixer.attach();
|
||||||
LawlessHacks lawlessHacks = new LawlessHacks();
|
LawlessHacks lawlessHacks = new LawlessHacks();
|
||||||
int track = 0;
|
int track = 0;
|
||||||
Random rnd = new Random();
|
// Random rnd = new Random();
|
||||||
for (int i=0; i < 500; i++) {
|
for (int i=0; i < 500; i++) {
|
||||||
System.out.println(">>>>>>>>> Cycle " + i);
|
System.out.println(">>>>>>>>> Cycle " + i);
|
||||||
// Get a random song
|
// Get a random song
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package jace.core;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class UtilityTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLevenshteinDistance() {
|
||||||
|
String s1 = "kitten";
|
||||||
|
String s2 = "sitting";
|
||||||
|
int distance = Utility.levenshteinDistance(s1, s2);
|
||||||
|
assertEquals(3, distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAdjustedLevenshteinDistance() {
|
||||||
|
String s1 = "kitten";
|
||||||
|
String s2 = "sitting";
|
||||||
|
int adjustedDistance = Utility.adjustedLevenshteinDistance(s1, s2);
|
||||||
|
assertEquals(4, adjustedDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRankMatch() {
|
||||||
|
String s1 = "apple";
|
||||||
|
String s2 = "banana";
|
||||||
|
double score = Utility.rankMatch(s1, s2, 3);
|
||||||
|
assertEquals(0, score, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindBestMatch() {
|
||||||
|
String match = "apple";
|
||||||
|
Collection<String> search = Arrays.asList("banana", "orange", "apple pie");
|
||||||
|
String bestMatch = Utility.findBestMatch(match, search);
|
||||||
|
assertEquals("apple pie", bestMatch);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package jace.hardware;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import jace.core.RAMEvent;
|
||||||
|
import jace.core.RAMEvent.SCOPE;
|
||||||
|
import jace.core.RAMEvent.TYPE;
|
||||||
|
import jace.core.RAMEvent.VALUE;
|
||||||
|
import javafx.geometry.Rectangle2D;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
|
||||||
|
public class CardAppleMouseTest extends AbstractFXTest {
|
||||||
|
|
||||||
|
private CardAppleMouse cardAppleMouse;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
cardAppleMouse = new CardAppleMouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDeviceName() {
|
||||||
|
assertEquals("Apple Mouse", cardAppleMouse.getDeviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReset() {
|
||||||
|
cardAppleMouse.mode = 1;
|
||||||
|
cardAppleMouse.clampWindow = new Rectangle2D(10, 10, 100, 100);
|
||||||
|
cardAppleMouse.detach();
|
||||||
|
|
||||||
|
cardAppleMouse.reset();
|
||||||
|
|
||||||
|
assertEquals(0, cardAppleMouse.mode);
|
||||||
|
assertEquals(new Rectangle2D(0, 0, 0x03ff, 0x03ff), cardAppleMouse.clampWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// Test mouseHandler responses to mouse events
|
||||||
|
public void testMouseHandler() {
|
||||||
|
MouseEvent clickEvent = new MouseEvent(MouseEvent.MOUSE_CLICKED,0,0,0,0,MouseButton.PRIMARY,1,false,false,false, false, false, false, false, false, false, false, null);
|
||||||
|
MouseEvent releaseEvent = new MouseEvent(MouseEvent.MOUSE_RELEASED,0,0,0,0,MouseButton.PRIMARY,1,false,false,false, false, false, false, false, false, false, false, null);
|
||||||
|
MouseEvent dragEvent = new MouseEvent(MouseEvent.MOUSE_DRAGGED,0,0,0,0,MouseButton.PRIMARY,1,false,false,false, false, false, false, false, false, false, false, null);
|
||||||
|
|
||||||
|
cardAppleMouse.mode = 1;
|
||||||
|
cardAppleMouse.mouseHandler.handle(clickEvent);
|
||||||
|
cardAppleMouse.mouseHandler.handle(dragEvent);
|
||||||
|
cardAppleMouse.mouseHandler.handle(releaseEvent);
|
||||||
|
assertEquals(1, cardAppleMouse.mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// Test firmware entry points
|
||||||
|
public void testFirmware() {
|
||||||
|
// Test reads
|
||||||
|
RAMEvent event = new RAMEvent(TYPE.READ, SCOPE.ANY, VALUE.ANY, 0, 0, 0);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x80, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x81, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x82, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x83, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x84, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x85, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x86, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x87, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x88, TYPE.EXECUTE, 0, event);
|
||||||
|
assertEquals(0x60, event.getNewValue());
|
||||||
|
event.setNewValue(0x00);
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x05, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x38, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x07, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x18, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x08, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x01, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x0B, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x01, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x0C, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x20, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x11, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x00, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x12, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x080, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x13, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x081, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x14, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x082, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x15, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x083, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x16, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x084, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x17, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x085, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x18, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x086, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x19, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x087, event.getNewValue());
|
||||||
|
cardAppleMouse.handleFirmwareAccess(0x1A, TYPE.READ, 0, event);
|
||||||
|
assertEquals(0x088, event.getNewValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package jace.hardware;
|
||||||
|
|
||||||
|
import static jace.hardware.CardSSC.ACIA_Command;
|
||||||
|
import static jace.hardware.CardSSC.ACIA_Control;
|
||||||
|
import static jace.hardware.CardSSC.ACIA_Data;
|
||||||
|
import static jace.hardware.CardSSC.ACIA_Status;
|
||||||
|
import static jace.hardware.CardSSC.SW1;
|
||||||
|
import static jace.hardware.CardSSC.SW2_CTS;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import jace.core.RAMEvent;
|
||||||
|
import jace.core.RAMEvent.SCOPE;
|
||||||
|
import jace.core.RAMEvent.TYPE;
|
||||||
|
import jace.core.RAMEvent.VALUE;
|
||||||
|
|
||||||
|
public class CardSSCTest extends AbstractFXTest {
|
||||||
|
|
||||||
|
private CardSSC cardSSC;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
cardSSC = new CardSSC();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDeviceName() {
|
||||||
|
assertEquals("Super Serial Card", cardSSC.getDeviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetSlot() {
|
||||||
|
cardSSC.setSlot(1);
|
||||||
|
// assertEquals("Slot 1", cardSSC.activityIndicator.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReset() {
|
||||||
|
cardSSC.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIOAccess() {
|
||||||
|
RAMEvent event = new RAMEvent(TYPE.READ_DATA, SCOPE.ANY, VALUE.ANY, 0, 0, 0);
|
||||||
|
int[] registers = {SW1, SW2_CTS, ACIA_Data, ACIA_Control, ACIA_Status, ACIA_Command};
|
||||||
|
for (int register : registers) {
|
||||||
|
cardSSC.handleIOAccess(register, TYPE.READ_DATA, 0, event);
|
||||||
|
cardSSC.handleIOAccess(register, TYPE.WRITE, 0, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package jace.hardware;
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static jace.hardware.FloppyDisk.PRODOS_SECTOR_ORDER;
|
||||||
|
|
||||||
|
public class FloppyDiskTest extends AbstractFXTest {
|
||||||
|
|
||||||
|
private FloppyDisk floppyDisk;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws IOException {
|
||||||
|
floppyDisk = new FloppyDisk();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readDisk_ValidDiskFile_Success() throws IOException {
|
||||||
|
// Create a sample disk file
|
||||||
|
byte[] diskData = new byte[232960];
|
||||||
|
File diskFile = File.createTempFile("test_disk", ".dsk");
|
||||||
|
diskFile.deleteOnExit();
|
||||||
|
ByteArrayInputStream diskInputStream = new ByteArrayInputStream(diskData);
|
||||||
|
|
||||||
|
// Read the disk file
|
||||||
|
floppyDisk.readDisk(diskInputStream, false);
|
||||||
|
|
||||||
|
// Verify the disk properties
|
||||||
|
assert(floppyDisk.isNibblizedImage);
|
||||||
|
assertEquals(254, floppyDisk.volumeNumber);
|
||||||
|
assertEquals(0, floppyDisk.headerLength);
|
||||||
|
assertEquals(232960, floppyDisk.nibbles.length);
|
||||||
|
assertEquals("Sector order not null", true, null != floppyDisk.currentSectorOrder);
|
||||||
|
assertNull(floppyDisk.diskPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nibblize_ValidNibbles_Success() throws IOException {
|
||||||
|
// Create a sample nibbles array
|
||||||
|
byte[] nibbles = new byte[FloppyDisk.DISK_NIBBLE_LENGTH];
|
||||||
|
for (int i = 0; i < nibbles.length; i++) {
|
||||||
|
nibbles[i] = (byte) (i % 256);
|
||||||
|
}
|
||||||
|
floppyDisk.currentSectorOrder = PRODOS_SECTOR_ORDER;
|
||||||
|
// Nibblize the nibbles array
|
||||||
|
byte[] nibblizedData = floppyDisk.nibblize(nibbles);
|
||||||
|
|
||||||
|
// Verify the nibblized data
|
||||||
|
assertEquals(FloppyDisk.DISK_NIBBLE_LENGTH, nibblizedData.length);
|
||||||
|
// for (int i = 0; i < nibblizedData.length; i++) {
|
||||||
|
// assertEquals((i % 256) >> 2, nibblizedData[i]);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package jace.hardware;
|
||||||
|
|
||||||
|
import static jace.hardware.PassportMidiInterface.ACIA_RECV;
|
||||||
|
import static jace.hardware.PassportMidiInterface.ACIA_STATUS;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER1_LSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER1_MSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER2_LSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER2_MSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER3_LSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER3_MSB;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER_CONTROL_1;
|
||||||
|
import static jace.hardware.PassportMidiInterface.TIMER_CONTROL_2;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import jace.AbstractFXTest;
|
||||||
|
import jace.core.RAMEvent;
|
||||||
|
import jace.core.RAMEvent.SCOPE;
|
||||||
|
import jace.core.RAMEvent.TYPE;
|
||||||
|
import jace.core.RAMEvent.VALUE;
|
||||||
|
|
||||||
|
|
||||||
|
public class PassportMidiInterfaceTest extends AbstractFXTest {
|
||||||
|
PassportMidiInterface midi = new PassportMidiInterface();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeviceSelection() {
|
||||||
|
assertNotNull(PassportMidiInterface.preferredMidiDevice.getSelections());
|
||||||
|
assertNotEquals(0, PassportMidiInterface.preferredMidiDevice.getSelections().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIOAccess() {
|
||||||
|
RAMEvent event = new RAMEvent(TYPE.READ_DATA, SCOPE.ANY, VALUE.ANY, 0, 0, 0);
|
||||||
|
int[] registers = {ACIA_STATUS, ACIA_RECV, TIMER_CONTROL_1, TIMER_CONTROL_2, TIMER1_LSB, TIMER1_MSB, TIMER2_LSB, TIMER2_MSB, TIMER3_LSB, TIMER3_MSB};
|
||||||
|
for (int register : registers) {
|
||||||
|
midi.handleIOAccess(register, TYPE.READ_DATA, 0, event);
|
||||||
|
midi.handleIOAccess(register, TYPE.WRITE, 0, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package jace.hardware.mockingboard;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class PSGTest {
|
||||||
|
|
||||||
|
private PSG psg;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
psg = new PSG(0, 100, 44100, "name", 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setControl_InactiveCommand_NoAction() {
|
||||||
|
psg.setControl(0); // Set control to inactive
|
||||||
|
// Assert that no action is taken
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setControl_LatchCommand_SelectedRegUpdated() {
|
||||||
|
psg.setControl(1); // Set control to latch
|
||||||
|
// Assert that selectedReg is updated correctly
|
||||||
|
// Add your assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setControl_ReadCommand_BusUpdated() {
|
||||||
|
psg.setControl(2); // Set control to read
|
||||||
|
// Assert that bus is updated correctly
|
||||||
|
// Add your assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setControl_WriteCommand_RegUpdated() {
|
||||||
|
psg.setControl(3); // Set control to write
|
||||||
|
// Assert that the corresponding register is updated correctly
|
||||||
|
// Add your assertions here
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateTest() {
|
||||||
|
AtomicInteger out = new AtomicInteger();
|
||||||
|
psg.update(out, false, out, false, out, false);
|
||||||
|
psg.update(out, true, out, true, out, true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package jace.hardware.mockingboard;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class R6522Test {
|
||||||
|
R6522 r6522 = new R6522() {
|
||||||
|
@Override
|
||||||
|
public String getShortName() {
|
||||||
|
return "name";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOutputA(int value) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOutputB(int value) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int receiveOutputA() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int receiveOutputB() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteRegs() {
|
||||||
|
for (R6522.Register reg : R6522.Register.values()) {
|
||||||
|
r6522.writeRegister(reg.val, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadRegs() {
|
||||||
|
for (R6522.Register reg : R6522.Register.values()) {
|
||||||
|
r6522.readRegister(reg.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user