jsbeeb - JavaScript BBC Micro Emulator
A BBC Micro and other 8-bit Acorn emulator written in JavaScript and running in modern browsers. Emulates a 32K BBC B (with sideways RAM), a 128K BBC Master, and an Acorn Atom (with AtoMMC2 SD card interface), along with a number of different peripherals.
Table of Contents
- Keyboard Mappings
- Remapping Keys
- Keyboard Layouts
- Discs and Tapes
- Emulator Shortcuts
- Printer Output
- Save State and Rewind
- Joystick Support
- Getting Set Up to Run Locally
- Running as a Desktop Application
- URL Parameters
- Patches
- Loading BASIC Files from GitHub Gists
- Things Left to Do
- Tests
- Thanks
- More Information
- License
- Contact
Keyboard Mappings
The BBC had a somewhat different-looking keyboard to a modern PC, and so it's useful to know some of the mappings:
- BBC
F0isF10 - BBC
Breakkey isF12 - BBC
*is on"(if it doesn't work for you try shift-2)
Remapping Keys
Plenty of games use keys that are awkward on a modern keyboard: COPY (which is End, or fn+→ on a Mac), or
CAPS LOCK (which on a Mac toggles rather than acting as a key you hold down). Any host key can be made to press any
BBC key by adding a KEY. parameter to the URL:
KEY.<host key>=<BBC key>
Add one for each key you want to change. For example, Superior Software's Space Invaders fires with COPY; this makes
Enter fire instead:
https://bbc.xania.org/?disc1=sth:Superior/SpaceInvaders-Superior.zip&autoboot&KEY.ENTER=COPY
Superior's Frogger uses A/Z/DELETE/COPY to move; this puts it on the arrow keys:
And Superior's Hunchback steers with CAPS LOCK and CTRL, which the arrow keys can stand in for:
The host key names are jsbeeb's names for the keys on your own keyboard. A host key is identified by where it sits, not by what it types, so these work the same on a Dvorak, AZERTY or Hungarian layout as on a QWERTY one. Most names are what you'd expect, but note:
ENTER(the BBC'sRETURNkey is calledENTERon the host side)K0toK9for the number keys,NUMPAD0toNUMPAD9for the keypadSHIFT_LEFT/SHIFT_RIGHT,CTRL_LEFT/CTRL_RIGHT,ALT_LEFT/ALT_RIGHTto distinguish the two of each.
SHIFT, CTRL and ALT map both sides at once
WINDOWSandWINDOWS_RIGHTfor the Windows or Command keys, andCLEARfor the key an Apple
BACK_QUOTE,APOSTROPHE,SEMICOLON,MINUS,EQUALS,BACKSLASH,LEFT_SQUARE_BRACKET,
RIGHT_SQUARE_BRACKET for punctuation
BACKSLASHis the key left ofEnteron a UK keyboard (printed#~) and the one above it on a US keyboard
\|); they are the same physical key. HASH is accepted as another name for it. INTL_BACKSLASH is the
extra key between the left shift and the Z that only a 102-key keyboard has
The BBC key names are:
RETURN COPY DELETE ESCAPE TAB SPACE SHIFT SHIFTLOCK CAPSLOCK CTRL
LEFT RIGHT UP DOWN
A-Z, K0-K9 (the number keys), F0-F9 (the red function keys)
SEMICOLON_PLUS MINUS COMMA PERIOD SLASH AT COLON_STAR HAT_TILDE
UNDERSCORE_POUND PIPE_BACKSLASH LEFT_SQUARE_BRACKET RIGHT_SQUARE_BRACKET
(and, on the Master's numeric keypad only)
NUMPAD0-NUMPAD9 NUMPADPLUS NUMPADMINUS NUMPADSLASH NUMPADASTERISK NUMPADCOMMA
NUMPADHASH NUMPADENTER NUMPAD_DELETE NUMPAD_DECIMAL_POINT
Some things to know:
- Names are case-insensitive, and a remapped key ignores the
SHIFTstate, soKEY.ENTER=COPYpressesCOPYwhether
- Any host key can be named here, including ones no layout uses by default, such as
PRINTSCREEN,SCROLL_LOCKand
WINDOWS_RIGHT. Naming one is the only way to make it press anything.
- Remapping replaces what that host key normally does; in the Space Invaders example above,
Enterno longer presses
RETURN.
- The remapping is applied on top of whichever keyboard layout is selected, and survives changing layout or model.
- If a name isn't recognised the mapping is skipped, and the emulator says so on startup, naming the parameter that
- On the Atom, use the Atom's own key names (
LOCK,UP_DOWN,LEFT_RIGHTand so on) rather than the BBC's.
keyCodes with keyCodeAliases (host) and BBC (BBC micro) in
src/keymap.js, and ATOM in
src/keymap-atom.js.
Keyboard Layouts
A BBC keyboard is not a PC keyboard, so Keyboard on the top bar offers three ways to bridge the
two. The choice is remembered, and ?keyLayout=physical, ?keyLayout=natural or ?keyLayout=gaming
sets it from a link.
Physical is the default, and the one to use for games. Each key presses the BBC key in the same
place on the keyboard, so Z is Z and the key to the right of the L is the BBC's : and *,
whatever your keyboard is printed with. A Dvorak or Hungarian keyboard works the same as a UK one,
because only position matters.
Natural is for typing. Each key presses whatever the BBC needs to print the character your
keyboard produces, so a @ gives a @ and a gives a , again whatever your layout. The BBC
holds shift for a different set of characters than a PC does, which this deals with: ^ is
unshifted on a BBC and " is shifted, and you do not have to know that.
Two keys are exceptions, because characters alone cannot settle them. The BBC has no backtick and
most keyboards cannot type a £ at all, so the key that would print a backtick gives a £
instead. And the Master's numeric keypad is a separate set of keys from the digits above the
letters, which the characters cannot tell apart, so the keypad goes by position.
Gaming moves the BBC keys that a PC keyboard handles badly. Its main job is the BBC's CAPS LOCK
and CTRL, which sit side by side and which many games use for left and right: Zalaga and Hunchback
both do. On a PC those two are a row apart and diagonal, so this layout puts them on the left Ctrl
and left Alt instead, which are side by side under one hand. The cost is that jsbeeb's own Alt
shortcuts win over those keys, so while this layout is chosen you cannot hold the BBC's CTRL and
press a letter or digit that jsbeeb has claimed.
Whichever you choose, a KEY. parameter overrides individual keys on top of it, and survives
changing layout.
Discs and Tapes
Media on the top bar opens the media window: two disc drives and a cassette deck showing what is loaded, and one searchable list of everything you can load: the built-in examples, the Stairway to Hell archive, the HFE archive of flux captures, the demos and games Bitshifters publish (each row links to its page, and picking one that needs a Master 128 switches the emulator to one: without asking when it is to boot, after asking otherwise), your Google Drive once connected, discs kept in this browser, and files opened this session. Type to search, Enter loads the best match into the aimed drive, Shift+Enter loads it, ticks Autoboot and boots it, and the arrows walk the rows. Aim at a drive or the deck with the Into control, by clicking a slot, or from its line in the LED panel under the screen. Each drive front has its eject latch, its 40/80 track switch (which pins the drive, so drive0Tracks= follows it in the URL), a Save menu to download the disc or copy it to Google Drive, and Surface to open the disc visualiser. The footer opens a file from this computer, makes a blank disc in this browser or on Google Drive, and connects Google Drive. docs/media-sources.md says how the sources fit together and what adding one takes.
Emulator Shortcuts
| Shortcut | Action |
| ---------------- | ------------------------------------------ |
| Alt-S | Enter the debugger, or leave it and resume |
| Alt-P | Pause emulation, or resume |
| Alt-T | Toggle turbo (fast-as-possible) |
| Alt-B | Open printer output window |
| Alt-W | Open rewind scrubber |
| Alt-M | Media window, aimed at drive 0 |
| Alt-Shift-M | Media window, aimed at drive 1 |
| Alt-C | Media window, aimed at the cassette |
| Alt-1 to Alt-8 | Hold an accessibility switch down |
Every shortcut is on Alt, so that Ctrl belongs to the emulated machine: Ctrl-B is VDU 2 on a BBC, Ctrl-L
clears the screen, and jsbeeb should not be taking any of them.
Printer Output
Anything the machine prints is captured whether or not the printer window is open, so programs that print (with VDU 2, *FX5,1 and the like) run rather than waiting for a printer that is not there.
Press Alt-B to open a window showing what has been printed so far; it then keeps up with the output as it arrives. Only the most recent output is kept, roughly a dozen pages' worth, so a program printing forever cannot fill memory.
Save State and Rewind
Save and load full emulator state snapshots from the State menu (or Ctrl+S / Ctrl+O in the Electron app).
The emulator continuously captures snapshots into a 30-slot rewind buffer (~1 per second). Open the rewind scrubber from State > Rewind or press Alt-W to browse recent states as a visual filmstrip:
- Left/Right arrows: navigate between snapshots (the main screen updates live)
- Click a thumbnail to jump to that point
- Enter: commit selection and close the panel
- Escape: cancel and restore the original state
Joystick Support
jsbeeb supports both USB/Bluetooth gamepads and mouse-based analogue joystick emulation. Note that BBC Micro joysticks use inverted axes:
- X-axis: Left = 65535, Right = 0
- Y-axis: Up = 65535, Down = 0
GP.<gamepad control>=<BBC key>
By default the D-pad presses the "Snapper" keys (Z, X, :, /), the A button presses RETURN and Start
presses SPACE. To play Superior's Space Invaders on a pad, where COPY fires:
https://bbc.xania.org/?disc1=sth:Superior/SpaceInvaders-Superior.zip&autoboot&GP.FIRE=COPY
The gamepad control names are:
FIRE: every button at once, which is usually what you want for a one-button gameUPDOWNLEFTRIGHT: both analogue sticks at once, plus one face button each (UPis alsoA,DOWNis
X, LEFT is Y, RIGHT is B)
UP1DOWN1LEFT1RIGHT1: the left stick only;UP2DOWN2and so on: the right stick only;UP3DOWN3
ABXYSTARTBACKLBRBLTRT: individual buttons, by their Xbox 360 namesFIRE1FIRE2: clicking the left and right sticks
GP.A=1 and GP.A=K1
both press 1. Unlike KEY., gamepad mappings are BBC-only: there's no Atom equivalent. The D-pad's default mapping
can't currently be changed.
The older LEFT=, RIGHT=, UP=, DOWN= and FIRE= parameters (no GP. prefix) still work and mean the same
thing.
Getting Set Up to Run Locally
Prerequisites
- Node.js (https://nodejs.org/)
- npm (comes with Node.js)
Installation
- Clone the repository:
git clone https://github.com/mattgodbolt/jsbeeb.git
cd jsbeeb
- Install dependencies:
npm install
- Start the local webserver:
npm start
- Visit
http://localhost:5173/in your browser.
Running as a Desktop Application
jsbeeb can also run as a standalone desktop application using Electron. Prebuilt packages (Debian/Ubuntu .deb,
Fedora/RHEL .rpm and a Windows installer) are attached to each
GitHub release, or you can build your own:
Running in Development
npm run electron
This automatically builds the latest code before launching Electron.
Building Distributable Packages
To build packages for Linux distribution:
npm run build
npm run electron:build
This creates two package formats in out/dist/:
- Debian/Ubuntu:
.debpackage - Fedora/RHEL:
.rpmpackage
gnome-3-28-1804 platform (Ubuntu 18.04), which causes GPU driver incompatibilities on modern systems, resulting in MESA loader failures and segfaults. While we were able to work around the initial Wayland issues (electron-builder sets DISABLE_WAYLAND=1 by default, fixed with allowNativeWayland: true), the GPU problems proved insurmountable. The snap builder hasn't been updated to support modern bases like core22 or core24. The .deb package works perfectly on all Debian-based systems.
Note for Ubuntu/Debian users: If you encounter RPM build errors, you may need to use the system FPM package manager instead of electron-builder's bundled version. First, install the required dependencies:
sudo apt-get install ruby rubygems build-essential
sudo gem install fpm
Then build with:
USE_SYSTEM_FPM=true npm run electron:build
Installing the Packaged Application
Debian/Ubuntu:
sudo apt install ./out/dist/jsbeeb_<version>_amd64.deb
Fedora/RHEL/CentOS:
sudo rpm -i out/dist/jsbeeb-<version>.x86_64.rpm
URL Parameters
autoboot- fakes a shift breakdisc1=XXX- loads disc XXX (from thediscs/directory) into drive 0disc2=XXX- as above, into drive 1disc1=local:YYY- creates a local disk YYY which will be kept in browser local storagedisc1=sth:ZZZ- loads disc ZZZ from the Stairway to Hell archivedisc1=hfe:ZZZ- loads disc ZZZ from the HFE archive of flux capturesdisc1=bitshifters:ZZZ- loads disc ZZZ from Bitshifters, e.g.
bitshifters:bs-paradroid.ssd
drive0Tracks=40/drive0Tracks=80- fixes drive 0's 40/80 track switch, as the switch on the back of a real
drive1Tracks does the same for drive 1. Left alone, each drive follows whatever disc is loaded into it:
a 40 track image is laid out the way a 40 track drive wrote it, on every other track of the surface, and the drive
double steps to read it. 40 reads an 80 track disc through a double stepping head, which is as much of a mess as it
was in 1985. 80 turns all of this off for that drive, loading every image the way jsbeeb did before it could tell
them apart. docs/disc-track-layouts.md explains how an image's layout is worked out.
tape=XXX- loads tape XXX (from thetapes/directory)tape=sth:ZZZ- loads tape ZZZ from the Stairway to Hell archiveKEY.X=Y- makes host keyXpress BBC keyY, e.g.KEY.ENTER=COPY. See
patch=P- applies a memory patchP. See below.loadBasic=X- loads 'X' (a resource on the webserver) as text, tokenises it and puts it inPAGEas if you'd typed
embedBasic=X- loads 'X' (a URI-encoded string) as text, tokenises it and puts it inPAGEas if you'd typed it in
autorun- typesTAPEthen/to run from tape. In conjunction withloadBasicit typesRUN.autochain- types*TAPEthenCH.""to run from tape.autotype- types whatever you put after. e.g.&autotype=PRINT"Matt is cool"%0a(return is URI escaped to%0a)embed- Remove the margins around the screen, hide most navigation entries and make the page background
cpuMultiplier=Xspeeds up the CPU by a factor ofXrelative to the peripherals: video, sound and the VIAs keep
tubeCpuMultiplier=Xoverclocks the second processor by a factor ofX, which may be fractional.1, the default,
sbLeft/sbRight/sbBottom- a URL to place left of, right of, or below the cub monitor. The left and right
videoCyclesBatch- the number of video cycles to batch up before running the video emulation. Defaults to zero:
rom- load the given URL or path as an extra ROM. If a URL is provided, that URL must allow cross-site requests.
disc and tape, but if given a ZIP file will attempt to use the .rom
file assumed to be within.
- (mostly internal use)
logFdcCommands,logFdcStateChanges- turn on logging in the disc controller. noseek- no disc drive noise: the motor, the head's clicks and runs all silent, and nothing scheduled for them.audioDebug- show the audio lead chart, and log one console line per second in which the emulator tick ran late or the sound stalled or skipped.audioLatencyMs- how far the sound runs behind the emulator, in milliseconds (default 20). Raising it lets the sound ride out longer stalls of the emulator, at the cost of lagging the picture by that much.audioOutput- what the sound chip is heard through:speaker(the default: the board's output stage and the
board (the board's output stage alone, as at
its line-level socket) or off (the chip resampled and nothing else). The top bar and the configuration dialog
have the same choice, and remember it. See docs/audio-path.md for the path, the Master's circuit and what a real
machine measures.
speakerAmount- how much of the speaker's character to apply, from 0 (the same asboard) to 1 (as measured,
audiofilterfreq/audiofilterq- the corner frequency in Hz and the Q of the lowpass modelling the board's output
audiofilterfreq=0 turns the whole output path off.
palPersistenceMs/rgbPersistenceMs- the phosphor afterglow of the PAL TV and RGB monitor displays, the time
displayMode=X- picks the display:rgb(the default, a plain monitor),pal(a television fed by the Beeb's UHF modulator)
xbr (an upscaler, see docs/xbr-display-mode.md). The top bar has the same choice, and remembers it.
Atom-specific parameters
model=Atom- select the Acorn Atom (MMC) model. Other Atom variants:Atom-Tape,Atom-Tape-FP,Atom-DOS.mmc=XXX- load an MMC/SD card image (ZIP) for the Atom.
atom (e.g. atom.xania.org)
defaults to the Atom model.
Patches
Patches can be applied by making a patch=P URL parameter. P is a sequence of semicolon-separated patches of the form
@XXXX,YYYY:ZZZZZ,... where the @XXXX specifies a PC address to breakpoint, the YYYY is the address to patch and
the ZZZZ is the data to write at address YYYY. The @ part is optional, but is handy to ensure the code you want to
patch has actually loaded. For example: patch=@31a6,0769:6e4c4d48465a which is a patch for the default Elite image.
Once the PC has reached $31a6, the bytes at 0769 are replaced with 6e4c4d48465a.
Loading BASIC Files from GitHub Gists
- Create a gist with your code. https://gist.github.com/ - here's
- Get the "Raw" link by clicking "raw" and copying the URL. In the case above
- Add that after "https://bbc.xania.org/?autorun&loadBasic=" or similar, for
Note that every update you make means you need to make a new raw link.
Things Left to Do
If you're looking to help:
- Play lots of games and report anything that doesn't behave like the real machine, either on
- Pick something from the issue tracker: there's a mix of emulation
Tests
For general correctness, there are several tests in the tests directory, including:
- Klaus Dormann's exhaustive test of all documented opcodes
- hoglet's Binary Coded Decimal tests.
- @dp111's timing tests. Also brought in as a git submodule.
- A public domain Commodore 64 6502 test suite which tests every 6502 opcode (documented or otherwise) for every
- Some tests by @scarybeasts testing VIA and 65C12 functionality.
- A timing test program written by Rich. It has been run on a real live BBC B and the results are in the directory. An
discs/ directory.
- Some of Kevin Edwards' protection systems (stripped of the games themselves). These are extremely timing- and
- Some 65C12-specific read-modify-write tests written by Ed Spittles.
npm test runs the whole suite. The vitest suites are npm run test:unit,
npm run test:integration and npm run test:shader; CPU tests are npm run test:cpu, and the smoke test is
npm run test:smoke (the shader and smoke tests need Chrome installed). Please note it can take a while to run the
whole test suite.
Thanks
jsbeeb was heavily based on Sarah Walker's C B-Em emulator; thanks to her for her hard work and for open sourcing her code. B-em is now being maintained by a group of enthusiasts - thanks to them too!
Huge thanks to Richard Talbot-Watkins for his advice and help along the way in fathoming out the instruction timings, interrupt fun, video code rewrite and for being such a good pal all these many years!
Thanks to Michael Borcherds for his help; improving the keyboard layouts and handling in JavaScript, reporting issues, chasing down game bugs and much more.
Thanks to David Banks (hoglet) for his help in testing the gnarly BCD flag behaviour on real live BBCs.
Cheers to Ed Spittles for testing various interrupt timing code on a real BBC.
Thanks to Chris Jordan for his thorough testing, bug reports, ideas and help.
Huge thanks to Andrew Hague (CommanderCoder) for the Acorn Atom emulation support. Andrew developed the original Atom implementation including the MC6847 video chip, 8255 PPIA, AtoMMC2 SD card interface, Atom keyboard mapping, tape support, and speaker output. His work in PR #505 was incrementally merged and refined into the codebase.
A lot of the early development used the amazing Visual 6502 as reference for intra-instruction timings. Amazing stuff.
Special shout out to the users of the 6502 Forums
More Information
I've written a lot about how the innards work on my blog in the emulation section. I gave a presentation on how it all fits together at work, and posted the video up on YouTube. I have another presentation at ABug.
License
This project is licensed under the GNU General Public License v3.0 (or later); see the LICENSE file for details.
Contact
For support or questions, please contact Matt Godbolt at [email protected].