This commit is contained in:
Martin Haye 2023-10-30 13:38:20 -07:00
commit e99bd01741
74 changed files with 1099 additions and 1105 deletions

View File

@ -0,0 +1,55 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"request": "attach",
"name": "Attach by Process ID",
"processId": "${command:PickJavaProcess}"
},
{
"type": "java",
"name": "Current File",
"request": "launch",
"mainClass": "${file}"
},
{
"type": "java",
"name": "ConvertDiskImage",
"request": "launch",
"mainClass": "lawlesslegends/jace.ConvertDiskImage",
"projectName": "lawlesslegends"
},
{
"type": "java",
"name": "LawlessLegends",
"request": "launch",
"mainClass": "lawlesslegends/jace.LawlessLegends",
"projectName": "lawlesslegends"
},
{
"type": "java",
"name": "ApplesoftProgram",
"request": "launch",
"mainClass": "lawlesslegends/jace.applesoft.ApplesoftProgram",
"projectName": "lawlesslegends"
},
{
"type": "java",
"name": "AcmeCrossAssembler",
"request": "launch",
"mainClass": "lawlesslegends/jace.assembly.AcmeCrossAssembler",
"projectName": "lawlesslegends"
},
{
"type": "java",
"name": "Library",
"request": "launch",
"mainClass": "lawlesslegends/jace.ui.Library",
"projectName": "lawlesslegends"
}
]
}

View File

@ -0,0 +1,4 @@
{
"java.configuration.updateBuildConfiguration": "automatic",
"java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable"
}

View File

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

View File

