diff --git a/Source/AppleIIGo.java b/Source/AppleIIGo.java index 3ef8e9b..8e58227 100644 --- a/Source/AppleIIGo.java +++ b/Source/AppleIIGo.java @@ -2,17 +2,38 @@ /** * AppleIIGo * The Java Apple II Emulator - * (C) 2006 by Marc S. Ressl(ressl@lonetree.com) + * (C) 2006 by Marc S. Ressl (ressl@lonetree.com) + * (C) 2008 by Nick Westgate (Nick.Westgate@gmail.com) * Released under the GPL + * + * Change list: + * + * Version 1.0.2 - changes by Nick: + * - improved sound sync by moving AppleSpeaker into the main thread + * - added version (F1) + * - added multiple disks & switching (F3, F4) + * - added ZIP archive support + * - fixed HTTP disk image access bug */ -import java.applet.*; -import java.io.*; -import java.net.*; -import java.util.zip.*; -import java.awt.*; -import java.awt.event.*; -import java.awt.Graphics2D; +import java.applet.Applet; +import java.awt.Graphics; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.io.DataInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.zip.GZIPInputStream; +import java.util.zip.ZipInputStream; /** * AppleIIGo class
@@ -20,10 +41,13 @@ import java.awt.Graphics2D;
*/
public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
MouseListener, MouseMotionListener {
+
+ final String version = "1.0.2";
+ final String versionString = "AppleIIGo Version " + version;
+
// Class instances
private EmAppleII apple;
private AppleDisplay display;
- private AppleSpeaker speaker;
private DiskII disk;
// Machine variables
@@ -39,6 +63,10 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
// Disk variables
private String diskDriveResource[] = new String[2];
private boolean diskWritable;
+ private String[] disks0;
+ private String[] disks1;
+ private int disk0;
+ private int disk1;
/**
* Debug
@@ -91,8 +119,8 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
display.setGlare(getAppletParameter("displayGlare", "false").equals("true"));
// Speaker
- speaker = new AppleSpeaker(apple);
- speaker.setVolume(new Integer(getAppletParameter("speakerVolume", "3")).intValue());
+ apple.speaker = new AppleSpeaker(apple);
+ apple.speaker.setVolume(new Integer(getAppletParameter("speakerVolume", "6")).intValue());
// Peripherals
disk = new DiskII();
@@ -100,8 +128,12 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
// Initialize disk drives
diskWritable = getAppletParameter("diskWritable", "false").equals("true");
- mountDisk(0, getAppletParameter("diskDrive1", ""));
- mountDisk(1, getAppletParameter("diskDrive2", ""));
+ disks0 = getAppletParameter("diskDrive1", "").split("[|]");
+ disk0 = 0;
+ disks1 = getAppletParameter("diskDrive2", "").split("[|]");
+ disk1 = 0;
+ mountDisk(0, disks0[disk0]);
+ mountDisk(1, disks1[disk1]);
// Start CPU
if (!isCpuPaused)
@@ -112,6 +144,7 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
* Start applet
*/
public void start() {
+ debug("AppleIIGo Version " + version);
debug("start()");
}
@@ -151,7 +184,7 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
isCpuPaused = true;
apple.setPaused(isCpuPaused);
display.setPaused(isCpuPaused);
-// speaker.setPaused(isCpuPaused);
+ apple.speaker.setPaused(isCpuPaused);
}
/**
@@ -160,7 +193,7 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
public void resume() {
debug("resume()");
isCpuPaused = false;
-// speaker.setPaused(isCpuPaused);
+ apple.speaker.setPaused(isCpuPaused);
display.setPaused(isCpuPaused);
apple.setPaused(isCpuPaused);
}
@@ -176,19 +209,37 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
/**
* Open input stream
*/
- private InputStream openInputStream(String resource) {
+ private DataInputStream openInputStream(String resource) {
InputStream is = null;
try {
URL url = new URL(getCodeBase(), resource);
- is = url.openStream();
+ debug("resource: " + url.toString());
+ is = url.openStream();
if (resource.toLowerCase().endsWith(".gz"))
+ {
is = new GZIPInputStream(is);
+ }
+ else if (resource.toLowerCase().endsWith(".zip"))
+ {
+ is = new ZipInputStream(is);
+ ((ZipInputStream)is).getNextEntry();
+ }
} catch (Exception e) {
+ debug("Exeption: " + e.getLocalizedMessage());
}
- return is;
+ if (is == null)
+ {
+ debug("failed");
+ return null;
+ }
+ else
+ {
+ debug("ok");
+ return new DataInputStream(is);
+ }
}
/**
@@ -214,10 +265,11 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
boolean success = false;
try {
- InputStream is = openInputStream(resource);
+ DataInputStream is = openInputStream(resource);
success = apple.loadRom(is);
is.close();
} catch (Exception e) {
+ debug("Exeption: " + e.getLocalizedMessage());
}
return success;
@@ -238,10 +290,11 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
diskDriveResource[drive] = resource;
- InputStream is = openInputStream(resource);
+ DataInputStream is = openInputStream(resource);
success = disk.readDisk(drive, is, 254, false);
is.close();
} catch (Exception e) {
+ debug("Exeption: " + e.getLocalizedMessage());
}
return success;
@@ -310,13 +363,13 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
break;
case KeyEvent.VK_UP:
if (e.isControlDown())
- speaker.setVolume(speaker.getVolume() + 1);
+ apple.speaker.setVolume(apple.speaker.getVolume() + 1);
else
apple.setKeyLatch(11);
break;
case KeyEvent.VK_DOWN:
if (e.isControlDown())
- speaker.setVolume(speaker.getVolume() - 1);
+ apple.speaker.setVolume(apple.speaker.getVolume() - 1);
else
apple.setKeyLatch(10);
break;
@@ -332,6 +385,23 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
else
apple.reset();
break;
+ case KeyEvent.VK_F1:
+ showStatus("AppleIIGo Version " + version);
+ break;
+ case KeyEvent.VK_F3:
+ if (disks0.length > 1) {
+ disk0 = ++disk0 % disks0.length;
+ mountDisk(0, disks0[disk0]);
+ showStatus("Disk 1: " + disks0[disk0]);
+ }
+ break;
+ case KeyEvent.VK_F4:
+ if (disks1.length > 1) {
+ disk1 = ++disk1 % disks1.length;
+ mountDisk(1, disks1[disk1]);
+ showStatus("Disk 2: " + disks1[disk1]);
+ }
+ break;
case KeyEvent.VK_F5:
if (isCpuDebugEnabled)
display.setStatMode(!display.getStatMode());
diff --git a/Source/AppleSpeaker.java b/Source/AppleSpeaker.java
index 4edcc38..84b1eaa 100644
--- a/Source/AppleSpeaker.java
+++ b/Source/AppleSpeaker.java
@@ -10,7 +10,7 @@ import javax.sound.sampled.*;
public class AppleSpeaker implements Runnable {
// Instances of other classes
- private EmAppleII apple;
+ private EmAppleII apple;
// Refresh
private int refreshRate;
@@ -29,7 +29,6 @@ public class AppleSpeaker implements Runnable {
private SourceDataLine line;
- private int bufferSamples;
private int bufferSize;
private byte[] buffer;
@@ -43,11 +42,8 @@ public class AppleSpeaker implements Runnable {
// Thread stuff
private boolean isPaused = true;
private Thread thread;
- private String threadError = null;
-
-
- public AppleSpeaker(EmAppleII apple) {
+ public AppleSpeaker(EmAppleII apple) {
this.apple = apple;
setVolume(4);
@@ -66,14 +62,14 @@ public class AppleSpeaker implements Runnable {
refreshInterval = (int) (1000.0 / value);
speakerClocksPerSample = (int) (apple.getCpuSpeed() * 1000.0f / SPEAKER_SAMPLERATE);
- }
+ }
/**
* Get refresh rate
*/
private int getRefreshRate() {
return refreshRate;
- }
+ }
/**
* Set speaker volume
@@ -94,7 +90,7 @@ public class AppleSpeaker implements Runnable {
*/
public int getVolume() {
return speakerVolume;
- }
+ }
/**
* Set pause state
@@ -130,8 +126,6 @@ public class AppleSpeaker implements Runnable {
try {
line = (SourceDataLine) AudioSystem.getLine(info);
bufferSize = line.getBufferSize();
- bufferSamples = bufferSize / SPEAKER_SAMPLESIZE;
-
buffer = new byte[bufferSize];
line.open(audioFormat);
@@ -139,15 +133,17 @@ public class AppleSpeaker implements Runnable {
} catch (LineUnavailableException e) {
}
- thread = new Thread(this);
- thread.start();
+ // TODO: this thread is not created any more (nick)
+ //thread = new Thread(this);
+ //thread.start();
}
}
/**
* Speaker refresh thread
+ * TODO: this thread is not created any more (nick)
*/
- public void run() {
+ public void run() {
try {
while (!isPaused) {
long refreshStart = System.currentTimeMillis();
@@ -169,7 +165,7 @@ public class AppleSpeaker implements Runnable {
/**
* Speaker refresh
*/
- private void refreshSpeaker() {
+ public void refreshSpeaker() {
clockEnd = apple.clock;
int bytes;
@@ -184,7 +180,7 @@ public class AppleSpeaker implements Runnable {
/**
* Fill buffer
*/
- private int fillBuffer() {
+ private int fillBuffer() {
int value = speakerFlipStateToVolume[speakerFlipState];
int clockEndSample = clockEnd - speakerClocksPerSample;
int bufferPointer = 0;
@@ -234,7 +230,7 @@ public class AppleSpeaker implements Runnable {
isFlipsBufferEmpty = true;
} else {
clockNextFlip = apple.speakerFlips[speakerFlipsPointer];
- speakerFlipsPointer = (speakerFlipsPointer + 1) & apple.SPEAKER_FLIPS_MASK;
+ speakerFlipsPointer = (speakerFlipsPointer + 1) & EmAppleII.SPEAKER_FLIPS_MASK;
}
}
}
diff --git a/Source/DiskII.java b/Source/DiskII.java
index 1cc0dbb..d9037da 100644
--- a/Source/DiskII.java
+++ b/Source/DiskII.java
@@ -265,7 +265,7 @@ public class DiskII extends Peripheral {
* @param is InputStream
* @param drive Disk II drive
*/
- public boolean readDisk(int drive, InputStream is, int volume, boolean isWriteProtected) {
+ public boolean readDisk(int drive, DataInputStream is, int volume, boolean isWriteProtected) {
byte[] track = new byte[RAW_TRACK_BYTES];
try {
@@ -273,7 +273,7 @@ public class DiskII extends Peripheral {
disk[drive][trackNum] = new byte[RAW_TRACK_BYTES];
if (is != null) {
- is.read(track, 0, DOS_TRACK_BYTES);
+ is.readFully(track, 0, DOS_TRACK_BYTES);
trackToNibbles(track, disk[drive][trackNum], volume, trackNum);
}
}
diff --git a/Source/EmAppleII.java b/Source/EmAppleII.java
index 1d82c33..1fb7186 100644
--- a/Source/EmAppleII.java
+++ b/Source/EmAppleII.java
@@ -58,6 +58,7 @@ public class EmAppleII extends Em6502 implements Runnable {
// Peripherals
public Paddle paddle;
public Peripheral[] slots;
+ public AppleSpeaker speaker;
// Graphics (dirty buffer every 0x80 bytes)
public int graphicsMode;
@@ -240,15 +241,11 @@ public class EmAppleII extends Em6502 implements Runnable {
return false;
}
- public boolean loadRom(InputStream is) {
+ public boolean loadRom(DataInputStream is) throws IOException {
byte[] rom = new byte[0x8000];
int offset = 0;
- try {
- is.read(rom, 0, 0x08000);
- } catch (IOException e) {
- return false;
- }
+ is.readFully(rom, 0, 0x08000);
if (isValidRom(rom, 0x0))
offset = 0x0;
@@ -1115,6 +1112,11 @@ public class EmAppleII extends Em6502 implements Runnable {
/**
* Emulator thread
+ *
+ * TODO: The speaker has been merged into this thread.
+ * This keeps it in sync but it still needs some work.
+ * Speeding up the CPU (or adding fast disk access as below)
+ * requires proper refactoring of the AppleSpeaker class.
*/
public void run() {
try {
@@ -1135,6 +1137,10 @@ public class EmAppleII extends Em6502 implements Runnable {
clocksNeeded -= executeInstructions(1 + (clocksNeeded >> 3));
}
+ // TODO: need something like the following for fast disk access
+ //if (slots[6] instanceof DiskII && !((DiskII)slots[6]).isMotorOn())
+
+ speaker.refreshSpeaker(); // NOTE: this blocks, syncing emulation and sound
refreshDelay = System.currentTimeMillis() - refreshStart;
refreshDelayCumulative += refreshDelay;
diff --git a/build.xml b/build.xml
index 523613c..820cec8 100644
--- a/build.xml
+++ b/build.xml
@@ -3,17 +3,16 @@