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
* 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<p>
@ -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());

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -3,17 +3,16 @@
<delete dir="Build" />
</target>
<target name="compile">
<target name="compile" depends="clean">
<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 name="jar" depends="compile">
<mkdir dir="Build/jar"/>
<copy todir="Build/classes/Resources">
<fileset dir="Resources"/>
<fileset dir="src/Resources"/>
</copy>
<jar destfile="Build/jar/AppleIIGo.jar" basedir="Build/classes"/>
<copy file="Build/jar/AppleIIGo.jar" todir="/Users/mressl/Library/Widgets/AppleIIGo.wdgt"/>
</target>
</project>