@ -35,7 +35,7 @@ import jace.lawless.LawlessComputer;
public class Emulator {
public static Emulator instance;
public static EmulatorUILogic logic = new EmulatorUILogic();
private static EmulatorUILogic logic;
public static Thread mainThread;
// public static void main(String... args) {
@ -44,6 +44,13 @@ public class Emulator {
// }
private final LawlessComputer computer;
public static EmulatorUILogic getUILogic() {
if (logic == null) {
logic = new EmulatorUILogic();
}
return logic;
}
public static Emulator getInstance(List<String> args) {
Emulator i = getInstance();

View File

@ -18,6 +18,8 @@
*/
package jace;
import static jace.core.Utility.gripe;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -44,7 +46,6 @@ import jace.config.Reconfigurable;
import jace.core.Debugger;
import jace.core.RAM;
import jace.core.RAMListener;
import static jace.core.Utility.gripe;
import jace.ide.IdeController;
import javafx.application.Platform;
import javafx.event.EventHandler;
@ -90,7 +91,7 @@ public class EmulatorUILogic implements Reconfigurable {
category = "General",
name = "Show Drives"
)
public boolean showDrives = false;
public boolean showDrives = Emulator.withComputer(c->!c.PRODUCTION_MODE, false);
public static void updateCPURegisters(MOS65C02 cpu) {
// DebuggerPanel debuggerPanel = Emulator.getFrame().getDebuggerPanel();
@ -233,10 +234,6 @@ public class EmulatorUILogic implements Reconfigurable {
}
public static void brun(File binary, int address) throws IOException {
// If it was halted already, then it was initiated outside of an opcode execution
// If it was not yet halted, then it is the case that the CPU is processing another opcode
// So if that is the case, the program counter will need to be decremented here to compensate
// TODO: Find a better mousetrap for this one -- it's an ugly hack
byte[] data;
try (FileInputStream in = new FileInputStream(binary)) {
data = new byte[in.available()];
@ -345,7 +342,7 @@ public class EmulatorUILogic implements Reconfigurable {
}
String filename = targetFile.getName();
System.out.println("Writing screenshot to " + filename);
String extension = filename.substring(filename.lastIndexOf(".") + 1);
// String extension = filename.substring(filename.lastIndexOf(".") + 1);
// BufferedImage bufImageRGB = new BufferedImage(bufImageARGB.getWidth(), bufImageARGB.getHeight(), BufferedImage.OPAQUE);
//
// Graphics2D graphics = bufImageRGB.createGraphics();

View File

@ -93,7 +93,7 @@ public class JaceUIController {
private Button menuButton;
@FXML
private ComboBox musicSelection;
private ComboBox<String> musicSelection;
private final BooleanProperty aspectRatioCorrectionEnabled = new SimpleBooleanProperty(false);
@ -246,9 +246,9 @@ public class JaceUIController {
});
speedSlider.valueProperty().addListener((val, oldValue, newValue) -> setSpeed(newValue.doubleValue()));
Platform.runLater(() -> {
speedSlider.setValue(Emulator.logic.speedSetting);
speedSlider.setValue(Emulator.getUILogic().speedSetting);
// Kind of redundant but make sure speed is properly set as if the user did it
setSpeed(Emulator.logic.speedSetting);
setSpeed(Emulator.getUILogic().speedSetting);
});
musicSelection.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) ->
Emulator.withComputer(computer ->
@ -268,7 +268,7 @@ public class JaceUIController {
}
public void setSpeed(double speed) {
Emulator.logic.speedSetting = (int) speed;
Emulator.getUILogic().speedSetting = (int) speed;
double speedRatio = convertSpeedToRatio(speed);
if (speedSlider.getValue() != speed) {
Platform.runLater(()->speedSlider.setValue(speed));
@ -404,7 +404,7 @@ public class JaceUIController {
private List<MediaConsumer> getMediaConsumers() {
List<MediaConsumer> consumers = new ArrayList<>();
Emulator.withComputer(c -> consumers.add(((LawlessComputer) c).getUpgradeHandler()));
if (Emulator.logic.showDrives) {
if (Emulator.getUILogic().showDrives) {
Emulator.withMemory(m -> {
for (Optional<Card> card : m.getAllCards()) {
card.filter(c -> c instanceof MediaConsumerParent).ifPresent(parent ->
@ -441,6 +441,7 @@ public class JaceUIController {
}
ScheduledExecutorService notificationExecutor = Executors.newSingleThreadScheduledExecutor();
@SuppressWarnings("all")
ScheduledFuture ttlCleanupTask = null;
private void trackTTL(Label icon, long TTL) {

View File

@ -138,7 +138,7 @@ public class LawlessLegends extends Application {
new Thread(()->{
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Booting with watchdog");
RAMListener startListener = c.getMemory().
observe(RAMEvent.TYPE.EXECUTE, 0x2000, (e) -> {
observe("Lawless Legends watchdog", RAMEvent.TYPE.EXECUTE, 0x2000, (e) -> {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Boot was detected, watchdog terminated.");
romStarted = true;
});

View File

@ -76,9 +76,9 @@ public class Apple2e extends Computer {
@ConfigurableField(name = "Slot 5", shortName = "s5card")
public DeviceSelection<Cards> card5 = new DeviceSelection<>(Cards.class, null);
@ConfigurableField(name = "Slot 6", shortName = "s6card")
public DeviceSelection<Cards> card6 = new DeviceSelection<>(Cards.class, Cards.DiskIIDrive);
public DeviceSelection<Cards> card6 = new DeviceSelection<>(Cards.class, Cards.DiskIIDrive, true);
@ConfigurableField(name = "Slot 7", shortName = "s7card")
public DeviceSelection<Cards> card7 = new DeviceSelection<>(Cards.class, Cards.MassStorage);
public DeviceSelection<Cards> card7 = new DeviceSelection<>(Cards.class, Cards.MassStorage, true);
@ConfigurableField(name = "Debug rom", shortName = "debugRom", description = "Use debugger //e rom")
public boolean useDebugRom = false;
@ConfigurableField(name = "Helpful hints", shortName = "hints")
@ -410,7 +410,7 @@ public class Apple2e extends Computer {
int animAddr, animCycleNumber;
byte animOldValue;
final String animation = "+xX*+-";
ScheduledFuture animationSchedule;
ScheduledFuture<?> animationSchedule;
Runnable doAnimation = () -> {
if (animAddr == 0 || animCycleNumber >= animation.length()) {
if (animAddr > 0) {
@ -434,7 +434,7 @@ public class Apple2e extends Computer {
private void enableHints() {
if (hints.isEmpty()) {
hints.add(getMemory().observe(RAMEvent.TYPE.EXECUTE, 0x0FB63, (e)->{
hints.add(getMemory().observe("Helpful hints", RAMEvent.TYPE.EXECUTE, 0x0FB63, (e)->{
animationTimer.schedule(drawHints, 1, TimeUnit.SECONDS);
animationSchedule =
animationTimer.scheduleAtFixedRate(doAnimation, 1250, 100, TimeUnit.MILLISECONDS);

View File

@ -53,9 +53,9 @@ abstract public class RAM128k extends RAM {
Function<Computer, ? extends RAM128k> factory;
String name;
Class clazz;
Class<? extends RAM128k> clazz;
RamCards(String name, Class clazz, Function<Computer, ? extends RAM128k> factory) {
RamCards(String name, Class<? extends RAM128k> clazz, Function<Computer, ? extends RAM128k> factory) {
this.factory = factory;
this.name = name;
this.clazz = clazz;
@ -350,7 +350,7 @@ abstract public class RAM128k extends RAM {
read.setBanks(7, 8, 0x0C8, cPageRom);
}
}
// All ROM reads not intecepted will return 0xFF! (TODO: floating bus)
// All ROM reads not intecepted will return 0xFF!
read.set(0x0c0, blank.get(0));
return read;
}
@ -409,7 +409,7 @@ abstract public class RAM128k extends RAM {
*
*/
@Override
public synchronized void configureActiveMemory() {
public void configureActiveMemory() {
String auxZpConfiguration = getAuxZPConfiguration();
String readConfiguration = getReadConfiguration() + auxZpConfiguration;
String writeConfiguration = getWriteConfiguration() + auxZpConfiguration;

View File

@ -24,6 +24,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Timer;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -37,6 +38,7 @@ import jace.core.RAMEvent;
import jace.core.RAMListener;
import jace.core.SoundGeneratorDevice;
import jace.core.SoundMixer;
import jace.core.SoundMixer.SoundBuffer;
import javafx.stage.FileChooser;
/**
@ -108,23 +110,15 @@ public class Speaker extends SoundGeneratorDevice {
* Manifestation of the apple speaker softswitch
*/
private boolean speakerBit = false;
//
/**
* Locking semaphore to prevent race conditions when working with buffer or
* related variables
*/
private final Object bufferLock = new Object();
/**
* Double-buffer used for playing processed sound -- as one is played the
* other fills up.
*/
private byte[] primaryBuffer;
private byte[] secondaryBuffer;
private int bufferPos = 0;
private Timer playbackTimer;
private double TICKS_PER_SAMPLE = ((double) Motherboard.DEFAULT_SPEED) / SoundMixer.RATE;
private double TICKS_PER_SAMPLE_FLOOR = Math.floor(TICKS_PER_SAMPLE);
private RAMListener listener = null;
private SoundBuffer buffer = null;
/**
* Creates a new instance of Speaker
@ -148,11 +142,14 @@ public class Speaker extends SoundGeneratorDevice {
playbackTimer = null;
}
speakerBit = false;
if (sdl != null && sdl.isOpen()) {
sdl.stop();
sdl.close();
if (buffer != null) {
try {
buffer.shutdown();
} catch (InterruptedException | ExecutionException e) {
// Ignore
}
}
sdl = null;
buffer = null;
return result;
}
@ -162,19 +159,17 @@ public class Speaker extends SoundGeneratorDevice {
*/
@Override
public void resume() {
if (sdl == null || !sdl.isOpen()) {
sdl = computer.mixer.getLine();
if (sdl != null) {
sdl.start();
counter = 0;
idleCycles = 0;
level = 0;
bufferPos = 0;
} else {
Logger.getLogger(getClass().getName()).severe("Unable to get audio line for speaker!");
detach();
return;
}
if (buffer == null || !buffer.isAlive()) {
buffer = SoundMixer.createBuffer(false);
}
if (buffer != null) {
counter = 0;
idleCycles = 0;
level = 0;
} else {
Logger.getLogger(getClass().getName()).severe("Unable to get audio buffer for speaker!");
detach();
return;
}
if (force1mhz) {
@ -187,27 +182,13 @@ public class Speaker extends SoundGeneratorDevice {
setRun(true);
}
public void playCurrentBuffer() {
byte[] buffer;
int len;
synchronized (bufferLock) {
len = bufferPos;
buffer = primaryBuffer;
primaryBuffer = secondaryBuffer;
bufferPos = 0;
}
secondaryBuffer = buffer;
if (sdl != null && len > 0) {
sdl.write(buffer, 0, len);
}
}
/**
* Reset idle counter whenever sound playback occurs
*/
public void resetIdle() {
idleCycles = 0;
if (!isRunning()) {
speakerBit = false;
resume();
}
}
@ -237,28 +218,31 @@ public class Speaker extends SoundGeneratorDevice {
}
private void toggleSpeaker(RAMEvent e) {
if (e.getType() == RAMEvent.TYPE.WRITE) {
level += 2;
}
// if (e.getType() == RAMEvent.TYPE.WRITE) {
// level += 2;
// }
speakerBit = !speakerBit;
resetIdle();
}
private void playSample(int sample) {
if (sdl == null || !sdl.isOpen()) {
resume();
if (buffer == null || !buffer.isAlive()) {
Logger.getLogger(getClass().getName()).severe("Audio buffer not initalized properly!");
buffer = SoundMixer.createBuffer(false);
}
try {
buffer.playSample((short) sample);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
int bytes = SoundMixer.BITS >> 3;
// Prepare sound output in little endian format
for (int i = 0; i < bytes; i++) {
primaryBuffer[i] = primaryBuffer[i+bytes] = (byte) (sample & 0x0ff);
sample >>= 8;
}
sdl.write(primaryBuffer, 0, bytes*2);
if (fileOutputActive) {
byte[] bytes = new byte[2];
bytes[0] = (byte) (sample & 0x0ff);
bytes[1] = (byte) ((sample >> 8) & 0x0ff);
try {
out.write(primaryBuffer, 0, bytes*2);
out.write(bytes, 0, 2);
} catch (IOException ex) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Error recording sound", ex);
toggleFileOutput();
@ -271,7 +255,7 @@ public class Speaker extends SoundGeneratorDevice {
* Add a memory event listener for C03x for capturing speaker events
*/
private void configureListener() {
listener = computer.getMemory().observe(RAMEvent.TYPE.ANY, 0x0c030, 0x0c03f, this::toggleSpeaker);
listener = computer.getMemory().observe("Speaker", RAMEvent.TYPE.ANY, 0x0c030, 0x0c03f, this::toggleSpeaker);
}
private void removeListener() {
@ -295,14 +279,7 @@ public class Speaker extends SoundGeneratorDevice {
@Override
public final void reconfigure() {
super.reconfigure();
if (primaryBuffer != null && secondaryBuffer != null) {
return;
}
BUFFER_SIZE = 20000 * (SoundMixer.BITS >> 3);
primaryBuffer = new byte[BUFFER_SIZE];
secondaryBuffer = new byte[BUFFER_SIZE];
super.reconfigure();
}
@Override

View File

@ -533,7 +533,9 @@ public class VideoDHGR extends Video {
} else {
flashTimer--;
if (flashTimer <= 0) {
markFlashDirtyBits();
if (SoftSwitches.MIXED.isOn() || SoftSwitches.TEXT.isOn()) {
markFlashDirtyBits();
}
flashTimer = FLASH_SPEED;
flashInverse = !flashInverse;
if (flashInverse) {
@ -690,7 +692,6 @@ public class VideoDHGR extends Video {
}
private void markFlashDirtyBits() {
// TODO: Be smarter about detecting where flash is used... one day...
for (int row = 0; row < 192; row++) {
currentTextWriter.markDirty(row);
}
@ -719,8 +720,8 @@ public class VideoDHGR extends Video {
}
private void registerDirtyFlagChecks() {
computer.getMemory().observe(RAMEvent.TYPE.WRITE, 0x0400, 0x0bff, this::registerTextDirtyFlag);
computer.getMemory().observe(RAMEvent.TYPE.WRITE, 0x02000, 0x05fff, this::registerHiresDirtyFlag);
computer.getMemory().observe("Check for text changes", RAMEvent.TYPE.WRITE, 0x0400, 0x0bff, this::registerTextDirtyFlag);
computer.getMemory().observe("Check for graphics changes", RAMEvent.TYPE.WRITE, 0x02000, 0x05fff, this::registerHiresDirtyFlag);
}
@Override

View File

@ -423,11 +423,11 @@ public class VideoNTSC extends VideoDHGR {
return;
}
RAM memory = computer.getMemory();
rgbStateListeners.add(memory.observe(RAMEvent.TYPE.ANY, 0x0c05e, (e) -> {
rgbStateListeners.add(memory.observe("NTSC: AN3 state change", RAMEvent.TYPE.ANY, 0x0c05e, (e) -> {
an3 = false;
rgbStateChange();
}));
rgbStateListeners.add(memory.observe(RAMEvent.TYPE.ANY, 0x0c05f, (e) -> {
rgbStateListeners.add(memory.observe("NTSC: 80COL state change", RAMEvent.TYPE.ANY, 0x0c05f, (e) -> {
if (!an3) {
f2 = f1;
f1 = SoftSwitches._80COL.getState();
@ -435,7 +435,7 @@ public class VideoNTSC extends VideoDHGR {
an3 = true;
rgbStateChange();
}));
rgbStateListeners.add(memory.observe(RAMEvent.TYPE.EXECUTE, 0x0fa62, (e) -> {
rgbStateListeners.add(memory.observe("NTSC: Reset hook for reverting RGB mode", RAMEvent.TYPE.EXECUTE, 0x0fa62, (e) -> {
// When reset hook is called, reset the graphics mode
// This is useful in case a program is running that
// is totally clueless how to set the RGB state correctly.

View File

@ -40,7 +40,7 @@ public class IntC8SoftSwitch extends SoftSwitch {
super("InternalC8Rom", false);
// INTC8Rom should activate whenever C3xx memory is accessed and SLOTC3ROM is off
addListener(
new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
new RAMListener("Softswitch " + getName() + " on", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x0C300);
@ -57,7 +57,7 @@ public class IntC8SoftSwitch extends SoftSwitch {
// INTCXRom shoud deactivate whenever CFFF is accessed
addListener(
new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
new RAMListener("Softswitch " + getName() + " off", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x0CFFF);

View File

@ -1,14 +1,15 @@
package jace.applesoft;
import jace.Emulator;
import jace.ide.Program;
import jace.ide.CompileResult;
import jace.ide.LanguageHandler;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jace.Emulator;
import jace.ide.CompileResult;
import jace.ide.LanguageHandler;
import jace.ide.Program;
/**
*
* @author blurry
@ -41,7 +42,7 @@ public class ApplesoftHandler implements LanguageHandler<ApplesoftProgram> {
@Override
public Map<Integer, String> getErrors() {
return Collections.EMPTY_MAP;
return Collections.emptyMap();
}
@Override
@ -51,12 +52,12 @@ public class ApplesoftHandler implements LanguageHandler<ApplesoftProgram> {
@Override
public List<String> getOtherMessages() {
return Collections.EMPTY_LIST;
return Collections.emptyList();
}
@Override
public List<String> getRawOutput() {
return Collections.EMPTY_LIST;
return Collections.emptyList();
}
};
}

View File

@ -59,9 +59,8 @@ public class ApplesoftProgram {
public static void main(String... args) {
byte[] source = null;
try {
File f = new File("/home/brobert/Documents/Personal/a2gameserver/lib/data/games/LEMONADE#fc0801");
FileInputStream in = new FileInputStream(f);
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) {
@ -202,7 +201,7 @@ public class ApplesoftProgram {
*/
private void whenReady(Runnable r) {
Emulator.withMemory(memory->{
memory.addListener(new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
memory.addListener(new RAMListener("Applesoft: Trap GOTO command", RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(GOTO_CMD);

View File

@ -1,6 +1,10 @@
package jace.assembly;
import org.ibex.nestedvm.UnixRuntime;
// Auto-generated file, don't perform any code lint checks
@SuppressWarnings("all")
/* This file was generated from acme by Mips2Java on Tue Jul 14 00:46:52 CDT 2015 */
public final class AcmeCrossAssembler extends UnixRuntime {

View File

@ -46,9 +46,9 @@ public abstract class Cheats extends Device {
Function<Computer, Cheats> factory;
String name;
Class clazz;
Class<? extends Cheats> clazz;
Cheat(String name, Class clazz, Function<Computer, Cheats> factory) {
Cheat(String name, Class<? extends Cheats> clazz, Function<Computer, Cheats> factory) {
this.name = name;
this.clazz = clazz;
this.factory = factory;
@ -87,36 +87,36 @@ public abstract class Cheats extends Device {
}
}
public RAMListener bypassCode(int address, int addressEnd) {
public RAMListener bypassCode(String name, int address, int addressEnd) {
int noOperation = MOS65C02.COMMAND.NOP.ordinal();
return addCheat(RAMEvent.TYPE.READ, (e) -> e.setNewValue(noOperation), address, addressEnd);
return addCheat(name, RAMEvent.TYPE.READ, (e) -> e.setNewValue(noOperation), address, addressEnd);
}
public RAMListener forceValue(int value, int... address) {
return addCheat(RAMEvent.TYPE.ANY, (e) -> e.setNewValue(value), address);
public RAMListener forceValue(String name, int value, int... address) {
return addCheat(name, RAMEvent.TYPE.ANY, (e) -> e.setNewValue(value), address);
}
public RAMListener forceValue(int value, Boolean auxFlag, int... address) {
return addCheat(RAMEvent.TYPE.ANY, auxFlag, (e) -> e.setNewValue(value), address);
public RAMListener forceValue(String name, int value, Boolean auxFlag, int... address) {
return addCheat(name, RAMEvent.TYPE.ANY, auxFlag, (e) -> e.setNewValue(value), address);
}
public RAMListener addCheat(RAMEvent.TYPE type, RAMEvent.RAMEventHandler handler, int... address) {
public RAMListener addCheat(String name, RAMEvent.TYPE type, RAMEvent.RAMEventHandler handler, int... address) {
RAMListener listener;
if (address.length == 1) {
listener = computer.getMemory().observe(type, address[0], handler);
listener = computer.getMemory().observe(getName() + ": " + name, type, address[0], handler);
} else {
listener = computer.getMemory().observe(type, address[0], address[1], handler);
listener = computer.getMemory().observe(getName() + ": " + name, type, address[0], address[1], handler);
}
listeners.add(listener);
return listener;
}
public RAMListener addCheat(RAMEvent.TYPE type, Boolean auxFlag, RAMEvent.RAMEventHandler handler, int... address) {
public RAMListener addCheat(String name, RAMEvent.TYPE type, Boolean auxFlag, RAMEvent.RAMEventHandler handler, int... address) {
RAMListener listener;
if (address.length == 1) {
listener = computer.getMemory().observe(type, address[0], auxFlag, handler);
listener = computer.getMemory().observe(getName() + ": " + name, type, address[0], auxFlag, handler);
} else {
listener = computer.getMemory().observe(type, address[0], address[1], auxFlag, handler);
listener = computer.getMemory().observe(getName() + ": " + name, type, address[0], address[1], auxFlag, handler);
}
listeners.add(listener);
return listener;

View File

@ -1,5 +1,7 @@
package jace.cheat;
import javax.script.ScriptException;
import jace.core.RAMEvent;
import jace.core.RAMListener;
import javafx.beans.property.BooleanProperty;
@ -9,7 +11,6 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.util.Callback;
import javax.script.ScriptException;
/**
*
@ -21,10 +22,11 @@ public class DynamicCheat extends RAMListener {
StringProperty expression;
BooleanProperty active;
StringProperty name;
String cheatName;
Callback<RAMEvent, Integer> expressionCallback;
public DynamicCheat(int address, String expr) {
super(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY);
public DynamicCheat(String cheatName, int address, String expr) {
super(cheatName, RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY);
id = (int) (Math.random() * 10000000);
addr = new SimpleIntegerProperty(address);
expression = new SimpleStringProperty(expr);
@ -106,18 +108,19 @@ public class DynamicCheat extends RAMListener {
public static final String DELIMITER = ";";
public String serialize() {
return escape(name.get()) + DELIMITER
return escape(cheatName) + DELIMITER + escape(name.get()) + DELIMITER
+ escape("$"+Integer.toHexString(addr.get())) + DELIMITER
+ escape(expression.get());
}
static public DynamicCheat deserialize(String in) {
String[] parts = in.split(DELIMITER);
String name = unescape(parts[0]);
Integer addr = Integer.parseInt(parts[1].substring(1), 16);
String expr = unescape(parts[2]);
String cheatName = unescape(parts[0]);
String name = unescape(parts[1]);
Integer addr = Integer.parseInt(parts[2].substring(1), 16);
String expr = unescape(parts[3]);
DynamicCheat out = new DynamicCheat(addr, expr);
DynamicCheat out = new DynamicCheat(cheatName, addr, expr);
out.name.set(name);
return out;
}

View File

@ -302,7 +302,7 @@ public class MetaCheat extends Cheats {
}
}
if (memoryViewListener == null) {
memoryViewListener = memory.observe(RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent);
memoryViewListener = memory.observe("Metacheat memory viewer", RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent);
listeners.add(memoryViewListener);
}
});

View File

@ -71,21 +71,21 @@ public class MontezumasRevengeCheats extends Cheats {
@Override
public void registerListeners() {
if (repulsiveHack) {
addCheat(RAMEvent.TYPE.WRITE, this::repulsiveBehavior, 0x1508, 0x1518);
addCheat("Repulsive", RAMEvent.TYPE.WRITE, this::repulsiveBehavior, 0x1508, 0x1518);
}
if (featherFall) {
addCheat(RAMEvent.TYPE.WRITE, this::featherFallBehavior, PLAYER_Y);
addCheat("Feather fall", RAMEvent.TYPE.WRITE, this::featherFallBehavior, PLAYER_Y);
// Bypass the part that realizes you should die when you hit the floor
bypassCode(0x6bb3, 0x6bb4);
bypassCode("Feather fall code hack", 0x6bb3, 0x6bb4);
}
if (moonJump) {
addCheat(RAMEvent.TYPE.WRITE, this::moonJumpBehavior, Y_VELOCITY);
addCheat("Moon jump", RAMEvent.TYPE.WRITE, this::moonJumpBehavior, Y_VELOCITY);
}
if (infiniteLives) {
forceValue(11, LIVES);
forceValue("Infinite lives", 11, LIVES);
}
if (safePassage) {
@ -103,22 +103,22 @@ public class MontezumasRevengeCheats extends Cheats {
memory.write(0x0f51, (byte) 0b00001000, false, false);
memory.write(0x0f52, (byte) 0b10000100, false, false);
memory.write(0x0f53, (byte) 0b11010101, false, false);
forceValue(32, FLOOR_TIMER);
forceValue(32, HAZARD_TIMER);
forceValue(1, HAZARD_FLAG);
forceValue("Hack floor timer", 32, FLOOR_TIMER);
forceValue("Hack hazard timer", 32, HAZARD_TIMER);
forceValue("Hack hazard flag", 1, HAZARD_FLAG);
});
}
if (scoreHack) {
// Score: 900913
forceValue(0x90, SCORE);
forceValue(0x09, SCORE + 1);
forceValue(0x13, SCORE + 2);
forceValue("Hack score 1", 0x90, SCORE);
forceValue("Hack score 2", 0x09, SCORE + 1);
forceValue("Hack score 3", 0x13, SCORE + 2);
}
if (snakeCharmer) {
// Skip the code that determines you're touching an enemy
bypassCode(0x07963, 0x07964);
bypassCode("Snake charmer", 0x07963, 0x07964);
}
if (mouseHack) {
EmulatorUILogic.addMouseListener(listener);

View File

@ -172,19 +172,19 @@ public class PrinceOfPersiaCheats extends Cheats {
@Override
public void registerListeners() {
if (velocityHack) {
addCheat(RAMEvent.TYPE.READ_DATA, true, this::velocityHackBehavior, CharYVel);
addCheat("Hack velocity", RAMEvent.TYPE.READ_DATA, true, this::velocityHackBehavior, CharYVel);
}
if (invincibilityHack) {
forceValue(3, true, KidStrength);
forceValue("Hack invincibility", 3, true, KidStrength);
}
if (sleepHack) {
forceValue(0, true, EnemyAlert);
forceValue("Go to sleep!", 0, true, EnemyAlert);
}
if (swordHack) {
forceValue(1, true, hasSword);
forceValue("Can haz sword", 1, true, hasSword);
}
if (timeHack) {
forceValue(0x69, true, MinLeft);
forceValue("Hack time", 0x69, true, MinLeft);
}
if (mouseHack) {
EmulatorUILogic.addMouseListener(listener);
@ -290,7 +290,7 @@ public class PrinceOfPersiaCheats extends Cheats {
}
currentScrn = (byte) scrnLeft;
byte prev = auxMem.readByte(PREV + row);
byte sprev = auxMem.readByte(SPREV + row);
// byte sprev = auxMem.readByte(SPREV + row);
// If the block to the left is gate, let's lie about it being open... for science
// This causes odd-looking screen behavior but it gets the job done.
if (prev == 4) {

View File

@ -62,6 +62,11 @@ import javafx.scene.image.ImageView;
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class Configuration implements Reconfigurable {
public EmulatorUILogic ui;
public Configuration() {
ui = Emulator.getUILogic();
}
static ConfigurableField getConfigurableFieldInfo(Reconfigurable subject, String settingName) {
Field f;
@ -104,6 +109,7 @@ public class Configuration implements Reconfigurable {
* configuration 2) Provide a simple persistence mechanism to load/store
* configuration
*/
@SuppressWarnings("all")
public static class ConfigNode extends TreeItem implements Serializable {
public transient ConfigNode root;
@ -136,8 +142,8 @@ public class Configuration implements Reconfigurable {
children.setAll(super.getChildren());
id = (String) in.readObject();
name = (String) in.readObject();
settings = (Map) in.readObject();
hotkeys = (Map) in.readObject();
settings = (Map<String, Serializable>) in.readObject();
hotkeys = (Map<String, String[]>) in.readObject();
Object[] nodeArray = (Object[]) in.readObject();
synchronized (children) {
for (Object child : nodeArray) {
@ -262,13 +268,12 @@ public class Configuration implements Reconfigurable {
}
}
public static ConfigNode BASE;
public static EmulatorUILogic ui = Emulator.logic;
@ConfigurableField(name = "Autosave Changes", description = "If unchecked, changes are only saved when the Save button is pressed.")
public static boolean saveAutomatically = false;
public static void buildTree() {
BASE = new ConfigNode(new Configuration());
Set visited = new LinkedHashSet();
Set<ConfigNode> visited = new LinkedHashSet<>();
buildTree(BASE, visited);
Emulator.withComputer(c->{
ConfigNode computer = new ConfigNode(BASE, c);
@ -277,6 +282,7 @@ public class Configuration implements Reconfigurable {
});
}
@SuppressWarnings("all")
private static void buildTree(ConfigNode node, Set visited) {
if (node.subject == null) {
return;
@ -512,6 +518,7 @@ public class Configuration implements Reconfigurable {
return hasChanged.get();
}
@SuppressWarnings("all")
private static void applyConfigTree(ConfigNode newRoot, ConfigNode oldRoot) {
if (oldRoot == null || newRoot == null) {
return;
@ -533,6 +540,7 @@ public class Configuration implements Reconfigurable {
});
}
@SuppressWarnings("all")
private static void doApply(ConfigNode node) {
List<String> removeList = new ArrayList<>();
registerKeyHandlers(node, false);
@ -600,7 +608,7 @@ public class Configuration implements Reconfigurable {
String fieldName = parts[1];
ConfigNode n = shortNames.get(deviceName.toLowerCase());
if (n == null) {
System.err.println("Unable to find device named " + deviceName + ", try one of these: " + Utility.join(shortNames.keySet(), ", "));
System.err.println("Unable to find device named " + deviceName + ", try one of these: " + String.join(", ", shortNames.keySet()));
continue;
}
@ -622,7 +630,7 @@ public class Configuration implements Reconfigurable {
}
}
if (!found) {
System.err.println("Unable to find property " + fieldName + " for device " + deviceName + ". Try one of these: " + Utility.join(shortFieldNames, ", "));
System.err.println("Unable to find property " + fieldName + " for device " + deviceName + ". Try one of these: " + String.join(", ", shortFieldNames));
}
}
}

View File

@ -93,6 +93,7 @@ public class ConfigurationUIController {
deviceTree.maxWidthProperty().bind(treeScroll.widthProperty());
}
@SuppressWarnings("all")
private void resetDeviceTree() {
Set<String> expanded = new HashSet<>();
String current = getCurrentNodePath();
@ -133,6 +134,7 @@ public class ConfigurationUIController {
return out;
}
@SuppressWarnings("all")
private void setCurrentNodePath(String value) {
if (value == null) return;
String[] parts = value.split(Pattern.quote(DELIMITER));
@ -213,6 +215,7 @@ public class ConfigurationUIController {
throw new UnsupportedOperationException("Not supported yet.");
}
@SuppressWarnings("all")
private Node buildEditField(ConfigNode node, String settingName, Serializable value) {
Field field;
try {
@ -235,8 +238,6 @@ public class ConfigurationUIController {
}
} else if (type.equals(File.class)) {
// TODO: Add file support!
} else if (Class.class.isEnum()) {
// TODO: Add enumeration support!
} else if (ISelection.class.isAssignableFrom(type)) {
return buildDynamicSelectComponent(node, settingName, value);
}
@ -256,6 +257,7 @@ public class ConfigurationUIController {
return widget;
}
@SuppressWarnings("all")
private Node buildDynamicSelectComponent(ConfigNode node, String settingName, Serializable value) {
try {
DynamicSelection sel = (DynamicSelection) node.subject.getClass().getField(settingName).get(node.subject);

View File

@ -30,6 +30,7 @@ import java.util.List;
* @param <C> Enum class which implements DeviceEnum
*/
// C is an enum class which implements DeviceEnum
@SuppressWarnings("all")
public class DeviceSelection<C extends Enum & DeviceEnum> extends DynamicSelection<C> {
Class<C> enumClass;

View File

@ -12,6 +12,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Logger;
@SuppressWarnings("all")
public abstract class InvokableActionRegistry {
protected static final Logger logger = Logger.getLogger(InvokableActionRegistry.class.getName());
private final Map<Class, Set<String>> staticMethodNames = new HashMap<>();

View File

@ -18,7 +18,6 @@
*/
package jace.core;
import jace.Emulator;
import jace.apple2e.SoftSwitches;
/**
@ -103,10 +102,10 @@ public abstract class Card extends Device {
@Override
public void reconfigure() {
//super.reconfigure();
Emulator.whileSuspended(c-> {
// Emulator.whileSuspended(c-> {
unregisterListeners();
registerListeners();
});
// });
}
public void notifyVBLStateChanged(boolean state) {
@ -121,12 +120,12 @@ public abstract class Card extends Device {
RAM memory = computer.getMemory();
int baseIO = 0x0c080 + slot * 16;
int baseRom = 0x0c000 + slot * 256;
ioListener = memory.observe(RAMEvent.TYPE.ANY, baseIO, baseIO + 15, (e) -> {
ioListener = memory.observe("Slot " + getSlot() + " " + getDeviceName() + " IO access", RAMEvent.TYPE.ANY, baseIO, baseIO + 15, (e) -> {
int address = e.getAddress() & 0x0f;
handleIOAccess(address, e.getType(), e.getNewValue(), e);
});
firmwareListener = memory.observe(RAMEvent.TYPE.ANY, baseRom, baseRom + 255, (e) -> {
firmwareListener = memory.observe("Slot " + getSlot() + " " + getDeviceName() + " CX Firmware access", RAMEvent.TYPE.ANY, baseRom, baseRom + 255, (e) -> {
computer.getMemory().setActiveCard(slot);
// Sather 6-4: Writes will still go through even when CXROM inhibits slot ROM
if (SoftSwitches.CXROM.isOff() || !e.getType().isRead()) {
@ -134,7 +133,7 @@ public abstract class Card extends Device {
}
});
c8firmwareListener = memory.observe(RAMEvent.TYPE.ANY, 0xc800, 0xcfff, (e) -> {
c8firmwareListener = memory.observe("Slot " + getSlot() + " " + getDeviceName() + " C8 Firmware access", RAMEvent.TYPE.ANY, 0xc800, 0xcfff, (e) -> {
if (SoftSwitches.CXROM.isOff() && SoftSwitches.INTC8ROM.isOff()
&& computer.getMemory().getActiveSlot() == slot) {
handleC8FirmwareAccess(e.getAddress() - 0x0c800, e.getType(), e.getNewValue(), e);

View File

@ -38,7 +38,7 @@ public class Motherboard extends TimedDevice {
@ConfigurableField(name = "Enable Speaker", shortName = "speaker", defaultValue = "true")
public static boolean enableSpeaker = true;
public Speaker speaker;
private Speaker speaker;
void vblankEnd() {
SoftSwitches.VBL.getSwitch().setState(true);
@ -135,6 +135,7 @@ public class Motherboard extends TimedDevice {
addChildDevice(speaker);
} catch (Throwable t) {
System.out.println("Unable to initalize sound -- deactivating speaker out");
t.printStackTrace();
}
} else {
System.out.println("Speaker not enabled, leaving it off.");

View File

@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentSkipListSet;
import java.util.function.Consumer;
import java.util.function.Supplier;
import jace.Emulator;
import jace.apple2e.SoftSwitches;
import jace.config.Reconfigurable;
@ -56,6 +57,7 @@ public abstract class RAM implements Reconfigurable {
*
* @param computer
*/
@SuppressWarnings("unchecked")
public RAM(Computer computer) {
this.computer = computer;
listeners = new ConcurrentSkipListSet<>();
@ -95,11 +97,10 @@ public abstract class RAM implements Reconfigurable {
cards[slot] = Optional.of(c);
c.setSlot(slot);
c.attach();
configureActiveMemory();
}
public void removeCard(Card c) {
c.suspend();
c.detach();
removeCard(c.getSlot());
}
@ -198,14 +199,15 @@ public abstract class RAM implements Reconfigurable {
}
}
@SuppressWarnings("unchecked")
private void refreshListenerMap() {
listenerMap = (Set<RAMListener>[]) new Set[256];
ioListenerMap = (Set<RAMListener>[]) new Set[256];
listeners.forEach(this::addListenerRange);
}
}
public RAMListener observe(RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(address);
@ -218,8 +220,8 @@ public abstract class RAM implements Reconfigurable {
});
}
public RAMListener observe(RAMEvent.TYPE type, int address, Boolean auxFlag, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int address, Boolean auxFlag, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(address);
@ -234,8 +236,8 @@ public abstract class RAM implements Reconfigurable {
});
}
public RAMListener observe(RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(addressStart);
@ -249,8 +251,8 @@ public abstract class RAM implements Reconfigurable {
});
}
public RAMListener observe(RAMEvent.TYPE type, int addressStart, int addressEnd, Boolean auxFlag, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int addressStart, int addressEnd, Boolean auxFlag, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(addressStart);
@ -280,14 +282,12 @@ public abstract class RAM implements Reconfigurable {
return l;
}
listeners.add(l);
computer.cpu.whileSuspended(()->{
addListenerRange(l);
});
Emulator.whileSuspended((c)->addListenerRange(l));
return l;
}
public RAMListener addExecutionTrap(int address, Consumer<RAMEvent> handler) {
RAMListener listener = new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
public RAMListener addExecutionTrap(String observerationName, int address, Consumer<RAMEvent> handler) {
RAMListener listener = new RAMListener(observerationName, RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(address);
@ -307,9 +307,7 @@ public abstract class RAM implements Reconfigurable {
return;
}
listeners.remove(l);
computer.cpu.whileSuspended(()->{
refreshListenerMap();
});
Emulator.whileSuspended(c->refreshListenerMap());
}
private byte _callListener(RAMEvent.TYPE t, int address, int oldValue, int newValue) {
@ -325,6 +323,9 @@ public abstract class RAM implements Reconfigurable {
if (activeListeners != null && !activeListeners.isEmpty()) {
RAMEvent e = new RAMEvent(t, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, address, oldValue, newValue);
activeListeners.forEach((l) -> l.handleEvent(e));
if (!e.isIntercepted() && (address & 0x0FF00) == 0x0C000) {
return computer.getVideo().getFloatingBus();
}
return (byte) e.getNewValue();
}
return (byte) newValue;

View File

@ -74,6 +74,7 @@ public class RAMEvent {
private SCOPE scope;
private VALUE value;
private int address, oldValue, newValue;
private boolean valueIntercepted = false;
/**
* Creates a new instance of RAMEvent
@ -142,5 +143,10 @@ public class RAMEvent {
public final void setNewValue(int newValue) {
this.newValue = newValue;
valueIntercepted = true;
}
public final boolean isIntercepted() {
return valueIntercepted;
}
}

View File

@ -39,14 +39,17 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler, Comparabl
private int valueStart;
private int valueEnd;
private int valueAmount;
private String name;
/**
* Creates a new instance of RAMListener
* @param name
* @param t
* @param s
* @param v
*/
public RAMListener(RAMEvent.TYPE t, RAMEvent.SCOPE s, RAMEvent.VALUE v) {
public RAMListener(String name, RAMEvent.TYPE t, RAMEvent.SCOPE s, RAMEvent.VALUE v) {
setName(name);
setType(t);
setScope(s);
setValue(v);
@ -77,6 +80,14 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler, Comparabl
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScopeStart() {
return scopeStart;
}
@ -161,18 +172,23 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler, Comparabl
@Override
public int compareTo(RAMListener o) {
if (o.scopeStart == scopeStart) {
if (o.scopeEnd == scopeEnd) {
if (o.type == type) {
return Integer.compare(o.hashCode(), hashCode());
if (o.name == name) {
if (o.scopeStart == scopeStart) {
if (o.scopeEnd == scopeEnd) {
if (o.type == type) {
// Ignore hash codes -- combination of name, address range and type should identify similar listeners.
return (int) 0;
} else {
return Integer.compare(o.type.ordinal(), type.ordinal());
}
} else {
return Integer.compare(o.type.ordinal(), type.ordinal());
return Integer.compare(o.scopeEnd, scopeEnd);
}
} else {
return Integer.compare(o.scopeEnd, scopeEnd);
return Integer.compare(o.scopeStart, scopeStart);
}
} else {
return Integer.compare(o.scopeStart, scopeStart);
return o.name.compareTo(name);
}
}
}

View File

@ -106,7 +106,7 @@ public abstract class SoftSwitch {
exclusionActivate.add(i);
}
}
RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
RAMListener l = new RAMListener("Softswitch toggle " + name, changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(beginAddr);
@ -136,7 +136,7 @@ public abstract class SoftSwitch {
exclusionActivate.add(i);
}
}
RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
RAMListener l = new RAMListener("Softswitch on " + name, changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(beginAddr);
@ -169,7 +169,7 @@ public abstract class SoftSwitch {
exclusionDeactivate.add(i);
}
}
RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
RAMListener l = new RAMListener("Softswitch off " + name, changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(beginAddr);
@ -201,7 +201,7 @@ public abstract class SoftSwitch {
}
}
// RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
RAMListener l = new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
RAMListener l = new RAMListener("Softswitch read state " + name, RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(beginAddr);

View File

@ -18,14 +18,25 @@
*/
package jace.core;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Logger;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.ALC;
import org.lwjgl.openal.ALC10;
import org.lwjgl.openal.ALCCapabilities;
import org.lwjgl.openal.ALCapabilities;
import jace.Emulator;
import jace.config.ConfigurableField;
/**
@ -41,8 +52,9 @@ public class SoundMixer extends Device {
/**
* Bits per sample
* Making this configurable requires too much effort and not a lot of benefit
*/
@ConfigurableField(name = "Bits per sample", shortName = "bits")
// @ConfigurableField(name = "Bits per sample", shortName = "bits")
public static int BITS = 16;
/**
* Sample playback rate
@ -51,38 +63,208 @@ public class SoundMixer extends Device {
public static int RATE = 48000;
@ConfigurableField(name = "Mute", shortName = "mute")
public static boolean MUTE = false;
@ConfigurableField(name = "Buffer size", shortName = "buffer")
public static int BUFFER_SIZE = 512;
public static boolean PLAYBACK_ENABLED = false;
// Innocent until proven guilty by a failed initialization
public static boolean PLAYBACK_DRIVER_DETECTED = true;
public static boolean PLAYBACK_INITIALIZED = false;
private final String defaultDeviceName;
private long audioDevice;
private long audioContext;
private ALCCapabilities audioCapabilities;
private ALCapabilities audioLibCapabilities;
private static String defaultDeviceName;
private static long audioDevice = -1;
private static long audioContext = -1;
private static ALCCapabilities audioCapabilities;
private static ALCapabilities audioLibCapabilities;
// In case the OpenAL implementation wants to be run in a single thread, use a single thread executor
protected static ExecutorService soundThreadExecutor = Executors.newSingleThreadExecutor();
public SoundMixer(Computer computer) {
super(computer);
defaultDeviceName = ALC10.alcGetString(0, ALC10.ALC_DEFAULT_DEVICE_SPECIFIER);
defaultDeviceName = ALC10.alcGetString(0, ALC10.ALC_DEFAULT_DEVICE_SPECIFIER);
}
public static <T> T performSoundFunction(Callable<T> operation) {
return performSoundFunction(operation, false);
}
public static <T> T performSoundFunction(Callable<T> operation, boolean ignoreError) {
Future<T> result = soundThreadExecutor.submit(operation);
try {
if (!ignoreError) {
Future<Integer> error = soundThreadExecutor.submit(AL10::alGetError);
int err;
err = error.get();
if (err != AL10.AL_NO_ERROR) {
throw new RuntimeException(AL10.alGetString(err));
}
}
return result.get();
} catch (ExecutionException e) {
System.out.println("Error when executing sound action: " + e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
// Do nothing: sound is probably being reset
}
return null;
}
public static void performSoundOperation(Runnable operation) {
performSoundOperation(operation, false);
}
public static void performSoundOperation(Runnable operation, boolean ignoreError) {
performSoundFunction(()->{
operation.run();
return null;
}, ignoreError);
}
public static void performSoundOperationAsync(Runnable operation) {
soundThreadExecutor.submit(operation);
}
protected static void initSound() {
performSoundOperation(()->{
if (!PLAYBACK_INITIALIZED) {
audioDevice = ALC10.alcOpenDevice(defaultDeviceName);
audioContext = ALC10.alcCreateContext(audioDevice, new int[]{0});
ALC10.alcMakeContextCurrent(audioContext);
audioCapabilities = ALC.createCapabilities(audioDevice);
audioLibCapabilities = AL.createCapabilities(audioCapabilities);
if (!audioLibCapabilities.OpenAL10) {
PLAYBACK_DRIVER_DETECTED = false;
Logger.getLogger(SoundMixer.class.getName()).warning("OpenAL 1.0 not supported");
Emulator.withComputer(c->c.mixer.detach());
}
PLAYBACK_INITIALIZED = true;
} else {
ALC10.alcMakeContextCurrent(audioContext);
}
});
}
// Lots of inspiration from https://www.youtube.com/watch?v=dLrqBTeipwg
@Override
public void attach() {
if (!PLAYBACK_DRIVER_DETECTED) {
Logger.getLogger(SoundMixer.class.getName()).warning("Sound driver not detected");
return;
}
super.attach();
audioDevice = ALC10.alcOpenDevice(defaultDeviceName);
// TODO: Other attributes?
audioContext = ALC10.alcCreateContext(audioDevice, new int[]{0});
ALC10.alcMakeContextCurrent(audioContext);
audioCapabilities = ALC.createCapabilities(audioDevice);
audioLibCapabilities = AL.createCapabilities(audioCapabilities);
if (!audioLibCapabilities.OpenAL10) {
Logger.getLogger(SoundMixer.class.getName()).warning("OpenAL 1.0 not supported");
detach();
initSound();
PLAYBACK_ENABLED = true;
}
private static List<SoundBuffer> buffers = new ArrayList<>();
public static SoundBuffer createBuffer(boolean stereo) {
if (!PLAYBACK_ENABLED) {
return null;
}
SoundBuffer buffer = new SoundBuffer(stereo);
buffers.add(buffer);
return buffer;
}
public static class SoundBuffer {
public static int MAX_BUFFER_ID;
private ShortBuffer currentBuffer;
private ShortBuffer alternateBuffer;
private int audioFormat;
private int currentBufferId;
private int alternateBufferId;
private int sourceId;
private boolean isAlive;
private int buffersGenerated = 0;
public SoundBuffer(boolean stereo) {
initSound();
currentBuffer = BufferUtils.createShortBuffer(BUFFER_SIZE * (stereo ? 2 : 1));
alternateBuffer = BufferUtils.createShortBuffer(BUFFER_SIZE * (stereo ? 2 : 1));
currentBufferId = performSoundFunction(AL10::alGenBuffers);
alternateBufferId = performSoundFunction(AL10::alGenBuffers);
sourceId = performSoundFunction(AL10::alGenSources);
audioFormat = stereo ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16;
isAlive = true;
}
public boolean isAlive() {
return isAlive;
}
/* If stereo, call this once for left and then again for right sample */
public void playSample(short sample) throws InterruptedException, ExecutionException {
if (!isAlive) {
Logger.getLogger(SoundMixer.class.getName()).warning("Playback attempted on stopped buffer!");
return;
}
if (!currentBuffer.hasRemaining()) {
buffersGenerated++;
currentBuffer.flip();
if (buffersGenerated > 2) {
int[] unqueueBuffers = new int[]{currentBufferId};
performSoundOperation(()->{
int buffersProcessed = AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED);
while (buffersProcessed < 1) {
Thread.onSpinWait();
buffersProcessed = AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED);
}
});
performSoundOperation(()->{
AL10.alSourceUnqueueBuffers(sourceId, unqueueBuffers);
});
}
performSoundOperation(()->AL10.alBufferData(currentBufferId, audioFormat, currentBuffer, RATE));
performSoundOperation(()->AL10.alSourceQueueBuffers(sourceId, currentBufferId));
performSoundOperationAsync(()->{
if (AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING) {
AL10.alSourcePlay(sourceId);
}
});
// Swap AL buffers
int tempId = currentBufferId;
currentBufferId = alternateBufferId;
alternateBufferId = tempId;
// Swap Java buffers
ShortBuffer tempBuffer = currentBuffer;
currentBuffer = alternateBuffer;
alternateBuffer = tempBuffer;
}
currentBuffer.put(sample);
}
public void shutdown() throws InterruptedException, ExecutionException {
if (!isAlive) {
return;
}
isAlive = false;
performSoundOperation(()->AL10.alSourceStop(sourceId));
performSoundOperation(()->AL10.alDeleteSources(sourceId));
performSoundOperation(()->AL10.alDeleteBuffers(alternateBufferId));
performSoundOperation(()->AL10.alDeleteBuffers(currentBufferId));
}
}
@Override
public void detach() {
ALC10.alcDestroyContext(audioContext);
ALC10.alcCloseDevice(audioDevice);
if (!PLAYBACK_ENABLED) {
return;
}
MUTE = true;
PLAYBACK_ENABLED = false;
for (SoundBuffer buffer : buffers) {
try {
buffer.shutdown();
} catch (InterruptedException | ExecutionException e) {
Logger.getLogger(SoundMixer.class.getName()).warning("Error when detaching sound mixer: " + e.getMessage());
}
}
buffers.clear();
PLAYBACK_INITIALIZED = false;
performSoundOperation(()->ALC10.alcDestroyContext(audioContext), true);
performSoundOperation(()->ALC10.alcCloseDevice(audioDevice), true);
super.detach();
}
@ -100,10 +282,11 @@ public class SoundMixer extends Device {
@Override
public synchronized void reconfigure() {
if (MUTE) {
detach();
} else {
PLAYBACK_ENABLED = PLAYBACK_DRIVER_DETECTED && !MUTE;
if (PLAYBACK_ENABLED) {
attach();
} else {
detach();
}
}

View File

@ -108,7 +108,7 @@ public abstract class TimedDevice extends Device {
if (worker != null && worker.isAlive()) {
return;
}
worker = new Thread(() -> {
Thread newWorker = new Thread(() -> {
// System.out.println("Worker thread for " + getDeviceName() + " starting");
while (isRunning()) {
hasStopped = false;
@ -124,10 +124,11 @@ public abstract class TimedDevice extends Device {
hasStopped = true;
// System.out.println("Worker thread for " + getDeviceName() + " stopped");
});
worker.setDaemon(false);
worker.setPriority(Thread.MAX_PRIORITY);
worker.start();
worker.setName("Timed device " + getDeviceName() + " worker");
this.worker = newWorker;
newWorker.setDaemon(false);
newWorker.setPriority(Thread.MAX_PRIORITY);
newWorker.setName("Timed device " + getDeviceName() + " worker");
newWorker.start();
}
long nanosPerInterval; // How long to wait between pauses
long cyclesPerInterval; // How many cycles to wait until a pause interval

View File

@ -136,11 +136,6 @@ public class Utility {
return score * adjustment * adjustment;
}
@Deprecated
public static String join(Collection<String> c, String d) {
return String.join(d, c);
}
private static boolean isHeadless = false;
public static void setHeadlessMode(boolean headless) {
@ -341,8 +336,10 @@ public class Utility {
});
}
@SuppressWarnings("all")
static Map<Class, Map<String, Object>> enumCache = new HashMap<>();
@SuppressWarnings("all")
public static Object findClosestEnumConstant(String value, Class type) {
Map<String, Object> enumConstants = enumCache.get(type);
if (enumConstants == null) {
@ -361,6 +358,7 @@ public class Utility {
return enumConstants.get(key);
}
@SuppressWarnings("all")
public static Object deserializeString(String value, Class type, boolean hex) {
int radix = hex ? 16 : 10;
if (type.equals(Integer.TYPE) || type == Integer.class) {
@ -404,7 +402,7 @@ public class Utility {
public static Function<Boolean, Boolean> getNamedInvokableAction(String action) {
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
List<InvokableAction> actionsList = new ArrayList(registry.getAllStaticActions());
List<InvokableAction> actionsList = new ArrayList<>(registry.getAllStaticActions());
actionsList.sort((a, b) -> Integer.compare(getActionNameMatch(action, a), getActionNameMatch(action, b)));
// for (InvokableAction a : actionsList) {
// String actionName = a.alternatives() == null ? a.name() : (a.name() + ";" + a.alternatives());

View File

@ -27,7 +27,6 @@ import java.util.logging.Logger;
import jace.EmulatorUILogic;
import jace.config.ConfigurableField;
import jace.config.Name;
import jace.config.Reconfigurable;
import jace.core.Card;
import jace.core.Computer;
import jace.core.RAMEvent;
@ -46,7 +45,7 @@ import jace.library.MediaConsumerParent;
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
@Name("Disk ][ Controller")
public class CardDiskII extends Card implements Reconfigurable, MediaConsumerParent {
public class CardDiskII extends Card implements MediaConsumerParent {
DiskIIDrive currentDrive;
DiskIIDrive drive1 = new DiskIIDrive(computer);
@ -128,7 +127,8 @@ public class CardDiskII extends Card implements Reconfigurable, MediaConsumerPar
case 0xC:
// read/write latch
currentDrive.write();
e.setNewValue(currentDrive.readLatch());
int latch = currentDrive.readLatch();
e.setNewValue(latch);
break;
case 0xF:
// write mode

View File

@ -18,6 +18,7 @@
*/
package jace.hardware;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@ -26,8 +27,6 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.SourceDataLine;
import jace.config.ConfigurableField;
import jace.config.Name;
import jace.core.Card;
@ -35,6 +34,7 @@ import jace.core.Computer;
import jace.core.Motherboard;
import jace.core.RAMEvent;
import jace.core.RAMEvent.TYPE;
import jace.core.SoundMixer.SoundBuffer;
import jace.core.RAMListener;
import jace.core.SoundMixer;
import jace.hardware.mockingboard.PSG;
@ -254,8 +254,6 @@ public class CardMockingboard extends Card implements Runnable {
///////////////////////////////////////////////////////////
public static int[] VolTable;
int[][] buffers;
int bufferLength = -1;
public void playSound(int[] left, int[] right) {
chips[0].update(left, true, left, false, left, false, BUFFER_LENGTH);
@ -277,7 +275,7 @@ public class CardMockingboard extends Card implements Runnable {
double out = (MAX_AMPLITUDE * volume) / 100.0;
// Reduce max amplitude to reflect post-mixer values so we don't have to scale volume when mixing channels
out = out * 2.0 / 3.0 / numChips;
double delta = 1.15;
// double delta = 1.15;
for (int i = 15; i > 0; i--) {
VolTable[i] = (int) (out / Math.pow(Math.sqrt(2),(15-i)));
// out /= 1.188502227; /* = 10 ^ (1.5/20) = 1.5dB */
@ -326,14 +324,13 @@ public class CardMockingboard extends Card implements Runnable {
}
public boolean suspendSound() {
setRun(false);
if (playbackThread == null || !playbackThread.isAlive()) {
return false;
}
if (playbackThread != null) {
playbackThread.interrupt();
try {
// Wait for thread to die
playbackThread.join();
playbackThread.join(500);
} catch (InterruptedException ex) {
}
}
@ -346,19 +343,16 @@ public class CardMockingboard extends Card implements Runnable {
* This is the audio playback thread
*/
public void run() {
SourceDataLine out = null;
SoundBuffer buffer = SoundMixer.createBuffer(true);
try {
out = computer.mixer.getLine();
if (out == null) {
if (buffer == null) {
setRun(false);
return;
}
int[] leftBuffer = new int[BUFFER_LENGTH];
int[] rightBuffer = new int[BUFFER_LENGTH];
int frameSize = out.getFormat().getFrameSize();
byte[] buffer = new byte[BUFFER_LENGTH * frameSize];
System.out.println("Mockingboard playback started");
int bytesPerSample = frameSize / 2;
int bufferSize = SoundMixer.BUFFER_SIZE;
int[] left = new int[bufferSize];
int[] right = new int[bufferSize];
buildMixerTable();
ticksBetweenPlayback = (int) ((Motherboard.DEFAULT_SPEED * BUFFER_LENGTH) / SAMPLE_RATE);
System.out.println("Ticks between playback: "+ticksBetweenPlayback);
@ -371,30 +365,27 @@ public class CardMockingboard extends Card implements Runnable {
Thread.sleep(1000);
}
if (isRunning() && !Thread.interrupted()) {
playSound(leftBuffer, rightBuffer);
int p = 0;
for (int idx = 0; idx < BUFFER_LENGTH; idx++) {
int sampleL = leftBuffer[idx];
int sampleR = rightBuffer[idx];
// Convert left + right samples into buffer format
if (sampleL == 0 && sampleR == 0) {
zeroSamples++;
} else {
zeroSamples = 0;
}
for (int shift = SoundMixer.BITS - 8, index = 0; shift >= 0; shift -= 8, index++) {
buffer[p + index] = (byte) (sampleR >> shift);
buffer[p + index + bytesPerSample] = (byte) (sampleL >> shift);
}
p += frameSize;
}
playSound(left, right);
try {
for (int i=0; i < bufferSize; i++) {
buffer.playSample((short) left[i]);
buffer.playSample((short) right[i]);
}
timerSync.lock();
ticksSinceLastPlayback -= ticksBetweenPlayback;
} catch (ExecutionException e) {
Logger.getLogger(CardMockingboard.class.getName()).log(Level.SEVERE, "Mockingboard playback encountered fatal exception", e);
try {
buffer.shutdown();
} catch (ExecutionException e1) {
// Ignore shutdown errors, we're already reporting a fatal error
}
buffer=null;
setRun(false);
break;
} finally {
timerSync.unlock();
}
out.write(buffer, 0, buffer.length);
if (zeroSamples >= MAX_IDLE_SAMPLES) {
zeroSamples = 0;
pause = true;
@ -437,9 +428,12 @@ public class CardMockingboard extends Card implements Runnable {
} finally {
computer.getMotherboard().cancelSpeedRequest(this);
System.out.println("Mockingboard playback stopped");
if (out != null && out.isRunning()) {
out.drain();
out.close();
if (buffer != null && buffer.isAlive()) {
try {
buffer.shutdown();
} catch (InterruptedException | ExecutionException e) {
// Ignore errors during shutdown
}
}
}
}

View File

@ -140,7 +140,7 @@ public class CardRamworks extends RAM128k {
private RAMListener bankSelectListener;
@Override
public void attach() {
bankSelectListener = observe(RAMEvent.TYPE.WRITE, BANK_SELECT, (e) -> {
bankSelectListener = observe("Ramworks bank select", RAMEvent.TYPE.WRITE, BANK_SELECT, (e) -> {
currentBank = e.getNewValue();
configureActiveMemory();
});

View File

@ -31,7 +31,6 @@ import java.util.logging.Logger;
import jace.EmulatorUILogic;
import jace.config.ConfigurableField;
import jace.config.Name;
import jace.config.Reconfigurable;
import jace.core.Card;
import jace.core.Computer;
import jace.core.RAMEvent;
@ -46,7 +45,7 @@ import javafx.scene.control.Label;
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
@Name("Super Serial Card")
public class CardSSC extends Card implements Reconfigurable {
public class CardSSC extends Card {
@ConfigurableField(name = "TCP/IP Port", shortName = "port")
public short IP_PORT = 1977;
@ -217,6 +216,7 @@ public class CardSSC extends Card implements Reconfigurable {
try {
int newValue = -1;
switch (type) {
case ANY:
case EXECUTE:
case READ_OPERAND:
case READ_DATA:

View File

@ -19,9 +19,9 @@ public enum Cards implements DeviceEnum<Card> {
Function<Computer, Card> factory;
String name;
Class clazz;
Class<? extends Card> clazz;
Cards(String name, Class clazz, Function<Computer, Card> factory) {
Cards(String name, Class<? extends Card> clazz, Function<Computer, Card> factory) {
this.name = name;
this.factory = factory;
this.clazz = clazz;

View File

@ -18,6 +18,14 @@
*/
package jace.hardware;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import jace.EmulatorUILogic;
import jace.core.Computer;
import jace.library.MediaConsumer;
@ -25,12 +33,6 @@ import jace.library.MediaEntry;
import jace.library.MediaEntry.MediaFile;
import jace.state.StateManager;
import jace.state.Stateful;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.locks.LockSupport;
import javafx.scene.control.Label;
/**
@ -50,11 +52,13 @@ public class DiskIIDrive implements MediaConsumer {
this.computer = computer;
}
public boolean DEBUG = true;
FloppyDisk disk;
// Number of milliseconds to wait between last write and update to disk image
public static long WRITE_UPDATE_DELAY = 1000;
// Flag to halt if any writes to floopy occur when updating physical disk image
boolean diskUpdatePending = false;
AtomicBoolean diskUpdatePending = new AtomicBoolean();
// Last time of write operation
long lastWriteTime;
// Managed thread to update disk image in background
@ -86,7 +90,7 @@ public class DiskIIDrive implements MediaConsumer {
driveOn = false;
magnets = 0;
dirtyTracks = new HashSet<>();
diskUpdatePending = false;
diskUpdatePending.set(false);
}
void step(int register) {
@ -113,13 +117,18 @@ public class DiskIIDrive implements MediaConsumer {
}
nibbleOffset = 0;
//System.out.printf("new half track %d\n", currentHalfTrack);
if (DEBUG) {
System.out.printf("step %d, new half track %d\n", register, halfTrack);
}
}
}
}
}
void setOn(boolean b) {
if (DEBUG) {
System.out.println("Drive setOn: "+b);
}
driveOn = b;
}
@ -155,17 +164,20 @@ public class DiskIIDrive implements MediaConsumer {
void write() {
if (writeMode) {
while (diskUpdatePending) {
while (diskUpdatePending.get()) {
// If another thread requested writes to block (e.g. because of disk activity), wait for it to finish!
LockSupport.parkNanos(1000);
Thread.onSpinWait();
}
if (disk != null) {
// Do nothing if write-protection is enabled!
if (getMediaEntry() == null || !getMediaEntry().writeProtected) {
dirtyTracks.add(trackStartOffset / FloppyDisk.TRACK_NIBBLE_LENGTH);
disk.nibbles[trackStartOffset + nibbleOffset++] = latch;
triggerDiskUpdate();
StateManager.markDirtyValue(disk.nibbles, computer);
// Holding the lock should block any other threads from writing to disk
synchronized (diskUpdatePending) {
if (disk != null) {
// Do nothing if write-protection is enabled!
if (getMediaEntry() == null || !getMediaEntry().writeProtected) {
dirtyTracks.add(trackStartOffset / FloppyDisk.TRACK_NIBBLE_LENGTH);
disk.nibbles[trackStartOffset + nibbleOffset++] = latch;
triggerDiskUpdate();
StateManager.markDirtyValue(disk.nibbles, computer);
}
}
}
@ -192,19 +204,20 @@ public class DiskIIDrive implements MediaConsumer {
}
private void updateDisk() {
// Signal disk update is underway
diskUpdatePending = true;
// Update all tracks as necessary
if (disk != null) {
dirtyTracks.stream().forEach((track) -> {
disk.updateTrack(track);
});
synchronized (diskUpdatePending) {
diskUpdatePending.set(true);
// Update all tracks as necessary
if (disk != null) {
dirtyTracks.stream().forEach((track) -> {
disk.updateTrack(track);
});
}
// Empty out dirty list
dirtyTracks.clear();
}
// Empty out dirty list
dirtyTracks.clear();
// Signal disk update is completed
diskUpdatePending = false;
diskUpdatePending.set(false);
}
private void triggerDiskUpdate() {
@ -226,6 +239,9 @@ public class DiskIIDrive implements MediaConsumer {
}
void insertDisk(File diskPath) throws IOException {
if (DEBUG) {
System.out.println("inserting disk " + diskPath.getAbsolutePath() + " into drive");
}
disk = new FloppyDisk(diskPath, computer);
dirtyTracks = new HashSet<>();
// Emulator state has changed significantly, reset state manager
@ -300,14 +316,16 @@ public class DiskIIDrive implements MediaConsumer {
@Override
public boolean isAccepted(MediaEntry e, MediaFile f) {
if (f == null) return false;
// System.out.println("Type is accepted: "+f.path+"; "+e.type.toString()+": "+e.type.is140kb);
if (DEBUG) {
System.out.println("Type is accepted: "+f.path+"; "+e.type.toString()+": "+e.type.is140kb);
}
return e.type.is140kb;
}
private void waitForPendingWrites() {
while (diskUpdatePending || !dirtyTracks.isEmpty()) {
while (diskUpdatePending.get()) {
// If the current disk has unsaved changes, wait!!!
LockSupport.parkNanos(1000);
Thread.onSpinWait();
}
}
}

View File

@ -18,9 +18,6 @@
*/
package jace.hardware;
import jace.core.Computer;
import jace.state.StateManager;
import jace.state.Stateful;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@ -32,6 +29,10 @@ import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import jace.core.Computer;
import jace.state.StateManager;
import jace.state.Stateful;
/**
* Representation of a 140kb floppy disk image. This also performs conversions
* as needed. Internally, the emulator will always use a "nibblized" disk
@ -180,6 +181,17 @@ public class FloppyDisk {
writeJunkBytes(output, 38 - gap2);
}
}
// Write output to stdout for debugging purposes
if (DEBUG) {
System.out.println("Nibblized disk:");
for (int i = 0; i < output.size(); i++) {
System.out.print(Integer.toString(output.toByteArray()[i] & 0x0ff, 16) + " ");
if (i % 16 == 255) {
System.out.println();
}
}
System.out.println();
}
return output.toByteArray();
}
@ -298,17 +310,23 @@ public class FloppyDisk {
byte[] trackNibbles = new byte[TRACK_NIBBLE_LENGTH];
byte[] trackData = new byte[SECTOR_COUNT * 256];
// Copy track into temporary buffer
// System.out.println("Nibblized track "+track);
// System.out.printf("%04d:",0);
for (int i = 0, pos = track * TRACK_NIBBLE_LENGTH; i < TRACK_NIBBLE_LENGTH; i++, pos++) {
trackNibbles[i] = nibbles[pos];
// System.out.print(Integer.toString(nibbles[pos] & 0x0ff, 16)+" ");
// if (i % 16 == 15) {
// System.out.println();
// System.out.printf("%04d:",i+1);
// }
if (DEBUG) {
System.out.println("Nibblized track "+track);
System.out.printf("%04d:",0);
}
for (int i = 0, pos = track * TRACK_NIBBLE_LENGTH; i < TRACK_NIBBLE_LENGTH; i++, pos++) {
trackNibbles[i] = nibbles[pos];
if (DEBUG) {
System.out.print(Integer.toString(nibbles[pos] & 0x0ff, 16)+" ");
if (i % 16 == 15) {
System.out.println();
System.out.printf("%04d:",i+1);
}
}
}
if (DEBUG) {
System.out.println();
}
// System.out.println();
int pos = 0;
for (int i = 0; i < SECTOR_COUNT; i++) {
@ -318,7 +336,9 @@ public class FloppyDisk {
int trackVerify = decodeOddEven(trackNibbles[pos + 5], trackNibbles[pos + 6]);
// Locate sector number
int sector = decodeOddEven(trackNibbles[pos + 7], trackNibbles[pos + 8]);
// System.out.println("Writing track " + track + ", getting address block for T" + trackVerify + ".S" + sector + " found at NIB offset "+pos);
if (DEBUG) {
System.out.println("Writing track " + track + ", getting address block for T" + trackVerify + ".S" + sector + " found at NIB offset "+pos);
}
// Skip to end of address block
pos = locatePattern(pos, trackNibbles, 0x0de, 0x0aa /*, 0x0eb this is sometimes being written as FF??*/);
// Locate start of sector data
@ -326,7 +346,9 @@ public class FloppyDisk {
// Determine offset in output data for sector
//int offset = reverseLoopkup(currentSectorOrder, sector) * 256;
int offset = currentSectorOrder[sector] * 256;
// System.out.println("Sector "+sector+" maps to physical sector "+reverseLoopkup(currentSectorOrder, sector));
if (DEBUG) {
System.out.println("Sector "+sector+" maps to physical sector "+reverseLoopkup(currentSectorOrder, sector));
}
// Decode sector data
denibblizeSector(trackNibbles, pos + 3, trackData, offset);
// Skip to end of sector

View File

@ -201,7 +201,7 @@ public class Joystick extends Device {
RAMListener listener;
private void registerListeners() {
listener = computer.getMemory().observe(RAMEvent.TYPE.ANY, 0x0c070, 0x0c07f, this::initJoystickRead);
listener = computer.getMemory().observe("Joystick I/O", RAMEvent.TYPE.ANY, 0x0c070, 0x0c07f, this::initJoystickRead);
}
private void removeListeners() {

View File

@ -1,5 +1,8 @@
package jace.hardware;
import java.util.Calendar;
import java.util.Optional;
import jace.EmulatorUILogic;
import jace.apple2e.SoftSwitches;
import jace.config.ConfigurableField;
@ -8,8 +11,6 @@ import jace.core.Device;
import jace.core.RAMEvent;
import jace.core.RAMListener;
import jace.core.Utility;
import java.util.Calendar;
import java.util.Optional;
import javafx.scene.control.Label;
/**
@ -31,7 +32,7 @@ public class NoSlotClock extends Device {
public boolean patchProdosClock = false;
Optional<Label> clockIcon;
private final RAMListener listener = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
private final RAMListener listener = new RAMListener("No slot clock read", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x0C100);

View File

@ -18,16 +18,10 @@
*/
package jace.hardware;
import jace.config.ConfigurableField;
import jace.config.DynamicSelection;
import jace.config.Name;
import jace.core.Card;
import jace.core.Computer;
import jace.core.RAMEvent;
import jace.core.RAMEvent.TYPE;
import java.util.LinkedHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
@ -36,6 +30,14 @@ import javax.sound.midi.Receiver;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Synthesizer;
import jace.config.ConfigurableField;
import jace.config.DynamicSelection;
import jace.config.Name;
import jace.core.Card;
import jace.core.Computer;
import jace.core.RAMEvent;
import jace.core.RAMEvent.TYPE;
/**
* Partial implementation of Passport midi card, supporting midi output routed
* to the java midi synth for playback. Compatible with Ultima V. Card
@ -188,8 +190,8 @@ public class PassportMidiInterface extends Card {
};
private boolean ptmStatusReadSinceIRQ = false;
// ---------------------- ACIA CONFIGURATION
private final boolean aciaInterruptOnSend = false;
private final boolean aciaInterruptOnReceive = false;
// private final boolean aciaInterruptOnSend = false;
// private final boolean aciaInterruptOnReceive = false;
// ---------------------- ACIA STATUS BITS
// True when MIDI IN receives a byte
private final boolean receivedACIAByte = false;

View File

@ -18,13 +18,14 @@
*/
package jace.hardware;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import jace.apple2e.MOS65C02;
import jace.core.Computer;
import jace.core.RAM;
import jace.hardware.massStorage.CardMassStorage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Generic abstraction of a smartport device.
@ -77,7 +78,7 @@ public abstract class SmartportDriver {
}
// Now process command
System.out.println("Received command " + command + " with address block " + Integer.toHexString(parmAddr));
byte numParms = ram.readRaw(parmAddr);
// byte numParms = ram.readRaw(parmAddr);
int[] params = new int[16];
for (int i = 0; i < 16; i++) {
int value = 0x0ff & ram.readRaw(parmAddr + i);

View File

@ -15,10 +15,10 @@ public enum VideoImpls implements DeviceEnum<Video> {
Lawless("Lawless enhanced video", LawlessVideo.class, LawlessVideo::new);
private final String name;
private final Class clazz;
private final Class<? extends Video> clazz;
private final Function<Computer, Video> factory;
VideoImpls(String name, Class clazz, Function<Computer, Video> factory) {
VideoImpls(String name, Class<? extends Video> clazz, Function<Computer, Video> factory) {
this.name = name;
this.clazz = clazz;
this.factory = factory;

View File

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

View File

@ -19,6 +19,7 @@
package jace.hardware.massStorage;
import static jace.hardware.massStorage.IDisk.BLOCK_SIZE;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
@ -28,7 +29,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -295,9 +295,9 @@ public class DirectoryNode extends DiskNode implements FileFilter {
}
}
private Optional<DiskNode> findChildByFilename(String name) {
return directoryEntries.stream().filter((child) -> child.getPhysicalFile().getName().equals(name)).findFirst();
}
// private Optional<DiskNode> findChildByFilename(String name) {
// return directoryEntries.stream().filter((child) -> child.getPhysicalFile().getName().equals(name)).findFirst();
// }
private void addFile(File file) {
if (!hasChildNamed(file.getName())) {

View File

@ -202,6 +202,8 @@ public class FileNode extends DiskNode {
readFile(buffer, (block - treeBlocks - 1));
}
break;
default:
// ignore
}
}

View File

@ -42,7 +42,8 @@ public class FreespaceBitmap extends DiskNode {
@Override
public void doAllocate() throws IOException {
for (int i = 1; i < size; i++) {
SubNode subNode = new SubNode(i, this, getBaseBlock());
// SubNode subNode =
new SubNode(i, this, getBaseBlock());
}
}

View File

@ -42,7 +42,7 @@ public class LargeDisk implements IDisk {
// Offset in input file where data can be found
private int dataOffset = 0;
private int physicalBlocks = 0;
private int logicalBlocks = 0;
// private int logicalBlocks;
public LargeDisk(File f) {
try {
@ -124,7 +124,7 @@ public class LargeDisk implements IDisk {
// todo: read header
dataOffset = 64;
physicalBlocks = (int) (f.length() / BLOCK_SIZE);
logicalBlocks = physicalBlocks;
// logicalBlocks = physicalBlocks;
result = true;
}
} catch (IOException ex) {
@ -145,7 +145,7 @@ public class LargeDisk implements IDisk {
System.out.println("Disk is HDV");
dataOffset = 0;
physicalBlocks = (int) (f.length() / BLOCK_SIZE);
logicalBlocks = physicalBlocks;
// logicalBlocks = physicalBlocks;
}
private void readDiskImage(File f) throws IOException {

View File

@ -18,7 +18,6 @@
*/
package jace.hardware.massStorage;
import jace.Emulator;
import jace.EmulatorUILogic;
import jace.apple2e.MOS65C02;
import jace.core.Computer;

View File

@ -1,638 +0,0 @@
/*
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package jace.hardware.mockingboard;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jace.hardware.CardMockingboard;
// Port of AY code from AppleWin -- not used buy kept for reference.
/***************************************************************************
*
* ay8910.c
*
*
* Emulation of the AY-3-8910 / YM2149 sound chip.
*
* Based on various code snippets by Ville Hallik, Michael Cuddy,
* Tatsuyuki Satoh, Fabrice Frances, Nicola Salmoria.
*
***************************************************************************/
//
// From mame.txt (http://www.mame.net/readme.html)
//
// VI. Reuse of Source Code
// --------------------------
// This chapter might not apply to specific portions of MAME (e.g. CPU
// emulators) which bear different copyright notices.
// The source code cannot be used in a commercial product without the written
// authorization of the authors. Use in non-commercial products is allowed, and
// indeed encouraged. If you use portions of the MAME source code in your
// program, however, you must make the full source code freely available as
// well.
// Usage of the _information_ contained in the source code is free for any use.
// However, given the amount of time and energy it took to collect this
// information, if you find new information we would appreciate if you made it
// freely available as well.
//
public class AY8910_old {
static final int MAX_OUTPUT = 0x007fff;
static final int MAX_AY8910 = 2;
static final int CLOCK = 1789770;
static final int SAMPLE_RATE = 44100;
// See AY8910_set_clock() for definition of STEP
static final int STEP = 0x008000;
static int num = 0, ym_num = 0;
int SampleRate = 0;
/* register id's */
public enum Reg {
AFine(0, 255),
ACoarse(1, 15),
BFine(2, 255),
BCoarse(3, 15),
CFine(4, 255),
CCoarse(5, 15),
NoisePeriod(6, 31),
Enable(7, 255),
AVol(8, 31),
BVol(9, 31),
CVol(10, 31),
EnvFine(11, 255),
EnvCoarse(12, 255),
EnvShape(13, 15),
PortA(14, 255),
PortB(15, 255);
public final int registerNumber;
public final int max;
Reg(int number, int maxValue) {
registerNumber = number;
max=maxValue;
}
static Reg get(int number) {
for (Reg r:Reg.values())
if (r.registerNumber == number) return r;
return null;
}
static public Reg[] preferredOrder = new Reg[]{
Enable,EnvShape,EnvCoarse,EnvFine,NoisePeriod,AVol,BVol,CVol,
AFine,ACoarse,BFine,BCoarse,CFine,CCoarse};
}
public AY8910_old() {
chips = new ArrayList<PSG>();
for (int i=0; i < MAX_AY8910; i++) {
PSG chip = new PSG();
chips.add(chip);
}
initAll(CLOCK, SAMPLE_RATE);
}
///////////////////////////////////////////////////////////
private final List<PSG> chips;
static int[] VolTable;
static {
buildMixerTable();
}
private class PSG {
int Channel;
int register_latch;
Map<Reg, Integer> registers;
int lastEnable;
int UpdateStep;
int PeriodA,PeriodB,PeriodC,PeriodN,PeriodE;
int CountA,CountB,CountC,CountN,CountE;
int VolA,VolB,VolC,VolE;
int EnvelopeA,EnvelopeB,EnvelopeC;
int OutputA,OutputB,OutputC,OutputN;
int CountEnv;
int Hold,Alternate,Attack,Holding;
int RNG;
public PSG() {
registers = new HashMap<Reg, Integer>();
for (Reg r: Reg.values())
setReg(r, 0);
}
public void reset() {
register_latch = 0;
RNG = 1;
OutputA = 0;
OutputB = 0;
OutputC = 0;
OutputN = 0x00ff;
lastEnable = -1; /* force a write */
for (Reg r: Reg.values())
writeReg(r, 0);
}
public void setClock(int clock) {
/* the step clock for the tone and noise generators is the chip clock */
/* divided by 8; for the envelope generator of the AY-3-8910, it is half */
/* that much (clock/16), but the envelope of the YM2149 goes twice as */
/* fast, therefore again clock/8. */
/* Here we calculate the number of steps which happen during one sample */
/* at the given sample rate. No. of events = sample rate / (clock/8). */
/* STEP is a multiplier used to turn the fraction into a fixed point */
/* number. */
double clk = clock;
double smprate = SampleRate;
UpdateStep = (int) ((STEP * smprate * 8.0 + clk/2.0) / clk);
}
public void setReg(Reg r, int value) {
registers.put(r,value);
}
public int getReg(Reg r) {
return registers.get(r);
}
public void writeReg(Reg r, int value) {
value &= r.max;
setReg(r, value);
int old;
/* A note about the period of tones, noise and envelope: for speed reasons,*/
/* we count down from the period to 0, but careful studies of the chip */
/* output prove that it instead counts up from 0 until the counter becomes */
/* greater or equal to the period. This is an important difference when the*/
/* program is rapidly changing the period to modulate the sound. */
/* To compensate for the difference, when the period is changed we adjust */
/* our internal counter. */
/* Also, note that period = 0 is the same as period = 1. This is mentioned */
/* in the YM2203 data sheets. However, this does NOT apply to the Envelope */
/* period. In that case, period = 0 is half as period = 1. */
switch(r) {
case ACoarse:
case AFine:
old = PeriodA;
PeriodA = (getReg(Reg.AFine) + 256 * getReg(Reg.ACoarse)) * UpdateStep;
if (PeriodA == 0) PeriodA = UpdateStep;
CountA += PeriodA - old;
if (CountA <= 0) CountA = 1;
break;
case BCoarse:
case BFine:
old = PeriodB;
PeriodB = (getReg(Reg.BFine) + 256 * getReg(Reg.BCoarse)) * UpdateStep;
if (PeriodB == 0) PeriodB = UpdateStep;
CountB += PeriodB - old;
if (CountB <= 0) CountB = 1;
break;
case CCoarse:
case CFine:
setReg(Reg.CCoarse, getReg(Reg.CCoarse) & 0x0f);
old = PeriodC;
PeriodA = (getReg(Reg.CFine) + 256 * getReg(Reg.CCoarse)) * UpdateStep;
if (PeriodC == 0) PeriodC = UpdateStep;
CountC += PeriodC - old;
if (CountC <= 0) CountC = 1;
break;
case NoisePeriod:
old = PeriodN;
PeriodN = getReg(Reg.NoisePeriod) * UpdateStep;
if (PeriodN == 0) PeriodN = UpdateStep;
CountN += PeriodN - old;
if (CountN <= 0) CountN = 1;
break;
case Enable:
lastEnable = value;
break;
case AVol:
EnvelopeA = value & 0x10;
if (EnvelopeA > 0)
VolA = VolE;
else {
if (value > 0)
VolA = CardMockingboard.VolTable[value];
else
VolA = CardMockingboard.VolTable[0];
}
break;
case BVol:
EnvelopeB = value & 0x10;
if (EnvelopeB > 0)
VolB = VolE;
else {
if (value > 0)
VolB = CardMockingboard.VolTable[value];
else
VolB = CardMockingboard.VolTable[0];
}
break;
case CVol:
EnvelopeC = value & 0x10;
if (EnvelopeC > 0)
VolC = VolE;
else {
if (value > 0)
VolC = CardMockingboard.VolTable[value];
else
VolC = CardMockingboard.VolTable[0];
}
break;
case EnvFine:
case EnvCoarse:
old = PeriodE;
PeriodE = ((getReg(Reg.EnvFine) + 256 * getReg(Reg.EnvCoarse))) * UpdateStep;
if (PeriodE == 0) PeriodE = UpdateStep / 2;
CountE += PeriodE - old;
if (CountE <= 0) CountE = 1;
if (PeriodE <= 0) PeriodE = 1;
break;
case EnvShape:
/* envelope shapes:
C AtAlH
0 0 x x \___
0 1 x x /|__
1 0 0 0 \\\\
1 0 0 1 \___
1 0 1 0 \/\/
__
1 0 1 1 \|
1 1 0 0 ////
___
1 1 0 1 /
1 1 1 0 /\/\
1 1 1 1 /|__
The envelope counter on the AY-3-8910 has 16 steps. On the YM2149 it
has twice the steps, happening twice as fast. Since the end result is
just a smoother curve, we always use the YM2149 behaviour.
*/
Attack = (value & 0x04) != 0 ? 0x1f : 0x00;
if ( (value & 0x08) == 0) {
/* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */
Hold = 1;
Alternate = Attack;
} else {
Hold = value & 0x01;
Alternate = value & 0x02;
}
CountE = PeriodE;
CountEnv = 0x1f;
Holding = 0;
VolE = CardMockingboard.VolTable[CountEnv ^ Attack];
if (EnvelopeA != 0) VolA = VolE;
if (EnvelopeB != 0) VolB = VolE;
if (EnvelopeC != 0) VolC = VolE;
break;
case PortA:
case PortB:
break;
}
}
void update(int[][] buffer, int length) {
int[] buf1, buf2, buf3;
int outn;
buf1 = buffer[0];
buf2 = buffer[1];
buf3 = buffer[2];
/* The 8910 has three outputs, each output is the mix of one of the three */
/* tone generators and of the (single) noise generator. The two are mixed */
/* BEFORE going into the DAC. The formula to mix each channel is: */
/* (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). */
/* Note that this means that if both tone and noise are disabled, the output */
/* is 1, not 0, and can be modulated changing the volume. */
/* If the channels are disabled, set their output to 1, and increase the */
/* counter, if necessary, so they will not be inverted during this update. */
/* Setting the output to 1 is necessary because a disabled channel is locked */
/* into the ON state (see above); and it has no effect if the volume is 0. */
/* If the volume is 0, increase the counter, but don't touch the output. */
if ( (getReg(Reg.Enable) & 0x01) != 0) {
if (CountA <= length*STEP) CountA += length*STEP;
OutputA = 1;
} else if (getReg(Reg.AVol) == 0) {
/* note that I do count += length, NOT count = length + 1. You might think */
/* it's the same since the volume is 0, but doing the latter could cause */
/* interferencies when the program is rapidly modulating the volume. */
if (CountA <= length*STEP) CountA += length*STEP;
}
if ( (getReg(Reg.Enable) & 0x02) != 0) {
if (CountB <= length*STEP) CountB += length*STEP;
OutputB = 1;
} else if (getReg(Reg.BVol) == 0) {
if (CountB <= length*STEP) CountB += length*STEP;
}
if ( (getReg(Reg.Enable) & 0x04) != 0) {
if (CountC <= length*STEP) CountC += length*STEP;
OutputC = 1;
} else if (getReg(Reg.CVol) == 0) {
if (CountC <= length*STEP) CountC += length*STEP;
}
/* for the noise channel we must not touch OutputN - it's also not necessary */
/* since we use outn. */
if ((getReg(Reg.Enable) & 0x38) == 0x38) /* all off */
if (CountN <= length*STEP) CountN += length*STEP;
outn = (OutputN | getReg(Reg.Enable));
int index = 0;
//System.out.println("Length:"+length);
/* buffering loop */
while (length != 0) {
int vola,volb,volc;
int left;
/* vola, volb and volc keep track of how long each square wave stays */
/* in the 1 position during the sample period. */
vola = volb = volc = 0;
//System.out.println("STEP:"+STEP);
left = STEP;
do {
int nextevent;
if (CountN < left) nextevent = CountN;
else nextevent = left;
if ( (outn & 0x08) != 0) {
if (OutputA != 0) vola += CountA;
CountA -= nextevent;
/* PeriodA is the half period of the square wave. Here, in each */
/* loop I add PeriodA twice, so that at the end of the loop the */
/* square wave is in the same status (0 or 1) it was at the start. */
/* vola is also incremented by PeriodA, since the wave has been 1 */
/* exactly half of the time, regardless of the initial position. */
/* If we exit the loop in the middle, OutputA has to be inverted */
/* and vola incremented only if the exit status of the square */
/* wave is 1. */
while (CountA <= 0 && PeriodA > 0) {
CountA += PeriodA;
if (CountA > 0) {
OutputA ^= 1;
if (OutputA != 0) vola += PeriodA;
break;
}
CountA += PeriodA;
vola += PeriodA;
}
if (OutputA != 0) vola -= CountA;
} else {
CountA -= nextevent;
while (CountA <= 0 && PeriodA > 0) {
CountA += PeriodA;
if (CountA > 0) {
OutputA ^= 1;
break;
}
CountA += PeriodA;
}
}
if ((outn & 0x10) != 0) {
if (OutputB != 0) volb += CountB;
CountB -= nextevent;
while (CountB <= 0 && PeriodB > 0) {
CountB += PeriodB;
if (CountB > 0) {
OutputB ^= 1;
if (OutputB != 0) volb += PeriodB;
break;
}
CountB += PeriodB;
volb += PeriodB;
}
if (OutputB != 0) volb -= CountB;
} else {
CountB -= nextevent;
while (CountB <= 0 && PeriodB > 0) {
CountB += PeriodB;
if (CountB > 0) {
OutputB ^= 1;
break;
}
CountB += PeriodB;
}
}
if ( (outn & 0x20) != 0) {
if (OutputC != 0) volc += CountC;
CountC -= nextevent;
while (CountC <= 0 && PeriodC > 0) {
CountC += PeriodC;
if (CountC > 0) {
OutputC ^= 1;
if (OutputC != 0) volc += PeriodC;
break;
}
CountC += PeriodC;
volc += PeriodC;
}
if (OutputC != 0) volc -= CountC;
} else {
CountC -= nextevent;
while (CountC <= 0 && PeriodC > 0) {
CountC += PeriodC;
if (CountC > 0) {
OutputC ^= 1;
break;
}
CountC += PeriodC;
}
}
CountN -= nextevent;
if (CountN <= 0 && PeriodN > 0) {
/* Is noise output going to change? */
/* (bit0^bit1)? */
if (((RNG + 1) & 2) != 0) {
OutputN ^= 0x0FF;
outn = (OutputN | getReg(Reg.Enable));
}
/* The Random Number Generator of the 8910 is a 17-bit shift */
/* register. The input to the shift register is bit0 XOR bit3 */
/* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */
/* The following is a fast way to compute bit17 = bit0^bit3. */
/* Instead of doing all the logic operations, we only check */
/* bit0, relying on the fact that after three shifts of the */
/* register, what now is bit3 will become bit0, and will */
/* invert, if necessary, bit14, which previously was bit17. */
if ((RNG & 1) != 0) RNG ^= 0x0024000; /* This version is called the "Galois configuration". */
RNG >>= 1;
CountN += PeriodN;
}
left -= nextevent;
} while (left > 0);
// System.out.println("End left loop");
/* update envelope */
if (Holding == 0) {
CountE -= STEP;
if (CountE <= 0) {
do {
CountEnv--;
CountE += PeriodE;
} while (CountE <= 0);
/* check envelope current position */
if (CountEnv < 0) {
if (Hold != 0) {
if (Alternate != 0)
Attack ^= 0x1f;
Holding = 1;
CountEnv = 0;
} else {
/* if CountEnv has looped an odd number of times (usually 1), */
/* invert the output. */
if ( (Alternate != 0) && ((CountEnv & 0x20) != 0))
Attack ^= 0x1f;
CountEnv &= 0x1f;
}
}
VolE = VolTable[CountEnv ^ Attack];
/* reload volume */
if (EnvelopeA != 0) VolA = VolE;
if (EnvelopeB != 0) VolB = VolE;
if (EnvelopeC != 0) VolC = VolE;
}
}
// Output PCM wave [-32768...32767] instead of MAME's voltage level [0...32767]
// - This allows for better s/w mixing
buf1[index] = (vola * VolA) / STEP;
buf2[index] = (volb * VolB) / STEP;
buf3[index] = (volc * VolC) / STEP;
/*
if(VolA != 0) {
if (vola != 0) buf1[index] = (vola * VolA) / STEP;
else buf1[index] = -VolA;
} else {
buf1[index] = 0;
}
//
if(VolB != 0) {
if (volb != 0) buf2[index] = (volb * VolB) / STEP;
else buf2[index] = -VolB;
} else
buf2[index] = 0;
//
if(VolC != 0) {
if (volc != 0) buf3[index] = (volc * VolC) / STEP;
else buf3[index] = -VolC;
} else
buf3[index] = 0;
*/
index++;
length--;
}
}
}
public void writeReg(int chipNumber, int register, int value) {
Reg r = Reg.get(register);
writeReg(chipNumber, r, value);
}
public void writeReg(int chipNumber, Reg register, int value) {
chips.get(chipNumber).writeReg(register, value);
}
// /length/ is the number of samples we require
// NB. This should be called at twice the 6522 IRQ rate or (eg) 60Hz if no IRQ.
public void update(int chipNumber,int[][] buffer,int length) {
chips.get(chipNumber).update(buffer, length);
}
int[][] buffers;
int bufferLength = -1;
public int[][] getBuffers(int length) {
if (buffers == null || bufferLength != length) {
buffers = new int[3][length];
bufferLength = length;
}
return buffers;
}
public void playSound(int size, int[] left, int[] right) {
int[][] buffers = getBuffers(left.length);
update(0, buffers, size);
mixDown(left, buffers, size);
update(1, buffers, size);
mixDown(right, buffers, size);
}
public void mixDown(int[] out, int[][] in, int size) {
for (int i=0; i < size; i++) {
int sample = (in[0][i] + in[1][i] + in[2][i]) / 3;
out[i] = sample;
}
}
public void setClock(int chipNumber,int clock) {
chips.get(chipNumber).setClock(clock);
}
public void reset(int chipNumber) {
chips.get(chipNumber).reset();
}
public void initAll(int nClock, int nSampleRate) {
SampleRate = nSampleRate;
for (PSG p:chips) {
p.setClock(nClock);
p.reset();
}
}
public void initClock(int nClock) {
for (PSG p:chips) p.setClock(nClock);
}
static void buildMixerTable() {
VolTable = new int[32];
int SampleRate;
/* calculate the volume->voltage conversion table */
/* The AY-3-8910 has 16 levels, in a logarithmic scale (3dB per step) */
/* The YM2149 still has 16 levels for the tone generators, but 32 for */
/* the envelope generator (1.5dB per step). */
double out = MAX_OUTPUT;
for (int i = 31;i > 0;i--) {
VolTable[i] = (int) (out + 0.5); /* round to nearest */ // [TC: unsigned int cast]
out /= 1.188502227; /* = 10 ^ (1.5/20) = 1.5dB */
}
VolTable[0] = 0;
}
}

View File

@ -35,9 +35,6 @@ public class TimedGenerator {
double cyclesPerSample;
int clocksPerPeriod;
private TimedGenerator() {
}
public TimedGenerator(int _clock, int _sampleRate) {
setRate(clock, sampleRate);
reset();

View File

@ -21,9 +21,10 @@ import java.util.Collections;
* This is a program that is intended to be defined and executed outside of a IDE session
* @author blurry
*/
@SuppressWarnings("all")
public class HeadlessProgram extends Program {
public HeadlessProgram(DocumentType type) {
super(type, Collections.EMPTY_MAP);
super(type, Collections.emptyMap());
}
String program;

View File

@ -5,9 +5,6 @@
*/
package jace.ide;
import jace.LawlessLegends;
import jace.ide.Program.DocumentType;
import jace.ide.Program.Option;
import java.io.File;
import java.net.URL;
import java.util.EnumMap;
@ -15,6 +12,10 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import jace.LawlessLegends;
import jace.ide.Program.DocumentType;
import jace.ide.Program.Option;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.event.Event;
@ -98,7 +99,8 @@ public class IdeController {
@FXML
void newApplesoftBasicClicked(ActionEvent event) {
Program tab = createTab(DocumentType.applesoft, null, true);
// Program tab =
createTab(DocumentType.applesoft, null, true);
}
@FXML
@ -108,7 +110,8 @@ public class IdeController {
warningAlert.setContentText("If you proceed and applesoft is not running or there is no active program then the emulator might freeze. Press Cancel if you are unsure.");
Optional<ButtonType> result = warningAlert.showAndWait();
if (result.get() == ButtonType.OK) {
Program tab = createTab(DocumentType.applesoft, null, false);
// Program tab =
createTab(DocumentType.applesoft, null, false);
}
}
@ -264,6 +267,7 @@ public class IdeController {
});
}
@SuppressWarnings("all")
private void updateStatusMessages(CompileResult lastResult) {
String message = "Compiler was " + (lastResult.isSuccessful() ? " successful" : " NOT SUCCESSFUL");
message += " -- ";

View File

@ -18,6 +18,7 @@ import java.util.logging.Logger;
*
* @author blurry
*/
@SuppressWarnings("all")
public class Program {
public static String CODEMIRROR_EDITOR = "/codemirror/editor.html";

View File

@ -1,10 +1,11 @@
package jace.ide;
import jace.core.Keyboard;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import jace.core.Keyboard;
/**
*
* @author blurry
@ -33,22 +34,22 @@ public class TextHandler implements LanguageHandler<String> {
@Override
public Map<Integer, String> getErrors() {
return Collections.EMPTY_MAP;
return Collections.emptyMap();
}
@Override
public Map<Integer, String> getWarnings() {
return Collections.EMPTY_MAP;
return Collections.emptyMap();
}
@Override
public List<String> getOtherMessages() {
return Collections.EMPTY_LIST;
return Collections.emptyList();
}
@Override
public List<String> getRawOutput() {
return Collections.EMPTY_LIST;
return Collections.emptyList();
}
};
}

View File

@ -63,6 +63,7 @@ public class LawlessComputer extends Apple2e {
if (showBootAnimation && PRODUCTION_MODE) {
(new Thread(this::startAnimation)).start();
} else {
cpu.setPaused(false);
finishColdStart();
}
}
@ -159,6 +160,9 @@ public class LawlessComputer extends Apple2e {
}
public void waitForVBL(int count) throws InterruptedException {
if (!isRunning()) {
return;
}
Semaphore s = new Semaphore(0);
onNextVBL(s::release);
s.acquire();

View File

@ -1,6 +1,7 @@
package jace.lawless;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
@ -11,6 +12,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -18,9 +21,6 @@ import jace.cheat.Cheats;
import jace.core.Computer;
import jace.core.RAMEvent;
import jace.lawless.LawlessVideo.RenderEngine;
import javafx.beans.property.DoubleProperty;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.util.Duration;
/**
@ -47,15 +47,13 @@ public class LawlessHacks extends Cheats {
@Override
public void registerListeners() {
// Observe graphics changes
addCheat(RAMEvent.TYPE.ANY, (e) -> {
addCheat("Lawless Legends Graphics Modes", RAMEvent.TYPE.ANY, (e) -> {
int addr = e.getAddress();
if (addr >= MODE_SOFTSWITCH_MIN && e.getAddress() <= MODE_SOFTSWITCH_MAX) {
// System.out.println("Trapped " + e.getType().toString() + " to $" + Integer.toHexString(e.getAddress()));
setEngineByOrdinal(e.getAddress() - MODE_SOFTSWITCH_MIN);
}
}, MODE_SOFTSWITCH_MIN, MODE_SOFTSWITCH_MAX);
addCheat(RAMEvent.TYPE.WRITE, (e) -> {
// System.out.println(Integer.toHexString(e.getAddress()) + " => " + Integer.toHexString(e.getNewValue() & 0x0ff));
addCheat("Lawless Legends Music Commands", RAMEvent.TYPE.WRITE, (e) -> {
playSound(e.getNewValue());
}, SFX_TRIGGER);
}
@ -141,7 +139,12 @@ public class LawlessHacks extends Cheats {
System.out.println("Playing " + resourcePath);
}
// Log path
return new Media(resourcePath);
try {
return new Media(resourcePath);
} catch (IOException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Unable to load audio track " + resourcePath, e);
return null;
}
}
private void playMusic(int track, boolean switchScores) {
@ -165,7 +168,7 @@ public class LawlessHacks extends Cheats {
playbackEffect = null;
}
private Optional<Double> getCurrentTime() {
private Optional<Long> getCurrentTime() {
if (currentSongPlayer == null) {
return Optional.empty();
} else if (currentSongPlayer.getCurrentTime() == null) {
@ -181,9 +184,8 @@ public class LawlessHacks extends Cheats {
if (player != null) {
getCurrentTime().ifPresent(val -> lastTime.put(currentSong, val + 1500));
playbackEffect = new Thread(() -> {
DoubleProperty volume = player.volumeProperty();
while (playbackEffect == Thread.currentThread() && volume.get() > 0.0) {
volume.set(volume.get() - FADE_AMT);
while (playbackEffect == Thread.currentThread() && player.getVolume() > 0.0) {
player.setVolume(player.getVolume() - FADE_AMT);
try {
Thread.sleep(FADE_SPEED);
} catch (InterruptedException e) {
@ -208,14 +210,13 @@ public class LawlessHacks extends Cheats {
private void fadeInSong(MediaPlayer player) {
stopSongEffect();
currentSongPlayer = player;
DoubleProperty volume = player.volumeProperty();
if (volume.get() >= 1.0) {
if (player.getVolume() >= 1.0) {
return;
}
playbackEffect = new Thread(() -> {
while (playbackEffect == Thread.currentThread() && volume.get() < 1.0) {
volume.set(volume.get() + FADE_AMT);
while (playbackEffect == Thread.currentThread() && player.getVolume() < 1.0) {
player.setVolume(player.getVolume() + FADE_AMT);
try {
Thread.sleep(FADE_SPEED);
} catch (InterruptedException e) {
@ -250,7 +251,7 @@ public class LawlessHacks extends Cheats {
player.setCycleCount(repeatSong ? MediaPlayer.INDEFINITE : 1);
player.setVolume(0.0);
if (playingFightSong || autoResume.contains(track) || switchScores) {
double time = lastTime.getOrDefault(track, 0.0);
long time = lastTime.getOrDefault(track, 0L);
System.out.println("Auto-resume from time " + time);
player.setStartTime(Duration.millis(time));
}
@ -316,7 +317,7 @@ public class LawlessHacks extends Cheats {
Pattern ENTRY = Pattern.compile("([0-9]+)\\s+(.*)");
private final Map<String, Map<Integer, String>> scores = new HashMap<>();
private final Set<Integer> autoResume = new HashSet<>();
private final Map<Integer, Double> lastTime = new HashMap<>();
private final Map<Integer, Long> lastTime = new HashMap<>();
private void readScores() {
InputStream data = getClass().getResourceAsStream("/jace/data/sound/scores.txt");
readScores(data);

View File

@ -0,0 +1,83 @@
package jace.lawless;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import org.lwjgl.stb.STBVorbis;
import org.lwjgl.stb.STBVorbisInfo;
import org.lwjgl.system.MemoryStack;
import javafx.util.Duration;
public class Media {
long streamHandle = -1;
long totalSamples = 0;
float totalDuration = 0;
long sampleRate = 0;
boolean isStereo = true;
ShortBuffer sampleBuffer = ShortBuffer.allocate(2);
public Media(String resourcePath) throws IOException {
// Load resource into memory
try (InputStream inputStream = getClass().getResourceAsStream(resourcePath)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
ByteBuffer byteBuffer = ByteBuffer.wrap(outputStream.toByteArray());
IntBuffer error = IntBuffer.allocate(1);
streamHandle = STBVorbis.stb_vorbis_open_memory(byteBuffer, error, null);
MemoryStack stack = MemoryStack.stackPush();
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
STBVorbis.stb_vorbis_get_info(streamHandle, info);
totalSamples = STBVorbis.stb_vorbis_stream_length_in_samples(streamHandle);
totalDuration = STBVorbis.stb_vorbis_stream_length_in_seconds(streamHandle);
sampleRate = info.sample_rate();
isStereo = info.channels() == 2;
info.free();
}
}
public void close() {
STBVorbis.stb_vorbis_close(streamHandle);
}
public void seekToTime(Duration millis) {
int sampleNumber = (int) (millis.toMillis() * sampleRate / 1000);
STBVorbis.stb_vorbis_seek(streamHandle, sampleNumber);
}
public boolean isEnded() {
return STBVorbis.stb_vorbis_get_sample_offset(streamHandle) >= totalSamples;
}
public void restart() {
STBVorbis.stb_vorbis_seek_start(streamHandle);
}
public short getNextLeftSample() {
// read next sample for left and right channels
int numSamples = STBVorbis.stb_vorbis_get_frame_short_interleaved(streamHandle, isStereo ? 2 : 1, sampleBuffer);
if (numSamples == 0) {
return 0;
}
return sampleBuffer.get(0);
}
public short getNextRightSample() {
return isStereo ? sampleBuffer.get(1) : sampleBuffer.get(0);
}
public java.time.Duration getCurrentTime() {
int sampleNumber = STBVorbis.stb_vorbis_get_sample_offset(streamHandle);
return java.time.Duration.ofMillis((long) (sampleNumber * 1000 / sampleRate));
}
}

View File

@ -0,0 +1,107 @@
package jace.lawless;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import jace.core.SoundMixer;
import jace.core.SoundMixer.SoundBuffer;
public class MediaPlayer {
double vol = 1.0;
int repeats = 0;
int maxRepetitions = 1;
Status status = Status.STOPPED;
Media song;
SoundBuffer playbackBuffer;
Executor executor = Executors.newSingleThreadExecutor();
public static enum Status {
NOT_STARTED, PLAYING, PAUSED, STOPPED
}
public static final int INDEFINITE = -1;
public MediaPlayer(Media song) {
this.song = song;
}
public Status getStatus() {
return status;
}
public Duration getCurrentTime() {
return song.getCurrentTime();
}
public double getVolume() {
return vol;
}
// NOTE: Once a song is stopped, it cannot be restarted.
public void stop() {
status = Status.STOPPED;
try {
playbackBuffer.shutdown();
} catch (InterruptedException | ExecutionException e) {
// Ignore exception on shutdown
}
song.close();
}
public void setCycleCount(int i) {
maxRepetitions = i;
}
public void setVolume(double d) {
vol = Math.max(0.0, Math.min(1.0, d));
}
public void setStartTime(javafx.util.Duration millis) {
song.seekToTime(millis);
}
public void pause() {
status = Status.PAUSED;
}
public void play() {
if (status == Status.STOPPED) {
return;
} else if (status == Status.NOT_STARTED) {
repeats = 0;
status = Status.PLAYING;
if (playbackBuffer == null || !playbackBuffer.isAlive()) {
playbackBuffer = SoundMixer.createBuffer(true);
}
}
executor.execute(() -> {
while (status == Status.PLAYING && (maxRepetitions == INDEFINITE || repeats < maxRepetitions)) {
if (song.isEnded()) {
if (maxRepetitions == INDEFINITE) {
song.restart();
} else {
repeats++;
if (repeats < maxRepetitions) {
song.restart();
} else {
this.stop();
break;
}
}
}
short leftSample = song.getNextLeftSample();
short rightSample = song.getNextRightSample();
try {
playbackBuffer.playSample((short) (leftSample * vol));
playbackBuffer.playSample((short) (rightSample * vol));
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
this.stop();
}
}
});
}
}

View File

@ -18,8 +18,6 @@
*/
package jace.library;
import jace.core.Utility;
import jace.library.MediaEntry.MediaFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@ -29,9 +27,6 @@ import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -41,6 +36,9 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import jace.core.Utility;
import jace.library.MediaEntry.MediaFile;
/**
* Holds all information about media titles, manages low-level operations of
* downloading images from online sources, and also manages the persistence of

View File

@ -94,13 +94,14 @@ public class TocTreeModel implements TreeModel {
return getChildCount(node) == 0;
}
@SuppressWarnings("all")
public void valueForPathChanged(TreePath path, Object newValue) {
// Do nothing...
}
public int getIndexOfChild(Object parent, Object child) {
if (parent instanceof String) {
String n = (String) parent;
// String n = (String) parent;
int index = 0;
for (String c : tree.get(parent).keySet()) {
if (c.equals(child)) {
@ -120,6 +121,7 @@ public class TocTreeModel implements TreeModel {
// Do nothing...
}
@SuppressWarnings("all")
public Set<Long> getEntries(Object selection) {
if (selection.equals(this)) return getEntries(tree);
if (selection instanceof Set) return (Set<Long>) selection;

View File

@ -36,6 +36,7 @@ import java.util.logging.Logger;
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
* @param <T>
*/
@SuppressWarnings("all")
public class ObjectGraphNode<T> implements Serializable {
public ObjectGraphNode parent;

View File

@ -18,11 +18,10 @@
*/
package jace.state;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import javafx.scene.image.Image;
/**
@ -34,7 +33,8 @@ import javafx.scene.image.Image;
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class State extends HashMap<ObjectGraphNode, StateValue> implements Serializable {
@SuppressWarnings("all")
public class State extends HashMap<ObjectGraphNode, StateValue> {
boolean deltaState;
State previousState;

View File

@ -45,6 +45,7 @@ import javafx.scene.image.WritableImage;
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
@SuppressWarnings("all")
public class StateManager implements Reconfigurable {
private static StateManager instance;

View File

@ -57,6 +57,7 @@ public class StateValue<T> implements Serializable {
// Do nothing -- this is here in case it is necessary to implement partial changes
}
@SuppressWarnings("all")
private T copyObject(T currentValue) {
if (currentValue == null) return null;
if (type.isPrimitive()) {

View File

@ -157,7 +157,7 @@ public class MetacheatUI {
@FXML
void addCheat(ActionEvent event) {
cheatEngine.addCheat(new DynamicCheat(0, "?"));
cheatEngine.addCheat(new DynamicCheat(event.toString(), 0, "?"));
}
@FXML
@ -316,14 +316,17 @@ public class MetacheatUI {
searchStartAddressField.textProperty().addListener(addressRangeListener);
searchEndAddressField.textProperty().addListener(addressRangeListener);
@SuppressWarnings("all")
TableColumn<DynamicCheat, Boolean> activeColumn = (TableColumn<DynamicCheat, Boolean>) cheatsTableView.getColumns().get(0);
activeColumn.setCellValueFactory(new PropertyValueFactory<>("active"));
activeColumn.setCellFactory((TableColumn<DynamicCheat, Boolean> param) -> new CheckBoxTableCell<>());
@SuppressWarnings("all")
TableColumn<DynamicCheat, String> nameColumn = (TableColumn<DynamicCheat, String>) cheatsTableView.getColumns().get(1);
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setCellFactory((TableColumn<DynamicCheat, String> param) -> new TextFieldTableCell<>(new DefaultStringConverter()));
@SuppressWarnings("all")
TableColumn<DynamicCheat, Integer> addrColumn = (TableColumn<DynamicCheat, Integer>) cheatsTableView.getColumns().get(2);
addrColumn.setCellValueFactory(new PropertyValueFactory<>("address"));
addrColumn.setCellFactory((TableColumn<DynamicCheat, Integer> param) -> {
@ -340,6 +343,7 @@ public class MetacheatUI {
});
});
@SuppressWarnings("all")
TableColumn<DynamicCheat, String> exprColumn = (TableColumn<DynamicCheat, String>) cheatsTableView.getColumns().get(3);
exprColumn.setCellValueFactory(new PropertyValueFactory<>("expression"));
exprColumn.setCellFactory((TableColumn<DynamicCheat, String> param) -> new TextFieldTableCell<>(new DefaultStringConverter()));
@ -383,6 +387,7 @@ public class MetacheatUI {
public static Set<MemoryCell> redrawNodes = new ConcurrentSkipListSet<>();
ScheduledExecutorService animationTimer = null;
@SuppressWarnings("all")
ScheduledFuture animationFuture = null;
Tooltip memoryWatchTooltip = new Tooltip();
@ -412,7 +417,7 @@ public class MetacheatUI {
Label addCheat = new Label("Cheat >>");
addCheat.setOnMouseClicked((mouseEvent) -> {
Platform.runLater(() -> addCheat(addr, watch.getValue()));
Platform.runLater(() -> addCheat("Memory View " + Integer.toHexString(addr), addr, watch.getValue()));
});
watch.getChildren().add(addCheat);
@ -539,7 +544,7 @@ public class MetacheatUI {
Label addCheat = new Label("Cheat >>");
addCheat.setOnMouseClicked((mouseEvent) -> {
addCheat(addr, watch.getValue());
addCheat("Metacheat " + Integer.toHexString(addr), addr, watch.getValue());
});
addCheat.setTextFill(Color.WHITE);
watch.getChildren().add(addCheat);
@ -556,8 +561,8 @@ public class MetacheatUI {
return watch;
}
private void addCheat(int addr, int val) {
cheatEngine.addCheat(new DynamicCheat(addr, String.valueOf(val)));
private void addCheat(String name, int addr, int val) {
cheatEngine.addCheat(new DynamicCheat(name, addr, String.valueOf(val)));
}
int currentlyInspecting = 0;

View File

@ -38,6 +38,7 @@ class Watch extends VBox {
private static final int GRAPH_WIDTH = 50;
private static final double GRAPH_HEIGHT = 50;
int address;
@SuppressWarnings("all")
ScheduledFuture redraw;
Canvas graph;
List<Integer> samples = Collections.synchronizedList(new ArrayList<>());
@ -118,7 +119,7 @@ class Watch extends VBox {
holdListener = null;
} else {
value = Emulator.withComputer(c->c.getMemory().readRaw(address) & 0x0ff, 0);
holdListener = outer.cheatEngine.forceValue(value, address);
holdListener = outer.cheatEngine.forceValue("Watch force value", value, address);
}
}

View File

@ -14,19 +14,20 @@
* limitations under the License.
*/
@SuppressWarnings("all")
module lawlesslegends {
requires nestedvm;
requires java.base;
requires java.logging;
requires java.desktop;
requires transitive java.desktop;
requires java.datatransfer;
requires java.scripting;
requires static java.compiler;
requires static transitive java.compiler;
requires javafx.fxmlEmpty;
requires javafx.fxml;
requires javafx.swing;
requires javafx.controlsEmpty;
requires javafx.controls;
requires transitive javafx.controls;
requires javafx.baseEmpty;
requires javafx.base;
requires javafx.webEmpty;
@ -37,20 +38,31 @@ module lawlesslegends {
requires javafx.media;
requires jdk.jsobject;
requires org.lwjgl.openal;
requires org.lwjgl.stb;
// requires org.reflections;
opens jace to javafx.graphics, javafx.fxml, javafx.controls;
opens jace.config to javafx.fxml, javafx.controls;
opens jace.data to javafx.graphics, javafx.fxml, javafx.controls;
// opens jace.data to javafx.graphics, javafx.fxml, javafx.controls;
opens jace.ide to javafx.graphics, javafx.fxml, javafx.controls;
opens fxml to javafx.graphics, javafx.fxml, javafx.controls;
opens styles to javafx.graphics, javafx.fxml, javafx.controls;
// opens fxml to javafx.graphics, javafx.fxml, javafx.controls;
// opens styles to javafx.graphics, javafx.fxml, javafx.controls;
uses javax.sound.sampled.SourceDataLine;
provides javax.annotation.processing.Processor with jace.config.InvokableActionAnnotationProcessor;
exports jace;
exports jace.apple2e;
exports jace.cheat;
exports jace.config;
exports jace.core;
exports jace.hardware;
exports jace.hardware.mockingboard;
exports jace.lawless;
exports jace.library;
exports jace.state;
exports jace.ui;
}

View File

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

View File

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

View File

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