Audio cleanup.

This commit is contained in:
Will Scullin 2015-07-10 22:55:36 -07:00
parent 8a761be9a9
commit 1dfbc0b61b
4 changed files with 179 additions and 259 deletions

View File

@ -1,7 +1,7 @@
<!DOCTYPE html><!-- -*- mode: HTML; indent-tabs-mode: nil -*- --> <!DOCTYPE html><!-- -*- mode: HTML; indent-tabs-mode: nil -*- -->
<!-- <!--
Copyright 2010-2013 Will Scullin <scullin@scullinsteel.com> Copyright 2010-2013 Will Scullin <scullin@scullinsteel.com>
Permission to use, copy, modify, distribute, and sell this software and its Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that the above copyright notice appear in all copies and that both that
@ -27,7 +27,7 @@
<link rel="apple-touch-icon" size="72x72" href="img/webapp-ipad.png" /> <link rel="apple-touch-icon" size="72x72" href="img/webapp-ipad.png" />
<link rel="shortcut icon" href="logoicon.png" /> <link rel="shortcut icon" href="logoicon.png" />
<link rel="stylesheet" type="text/css" href="css/apple2.css" /> <link rel="stylesheet" type="text/css" href="css/apple2.css" />
<link rel="stylesheet" type="text/css" <link rel="stylesheet" type="text/css"
href="http://code.jquery.com/ui/1.10.3/themes/mint-choc/jquery-ui.css" /> href="http://code.jquery.com/ui/1.10.3/themes/mint-choc/jquery-ui.css" />
<meta property="og:title" content="Apple ][js" /> <meta property="og:title" content="Apple ][js" />
@ -88,8 +88,8 @@ function DriveLights()
{ {
return { return {
driveLight: function(drive, on) { driveLight: function(drive, on) {
$("#disk" + drive).css("background-image", $("#disk" + drive).css("background-image",
on ? "url(css/red-on-16.png)" : on ? "url(css/red-on-16.png)" :
"url(css/red-off-16.png)"); "url(css/red-off-16.png)");
}, },
dirty: function(drive, dirty) { dirty: function(drive, dirty) {
@ -277,7 +277,7 @@ function loadHTTP(url) {
var req = new XMLHttpRequest(); var req = new XMLHttpRequest();
req.open("GET", url, true); req.open("GET", url, true);
req.responseType = "arraybuffer"; req.responseType = "arraybuffer";
req.onload = function(event) { req.onload = function(event) {
var parts = url.split(/[\/\.]/); var parts = url.split(/[\/\.]/);
var name = decodeURIComponent(parts[parts.length - 2]); var name = decodeURIComponent(parts[parts.length - 2]);
@ -360,7 +360,7 @@ function updateKHz() {
} }
/* Audio Handling */ /* Audio Handling */
initAudio(); initAudio(io);
function updateSound() function updateSound()
{ {
@ -403,9 +403,9 @@ function updateSpeed()
} }
var _requestAnimationFrame = var _requestAnimationFrame =
window.requestAnimationFrame || window.requestAnimationFrame ||
window.mozRequestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame; window.msRequestAnimationFrame;
function run(pc) { function run(pc) {
@ -418,8 +418,8 @@ function run(pc) {
} }
var ival = 30; var ival = 30;
var now, last = Date.now(), lastSample = last; var now, last = Date.now();
var runFn = function() { var runFn = function() {
now = Date.now(); now = Date.now();
frames++; frames++;
@ -439,18 +439,13 @@ function run(pc) {
loadAjax("json/disks/" + filename + ".json"); loadAjax("json/disks/" + filename + ".json");
} }
} }
} }
if (!loading) { if (!loading) {
running = true; running = true;
cpu.stepCycles(step); cpu.stepCycles(step);
running = false; running = false;
vm.blit(); vm.blit();
if (now - lastSample >= 200) { io.sampleTick();
if (!audioAPI) {
playSample();
}
lastSample = now;
}
} }
processGamepad(io); processGamepad(io);
@ -510,7 +505,7 @@ function selectDisk(event) {
} }
function clickDisk(event) { function clickDisk(event) {
doLoad(); doLoad();
} }
function loadDisk(data) { function loadDisk(data) {
@ -540,10 +535,10 @@ function loadJSON(data) {
loading = false; loading = false;
} }
/* /*
* LocalStorage Disk Storage * LocalStorage Disk Storage
*/ */
function updateLocalStorage() { function updateLocalStorage() {
var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}"); var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}");
var names = [], name, cat; var names = [], name, cat;
@ -555,38 +550,38 @@ function updateLocalStorage() {
} }
cat = disk_categories['Local Saves'] = []; cat = disk_categories['Local Saves'] = [];
$("#manage").empty(); $("#manage").empty();
names.forEach(function(name) { names.forEach(function(name) {
cat.push({'category': 'Local Saves', cat.push({'category': 'Local Saves',
'name': name, 'name': name,
'filename': 'local:' + name}); 'filename': 'local:' + name});
$("#manage").append("<span class='local_save'>" + $("#manage").append("<span class='local_save'>" +
name + name +
" <a href='#' onclick='doDelete(\"" + " <a href='#' onclick='doDelete(\"" +
name + name +
"\")'>Delete</a><br /></span>"); "\")'>Delete</a><br /></span>");
}); });
cat.push({'category': 'Local Saves', cat.push({'category': 'Local Saves',
'name': 'Manage Saves...', 'name': 'Manage Saves...',
'filename': 'local:__manage'}); 'filename': 'local:__manage'});
} }
function saveLocalStorage(drive, name) { function saveLocalStorage(drive, name) {
var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}"); var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}");
var json = disk2.getJSON(drive); var json = disk2.getJSON(drive);
diskIndex[name] = json; diskIndex[name] = json;
window.localStorage.diskIndex = JSON.stringify(diskIndex); window.localStorage.diskIndex = JSON.stringify(diskIndex);
window.alert("Saved"); window.alert("Saved");
drivelights.label(drive, name); drivelights.label(drive, name);
drivelights.dirty(drive, false); drivelights.dirty(drive, false);
updateLocalStorage(); updateLocalStorage();
} }
function deleteLocalStorage(name) { function deleteLocalStorage(name) {
var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}"); var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}");
if (diskIndex[name]) { if (diskIndex[name]) {
@ -640,7 +635,7 @@ function _keydown(evt) {
keyboard.controlKey(true); keyboard.controlKey(true);
} else if (!focused && (!evt.metaKey || evt.ctrlKey)) { } else if (!focused && (!evt.metaKey || evt.ctrlKey)) {
evt.preventDefault(); evt.preventDefault();
var key = keyboard.mapKeyEvent(evt); var key = keyboard.mapKeyEvent(evt);
if (key != 0xff) { if (key != 0xff) {
io.keyDown(key); io.keyDown(key);
@ -754,12 +749,12 @@ $(function() {
$("input,textarea").blur(function() { focused = false; }); $("input,textarea").blur(function() { focused = false; });
keyboard.create($("#keyboard")); keyboard.create($("#keyboard"));
if (prefs.havePrefs()) { if (prefs.havePrefs()) {
$("input[type=checkbox]").each(function() { $("input[type=checkbox]").each(function() {
var val = prefs.readPref(this.id); var val = prefs.readPref(this.id);
if (val != null) if (val != null)
this.checked = JSON.parse(val); this.checked = JSON.parse(val);
}); });
$("input[type=checkbox]").change(function() { $("input[type=checkbox]").change(function() {
prefs.writePref(this.id, JSON.stringify(this.checked)); prefs.writePref(this.id, JSON.stringify(this.checked));
@ -794,7 +789,7 @@ $(function() {
buttons: {"Close": cancel }}); buttons: {"Close": cancel }});
if (window.localStorage !== undefined) { if (window.localStorage !== undefined) {
$("button.disksave").show(); $("button.disksave").show();
} }
var oldcat = ""; var oldcat = "";
@ -806,11 +801,11 @@ $(function() {
$("<option />").val(cat).text(cat).appendTo("#category_select"); $("<option />").val(cat).text(cat).appendTo("#category_select");
disk_categories[cat] = []; disk_categories[cat] = [];
oldcat = cat; oldcat = cat;
} }
disk_categories[cat].push(file); disk_categories[cat].push(file);
if (disk) { if (disk) {
if (!disk_sets[name]) { if (!disk_sets[name]) {
disk_sets[name] = [] disk_sets[name] = []
} }
disk_sets[name].push(file); disk_sets[name].push(file);
} }
@ -887,7 +882,7 @@ $(function() {
<div style="float: right"> <div style="float: right">
<input type="button" onclick="window.open('about.html','_html')" <input type="button" onclick="window.open('about.html','_html')"
name="About" value="About"> name="About" value="About">
<input type="button" onclick="$('#options').dialog('open')" <input type="button" onclick="$('#options').dialog('open')"
name="Options" value="Options"> name="Options" value="Options">
</div> </div>
</div> </div>
@ -911,21 +906,21 @@ $(function() {
<h3>Joystick</h3> <h3>Joystick</h3>
<ul> <ul>
<li> <li>
<input type="checkbox" id="flip_x" <input type="checkbox" id="flip_x"
onclick="updateJoystick()" /> onclick="updateJoystick()" />
<label for="flip_x"> <label for="flip_x">
Flip X-Axis Flip X-Axis
</label> </label>
</li> </li>
<li> <li>
<input type="checkbox" id="flip_y" <input type="checkbox" id="flip_y"
onclick="updateJoystick()" /> onclick="updateJoystick()" />
<label for="flip_y"> <label for="flip_y">
Flip Y-Axis Flip Y-Axis
</label> </label>
</li> </li>
<li> <li>
<input type="checkbox" id="swap_x_y" <input type="checkbox" id="swap_x_y"
onclick="updateJoystick()" /> onclick="updateJoystick()" />
<label for="swap_x_y"> <label for="swap_x_y">
Swap X-Y Axis Swap X-Y Axis
@ -935,14 +930,14 @@ $(function() {
<h3>Monitor</h3> <h3>Monitor</h3>
<ul> <ul>
<li> <li>
<input type="checkbox" id="green_screen" <input type="checkbox" id="green_screen"
onclick="updateScreen()" /> onclick="updateScreen()" />
<label for="green_screen"> <label for="green_screen">
Green Screen Green Screen
</label> </label>
</li> </li>
<li> <li>
<input type="checkbox" id="show_scanlines" <input type="checkbox" id="show_scanlines"
onclick="updateScreen()" /> onclick="updateScreen()" />
<label for="show_scanlines"> <label for="show_scanlines">
Show Scanlines Show Scanlines
@ -952,7 +947,7 @@ $(function() {
<h3>Sound</h3> <h3>Sound</h3>
<ul> <ul>
<li> <li>
<input type="checkbox" id="enable_sound" <input type="checkbox" id="enable_sound"
onclick="updateSound()" checked="checked" /> onclick="updateSound()" checked="checked" />
<label for="enable_sound"> <label for="enable_sound">
Enable (Experimental) Enable (Experimental)
@ -962,7 +957,7 @@ $(function() {
</div> </div>
<div id="save" title="Save Disk"> <div id="save" title="Save Disk">
<form action="#" onsubmit="return false;"> <form action="#" onsubmit="return false;">
Save Name: <input type="text" name="name" id="save_name" Save Name: <input type="text" name="name" id="save_name"
style="width: 200px" /> style="width: 200px" />
</form> </form>
</div> </div>
@ -972,7 +967,7 @@ $(function() {
<table> <table>
<tr> <tr>
<td> <td>
<select id="category_select" multiple="multiple" <select id="category_select" multiple="multiple"
class="ui-widget ui-state-default" class="ui-widget ui-state-default"
onchange="selectCategory(event)" > onchange="selectCategory(event)" >
</select> </select>
@ -981,7 +976,7 @@ $(function() {
<select id="disk_select" multiple="multiple" <select id="disk_select" multiple="multiple"
class="ui-widget ui-state-default" class="ui-widget ui-state-default"
onchange="selectDisk(event)" onchange="selectDisk(event)"
ondblclick="clickDisk(event)"> ondblclick="clickDisk(event)">
</select> </select>
</td> </td>
</tr> </tr>

View File

@ -1,7 +1,7 @@
<!DOCTYPE html><!-- -*- mode: HTML; indent-tabs-mode: nil -*- --> <!DOCTYPE html><!-- -*- mode: HTML; indent-tabs-mode: nil -*- -->
<!-- <!--
Copyright 2010-2013 Will Scullin <scullin@scullinsteel.com> Copyright 2010-2013 Will Scullin <scullin@scullinsteel.com>
Permission to use, copy, modify, distribute, and sell this software and its Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that the above copyright notice appear in all copies and that both that
@ -27,7 +27,7 @@
<link rel="apple-touch-icon" size="72x72" href="img/webapp-ipad.png" /> <link rel="apple-touch-icon" size="72x72" href="img/webapp-ipad.png" />
<link rel="shortcut icon" href="logoicon.png" /> <link rel="shortcut icon" href="logoicon.png" />
<link rel="stylesheet" type="text/css" href="css/apple2.css" /> <link rel="stylesheet" type="text/css" href="css/apple2.css" />
<link rel="stylesheet" type="text/css" <link rel="stylesheet" type="text/css"
href="http://code.jquery.com/ui/1.10.3/themes/mint-choc/jquery-ui.css" /> href="http://code.jquery.com/ui/1.10.3/themes/mint-choc/jquery-ui.css" />
<meta property="og:title" content="Apple //js" /> <meta property="og:title" content="Apple //js" />
@ -59,7 +59,7 @@
<script type="text/javascript" src="js/ramfactor.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/thunderclock.js"></script>
<script type="text/javascript" src="js/cpu6502.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/base64.js"></script>
<script type="text/javascript" src="js/ui/audio.js"></script> <script type="text/javascript" src="js/ui/audio.js"></script>
<script type="text/javascript" src="js/ui/keyboard2e.js"></script> <script type="text/javascript" src="js/ui/keyboard2e.js"></script>
<script type="text/javascript" src="js/ui/gamepad.js"></script> <script type="text/javascript" src="js/ui/gamepad.js"></script>
@ -89,8 +89,8 @@ function DriveLights()
{ {
return { return {
driveLight: function(drive, on) { driveLight: function(drive, on) {
$("#disk" + drive).css("background-image", $("#disk" + drive).css("background-image",
on ? "url(css/red-on-16.png)" : on ? "url(css/red-on-16.png)" :
"url(css/red-off-16.png)"); "url(css/red-off-16.png)");
}, },
dirty: function(drive, dirty) { dirty: function(drive, dirty) {
@ -278,7 +278,7 @@ function loadHTTP(url) {
var req = new XMLHttpRequest(); var req = new XMLHttpRequest();
req.open("GET", url, true); req.open("GET", url, true);
req.responseType = "arraybuffer"; req.responseType = "arraybuffer";
req.onload = function(event) { req.onload = function(event) {
var parts = url.split(/[\/\.]/); var parts = url.split(/[\/\.]/);
var name = decodeURIComponent(parts[parts.length - 2]); var name = decodeURIComponent(parts[parts.length - 2]);
@ -353,7 +353,7 @@ function updateKHz() {
} }
/* Audio Handling */ /* Audio Handling */
initAudio(); initAudio(io);
function updateSound() function updateSound()
{ {
@ -396,9 +396,9 @@ function updateSpeed()
} }
var _requestAnimationFrame = var _requestAnimationFrame =
window.requestAnimationFrame || window.requestAnimationFrame ||
window.mozRequestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame; window.msRequestAnimationFrame;
function run(pc) { function run(pc) {
@ -411,8 +411,8 @@ function run(pc) {
} }
var ival = 30; var ival = 30;
var now, last = Date.now(), lastSample = last; var now, last = Date.now();
var runFn = function() { var runFn = function() {
now = Date.now(); now = Date.now();
frames++; frames++;
@ -432,19 +432,14 @@ function run(pc) {
loadAjax("json/disks/" + filename + ".json"); loadAjax("json/disks/" + filename + ".json");
} }
} }
} }
if (!loading) { if (!loading) {
mmu.resetVB(); mmu.resetVB();
running = true; running = true;
cpu.stepCycles(step); cpu.stepCycles(step);
running = false; running = false;
vm.blit(); vm.blit();
if (now - lastSample >= 200) { io.sampleTick();
if (!audioAPI) {
playSample();
}
lastSample = now;
}
} }
processGamepad(io); processGamepad(io);
@ -504,7 +499,7 @@ function selectDisk(event) {
} }
function clickDisk(event) { function clickDisk(event) {
doLoad(); doLoad();
} }
function loadDisk(data) { function loadDisk(data) {
@ -534,10 +529,10 @@ function loadJSON(data) {
loading = false; loading = false;
} }
/* /*
* LocalStorage Disk Storage * LocalStorage Disk Storage
*/ */
function updateLocalStorage() { function updateLocalStorage() {
var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}"); var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}");
var names = [], name, cat; var names = [], name, cat;
@ -549,38 +544,38 @@ function updateLocalStorage() {
} }
cat = disk_categories['Local Saves'] = []; cat = disk_categories['Local Saves'] = [];
$("#manage").empty(); $("#manage").empty();
names.forEach(function(name) { names.forEach(function(name) {
cat.push({'category': 'Local Saves', cat.push({'category': 'Local Saves',
'name': name, 'name': name,
'filename': 'local:' + name}); 'filename': 'local:' + name});
$("#manage").append("<span class='local_save'>" + $("#manage").append("<span class='local_save'>" +
name + name +
" <a href='#' onclick='doDelete(\"" + " <a href='#' onclick='doDelete(\"" +
name + name +
"\")'>Delete</a><br /></span>"); "\")'>Delete</a><br /></span>");
}); });
cat.push({'category': 'Local Saves', cat.push({'category': 'Local Saves',
'name': 'Manage Saves...', 'name': 'Manage Saves...',
'filename': 'local:__manage'}); 'filename': 'local:__manage'});
} }
function saveLocalStorage(drive, name) { function saveLocalStorage(drive, name) {
var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}"); var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}");
var json = disk2.getJSON(drive); var json = disk2.getJSON(drive);
diskIndex[name] = json; diskIndex[name] = json;
window.localStorage.diskIndex = JSON.stringify(diskIndex); window.localStorage.diskIndex = JSON.stringify(diskIndex);
window.alert("Saved"); window.alert("Saved");
drivelights.label(drive, name); drivelights.label(drive, name);
drivelights.dirty(drive, false); drivelights.dirty(drive, false);
updateLocalStorage(); updateLocalStorage();
} }
function deleteLocalStorage(name) { function deleteLocalStorage(name) {
var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}"); var diskIndex = JSON.parse(window.localStorage.diskIndex || "{}");
if (diskIndex[name]) { if (diskIndex[name]) {
@ -638,7 +633,7 @@ function _keydown(evt) {
keyboard.optionKey(true); keyboard.optionKey(true);
} else if (!focused) { } else if (!focused) {
evt.preventDefault(); evt.preventDefault();
var key = keyboard.mapKeyEvent(evt); var key = keyboard.mapKeyEvent(evt);
if (key != 0xff) { if (key != 0xff) {
io.keyDown(key); io.keyDown(key);
@ -754,17 +749,17 @@ $(function() {
$("input,textarea").focus(function() { focused = true; }) $("input,textarea").focus(function() { focused = true; })
.blur(function() { focused = false; }); .blur(function() { focused = false; });
$("body > div").hover(function() { focused = false; }, $("body > div").hover(function() { focused = false; },
function() { focused = true; }); function() { focused = true; });
keyboard.create($("#keyboard")); keyboard.create($("#keyboard"));
if (prefs.havePrefs()) { if (prefs.havePrefs()) {
$("input[type=checkbox]").each(function() { $("input[type=checkbox]").each(function() {
var val = prefs.readPref(this.id); var val = prefs.readPref(this.id);
if (val != null) if (val != null)
this.checked = JSON.parse(val); this.checked = JSON.parse(val);
}); });
$("input[type=checkbox]").change(function() { $("input[type=checkbox]").change(function() {
prefs.writePref(this.id, JSON.stringify(this.checked)); prefs.writePref(this.id, JSON.stringify(this.checked));
@ -799,7 +794,7 @@ $(function() {
buttons: {"Close": cancel }}); buttons: {"Close": cancel }});
if (window.localStorage !== undefined) { if (window.localStorage !== undefined) {
$("button.disksave").show(); $("button.disksave").show();
} }
var oldcat = ""; var oldcat = "";
@ -811,11 +806,11 @@ $(function() {
$("<option />").val(cat).text(cat).appendTo("#category_select"); $("<option />").val(cat).text(cat).appendTo("#category_select");
disk_categories[cat] = []; disk_categories[cat] = [];
oldcat = cat; oldcat = cat;
} }
disk_categories[cat].push(file); disk_categories[cat].push(file);
if (disk) { if (disk) {
if (!disk_sets[name]) { if (!disk_sets[name]) {
disk_sets[name] = [] disk_sets[name] = []
} }
disk_sets[name].push(file); disk_sets[name].push(file);
} }
@ -852,7 +847,7 @@ $(function() {
<table id="display"> <table id="display">
<tr> <tr>
<td style="vertical-align: top"> <td style="vertical-align: top">
<div role="textbox" class="overscan" <div role="textbox" class="overscan"
onKeyDown="_keydown(event);" onKeyDown="_keydown(event);"
onKeyUp="_keyup(event);"> onKeyUp="_keyup(event);">
<canvas id="screen" width="560" height="384"></canvas> <canvas id="screen" width="560" height="384"></canvas>
@ -894,7 +889,7 @@ $(function() {
<div style="float: right"> <div style="float: right">
<input type="button" onclick="window.open('about.html','_html')" <input type="button" onclick="window.open('about.html','_html')"
name="About" value="About"> name="About" value="About">
<input type="button" onclick="$('#options').dialog('open')" <input type="button" onclick="$('#options').dialog('open')"
name="Options" value="Options"> name="Options" value="Options">
</div> </div>
</div> </div>
@ -918,21 +913,21 @@ $(function() {
<h3>Joystick</h3> <h3>Joystick</h3>
<ul> <ul>
<li> <li>
<input type="checkbox" id="flip_x" <input type="checkbox" id="flip_x"
onclick="updateJoystick()" /> onclick="updateJoystick()" />
<label for="flip_x"> <label for="flip_x">
Flip X-Axis Flip X-Axis
</label> </label>
</li> </li>
<li> <li>
<input type="checkbox" id="flip_y" <input type="checkbox" id="flip_y"
onclick="updateJoystick()" /> onclick="updateJoystick()" />
<label for="flip_y"> <label for="flip_y">
Flip Y-Axis Flip Y-Axis
</label> </label>
</li> </li>
<li> <li>
<input type="checkbox" id="swap_x_y" <input type="checkbox" id="swap_x_y"
onclick="updateJoystick()" /> onclick="updateJoystick()" />
<label for="swap_x_y"> <label for="swap_x_y">
Swap X-Y Axis Swap X-Y Axis
@ -942,14 +937,14 @@ $(function() {
<h3>Monitor</h3> <h3>Monitor</h3>
<ul> <ul>
<li> <li>
<input type="checkbox" id="green_screen" <input type="checkbox" id="green_screen"
onclick="updateScreen()" /> onclick="updateScreen()" />
<label for="green_screen"> <label for="green_screen">
Green Screen Green Screen
</label> </label>
</li> </li>
<li> <li>
<input type="checkbox" id="show_scanlines" <input type="checkbox" id="show_scanlines"
onclick="updateScreen()" /> onclick="updateScreen()" />
<label for="show_scanlines"> <label for="show_scanlines">
Show Scanlines Show Scanlines
@ -959,7 +954,7 @@ $(function() {
<h3>Sound</h3> <h3>Sound</h3>
<ul> <ul>
<li> <li>
<input type="checkbox" id="enable_sound" <input type="checkbox" id="enable_sound"
onclick="updateSound()" checked="checked" /> onclick="updateSound()" checked="checked" />
<label for="enable_sound"> <label for="enable_sound">
Enable (Experimental) Enable (Experimental)
@ -969,7 +964,7 @@ $(function() {
</div> </div>
<div id="save" title="Save Disk"> <div id="save" title="Save Disk">
<form action="#" onsubmit="return false;"> <form action="#" onsubmit="return false;">
Save Name: <input type="text" name="name" id="save_name" Save Name: <input type="text" name="name" id="save_name"
style="width: 200px" /> style="width: 200px" />
</form> </form>
</div> </div>
@ -979,7 +974,7 @@ $(function() {
<table> <table>
<tr> <tr>
<td> <td>
<select id="category_select" multiple="multiple" <select id="category_select" multiple="multiple"
class="ui-widget ui-state-default" class="ui-widget ui-state-default"
onchange="selectCategory(event)" > onchange="selectCategory(event)" >
</select> </select>
@ -988,7 +983,7 @@ $(function() {
<select id="disk_select" multiple="multiple" <select id="disk_select" multiple="multiple"
class="ui-widget ui-state-default" class="ui-widget ui-state-default"
onchange="selectDisk(event)" onchange="selectDisk(event)"
ondblclick="clickDisk(event)"> ondblclick="clickDisk(event)">
</select> </select>
</td> </td>
</tr> </tr>

View File

@ -18,7 +18,8 @@ function Apple2IO(cpu, callbacks)
"use strict"; "use strict";
var _hz = 1023000; var _hz = 1023000;
var _rate = 16000; var _rate = 44000;
var _sample_size = 4096;
var _cycles_per_sample = _hz / _rate; var _cycles_per_sample = _hz / _rate;
@ -31,10 +32,11 @@ function Apple2IO(cpu, callbacks)
var _sample = []; var _sample = [];
var _sampleTime = 0; var _sampleTime = 0;
var _high = "%A0"; var _high = 0.5;
var _mid = "%80"; var _low = -0.5;
var _low = "%60";
var _audioListener = null;
var _trigger = 0; var _trigger = 0;
var _tape = []; var _tape = [];
@ -94,6 +96,20 @@ function Apple2IO(cpu, callbacks)
var _locs = []; var _locs = [];
function _tick() {
var now = cpu.cycles();
var phase = _phase > 0 ? _high : _low;
for (; _sampleTime < now; _sampleTime += _cycles_per_sample) {
_sample.push(phase);
if (_sample.length >= _sample_size) {
if (_audioListener) {
_audioListener(_sample);
}
_sample = [];
}
}
}
function _access(off) { function _access(off) {
var result = 0; var result = 0;
var now = cpu.cycles(); var now = cpu.cycles();
@ -204,13 +220,8 @@ function Apple2IO(cpu, callbacks)
if ('doublehires' in callbacks) callbacks.doublehires(true); if ('doublehires' in callbacks) callbacks.doublehires(true);
break; break;
case LOC.SPEAKER: case LOC.SPEAKER:
if (_sampleTime) { _phase = -_phase;
var phase = _phase > 0 ? _high : _low; _tick();
for (; _sampleTime < now; _sampleTime += _cycles_per_sample) {
_sample.push(phase);
}
_phase = -_phase;
}
break; break;
case LOC.STROBE: case LOC.STROBE:
_key &= 0x7f; _key &= 0x7f;
@ -342,45 +353,14 @@ function Apple2IO(cpu, callbacks)
paddle: function apple2io_paddle(p, v) { paddle: function apple2io_paddle(p, v) {
_paddle[p] = v; _paddle[p] = v;
}, },
getSample: function apple2io_getSample() {
var result = _sample;
var now = cpu.cycles();
var phase = _mid;
if (_sample.length) {
phase = _phase > 0 ? _high : _low;
}
for (; _sampleTime < now; _sampleTime += _cycles_per_sample) {
_sample.push(phase);
}
_sample = [];
return result;
},
floatAudio: function apple2io_floatAudio(rate) {
_rate = rate;
_low = -0.5;
_mid = 0.0;
_high = 0.5;
_cycles_per_sample = _hz / _rate;
},
byteAudio: function apple2io_byteAudio(rate) {
_rate = rate;
_low = 0xa0;
_mid = 0x80;
_high = 0x60;
_cycles_per_sample = _hz / _rate;
},
updateHz: function apple2io_updateHz(hz) { updateHz: function apple2io_updateHz(hz) {
_hz = hz; _hz = hz;
_cycles_per_sample = _hz / _rate; _cycles_per_sample = _hz / _rate;
}, },
setKeyBuffer: function apple2io_setKeyBuffer(buffer) { setKeyBuffer: function apple2io_setKeyBuffer(buffer) {
_buffer = buffer.split(""); _buffer = buffer.split('');
if (_buffer.length > 0) { if (_buffer.length > 0) {
_keyDown = true; _keyDown = true;
_key = _buffer.shift().charCodeAt(0) | 0x80; _key = _buffer.shift().charCodeAt(0) | 0x80;
@ -391,6 +371,19 @@ function Apple2IO(cpu, callbacks)
debug('Tape length: ' + tape.length); debug('Tape length: ' + tape.length);
_tape = tape; _tape = tape;
_tapeOffset = -1; _tapeOffset = -1;
},
sampleRate: function sampleRate(rate) {
_rate = rate;
_cycles_per_sample = _hz / _rate;
},
sampleTick: function sampleTick() {
_tick();
},
addSampleListener: function addSampleListener(cb) {
_audioListener = cb;
} }
}; };
} }

View File

@ -11,132 +11,69 @@
*/ */
/*jshint jquery: true, browser: true */ /*jshint jquery: true, browser: true */
/*globals io: false, toHex:false, debug: false */ /*globals debug: false */
/*exported playSample, enableSound, initAudio */ /*exported enableSound, initAudio */
/* /*
* Audio Handling * Audio Handling
*/ */
var sound = true; var sound = true;
var _samples = [];
// 8000 = 0x1f40 = 64, 31 var audioContext;
// 16000 = 0x3e80 = 128, 62 var audioNode;
var AC = window.webkitAudioContext || window.AudioContext;
var wavHeader = if (typeof AC !== 'undefined') {
['R','I','F','F',68 ,3 ,0 ,0 ,'W','A','V','E','f','m','t',' ', audioContext = new AC();
16 ,0 ,0 ,0 ,1 ,0 ,1 ,0 ,128,62 ,0 ,0 ,128,62 ,0 ,0 , audioNode = audioContext.createScriptProcessor(4096, 1, 1);
1 ,0 ,8 ,0 ,'d','a','t','a',32 ,3 ,0 ,0 ];
function percentEncode(ary) { audioNode.onaudioprocess = function(event) {
var buf = ""; var data = event.outputBuffer.getChannelData(0);
for (var idx = 0; idx < ary.length; idx++) { var sample = _samples.shift();
buf += "%" + toHex(ary[idx]); var idx = 0;
}
return buf;
}
wavHeader = $.map(wavHeader, function(n) { var len = data.length;
return typeof(n) == "string" ? n.charCodeAt(0) : n; if (sample) {
}); len = Math.min(sample.length, len);
for (; idx < len; idx++) {
data[idx] = sample[idx];
}
}
for (; idx < data.length; idx++) {
data[idx] = 0.0;
}
};
var wavHeaderStr = percentEncode(wavHeader); /*
// Create and specify parameters for the low-pass filter.
var filter = audioContext.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 11000;
filter.connect(audioContext.destination);
audioNode.connect(filter);
*/
var audioAPI = false; audioNode.connect(audioContext.destination);
var audioMoz = false; }
var audioContext;
var audioNode;
var audio, audio2;
function initAudio() { function initAudio(io) {
var AC = window.webkitAudioContext || window.AudioContext; if (audioContext) {
if (typeof AC != "undefined") { debug('Using Webkit Audio');
debug("Using Web Audio API"); io.sampleRate(audioContext.sampleRate);
io.addSampleListener(function(sample) {
audioAPI = true; if (sound) {
audioContext = new AC(); _samples.push(sample);
audioNode = audioContext.createScriptProcessor(4096, 1, 1); while (_samples.length > 5) {
io.floatAudio(audioContext.sampleRate); _samples.shift();
}
audioNode.onaudioprocess = function(event) {
var data = event.outputBuffer.getChannelData(0);
var sample = io.getSample();
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 {
audio = document.createElement("audio");
if (audio.mozSetup) {
debug("Using Mozilla Audio API");
audio.mozSetup(1, 16000);
io.floatAudio(16000);
audioMoz = true;
} else {
debug("Using audio elements");
audio2 = document.createElement("audio");
}
} }
} }
function playSample() { function enableSound(enable) {
// [audio,audio2] = [audio2,audio]; sound = enable;
var sample = io.getSample();
if (!sound) {
return;
}
if (audioMoz) {
audio.mozWriteAudio(sample);
return;
}
var tmp = audio;
audio = audio2;
audio2 = tmp;
if (sample && sample.length) {
var len = sample.length,
buf = sample.join(""),
o1 = percentEncode([(len + 36) & 0xff, (len + 36) >> 8]),
o2 = percentEncode([len & 0xff, len >> 8]),
header = wavHeaderStr.replace("%44%03",o1).replace("%20%03", o2);
audio.src = "data:audio/x-wav," + header + buf;
// debug(audio.src);
audio.play();
}
} }
function enableSound(on)
{
sound = on;
if (audioAPI) {
if (sound) {
audioNode.connect(audioContext.destination);
} else {
audioNode.disconnect();
}
} else {
if (sound) {
if (audio) audio.volume = 0.5;
if (audio2) audio2.volume = 0.5;
} else {
io.getSample(true);
}
}
}