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,149 @@
|
|
|
1
|
+
import { AnalogueSource } from "./analogue-source.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provides microphone input as an analogue source for the BBC Micro's ADC
|
|
5
|
+
* Used for software like MicroMike that uses the analogue port for sound input
|
|
6
|
+
*/
|
|
7
|
+
export class MicrophoneInput extends AnalogueSource {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new MicrophoneInput
|
|
10
|
+
*/
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.audioContext = null;
|
|
14
|
+
this.microphoneStream = null;
|
|
15
|
+
this.microphoneSource = null;
|
|
16
|
+
this.microphoneAnalyser = null;
|
|
17
|
+
this.microphoneDataArray = null;
|
|
18
|
+
this.errorCallback = null;
|
|
19
|
+
this.errorMessage = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Set error callback function
|
|
24
|
+
* @param {Function} callback - Function to call with error messages
|
|
25
|
+
*/
|
|
26
|
+
setErrorCallback(callback) {
|
|
27
|
+
this.errorCallback = callback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialise microphone access
|
|
32
|
+
* @returns {Promise<boolean>} True if initialiation was successful
|
|
33
|
+
*/
|
|
34
|
+
async initialise() {
|
|
35
|
+
console.log("MicrophoneInput: Initialising microphone input");
|
|
36
|
+
|
|
37
|
+
// Create audio context if needed
|
|
38
|
+
if (!this.audioContext) {
|
|
39
|
+
try {
|
|
40
|
+
console.log("MicrophoneInput: Creating audio context");
|
|
41
|
+
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
42
|
+
console.log("MicrophoneInput: Audio context created:", this.audioContext.state);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("MicrophoneInput: Error creating audio context:", error);
|
|
45
|
+
this.errorMessage = `Could not create audio context: ${error.message}`;
|
|
46
|
+
if (this.errorCallback) this.errorCallback(this.errorMessage);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
console.log("MicrophoneInput: Requesting microphone access");
|
|
53
|
+
// Request microphone access
|
|
54
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
55
|
+
console.log("MicrophoneInput: Microphone access granted");
|
|
56
|
+
|
|
57
|
+
// Store the stream so we can stop it if needed
|
|
58
|
+
this.microphoneStream = stream;
|
|
59
|
+
|
|
60
|
+
// Create analyser node
|
|
61
|
+
console.log("MicrophoneInput: Creating audio analyser");
|
|
62
|
+
this.microphoneAnalyser = this.audioContext.createAnalyser();
|
|
63
|
+
this.microphoneAnalyser.fftSize = 1024; // Larger FFT size for better resolution
|
|
64
|
+
this.microphoneAnalyser.smoothingTimeConstant = 0.2; // Less smoothing for more responsive input
|
|
65
|
+
|
|
66
|
+
// Create buffer for analyser data
|
|
67
|
+
this.microphoneDataArray = new Uint8Array(this.microphoneAnalyser.frequencyBinCount);
|
|
68
|
+
console.log("MicrophoneInput: Created data buffer with", this.microphoneDataArray.length, "samples");
|
|
69
|
+
|
|
70
|
+
// Create media stream source from microphone
|
|
71
|
+
console.log("MicrophoneInput: Creating media stream source");
|
|
72
|
+
this.microphoneSource = this.audioContext.createMediaStreamSource(stream);
|
|
73
|
+
|
|
74
|
+
// Connect microphone to analyser
|
|
75
|
+
console.log("MicrophoneInput: Connecting microphone to analyser");
|
|
76
|
+
this.microphoneSource.connect(this.microphoneAnalyser);
|
|
77
|
+
|
|
78
|
+
this.errorMessage = null;
|
|
79
|
+
console.log("MicrophoneInput: Initialisation complete");
|
|
80
|
+
return true;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error("MicrophoneInput: Error accessing microphone:", error);
|
|
83
|
+
this.errorMessage = `Error accessing microphone: ${error.message}`;
|
|
84
|
+
if (this.errorCallback) this.errorCallback(this.errorMessage);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get the last error message if any
|
|
91
|
+
* @returns {string|null} The last error message or null
|
|
92
|
+
*/
|
|
93
|
+
getErrorMessage() {
|
|
94
|
+
return this.errorMessage;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get analog value from microphone for the specified channel
|
|
99
|
+
* @param {number} _channel - The ADC channel (0-3)
|
|
100
|
+
* @returns {number} A value between 0 and 0xffff
|
|
101
|
+
*/
|
|
102
|
+
getValue(_channel) {
|
|
103
|
+
if (!this.microphoneAnalyser || !this.microphoneDataArray) {
|
|
104
|
+
throw new Error("MicrophoneInput: getValue called but analyser not initialised");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Get time domain data (waveform)
|
|
108
|
+
this.microphoneAnalyser.getByteTimeDomainData(this.microphoneDataArray);
|
|
109
|
+
|
|
110
|
+
// Calculate volume as average deviation from the center (128)
|
|
111
|
+
let sum = 0;
|
|
112
|
+
for (let i = 0; i < this.microphoneDataArray.length; i++) {
|
|
113
|
+
sum += Math.abs(this.microphoneDataArray[i] - 128);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const average = sum / this.microphoneDataArray.length;
|
|
117
|
+
|
|
118
|
+
// Scale up the signal using a configurable scaling factor
|
|
119
|
+
// This can be adjusted based on testing
|
|
120
|
+
const scaleFactor = 800; // Amplify the signal
|
|
121
|
+
const scaledValue = average * scaleFactor;
|
|
122
|
+
|
|
123
|
+
// Map to 16-bit range (0-0xFFFF)
|
|
124
|
+
return Math.min(0xffff, scaledValue) | 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Clean up resources when no longer needed
|
|
129
|
+
*/
|
|
130
|
+
dispose() {
|
|
131
|
+
if (this.microphoneStream) {
|
|
132
|
+
this.microphoneStream.getTracks().forEach((track) => track.stop());
|
|
133
|
+
this.microphoneStream = null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (this.microphoneSource) {
|
|
137
|
+
this.microphoneSource.disconnect();
|
|
138
|
+
this.microphoneSource = null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.microphoneAnalyser = null;
|
|
142
|
+
this.microphoneDataArray = null;
|
|
143
|
+
|
|
144
|
+
if (this.audioContext && this.audioContext.state !== "closed") {
|
|
145
|
+
this.audioContext.close().catch((err) => console.error("Error closing audio context:", err));
|
|
146
|
+
}
|
|
147
|
+
this.audioContext = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/models.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { NoiseAwareWdFdc } from "./wd-fdc.js";
|
|
4
|
+
import { NoiseAwareIntelFdc } from "./intel-fdc.js";
|
|
5
|
+
import * as opcodes from "./6502.opcodes.js";
|
|
6
|
+
|
|
7
|
+
const CpuModel = Object.freeze({
|
|
8
|
+
MOS6502: 0,
|
|
9
|
+
CMOS65C02: 1,
|
|
10
|
+
CMOS65C12: 2,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
class Model {
|
|
14
|
+
constructor(name, synonyms, os, cpuModel, isMaster, swram, fdc, tube, cmosOverride) {
|
|
15
|
+
this.name = name;
|
|
16
|
+
this.synonyms = synonyms;
|
|
17
|
+
this.os = os;
|
|
18
|
+
this._cpuModel = cpuModel;
|
|
19
|
+
this.isMaster = isMaster;
|
|
20
|
+
this.Fdc = fdc;
|
|
21
|
+
this.swram = swram;
|
|
22
|
+
this.isTest = false;
|
|
23
|
+
this.tube = tube;
|
|
24
|
+
this.cmosOverride = cmosOverride;
|
|
25
|
+
this.hasEconet = false;
|
|
26
|
+
this.hasMusic5000 = false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get nmos() {
|
|
30
|
+
return this._cpuModel === CpuModel.MOS6502;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get opcodesFactory() {
|
|
34
|
+
switch (this._cpuModel) {
|
|
35
|
+
case CpuModel.MOS6502:
|
|
36
|
+
return opcodes.Cpu6502;
|
|
37
|
+
case CpuModel.CMOS65C02:
|
|
38
|
+
return opcodes.Cpu65c02;
|
|
39
|
+
case CpuModel.CMOS65C12:
|
|
40
|
+
return opcodes.Cpu65c12;
|
|
41
|
+
}
|
|
42
|
+
throw new Error("Unknown CPU model");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function pickAdfs(cmos) {
|
|
47
|
+
cmos[19] = (cmos[19] & 0xf0) | 13;
|
|
48
|
+
return cmos;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pickAnfs(cmos) {
|
|
52
|
+
cmos[19] = (cmos[19] & 0xf0) | 8;
|
|
53
|
+
return cmos;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function pickDfs(cmos) {
|
|
57
|
+
cmos[19] = (cmos[19] & 0xf0) | 9;
|
|
58
|
+
return cmos;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// TODO: semi-bplus-style to get swram for exile hardcoded here
|
|
62
|
+
const beebSwram = [
|
|
63
|
+
true,
|
|
64
|
+
true,
|
|
65
|
+
true,
|
|
66
|
+
true, // Dunjunz variants. Exile (not picky).
|
|
67
|
+
true,
|
|
68
|
+
true,
|
|
69
|
+
true,
|
|
70
|
+
true, // Crazee Rider.
|
|
71
|
+
false,
|
|
72
|
+
false,
|
|
73
|
+
false,
|
|
74
|
+
false,
|
|
75
|
+
false,
|
|
76
|
+
false,
|
|
77
|
+
false,
|
|
78
|
+
false,
|
|
79
|
+
];
|
|
80
|
+
const masterSwram = [
|
|
81
|
+
false,
|
|
82
|
+
false,
|
|
83
|
+
false,
|
|
84
|
+
false,
|
|
85
|
+
true,
|
|
86
|
+
true,
|
|
87
|
+
true,
|
|
88
|
+
true,
|
|
89
|
+
false,
|
|
90
|
+
false,
|
|
91
|
+
false,
|
|
92
|
+
false,
|
|
93
|
+
false,
|
|
94
|
+
false,
|
|
95
|
+
false,
|
|
96
|
+
false,
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
export const allModels = [
|
|
100
|
+
new Model(
|
|
101
|
+
"BBC B with DFS 1.2",
|
|
102
|
+
["B-DFS1.2"],
|
|
103
|
+
["os.rom", "BASIC.ROM", "b/DFS-1.2.rom"],
|
|
104
|
+
CpuModel.MOS6502,
|
|
105
|
+
false,
|
|
106
|
+
beebSwram,
|
|
107
|
+
NoiseAwareIntelFdc,
|
|
108
|
+
),
|
|
109
|
+
new Model(
|
|
110
|
+
"BBC B with DFS 0.9",
|
|
111
|
+
["B-DFS0.9", "B"],
|
|
112
|
+
["os.rom", "BASIC.ROM", "b/DFS-0.9.rom"],
|
|
113
|
+
CpuModel.MOS6502,
|
|
114
|
+
false,
|
|
115
|
+
beebSwram,
|
|
116
|
+
NoiseAwareIntelFdc,
|
|
117
|
+
),
|
|
118
|
+
new Model(
|
|
119
|
+
"BBC B with 1770 (DFS)",
|
|
120
|
+
["B1770"],
|
|
121
|
+
["os.rom", "BASIC.ROM", "b1770/dfs1770.rom", "b1770/zADFS.ROM"],
|
|
122
|
+
CpuModel.MOS6502,
|
|
123
|
+
false,
|
|
124
|
+
beebSwram,
|
|
125
|
+
NoiseAwareWdFdc,
|
|
126
|
+
),
|
|
127
|
+
// putting ADFS in a higher ROM slot gives it priority
|
|
128
|
+
new Model(
|
|
129
|
+
"BBC B with 1770 (ADFS)",
|
|
130
|
+
["B1770A"],
|
|
131
|
+
["os.rom", "BASIC.ROM", "b1770/zADFS.ROM", "b1770/dfs1770.rom"],
|
|
132
|
+
CpuModel.MOS6502,
|
|
133
|
+
false,
|
|
134
|
+
beebSwram,
|
|
135
|
+
NoiseAwareWdFdc,
|
|
136
|
+
),
|
|
137
|
+
new Model(
|
|
138
|
+
"BBC Master 128 (DFS)",
|
|
139
|
+
["Master"],
|
|
140
|
+
["master/mos3.20"],
|
|
141
|
+
CpuModel.CMOS65C12,
|
|
142
|
+
true,
|
|
143
|
+
masterSwram,
|
|
144
|
+
NoiseAwareWdFdc,
|
|
145
|
+
null,
|
|
146
|
+
pickDfs,
|
|
147
|
+
),
|
|
148
|
+
new Model(
|
|
149
|
+
"BBC Master 128 (ADFS)",
|
|
150
|
+
["MasterADFS"],
|
|
151
|
+
["master/mos3.20"],
|
|
152
|
+
CpuModel.CMOS65C12,
|
|
153
|
+
true,
|
|
154
|
+
masterSwram,
|
|
155
|
+
NoiseAwareWdFdc,
|
|
156
|
+
null,
|
|
157
|
+
pickAdfs,
|
|
158
|
+
),
|
|
159
|
+
new Model(
|
|
160
|
+
"BBC Master 128 (ANFS)",
|
|
161
|
+
["MasterANFS"],
|
|
162
|
+
["master/mos3.20"],
|
|
163
|
+
CpuModel.CMOS65C12,
|
|
164
|
+
true,
|
|
165
|
+
masterSwram,
|
|
166
|
+
NoiseAwareWdFdc,
|
|
167
|
+
null,
|
|
168
|
+
pickAnfs,
|
|
169
|
+
),
|
|
170
|
+
new Model("Tube65C02", [], ["tube/6502Tube.rom"], CpuModel.CMOS65C02, false), // Although this can not be explicitly selected as a model, it is required by the configuration builder later
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
export function findModel(name) {
|
|
174
|
+
name = name.toLowerCase();
|
|
175
|
+
for (let i = 0; i < allModels.length; ++i) {
|
|
176
|
+
const model = allModels[i];
|
|
177
|
+
if (model.name.toLowerCase() === name) return model;
|
|
178
|
+
for (let j = 0; j < model.synonyms.length; ++j) {
|
|
179
|
+
if (model.synonyms[j].toLowerCase() === name) return model;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export const TEST_6502 = new Model("TEST", ["TEST"], [], CpuModel.MOS6502, false, beebSwram, NoiseAwareIntelFdc);
|
|
186
|
+
TEST_6502.isTest = true;
|
|
187
|
+
export const TEST_65C02 = new Model("TEST", ["TEST"], [], CpuModel.CMOS65C02, false, masterSwram, NoiseAwareIntelFdc);
|
|
188
|
+
TEST_65C02.isTest = true;
|
|
189
|
+
export const TEST_65C12 = new Model("TEST", ["TEST"], [], CpuModel.CMOS65C12, false, masterSwram, NoiseAwareIntelFdc);
|
|
190
|
+
TEST_65C12.isTest = true;
|
|
191
|
+
|
|
192
|
+
export const basicOnly = new Model(
|
|
193
|
+
"Basic only",
|
|
194
|
+
["Basic only"],
|
|
195
|
+
["master/mos3.20"],
|
|
196
|
+
CpuModel.CMOS65C12,
|
|
197
|
+
true,
|
|
198
|
+
masterSwram,
|
|
199
|
+
NoiseAwareWdFdc,
|
|
200
|
+
);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { AnalogueSource } from "./analogue-source.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mouse-based joystick implementation of AnalogueSource
|
|
5
|
+
* Maps mouse position relative to BBC display center to ADC channels
|
|
6
|
+
*/
|
|
7
|
+
export class MouseJoystickSource extends AnalogueSource {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new MouseJoystickSource
|
|
10
|
+
* @param {HTMLCanvasElement} canvas - The BBC display canvas element
|
|
11
|
+
*/
|
|
12
|
+
constructor(canvas) {
|
|
13
|
+
super();
|
|
14
|
+
this.canvas = canvas;
|
|
15
|
+
this.mouseX = 0.5; // Normalized position (0-1)
|
|
16
|
+
this.mouseY = 0.5; // Normalized position (0-1)
|
|
17
|
+
this.isActive = false;
|
|
18
|
+
this.via = null; // Will be set later
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Set the VIA reference for button handling
|
|
23
|
+
* @param {object} via - The system VIA
|
|
24
|
+
*/
|
|
25
|
+
setVia(via) {
|
|
26
|
+
this.via = via;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Handle mouse movement event from external handler
|
|
31
|
+
* @param {number} x - Normalized X position (0-1)
|
|
32
|
+
* @param {number} y - Normalized Y position (0-1)
|
|
33
|
+
*/
|
|
34
|
+
onMouseMove(x, y) {
|
|
35
|
+
this.mouseX = Math.max(0, Math.min(1, x));
|
|
36
|
+
this.mouseY = Math.max(0, Math.min(1, y));
|
|
37
|
+
this.isActive = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Handle mouse button press from external handler
|
|
42
|
+
* @param {number} button - Mouse button number (0 = left, 1 = middle, 2 = right)
|
|
43
|
+
*/
|
|
44
|
+
onMouseDown(button) {
|
|
45
|
+
if (!this.via) return;
|
|
46
|
+
|
|
47
|
+
// Only handle left mouse button (button 0)
|
|
48
|
+
if (button === 0) {
|
|
49
|
+
this.via.setJoystickButton(0, true);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Handle mouse button release from external handler
|
|
55
|
+
* @param {number} button - Mouse button number (0 = left, 1 = middle, 2 = right)
|
|
56
|
+
*/
|
|
57
|
+
onMouseUp(button) {
|
|
58
|
+
if (!this.via) return;
|
|
59
|
+
|
|
60
|
+
// Only handle left mouse button (button 0)
|
|
61
|
+
if (button === 0) {
|
|
62
|
+
this.via.setJoystickButton(0, false);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if mouse joystick is enabled and ready
|
|
68
|
+
* @returns {boolean} True if mouse joystick can handle events
|
|
69
|
+
*/
|
|
70
|
+
isEnabled() {
|
|
71
|
+
return !!this.via;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get analog value from mouse position for the specified channel
|
|
76
|
+
* @param {number} channel - The ADC channel (0-3)
|
|
77
|
+
* @returns {number} A value between 0 and 0xffff
|
|
78
|
+
*/
|
|
79
|
+
getValue(channel) {
|
|
80
|
+
switch (channel) {
|
|
81
|
+
case 0:
|
|
82
|
+
// X axis for joystick 1
|
|
83
|
+
// BBC Micro: left=65535, right=0
|
|
84
|
+
return Math.floor((1 - this.mouseX) * 0xffff);
|
|
85
|
+
case 1:
|
|
86
|
+
// Y axis for joystick 1
|
|
87
|
+
// BBC Micro: up=65535, down=0
|
|
88
|
+
return Math.floor((1 - this.mouseY) * 0xffff);
|
|
89
|
+
case 2:
|
|
90
|
+
case 3:
|
|
91
|
+
// Joystick 2 axes (not used for mouse)
|
|
92
|
+
return 0x8000;
|
|
93
|
+
default:
|
|
94
|
+
return 0x8000;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Clean up when source is no longer needed
|
|
100
|
+
* Called by ADC when switching to a different source
|
|
101
|
+
*/
|
|
102
|
+
dispose() {
|
|
103
|
+
// Reset state
|
|
104
|
+
this.via = null;
|
|
105
|
+
this.isActive = false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const BUFFER_SIZE = 65536;
|
|
2
|
+
|
|
3
|
+
registerProcessor(
|
|
4
|
+
"music5000",
|
|
5
|
+
class extends AudioWorkletProcessor {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.port.onmessage = this.onmessage.bind(this);
|
|
9
|
+
|
|
10
|
+
this.sampleBuffer = new Float32Array(BUFFER_SIZE);
|
|
11
|
+
this.readPosition = 0;
|
|
12
|
+
this.writePosition = 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
onmessage(event) {
|
|
16
|
+
// Receive a new 128-byte sample from the audio processor and write to the FIFO buffer
|
|
17
|
+
const { data } = event;
|
|
18
|
+
const sample = new Float32Array(data);
|
|
19
|
+
|
|
20
|
+
if (this.writePosition === BUFFER_SIZE) {
|
|
21
|
+
this.writePosition = 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < data.length; i++) {
|
|
25
|
+
this.sampleBuffer[this.writePosition++] = sample[i] / 32768.0; // Conversion to a range -1 to +1
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
process(inputs, outputs) {
|
|
30
|
+
// Playback
|
|
31
|
+
if (this.readPosition === BUFFER_SIZE) {
|
|
32
|
+
this.readPosition = 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < outputs[0][0].length; i++) {
|
|
36
|
+
outputs[0][0][i] = this.sampleBuffer[this.readPosition++];
|
|
37
|
+
outputs[0][1][i] = this.sampleBuffer[this.readPosition++];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
);
|
package/src/music5000.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Code ported from Beebem (C to .js) by Jason Robson
|
|
4
|
+
const AUDIO_BUFFER_SIZE = 256;
|
|
5
|
+
|
|
6
|
+
const RAM_SIZE = 2048;
|
|
7
|
+
const WAVE_TABLE_SIZE = 128;
|
|
8
|
+
const WAVE_TABLES = 14;
|
|
9
|
+
const NUM_CHANNELS = 16;
|
|
10
|
+
const CHANNEL_REG_OFFSET = WAVE_TABLES * WAVE_TABLE_SIZE;
|
|
11
|
+
const CHANNEL_ROW_SIZE = 128;
|
|
12
|
+
|
|
13
|
+
// Control register bits
|
|
14
|
+
const CTRL_STEREO_POS = 0xf;
|
|
15
|
+
const CTRL_INVERT_WAVE = 1 << 4;
|
|
16
|
+
const CTRL_MODULATE_ADJ = 1 << 5;
|
|
17
|
+
|
|
18
|
+
// Data bits
|
|
19
|
+
const DATA_SIGN = 1 << 7;
|
|
20
|
+
const DATA_VALUE = 0x7f;
|
|
21
|
+
const FREQ_DISABLE = 1 << 0;
|
|
22
|
+
const NOT_FREQ_DISABLE = 0xfe;
|
|
23
|
+
|
|
24
|
+
// Channel register sets
|
|
25
|
+
const REG_SET_NORMAL = 0;
|
|
26
|
+
const REG_SET_ALT = 1;
|
|
27
|
+
|
|
28
|
+
export class Music5000 {
|
|
29
|
+
constructor(onBuffer) {
|
|
30
|
+
this._onBufferMusic5000 = onBuffer;
|
|
31
|
+
|
|
32
|
+
this.waveRam = new Uint8Array(RAM_SIZE);
|
|
33
|
+
this.phaseRam = new Uint32Array(NUM_CHANNELS);
|
|
34
|
+
this.cycleCount = 0;
|
|
35
|
+
this.curCh = 0;
|
|
36
|
+
this.activeRegSet = REG_SET_NORMAL;
|
|
37
|
+
this.sampleLeft = 0;
|
|
38
|
+
this.sampleRight = 0;
|
|
39
|
+
|
|
40
|
+
this.chordBase = [0, 8.25, 24.75, 57.75, 123.75, 255.75, 519.75, 1047.75];
|
|
41
|
+
this.stepInc = [0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0];
|
|
42
|
+
|
|
43
|
+
this.stereoLeft = [0, 0, 0, 0, 0, 0, 0, 0, 100, 100, 100, 83, 67, 50, 33, 17];
|
|
44
|
+
this.stereoRight = [100, 100, 100, 100, 100, 100, 100, 100, 0, 0, 0, 17, 33, 50, 67, 83];
|
|
45
|
+
|
|
46
|
+
this.D2ATable = new Uint16Array(128);
|
|
47
|
+
|
|
48
|
+
this.sampleBuffer = new Float64Array(AUDIO_BUFFER_SIZE);
|
|
49
|
+
this.position = 0;
|
|
50
|
+
|
|
51
|
+
// Helper functions to access the register set and wavetable
|
|
52
|
+
this.cReg_freqLow = function (channel, regSet) {
|
|
53
|
+
return this.waveRam[CHANNEL_REG_OFFSET + regSet * CHANNEL_ROW_SIZE + 0 * NUM_CHANNELS + channel];
|
|
54
|
+
};
|
|
55
|
+
this.cReg_freqMedium = function (channel, regSet) {
|
|
56
|
+
return this.waveRam[CHANNEL_REG_OFFSET + regSet * CHANNEL_ROW_SIZE + 1 * NUM_CHANNELS + channel];
|
|
57
|
+
};
|
|
58
|
+
this.cReg_freqHigh = function (channel, regSet) {
|
|
59
|
+
return this.waveRam[CHANNEL_REG_OFFSET + regSet * CHANNEL_ROW_SIZE + 2 * NUM_CHANNELS + channel];
|
|
60
|
+
};
|
|
61
|
+
this.cReg_waveformReg = function (channel, regSet) {
|
|
62
|
+
return this.waveRam[CHANNEL_REG_OFFSET + regSet * CHANNEL_ROW_SIZE + 5 * NUM_CHANNELS + channel];
|
|
63
|
+
};
|
|
64
|
+
this.cReg_amplitudeReg = function (channel, regSet) {
|
|
65
|
+
return this.waveRam[CHANNEL_REG_OFFSET + regSet * CHANNEL_ROW_SIZE + 6 * NUM_CHANNELS + channel];
|
|
66
|
+
};
|
|
67
|
+
this.cReg_controlReg = function (channel, regSet) {
|
|
68
|
+
return this.waveRam[CHANNEL_REG_OFFSET + regSet * CHANNEL_ROW_SIZE + 7 * NUM_CHANNELS + channel];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.waveTableVal = function (table, b) {
|
|
72
|
+
return this.waveRam[table * WAVE_TABLE_SIZE + b];
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
reset(hard) {
|
|
77
|
+
if (hard) {
|
|
78
|
+
console.log("Music 5000: initialisation");
|
|
79
|
+
|
|
80
|
+
// Build the D2A table
|
|
81
|
+
let i = 0;
|
|
82
|
+
for (let chord = 0; chord < 8; chord++) {
|
|
83
|
+
let val = this.chordBase[chord];
|
|
84
|
+
for (let step = 0; step < 16; step++) {
|
|
85
|
+
this.D2ATable[i] = Math.floor(val * 4); // Multiply up to get an integer
|
|
86
|
+
val += this.stepInc[chord];
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Clear RAM
|
|
93
|
+
for (let w = 0; w < RAM_SIZE; w++) {
|
|
94
|
+
this.waveRam[w] = 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (let p = 0; p < NUM_CHANNELS; p++) {
|
|
98
|
+
this.phaseRam[p] = 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
read(page, addr) {
|
|
103
|
+
// Bit0 unused
|
|
104
|
+
const offset = ((page & 0x0e) << 7) + addr;
|
|
105
|
+
return this.waveRam[offset];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
write(page, addr, value) {
|
|
109
|
+
// Bit0 unused
|
|
110
|
+
const offset = ((page & 0x0e) << 7) + (addr & 0xff);
|
|
111
|
+
this.waveRam[offset] = value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
polltime(cycles) {
|
|
115
|
+
let c4d,
|
|
116
|
+
freq,
|
|
117
|
+
offset,
|
|
118
|
+
wavetable,
|
|
119
|
+
amplitude,
|
|
120
|
+
control,
|
|
121
|
+
data,
|
|
122
|
+
sign,
|
|
123
|
+
pos = 0 >>> 0;
|
|
124
|
+
|
|
125
|
+
// Convert 2MHz 6502 cycles to 6MHz Music5000 cycles
|
|
126
|
+
this.cycleCount += cycles * 3;
|
|
127
|
+
|
|
128
|
+
// Need 8 cycles to update a channel
|
|
129
|
+
while (this.cycleCount >= 8) {
|
|
130
|
+
// Update phase for active register set
|
|
131
|
+
if (this.cReg_freqLow(this.curCh, this.activeRegSet) & FREQ_DISABLE) {
|
|
132
|
+
this.phaseRam[this.curCh] = 0;
|
|
133
|
+
c4d = 0;
|
|
134
|
+
} else {
|
|
135
|
+
freq =
|
|
136
|
+
(this.cReg_freqHigh(this.curCh, this.activeRegSet) << 16) +
|
|
137
|
+
(this.cReg_freqMedium(this.curCh, this.activeRegSet) << 8) +
|
|
138
|
+
(this.cReg_freqLow(this.curCh, this.activeRegSet) & NOT_FREQ_DISABLE);
|
|
139
|
+
this.phaseRam[this.curCh] += freq;
|
|
140
|
+
c4d = this.phaseRam[this.curCh] & (1 << 24);
|
|
141
|
+
this.phaseRam[this.curCh] &= 0xffffff;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Pull wave sample out for the active register set
|
|
145
|
+
offset = (this.phaseRam[this.curCh] >> 17) & 0x7f;
|
|
146
|
+
wavetable = this.cReg_waveformReg(this.curCh, this.activeRegSet) >> 4;
|
|
147
|
+
data = this.waveTableVal(wavetable, offset);
|
|
148
|
+
amplitude = this.cReg_amplitudeReg(this.curCh, this.activeRegSet);
|
|
149
|
+
control = this.cReg_controlReg(this.curCh, this.activeRegSet);
|
|
150
|
+
sign = data & DATA_SIGN;
|
|
151
|
+
data &= DATA_VALUE;
|
|
152
|
+
|
|
153
|
+
// Modulate the next channel?
|
|
154
|
+
if (control & CTRL_MODULATE_ADJ && (sign || c4d)) this.activeRegSet = REG_SET_ALT;
|
|
155
|
+
else this.activeRegSet = REG_SET_NORMAL;
|
|
156
|
+
|
|
157
|
+
if (amplitude > 0x80) amplitude = 0x80;
|
|
158
|
+
data = (data * amplitude) / 0x80;
|
|
159
|
+
|
|
160
|
+
let sample = this.D2ATable[parseInt(data)];
|
|
161
|
+
if (control & CTRL_INVERT_WAVE) sign ^= DATA_SIGN;
|
|
162
|
+
if (sign) sample = -sample;
|
|
163
|
+
|
|
164
|
+
// Stereo
|
|
165
|
+
pos = control & CTRL_STEREO_POS;
|
|
166
|
+
this.sampleLeft += (sample * this.stereoLeft[pos]) / 100;
|
|
167
|
+
this.sampleRight += (sample * this.stereoRight[pos]) / 100;
|
|
168
|
+
|
|
169
|
+
this.curCh++;
|
|
170
|
+
if (this.curCh === NUM_CHANNELS) {
|
|
171
|
+
this.curCh = 0;
|
|
172
|
+
this.activeRegSet = REG_SET_NORMAL;
|
|
173
|
+
|
|
174
|
+
// Range check
|
|
175
|
+
if (this.sampleLeft < -32768) this.sampleLeft = -32768;
|
|
176
|
+
else if (this.sampleLeft > 32767) this.sampleLeft = 32767;
|
|
177
|
+
|
|
178
|
+
if (this.sampleRight < -32768) this.sampleRight = -32768;
|
|
179
|
+
else if (this.sampleRight > 32767) this.sampleRight = 32767;
|
|
180
|
+
|
|
181
|
+
this.sampleBuffer[this.position++] = this.sampleLeft;
|
|
182
|
+
this.sampleBuffer[this.position++] = this.sampleRight;
|
|
183
|
+
|
|
184
|
+
if (this.position === AUDIO_BUFFER_SIZE) {
|
|
185
|
+
this._onBufferMusic5000(this.sampleBuffer);
|
|
186
|
+
this.position = 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.sampleLeft = 0;
|
|
190
|
+
this.sampleRight = 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.cycleCount -= 8;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export class FakeMusic5000 {
|
|
199
|
+
constructor() {
|
|
200
|
+
this.reset = function () {};
|
|
201
|
+
this.polltime = function () {};
|
|
202
|
+
this.read = function () {
|
|
203
|
+
return 0;
|
|
204
|
+
};
|
|
205
|
+
this.write = function () {};
|
|
206
|
+
}
|
|
207
|
+
}
|