mirror of
https://github.com/badvision/lawless-legends.git
synced 2024-06-09 23:29:37 +00:00
Merge branch 'master' of https://github.com/badvision/lawless-legends
This commit is contained in:
commit
e99bd01741
55
Platform/Apple/tools/jace/.vscode/launch.json
vendored
Normal file
55
Platform/Apple/tools/jace/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "java",
|
||||
"request": "attach",
|
||||
"name": "Attach by Process ID",
|
||||
"processId": "${command:PickJavaProcess}"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Current File",
|
||||
"request": "launch",
|
||||
"mainClass": "${file}"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "ConvertDiskImage",
|
||||
"request": "launch",
|
||||
"mainClass": "lawlesslegends/jace.ConvertDiskImage",
|
||||
"projectName": "lawlesslegends"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "LawlessLegends",
|
||||
"request": "launch",
|
||||
"mainClass": "lawlesslegends/jace.LawlessLegends",
|
||||
"projectName": "lawlesslegends"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "ApplesoftProgram",
|
||||
"request": "launch",
|
||||
"mainClass": "lawlesslegends/jace.applesoft.ApplesoftProgram",
|
||||
"projectName": "lawlesslegends"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "AcmeCrossAssembler",
|
||||
"request": "launch",
|
||||
"mainClass": "lawlesslegends/jace.assembly.AcmeCrossAssembler",
|
||||
"projectName": "lawlesslegends"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Library",
|
||||
"request": "launch",
|
||||
"mainClass": "lawlesslegends/jace.ui.Library",
|
||||
"projectName": "lawlesslegends"
|
||||
}
|
||||
]
|
||||
}
|
4
Platform/Apple/tools/jace/.vscode/settings.json
vendored
Normal file
4
Platform/Apple/tools/jace/.vscode/settings.json
vendored
Normal 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"
|
||||
}
|
|
@ -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>
|
|
@ -35,7 +35,7 @@ import jace.lawless.LawlessComputer;
|
|||
public class Emulator {
|
||||
|
||||
public static Emulator instance;
|
||||
public static EmulatorUILogic logic = new EmulatorUILogic();
|
||||
private static EmulatorUILogic logic;
|
||||
public static Thread mainThread;
|
||||
|
||||
// public static void main(String... args) {
|
||||
|
@ -44,6 +44,13 @@ public class Emulator {
|
|||
// }
|
||||
|
||||
private final LawlessComputer computer;
|
||||
|
||||
public static EmulatorUILogic getUILogic() {
|
||||
if (logic == null) {
|
||||
logic = new EmulatorUILogic();
|
||||
}
|
||||
return logic;
|
||||
}
|
||||
|
||||
public static Emulator getInstance(List<String> args) {
|
||||
Emulator i = getInstance();
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
package jace;
|
||||
|
||||
import static jace.core.Utility.gripe;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
@ -44,7 +46,6 @@ import jace.config.Reconfigurable;
|
|||
import jace.core.Debugger;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMListener;
|
||||
import static jace.core.Utility.gripe;
|
||||
import jace.ide.IdeController;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.EventHandler;
|
||||
|
@ -90,7 +91,7 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
category = "General",
|
||||
name = "Show Drives"
|
||||
)
|
||||
public boolean showDrives = false;
|
||||
public boolean showDrives = Emulator.withComputer(c->!c.PRODUCTION_MODE, false);
|
||||
|
||||
public static void updateCPURegisters(MOS65C02 cpu) {
|
||||
// DebuggerPanel debuggerPanel = Emulator.getFrame().getDebuggerPanel();
|
||||
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -138,7 +138,7 @@ public class LawlessLegends extends Application {
|
|||
new Thread(()->{
|
||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Booting with watchdog");
|
||||
RAMListener startListener = c.getMemory().
|
||||
observe(RAMEvent.TYPE.EXECUTE, 0x2000, (e) -> {
|
||||
observe("Lawless Legends watchdog", RAMEvent.TYPE.EXECUTE, 0x2000, (e) -> {
|
||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Boot was detected, watchdog terminated.");
|
||||
romStarted = true;
|
||||
});
|
||||
|
|
|
@ -76,9 +76,9 @@ public class Apple2e extends Computer {
|
|||
@ConfigurableField(name = "Slot 5", shortName = "s5card")
|
||||
public DeviceSelection<Cards> card5 = new DeviceSelection<>(Cards.class, null);
|
||||
@ConfigurableField(name = "Slot 6", shortName = "s6card")
|
||||
public DeviceSelection<Cards> card6 = new DeviceSelection<>(Cards.class, Cards.DiskIIDrive);
|
||||
public DeviceSelection<Cards> card6 = new DeviceSelection<>(Cards.class, Cards.DiskIIDrive, true);
|
||||
@ConfigurableField(name = "Slot 7", shortName = "s7card")
|
||||
public DeviceSelection<Cards> card7 = new DeviceSelection<>(Cards.class, Cards.MassStorage);
|
||||
public DeviceSelection<Cards> card7 = new DeviceSelection<>(Cards.class, Cards.MassStorage, true);
|
||||
@ConfigurableField(name = "Debug rom", shortName = "debugRom", description = "Use debugger //e rom")
|
||||
public boolean useDebugRom = false;
|
||||
@ConfigurableField(name = "Helpful hints", shortName = "hints")
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -423,11 +423,11 @@ public class VideoNTSC extends VideoDHGR {
|
|||
return;
|
||||
}
|
||||
RAM memory = computer.getMemory();
|
||||
rgbStateListeners.add(memory.observe(RAMEvent.TYPE.ANY, 0x0c05e, (e) -> {
|
||||
rgbStateListeners.add(memory.observe("NTSC: AN3 state change", RAMEvent.TYPE.ANY, 0x0c05e, (e) -> {
|
||||
an3 = false;
|
||||
rgbStateChange();
|
||||
}));
|
||||
rgbStateListeners.add(memory.observe(RAMEvent.TYPE.ANY, 0x0c05f, (e) -> {
|
||||
rgbStateListeners.add(memory.observe("NTSC: 80COL state change", RAMEvent.TYPE.ANY, 0x0c05f, (e) -> {
|
||||
if (!an3) {
|
||||
f2 = f1;
|
||||
f1 = SoftSwitches._80COL.getState();
|
||||
|
@ -435,7 +435,7 @@ public class VideoNTSC extends VideoDHGR {
|
|||
an3 = true;
|
||||
rgbStateChange();
|
||||
}));
|
||||
rgbStateListeners.add(memory.observe(RAMEvent.TYPE.EXECUTE, 0x0fa62, (e) -> {
|
||||
rgbStateListeners.add(memory.observe("NTSC: Reset hook for reverting RGB mode", RAMEvent.TYPE.EXECUTE, 0x0fa62, (e) -> {
|
||||
// When reset hook is called, reset the graphics mode
|
||||
// This is useful in case a program is running that
|
||||
// is totally clueless how to set the RGB state correctly.
|
||||
|
|
|
@ -40,7 +40,7 @@ public class IntC8SoftSwitch extends SoftSwitch {
|
|||
super("InternalC8Rom", false);
|
||||
// INTC8Rom should activate whenever C3xx memory is accessed and SLOTC3ROM is off
|
||||
addListener(
|
||||
new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
new RAMListener("Softswitch " + getName() + " on", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0C300);
|
||||
|
@ -57,7 +57,7 @@ public class IntC8SoftSwitch extends SoftSwitch {
|
|||
|
||||
// INTCXRom shoud deactivate whenever CFFF is accessed
|
||||
addListener(
|
||||
new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
new RAMListener("Softswitch " + getName() + " off", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0CFFF);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package jace.cheat;
|
||||
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
|
@ -9,7 +11,6 @@ import javafx.beans.property.SimpleIntegerProperty;
|
|||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.util.Callback;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -21,10 +22,11 @@ public class DynamicCheat extends RAMListener {
|
|||
StringProperty expression;
|
||||
BooleanProperty active;
|
||||
StringProperty name;
|
||||
String cheatName;
|
||||
Callback<RAMEvent, Integer> expressionCallback;
|
||||
|
||||
public DynamicCheat(int address, String expr) {
|
||||
super(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY);
|
||||
public DynamicCheat(String cheatName, int address, String expr) {
|
||||
super(cheatName, RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY);
|
||||
id = (int) (Math.random() * 10000000);
|
||||
addr = new SimpleIntegerProperty(address);
|
||||
expression = new SimpleStringProperty(expr);
|
||||
|
@ -106,18 +108,19 @@ public class DynamicCheat extends RAMListener {
|
|||
|
||||
public static final String DELIMITER = ";";
|
||||
public String serialize() {
|
||||
return escape(name.get()) + DELIMITER
|
||||
return escape(cheatName) + DELIMITER + escape(name.get()) + DELIMITER
|
||||
+ escape("$"+Integer.toHexString(addr.get())) + DELIMITER
|
||||
+ escape(expression.get());
|
||||
}
|
||||
|
||||
static public DynamicCheat deserialize(String in) {
|
||||
String[] parts = in.split(DELIMITER);
|
||||
String name = unescape(parts[0]);
|
||||
Integer addr = Integer.parseInt(parts[1].substring(1), 16);
|
||||
String expr = unescape(parts[2]);
|
||||
String cheatName = unescape(parts[0]);
|
||||
String name = unescape(parts[1]);
|
||||
Integer addr = Integer.parseInt(parts[2].substring(1), 16);
|
||||
String expr = unescape(parts[3]);
|
||||
|
||||
DynamicCheat out = new DynamicCheat(addr, expr);
|
||||
DynamicCheat out = new DynamicCheat(cheatName, addr, expr);
|
||||
out.name.set(name);
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -302,7 +302,7 @@ public class MetaCheat extends Cheats {
|
|||
}
|
||||
}
|
||||
if (memoryViewListener == null) {
|
||||
memoryViewListener = memory.observe(RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent);
|
||||
memoryViewListener = memory.observe("Metacheat memory viewer", RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent);
|
||||
listeners.add(memoryViewListener);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -71,21 +71,21 @@ public class MontezumasRevengeCheats extends Cheats {
|
|||
@Override
|
||||
public void registerListeners() {
|
||||
if (repulsiveHack) {
|
||||
addCheat(RAMEvent.TYPE.WRITE, this::repulsiveBehavior, 0x1508, 0x1518);
|
||||
addCheat("Repulsive", RAMEvent.TYPE.WRITE, this::repulsiveBehavior, 0x1508, 0x1518);
|
||||
}
|
||||
|
||||
if (featherFall) {
|
||||
addCheat(RAMEvent.TYPE.WRITE, this::featherFallBehavior, PLAYER_Y);
|
||||
addCheat("Feather fall", RAMEvent.TYPE.WRITE, this::featherFallBehavior, PLAYER_Y);
|
||||
// Bypass the part that realizes you should die when you hit the floor
|
||||
bypassCode(0x6bb3, 0x6bb4);
|
||||
bypassCode("Feather fall code hack", 0x6bb3, 0x6bb4);
|
||||
}
|
||||
|
||||
if (moonJump) {
|
||||
addCheat(RAMEvent.TYPE.WRITE, this::moonJumpBehavior, Y_VELOCITY);
|
||||
addCheat("Moon jump", RAMEvent.TYPE.WRITE, this::moonJumpBehavior, Y_VELOCITY);
|
||||
}
|
||||
|
||||
if (infiniteLives) {
|
||||
forceValue(11, LIVES);
|
||||
forceValue("Infinite lives", 11, LIVES);
|
||||
}
|
||||
|
||||
if (safePassage) {
|
||||
|
@ -103,22 +103,22 @@ public class MontezumasRevengeCheats extends Cheats {
|
|||
memory.write(0x0f51, (byte) 0b00001000, false, false);
|
||||
memory.write(0x0f52, (byte) 0b10000100, false, false);
|
||||
memory.write(0x0f53, (byte) 0b11010101, false, false);
|
||||
forceValue(32, FLOOR_TIMER);
|
||||
forceValue(32, HAZARD_TIMER);
|
||||
forceValue(1, HAZARD_FLAG);
|
||||
forceValue("Hack floor timer", 32, FLOOR_TIMER);
|
||||
forceValue("Hack hazard timer", 32, HAZARD_TIMER);
|
||||
forceValue("Hack hazard flag", 1, HAZARD_FLAG);
|
||||
});
|
||||
}
|
||||
|
||||
if (scoreHack) {
|
||||
// Score: 900913
|
||||
forceValue(0x90, SCORE);
|
||||
forceValue(0x09, SCORE + 1);
|
||||
forceValue(0x13, SCORE + 2);
|
||||
forceValue("Hack score 1", 0x90, SCORE);
|
||||
forceValue("Hack score 2", 0x09, SCORE + 1);
|
||||
forceValue("Hack score 3", 0x13, SCORE + 2);
|
||||
}
|
||||
|
||||
if (snakeCharmer) {
|
||||
// Skip the code that determines you're touching an enemy
|
||||
bypassCode(0x07963, 0x07964);
|
||||
bypassCode("Snake charmer", 0x07963, 0x07964);
|
||||
}
|
||||
if (mouseHack) {
|
||||
EmulatorUILogic.addMouseListener(listener);
|
||||
|
|
|
@ -172,19 +172,19 @@ public class PrinceOfPersiaCheats extends Cheats {
|
|||
@Override
|
||||
public void registerListeners() {
|
||||
if (velocityHack) {
|
||||
addCheat(RAMEvent.TYPE.READ_DATA, true, this::velocityHackBehavior, CharYVel);
|
||||
addCheat("Hack velocity", RAMEvent.TYPE.READ_DATA, true, this::velocityHackBehavior, CharYVel);
|
||||
}
|
||||
if (invincibilityHack) {
|
||||
forceValue(3, true, KidStrength);
|
||||
forceValue("Hack invincibility", 3, true, KidStrength);
|
||||
}
|
||||
if (sleepHack) {
|
||||
forceValue(0, true, EnemyAlert);
|
||||
forceValue("Go to sleep!", 0, true, EnemyAlert);
|
||||
}
|
||||
if (swordHack) {
|
||||
forceValue(1, true, hasSword);
|
||||
forceValue("Can haz sword", 1, true, hasSword);
|
||||
}
|
||||
if (timeHack) {
|
||||
forceValue(0x69, true, MinLeft);
|
||||
forceValue("Hack time", 0x69, true, MinLeft);
|
||||
}
|
||||
if (mouseHack) {
|
||||
EmulatorUILogic.addMouseListener(listener);
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,14 +39,17 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler, Comparabl
|
|||
private int valueStart;
|
||||
private int valueEnd;
|
||||
private int valueAmount;
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Creates a new instance of RAMListener
|
||||
* @param name
|
||||
* @param t
|
||||
* @param s
|
||||
* @param v
|
||||
*/
|
||||
public RAMListener(RAMEvent.TYPE t, RAMEvent.SCOPE s, RAMEvent.VALUE v) {
|
||||
public RAMListener(String name, RAMEvent.TYPE t, RAMEvent.SCOPE s, RAMEvent.VALUE v) {
|
||||
setName(name);
|
||||
setType(t);
|
||||
setScope(s);
|
||||
setValue(v);
|
||||
|
@ -77,6 +80,14 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler, Comparabl
|
|||
this.value = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getScopeStart() {
|
||||
return scopeStart;
|
||||
}
|
||||
|
@ -161,18 +172,23 @@ public abstract class RAMListener implements RAMEvent.RAMEventHandler, Comparabl
|
|||
|
||||
@Override
|
||||
public int compareTo(RAMListener o) {
|
||||
if (o.scopeStart == scopeStart) {
|
||||
if (o.scopeEnd == scopeEnd) {
|
||||
if (o.type == type) {
|
||||
return Integer.compare(o.hashCode(), hashCode());
|
||||
if (o.name == name) {
|
||||
if (o.scopeStart == scopeStart) {
|
||||
if (o.scopeEnd == scopeEnd) {
|
||||
if (o.type == type) {
|
||||
// Ignore hash codes -- combination of name, address range and type should identify similar listeners.
|
||||
return (int) 0;
|
||||
} else {
|
||||
return Integer.compare(o.type.ordinal(), type.ordinal());
|
||||
}
|
||||
} else {
|
||||
return Integer.compare(o.type.ordinal(), type.ordinal());
|
||||
return Integer.compare(o.scopeEnd, scopeEnd);
|
||||
}
|
||||
} else {
|
||||
return Integer.compare(o.scopeEnd, scopeEnd);
|
||||
return Integer.compare(o.scopeStart, scopeStart);
|
||||
}
|
||||
} else {
|
||||
return Integer.compare(o.scopeStart, scopeStart);
|
||||
return o.name.compareTo(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ public abstract class SoftSwitch {
|
|||
exclusionActivate.add(i);
|
||||
}
|
||||
}
|
||||
RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
RAMListener l = new RAMListener("Softswitch toggle " + name, changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(beginAddr);
|
||||
|
@ -136,7 +136,7 @@ public abstract class SoftSwitch {
|
|||
exclusionActivate.add(i);
|
||||
}
|
||||
}
|
||||
RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
RAMListener l = new RAMListener("Softswitch on " + name, changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(beginAddr);
|
||||
|
@ -169,7 +169,7 @@ public abstract class SoftSwitch {
|
|||
exclusionDeactivate.add(i);
|
||||
}
|
||||
}
|
||||
RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
RAMListener l = new RAMListener("Softswitch off " + name, changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(beginAddr);
|
||||
|
@ -201,7 +201,7 @@ public abstract class SoftSwitch {
|
|||
}
|
||||
}
|
||||
// RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
RAMListener l = new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
RAMListener l = new RAMListener("Softswitch read state " + name, RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(beginAddr);
|
||||
|
|
|
@ -18,14 +18,25 @@
|
|||
*/
|
||||
package jace.core;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL;
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.ALC;
|
||||
import org.lwjgl.openal.ALC10;
|
||||
import org.lwjgl.openal.ALCCapabilities;
|
||||
import org.lwjgl.openal.ALCapabilities;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.config.ConfigurableField;
|
||||
|
||||
/**
|
||||
|
@ -41,8 +52,9 @@ public class SoundMixer extends Device {
|
|||
|
||||
/**
|
||||
* Bits per sample
|
||||
* Making this configurable requires too much effort and not a lot of benefit
|
||||
*/
|
||||
@ConfigurableField(name = "Bits per sample", shortName = "bits")
|
||||
// @ConfigurableField(name = "Bits per sample", shortName = "bits")
|
||||
public static int BITS = 16;
|
||||
/**
|
||||
* Sample playback rate
|
||||
|
@ -51,38 +63,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -18,6 +18,14 @@
|
|||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jace.EmulatorUILogic;
|
||||
import jace.core.Computer;
|
||||
import jace.library.MediaConsumer;
|
||||
|
@ -25,12 +33,6 @@ import jace.library.MediaEntry;
|
|||
import jace.library.MediaEntry.MediaFile;
|
||||
import jace.state.StateManager;
|
||||
import jace.state.Stateful;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
/**
|
||||
|
@ -50,11 +52,13 @@ public class DiskIIDrive implements MediaConsumer {
|
|||
this.computer = computer;
|
||||
}
|
||||
|
||||
public boolean DEBUG = true;
|
||||
|
||||
FloppyDisk disk;
|
||||
// Number of milliseconds to wait between last write and update to disk image
|
||||
public static long WRITE_UPDATE_DELAY = 1000;
|
||||
// Flag to halt if any writes to floopy occur when updating physical disk image
|
||||
boolean diskUpdatePending = false;
|
||||
AtomicBoolean diskUpdatePending = new AtomicBoolean();
|
||||
// Last time of write operation
|
||||
long lastWriteTime;
|
||||
// Managed thread to update disk image in background
|
||||
|
@ -86,7 +90,7 @@ public class DiskIIDrive implements MediaConsumer {
|
|||
driveOn = false;
|
||||
magnets = 0;
|
||||
dirtyTracks = new HashSet<>();
|
||||
diskUpdatePending = false;
|
||||
diskUpdatePending.set(false);
|
||||
}
|
||||
|
||||
void step(int register) {
|
||||
|
@ -113,13 +117,18 @@ public class DiskIIDrive implements MediaConsumer {
|
|||
}
|
||||
nibbleOffset = 0;
|
||||
|
||||
//System.out.printf("new half track %d\n", currentHalfTrack);
|
||||
if (DEBUG) {
|
||||
System.out.printf("step %d, new half track %d\n", register, halfTrack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setOn(boolean b) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Drive setOn: "+b);
|
||||
}
|
||||
driveOn = b;
|
||||
}
|
||||
|
||||
|
@ -155,17 +164,20 @@ public class DiskIIDrive implements MediaConsumer {
|
|||
|
||||
void write() {
|
||||
if (writeMode) {
|
||||
while (diskUpdatePending) {
|
||||
while (diskUpdatePending.get()) {
|
||||
// If another thread requested writes to block (e.g. because of disk activity), wait for it to finish!
|
||||
LockSupport.parkNanos(1000);
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
if (disk != null) {
|
||||
// Do nothing if write-protection is enabled!
|
||||
if (getMediaEntry() == null || !getMediaEntry().writeProtected) {
|
||||
dirtyTracks.add(trackStartOffset / FloppyDisk.TRACK_NIBBLE_LENGTH);
|
||||
disk.nibbles[trackStartOffset + nibbleOffset++] = latch;
|
||||
triggerDiskUpdate();
|
||||
StateManager.markDirtyValue(disk.nibbles, computer);
|
||||
// Holding the lock should block any other threads from writing to disk
|
||||
synchronized (diskUpdatePending) {
|
||||
if (disk != null) {
|
||||
// Do nothing if write-protection is enabled!
|
||||
if (getMediaEntry() == null || !getMediaEntry().writeProtected) {
|
||||
dirtyTracks.add(trackStartOffset / FloppyDisk.TRACK_NIBBLE_LENGTH);
|
||||
disk.nibbles[trackStartOffset + nibbleOffset++] = latch;
|
||||
triggerDiskUpdate();
|
||||
StateManager.markDirtyValue(disk.nibbles, computer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,19 +204,20 @@ public class DiskIIDrive implements MediaConsumer {
|
|||
}
|
||||
|
||||
private void updateDisk() {
|
||||
|
||||
// Signal disk update is underway
|
||||
diskUpdatePending = true;
|
||||
// Update all tracks as necessary
|
||||
if (disk != null) {
|
||||
dirtyTracks.stream().forEach((track) -> {
|
||||
disk.updateTrack(track);
|
||||
});
|
||||
synchronized (diskUpdatePending) {
|
||||
diskUpdatePending.set(true);
|
||||
// Update all tracks as necessary
|
||||
if (disk != null) {
|
||||
dirtyTracks.stream().forEach((track) -> {
|
||||
disk.updateTrack(track);
|
||||
});
|
||||
}
|
||||
// Empty out dirty list
|
||||
dirtyTracks.clear();
|
||||
}
|
||||
// Empty out dirty list
|
||||
dirtyTracks.clear();
|
||||
// Signal disk update is completed
|
||||
diskUpdatePending = false;
|
||||
diskUpdatePending.set(false);
|
||||
}
|
||||
|
||||
private void triggerDiskUpdate() {
|
||||
|
@ -226,6 +239,9 @@ public class DiskIIDrive implements MediaConsumer {
|
|||
}
|
||||
|
||||
void insertDisk(File diskPath) throws IOException {
|
||||
if (DEBUG) {
|
||||
System.out.println("inserting disk " + diskPath.getAbsolutePath() + " into drive");
|
||||
}
|
||||
disk = new FloppyDisk(diskPath, computer);
|
||||
dirtyTracks = new HashSet<>();
|
||||
// Emulator state has changed significantly, reset state manager
|
||||
|
@ -300,14 +316,16 @@ public class DiskIIDrive implements MediaConsumer {
|
|||
@Override
|
||||
public boolean isAccepted(MediaEntry e, MediaFile f) {
|
||||
if (f == null) return false;
|
||||
// System.out.println("Type is accepted: "+f.path+"; "+e.type.toString()+": "+e.type.is140kb);
|
||||
if (DEBUG) {
|
||||
System.out.println("Type is accepted: "+f.path+"; "+e.type.toString()+": "+e.type.is140kb);
|
||||
}
|
||||
return e.type.is140kb;
|
||||
}
|
||||
|
||||
private void waitForPendingWrites() {
|
||||
while (diskUpdatePending || !dirtyTracks.isEmpty()) {
|
||||
while (diskUpdatePending.get()) {
|
||||
// If the current disk has unsaved changes, wait!!!
|
||||
LockSupport.parkNanos(1000);
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,9 +18,6 @@
|
|||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.state.StateManager;
|
||||
import jace.state.Stateful;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -32,6 +29,10 @@ import java.util.Arrays;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.state.StateManager;
|
||||
import jace.state.Stateful;
|
||||
|
||||
/**
|
||||
* Representation of a 140kb floppy disk image. This also performs conversions
|
||||
* as needed. Internally, the emulator will always use a "nibblized" disk
|
||||
|
@ -180,6 +181,17 @@ public class FloppyDisk {
|
|||
writeJunkBytes(output, 38 - gap2);
|
||||
}
|
||||
}
|
||||
// Write output to stdout for debugging purposes
|
||||
if (DEBUG) {
|
||||
System.out.println("Nibblized disk:");
|
||||
for (int i = 0; i < output.size(); i++) {
|
||||
System.out.print(Integer.toString(output.toByteArray()[i] & 0x0ff, 16) + " ");
|
||||
if (i % 16 == 255) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
|
@ -298,17 +310,23 @@ public class FloppyDisk {
|
|||
byte[] trackNibbles = new byte[TRACK_NIBBLE_LENGTH];
|
||||
byte[] trackData = new byte[SECTOR_COUNT * 256];
|
||||
// Copy track into temporary buffer
|
||||
// System.out.println("Nibblized track "+track);
|
||||
// System.out.printf("%04d:",0);
|
||||
for (int i = 0, pos = track * TRACK_NIBBLE_LENGTH; i < TRACK_NIBBLE_LENGTH; i++, pos++) {
|
||||
trackNibbles[i] = nibbles[pos];
|
||||
// System.out.print(Integer.toString(nibbles[pos] & 0x0ff, 16)+" ");
|
||||
// if (i % 16 == 15) {
|
||||
// System.out.println();
|
||||
// System.out.printf("%04d:",i+1);
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println("Nibblized track "+track);
|
||||
System.out.printf("%04d:",0);
|
||||
}
|
||||
for (int i = 0, pos = track * TRACK_NIBBLE_LENGTH; i < TRACK_NIBBLE_LENGTH; i++, pos++) {
|
||||
trackNibbles[i] = nibbles[pos];
|
||||
if (DEBUG) {
|
||||
System.out.print(Integer.toString(nibbles[pos] & 0x0ff, 16)+" ");
|
||||
if (i % 16 == 15) {
|
||||
System.out.println();
|
||||
System.out.printf("%04d:",i+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
System.out.println();
|
||||
}
|
||||
// System.out.println();
|
||||
|
||||
int pos = 0;
|
||||
for (int i = 0; i < SECTOR_COUNT; i++) {
|
||||
|
@ -318,7 +336,9 @@ public class FloppyDisk {
|
|||
int trackVerify = decodeOddEven(trackNibbles[pos + 5], trackNibbles[pos + 6]);
|
||||
// Locate sector number
|
||||
int sector = decodeOddEven(trackNibbles[pos + 7], trackNibbles[pos + 8]);
|
||||
// System.out.println("Writing track " + track + ", getting address block for T" + trackVerify + ".S" + sector + " found at NIB offset "+pos);
|
||||
if (DEBUG) {
|
||||
System.out.println("Writing track " + track + ", getting address block for T" + trackVerify + ".S" + sector + " found at NIB offset "+pos);
|
||||
}
|
||||
// Skip to end of address block
|
||||
pos = locatePattern(pos, trackNibbles, 0x0de, 0x0aa /*, 0x0eb this is sometimes being written as FF??*/);
|
||||
// Locate start of sector data
|
||||
|
@ -326,7 +346,9 @@ public class FloppyDisk {
|
|||
// Determine offset in output data for sector
|
||||
//int offset = reverseLoopkup(currentSectorOrder, sector) * 256;
|
||||
int offset = currentSectorOrder[sector] * 256;
|
||||
// System.out.println("Sector "+sector+" maps to physical sector "+reverseLoopkup(currentSectorOrder, sector));
|
||||
if (DEBUG) {
|
||||
System.out.println("Sector "+sector+" maps to physical sector "+reverseLoopkup(currentSectorOrder, sector));
|
||||
}
|
||||
// Decode sector data
|
||||
denibblizeSector(trackNibbles, pos + 3, trackData, offset);
|
||||
// Skip to end of sector
|
||||
|
|
|
@ -201,7 +201,7 @@ public class Joystick extends Device {
|
|||
RAMListener listener;
|
||||
|
||||
private void registerListeners() {
|
||||
listener = computer.getMemory().observe(RAMEvent.TYPE.ANY, 0x0c070, 0x0c07f, this::initJoystickRead);
|
||||
listener = computer.getMemory().observe("Joystick I/O", RAMEvent.TYPE.ANY, 0x0c070, 0x0c07f, this::initJoystickRead);
|
||||
}
|
||||
|
||||
private void removeListeners() {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package jace.hardware;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Optional;
|
||||
|
||||
import jace.EmulatorUILogic;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.config.ConfigurableField;
|
||||
|
@ -8,8 +11,6 @@ import jace.core.Device;
|
|||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.core.Utility;
|
||||
import java.util.Calendar;
|
||||
import java.util.Optional;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
/**
|
||||
|
@ -31,7 +32,7 @@ public class NoSlotClock extends Device {
|
|||
public boolean patchProdosClock = false;
|
||||
Optional<Label> clockIcon;
|
||||
|
||||
private final RAMListener listener = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
private final RAMListener listener = new RAMListener("No slot clock read", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0C100);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -202,6 +202,8 @@ public class FileNode extends DiskNode {
|
|||
readFile(buffer, (block - treeBlocks - 1));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.EmulatorUILogic;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.core.Computer;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -35,9 +35,6 @@ public class TimedGenerator {
|
|||
double cyclesPerSample;
|
||||
int clocksPerPeriod;
|
||||
|
||||
private TimedGenerator() {
|
||||
}
|
||||
|
||||
public TimedGenerator(int _clock, int _sampleRate) {
|
||||
setRate(clock, sampleRate);
|
||||
reset();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 += " -- ";
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.logging.Logger;
|
|||
*
|
||||
* @author blurry
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public class Program {
|
||||
|
||||
public static String CODEMIRROR_EDITOR = "/codemirror/editor.html";
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
34
Platform/Apple/tools/jace/src/test/java/jace/SoundTest.java
Normal file
34
Platform/Apple/tools/jace/src/test/java/jace/SoundTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,17 +14,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
package jace.core;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static jace.TestUtils.initComputer;
|
||||
import static jace.TestUtils.runAssemblyCode;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.Emulator;
|
||||
import static jace.TestUtils.initComputer;
|
||||
import static jace.TestUtils.runAssemblyCode;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
|
||||
|
@ -50,7 +51,7 @@ public class MemoryListenerTest {
|
|||
@Test
|
||||
public void testListenerRelevance() {
|
||||
AtomicInteger anyEventCaught = new AtomicInteger();
|
||||
RAMListener anyListener = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
RAMListener anyListener = new RAMListener("Execution test", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0100);
|
||||
|
@ -63,7 +64,7 @@ public class MemoryListenerTest {
|
|||
};
|
||||
|
||||
AtomicInteger readAnyEventCaught = new AtomicInteger();
|
||||
RAMListener readAnyListener = new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
RAMListener readAnyListener = new RAMListener("Execution test 1", RAMEvent.TYPE.READ, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0100);
|
||||
|
@ -76,7 +77,7 @@ public class MemoryListenerTest {
|
|||
};
|
||||
|
||||
AtomicInteger writeEventCaught = new AtomicInteger();
|
||||
RAMListener writeListener = new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
RAMListener writeListener = new RAMListener("Execution test 2", RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0100);
|
||||
|
@ -89,7 +90,7 @@ public class MemoryListenerTest {
|
|||
};
|
||||
|
||||
AtomicInteger executeEventCaught = new AtomicInteger();
|
||||
RAMListener executeListener = new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
RAMListener executeListener = new RAMListener("Execution test 3", RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0100);
|
||||
|
|
|
@ -15,17 +15,18 @@
|
|||
*/
|
||||
package jace.cpu;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.Emulator;
|
||||
import static jace.TestUtils.assemble;
|
||||
import static jace.TestUtils.createSimpleDevice;
|
||||
import static jace.TestUtils.initComputer;
|
||||
import static jace.TestUtils.runAssemblyCode;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.core.Computer;
|
||||
|
@ -90,7 +91,7 @@ WAIT3 sbc #$01
|
|||
AtomicInteger breakpointEncountered = new AtomicInteger();
|
||||
AtomicInteger cycleCount = new AtomicInteger();
|
||||
// This listener will increment our breakpoint counter if it reaches our desired stoppoing point in time
|
||||
RAMListener l = ram.addExecutionTrap(0x01000, e -> breakpointEncountered.incrementAndGet());
|
||||
RAMListener l = ram.addExecutionTrap("Halting test", 0x01000, e -> breakpointEncountered.incrementAndGet());
|
||||
// This faux device counts the number of cycles executed
|
||||
cpu.addChildDevice(createSimpleDevice(()->{
|
||||
if (breakpointEncountered.get() == 0) {
|
||||
|
@ -116,7 +117,7 @@ WAIT3 sbc #$01
|
|||
AtomicInteger breakpointEncountered = new AtomicInteger();
|
||||
AtomicInteger cycleCount = new AtomicInteger();
|
||||
// This listener will increment our breakpoint counter if it reaches our desired stoppoing point in time
|
||||
RAMListener l = ram.addExecutionTrap(0x01000, e -> breakpointEncountered.incrementAndGet());
|
||||
RAMListener l = ram.addExecutionTrap("Halting test", 0x01000, e -> breakpointEncountered.incrementAndGet());
|
||||
|
||||
// This faux device counts the number of cycles executed
|
||||
cpu.addChildDevice(createSimpleDevice(()->{
|
||||
|
|
Loading…
Reference in New Issue
Block a user