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
This commit is contained in:
sicklittlemonkey 2015-08-01 19:09:49 +12:00
parent 7f130be8ec
commit 2e59a5c31b
5 changed files with 122 additions and 51 deletions

View File

@ -2,17 +2,38 @@
/** /**
* AppleIIGo * AppleIIGo
* The Java Apple II Emulator * 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 * 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.applet.Applet;
import java.io.*; import java.awt.Graphics;
import java.net.*; import java.awt.event.ComponentEvent;
import java.util.zip.*; import java.awt.event.ComponentListener;
import java.awt.*; import java.awt.event.InputEvent;
import java.awt.event.*; import java.awt.event.KeyEvent;
import java.awt.Graphics2D; 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<p> * AppleIIGo class<p>
@ -20,10 +41,13 @@ import java.awt.Graphics2D;
*/ */
public class AppleIIGo extends Applet implements KeyListener, ComponentListener, public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
MouseListener, MouseMotionListener { MouseListener, MouseMotionListener {
final String version = "1.0.2";
final String versionString = "AppleIIGo Version " + version;
// Class instances // Class instances
private EmAppleII apple; private EmAppleII apple;
private AppleDisplay display; private AppleDisplay display;
private AppleSpeaker speaker;
private DiskII disk; private DiskII disk;
// Machine variables // Machine variables
@ -39,6 +63,10 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
// Disk variables // Disk variables
private String diskDriveResource[] = new String[2]; private String diskDriveResource[] = new String[2];
private boolean diskWritable; private boolean diskWritable;
private String[] disks0;
private String[] disks1;
private int disk0;
private int disk1;
/** /**
* Debug * Debug
@ -91,8 +119,8 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
display.setGlare(getAppletParameter("displayGlare", "false").equals("true")); display.setGlare(getAppletParameter("displayGlare", "false").equals("true"));
// Speaker // Speaker
speaker = new AppleSpeaker(apple); apple.speaker = new AppleSpeaker(apple);
speaker.setVolume(new Integer(getAppletParameter("speakerVolume", "3")).intValue()); apple.speaker.setVolume(new Integer(getAppletParameter("speakerVolume", "6")).intValue());
// Peripherals // Peripherals
disk = new DiskII(); disk = new DiskII();
@ -100,8 +128,12 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
// Initialize disk drives // Initialize disk drives
diskWritable = getAppletParameter("diskWritable", "false").equals("true"); diskWritable = getAppletParameter("diskWritable", "false").equals("true");
mountDisk(0, getAppletParameter("diskDrive1", "")); disks0 = getAppletParameter("diskDrive1", "").split("[|]");
mountDisk(1, getAppletParameter("diskDrive2", "")); disk0 = 0;
disks1 = getAppletParameter("diskDrive2", "").split("[|]");
disk1 = 0;
mountDisk(0, disks0[disk0]);
mountDisk(1, disks1[disk1]);
// Start CPU // Start CPU
if (!isCpuPaused) if (!isCpuPaused)
@ -112,6 +144,7 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
* Start applet * Start applet
*/ */
public void start() { public void start() {
debug("AppleIIGo Version " + version);
debug("start()"); debug("start()");
} }
@ -151,7 +184,7 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
isCpuPaused = true; isCpuPaused = true;
apple.setPaused(isCpuPaused); apple.setPaused(isCpuPaused);
display.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() { public void resume() {
debug("resume()"); debug("resume()");
isCpuPaused = false; isCpuPaused = false;
// speaker.setPaused(isCpuPaused); apple.speaker.setPaused(isCpuPaused);
display.setPaused(isCpuPaused); display.setPaused(isCpuPaused);
apple.setPaused(isCpuPaused); apple.setPaused(isCpuPaused);
} }
@ -176,19 +209,37 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
/** /**
* Open input stream * Open input stream
*/ */
private InputStream openInputStream(String resource) { private DataInputStream openInputStream(String resource) {
InputStream is = null; InputStream is = null;
try { try {
URL url = new URL(getCodeBase(), resource); URL url = new URL(getCodeBase(), resource);
is = url.openStream(); debug("resource: " + url.toString());
is = url.openStream();
if (resource.toLowerCase().endsWith(".gz")) if (resource.toLowerCase().endsWith(".gz"))
{
is = new GZIPInputStream(is); is = new GZIPInputStream(is);
}
else if (resource.toLowerCase().endsWith(".zip"))
{
is = new ZipInputStream(is);
((ZipInputStream)is).getNextEntry();
}
} catch (Exception e) { } 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; boolean success = false;
try { try {
InputStream is = openInputStream(resource); DataInputStream is = openInputStream(resource);
success = apple.loadRom(is); success = apple.loadRom(is);
is.close(); is.close();
} catch (Exception e) { } catch (Exception e) {
debug("Exeption: " + e.getLocalizedMessage());
} }
return success; return success;
@ -238,10 +290,11 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
diskDriveResource[drive] = resource; diskDriveResource[drive] = resource;
InputStream is = openInputStream(resource); DataInputStream is = openInputStream(resource);
success = disk.readDisk(drive, is, 254, false); success = disk.readDisk(drive, is, 254, false);
is.close(); is.close();
} catch (Exception e) { } catch (Exception e) {
debug("Exeption: " + e.getLocalizedMessage());
} }
return success; return success;
@ -310,13 +363,13 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
break; break;
case KeyEvent.VK_UP: case KeyEvent.VK_UP:
if (e.isControlDown()) if (e.isControlDown())
speaker.setVolume(speaker.getVolume() + 1); apple.speaker.setVolume(apple.speaker.getVolume() + 1);
else else
apple.setKeyLatch(11); apple.setKeyLatch(11);
break; break;
case KeyEvent.VK_DOWN: case KeyEvent.VK_DOWN:
if (e.isControlDown()) if (e.isControlDown())
speaker.setVolume(speaker.getVolume() - 1); apple.speaker.setVolume(apple.speaker.getVolume() - 1);
else else
apple.setKeyLatch(10); apple.setKeyLatch(10);
break; break;
@ -332,6 +385,23 @@ public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
else else
apple.reset(); apple.reset();
break; 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: case KeyEvent.VK_F5:
if (isCpuDebugEnabled) if (isCpuDebugEnabled)
display.setStatMode(!display.getStatMode()); display.setStatMode(!display.getStatMode());

View File

@ -10,7 +10,7 @@ import javax.sound.sampled.*;
public class AppleSpeaker implements Runnable { public class AppleSpeaker implements Runnable {
// Instances of other classes // Instances of other classes
private EmAppleII apple; private EmAppleII apple;
// Refresh // Refresh
private int refreshRate; private int refreshRate;
@ -29,7 +29,6 @@ public class AppleSpeaker implements Runnable {
private SourceDataLine line; private SourceDataLine line;
private int bufferSamples;
private int bufferSize; private int bufferSize;
private byte[] buffer; private byte[] buffer;
@ -43,11 +42,8 @@ public class AppleSpeaker implements Runnable {
// Thread stuff // Thread stuff
private boolean isPaused = true; private boolean isPaused = true;
private Thread thread; private Thread thread;
private String threadError = null;
public AppleSpeaker(EmAppleII apple) { public AppleSpeaker(EmAppleII apple) {
this.apple = apple; this.apple = apple;
setVolume(4); setVolume(4);
@ -66,14 +62,14 @@ public class AppleSpeaker implements Runnable {
refreshInterval = (int) (1000.0 / value); refreshInterval = (int) (1000.0 / value);
speakerClocksPerSample = (int) (apple.getCpuSpeed() * 1000.0f / SPEAKER_SAMPLERATE); speakerClocksPerSample = (int) (apple.getCpuSpeed() * 1000.0f / SPEAKER_SAMPLERATE);
} }
/** /**
* Get refresh rate * Get refresh rate
*/ */
private int getRefreshRate() { private int getRefreshRate() {
return refreshRate; return refreshRate;
} }
/** /**
* Set speaker volume * Set speaker volume
@ -94,7 +90,7 @@ public class AppleSpeaker implements Runnable {
*/ */
public int getVolume() { public int getVolume() {
return speakerVolume; return speakerVolume;
} }
/** /**
* Set pause state * Set pause state
@ -130,8 +126,6 @@ public class AppleSpeaker implements Runnable {
try { try {
line = (SourceDataLine) AudioSystem.getLine(info); line = (SourceDataLine) AudioSystem.getLine(info);
bufferSize = line.getBufferSize(); bufferSize = line.getBufferSize();
bufferSamples = bufferSize / SPEAKER_SAMPLESIZE;
buffer = new byte[bufferSize]; buffer = new byte[bufferSize];
line.open(audioFormat); line.open(audioFormat);
@ -139,15 +133,17 @@ public class AppleSpeaker implements Runnable {
} catch (LineUnavailableException e) { } catch (LineUnavailableException e) {
} }
thread = new Thread(this); // TODO: this thread is not created any more (nick)
thread.start(); //thread = new Thread(this);
//thread.start();
} }
} }
/** /**
* Speaker refresh thread * Speaker refresh thread
* TODO: this thread is not created any more (nick)
*/ */
public void run() { public void run() {
try { try {
while (!isPaused) { while (!isPaused) {
long refreshStart = System.currentTimeMillis(); long refreshStart = System.currentTimeMillis();
@ -169,7 +165,7 @@ public class AppleSpeaker implements Runnable {
/** /**
* Speaker refresh * Speaker refresh
*/ */
private void refreshSpeaker() { public void refreshSpeaker() {
clockEnd = apple.clock; clockEnd = apple.clock;
int bytes; int bytes;
@ -184,7 +180,7 @@ public class AppleSpeaker implements Runnable {
/** /**
* Fill buffer * Fill buffer
*/ */
private int fillBuffer() { private int fillBuffer() {
int value = speakerFlipStateToVolume[speakerFlipState]; int value = speakerFlipStateToVolume[speakerFlipState];
int clockEndSample = clockEnd - speakerClocksPerSample; int clockEndSample = clockEnd - speakerClocksPerSample;
int bufferPointer = 0; int bufferPointer = 0;
@ -234,7 +230,7 @@ public class AppleSpeaker implements Runnable {
isFlipsBufferEmpty = true; isFlipsBufferEmpty = true;
} else { } else {
clockNextFlip = apple.speakerFlips[speakerFlipsPointer]; clockNextFlip = apple.speakerFlips[speakerFlipsPointer];
speakerFlipsPointer = (speakerFlipsPointer + 1) & apple.SPEAKER_FLIPS_MASK; speakerFlipsPointer = (speakerFlipsPointer + 1) & EmAppleII.SPEAKER_FLIPS_MASK;
} }
} }
} }

View File

@ -265,7 +265,7 @@ public class DiskII extends Peripheral {
* @param is InputStream * @param is InputStream
* @param drive Disk II drive * @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]; byte[] track = new byte[RAW_TRACK_BYTES];
try { try {
@ -273,7 +273,7 @@ public class DiskII extends Peripheral {
disk[drive][trackNum] = new byte[RAW_TRACK_BYTES]; disk[drive][trackNum] = new byte[RAW_TRACK_BYTES];
if (is != null) { if (is != null) {
is.read(track, 0, DOS_TRACK_BYTES); is.readFully(track, 0, DOS_TRACK_BYTES);
trackToNibbles(track, disk[drive][trackNum], volume, trackNum); trackToNibbles(track, disk[drive][trackNum], volume, trackNum);
} }
} }

View File

@ -58,6 +58,7 @@ public class EmAppleII extends Em6502 implements Runnable {
// Peripherals // Peripherals
public Paddle paddle; public Paddle paddle;
public Peripheral[] slots; public Peripheral[] slots;
public AppleSpeaker speaker;
// Graphics (dirty buffer every 0x80 bytes) // Graphics (dirty buffer every 0x80 bytes)
public int graphicsMode; public int graphicsMode;
@ -240,15 +241,11 @@ public class EmAppleII extends Em6502 implements Runnable {
return false; return false;
} }
public boolean loadRom(InputStream is) { public boolean loadRom(DataInputStream is) throws IOException {
byte[] rom = new byte[0x8000]; byte[] rom = new byte[0x8000];
int offset = 0; int offset = 0;
try { is.readFully(rom, 0, 0x08000);
is.read(rom, 0, 0x08000);
} catch (IOException e) {
return false;
}
if (isValidRom(rom, 0x0)) if (isValidRom(rom, 0x0))
offset = 0x0; offset = 0x0;
@ -1115,6 +1112,11 @@ public class EmAppleII extends Em6502 implements Runnable {
/** /**
* Emulator thread * 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() { public void run() {
try { try {
@ -1135,6 +1137,10 @@ public class EmAppleII extends Em6502 implements Runnable {
clocksNeeded -= executeInstructions(1 + (clocksNeeded >> 3)); 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; refreshDelay = System.currentTimeMillis() - refreshStart;
refreshDelayCumulative += refreshDelay; refreshDelayCumulative += refreshDelay;

View File

@ -3,17 +3,16 @@
<delete dir="Build" /> <delete dir="Build" />
</target> </target>
<target name="compile"> <target name="compile" depends="clean">
<mkdir dir="Build/classes"/> <mkdir dir="Build/classes"/>
<javac srcdir="Source" destdir="Build/classes" optimize="yes"/> <javac srcdir="src" destdir="Build/classes" source="1.4" target="1.4"/>
</target> </target>
<target name="jar" depends="compile"> <target name="jar" depends="compile">
<mkdir dir="Build/jar"/> <mkdir dir="Build/jar"/>
<copy todir="Build/classes/Resources"> <copy todir="Build/classes/Resources">
<fileset dir="Resources"/> <fileset dir="src/Resources"/>
</copy> </copy>
<jar destfile="Build/jar/AppleIIGo.jar" basedir="Build/classes"/> <jar destfile="Build/jar/AppleIIGo.jar" basedir="Build/classes"/>
<copy file="Build/jar/AppleIIGo.jar" todir="/Users/mressl/Library/Widgets/AppleIIGo.wdgt"/>
</target> </target>
</project> </project>