jsbeeb 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +15 -0
- package/.git-blame-ignore-revs +3 -0
- package/.github/copilot-instructions.md +94 -0
- package/.github/workflows/claude-issue-triage.yml +105 -0
- package/.github/workflows/claude.yml +63 -0
- package/.github/workflows/release-please.yml +75 -0
- package/.github/workflows/test-and-deploy.yml +86 -0
- package/.gitmodules +6 -0
- package/.husky/pre-commit +1 -0
- package/.idea/codeStyleSettings.xml +9 -0
- package/.idea/codeStyles/Project.xml +62 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/compiler.xml +22 -0
- package/.idea/copyright/profiles_settings.xml +3 -0
- package/.idea/encodings.xml +6 -0
- package/.idea/inspectionProfiles/Project_Default.xml +7 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/jsLinters/jshint.xml +85 -0
- package/.idea/jsLinters/jslint.xml +15 -0
- package/.idea/jsbeeb.iml +11 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +7 -0
- package/.idea/runConfigurations/Debug.xml +5 -0
- package/.idea/scopes/scope_settings.xml +5 -0
- package/.idea/vcs.xml +8 -0
- package/.prettierignore +4 -0
- package/.prettierrc.json +1 -0
- package/.release-please-manifest.json +3 -0
- package/.vscode/launch.json +14 -0
- package/.vscode/settings.json +6 -0
- package/CHANGELOG.md +32 -0
- package/CLAUDE.md +136 -0
- package/COPYING +674 -0
- package/Dockerfile +22 -0
- package/Makefile +30 -0
- package/README.md +259 -0
- package/docker/nginx-default.conf +10 -0
- package/docs/pal-comb-filter-research.md +129 -0
- package/docs/pal-simulation-design.md +368 -0
- package/eslint.config.js +35 -0
- package/index.html +954 -0
- package/jsconfig.json +10 -0
- package/package.json +102 -0
- package/public/discs/README.Irq-Timing +3 -0
- package/public/discs/README.bcdtest +5 -0
- package/public/discs/README.elite +6 -0
- package/public/discs/README.eng_test +3 -0
- package/public/discs/README.protection +7 -0
- package/public/favicon.ico +0 -0
- package/public/images/botbar.png +0 -0
- package/public/images/cub-monitor.png +0 -0
- package/public/images/jsbeeb-example.png +0 -0
- package/public/images/placeholder.png +0 -0
- package/public/images/red-off-16.png +0 -0
- package/public/images/red-on-16.png +0 -0
- package/public/images/sb/CD-left.jpg +0 -0
- package/public/images/sb/CD-right.jpg +0 -0
- package/public/images/sidebar.png +0 -0
- package/public/images/tv.png +0 -0
- package/public/images/yellow-off-16.png +0 -0
- package/public/images/yellow-on-16.png +0 -0
- package/public/jsbeeb-icon.png +0 -0
- package/public/robots.txt +3 -0
- package/public/roms/ADFS1-53.rom +0 -0
- package/public/roms/BASIC.ROM +0 -0
- package/public/roms/README +4 -0
- package/public/roms/a01/BASIC1.rom +0 -0
- package/public/roms/ample.rom +0 -0
- package/public/roms/ats-3.0.rom +0 -0
- package/public/roms/b/DFS-0.9.rom +0 -0
- package/public/roms/b/DFS-1.2.rom +0 -0
- package/public/roms/b1770/dfs1770.rom +0 -0
- package/public/roms/b1770/zADFS.ROM +0 -0
- package/public/roms/bp/dfs.rom +0 -0
- package/public/roms/bp/zADFS.ROM +0 -0
- package/public/roms/bpos.rom +0 -0
- package/public/roms/compact/adfs210.rom +0 -0
- package/public/roms/compact/basic48.rom +0 -0
- package/public/roms/compact/basic486.rom +0 -0
- package/public/roms/compact/os51.rom +0 -0
- package/public/roms/compact/utils.rom +0 -0
- package/public/roms/deos.rom +0 -0
- package/public/roms/master/anfs-4.25.rom +0 -0
- package/public/roms/master/mos.txt +68819 -0
- package/public/roms/master/mos3.20 +0 -0
- package/public/roms/os.rom +0 -0
- package/public/roms/os01.rom +0 -0
- package/public/roms/tube/6502Tube.rom +0 -0
- package/public/roms/tube/ARMeval_100.rom +0 -0
- package/public/roms/tube/BIOS.ROM +0 -0
- package/public/roms/tube/ReCo6502ROM_816 +0 -0
- package/public/roms/tube/Z80_120.rom +0 -0
- package/public/roms/us/USBASIC.rom +0 -0
- package/public/roms/us/USDNFS.rom +0 -0
- package/public/roms/usmos.rom +0 -0
- package/public/sounds/disc525/motor.wav +0 -0
- package/public/sounds/disc525/motoroff.wav +0 -0
- package/public/sounds/disc525/motoron.wav +0 -0
- package/public/sounds/disc525/seek.wav +0 -0
- package/public/sounds/disc525/seek2.wav +0 -0
- package/public/sounds/disc525/seek3.wav +0 -0
- package/public/sounds/disc525/step.wav +0 -0
- package/public/teletext/txt0.dat +0 -0
- package/public/teletext/txt1.dat +0 -0
- package/public/teletext/txt2.dat +0 -0
- package/public/teletext/txt3.dat +0 -0
- package/release-please-config.json +13 -0
- package/run-container.sh +92 -0
- package/src/6502.js +1347 -0
- package/src/6502.opcodes.js +1411 -0
- package/src/acia.js +261 -0
- package/src/adc.js +149 -0
- package/src/analogue-source.js +21 -0
- package/src/app/app.js +175 -0
- package/src/app/electron.js +20 -0
- package/src/app/preload.js +8 -0
- package/src/app-bench.js +33 -0
- package/src/basic/multiline-tetris +9 -0
- package/src/basic-tokenise.js +104 -0
- package/src/canvas.js +177 -0
- package/src/cmos.js +141 -0
- package/src/config.js +165 -0
- package/src/ddnoise.js +138 -0
- package/src/disc-drive.js +371 -0
- package/src/disc-hfe.js +396 -0
- package/src/disc.js +997 -0
- package/src/econet/L3FS.dat +0 -0
- package/src/econet/scsi.dat +0 -0
- package/src/econet.js +714 -0
- package/src/fake6502.js +32 -0
- package/src/fdc.js +248 -0
- package/src/filestore.js +666 -0
- package/src/gamepad-source.js +59 -0
- package/src/gamepads.js +268 -0
- package/src/google-drive.js +160 -0
- package/src/intel-fdc.js +1717 -0
- package/src/jsbeeb.css +363 -0
- package/src/keyboard.js +411 -0
- package/src/lib/README +1 -0
- package/src/lib/webgl-debug.js +911 -0
- package/src/main.js +1759 -0
- package/src/microphone-input.js +149 -0
- package/src/models.js +200 -0
- package/src/mouse-joystick-source.js +107 -0
- package/src/music5000-worklet.js +43 -0
- package/src/music5000.js +207 -0
- package/src/scheduler.js +148 -0
- package/src/serial.js +31 -0
- package/src/soundchip.js +314 -0
- package/src/sth.js +59 -0
- package/src/tapes.js +265 -0
- package/src/teletext.js +348 -0
- package/src/teletext_adaptor.js +172 -0
- package/src/teletext_data.js +1064 -0
- package/src/touchscreen.js +86 -0
- package/src/tube.js +349 -0
- package/src/url-params.js +256 -0
- package/src/utils.js +1090 -0
- package/src/via.js +702 -0
- package/src/video-filters/pal-composite.js +94 -0
- package/src/video-filters/passthrough-filter.js +70 -0
- package/src/video-filters/shaders/pal-composite.frag.glsl +147 -0
- package/src/video-filters/shaders/pal-composite.vert.glsl +8 -0
- package/src/video-filters/shaders/passthrough.frag.glsl +6 -0
- package/src/video-filters/shaders/passthrough.vert.glsl +7 -0
- package/src/video.js +794 -0
- package/src/wd-fdc.js +1344 -0
- package/src/web/audio-handler.js +146 -0
- package/src/web/audio-renderer.js +115 -0
- package/src/web/debug.js +529 -0
- package/tests/integration/RmwX.asm +47 -0
- package/tests/integration/TestInstructionsSource +27 -0
- package/tests/integration/TestTimingsResults +27 -0
- package/tests/integration/TestTimingsSource +61 -0
- package/tests/integration/bcd.js +23 -0
- package/tests/integration/dormann.js +101 -0
- package/tests/integration/dp111timing.js +42 -0
- package/tests/integration/ensure-submodules.js +25 -0
- package/tests/integration/nops.bas +119 -0
- package/tests/integration/nops.js +24 -0
- package/tests/integration/protection.js +26 -0
- package/tests/integration/rmw.js +69 -0
- package/tests/integration/teletext/expected_bug_469.png +0 -0
- package/tests/integration/teletext/expected_flash_0.png +0 -0
- package/tests/integration/teletext/expected_flash_1.png +0 -0
- package/tests/integration/teletext/expected_hoglet_held_char.png +0 -0
- package/tests/integration/teletext/expected_reveal_flash_0.png +0 -0
- package/tests/integration/teletext/expected_reveal_flash_1.png +0 -0
- package/tests/integration/teletext.js +126 -0
- package/tests/integration/timings.js +56 -0
- package/tests/integration/via.js +1125 -0
- package/tests/suite/README.md +7 -0
- package/tests/suite/Test Suite 2.15.txt +373 -0
- package/tests/suite/bin/ start +0 -0
- package/tests/suite/bin/adca +0 -0
- package/tests/suite/bin/adcax +0 -0
- package/tests/suite/bin/adcay +0 -0
- package/tests/suite/bin/adcb +0 -0
- package/tests/suite/bin/adcix +0 -0
- package/tests/suite/bin/adciy +0 -0
- package/tests/suite/bin/adcz +0 -0
- package/tests/suite/bin/adczx +0 -0
- package/tests/suite/bin/alrb +0 -0
- package/tests/suite/bin/ancb +0 -0
- package/tests/suite/bin/anda +0 -0
- package/tests/suite/bin/andax +0 -0
- package/tests/suite/bin/anday +0 -0
- package/tests/suite/bin/andb +0 -0
- package/tests/suite/bin/andix +0 -0
- package/tests/suite/bin/andiy +0 -0
- package/tests/suite/bin/andz +0 -0
- package/tests/suite/bin/andzx +0 -0
- package/tests/suite/bin/aneb +0 -0
- package/tests/suite/bin/arrb +0 -0
- package/tests/suite/bin/asla +0 -0
- package/tests/suite/bin/aslax +0 -0
- package/tests/suite/bin/asln +0 -0
- package/tests/suite/bin/aslz +0 -0
- package/tests/suite/bin/aslzx +0 -0
- package/tests/suite/bin/asoa +0 -0
- package/tests/suite/bin/asoax +0 -0
- package/tests/suite/bin/asoay +0 -0
- package/tests/suite/bin/asoix +0 -0
- package/tests/suite/bin/asoiy +0 -0
- package/tests/suite/bin/asoz +0 -0
- package/tests/suite/bin/asozx +0 -0
- package/tests/suite/bin/axsa +0 -0
- package/tests/suite/bin/axsix +0 -0
- package/tests/suite/bin/axsz +0 -0
- package/tests/suite/bin/axszy +0 -0
- package/tests/suite/bin/bccr +0 -0
- package/tests/suite/bin/bcsr +0 -0
- package/tests/suite/bin/beqr +0 -0
- package/tests/suite/bin/bita +0 -0
- package/tests/suite/bin/bitz +0 -0
- package/tests/suite/bin/bmir +0 -0
- package/tests/suite/bin/bner +0 -0
- package/tests/suite/bin/bplr +0 -0
- package/tests/suite/bin/branchwrap +0 -0
- package/tests/suite/bin/brkn +0 -0
- package/tests/suite/bin/bvcr +0 -0
- package/tests/suite/bin/bvsr +0 -0
- package/tests/suite/bin/cia1pb6 +0 -0
- package/tests/suite/bin/cia1pb7 +0 -0
- package/tests/suite/bin/cia1ta +0 -0
- package/tests/suite/bin/cia1tab +0 -0
- package/tests/suite/bin/cia1tb +0 -0
- package/tests/suite/bin/cia1tb123 +0 -0
- package/tests/suite/bin/cia2pb6 +0 -0
- package/tests/suite/bin/cia2pb7 +0 -0
- package/tests/suite/bin/cia2ta +0 -0
- package/tests/suite/bin/cia2tb +0 -0
- package/tests/suite/bin/cia2tb123 +0 -0
- package/tests/suite/bin/clcn +0 -0
- package/tests/suite/bin/cldn +0 -0
- package/tests/suite/bin/clin +0 -0
- package/tests/suite/bin/clvn +0 -0
- package/tests/suite/bin/cmpa +0 -0
- package/tests/suite/bin/cmpax +0 -0
- package/tests/suite/bin/cmpay +0 -0
- package/tests/suite/bin/cmpb +0 -0
- package/tests/suite/bin/cmpix +0 -0
- package/tests/suite/bin/cmpiy +0 -0
- package/tests/suite/bin/cmpz +0 -0
- package/tests/suite/bin/cmpzx +0 -0
- package/tests/suite/bin/cntdef +0 -0
- package/tests/suite/bin/cnto2 +0 -0
- package/tests/suite/bin/cpuport +0 -0
- package/tests/suite/bin/cputiming +0 -0
- package/tests/suite/bin/cpxa +0 -0
- package/tests/suite/bin/cpxb +0 -0
- package/tests/suite/bin/cpxz +0 -0
- package/tests/suite/bin/cpya +0 -0
- package/tests/suite/bin/cpyb +0 -0
- package/tests/suite/bin/cpyz +0 -0
- package/tests/suite/bin/dcma +0 -0
- package/tests/suite/bin/dcmax +0 -0
- package/tests/suite/bin/dcmay +0 -0
- package/tests/suite/bin/dcmix +0 -0
- package/tests/suite/bin/dcmiy +0 -0
- package/tests/suite/bin/dcmz +0 -0
- package/tests/suite/bin/dcmzx +0 -0
- package/tests/suite/bin/deca +0 -0
- package/tests/suite/bin/decax +0 -0
- package/tests/suite/bin/decz +0 -0
- package/tests/suite/bin/deczx +0 -0
- package/tests/suite/bin/dexn +0 -0
- package/tests/suite/bin/deyn +0 -0
- package/tests/suite/bin/eora +0 -0
- package/tests/suite/bin/eorax +0 -0
- package/tests/suite/bin/eoray +0 -0
- package/tests/suite/bin/eorb +0 -0
- package/tests/suite/bin/eorix +0 -0
- package/tests/suite/bin/eoriy +0 -0
- package/tests/suite/bin/eorz +0 -0
- package/tests/suite/bin/eorzx +0 -0
- package/tests/suite/bin/finish +0 -0
- package/tests/suite/bin/flipos +0 -0
- package/tests/suite/bin/icr01 +0 -0
- package/tests/suite/bin/imr +0 -0
- package/tests/suite/bin/inca +0 -0
- package/tests/suite/bin/incax +0 -0
- package/tests/suite/bin/incz +0 -0
- package/tests/suite/bin/inczx +0 -0
- package/tests/suite/bin/insa +0 -0
- package/tests/suite/bin/insax +0 -0
- package/tests/suite/bin/insay +0 -0
- package/tests/suite/bin/insix +0 -0
- package/tests/suite/bin/insiy +0 -0
- package/tests/suite/bin/insz +0 -0
- package/tests/suite/bin/inszx +0 -0
- package/tests/suite/bin/inxn +0 -0
- package/tests/suite/bin/inyn +0 -0
- package/tests/suite/bin/irq +0 -0
- package/tests/suite/bin/jmpi +0 -0
- package/tests/suite/bin/jmpw +0 -0
- package/tests/suite/bin/jsrw +0 -0
- package/tests/suite/bin/lasay +0 -0
- package/tests/suite/bin/laxa +0 -0
- package/tests/suite/bin/laxay +0 -0
- package/tests/suite/bin/laxix +0 -0
- package/tests/suite/bin/laxiy +0 -0
- package/tests/suite/bin/laxz +0 -0
- package/tests/suite/bin/laxzy +0 -0
- package/tests/suite/bin/ldaa +0 -0
- package/tests/suite/bin/ldaax +0 -0
- package/tests/suite/bin/ldaay +0 -0
- package/tests/suite/bin/ldab +0 -0
- package/tests/suite/bin/ldaix +0 -0
- package/tests/suite/bin/ldaiy +0 -0
- package/tests/suite/bin/ldaz +0 -0
- package/tests/suite/bin/ldazx +0 -0
- package/tests/suite/bin/ldxa +0 -0
- package/tests/suite/bin/ldxay +0 -0
- package/tests/suite/bin/ldxb +0 -0
- package/tests/suite/bin/ldxz +0 -0
- package/tests/suite/bin/ldxzy +0 -0
- package/tests/suite/bin/ldya +0 -0
- package/tests/suite/bin/ldyax +0 -0
- package/tests/suite/bin/ldyb +0 -0
- package/tests/suite/bin/ldyz +0 -0
- package/tests/suite/bin/ldyzx +0 -0
- package/tests/suite/bin/loadth +0 -0
- package/tests/suite/bin/lsea +0 -0
- package/tests/suite/bin/lseax +0 -0
- package/tests/suite/bin/lseay +0 -0
- package/tests/suite/bin/lseix +0 -0
- package/tests/suite/bin/lseiy +0 -0
- package/tests/suite/bin/lsez +0 -0
- package/tests/suite/bin/lsezx +0 -0
- package/tests/suite/bin/lsra +0 -0
- package/tests/suite/bin/lsrax +0 -0
- package/tests/suite/bin/lsrn +0 -0
- package/tests/suite/bin/lsrz +0 -0
- package/tests/suite/bin/lsrzx +0 -0
- package/tests/suite/bin/lxab +0 -0
- package/tests/suite/bin/mmu +0 -0
- package/tests/suite/bin/mmufetch +0 -0
- package/tests/suite/bin/nmi +0 -0
- package/tests/suite/bin/nopa +0 -0
- package/tests/suite/bin/nopax +0 -0
- package/tests/suite/bin/nopb +0 -0
- package/tests/suite/bin/nopn +0 -0
- package/tests/suite/bin/nopz +0 -0
- package/tests/suite/bin/nopzx +0 -0
- package/tests/suite/bin/oneshot +0 -0
- package/tests/suite/bin/oraa +0 -0
- package/tests/suite/bin/oraax +0 -0
- package/tests/suite/bin/oraay +0 -0
- package/tests/suite/bin/orab +0 -0
- package/tests/suite/bin/oraix +0 -0
- package/tests/suite/bin/oraiy +0 -0
- package/tests/suite/bin/oraz +0 -0
- package/tests/suite/bin/orazx +0 -0
- package/tests/suite/bin/phan +0 -0
- package/tests/suite/bin/phpn +0 -0
- package/tests/suite/bin/plan +0 -0
- package/tests/suite/bin/plpn +0 -0
- package/tests/suite/bin/rlaa +0 -0
- package/tests/suite/bin/rlaax +0 -0
- package/tests/suite/bin/rlaay +0 -0
- package/tests/suite/bin/rlaix +0 -0
- package/tests/suite/bin/rlaiy +0 -0
- package/tests/suite/bin/rlaz +0 -0
- package/tests/suite/bin/rlazx +0 -0
- package/tests/suite/bin/rola +0 -0
- package/tests/suite/bin/rolax +0 -0
- package/tests/suite/bin/roln +0 -0
- package/tests/suite/bin/rolz +0 -0
- package/tests/suite/bin/rolzx +0 -0
- package/tests/suite/bin/rora +0 -0
- package/tests/suite/bin/rorax +0 -0
- package/tests/suite/bin/rorn +0 -0
- package/tests/suite/bin/rorz +0 -0
- package/tests/suite/bin/rorzx +0 -0
- package/tests/suite/bin/rraa +0 -0
- package/tests/suite/bin/rraax +0 -0
- package/tests/suite/bin/rraay +0 -0
- package/tests/suite/bin/rraix +0 -0
- package/tests/suite/bin/rraiy +0 -0
- package/tests/suite/bin/rraz +0 -0
- package/tests/suite/bin/rrazx +0 -0
- package/tests/suite/bin/rtin +0 -0
- package/tests/suite/bin/rtsn +0 -0
- package/tests/suite/bin/sbca +0 -0
- package/tests/suite/bin/sbcax +0 -0
- package/tests/suite/bin/sbcay +0 -0
- package/tests/suite/bin/sbcb +0 -0
- package/tests/suite/bin/sbcb(eb) +0 -0
- package/tests/suite/bin/sbcix +0 -0
- package/tests/suite/bin/sbciy +0 -0
- package/tests/suite/bin/sbcz +0 -0
- package/tests/suite/bin/sbczx +0 -0
- package/tests/suite/bin/sbxb +0 -0
- package/tests/suite/bin/secn +0 -0
- package/tests/suite/bin/sedn +0 -0
- package/tests/suite/bin/sein +0 -0
- package/tests/suite/bin/shaay +0 -0
- package/tests/suite/bin/shaiy +0 -0
- package/tests/suite/bin/shsay +0 -0
- package/tests/suite/bin/shxay +0 -0
- package/tests/suite/bin/shyax +0 -0
- package/tests/suite/bin/staa +0 -0
- package/tests/suite/bin/staax +0 -0
- package/tests/suite/bin/staay +0 -0
- package/tests/suite/bin/staix +0 -0
- package/tests/suite/bin/staiy +0 -0
- package/tests/suite/bin/staz +0 -0
- package/tests/suite/bin/stazx +0 -0
- package/tests/suite/bin/stxa +0 -0
- package/tests/suite/bin/stxz +0 -0
- package/tests/suite/bin/stxzy +0 -0
- package/tests/suite/bin/stya +0 -0
- package/tests/suite/bin/styz +0 -0
- package/tests/suite/bin/styzx +0 -0
- package/tests/suite/bin/taxn +0 -0
- package/tests/suite/bin/tayn +0 -0
- package/tests/suite/bin/trap1 +0 -0
- package/tests/suite/bin/trap10 +0 -0
- package/tests/suite/bin/trap11 +0 -0
- package/tests/suite/bin/trap12 +0 -0
- package/tests/suite/bin/trap13 +0 -0
- package/tests/suite/bin/trap14 +0 -0
- package/tests/suite/bin/trap15 +0 -0
- package/tests/suite/bin/trap16 +0 -0
- package/tests/suite/bin/trap17 +0 -0
- package/tests/suite/bin/trap2 +0 -0
- package/tests/suite/bin/trap3 +0 -0
- package/tests/suite/bin/trap4 +0 -0
- package/tests/suite/bin/trap5 +0 -0
- package/tests/suite/bin/trap6 +0 -0
- package/tests/suite/bin/trap7 +0 -0
- package/tests/suite/bin/trap8 +0 -0
- package/tests/suite/bin/trap9 +0 -0
- package/tests/suite/bin/tsxn +0 -0
- package/tests/suite/bin/txan +0 -0
- package/tests/suite/bin/txsn +0 -0
- package/tests/suite/bin/tyan +0 -0
- package/tests/suite/cbm-hackers-post.html +178 -0
- package/tests/suite/cbm-hackers-post.md +78 -0
- package/tests/test-machine.js +288 -0
- package/tests/test-suite.js +147 -0
- package/tests/test.css +7 -0
- package/tests/unit/gzip/test-1 +0 -0
- package/tests/unit/gzip/test-1.gz +0 -0
- package/tests/unit/gzip/test-2 +0 -0
- package/tests/unit/gzip/test-2.gz +0 -0
- package/tests/unit/gzip/test-3 +0 -0
- package/tests/unit/gzip/test-3.gz +0 -0
- package/tests/unit/gzip/test-4 +0 -0
- package/tests/unit/gzip/test-4.gz +0 -0
- package/tests/unit/test-adc.js +307 -0
- package/tests/unit/test-bcd.js +30 -0
- package/tests/unit/test-cmos.js +266 -0
- package/tests/unit/test-disc-drive.js +85 -0
- package/tests/unit/test-disc-hfe.js +347 -0
- package/tests/unit/test-disc.js +232 -0
- package/tests/unit/test-fifo.js +35 -0
- package/tests/unit/test-gamepad-source.js +67 -0
- package/tests/unit/test-gzip.js +22 -0
- package/tests/unit/test-intel-fdc.js +93 -0
- package/tests/unit/test-keyboard.js +410 -0
- package/tests/unit/test-mouse-joystick-source.js +128 -0
- package/tests/unit/test-scheduler.js +190 -0
- package/tests/unit/test-serial.js +154 -0
- package/tests/unit/test-teletext-adaptor.js +359 -0
- package/tests/unit/test-tokenise.js +65 -0
- package/tests/unit/test-url-params.js +398 -0
- package/tests/unit/test-utils.js +276 -0
- package/tests/unit/test-video.js +498 -0
- package/tests/unit/test-zip.js +56 -0
- package/tests/unit/zip/test-mixed.zip +0 -0
- package/tests/unit/zip/test-rom.zip +0 -0
- package/tests/unit/zip/test-ssd.zip +0 -0
- package/tools/fir-generator.js +80 -0
- package/tools/vite-plugin-fir-shader.js +131 -0
- package/vite.config.js +34 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as utils from "./utils.js";
|
|
4
|
+
|
|
5
|
+
const PollHz = 8; // Made up
|
|
6
|
+
const PollCycles = (2 * 1000 * 1000) / PollHz;
|
|
7
|
+
|
|
8
|
+
function doScale(val, scale, margin) {
|
|
9
|
+
val = (val - margin) / (1 - 2 * margin);
|
|
10
|
+
return val * scale;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class TouchScreen {
|
|
14
|
+
constructor(scheduler) {
|
|
15
|
+
this.scheduler = scheduler;
|
|
16
|
+
this.mouse = [];
|
|
17
|
+
this.outBuffer = new utils.Fifo(16);
|
|
18
|
+
this.delay = 0;
|
|
19
|
+
this.mode = 0;
|
|
20
|
+
this.pollTask = this.scheduler.newTask(this.poll);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
tryReceive(rts) {
|
|
24
|
+
if (this.outBuffer.size && rts) return this.outBuffer.get();
|
|
25
|
+
return -1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
doRead() {
|
|
29
|
+
const scaleX = 120,
|
|
30
|
+
marginX = 0.13;
|
|
31
|
+
const scaleY = 100,
|
|
32
|
+
marginY = 0.03;
|
|
33
|
+
const scaledX = doScale(this.mouse.x, scaleX, marginX);
|
|
34
|
+
const scaledY = doScale(1 - this.mouse.y, scaleY, marginY);
|
|
35
|
+
const toSend = [0x4f, 0x4f, 0x4f, 0x4f];
|
|
36
|
+
const x = Math.min(255, Math.max(0, scaledX)) | 0;
|
|
37
|
+
const y = Math.min(255, Math.max(0, scaledY)) | 0;
|
|
38
|
+
if (this.mouse.button) {
|
|
39
|
+
toSend[0] = 0x40 | ((x & 0xf0) >>> 4);
|
|
40
|
+
toSend[1] = 0x40 | (x & 0x0f);
|
|
41
|
+
toSend[2] = 0x40 | ((y & 0xf0) >>> 4);
|
|
42
|
+
toSend[3] = 0x40 | (y & 0x0f);
|
|
43
|
+
}
|
|
44
|
+
for (let i = 0; i < 4; ++i) this.store(toSend[i]);
|
|
45
|
+
this.store(".".charCodeAt(0));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
poll() {
|
|
49
|
+
this.doRead();
|
|
50
|
+
this.pollTask.reschedule(PollCycles);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
store(byte) {
|
|
54
|
+
this.outBuffer.put(byte);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onMouse(x, y, button) {
|
|
58
|
+
this.mouse = { x: x, y: y, button: button };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onTransmit(val) {
|
|
62
|
+
switch (String.fromCharCode(val)) {
|
|
63
|
+
case "M":
|
|
64
|
+
this.mode = 0;
|
|
65
|
+
break;
|
|
66
|
+
case "0":
|
|
67
|
+
case "1":
|
|
68
|
+
case "2":
|
|
69
|
+
case "3":
|
|
70
|
+
case "4":
|
|
71
|
+
case "5":
|
|
72
|
+
case "6":
|
|
73
|
+
case "7":
|
|
74
|
+
case "8":
|
|
75
|
+
case "9":
|
|
76
|
+
this.mode = 10 * this.mode + val - "0".charCodeAt(0);
|
|
77
|
+
break;
|
|
78
|
+
case ".":
|
|
79
|
+
break;
|
|
80
|
+
case "?":
|
|
81
|
+
if (this.mode === 1) this.doRead();
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
this.pollTask.ensureScheduled(this.mode === 129 || this.mode === 130, PollCycles);
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/tube.js
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
import * as utils from "./utils.js";
|
|
3
|
+
|
|
4
|
+
// this one should be declared more globally
|
|
5
|
+
const HOST_CPU_FLAG_IRQ_TUBE_ULA = 8;
|
|
6
|
+
|
|
7
|
+
const TUBE_ULA_R1 = 0;
|
|
8
|
+
const TUBE_ULA_R2 = 1;
|
|
9
|
+
const TUBE_ULA_R3 = 2;
|
|
10
|
+
const TUBE_ULA_R4 = 3;
|
|
11
|
+
const TUBE_ULA_R1_STATUS_ADDRESS = 0;
|
|
12
|
+
const TUBE_ULA_R1_DATA_ADDRESS = 1;
|
|
13
|
+
const TUBE_ULA_R2_STATUS_ADDRESS = 2;
|
|
14
|
+
const TUBE_ULA_R2_DATA_ADDRESS = 3;
|
|
15
|
+
const TUBE_ULA_R3_STATUS_ADDRESS = 4;
|
|
16
|
+
const TUBE_ULA_R3_DATA_ADDRESS = 5;
|
|
17
|
+
const TUBE_ULA_R4_STATUS_ADDRESS = 6;
|
|
18
|
+
const TUBE_ULA_R4_DATA_ADDRESS = 7;
|
|
19
|
+
const TUBE_ULA_FLAG_DATA_AVAILABLE = 0x80;
|
|
20
|
+
const TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL = 0x40;
|
|
21
|
+
const TUBE_ULA_FLAG_STATUS_Q = 0x01;
|
|
22
|
+
const TUBE_ULA_FLAG_STATUS_I = 0x02;
|
|
23
|
+
const TUBE_ULA_FLAG_STATUS_J = 0x04;
|
|
24
|
+
const TUBE_ULA_FLAG_STATUS_M = 0x08;
|
|
25
|
+
const TUBE_ULA_FLAG_STATUS_V = 0x10;
|
|
26
|
+
const TUBE_ULA_FLAG_STATUS_P = 0x20;
|
|
27
|
+
const TUBE_ULA_FLAG_STATUS_T = 0x40;
|
|
28
|
+
const TUBE_ULA_FLAG_STATUS_S = 0x80;
|
|
29
|
+
// human-readable aliases for the above flags
|
|
30
|
+
const TUBE_ULA_FLAG_STATUS_ENABLE_HOST_IRQ_FROM_R4_DATA = TUBE_ULA_FLAG_STATUS_Q;
|
|
31
|
+
const TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_IRQ_FROM_R1_DATA = TUBE_ULA_FLAG_STATUS_I;
|
|
32
|
+
const TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_IRQ_FROM_R4_DATA = TUBE_ULA_FLAG_STATUS_J;
|
|
33
|
+
const TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_NMI_FROM_R3_DATA = TUBE_ULA_FLAG_STATUS_M;
|
|
34
|
+
const TUBE_ULA_FLAG_STATUS_ENABLE_2_BYTE_R3_DATA = TUBE_ULA_FLAG_STATUS_V;
|
|
35
|
+
const TUBE_ULA_FLAG_STATUS_PARASITE_RESET_ACTIVE_LOW = TUBE_ULA_FLAG_STATUS_P;
|
|
36
|
+
const TUBE_ULA_FLAG_STATUS_CLEAR_ALL_TUBE_REGISTERS = TUBE_ULA_FLAG_STATUS_T;
|
|
37
|
+
const TUBE_ULA_FLAG_STATUS_SET_CONTROL_FLAGS = TUBE_ULA_FLAG_STATUS_S;
|
|
38
|
+
const TUBE_ULA_R1_PARASITE_BYTE_COUNT = 24;
|
|
39
|
+
|
|
40
|
+
export class Tube {
|
|
41
|
+
constructor(hostCpu, parasiteCpu) {
|
|
42
|
+
this.hostCpu = hostCpu;
|
|
43
|
+
this.parasiteCpu = parasiteCpu;
|
|
44
|
+
this.internalStatusRegister = 0;
|
|
45
|
+
this.hostStatus = new Uint8Array(4);
|
|
46
|
+
this.parasiteStatus = new Uint8Array(4);
|
|
47
|
+
this.parasiteToHostData = [
|
|
48
|
+
new Uint8Array(TUBE_ULA_R1_PARASITE_BYTE_COUNT),
|
|
49
|
+
new Uint8Array(1),
|
|
50
|
+
new Uint8Array(2),
|
|
51
|
+
new Uint8Array(1),
|
|
52
|
+
];
|
|
53
|
+
this.hostToParasiteData = [new Uint8Array(1), new Uint8Array(1), new Uint8Array(2), new Uint8Array(1)];
|
|
54
|
+
this.parasiteToHostFifoByteCount1 = 0;
|
|
55
|
+
this.parasiteToHostFifoByteCount3 = 0;
|
|
56
|
+
this.hostToParasiteFifoByteCount3 = 0;
|
|
57
|
+
this.debug = false;
|
|
58
|
+
}
|
|
59
|
+
reset(updateInternalStatusRegister = true) {
|
|
60
|
+
if (updateInternalStatusRegister) {
|
|
61
|
+
this.internalStatusRegister = 0;
|
|
62
|
+
}
|
|
63
|
+
for (let i = 0; i < 4; i++) {
|
|
64
|
+
this.hostStatus[i] = TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
65
|
+
this.parasiteStatus[i] = TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
66
|
+
if (i === TUBE_ULA_R3) {
|
|
67
|
+
// register 3 has one valid but insignificant byte in the parasite to host FIFO (this is to prevent an immediate PNMI state after PRST)
|
|
68
|
+
this.hostStatus[i] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
69
|
+
this.parasiteToHostData[i][0] = 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
this.parasiteToHostFifoByteCount1 = 0;
|
|
73
|
+
// see info in the loop above from Tube Application Note about R3
|
|
74
|
+
this.parasiteToHostFifoByteCount3 = 1;
|
|
75
|
+
this.hostToParasiteFifoByteCount3 = 0;
|
|
76
|
+
this.updateInterrupts();
|
|
77
|
+
}
|
|
78
|
+
updateInterrupts() {
|
|
79
|
+
// host IRQ
|
|
80
|
+
if (
|
|
81
|
+
this.hostStatus[TUBE_ULA_R4] & TUBE_ULA_FLAG_DATA_AVAILABLE &&
|
|
82
|
+
this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_HOST_IRQ_FROM_R4_DATA
|
|
83
|
+
) {
|
|
84
|
+
this.hostCpu.interrupt |= HOST_CPU_FLAG_IRQ_TUBE_ULA;
|
|
85
|
+
} else {
|
|
86
|
+
this.hostCpu.interrupt &= ~HOST_CPU_FLAG_IRQ_TUBE_ULA;
|
|
87
|
+
}
|
|
88
|
+
// parasite IRQ
|
|
89
|
+
if (
|
|
90
|
+
(this.parasiteStatus[TUBE_ULA_R1] & TUBE_ULA_FLAG_DATA_AVAILABLE &&
|
|
91
|
+
this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_IRQ_FROM_R1_DATA) ||
|
|
92
|
+
(this.parasiteStatus[TUBE_ULA_R4] & TUBE_ULA_FLAG_DATA_AVAILABLE &&
|
|
93
|
+
this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_IRQ_FROM_R4_DATA)
|
|
94
|
+
) {
|
|
95
|
+
this.parasiteCpu.interrupt = true;
|
|
96
|
+
} else {
|
|
97
|
+
this.parasiteCpu.interrupt = false;
|
|
98
|
+
}
|
|
99
|
+
// parasite NMI
|
|
100
|
+
// (from Tube Application Note)
|
|
101
|
+
// either: M = 1, V = 0, 1 or 2 bytes in host to parasite register 3 FIFO or 0 bytes in parasite
|
|
102
|
+
// to host register 3 FIFO (this allows single byte transfers across
|
|
103
|
+
// register 3)
|
|
104
|
+
// or: M = 1, V = 1, 2 bytes in host to parasite register 3 FIFO or 0 bytes in parasite to host
|
|
105
|
+
// register 3 FIFO. (this allows two byte transfers across register 3)
|
|
106
|
+
const r3Size = this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_2_BYTE_R3_DATA ? 2 : 1;
|
|
107
|
+
if (
|
|
108
|
+
this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_PARASITE_NMI_FROM_R3_DATA &&
|
|
109
|
+
(this.hostToParasiteFifoByteCount3 >= r3Size || this.parasiteToHostFifoByteCount3 === 0)
|
|
110
|
+
) {
|
|
111
|
+
this.parasiteCpu.NMI(true);
|
|
112
|
+
} else {
|
|
113
|
+
this.parasiteCpu.NMI(false);
|
|
114
|
+
}
|
|
115
|
+
// parasite CPU RESET held low - not implemented in the CPU - the CPU should be frozen until this signal is released
|
|
116
|
+
this.parasiteCpu.resetHeldLow = this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_PARASITE_RESET_ACTIVE_LOW;
|
|
117
|
+
}
|
|
118
|
+
hostRead(address) {
|
|
119
|
+
let result = 0xfe;
|
|
120
|
+
switch (address & 7) {
|
|
121
|
+
case TUBE_ULA_R1_STATUS_ADDRESS:
|
|
122
|
+
result =
|
|
123
|
+
(this.hostStatus[TUBE_ULA_R1] &
|
|
124
|
+
(TUBE_ULA_FLAG_DATA_AVAILABLE | TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL)) |
|
|
125
|
+
(this.internalStatusRegister &
|
|
126
|
+
~(TUBE_ULA_FLAG_DATA_AVAILABLE | TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL));
|
|
127
|
+
break;
|
|
128
|
+
case TUBE_ULA_R1_DATA_ADDRESS:
|
|
129
|
+
result = this.parasiteToHostData[TUBE_ULA_R1][0];
|
|
130
|
+
if (this.hostStatus[TUBE_ULA_R1] & TUBE_ULA_FLAG_DATA_AVAILABLE) {
|
|
131
|
+
for (let i = 1; i < TUBE_ULA_R1_PARASITE_BYTE_COUNT; i++) {
|
|
132
|
+
this.parasiteToHostData[TUBE_ULA_R1][i - 1] = this.parasiteToHostData[TUBE_ULA_R1][i];
|
|
133
|
+
}
|
|
134
|
+
this.parasiteStatus[TUBE_ULA_R1] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
135
|
+
this.parasiteToHostFifoByteCount1--;
|
|
136
|
+
if (this.parasiteToHostFifoByteCount1 === 0) {
|
|
137
|
+
this.hostStatus[TUBE_ULA_R1] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
case TUBE_ULA_R2_STATUS_ADDRESS:
|
|
142
|
+
result = this.hostStatus[TUBE_ULA_R2];
|
|
143
|
+
break;
|
|
144
|
+
case TUBE_ULA_R2_DATA_ADDRESS:
|
|
145
|
+
result = this.parasiteToHostData[TUBE_ULA_R2][0];
|
|
146
|
+
this.parasiteStatus[TUBE_ULA_R2] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
147
|
+
this.hostStatus[TUBE_ULA_R2] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
148
|
+
break;
|
|
149
|
+
case TUBE_ULA_R3_STATUS_ADDRESS:
|
|
150
|
+
result = this.hostStatus[TUBE_ULA_R3];
|
|
151
|
+
break;
|
|
152
|
+
case TUBE_ULA_R3_DATA_ADDRESS:
|
|
153
|
+
result = this.parasiteToHostData[TUBE_ULA_R3][0];
|
|
154
|
+
if (this.hostStatus[TUBE_ULA_R3] & TUBE_ULA_FLAG_DATA_AVAILABLE) {
|
|
155
|
+
this.parasiteToHostData[TUBE_ULA_R3][0] = this.parasiteToHostData[TUBE_ULA_R3][1];
|
|
156
|
+
this.parasiteStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
157
|
+
this.parasiteToHostFifoByteCount3--;
|
|
158
|
+
if (this.parasiteToHostFifoByteCount3 === 0) {
|
|
159
|
+
this.hostStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
case TUBE_ULA_R4_STATUS_ADDRESS:
|
|
164
|
+
result = this.hostStatus[TUBE_ULA_R4];
|
|
165
|
+
break;
|
|
166
|
+
case TUBE_ULA_R4_DATA_ADDRESS:
|
|
167
|
+
result = this.parasiteToHostData[TUBE_ULA_R4][0];
|
|
168
|
+
this.parasiteStatus[TUBE_ULA_R4] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
169
|
+
this.hostStatus[TUBE_ULA_R4] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
this.updateInterrupts();
|
|
173
|
+
if (this.debug) {
|
|
174
|
+
console.log("TUBE ULA: host read " + utils.hexword(address) + " = " + utils.hexbyte(result));
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
hostWrite(address, value) {
|
|
179
|
+
if (this.debug) {
|
|
180
|
+
console.log("TUBE ULA: host write " + utils.hexword(address) + " = " + utils.hexbyte(value));
|
|
181
|
+
}
|
|
182
|
+
switch (address & 7) {
|
|
183
|
+
case TUBE_ULA_R1_STATUS_ADDRESS:
|
|
184
|
+
if (value & TUBE_ULA_FLAG_STATUS_SET_CONTROL_FLAGS) {
|
|
185
|
+
this.internalStatusRegister |=
|
|
186
|
+
value & ~(TUBE_ULA_FLAG_DATA_AVAILABLE | TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL);
|
|
187
|
+
} else {
|
|
188
|
+
this.internalStatusRegister &= ~(
|
|
189
|
+
value & ~(TUBE_ULA_FLAG_DATA_AVAILABLE | TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (value & TUBE_ULA_FLAG_STATUS_CLEAR_ALL_TUBE_REGISTERS) {
|
|
193
|
+
this.reset(false);
|
|
194
|
+
}
|
|
195
|
+
if (value & TUBE_ULA_FLAG_STATUS_PARASITE_RESET_ACTIVE_LOW) {
|
|
196
|
+
// there is still an issue with the parasite OS that runs after this happens
|
|
197
|
+
// it prints the startup banner but then seems to stop responding when a R3 data
|
|
198
|
+
// transfer (based on Advanced User Guide example) is attempted
|
|
199
|
+
if (this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_PARASITE_RESET_ACTIVE_LOW) {
|
|
200
|
+
this.parasiteCpu.reset(true); // this in turn calls our this.reset(true)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
case TUBE_ULA_R1_DATA_ADDRESS:
|
|
205
|
+
if (this.hostStatus[TUBE_ULA_R1] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
|
|
206
|
+
this.hostToParasiteData[TUBE_ULA_R1][0] = value;
|
|
207
|
+
this.parasiteStatus[TUBE_ULA_R1] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
208
|
+
this.hostStatus[TUBE_ULA_R1] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
case TUBE_ULA_R2_DATA_ADDRESS:
|
|
212
|
+
if (this.hostStatus[TUBE_ULA_R2] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
|
|
213
|
+
this.hostToParasiteData[TUBE_ULA_R2][0] = value;
|
|
214
|
+
this.parasiteStatus[TUBE_ULA_R2] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
215
|
+
this.hostStatus[TUBE_ULA_R2] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case TUBE_ULA_R3_DATA_ADDRESS:
|
|
219
|
+
if (this.hostStatus[TUBE_ULA_R3] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
|
|
220
|
+
if (this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_2_BYTE_R3_DATA) {
|
|
221
|
+
if (this.hostToParasiteFifoByteCount3 < 2) {
|
|
222
|
+
this.hostToParasiteData[this.hostToParasiteFifoByteCount3++] = value;
|
|
223
|
+
}
|
|
224
|
+
if (this.hostToParasiteFifoByteCount3 === 2) {
|
|
225
|
+
this.parasiteStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
226
|
+
this.hostStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
this.hostToParasiteData[TUBE_ULA_R3][0] = value;
|
|
230
|
+
this.hostToParasiteFifoByteCount3 = 1;
|
|
231
|
+
this.parasiteStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
232
|
+
this.hostStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
case TUBE_ULA_R4_DATA_ADDRESS:
|
|
237
|
+
if (this.hostStatus[TUBE_ULA_R4] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
|
|
238
|
+
this.hostToParasiteData[TUBE_ULA_R4][0] = value;
|
|
239
|
+
this.parasiteStatus[TUBE_ULA_R4] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
240
|
+
this.hostStatus[TUBE_ULA_R4] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
this.updateInterrupts();
|
|
245
|
+
}
|
|
246
|
+
parasiteRead(address) {
|
|
247
|
+
// Not implemented - needs to be integrated with the parasite CPU code:
|
|
248
|
+
// Boot mode is terminated by the software when it selects any one of the Tube addresses.
|
|
249
|
+
// This deselects the ROM
|
|
250
|
+
let result = 0;
|
|
251
|
+
switch (address & 7) {
|
|
252
|
+
case TUBE_ULA_R1_STATUS_ADDRESS:
|
|
253
|
+
result = this.parasiteStatus[TUBE_ULA_R1];
|
|
254
|
+
break;
|
|
255
|
+
case TUBE_ULA_R1_DATA_ADDRESS:
|
|
256
|
+
result = this.hostToParasiteData[TUBE_ULA_R1][0];
|
|
257
|
+
this.hostStatus[TUBE_ULA_R1] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
258
|
+
this.parasiteStatus[TUBE_ULA_R1] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
259
|
+
break;
|
|
260
|
+
case TUBE_ULA_R2_STATUS_ADDRESS:
|
|
261
|
+
result = this.parasiteStatus[TUBE_ULA_R2];
|
|
262
|
+
break;
|
|
263
|
+
case TUBE_ULA_R2_DATA_ADDRESS:
|
|
264
|
+
result = this.hostToParasiteData[TUBE_ULA_R2][0];
|
|
265
|
+
this.hostStatus[TUBE_ULA_R2] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
266
|
+
this.parasiteStatus[TUBE_ULA_R2] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
267
|
+
break;
|
|
268
|
+
case TUBE_ULA_R3_STATUS_ADDRESS:
|
|
269
|
+
result = this.parasiteStatus[TUBE_ULA_R3];
|
|
270
|
+
break;
|
|
271
|
+
case TUBE_ULA_R3_DATA_ADDRESS:
|
|
272
|
+
result = this.hostToParasiteData[TUBE_ULA_R3][0];
|
|
273
|
+
if (this.parasiteStatus[TUBE_ULA_R3] & TUBE_ULA_FLAG_DATA_AVAILABLE) {
|
|
274
|
+
this.hostToParasiteData[TUBE_ULA_R3][0] = this.hostToParasiteData[TUBE_ULA_R3][1];
|
|
275
|
+
this.hostToParasiteFifoByteCount3--;
|
|
276
|
+
if (this.hostToParasiteFifoByteCount3 === 0) {
|
|
277
|
+
this.hostStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
278
|
+
this.parasiteStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
case TUBE_ULA_R4_STATUS_ADDRESS:
|
|
283
|
+
result = this.parasiteStatus[TUBE_ULA_R4];
|
|
284
|
+
break;
|
|
285
|
+
case TUBE_ULA_R4_DATA_ADDRESS:
|
|
286
|
+
result = this.hostToParasiteData[TUBE_ULA_R4][0];
|
|
287
|
+
this.hostStatus[TUBE_ULA_R4] |= TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
288
|
+
this.parasiteStatus[TUBE_ULA_R4] &= ~TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
this.updateInterrupts();
|
|
292
|
+
if (this.debug) {
|
|
293
|
+
console.log("TUBE ULA: parasite read " + utils.hexword(address) + " = " + utils.hexbyte(result));
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
parasiteWrite(address, value) {
|
|
298
|
+
// Not implemented - needs to be integrated with the parasite CPU code:
|
|
299
|
+
// Boot mode is terminated by the software when it selects any one of the Tube addresses.
|
|
300
|
+
// This deselects the ROM
|
|
301
|
+
if (this.debug) {
|
|
302
|
+
console.log("TUBE ULA: parasite write " + utils.hexword(address) + " = " + utils.hexbyte(value));
|
|
303
|
+
}
|
|
304
|
+
switch (address & 7) {
|
|
305
|
+
case TUBE_ULA_R1_DATA_ADDRESS:
|
|
306
|
+
if (this.parasiteStatus[TUBE_ULA_R1] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
|
|
307
|
+
this.parasiteToHostData[TUBE_ULA_R1][this.parasiteToHostFifoByteCount1++] = value;
|
|
308
|
+
this.hostStatus[TUBE_ULA_R1] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
309
|
+
if (this.parasiteToHostFifoByteCount1 === TUBE_ULA_R1_PARASITE_BYTE_COUNT) {
|
|
310
|
+
this.parasiteStatus[TUBE_ULA_R1] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
case TUBE_ULA_R2_DATA_ADDRESS:
|
|
315
|
+
if (this.parasiteStatus[TUBE_ULA_R2] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
|
|
316
|
+
this.parasiteToHostData[TUBE_ULA_R2][0] = value;
|
|
317
|
+
this.hostStatus[TUBE_ULA_R2] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
318
|
+
this.parasiteStatus[TUBE_ULA_R2] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
case TUBE_ULA_R3_DATA_ADDRESS:
|
|
322
|
+
if (this.parasiteStatus[TUBE_ULA_R3] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
|
|
323
|
+
if (this.internalStatusRegister & TUBE_ULA_FLAG_STATUS_ENABLE_2_BYTE_R3_DATA) {
|
|
324
|
+
if (this.parasiteToHostFifoByteCount3 < 2) {
|
|
325
|
+
this.parasiteToHostData[TUBE_ULA_R3][this.parasiteToHostFifoByteCount3++] = value;
|
|
326
|
+
}
|
|
327
|
+
if (this.parasiteToHostFifoByteCount3 === 2) {
|
|
328
|
+
this.hostStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
329
|
+
this.parasiteStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
this.parasiteToHostData[TUBE_ULA_R3][0] = value;
|
|
333
|
+
this.parasiteToHostFifoByteCount3 = 1;
|
|
334
|
+
this.hostStatus[TUBE_ULA_R3] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
335
|
+
this.parasiteStatus[TUBE_ULA_R3] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
case TUBE_ULA_R4_DATA_ADDRESS:
|
|
340
|
+
if (this.parasiteStatus[TUBE_ULA_R4] & TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL) {
|
|
341
|
+
this.parasiteToHostData[TUBE_ULA_R4][0] = value;
|
|
342
|
+
this.hostStatus[TUBE_ULA_R4] |= TUBE_ULA_FLAG_DATA_AVAILABLE;
|
|
343
|
+
this.parasiteStatus[TUBE_ULA_R4] &= ~TUBE_ULA_FLAG_DATA_REGISTER_NOT_FULL;
|
|
344
|
+
}
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
this.updateInterrupts();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL parameter handling for jsbeeb
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if a value is defined (not null and not undefined)
|
|
7
|
+
* @param {*} value - The value to check
|
|
8
|
+
* @returns {boolean} True if the value is neither null nor undefined
|
|
9
|
+
*/
|
|
10
|
+
function isDefined(value) {
|
|
11
|
+
return value !== null && value !== undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {"string"|"array"|"int"|"float"|"bool"} ParamType
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parameter type enum to avoid string literals
|
|
20
|
+
* @enum {string}
|
|
21
|
+
*/
|
|
22
|
+
export const ParamTypes = {
|
|
23
|
+
/** String parameter (default) */
|
|
24
|
+
STRING: "string",
|
|
25
|
+
/** Array parameter (for parameters that can appear multiple times) */
|
|
26
|
+
ARRAY: "array",
|
|
27
|
+
/** Integer parameter */
|
|
28
|
+
INT: "int",
|
|
29
|
+
/** Float parameter */
|
|
30
|
+
FLOAT: "float",
|
|
31
|
+
/** Boolean parameter (true if present, regardless of value) */
|
|
32
|
+
BOOL: "bool",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse a query string into an object
|
|
37
|
+
* @param {string} queryString - The query string to parse
|
|
38
|
+
* @param {Object.<string, ParamType>} [paramTypes={}] - A map of parameter names to their types
|
|
39
|
+
* @returns {Object} Object containing parsed query parameters
|
|
40
|
+
*/
|
|
41
|
+
export function parseQueryString(queryString, paramTypes = {}) {
|
|
42
|
+
if (!queryString) return {};
|
|
43
|
+
|
|
44
|
+
// workaround for shonky python web server
|
|
45
|
+
const cleanQueryString = queryString.endsWith("/") ? queryString.substring(0, queryString.length - 1) : queryString;
|
|
46
|
+
|
|
47
|
+
const parsedQuery = {};
|
|
48
|
+
|
|
49
|
+
cleanQueryString.split("&").forEach(function (keyval) {
|
|
50
|
+
if (!keyval) return;
|
|
51
|
+
|
|
52
|
+
const keyAndVal = keyval.split("=");
|
|
53
|
+
const key = decodeURIComponent(keyAndVal[0]);
|
|
54
|
+
let val = null;
|
|
55
|
+
if (keyAndVal.length > 1) val = decodeURIComponent(keyAndVal[1]);
|
|
56
|
+
|
|
57
|
+
const paramType = paramTypes[key] || ParamTypes.STRING;
|
|
58
|
+
|
|
59
|
+
switch (paramType) {
|
|
60
|
+
case ParamTypes.ARRAY:
|
|
61
|
+
if (!parsedQuery[key]) {
|
|
62
|
+
parsedQuery[key] = [];
|
|
63
|
+
}
|
|
64
|
+
parsedQuery[key].push(val);
|
|
65
|
+
break;
|
|
66
|
+
case ParamTypes.INT:
|
|
67
|
+
if (val !== undefined) {
|
|
68
|
+
const parsed = parseInt(val, 10);
|
|
69
|
+
parsedQuery[key] = isNaN(parsed) ? 0 : parsed;
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
case ParamTypes.FLOAT:
|
|
73
|
+
if (val !== undefined) {
|
|
74
|
+
const parsed = parseFloat(val);
|
|
75
|
+
parsedQuery[key] = isNaN(parsed) ? 0 : parsed;
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
case ParamTypes.BOOL:
|
|
79
|
+
// Only the exact 'false' string is treated as false.
|
|
80
|
+
parsedQuery[key] = val !== "false";
|
|
81
|
+
break;
|
|
82
|
+
case ParamTypes.STRING:
|
|
83
|
+
default:
|
|
84
|
+
parsedQuery[key] = val;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return parsedQuery;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build a URL string from base URL and query parameters
|
|
94
|
+
* @param {string} baseUrl - The base URL (without query string)
|
|
95
|
+
* @param {Object} parsedQuery - Object containing query parameters
|
|
96
|
+
* @param {Object.<string, ParamType>} [paramTypes={}] - Object mapping parameter names to their types
|
|
97
|
+
* @returns {string} The complete URL with query parameters
|
|
98
|
+
*/
|
|
99
|
+
/**
|
|
100
|
+
* Append a parameter to the URL
|
|
101
|
+
* @param {string} url - Current URL
|
|
102
|
+
* @param {string} sep - Current separator (? or &)
|
|
103
|
+
* @param {string} key - Parameter key
|
|
104
|
+
* @param {string} [value] - Parameter value (optional for boolean parameters)
|
|
105
|
+
* @returns {Object} Updated URL and separator
|
|
106
|
+
*/
|
|
107
|
+
function appendParam(url, sep, key, value = undefined) {
|
|
108
|
+
url += sep + encodeURIComponent(key);
|
|
109
|
+
if (value !== undefined) {
|
|
110
|
+
url += "=" + encodeURIComponent(value);
|
|
111
|
+
}
|
|
112
|
+
return { url, sep: "&" };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function buildUrlFromParams(baseUrl, parsedQuery, paramTypes = {}) {
|
|
116
|
+
let url = baseUrl;
|
|
117
|
+
let sep = "?";
|
|
118
|
+
|
|
119
|
+
Object.entries(parsedQuery).forEach(([key, value]) => {
|
|
120
|
+
if (key.length === 0) return;
|
|
121
|
+
|
|
122
|
+
// Default to STRING unless explicitly specified
|
|
123
|
+
const paramType = paramTypes[key] || ParamTypes.STRING;
|
|
124
|
+
|
|
125
|
+
switch (paramType) {
|
|
126
|
+
case ParamTypes.ARRAY:
|
|
127
|
+
// Handle array parameters - each item becomes a separate parameter
|
|
128
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
129
|
+
value.forEach((val) => {
|
|
130
|
+
if (isDefined(val)) {
|
|
131
|
+
const result = appendParam(url, sep, key, val);
|
|
132
|
+
url = result.url;
|
|
133
|
+
sep = result.sep;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case ParamTypes.BOOL:
|
|
139
|
+
// For boolean params, only add the key without value if true
|
|
140
|
+
if (value === true) {
|
|
141
|
+
const result = appendParam(url, sep, key);
|
|
142
|
+
url = result.url;
|
|
143
|
+
sep = result.sep;
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
case ParamTypes.INT:
|
|
147
|
+
case ParamTypes.FLOAT:
|
|
148
|
+
case ParamTypes.STRING:
|
|
149
|
+
default:
|
|
150
|
+
// Include the parameter if it has a value (including zero)
|
|
151
|
+
if (isDefined(value) && value !== "") {
|
|
152
|
+
const result = appendParam(url, sep, key, value);
|
|
153
|
+
url = result.url;
|
|
154
|
+
sep = result.sep;
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return url;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Process keyboard mapping parameters from query string
|
|
165
|
+
* @param {Object} parsedQuery - The parsed query parameters
|
|
166
|
+
* @param {Object} BBC - BBC key constants
|
|
167
|
+
* @param {Object} keyCodes - Key code constants
|
|
168
|
+
* @param {Array} userKeymap - Array to store user key mappings
|
|
169
|
+
* @param {Object} gamepad - Gamepad object for handling mapping
|
|
170
|
+
* @returns {Object} Updated query parameters
|
|
171
|
+
*/
|
|
172
|
+
export function processKeyboardParams(parsedQuery, BBC, keyCodes, userKeymap, gamepad) {
|
|
173
|
+
Object.entries(parsedQuery).forEach(([key, val]) => {
|
|
174
|
+
if (!val) return;
|
|
175
|
+
|
|
176
|
+
// eg KEY.CAPSLOCK=CTRL
|
|
177
|
+
if (key.toUpperCase().indexOf("KEY.") === 0) {
|
|
178
|
+
const bbcKey = val.toUpperCase();
|
|
179
|
+
|
|
180
|
+
if (BBC[bbcKey]) {
|
|
181
|
+
const nativeKey = key.substring(4).toUpperCase(); // remove KEY.
|
|
182
|
+
if (keyCodes[nativeKey]) {
|
|
183
|
+
console.log("mapping " + nativeKey + " to " + bbcKey);
|
|
184
|
+
userKeymap.push({ native: nativeKey, bbc: bbcKey });
|
|
185
|
+
} else {
|
|
186
|
+
console.log("unknown key: " + nativeKey);
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
console.log("unknown BBC key: " + val);
|
|
190
|
+
}
|
|
191
|
+
} else if (key.indexOf("GP.") === 0) {
|
|
192
|
+
// gamepad mapping
|
|
193
|
+
// eg ?GP.FIRE2=RETURN
|
|
194
|
+
const gamepadKey = key.substring(3).toUpperCase(); // remove GP. prefix
|
|
195
|
+
gamepad.remap(gamepadKey, val.toUpperCase());
|
|
196
|
+
} else {
|
|
197
|
+
switch (key) {
|
|
198
|
+
case "LEFT":
|
|
199
|
+
case "RIGHT":
|
|
200
|
+
case "UP":
|
|
201
|
+
case "DOWN":
|
|
202
|
+
case "FIRE":
|
|
203
|
+
gamepad.remap(key, val.toUpperCase());
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return parsedQuery;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Process autoboot and other emulation parameters
|
|
214
|
+
* @param {Object} parsedQuery - The parsed query parameters
|
|
215
|
+
* @returns {Object} Information about autoboot settings
|
|
216
|
+
*/
|
|
217
|
+
export function processAutobootParams(parsedQuery) {
|
|
218
|
+
let needsAutoboot = false;
|
|
219
|
+
let autoType = "";
|
|
220
|
+
|
|
221
|
+
if (isDefined(parsedQuery.autoboot)) {
|
|
222
|
+
needsAutoboot = "boot";
|
|
223
|
+
} else if (isDefined(parsedQuery.autochain)) {
|
|
224
|
+
needsAutoboot = "chain";
|
|
225
|
+
} else if (isDefined(parsedQuery.autorun)) {
|
|
226
|
+
needsAutoboot = "run";
|
|
227
|
+
} else if (isDefined(parsedQuery.autotype)) {
|
|
228
|
+
needsAutoboot = "type";
|
|
229
|
+
autoType = parsedQuery.autotype;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { needsAutoboot, autoType };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Guess the appropriate model based on the hostname
|
|
237
|
+
* @param {string} hostname - The hostname to check
|
|
238
|
+
* @returns {string} Model identifier
|
|
239
|
+
*/
|
|
240
|
+
export function guessModelFromHostname(hostname) {
|
|
241
|
+
if (hostname.startsWith("bbc")) return "B-DFS1.2";
|
|
242
|
+
if (hostname.startsWith("master")) return "Master";
|
|
243
|
+
return "B-DFS1.2";
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Parse disc or tape images from the query parameters
|
|
248
|
+
* @param {Object} parsedQuery - The query parameters
|
|
249
|
+
* @returns {Object} Object containing disc and tape information
|
|
250
|
+
*/
|
|
251
|
+
export function parseMediaParams(parsedQuery) {
|
|
252
|
+
const { disc, disc1, disc2, tape } = parsedQuery;
|
|
253
|
+
const discImage = disc || disc1;
|
|
254
|
+
|
|
255
|
+
return { discImage, secondDiscImage: disc2, tapeImage: tape };
|
|
256
|
+
}
|