Various bug fixes, tape support.

This commit is contained in:
Will Scullin 2014-07-30 12:01:57 -07:00
parent a6f454f845
commit 0a1127f541
13 changed files with 433 additions and 140 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -771,7 +771,7 @@ function DiskII(io, callbacks, slot)
}
}
}
tracks[t] = track;
tracks[t] = bytify(track);
}
_cur.volume = v;
_cur.format = fmt;

View File

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

View File

@ -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) {

View File

@ -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",

View File

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

View File

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

View File

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