SearchA-ZJ › JSPCE

JSPCE

2019 Open source · MIT Online

JSPCE is a NEC PC Engine / TurboGrafx-16 emulator written entirely in JavaScript by yhzmr442, running in-browser with an HTML5 canvas. The whole console, the HuC6280 CPU, the HuC6270 video display controller, the colour encoder and the sound generator, is a single readable class, which makes it a compact, fully inspectable HuCard machine.

Visit the project on GitHub ↗

Visit the official site ↗

Runs on: Web browser

JSPCE Online Emulator

Play JSPCE using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
Cross ChaseJSPCENEC PC EngineopenOpen ⛶
PC Engine Sound DemoJSPCENEC PC EngineopenOpen ⛶
PC Engine Test ROMJSPCENEC PC EngineopenOpen ⛶
PC Engine Test ROM 3JSPCENEC PC EngineopenOpen ⛶

Machines emulated

Chips

Notes

Embedding

jspce is a single-file, pure-JavaScript emulator: the whole PC Engine is one PCE class. Vendor PCE.js, give it a <canvas>, hand it a ROM as a byte array, then drive it yourself instead of calling its demo driver — that owns a requestAnimationFrame loop the debugger can't pause or step.

Boot and load a HuCard ROM. Construct the machine, point it at a canvas, then feed SetROM the .pce bytes as an ordinary array:

var pce = new PCE();
pce.SetCanvas("screen");                 // a <canvas> id; builds the 684×262 framebuffer
var buf = await (await fetch(romUrl)).arrayBuffer();
var rom = Array.from(new Uint8Array(buf));
pce.SetROM(rom);                        // Init + install the mapper + reset from $FFFE
(function loop(){ pce.Run(); requestAnimationFrame(loop); })();   // Run() = one video frame

The machine is one plain object. Everything the debugger needs is a field or method on the PCE instance — no wasm heap to reach into:

MemberKindWhat it does
Run()methodEmulate one video frame — steps the CPU, VDC, timer and PSG until the frame is drawn to the canvas.
CPURun()methodExecute exactly one HuC6280 instruction (updating ProgressClock with its cycle count). The single-step primitive.
VDCRun(), TimerRun(), PSGRun()methodAdvance the video, timer and sound units by the cycles the last instruction took.
SetROM(bytes)methodInstall a HuCard ROM (a byte array), select the mapper and reset the CPU.
Reset()methodSoft-reset the machine (the console reset button).
A X Y PC S PfieldsThe live HuC6280 registers — accumulator, index X/Y, program counter, stack pointer and processor-status byte, all plain numbers.
Get(a) / Set(a, v)methodRead / write the CPU bus at a logical 16-bit address, resolved through the eight MPR paging registers.
MPRfieldThe eight memory-paging registers: each maps an 8 KB logical bank to a physical 21-bit address.
RAM, Mapper.ROM, VDC[0].VRAMfieldsThe work RAM, the HuCard ROM image, and the VDC's 64 K-word video RAM — all ordinary arrays.
SetButtonI/II/UP/DOWN/…(pad)methodPress a pad button; the Unset… pair releases it. What an on-screen gamepad calls (pad = 0 for player 1).

Because the CPU, bus and RAM are plain JavaScript, a host page can single-step, read and poke memory, and drive the pad straight from the instance.

Debugger integration

The debugger plug-in (jspce-debug.js) reads the live PCE instance from window.EMU_BOOT and wires it to the shared framework.

  • CPU. The HuC6280 is a 65C02 superset, so the plug-in reuses the shared mos6502 decoder. The 6502/65C02 base opcodes disassemble correctly; the HuC6280-only ones — the block-transfer instructions (TAI TII TDD TIA TIN), ST0/ST1/ST2, the TAM/TMA bank swaps and the 65C02 additions — fall through to .byte $xx.
  • Registers are the plain fields A X Y PC S P, read live each refresh and written straight back; the status flags N V T B D I Z C are bit-masks of P (note the HuC6280's T flag at bit 5).
  • Memory. The CPU-bus view resolves each logical address through the eight MPR registers and reads only backing stores, skipping the I/O windows so auto-polling never trips a side effect. Separate views expose the work RAM, the HuCard ROM and the VDC video RAM.
  • Single step is one CPURun() plus the peripheral catch-up; breakpoints check PC before each instruction and watchpoints wrap Set() to catch a watched write — both host-side, with no change to the emulator core.

Architecture

jspce is a compact, readable PC Engine emulator. Unusually, the entire machine is one PCE class whose methods are grouped by subsystem:

  • HuC6280 CPU — a 65C02-superset core; CPURun() executes one instruction, Get/Set route the bus through the eight MPR paging registers into a 21-bit physical space (ROM, RAM, BRAM and the hardware I/O ports).
  • HuC6270 VDC — the video display controller with its own 64 K-word VRAM, drawing background tiles and sprites line-by-line into the canvas framebuffer.
  • VCE — the colour encoder, holding the palette that turns VDC pixels into RGB.
  • PSG — the six-channel wavetable sound generator.
  • Timer and Joystick — the interrupt timer and the pad interface (Run/Select/I/II and the D-pad).
  • Mapper — the HuCard bank layout, selected from the ROM size when SetROM installs it.

Each Run() steps the CPU and clocks the VDC, timer and PSG in lockstep until a whole frame is drawn — a complete, inspectable console with a HuCard as the only thing to load.

Sound

jspce already emulates the HuC6280 PSG — the six-channel wavetable generator — and plays it through its own WebAudio graph, built in SoundInit: a ScriptProcessor whose callback drains the mixed sample queue into a GainNode that feeds AudioContext.destination. Because a working pipeline is already there, the embed does not re-route the samples through the shared sink; it simply gates the core's native pipeline (Pattern V-native).

A small audioApply() in the boot script owns the mute state and drives the core's own context and gain. The PSG's per-sample mixer (SoundSet) writes the master field WaveVolume into the gain node every block, so that field is the lever for muting rather than a raw gain.value that would be overwritten:

function audioApply() {
  var ctx = pce.WebAudioCtx;
  if (!ctx) return;
  if (_muted) {
    pce.WaveVolume = 0;
    pce.WebAudioGainNode.gain.value = 0;
    ctx.suspend();               // stop the core's own AudioContext
  } else {
    pce.WaveVolume = 1.0;
    pce.WebAudioGainNode.gain.value = 1.0;
    ctx.resume();                // resume it from the button's real click
  }
}

The transport exposes isMuted and setMute, which the shared shell renders as the Mute button. Boot starts silent: audioApply() runs right after SetROM (which, through InitPSGInitSoundInit, is what creates the context), so the context is suspended and the gain is zero until the first user gesture unmutes it. The shared /debugger/src/audio.js sink is deliberately not loaded — this core owns its own output.

Sound support. The HuC6280 PSG is fully emulated and plays whenever the running HuCard programs it; the page starts muted, so click Sound to enable it. The default is a Test ROM (video-only), while the bundled PC Engine Sound Demo HuCard (8 KB, hand-assembled HuC6280) drives the chip: it maps the hardware I/O page (MPR0=$FF) and the work RAM (MPR1=$F8), loads a 32-sample sine wavetable into PSG channel 0, sets the main and channel volumes to full, and loops a short C-major arpeggio by rewriting the 12-bit frequency registers ($0802/$0803) with a delay between notes.