From 9324b23def6682f8a113c3fb8c73df6a23c89888 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Fri, 7 Jun 2019 13:03:30 -0400 Subject: [PATCH] added Gamepad API support --- doc/notes.txt | 2 +- src/baseplatform.ts | 12 +++- src/emu.ts | 116 ++++++++++++++++++++++++++++++++------ src/platform/astrocade.ts | 5 +- src/platform/coleco.ts | 5 +- src/platform/galaxian.ts | 5 +- src/platform/nes.ts | 7 ++- src/platform/verilog.ts | 5 +- 8 files changed, 131 insertions(+), 26 deletions(-) diff --git a/doc/notes.txt b/doc/notes.txt index be4e20d3..627d0bbd 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -86,7 +86,7 @@ TODO: - upload binary files doesn't do what's expected, changing pulldown and whatnot - chrome autostart audio: https://github.com/processing/p5.js-sound/issues/249 - firefox autostart audio: https://support.mozilla.org/en-US/kb/block-autoplay -- show player controls for each platform, allow touch support, navigator.getGamepads +- show player controls for each platform, allow touch support - better undo/diff for mistakes? - ide bug/feature visualizer for sponsors - global undo/redo at checkpoints (when rom changes) diff --git a/src/baseplatform.ts b/src/baseplatform.ts index 70d61459..ab46f7ee 100644 --- a/src/baseplatform.ts +++ b/src/baseplatform.ts @@ -1,5 +1,5 @@ -import { RAM, RasterVideo, dumpRAM, AnimationTimer, setKeyboardFromMap, padBytes } from "./emu"; +import { RAM, RasterVideo, dumpRAM, AnimationTimer, setKeyboardFromMap, padBytes, ControllerPoller } from "./emu"; import { hex, printFlags, invertMap } from "./util"; import { CodeAnalyzer } from "./analysis"; import { disassemble6502 } from "./cpu/disasm6502"; @@ -265,11 +265,14 @@ export abstract class BaseDebugPlatform extends BasePlatform { this.resume(); } preFrame() { - this.updateRecorder(); } postFrame() { } + pollControls() { + } nextFrame(novideo : boolean) { + this.pollControls(); + this.updateRecorder(); this.preFrame(); this.advance(novideo); this.postFrame(); @@ -1101,6 +1104,7 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform { pixels : Uint32Array; inputs = new Uint8Array(16); mainElement : HTMLElement; + poller : ControllerPoller; abstract newRAM() : Uint8Array; abstract newMembus() : MemoryBus; @@ -1126,13 +1130,15 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform { this.video = new RasterVideo(this.mainElement, this.canvasWidth, this.numVisibleScanlines, this.getVideoOptions()); this.video.create(); this.pixels = this.video.getFrameData(); - setKeyboardFromMap(this.video, this.inputs, this.getKeyboardMap(), this.getKeyboardFunction()); + this.poller = setKeyboardFromMap(this.video, this.inputs, this.getKeyboardMap(), this.getKeyboardFunction()); this.timer = new AnimationTimer(60, this.nextFrame.bind(this)); } readAddress(addr) { return this.membus.read(addr); } + + pollControls() { this.poller.poll(); } advance(novideo : boolean) { var extraCycles = 0; diff --git a/src/emu.ts b/src/emu.ts index 6727e5c0..8b40df99 100644 --- a/src/emu.ts +++ b/src/emu.ts @@ -51,13 +51,13 @@ export enum KeyFlags { } function _setKeyboardEvents(canvas:HTMLElement, callback:KeyboardCallback) { - canvas.onkeydown = function(e) { + canvas.onkeydown = (e) => { callback(e.which, 0, KeyFlags.KeyDown|_metakeyflags(e)); }; - canvas.onkeyup = function(e) { + canvas.onkeyup = (e) => { callback(e.which, 0, KeyFlags.KeyUp|_metakeyflags(e)); }; - canvas.onkeypress = function(e) { + canvas.onkeypress = (e) => { callback(e.which, e.charCode, KeyFlags.KeyPress|_metakeyflags(e)); }; }; @@ -327,16 +327,24 @@ interface KeyDef { button?:number }; +interface KeyMapEntry { + index:number; + mask:number; + def:KeyDef; +} + +type KeyCodeMap = Map; + export const Keys : {[keycode:string]:KeyDef} = { // gamepad and keyboard (player 0) UP: {c: 38, n: "Up", plyr:0, yaxis:-1}, DOWN: {c: 40, n: "Down", plyr:0, yaxis:1}, LEFT: {c: 37, n: "Left", plyr:0, xaxis:-1}, RIGHT: {c: 39, n: "Right", plyr:0, xaxis:1}, - A: {c: 32, n: "Space", plyr:0, button:0}, - B: {c: 17, n: "Ctrl", plyr:0, button:1}, - GP_A: {c: 88, n: "X", plyr:0, button:0}, - GP_B: {c: 90, n: "Z", plyr:0, button:1}, + A: {c: 32, n: "Space", plyr:0, button:0}, + B: {c: 17, n: "Ctrl", plyr:0, button:1}, + GP_A: {c: 88, n: "X", plyr:0, button:0}, + GP_B: {c: 90, n: "Z", plyr:0, button:1}, SELECT: {c: 220, n: "\\", plyr:0, button:8}, START: {c: 13, n: "Enter", plyr:0, button:9}, // gamepad and keyboard (player 1) @@ -344,8 +352,8 @@ export const Keys : {[keycode:string]:KeyDef} = { P2_DOWN: {c: 83, n: "S", plyr:1, yaxis:1}, P2_LEFT: {c: 65, n: "A", plyr:1, xaxis:-1}, P2_RIGHT: {c: 68, n: "D", plyr:1, xaxis:1}, - P2_A: {c: 84, n: "T", plyr:1, button:0}, - P2_B: {c: 82, n: "R", plyr:1, button:1}, + P2_A: {c: 84, n: "T", plyr:1, button:0}, + P2_B: {c: 82, n: "R", plyr:1, button:1}, P2_SELECT: {c: 70, n: "F", plyr:1, button:8}, P2_START: {c: 71, n: "G", plyr:1, button:9}, // keyboard only @@ -459,9 +467,11 @@ function _metakeyflags(e) { (e.metaKey?KeyFlags.Meta:0); } -export function setKeyboardFromMap(video, switches, map, func?) { - video.setKeyboardEvents(function(key,code,flags) { - var o = map[key]; +type KeyMapFunction = (o:KeyMapEntry, key:number, code:number, flags:number) => void; + +export function setKeyboardFromMap(video:RasterVideo, switches:number[]|Uint8Array, map:KeyCodeMap, func?:KeyMapFunction) { + var handler = (key,code,flags) => { + var o : KeyMapEntry = map[key]; if (o && func) { func(o, key, code, flags); } @@ -479,18 +489,92 @@ export function setKeyboardFromMap(video, switches, map, func?) { switches[o.index] &= ~mask; } } - }); + }; + video.setKeyboardEvents(handler); + return new ControllerPoller(map, handler); } -export function makeKeycodeMap(table : [KeyDef,number,number][]) { - var map = new Map(); +export function makeKeycodeMap(table : [KeyDef,number,number][]) : KeyCodeMap { + var map = new Map(); for (var i=0; i void) { + this.map = map; + this.handler = handler; + window.addEventListener("gamepadconnected", (event) => { + console.log("Gamepad connected:", event); + this.active = typeof navigator.getGamepads === 'function'; + }); + window.addEventListener("gamepaddisconnected", (event) => { + console.log("Gamepad disconnected:", event); + }); + } + poll() { + if (!this.active) return; + var gamepads = navigator.getGamepads(); + for (var gpi=0; gpi len) { throw Error("Data too long, " + data.length + " > " + len); diff --git a/src/platform/astrocade.ts b/src/platform/astrocade.ts index 77dffe66..6fe71e75 100644 --- a/src/platform/astrocade.ts +++ b/src/platform/astrocade.ts @@ -201,6 +201,7 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { class BallyAstrocadePlatform extends BaseZ80Platform implements Platform { scanline : number; + poller; getPresets() { return ASTROCADE_PRESETS; @@ -328,7 +329,7 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { video.create(); video.setupMouseEvents(); var idata = video.getFrameData(); - setKeyboardFromMap(video, inputs, ASTROCADE_KEYCODE_MAP); + this.poller = setKeyboardFromMap(video, inputs, ASTROCADE_KEYCODE_MAP); pixels = video.getFrameData(); timer = new AnimationTimer(60, this.nextFrame.bind(this)); // default palette @@ -345,6 +346,8 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) { inputs[0x1d] = video.paddle_y & 0xff; } + pollControls() { this.poller.poll(); } + advance(novideo : boolean) { this.scanline = 0; var extra = 0; // keep track of spare cycles diff --git a/src/platform/coleco.ts b/src/platform/coleco.ts index 4201b519..659f4759 100644 --- a/src/platform/coleco.ts +++ b/src/platform/coleco.ts @@ -72,6 +72,7 @@ const _ColecoVisionPlatform = function(mainElement) { var audio, psg; var inputs = new Uint8Array(4); var keypadMode = false; + var poller; class ColecoVisionPlatform extends BaseZ80Platform implements Platform { @@ -137,9 +138,11 @@ const _ColecoVisionPlatform = function(mainElement) { } }; vdp = new TMS9918A(video.getFrameData(), cru, true); // true = 4 sprites/line - setKeyboardFromMap(video, inputs, COLECOVISION_KEYCODE_MAP); + poller = setKeyboardFromMap(video, inputs, COLECOVISION_KEYCODE_MAP); timer = new AnimationTimer(60, this.nextFrame.bind(this)); } + + pollControls() { poller.poll(); } readAddress(addr) { return membus.read(addr); diff --git a/src/platform/galaxian.ts b/src/platform/galaxian.ts index 312ae607..87b4d078 100644 --- a/src/platform/galaxian.ts +++ b/src/platform/galaxian.ts @@ -203,6 +203,7 @@ const _GalaxianPlatform = function(mainElement, options) { class GalaxianPlatform extends BaseZ80Platform implements Platform { scanline : number; + poller; getPresets() { return GALAXIAN_PRESETS; @@ -295,10 +296,12 @@ const _GalaxianPlatform = function(mainElement, options) { video = new RasterVideo(mainElement,264,264,{rotate:90}); video.create(); var idata = video.getFrameData(); - setKeyboardFromMap(video, inputs, keyMap); + this.poller = setKeyboardFromMap(video, inputs, keyMap); pixels = video.getFrameData(); timer = new AnimationTimer(60, this.nextFrame.bind(this)); } + + pollControls() { this.poller.poll(); } readAddress(a) { return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog diff --git a/src/platform/nes.ts b/src/platform/nes.ts index 80d4bbff..e25a6239 100644 --- a/src/platform/nes.ts +++ b/src/platform/nes.ts @@ -1,7 +1,7 @@ "use strict"; import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502, dumpStackToString, ProfilerOutput } from "../baseplatform"; -import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, KeyFlags, EmuHalt } from "../emu"; +import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, KeyFlags, EmuHalt, ControllerPoller } from "../emu"; import { hex, lpad, lzgmini, byteArrayToString } from "../util"; import { CodeAnalyzer_nes } from "../analysis"; import { SampleAudio } from "../audio"; @@ -71,6 +71,7 @@ class JSNESPlatform extends Base6502Platform implements Platform { video; audio; timer; + poller : ControllerPoller; audioFrequency = 44030; //44100 frameindex = 0; ntvideo; @@ -136,7 +137,7 @@ class JSNESPlatform extends Base6502Platform implements Platform { } this.timer = new AnimationTimer(60, this.nextFrame.bind(this)); // set keyboard map - setKeyboardFromMap(this.video, [], JSNES_KEYCODE_MAP, (o,key,code,flags) => { + this.poller = setKeyboardFromMap(this.video, [], JSNES_KEYCODE_MAP, (o,key,code,flags) => { if (flags & KeyFlags.KeyDown) this.nes.buttonDown(o.index+1, o.mask); // controller, button else if (flags & KeyFlags.KeyUp) @@ -144,6 +145,8 @@ class JSNESPlatform extends Base6502Platform implements Platform { }); //var s = ''; nes.ppu.palTable.curTable.forEach((rgb) => { s += "0x"+hex(rgb,6)+", "; }); console.log(s); } + + pollControls() { this.poller.poll(); } advance(novideo : boolean) { this.nes.frame(); diff --git a/src/platform/verilog.ts b/src/platform/verilog.ts index cf964935..9b57fa26 100644 --- a/src/platform/verilog.ts +++ b/src/platform/verilog.ts @@ -223,6 +223,7 @@ var VerilogPlatform = function(mainElement, options) { this.__proto__ = new (BasePlatform as any)(); var video, audio; + var poller; var useAudio = false; var videoWidth = 292; var videoHeight = 256; @@ -327,7 +328,7 @@ var VerilogPlatform = function(mainElement, options) { ctx.font = "8px TinyFont"; ctx.fillStyle = "white"; ctx.textAlign = "left"; - setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP); + poller = setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP); var vcanvas = $(video.canvas); idata = video.getFrameData(); timerCallback = () => { @@ -376,6 +377,8 @@ var VerilogPlatform = function(mainElement, options) { }); } + // TODO: pollControls() { poller.poll(); } + resize() { if (this.waveview) this.waveview.recreate(); }