/* globals debug: false, gup: false, hup: false, toHex: false, CPU6502: false, RAM: false, Apple2ROM: false, IntBASIC: false, OriginalROM: false, Apple2jROM: false, apple2_charset: false, apple2j_charset: false, pigfont_charset: false, apple2lc_charset: false, Apple2IO: false LoresPage: false, HiresPage: false, VideoModes: false, KeyBoard: false, Parallel: false, Videoterm: false, DiskII: false, Printer: false, LanguageCard: false, RAMFactor: false, Thunderclock: false, Prefs: false, disk_index: false, Audio: false, initGamepad: false, processGamepad: false, gamepad: false, ApplesoftDump: false, SYMBOLS: false, multiScreen: true */ /* exported openLoad, openSave, doDelete, selectCategory, selectDisk, clickDisk, multiScreen, updateJoystick, pauseRun, step, toggleSound, restoreState, saveState, dumpProgram, PageDebug, enhanced */ var kHz = 1023; var focused = false; var startTime = Date.now(); var lastCycles = 0; var renderedFrames = 0, lastFrames = 0; var paused = false; var hashtag; var DEBUG = false; var TRACE = false; var MAX_TRACE = 256; var trace = []; /* * Page viewer */ function PageDebug(page) { var _page = page; function _init() { var r, c; var row = $('').appendTo('#page' + toHex(_page)); $('\\').appendTo(row); for (c = 0; c < 16; c++) { $('' + toHex(c) + '').appendTo(row); } for (r = 0; r < 16; r++) { row = $('').appendTo('#page' + toHex(_page)); $('' + toHex(r * 16) + '').appendTo(row); for (c = 0; c < 16; c++) { $('--').appendTo(row).attr('id', 'page' + toHex(_page) + '-' + toHex(r * 16 + c)); } } } _init(); return { start: function() { return _page; }, end: function() { return _page; }, read: null, write: function(page, off, val) { $('#page' + toHex(page) + '-' + toHex(off)).text(toHex(val)); } }; } var disk_categories = {'Local Saves': []}; var disk_sets = {}; var disk_cur_name = []; var disk_cur_cat = []; function DriveLights() { return { driveLight: function(drive, on) { $('#disk' + drive).css( 'background-image', on ? 'url(css/red-on-16.png)' : 'url(css/red-off-16.png)' ); }, dirty: function() { // $('#disksave' + drive).button('option', 'disabled', !dirty); }, label: function(drive, label) { if (label) { $('#disklabel' + drive).text(label); } return $('#disklabel' + drive).text(); }, getState: function() { return { disks: [ this.label(1), this.label(2) ] }; }, setState: function(state) { if (state && state.disks) { this.label(1, state.disks[0].label); this.label(2, state.disks[1].label); } } }; } var DISK_TYPES = ['dsk','d13','do','po','raw','nib','2mg']; var TAPE_TYPES = ['wav','aiff','aif','mp3']; var _currentDrive = 1; function openLoad(drive, event) { _currentDrive = parseInt(drive, 10); if (event.metaKey) { openLoadHTTP(drive); } else { if (disk_cur_cat[drive]) { $('#category_select').val(disk_cur_cat[drive]).change(); } $('#load').dialog('open'); } } function openSave(drive, event) { _currentDrive = parseInt(drive, 10); var mimetype = 'application/octet-stream'; var data = disk2.getBinary(drive); var a = $('#local_save_link'); var blob = new Blob([data], { 'type': mimetype }); a.attr('href', window.URL.createObjectURL(blob)); a.attr('download', drivelights.label(drive) + '.dsk'); if (event.metaKey) { dumpDisk(drive); } else { $('#save_name').val(drivelights.label(drive)); $('#save').dialog('open'); } } var loading = false; function loadAjax(drive, url) { loading = true; $('#loading').dialog('open'); $.ajax({ url: url, dataType: 'json', modifiedSince: true, error: function(xhr, status, error) { alert(error || status); $('#loading').dialog('close'); loading = false; }, success: function(data) { if (data.type == 'binary') { loadBinary(drive, data); } else if ($.inArray(data.type, DISK_TYPES) >= 0) { loadDisk(drive, data); } initGamepad(data.gamepad); $('#loading').dialog('close'); loading = false; } }); } function doLoad() { var urls = $('#disk_select').val(), url; if (urls && urls.length) { if (typeof(urls) == 'string') { url = urls; } else { url = urls[0]; } } var files = $('#local_file').prop('files'); if (files.length == 1) { doLoadLocal(_currentDrive); } else if (url) { var filename; $('#load').dialog('close'); if (url.substr(0,6) == 'local:') { filename = url.substr(6); if (filename == '__manage') { openManage(); } else { loadLocalStorage(_currentDrive, filename); } } else { var r1 = /json\/disks\/(.*).json$/.exec(url); if (r1) { filename = r1[1]; } else { filename = url; } var parts = document.location.hash.split('|'); parts[_currentDrive - 1] = filename; document.location.hash = parts.join('|'); loadAjax(_currentDrive, url); } } } function doSave() { var name = $('#save_name').val(); saveLocalStorage(_currentDrive, name); $('#save').dialog('close'); } function doDelete(name) { if (window.confirm('Delete ' + name + '?')) { deleteLocalStorage(name); } } function doLoadLocal(drive) { var files = $('#local_file').prop('files'); if (files.length == 1) { var file = files[0]; var parts = file.name.split('.'); var ext = parts[parts.length - 1].toLowerCase(); if ($.inArray(ext, DISK_TYPES) >= 0) { doLoadLocalDisk(drive, file); } else if ($.inArray(ext, TAPE_TYPES) >= 0) { doLoadLocalTape(file); } else { window.alert('Unknown file type: ' + ext); $('#load').dialog('close'); } } } function doLoadLocalDisk(drive, file) { var fileReader = new FileReader(); fileReader.onload = function() { var parts = file.name.split('.'); var name = parts[0], ext = parts[parts.length - 1].toLowerCase(); if (disk2.setBinary(drive, name, ext, this.result)) { drivelights.label(drive, name); $('#load').dialog('close'); initGamepad(); } }; fileReader.readAsArrayBuffer(file); } function doLoadLocalTape(file) { // Audio Buffer Source var context; if (typeof window.AudioContext != 'undefined') { context = new window.AudioContext(); } else { window.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 doLoadHTTP(drive, _url) { var url = _url || $('#http_url').val(); if (url) { var req = new XMLHttpRequest(); req.open('GET', url, true); req.responseType = 'arraybuffer'; req.onload = function() { var parts = url.split(/[/.]/); var name = decodeURIComponent(parts[parts.length - 2]); var ext = parts[parts.length - 1].toLowerCase(); if (disk2.setBinary(drive, name, ext, req.response)) { drivelights.label(drive, name); $('#http_load').dialog('close'); initGamepad(); } }; req.send(null); } } function openLoadHTTP(drive) { _currentDrive = parseInt(drive, 10); $('#http_load').dialog('open'); } function openManage() { $('#manage').dialog('open'); } var prefs = new Prefs(); var romVersion = prefs.readPref('computer_type2'); var enhanced = false; var rom; var char_rom = apple2_charset; switch (romVersion) { case 'apple2': rom = new IntBASIC(); break; case'original': rom = new OriginalROM(); break; case 'apple2jplus': rom = new Apple2jROM(); char_rom = apple2j_charset; break; case 'apple2pig': rom = new Apple2ROM(); char_rom = pigfont_charset; break; case 'apple2lc': rom = new Apple2ROM(); char_rom = apple2lc_charset; break; default: rom = new Apple2ROM(); } var runTimer = null; var cpu = new CPU6502(); var context1, context2, context3, context4; var canvas1 = document.getElementById('screen'); var canvas2 = document.getElementById('screen2'); var canvas3 = document.getElementById('screen3'); var canvas4 = document.getElementById('screen4'); context1 = canvas1.getContext('2d'); if (canvas4) { multiScreen = true; context2 = canvas2.getContext('2d'); context3 = canvas3.getContext('2d'); context4 = canvas4.getContext('2d'); } else if (canvas2) { multiScreen = true; context2 = context1; context3 = canvas2.getContext('2d'); context4 = context3; } else { context2 = context1; context3 = context1; context4 = context1; } var gr = new LoresPage(1, char_rom, false, context1); var gr2 = new LoresPage(2, char_rom, false, context2); var hgr = new HiresPage(1, context3); var hgr2 = new HiresPage(2, context4); var ram1 = new RAM(0x00, 0x03), ram2 = new RAM(0x0C, 0x1F), ram3 = new RAM(0x60, 0xBF); var vm = new VideoModes(gr, hgr, gr2, hgr2, false); var dumper = new ApplesoftDump(cpu); var drivelights = new DriveLights(); var io = new Apple2IO(cpu, vm); var keyboard = new KeyBoard(io); var audio = new Audio(io); var lc = new LanguageCard(io, 0, rom); var parallel = new Parallel(io, 1, new Printer()); var slinky = new RAMFactor(io, 2, 1024 * 1024); var videoterm = new Videoterm(io, 3, context1); var disk2 = new DiskII(io, 6, drivelights); var clock = new Thunderclock(io, 7); cpu.addPageHandler(ram1); cpu.addPageHandler(gr); cpu.addPageHandler(gr2); cpu.addPageHandler(ram2); cpu.addPageHandler(hgr); cpu.addPageHandler(hgr2); cpu.addPageHandler(ram3); cpu.addPageHandler(io); cpu.addPageHandler(lc); io.setSlot(0, lc); io.setSlot(1, parallel); io.setSlot(2, slinky); io.setSlot(3, videoterm); io.setSlot(6, disk2); io.setSlot(7, clock); var showFPS = false; function updateKHz() { var now = Date.now(); var ms = now - startTime; var cycles = cpu.cycles(); var delta; if (showFPS) { delta = renderedFrames - lastFrames; var fps = parseInt(delta/(ms/1000), 10); $('#khz').text( fps + 'fps'); } else { delta = cycles - lastCycles; var khz = parseInt(delta/ms); $('#khz').text( khz + 'KHz'); } startTime = now; lastCycles = cycles; lastFrames = renderedFrames; } function updateSound() { audio.enable($('#enable_sound').attr('checked')); } function dumpDisk(drive) { var wind = window.open('', '_blank'); wind.document.title = drivelights.label(drive); wind.document.write('
');
    wind.document.write(disk2.getJSON(drive, true));
    wind.document.write('
'); wind.document.close(); } function dumpProgram() { var wind = window.open('', '_blank'); wind.document.title = 'Program Listing'; wind.document.write('
');
    wind.document.write(dumper.toString());
    wind.document.write('
'); wind.document.close(); } function step() { if (runTimer) { clearInterval(runTimer); } runTimer = null; cpu.step(function() { debug(cpu.dumpRegisters()); debug(cpu.dumpPC()); }); } var accelerated = false; function updateCPU() { accelerated = $('#accelerator_toggle').prop('checked'); kHz = accelerated ? 4092 : 1023; io.updateHz(kHz * 1000); if (runTimer) { run(); } } var _requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; function run(pc) { if (runTimer) { clearInterval(runTimer); } if (pc) { cpu.setPC(pc); } var ival = 30; var now, last = Date.now(); var runFn = function() { now = Date.now(); renderedFrames++; var step = (now - last) * kHz, stepMax = kHz * ival; last = now; if (step > stepMax) { step = stepMax; } if (document.location.hash != hashtag) { hashtag = document.location.hash; var hash = hup(); if (hash) { processHash(hash); } } if (!loading) { if (DEBUG) { cpu.stepCyclesDebug(TRACE ? 1 : step, function() { var line = cpu.dumpRegisters() + ' ' + cpu.dumpPC(undefined, SYMBOLS); if (TRACE) { debug(line); } else { trace.push(line); if (trace.length > MAX_TRACE) { trace.shift(); } } }); } else { cpu.stepCycles(step); } if (io.annunciator(0)) { if (multiScreen) { vm.blit(); } videoterm.blit(); } else { vm.blit(); } io.sampleTick(); } processGamepad(io); if (!paused && _requestAnimationFrame) { _requestAnimationFrame(runFn); } }; if (_requestAnimationFrame) { _requestAnimationFrame(runFn); } else { runTimer = setInterval(runFn, ival); } } function stop() { if (runTimer) { clearInterval(runTimer); } runTimer = null; } function reset() { cpu.reset(); } var state = null; function storeStateLocal() { window.localStorage['apple2.state'] = JSON.stringify(state); } function restoreStateLocal() { var data = window.localStorage['apple2.state']; if (data) { state = JSON.parse(data); } } function saveState() { if (state && !window.confirm('Overwrite Saved State?')) { return; } state = { cpu: cpu.getState(), ram1: ram1.getState(), ram2: ram2.getState(), ram3: ram3.getState(), io: io.getState(), lc: lc.getState(), vm: vm.getState(), disk2: disk2.getState(), drivelights: drivelights.getState() }; if (slinky) { state.slinky = slinky.getState(); } if (window.localStorage) { storeStateLocal(); } } function restoreState() { if (window.localStorage) { restoreStateLocal(); } if (!state) { return; } cpu.setState(state.cpu); ram1.setState(state.ram1); ram2.setState(state.ram2); ram3.setState(state.ram3); io.setState(state.io); lc.setState(state.lc); vm.setState(state.vm); disk2.setState(state.disk2); drivelights.setState(state.drivelights); if (slinky && state.slinky) { slinky.setState(state.slinky); } } function loadBinary(bin) { stop(); for (var idx = 0; idx < bin.length; idx++) { var pos = bin.start + idx; cpu.write(pos >> 8, pos & 0xff, bin.data[idx]); } run(bin.start); } function selectCategory() { $('#disk_select').empty(); var cat = disk_categories[$('#category_select').val()]; if (cat) { for (var idx = 0; idx < cat.length; idx++) { var file = cat[idx], name = file.name; if (file.disk) { name += ' - ' + file.disk; } var option = $('