mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Various bug fixes, tape support.
This commit is contained in:
parent
a6f454f845
commit
0a1127f541
179
apple2js.html
179
apple2js.html
@ -20,7 +20,7 @@
|
||||
<meta name="apple-mobile-web-app-title" content="Apple ][js">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="Apple ][js is an Apple ][ (or Apple II or Apple2) emulator written using only JavaScript and HTML5. It has color display, sound and disk support. It works best in the Chrome and Safari browsers." />
|
||||
<meta name="description" content="Apple ][js is an Apple ][ emulator written using only JavaScript and HTML5. It has color display, sound and disk support. It works best in the Chrome and Safari browsers." />
|
||||
<meta name="keywords" content="apple2,apple,ii,javascript,emulator,html5" />
|
||||
|
||||
<link rel="apple-touch-icon" href="img/webapp-iphone.png" />
|
||||
@ -32,7 +32,7 @@
|
||||
|
||||
<meta property="og:title" content="Apple ][js" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:description" content="Apple ][js is an Apple ][ (or Apple II or Apple2) emulator written using only JavaScript and HTML5." />
|
||||
<meta property="og:description" content="Apple ][js is an Apple 2 emulator written using only JavaScript and HTML5." />
|
||||
<meta property="og:url" content="http://www.scullinsteel.com/apple2/" />
|
||||
<meta property="og:image" content="http://www.scullinsteel.com/apple2/img/image.png" />
|
||||
<meta property="fb:admins" content="700585391" />
|
||||
@ -55,6 +55,7 @@
|
||||
<script type="text/javascript" src="js/parallel.js"></script>
|
||||
<script type="text/javascript" src="js/disk2.js"></script>
|
||||
<script type="text/javascript" src="js/ramfactor.js"></script>
|
||||
<script type="text/javascript" src="js/thunderclock.js"></script>
|
||||
<script type="text/javascript" src="js/cpu6502.js"></script>
|
||||
<script type="text/javascript" src="js/base64.js"></script>
|
||||
<script type="text/javascript" src="js/ui/audio.js"></script>
|
||||
@ -67,12 +68,15 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var kHz = 1023;
|
||||
|
||||
var focused = false;
|
||||
var startTime = Date.now();
|
||||
var lastCycles = 0;
|
||||
var frames = 0, lastFrames = 0;
|
||||
var paused = false;
|
||||
var sound = true;
|
||||
|
||||
var hashtag;
|
||||
|
||||
var disk_categories = {'Local Saves': []};
|
||||
@ -96,6 +100,9 @@ function DriveLights()
|
||||
}
|
||||
}
|
||||
|
||||
var DISK_TYPES = ['dsk','do','po','raw','nib','2mg'];
|
||||
var TAPE_TYPES = ['wav','aiff','aif','mp3'];
|
||||
|
||||
var _saveDrive = 1;
|
||||
var _loadDrive = 1;
|
||||
|
||||
@ -179,21 +186,84 @@ function doLoadLocal() {
|
||||
var files = $("#local_file").prop("files")
|
||||
if (files.length == 1) {
|
||||
var file = files[0];
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(event) {
|
||||
var parts = file.name.split(".");
|
||||
var name = parts[0], ext = parts[parts.length - 1].toLowerCase();
|
||||
if (disk2.setBinary(_saveDrive, name, ext, this.result)) {
|
||||
$("#load").dialog("close");
|
||||
}
|
||||
var parts = file.name.split(".");
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if ($.inArray(ext, DISK_TYPES) >= 0) {
|
||||
doLoadLocalDisk(file);
|
||||
} else if ($.inArray(ext, TAPE_TYPES) >= 0) {
|
||||
doLoadLocalTape(file);
|
||||
} else {
|
||||
alert('Unknown file type: ' + ext);
|
||||
$("#load").dialog("close");
|
||||
}
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
}
|
||||
|
||||
function openLoadLocal(drive) {
|
||||
_saveDrive = drive;
|
||||
$("#local").dialog("open");
|
||||
function doLoadLocalDisk(file) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(event) {
|
||||
var parts = file.name.split(".");
|
||||
var name = parts[0], ext = parts[parts.length - 1].toLowerCase();
|
||||
if (disk2.setBinary(_saveDrive, name, ext, this.result)) {
|
||||
$("#disklabel" + _saveDrive).text(name);
|
||||
$("#load").dialog("close");
|
||||
initGamepad();
|
||||
}
|
||||
}
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function doLoadLocalTape(file) {
|
||||
// Audio Buffer Source
|
||||
if (typeof webkitAudioContext != "undefined") {
|
||||
var context = new webkitAudioContext();
|
||||
} else {
|
||||
alert("Not supported by your browser");
|
||||
$("#load").dialog("close");
|
||||
return;
|
||||
}
|
||||
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(ev) {
|
||||
context.decodeAudioData(ev.target.result, function(buffer) {
|
||||
var buf = [];
|
||||
var data = buffer.getChannelData(0), datum = data[0];
|
||||
var old = (datum > 0.0), current;
|
||||
var last = 0, delta, ival;
|
||||
debug('Sample Count: ' + data.length);
|
||||
debug('Sample rate: ' + buffer.sampleRate);
|
||||
for (var idx = 1; idx < data.length; idx++) {
|
||||
datum = data[idx];
|
||||
if ((datum > 0.1) || (datum < -0.1)) {
|
||||
current = (datum > 0.0);
|
||||
if (current != old) {
|
||||
delta = idx - last;
|
||||
if (delta > 2000000) {
|
||||
delta = 2000000;
|
||||
}
|
||||
ival = delta / buffer.sampleRate * 1000;
|
||||
if (ival >= 0.550 && ival < 0.750) {
|
||||
ival = 0.650; // Header
|
||||
} else if (ival >= 0.175 && ival < 0.225) {
|
||||
ival = 0.200; // sync 1
|
||||
} else if (ival >= 0.225 && ival < 0.275) {
|
||||
ival = 0.250; // 0 / sync 2
|
||||
} else if (ival >= 0.450 && ival < 0.550) {
|
||||
ival = 0.500; // 1
|
||||
} else {
|
||||
// debug(idx + ' ' + buf.length + ' ' + ival);
|
||||
}
|
||||
buf.push(parseInt(ival * kHz));
|
||||
old = current;
|
||||
last = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
io.setTape(buf);
|
||||
$("#load").dialog("close");
|
||||
});
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function openManage() {
|
||||
@ -248,6 +318,7 @@ var keyboard = new KeyBoard(io);
|
||||
var parallel = new Parallel(io, new Printer(), 1);
|
||||
var disk2 = new DiskII(io, drivelights, 6);
|
||||
var slinky = new RAMFactor(mmu, io, 2, 1024 * 1024);
|
||||
var clock = new Thunderclock(mmu, io, 7);
|
||||
var lc = new LanguageCard(io, rom);
|
||||
|
||||
cpu.addPageHandler(ram1);
|
||||
@ -263,6 +334,7 @@ cpu.addPageHandler(io);
|
||||
cpu.addPageHandler(parallel);
|
||||
cpu.addPageHandler(slinky);
|
||||
cpu.addPageHandler(disk2);
|
||||
cpu.addPageHandler(clock);
|
||||
|
||||
var showFPS = false;
|
||||
|
||||
@ -275,11 +347,11 @@ function updateKHz() {
|
||||
if (showFPS) {
|
||||
delta = frames - lastFrames;
|
||||
var fps = parseInt(delta/(ms/1000), 10);
|
||||
$("#khz").text( fps + "fps");
|
||||
$("#khz").html( fps + "fps");
|
||||
} else {
|
||||
delta = cycles - lastCycles;
|
||||
khz = parseInt(delta/ms);
|
||||
$("#khz").text( khz + "KHz");
|
||||
$("#khz").html( khz + "KHz");
|
||||
}
|
||||
|
||||
startTime = now;
|
||||
@ -318,11 +390,13 @@ function step()
|
||||
}
|
||||
|
||||
var running = false;
|
||||
var throttling = true;
|
||||
var accelerated = false;
|
||||
|
||||
function toggleSpeed()
|
||||
function updateSpeed()
|
||||
{
|
||||
throttling = $("#speed_toggle").prop("checked");
|
||||
accelerated = $("#accelerator_toggle").prop("checked");
|
||||
kHz = accelerated ? 4092 : 1023;
|
||||
io.updateHz(kHz * 1000);
|
||||
if (runTimer) {
|
||||
run();
|
||||
}
|
||||
@ -343,23 +417,18 @@ function run(pc) {
|
||||
cpu.setPC(pc);
|
||||
}
|
||||
|
||||
var ival = 30, step = 1023 * ival, stepMax = step;
|
||||
|
||||
if (!throttling) {
|
||||
ival = 1;
|
||||
}
|
||||
|
||||
var ival = 30;
|
||||
var now, last = Date.now(), lastSample = last;
|
||||
var runFn = function() {
|
||||
now = Date.now();
|
||||
frames++;
|
||||
if (_requestAnimationFrame) {
|
||||
step = (now - last) * 1023;
|
||||
last = now;
|
||||
if (step > stepMax) {
|
||||
step = stepMax;
|
||||
}
|
||||
|
||||
var step = (now - last) * kHz, stepMax = kHz * ival;
|
||||
last = now;
|
||||
if (step > stepMax) {
|
||||
step = stepMax;
|
||||
}
|
||||
|
||||
if (document.location.hash != hashtag) {
|
||||
hashtag = document.location.hash;
|
||||
filename = hup()
|
||||
@ -463,9 +532,10 @@ function loadDisk(data) {
|
||||
function loadJSON(data) {
|
||||
if (data.type == "binary") {
|
||||
loadBinary(data);
|
||||
} else if ($.inArray(data.type, ["dsk","po","raw","nib"]) >= 0) {
|
||||
} else if ($.inArray(data.type, DISK_TYPES) >= 0) {
|
||||
loadDisk(data);
|
||||
}
|
||||
initGamepad(data.gamepad);
|
||||
$("#loading").dialog("close");
|
||||
loading = false;
|
||||
}
|
||||
@ -476,7 +546,7 @@ function loadJSON(data) {
|
||||
|
||||
function updateLocalStorage() {
|
||||
var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}");
|
||||
var names = [], name, cat, idx;
|
||||
var names = [], name, cat;
|
||||
|
||||
for (name in diskIndex) {
|
||||
if (diskIndex.hasOwnProperty(name)) {
|
||||
@ -485,19 +555,21 @@ function updateLocalStorage() {
|
||||
}
|
||||
|
||||
cat = disk_categories['Local Saves'] = [];
|
||||
for (name in diskIndex) {
|
||||
$("#manage").empty();
|
||||
|
||||
names.forEach(function(name) {
|
||||
cat.push({'category': 'Local Saves',
|
||||
'name': name,
|
||||
'filename': 'local:' + name});
|
||||
}
|
||||
$("#manage").append("<span class='local_save'>" +
|
||||
name +
|
||||
" <a href='#' onclick='doDelete(\"" +
|
||||
name +
|
||||
"\")'>Delete</a><br /></span>");
|
||||
});
|
||||
cat.push({'category': 'Local Saves',
|
||||
'name': 'Manage Saves...',
|
||||
'filename': 'local:__manage'});
|
||||
|
||||
$("#manage").empty();
|
||||
for (idx in names) {
|
||||
$("#manage").before("<span class='local_save'>" + names[idx] + " <a href='#' onclick='doDelete(\"" + names[idx] + "\")'>Delete</a><br /></span>");
|
||||
}
|
||||
}
|
||||
|
||||
function saveLocalStorage(drive, name) {
|
||||
@ -522,6 +594,7 @@ function deleteLocalStorage(name) {
|
||||
window.alert("Deleted");
|
||||
}
|
||||
window.localStorage.diskIndex = JSON.stringify(diskIndex);
|
||||
updateLocalStorage();
|
||||
}
|
||||
|
||||
function loadLocalStorage(drive, name) {
|
||||
@ -562,7 +635,7 @@ function _keydown(evt) {
|
||||
}
|
||||
} else if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(true);
|
||||
io.buttonDown(2, true);
|
||||
io.buttonDown(2);
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(true);
|
||||
} else if (!focused && (!evt.metaKey || evt.ctrlKey)) {
|
||||
@ -570,7 +643,6 @@ function _keydown(evt) {
|
||||
|
||||
var key = keyboard.mapKeyEvent(evt);
|
||||
if (key != 0xff) {
|
||||
if (_key != 0xff) io.keyUp();
|
||||
io.keyDown(key);
|
||||
_key = key;
|
||||
}
|
||||
@ -578,13 +650,14 @@ function _keydown(evt) {
|
||||
}
|
||||
|
||||
function _keyup(evt) {
|
||||
_key = 0xff;
|
||||
|
||||
if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(false);
|
||||
io.buttonDown(2, false);
|
||||
io.buttonUp(2);
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(false);
|
||||
} else {
|
||||
_key = 0xff;
|
||||
if (!focused) {
|
||||
io.keyUp();
|
||||
}
|
||||
@ -673,11 +746,12 @@ $(function() {
|
||||
io.buttonUp(evt.which == 1 ? 0 : 1);
|
||||
}
|
||||
})
|
||||
.mousemove(_mousemove)
|
||||
.bind("contextmenu", function(e) { e.preventDefault(); });
|
||||
|
||||
$("input,textarea").focus(function() { focused = true; })
|
||||
.blur(function() { focused = false; });
|
||||
$('body').mousemove(_mousemove);
|
||||
|
||||
$("input,textarea").focus(function() { focused = true; });
|
||||
$("input,textarea").blur(function() { focused = false; });
|
||||
|
||||
keyboard.create($("#keyboard"));
|
||||
|
||||
@ -697,6 +771,7 @@ $(function() {
|
||||
setInterval(updateKHz, 1000);
|
||||
updateSound();
|
||||
updateScreen();
|
||||
updateSpeed();
|
||||
|
||||
var cancel = function() { $(this).dialog("close"); };
|
||||
$("#loading").dialog({ autoOpen: false, modal: true });
|
||||
@ -743,6 +818,7 @@ $(function() {
|
||||
$("<option/>").text("Local Saves").appendTo("#category_select");
|
||||
|
||||
updateLocalStorage();
|
||||
initGamepad();
|
||||
|
||||
// Check for disks in hashtag
|
||||
|
||||
@ -772,9 +848,7 @@ $(function() {
|
||||
<table id="display">
|
||||
<tr>
|
||||
<td style="vertical-align: top">
|
||||
<div role="textbox" class="overscan"
|
||||
onKeyDown="_keydown(event);"
|
||||
onKeyUp="_keyup(event);">
|
||||
<div class="overscan">
|
||||
<canvas id="screen" width="560" height="384"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
@ -828,10 +902,9 @@ $(function() {
|
||||
<h3>CPU</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" checked="checked" id="speed_toggle"
|
||||
onclick="toggleSpeed()"/>
|
||||
<label for="speed_toggle">
|
||||
Regulate Speed
|
||||
<input type="checkbox" id="accelerator_toggle" onclick="updateSpeed()"/>
|
||||
<label for="accelerator_toggle">
|
||||
Accelerated CPU
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
|
158
apple2jse.html
158
apple2jse.html
@ -77,6 +77,7 @@ var lastCycles = 0;
|
||||
var frames = 0, lastFrames = 0;
|
||||
var paused = false;
|
||||
var sound = true;
|
||||
|
||||
var hashtag;
|
||||
|
||||
var disk_categories = {'Local Saves': []};
|
||||
@ -100,6 +101,9 @@ function DriveLights()
|
||||
}
|
||||
}
|
||||
|
||||
var DISK_TYPES = ['dsk','do','po','raw','nib','2mg'];
|
||||
var TAPE_TYPES = ['wav','aiff','aif','mp3'];
|
||||
|
||||
var _saveDrive = 1;
|
||||
var _loadDrive = 1;
|
||||
|
||||
@ -183,21 +187,84 @@ function doLoadLocal() {
|
||||
var files = $("#local_file").prop("files")
|
||||
if (files.length == 1) {
|
||||
var file = files[0];
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(event) {
|
||||
var parts = file.name.split(".");
|
||||
var name = parts[0], ext = parts[parts.length - 1].toLowerCase();
|
||||
if (disk2.setBinary(_saveDrive, name, ext, this.result)) {
|
||||
$("#load").dialog("close");
|
||||
}
|
||||
var parts = file.name.split(".");
|
||||
var ext = parts[parts.length - 1].toLowerCase();
|
||||
if ($.inArray(ext, DISK_TYPES) >= 0) {
|
||||
doLoadLocalDisk(file);
|
||||
} else if ($.inArray(ext, TAPE_TYPES) >= 0) {
|
||||
doLoadLocalTape(file);
|
||||
} else {
|
||||
alert('Unknown file type: ' + ext);
|
||||
$("#load").dialog("close");
|
||||
}
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
}
|
||||
|
||||
function openLoadLocal(drive) {
|
||||
_saveDrive = drive;
|
||||
$("#local").dialog("open");
|
||||
function doLoadLocalDisk(file) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(event) {
|
||||
var parts = file.name.split(".");
|
||||
var name = parts[0], ext = parts[parts.length - 1].toLowerCase();
|
||||
if (disk2.setBinary(_saveDrive, name, ext, this.result)) {
|
||||
$("#disklabel" + _saveDrive).text(name);
|
||||
$("#load").dialog("close");
|
||||
initGamepad();
|
||||
}
|
||||
}
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function doLoadLocalTape(file) {
|
||||
// Audio Buffer Source
|
||||
if (typeof webkitAudioContext != "undefined") {
|
||||
var context = new webkitAudioContext();
|
||||
} else {
|
||||
alert("Not supported by your browser");
|
||||
$("#load").dialog("close");
|
||||
return;
|
||||
}
|
||||
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(ev) {
|
||||
context.decodeAudioData(ev.target.result, function(buffer) {
|
||||
var buf = [];
|
||||
var data = buffer.getChannelData(0), datum = data[0];
|
||||
var old = (datum > 0.0), current;
|
||||
var last = 0, delta, ival;
|
||||
debug('Sample Count: ' + data.length);
|
||||
debug('Sample rate: ' + buffer.sampleRate);
|
||||
for (var idx = 1; idx < data.length; idx++) {
|
||||
datum = data[idx];
|
||||
if ((datum > 0.1) || (datum < -0.1)) {
|
||||
current = (datum > 0.0);
|
||||
if (current != old) {
|
||||
delta = idx - last;
|
||||
if (delta > 2000000) {
|
||||
delta = 2000000;
|
||||
}
|
||||
ival = delta / buffer.sampleRate * 1000;
|
||||
if (ival >= 0.550 && ival < 0.750) {
|
||||
ival = 0.650; // Header
|
||||
} else if (ival >= 0.175 && ival < 0.225) {
|
||||
ival = 0.200; // sync 1
|
||||
} else if (ival >= 0.225 && ival < 0.275) {
|
||||
ival = 0.250; // 0 / sync 2
|
||||
} else if (ival >= 0.450 && ival < 0.550) {
|
||||
ival = 0.500; // 1
|
||||
} else {
|
||||
// debug(idx + ' ' + buf.length + ' ' + ival);
|
||||
}
|
||||
buf.push(parseInt(ival * kHz));
|
||||
old = current;
|
||||
last = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
io.setTape(buf);
|
||||
$("#load").dialog("close");
|
||||
});
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function openManage() {
|
||||
@ -273,11 +340,11 @@ function updateKHz() {
|
||||
if (showFPS) {
|
||||
delta = frames - lastFrames;
|
||||
var fps = parseInt(delta/(ms/1000), 10);
|
||||
$("#khz").text( fps + "fps");
|
||||
$("#khz").html( fps + "fps");
|
||||
} else {
|
||||
delta = cycles - lastCycles;
|
||||
khz = parseInt(delta/ms);
|
||||
$("#khz").text( khz + "KHz");
|
||||
$("#khz").html( khz + "KHz");
|
||||
}
|
||||
|
||||
startTime = now;
|
||||
@ -316,11 +383,13 @@ function step()
|
||||
}
|
||||
|
||||
var running = false;
|
||||
var throttling = true;
|
||||
var accelerated = false;
|
||||
|
||||
function toggleSpeed()
|
||||
function updateSpeed()
|
||||
{
|
||||
throttling = $("#speed_toggle").prop("checked");
|
||||
accelerated = $("#accelerator_toggle").prop("checked");
|
||||
kHz = accelerated ? 4092 : 1023;
|
||||
io.updateHz(kHz * 1000);
|
||||
if (runTimer) {
|
||||
run();
|
||||
}
|
||||
@ -341,23 +410,18 @@ function run(pc) {
|
||||
cpu.setPC(pc);
|
||||
}
|
||||
|
||||
var ival = 30, step = 1023 * ival, stepMax = step;
|
||||
|
||||
if (!throttling) {
|
||||
ival = 1;
|
||||
}
|
||||
|
||||
var ival = 30;
|
||||
var now, last = Date.now(), lastSample = last;
|
||||
var runFn = function() {
|
||||
now = Date.now();
|
||||
frames++;
|
||||
if (_requestAnimationFrame) {
|
||||
step = (now - last) * 1023;
|
||||
last = now;
|
||||
if (step > stepMax) {
|
||||
step = stepMax;
|
||||
}
|
||||
|
||||
var step = (now - last) * kHz, stepMax = kHz * ival;
|
||||
last = now;
|
||||
if (step > stepMax) {
|
||||
step = stepMax;
|
||||
}
|
||||
|
||||
if (document.location.hash != hashtag) {
|
||||
hashtag = document.location.hash;
|
||||
filename = hup()
|
||||
@ -462,9 +526,10 @@ function loadDisk(data) {
|
||||
function loadJSON(data) {
|
||||
if (data.type == "binary") {
|
||||
loadBinary(data);
|
||||
} else if ($.inArray(data.type, ["dsk","po","raw","nib"]) >= 0) {
|
||||
} else if ($.inArray(data.type, DISK_TYPES) >= 0) {
|
||||
loadDisk(data);
|
||||
}
|
||||
initGamepad(data.gamepad);
|
||||
$("#loading").dialog("close");
|
||||
loading = false;
|
||||
}
|
||||
@ -475,7 +540,7 @@ function loadJSON(data) {
|
||||
|
||||
function updateLocalStorage() {
|
||||
var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}");
|
||||
var names = [], name, cat, idx;
|
||||
var names = [], name, cat;
|
||||
|
||||
for (name in diskIndex) {
|
||||
if (diskIndex.hasOwnProperty(name)) {
|
||||
@ -484,19 +549,21 @@ function updateLocalStorage() {
|
||||
}
|
||||
|
||||
cat = disk_categories['Local Saves'] = [];
|
||||
for (name in diskIndex) {
|
||||
$("#manage").empty();
|
||||
|
||||
names.forEach(function(name) {
|
||||
cat.push({'category': 'Local Saves',
|
||||
'name': name,
|
||||
'filename': 'local:' + name});
|
||||
}
|
||||
$("#manage").append("<span class='local_save'>" +
|
||||
name +
|
||||
" <a href='#' onclick='doDelete(\"" +
|
||||
name +
|
||||
"\")'>Delete</a><br /></span>");
|
||||
});
|
||||
cat.push({'category': 'Local Saves',
|
||||
'name': 'Manage Saves...',
|
||||
'filename': 'local:__manage'});
|
||||
|
||||
$("#manage").empty();
|
||||
for (idx in names) {
|
||||
$("#manage").before("<span class='local_save'>" + names[idx] + " <a href='#' onclick='doDelete(\"" + names[idx] + "\")'>Delete</a><br /></span>");
|
||||
}
|
||||
}
|
||||
|
||||
function saveLocalStorage(drive, name) {
|
||||
@ -521,6 +588,7 @@ function deleteLocalStorage(name) {
|
||||
window.alert("Deleted");
|
||||
}
|
||||
window.localStorage.diskIndex = JSON.stringify(diskIndex);
|
||||
updateLocalStorage();
|
||||
}
|
||||
|
||||
function loadLocalStorage(drive, name) {
|
||||
@ -561,7 +629,7 @@ function _keydown(evt) {
|
||||
}
|
||||
} else if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(true);
|
||||
io.buttonDown(2, true);
|
||||
io.buttonDown(2);
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(true);
|
||||
} else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
|
||||
@ -573,7 +641,6 @@ function _keydown(evt) {
|
||||
|
||||
var key = keyboard.mapKeyEvent(evt);
|
||||
if (key != 0xff) {
|
||||
if (_key != 0xff) io.keyUp();
|
||||
io.keyDown(key);
|
||||
_key = key;
|
||||
}
|
||||
@ -581,9 +648,11 @@ function _keydown(evt) {
|
||||
}
|
||||
|
||||
function _keyup(evt) {
|
||||
_key = 0xff;
|
||||
|
||||
if (evt.keyCode == 16) { // Shift
|
||||
keyboard.shiftKey(false);
|
||||
io.buttonDown(2, false);
|
||||
io.buttonUp(2);
|
||||
} else if (evt.keyCode == 17) { // Control
|
||||
keyboard.controlKey(false);
|
||||
} else if (evt.keyCode == 91 || evt.keyCode == 93) { // Command
|
||||
@ -591,7 +660,6 @@ function _keyup(evt) {
|
||||
} else if (evt.keyCode == 18) { // Alt
|
||||
keyboard.optionKey(false);
|
||||
} else {
|
||||
_key = 0xff;
|
||||
if (!focused) {
|
||||
io.keyUp();
|
||||
}
|
||||
@ -708,6 +776,7 @@ $(function() {
|
||||
setInterval(updateKHz, 1000);
|
||||
updateSound();
|
||||
updateScreen();
|
||||
updateSpeed();
|
||||
|
||||
var cancel = function() { $(this).dialog("close"); };
|
||||
$("#loading").dialog({ autoOpen: false, modal: true });
|
||||
@ -840,10 +909,9 @@ $(function() {
|
||||
<h3>CPU</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input type="checkbox" checked="checked" id="speed_toggle"
|
||||
onclick="toggleSpeed()"/>
|
||||
<label for="speed_toggle">
|
||||
Regulate Speed
|
||||
<input type="checkbox" id="accelerator_toggle" onclick="updateSpeed()"/>
|
||||
<label for="accelerator_toggle">
|
||||
Accelerated CPU
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* -*- mode: JavaScript; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
/*global bytify */
|
||||
/*exported charset */
|
||||
|
||||
var charset = [
|
||||
@ -260,3 +261,5 @@ var charset = [
|
||||
0x80,0x90,0x88,0x84,0x82,0x84,0x88,0x90,
|
||||
0x80,0x9c,0xa2,0x84,0x88,0x88,0x80,0x88
|
||||
];
|
||||
|
||||
charset = bytify(charset);
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* -*- mode: JavaScript; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
/*global bytify */
|
||||
/*exported charset */
|
||||
|
||||
var charset = [
|
||||
@ -516,3 +517,5 @@ var charset = [
|
||||
0x81,0x81,0x11,0x44,0x81,0x81,0x00,0x00,
|
||||
0x80,0x80,0x00,0x00,0x80,0x80,0x00,0x00
|
||||
];
|
||||
|
||||
charset = bytify(charset);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* -*- mode: JavaScript; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* Copyright 2010-2013 Will Scullin <scullin@scullinsteel.com>
|
||||
/* Copyright 2010-2014 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
@ -15,7 +15,12 @@
|
||||
|
||||
function Apple2IO(cpu, callbacks)
|
||||
{
|
||||
var SAMPLE_RATE = 64;
|
||||
"use strict";
|
||||
|
||||
var _hz = 1023000;
|
||||
var _rate = 16000;
|
||||
|
||||
var _cycles_per_sample = _hz / _rate;
|
||||
|
||||
var _buffer = [];
|
||||
var _key = 0;
|
||||
@ -24,20 +29,25 @@ function Apple2IO(cpu, callbacks)
|
||||
var _paddle = [0.0, 0.0, 0.0, 0,0];
|
||||
var _phase = -1;
|
||||
var _sample = [];
|
||||
var _oldSample = [];
|
||||
var _sampleTime = 0;
|
||||
|
||||
var _high = "%A0";
|
||||
var _mid = "%80";
|
||||
var _low = "%60";
|
||||
|
||||
var _trigger = 0;
|
||||
|
||||
var _tape = [];
|
||||
var _tapeOffset = 0;
|
||||
var _tapeNext = 0;
|
||||
var _tapeFlip = false;
|
||||
|
||||
var LOC = {
|
||||
KEYBOARD: 0x00, // keyboard data (latched) (Read),
|
||||
CLR80VID: 0x0C, // clear 80 column mode
|
||||
SET80VID: 0x0D, // set 80 column mode
|
||||
CLRALTCH: 0x0E, // clear 80 column mode
|
||||
SETALTCH: 0x0F, // set 80 column mode
|
||||
CLRALTCH: 0x0E, // clear mousetext
|
||||
SETALTCH: 0x0F, // set mousetext
|
||||
STROBE: 0x10, // clear bit 7 of keyboard data ($C000)
|
||||
|
||||
RDTEXT: 0x1A, // using text mode
|
||||
@ -196,7 +206,7 @@ function Apple2IO(cpu, callbacks)
|
||||
case LOC.SPEAKER:
|
||||
if (_sampleTime) {
|
||||
var phase = _phase > 0 ? _high : _low;
|
||||
for (; _sampleTime < now; _sampleTime += SAMPLE_RATE) {
|
||||
for (; _sampleTime < now; _sampleTime += _cycles_per_sample) {
|
||||
_sample.push(phase);
|
||||
}
|
||||
_phase = -_phase;
|
||||
@ -240,6 +250,38 @@ function Apple2IO(cpu, callbacks)
|
||||
case LOC.PDLTRIG:
|
||||
_trigger = cpu.cycles();
|
||||
break;
|
||||
case LOC.TAPEIN:
|
||||
var flipped = false;
|
||||
if (_tapeOffset == -1) {
|
||||
_tapeOffset = 0;
|
||||
_tapeNext = now;
|
||||
}
|
||||
if (_tapeOffset < _tape.length) {
|
||||
while (now >= _tapeNext) {
|
||||
if ((_tapeOffset % 1000) === 0) {
|
||||
debug("Read " + (_tapeOffset / 1000));
|
||||
}
|
||||
_tapeFlip = !_tapeFlip;
|
||||
flipped = true;
|
||||
_tapeNext += _tape[_tapeOffset++];
|
||||
}
|
||||
result = _tapeFlip ? 0x80 : 0x00;
|
||||
}
|
||||
/*
|
||||
if (flipped) {
|
||||
debug("now=" + now + " next=" + _tapeNext + " (" + (_tapeNext - now) + ")");
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
var progress =
|
||||
Math.round(_tapeOffset / _tapeBuffer.length * 100) / 100;
|
||||
|
||||
if (_progress != progress) {
|
||||
_progress = progress;
|
||||
cb.progress(_progress);
|
||||
}
|
||||
*/
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -300,35 +342,42 @@ function Apple2IO(cpu, callbacks)
|
||||
paddle: function apple2io_paddle(p, v) {
|
||||
_paddle[p] = v;
|
||||
},
|
||||
getSample: function apple2io_getSample(stop) {
|
||||
getSample: function apple2io_getSample() {
|
||||
var result = _sample;
|
||||
var now = cpu.cycles();
|
||||
|
||||
//if (_sampleTime) {
|
||||
// var phase = _phase > 0 ? _high : _low;
|
||||
// for (; _sampleTime < now; _sampleTime += SAMPLE_RATE)
|
||||
// _sample.push(phase);
|
||||
//}
|
||||
var phase = _mid;
|
||||
if (_sample.length) {
|
||||
phase = _phase > 0 ? _high : _low;
|
||||
}
|
||||
for (; _sampleTime < now; _sampleTime += _cycles_per_sample) {
|
||||
_sample.push(phase);
|
||||
}
|
||||
|
||||
_sample = _oldSample;
|
||||
_sample.length = 0;
|
||||
_sampleTime = stop ? 0 : now;
|
||||
|
||||
_oldSample = result;
|
||||
_sample = [];
|
||||
|
||||
return result;
|
||||
},
|
||||
floatAudio: function apple2io_floatAudio(rate) {
|
||||
_rate = rate;
|
||||
_low = -0.5;
|
||||
_mid = 0.0;
|
||||
_high = 0.5;
|
||||
|
||||
SAMPLE_RATE = 1023000.0 / rate;
|
||||
_cycles_per_sample = _hz / _rate;
|
||||
},
|
||||
byteAudio: function apple2io_floatAudio(rate) {
|
||||
byteAudio: function apple2io_byteAudio(rate) {
|
||||
_rate = rate;
|
||||
_low = 0xa0;
|
||||
_mid = 0x80;
|
||||
_high = 0x60;
|
||||
|
||||
SAMPLE_RATE = 1023000.0 / rate;
|
||||
_cycles_per_sample = _hz / _rate;
|
||||
},
|
||||
updateHz: function apple2io_updateHz(hz) {
|
||||
_hz = hz;
|
||||
|
||||
_cycles_per_sample = _hz / _rate;
|
||||
},
|
||||
setKeyBuffer: function apple2io_setKeyBuffer(buffer) {
|
||||
_buffer = buffer.split("");
|
||||
@ -336,6 +385,12 @@ function Apple2IO(cpu, callbacks)
|
||||
_keyDown = true;
|
||||
_key = _buffer.shift().charCodeAt(0) | 0x80;
|
||||
}
|
||||
},
|
||||
|
||||
setTape: function apple2io_setTape(tape) {
|
||||
debug('Tape length: ' + tape.length);
|
||||
_tape = tape;
|
||||
_tapeOffset = -1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -588,6 +588,8 @@ function HiresPage(page)
|
||||
(bits & 0x01e)) ||
|
||||
(bits & 0x10)) {
|
||||
_drawHalfPixel(data, off, dcolor);
|
||||
} else if ((bits & 0x28) == 0x28) {
|
||||
_drawHalfPixel(data, off, dcolor);
|
||||
} else {
|
||||
_drawHalfPixel(data, off, blackCol);
|
||||
}
|
||||
|
@ -771,7 +771,7 @@ function DiskII(io, callbacks, slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
tracks[t] = track;
|
||||
tracks[t] = bytify(track);
|
||||
}
|
||||
_cur.volume = v;
|
||||
_cur.format = fmt;
|
||||
|
@ -539,7 +539,7 @@ function MMU(cpu, lores1, lores2, hires1, hires2, io, rom)
|
||||
break;
|
||||
case LOC.SLOTC3ROM: // 0xC017
|
||||
_debug("Slot C3 ROM " + _slot3rom);
|
||||
result = _slot3rom ? 0x80 : 0x00;
|
||||
result = _slot3rom ? 0x00 : 0x80;
|
||||
break;
|
||||
case LOC._80STORE: // 0xC018
|
||||
result = _80store ? 0x80 : 0x00;
|
||||
|
@ -10,7 +10,7 @@
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
/*globals allocMem:false, base64_encode, base64_decode, each: false */
|
||||
/*globals allocMem:false, bytify, base64_encode, base64_decode, each: false */
|
||||
/*exported RAMFactor*/
|
||||
|
||||
function RAMFactor(mmu, io, slot, size) {
|
||||
@ -1071,6 +1071,7 @@ function RAMFactor(mmu, io, slot, size) {
|
||||
for (var off = 0; off < size; off++) {
|
||||
mem[off] = 0;
|
||||
}
|
||||
rom = bytify(rom);
|
||||
}
|
||||
|
||||
function _sethi(val) {
|
||||
|
@ -124,13 +124,82 @@ var SYMBOLS = {
|
||||
0x03F5: "AMPERSAND.VECTOR",
|
||||
*/
|
||||
0xC000: "KEYBOARD",
|
||||
0xC050: "SW.TXTCLR",
|
||||
0xC052: "SW.MIXCLR",
|
||||
0xC053: "SW.MIXSET",
|
||||
0xC054: "SW.LOWSCR",
|
||||
0xC055: "SW.HISCR",
|
||||
0xC056: "SW.LORES",
|
||||
0xC057: "SW.HIRES",
|
||||
0xC001: "80STOREON",
|
||||
0xC002: "RAMRDOFF",
|
||||
0xC003: "RAMRDON",
|
||||
0xC004: "RAMWROFF",
|
||||
0xC005: "RAMWRON",
|
||||
0xC006: "INTCXOFF",
|
||||
0xC007: "INTCXON",
|
||||
0xC008: "ALTZPOFF",
|
||||
0xC009: "ALTZPON",
|
||||
0xC00A: "SLOT3OFF",
|
||||
0xC00B: "SLOT3ON",
|
||||
0xC00C: "CLR80VID",
|
||||
0xC00D: "SET80VID",
|
||||
0xC00E: "CLRALTCH",
|
||||
0xC00F: "SETALTCH",
|
||||
0xC010: "STROBE",
|
||||
0xC011: "BSRBANK2",
|
||||
0xC012: "BSRREAD",
|
||||
0xC013: "RAMRD",
|
||||
0xC014: "RAMWRT",
|
||||
0xC015: "INTCXROM",
|
||||
0xC016: "ALTZP",
|
||||
0xC017: "SLOT3ROM",
|
||||
0xC018: "80STRORE",
|
||||
0xC019: "VERTBLANK",
|
||||
0xC01A: "RDTEXT",
|
||||
0xC01B: "RDMIXED",
|
||||
0xC01C: "RDPAGE2",
|
||||
0xC01D: "RDHIRES",
|
||||
0xC01E: "RDALTCH",
|
||||
0xC01F: "RD80VID",
|
||||
0xC020: "TAPEOUT",
|
||||
0xC030: "SPEAKER",
|
||||
0xC050: "CLRTEXT",
|
||||
0xC051: "SETTEXT",
|
||||
0xC052: "CLRMIXED",
|
||||
0xC053: "SETMIXED",
|
||||
0xC054: "PAGE1",
|
||||
0xC055: "PAGE2",
|
||||
0xC056: "CLRHIRS",
|
||||
0xC057: "SETHIRES",
|
||||
0xC058: "CLRAN0",
|
||||
0xC059: "SETAN0",
|
||||
0xC05A: "CLRAN1",
|
||||
0xC05B: "SETAN1",
|
||||
0xC05C: "CLRAN2",
|
||||
0xC05D: "SETAN2",
|
||||
0xC05E: "CLRAN3",
|
||||
0xC05F: "SETAN3",
|
||||
0xC060: "TAPEIN",
|
||||
0xC061: "PB0",
|
||||
0xC062: "PB1",
|
||||
0xC063: "PB2",
|
||||
0xC064: "PADDLE0",
|
||||
0xC065: "PADDLE1",
|
||||
0xC066: "PADDLE2",
|
||||
0xC067: "PADDLE3",
|
||||
0xC070: "PDLTRIG",
|
||||
0xC07E: "SETIOUDIS",
|
||||
0xC07F: "CLRIOUDIS",
|
||||
0xC080: "RDBSR2",
|
||||
0xC081: "WRBSR2",
|
||||
0xC082: "OFFBSR2",
|
||||
0xC083: "RWBSR2",
|
||||
0xC084: "RDBSR2",
|
||||
0xC085: "WRBSR2",
|
||||
0xC086: "OFFBSR2",
|
||||
0xC087: "RWBSR2",
|
||||
0xC088: "RDBSR1",
|
||||
0xC089: "WRBSR1",
|
||||
0xC08A: "OFFBSR1",
|
||||
0xC08B: "RWBSR1",
|
||||
0xC08C: "RDBSR1",
|
||||
0xC08D: "WRBSR1",
|
||||
0xC08E: "OFFBSR1",
|
||||
0xC08F: "RWBSR1",
|
||||
0xD000: "TOKEN.ADDRESS.TABLE",
|
||||
0xD080: "UNFNC",
|
||||
0xD0B2: "MATHTBL",
|
||||
|
@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
/*exported Thunderclock */
|
||||
/*global each */
|
||||
|
||||
function Thunderclock(mmu, io, slot)
|
||||
{
|
||||
@ -274,7 +275,8 @@ function Thunderclock(mmu, io, slot)
|
||||
];
|
||||
|
||||
var LOC = {
|
||||
CONTROL: 0x80
|
||||
CONTROL: 0x80,
|
||||
AUX: 0x88
|
||||
};
|
||||
|
||||
var FLAGS = {
|
||||
@ -284,7 +286,9 @@ function Thunderclock(mmu, io, slot)
|
||||
};
|
||||
|
||||
function _init() {
|
||||
LOC.CONTROL += slot * 0x10;
|
||||
each(LOC, function(key) {
|
||||
LOC[key] += slot * 0x10;
|
||||
});
|
||||
}
|
||||
|
||||
var auxRomFn = {
|
||||
@ -350,7 +354,16 @@ function Thunderclock(mmu, io, slot)
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LOC.AUX:
|
||||
break;
|
||||
}
|
||||
/*
|
||||
if (val === undefined) {
|
||||
debug("Read " + toHex(_command) + " from " + toHex(off))
|
||||
} else {
|
||||
debug("Wrote " + toHex(val) + " to " + toHex(off))
|
||||
}
|
||||
*/
|
||||
return _command;
|
||||
}
|
||||
|
||||
|
@ -49,23 +49,29 @@ var audioNode;
|
||||
var audio, audio2;
|
||||
|
||||
function initAudio() {
|
||||
if (typeof window.webkitAudioContext != "undefined") {
|
||||
var AC = window.webkitAudioContext || window.AudioContext;
|
||||
if (typeof AC != "undefined") {
|
||||
debug("Using Web Audio API");
|
||||
|
||||
audioAPI = true;
|
||||
audioContext = new window.webkitAudioContext();
|
||||
audioNode = audioContext.createScriptProcessor(2048, 1, 1);
|
||||
audioContext = new AC();
|
||||
audioNode = audioContext.createScriptProcessor(4096, 1, 1);
|
||||
io.floatAudio(audioContext.sampleRate);
|
||||
|
||||
audioNode.onaudioprocess = function(event) {
|
||||
var data = event.outputBuffer.getChannelData(0);
|
||||
var sample = io.getSample();
|
||||
for (var idx = 0; idx < data.length; idx++) {
|
||||
if (idx < sample.length) {
|
||||
data[idx] = sample[idx];
|
||||
} else {
|
||||
data[idx] = 0;
|
||||
}
|
||||
|
||||
var delta = 1; // sample.length / data.length;
|
||||
|
||||
var idx, kdx;
|
||||
for (idx = 0, kdx = 0;
|
||||
idx < data.length && parseInt(kdx, 10) < sample.length;
|
||||
kdx += delta, idx++) {
|
||||
data[idx] = sample[parseInt(kdx, 10)];
|
||||
}
|
||||
for (; idx < data.length; idx++) {
|
||||
data[idx] = sample[sample.length - 1];
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* -*- mode: JavaScript; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* Copyright 2010 Will Scullin <scullin@scullinsteel.com>
|
||||
/* Copyright 2010-2014 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
|
Loading…
x
Reference in New Issue
Block a user