jsbeeb 1.22.4 → 1.24.0
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/README.md +2 -2
- package/package.json +16 -11
- package/src/6502.js +39 -42
- package/src/6502.opcodes.js +198 -123
- package/src/6847.js +4 -5
- package/src/6847_fontdata.js +0 -2
- package/src/acia.js +2 -3
- package/src/app/app.js +0 -1
- package/src/app/args.js +0 -2
- package/src/app/electron.js +4 -6
- package/src/app-bench.js +1 -3
- package/src/archive.js +243 -0
- package/src/basic-loader.js +0 -7
- package/src/basic-tokenise.js +19 -6
- package/src/bbc-palette.js +0 -2
- package/src/bbcdiscs.js +0 -2
- package/src/bem-snapshot.js +1 -3
- package/src/binary.js +175 -0
- package/src/cmos.js +0 -2
- package/src/ddnoise.js +0 -1
- package/src/debounce.js +7 -0
- package/src/disc-surface.js +0 -2
- package/src/disc.js +10 -9
- package/src/fake6502.js +7 -23
- package/src/fdc.js +9 -51
- package/src/filestore.js +10 -17
- package/src/hex.js +38 -0
- package/src/index.js +11 -0
- package/src/intel-fdc.js +18 -20
- package/src/jsbeeb.css +33 -0
- package/src/{utils_atom.js → keymap-atom.js} +2 -18
- package/src/{utils.js → keymap.js} +47 -610
- package/src/loader.js +63 -0
- package/src/machine-session.js +37 -177
- package/src/machine-spec.js +59 -0
- package/src/main.js +69 -184
- package/src/media-resolver.js +68 -0
- package/src/mmc.js +3 -2
- package/src/models.js +40 -4
- package/src/music5000.js +0 -2
- package/src/ppia.js +3 -4
- package/src/printer.js +7 -14
- package/src/relaynoise.js +0 -1
- package/src/rewind.js +0 -2
- package/src/sample-player.js +2 -3
- package/src/serial.js +0 -2
- package/src/snapshot-helpers.js +0 -2
- package/src/snapshot.js +0 -2
- package/src/soundchip.js +1 -1
- package/src/state-utils.js +0 -2
- package/src/sth.js +2 -4
- package/src/tapes.js +10 -4
- package/src/teletext.js +2 -3
- package/src/teletext_adaptor.js +2 -4
- package/src/teletext_data.js +0 -2
- package/src/test-machine.js +307 -0
- package/src/touchscreen.js +2 -4
- package/src/tube.js +5 -6
- package/src/typist.js +103 -0
- package/src/uef-snapshot.js +0 -2
- package/src/url-params.js +4 -5
- package/src/vdu-capture.js +113 -0
- package/src/via.js +3 -4
- package/src/video-filters/pal-composite.js +0 -2
- package/src/video-filters/passthrough-filter.js +0 -2
- package/src/video-filters/pixel-grid.js +0 -2
- package/src/video-filters/shader-program.js +0 -2
- package/src/video-filters/xbr-filter.js +0 -2
- package/src/video.js +12 -11
- package/src/wd-fdc.js +20 -20
- package/src/web/analogue-inputs.js +6 -8
- package/src/web/analytics.js +13 -0
- package/src/web/audio-handler.js +2 -2
- package/src/{audio-utils.js → web/audio-utils.js} +0 -2
- package/src/web/autoboot.js +10 -13
- package/src/{canvas.js → web/canvas.js} +4 -5
- package/src/web/config.js +42 -72
- package/src/web/console-surface.js +25 -0
- package/src/web/debug.js +8 -49
- package/src/web/disc-visualiser.js +0 -2
- package/src/web/display.js +21 -4
- package/src/{dom-utils.js → web/dom-utils.js} +1 -11
- package/src/web/drives.js +1 -1
- package/src/web/emulation-loop.js +54 -33
- package/src/web/front-panel.js +13 -12
- package/src/{gamepads.js → web/gamepads.js} +2 -6
- package/src/web/google-drive-picker.js +14 -9
- package/src/{google-drive.js → web/google-drive.js} +9 -10
- package/src/web/hfe-picker.js +5 -4
- package/src/web/icons.js +42 -0
- package/src/web/keyboard-setup.js +29 -40
- package/src/{keyboard.js → web/keyboard.js} +18 -119
- package/src/web/layout.js +4 -0
- package/src/web/local-disc.js +44 -0
- package/src/web/machine.js +27 -20
- package/src/web/media-loader.js +38 -147
- package/src/{microphone-input.js → web/microphone-input.js} +1 -1
- package/src/web/modals.js +6 -9
- package/src/web/page-actions.js +88 -0
- package/src/web/quick-settings.js +16 -15
- package/src/web/reporting.js +0 -7
- package/src/{rewind-thumbnail.js → web/rewind-thumbnail.js} +0 -2
- package/src/web/rewind-ui.js +29 -29
- package/src/web/run-controls.js +39 -0
- package/src/web/settings.js +64 -135
- package/src/web/snapshot-ui.js +10 -16
- package/src/{speech-output.js → web/speech-output.js} +0 -2
- package/src/web/sth-picker.js +9 -7
- package/src/web/url-state.js +20 -6
- package/src/econet/L3FS.dat +0 -0
- package/src/econet/scsi.dat +0 -0
- package/tests/test-machine.js +0 -609
package/src/url-params.js
CHANGED
|
@@ -295,17 +295,16 @@ export function guessModelFromHostname(hostname) {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
/**
|
|
298
|
-
* Parse disc
|
|
298
|
+
* Parse disc images from the query parameters
|
|
299
299
|
* @param {Object} parsedQuery - The query parameters
|
|
300
|
-
* @returns {Object} Object containing disc
|
|
300
|
+
* @returns {Object} Object containing disc information
|
|
301
301
|
* - discImage: disc image URL (?disc= or ?disc1=)
|
|
302
302
|
* - secondDiscImage: second disc URL (?disc2=)
|
|
303
|
-
* - tapeImage: tape image URL (?tape=)
|
|
304
303
|
* - mmcImage: MMC/SD card image URL (?mmc=, Atom only)
|
|
305
304
|
*/
|
|
306
305
|
export function parseMediaParams(parsedQuery) {
|
|
307
|
-
const { disc, disc1, disc2,
|
|
306
|
+
const { disc, disc1, disc2, mmc } = parsedQuery;
|
|
308
307
|
const discImage = disc || disc1;
|
|
309
308
|
|
|
310
|
-
return { discImage, secondDiscImage: disc2,
|
|
309
|
+
return { discImage, secondDiscImage: disc2, mmcImage: mmc };
|
|
311
310
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns the bytes a program sends to the VDU into text elements,
|
|
3
|
+
* `{ x, y, text, foreground, background, mode }`, following the cursor and
|
|
4
|
+
* colour codes and swallowing the parameters of the rest. Text-mode output
|
|
5
|
+
* only; anything visual wants a screenshot.
|
|
6
|
+
*/
|
|
7
|
+
// How many parameter bytes follow each BBC VDU code that takes any.
|
|
8
|
+
const SequenceParamCounts = {
|
|
9
|
+
1: 1, // next character to the printer only
|
|
10
|
+
17: 1, // COLOUR n
|
|
11
|
+
18: 2, // GCOL
|
|
12
|
+
19: 5, // define logical colour
|
|
13
|
+
22: 1, // MODE n
|
|
14
|
+
25: 5, // PLOT
|
|
15
|
+
28: 4, // define text window
|
|
16
|
+
29: 4, // define graphics origin
|
|
17
|
+
31: 2, // TAB(x,y)
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class VduTextCapture {
|
|
21
|
+
constructor(onElement, { isAtom = false } = {}) {
|
|
22
|
+
this.onElement = onElement;
|
|
23
|
+
this.isAtom = isAtom;
|
|
24
|
+
this.attributes = { x: 0, y: 0, text: "", foreground: 7, background: 0, mode: 7 };
|
|
25
|
+
this.currentText = "";
|
|
26
|
+
this.params = [];
|
|
27
|
+
this.nextN = 0;
|
|
28
|
+
this.vduProc = null;
|
|
29
|
+
this.sequenceHandlers = {
|
|
30
|
+
17: (p) => {
|
|
31
|
+
if (p[0] & 0x80) this.attributes.background = p[0] & 0xf;
|
|
32
|
+
else this.attributes.foreground = p[0] & 0xf;
|
|
33
|
+
},
|
|
34
|
+
22: (p) => {
|
|
35
|
+
Object.assign(this.attributes, { mode: p[0], x: 0, y: 0, foreground: 7, background: 0 });
|
|
36
|
+
},
|
|
37
|
+
31: (p) => {
|
|
38
|
+
this.attributes.x = p[0];
|
|
39
|
+
this.attributes.y = p[1];
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
flush() {
|
|
45
|
+
if (this.currentText.length) {
|
|
46
|
+
this.onElement({ ...this.attributes, text: this.currentText });
|
|
47
|
+
this.attributes.x += this.currentText.length;
|
|
48
|
+
}
|
|
49
|
+
this.currentText = "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
onChar(c) {
|
|
53
|
+
const attributes = this.attributes;
|
|
54
|
+
if (this.nextN) {
|
|
55
|
+
this.params.push(c);
|
|
56
|
+
if (--this.nextN === 0) {
|
|
57
|
+
if (this.vduProc) this.vduProc(this.params);
|
|
58
|
+
this.params = [];
|
|
59
|
+
this.vduProc = null;
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
switch (c) {
|
|
64
|
+
case 10:
|
|
65
|
+
this.flush();
|
|
66
|
+
attributes.y++;
|
|
67
|
+
return;
|
|
68
|
+
case 12:
|
|
69
|
+
this.flush();
|
|
70
|
+
attributes.x = 0;
|
|
71
|
+
attributes.y = 0;
|
|
72
|
+
return;
|
|
73
|
+
case 13:
|
|
74
|
+
this.flush();
|
|
75
|
+
attributes.x = 0;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// The Atom has no multi-byte VDU sequences; treating its bytes as
|
|
79
|
+
// parameters would swallow printable characters.
|
|
80
|
+
const paramCount = this.isAtom ? undefined : SequenceParamCounts[c];
|
|
81
|
+
if (paramCount !== undefined) {
|
|
82
|
+
this.flush();
|
|
83
|
+
this.nextN = paramCount;
|
|
84
|
+
this.vduProc = this.sequenceHandlers[c] ?? null;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (c >= 32 && c < 0x7f) this.currentText += String.fromCharCode(c);
|
|
88
|
+
else this.flush();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The decoder's position in the byte stream, for a machine snapshot to carry. */
|
|
92
|
+
snapshot() {
|
|
93
|
+
return {
|
|
94
|
+
attributes: { ...this.attributes },
|
|
95
|
+
currentText: this.currentText,
|
|
96
|
+
params: [...this.params],
|
|
97
|
+
nextN: this.nextN,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Puts back a snapshot(). The handler for a half-read sequence is a
|
|
103
|
+
* closure and does not survive, so such a sequence is dropped rather
|
|
104
|
+
* than printed as text.
|
|
105
|
+
*/
|
|
106
|
+
restore(state) {
|
|
107
|
+
Object.assign(this.attributes, state.attributes);
|
|
108
|
+
this.currentText = state.currentText;
|
|
109
|
+
this.params = [...state.params];
|
|
110
|
+
this.nextN = state.nextN;
|
|
111
|
+
this.vduProc = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
package/src/via.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import * as utils from "./utils.js";
|
|
1
|
+
import { BBC, getKeyMap } from "./keymap.js";
|
|
3
2
|
|
|
4
3
|
const ORB = 0x0,
|
|
5
4
|
ORA = 0x1,
|
|
@@ -602,7 +601,7 @@ export class SysVia extends Via {
|
|
|
602
601
|
}
|
|
603
602
|
|
|
604
603
|
setKeyLayout(map) {
|
|
605
|
-
this.keycodeToRowCol =
|
|
604
|
+
this.keycodeToRowCol = getKeyMap(map);
|
|
606
605
|
}
|
|
607
606
|
|
|
608
607
|
setVBlankInt(level) {
|
|
@@ -637,7 +636,7 @@ export class SysVia extends Via {
|
|
|
637
636
|
if (!mapping) return;
|
|
638
637
|
|
|
639
638
|
const [col, row, bbcShiftOverride] = mapping;
|
|
640
|
-
const [shiftCol, shiftRow] =
|
|
639
|
+
const [shiftCol, shiftRow] = BBC.SHIFT;
|
|
641
640
|
|
|
642
641
|
this.keys[col][row] = val;
|
|
643
642
|
|
package/src/video.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
import { Teletext } from "./teletext.js";
|
|
3
|
-
import * as utils from "./utils.js";
|
|
4
2
|
import { BbcDefaultPalette as NulaDefaultPalette } from "./bbc-palette.js";
|
|
5
3
|
import { Video6847 } from "./6847.js";
|
|
6
4
|
import { encodeLineGrid, texelsPerPixel, LineGridRows } from "./video-filters/pixel-grid.js";
|
|
5
|
+
import { makeFast32 } from "./binary.js";
|
|
6
|
+
|
|
7
|
+
const noop = () => {};
|
|
7
8
|
|
|
8
9
|
export const VDISPENABLE = 1 << 0;
|
|
9
10
|
export const HDISPENABLE = 1 << 1;
|
|
@@ -338,8 +339,8 @@ function table4bppOffset(ulamode, byte) {
|
|
|
338
339
|
export class Video {
|
|
339
340
|
constructor(isMaster, fb32_param, paint_ext_param, { isAtom = false } = {}) {
|
|
340
341
|
this.isMaster = isMaster;
|
|
341
|
-
this.fb32 =
|
|
342
|
-
this.collook =
|
|
342
|
+
this.fb32 = makeFast32(fb32_param);
|
|
343
|
+
this.collook = makeFast32(
|
|
343
344
|
new Uint32Array([
|
|
344
345
|
0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff,
|
|
345
346
|
]),
|
|
@@ -388,7 +389,7 @@ export class Video {
|
|
|
388
389
|
this.ulaMode = 0;
|
|
389
390
|
this.teletextMode = false;
|
|
390
391
|
this.displayEnableSkew = 0;
|
|
391
|
-
this.ulaPal =
|
|
392
|
+
this.ulaPal = makeFast32(new Uint32Array(16));
|
|
392
393
|
this.actualPal = new Uint8Array(16);
|
|
393
394
|
this.teletext = new Teletext();
|
|
394
395
|
this.cursorOn = false;
|
|
@@ -1020,8 +1021,8 @@ export class Video {
|
|
|
1020
1021
|
// Without interlace, frames are 312 scanlines. With interlace,
|
|
1021
1022
|
// both odd and even frames are 312.5 scanlines.
|
|
1022
1023
|
const isInterlace = !!(this.regs[8] & 1);
|
|
1023
|
-
// TODO
|
|
1024
|
-
// TODO
|
|
1024
|
+
// TODO(#1056) is this off-by-one? b2 uses regs[0]+1.
|
|
1025
|
+
// TODO(#1056) does this only hit at the half-scanline or is it a
|
|
1025
1026
|
// half-scanline counter that starts when an R7 hit is noticed?
|
|
1026
1027
|
const halfR0Hit = this.horizCounter === this.regs[0] >>> 1;
|
|
1027
1028
|
const isVsyncPoint = !isInterlace || !this.doEvenFrameLogic || halfR0Hit;
|
|
@@ -1056,7 +1057,7 @@ export class Video {
|
|
|
1056
1057
|
this.teletext.setDEW(this.inVSync);
|
|
1057
1058
|
}
|
|
1058
1059
|
|
|
1059
|
-
// TODO
|
|
1060
|
+
// TODO(#1056) this will be cleaner if we rework skew to have fetch
|
|
1060
1061
|
// independent from render.
|
|
1061
1062
|
const insideBorder = (this.dispEnabled & (HDISPENABLE | VDISPENABLE)) === (HDISPENABLE | VDISPENABLE);
|
|
1062
1063
|
if ((insideBorder || this.cursorDrawIndex) && this.dispEnabled & FRAMESKIPENABLE) {
|
|
@@ -1228,14 +1229,14 @@ export class FakeVideo {
|
|
|
1228
1229
|
read: function () {
|
|
1229
1230
|
return 0xff;
|
|
1230
1231
|
},
|
|
1231
|
-
write:
|
|
1232
|
+
write: noop,
|
|
1232
1233
|
};
|
|
1233
1234
|
this.ula = {
|
|
1234
1235
|
read: function () {
|
|
1235
1236
|
return 0xff;
|
|
1236
1237
|
},
|
|
1237
|
-
write:
|
|
1238
|
-
reset:
|
|
1238
|
+
write: noop,
|
|
1239
|
+
reset: noop,
|
|
1239
1240
|
disabled: false,
|
|
1240
1241
|
};
|
|
1241
1242
|
this.regs = new Uint8Array(32);
|
package/src/wd-fdc.js
CHANGED
|
@@ -7,7 +7,7 @@ import { BaseDiscDrive, DiscDrive } from "./disc-drive.js";
|
|
|
7
7
|
import { IbmDiscFormat } from "./disc.js";
|
|
8
8
|
// eslint-disable-next-line no-unused-vars
|
|
9
9
|
import { Scheduler } from "./scheduler.js";
|
|
10
|
-
import
|
|
10
|
+
import { hexbyte } from "./hex.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Commands.
|
|
@@ -147,8 +147,8 @@ export class WdFdc {
|
|
|
147
147
|
else this._drives = [new DiscDrive(0, scheduler), new DiscDrive(1, scheduler)];
|
|
148
148
|
|
|
149
149
|
this._isMaster = cpu.model.isMaster;
|
|
150
|
-
this._is1772 = false; // TODO
|
|
151
|
-
this._isOpus = false; // TODO
|
|
150
|
+
this._is1772 = false; // TODO(#1059) if we ever support Master Compact
|
|
151
|
+
this._isOpus = false; // TODO(#1059) if we ever support Opus
|
|
152
152
|
|
|
153
153
|
this._controlRegister = 0;
|
|
154
154
|
/** @type {Status|Number} */
|
|
@@ -219,7 +219,7 @@ export class WdFdc {
|
|
|
219
219
|
|
|
220
220
|
_updateNmi() {
|
|
221
221
|
const newLevel = this._isDrq | (this._isOpus ? false : this._isIntRq);
|
|
222
|
-
// TODO
|
|
222
|
+
// TODO(#1058) the cpu handling of NMIs is bad here. Should update to handle multiple
|
|
223
223
|
// NMI/interrupt sources. And when we do go back and implement the checks in the beebjit
|
|
224
224
|
// source here too.
|
|
225
225
|
this._cpu.NMI(newLevel);
|
|
@@ -337,7 +337,7 @@ export class WdFdc {
|
|
|
337
337
|
case 1:
|
|
338
338
|
case 2:
|
|
339
339
|
case 3:
|
|
340
|
-
this._logCommand(`control register now ${
|
|
340
|
+
this._logCommand(`control register now ${hexbyte(val)}`);
|
|
341
341
|
if (this._statusRegister & Status.busy && !this._isReset(val)) {
|
|
342
342
|
throw new Error(`Control register updated while busy; without reset`);
|
|
343
343
|
}
|
|
@@ -372,8 +372,8 @@ export class WdFdc {
|
|
|
372
372
|
_doCommand(val) {
|
|
373
373
|
if (!this._currentDrive) throw new Error("Command while no selected drive");
|
|
374
374
|
this._logCommand(
|
|
375
|
-
`command ${
|
|
376
|
-
`cr ${
|
|
375
|
+
`command ${hexbyte(val)} tr ${this._trackRegister} sr ${this._sectorRegister} dr ${this._dataRegister} ` +
|
|
376
|
+
`cr ${hexbyte(this._controlRegister)} ` +
|
|
377
377
|
`ptrk ${this._currentDrive.track} hpos ${this._currentDrive.headPosition}`,
|
|
378
378
|
);
|
|
379
379
|
const command = val & 0xf0;
|
|
@@ -387,7 +387,7 @@ export class WdFdc {
|
|
|
387
387
|
// rule here. Whether a command will do anything when busy seems to depend on
|
|
388
388
|
// the current command, the new command and also the current place in the
|
|
389
389
|
// internal state machine!
|
|
390
|
-
this._log(`command ${
|
|
390
|
+
this._log(`command ${hexbyte(val)} while busy with ${hexbyte(this._command)} - ignoring`);
|
|
391
391
|
return;
|
|
392
392
|
}
|
|
393
393
|
|
|
@@ -424,7 +424,7 @@ export class WdFdc {
|
|
|
424
424
|
this._commandType = 3;
|
|
425
425
|
break;
|
|
426
426
|
default:
|
|
427
|
-
throw new Error(`unimplemented command ${
|
|
427
|
+
throw new Error(`unimplemented command ${hexbyte(val)}`);
|
|
428
428
|
}
|
|
429
429
|
if (this._commandType === 2 || (this._commandType === 3 && val & CommandBits.typeIIorIIISettle))
|
|
430
430
|
this._isCommandSettle = true;
|
|
@@ -460,7 +460,7 @@ export class WdFdc {
|
|
|
460
460
|
// unreliable on a drive that takes a while to come up to speed.
|
|
461
461
|
if (val & CommandBits.disableSpinUp) {
|
|
462
462
|
this._indexPulseCount = 6;
|
|
463
|
-
this._log(`command ${
|
|
463
|
+
this._log(`command ${hexbyte(val)} spin up wait disabled, motor was off`);
|
|
464
464
|
this._dispatchCommand();
|
|
465
465
|
} else {
|
|
466
466
|
this._setState(State.spinUpWait);
|
|
@@ -476,7 +476,7 @@ export class WdFdc {
|
|
|
476
476
|
this._log(
|
|
477
477
|
`State ${this._state} -> ${state} @ tr ${this._trackRegister} ` +
|
|
478
478
|
`sr ${this._sectorRegister} dr ${this._dataRegister} ` +
|
|
479
|
-
`cr ${
|
|
479
|
+
`cr ${hexbyte(this._controlRegister)} ` +
|
|
480
480
|
`ptrk ${this._currentDrive.track} hpos ${this._currentDrive.headPosition}`,
|
|
481
481
|
);
|
|
482
482
|
}
|
|
@@ -599,7 +599,7 @@ export class WdFdc {
|
|
|
599
599
|
if (this._currentDrive && this._currentDrive.spinning) {
|
|
600
600
|
if (!isMotorOn) {
|
|
601
601
|
throw new Error(
|
|
602
|
-
`Unexpected motor control bit off when setting the control register to ${
|
|
602
|
+
`Unexpected motor control bit off when setting the control register to ${hexbyte(val)}`,
|
|
603
603
|
);
|
|
604
604
|
}
|
|
605
605
|
this._currentDrive.stopSpinning();
|
|
@@ -965,8 +965,8 @@ export class WdFdc {
|
|
|
965
965
|
this._deliverData = 0xa1;
|
|
966
966
|
return true;
|
|
967
967
|
}
|
|
968
|
-
// TODO
|
|
969
|
-
// Note
|
|
968
|
+
// TODO(#1059) sync to c2 (5224).
|
|
969
|
+
// Note that an early, naive attempt had it triggered in the middle of the sector data,
|
|
970
970
|
// so we'll need to study how it actually works in detail.
|
|
971
971
|
// Tag the byte after 3 sync bytes as a marker.
|
|
972
972
|
if ((this._markDetector & 0xffffffffffff0000n) === 0x4489448944890000n) {
|
|
@@ -980,8 +980,8 @@ export class WdFdc {
|
|
|
980
980
|
Number(this._markDetector & 0xffffffffn),
|
|
981
981
|
);
|
|
982
982
|
if (!iffyPulses && clocks === 0xc7) {
|
|
983
|
-
// TODO
|
|
984
|
-
// This suggests that a wider
|
|
983
|
+
// TODO(#1059) see http://info-coach.fr/atari/documents/_mydoc/WD1772-JLG.pdf
|
|
984
|
+
// This suggests that a wider range of byte values will function as markers. It may also differ FM vs. MFM.
|
|
985
985
|
if (data === 0xf8 || data === 0xfb || data === 0xfe) {
|
|
986
986
|
// Resync to marker.
|
|
987
987
|
this._deliverData = data;
|
|
@@ -1084,7 +1084,7 @@ export class WdFdc {
|
|
|
1084
1084
|
case 0xc2:
|
|
1085
1085
|
return IbmDiscFormat.mfmC2Sync;
|
|
1086
1086
|
default:
|
|
1087
|
-
throw new Error(`Bad marker byte ${
|
|
1087
|
+
throw new Error(`Bad marker byte ${hexbyte(byte)}`);
|
|
1088
1088
|
}
|
|
1089
1089
|
}
|
|
1090
1090
|
|
|
@@ -1119,7 +1119,7 @@ export class WdFdc {
|
|
|
1119
1119
|
// EMU NOTE: leave DRQ alone, if it is raised, leave it raised.
|
|
1120
1120
|
if (this._doRaiseIntRq) this._setIntRq(true);
|
|
1121
1121
|
|
|
1122
|
-
this._logCommand(`result status ${
|
|
1122
|
+
this._logCommand(`result status ${hexbyte(this._statusRegister)}`);
|
|
1123
1123
|
}
|
|
1124
1124
|
|
|
1125
1125
|
_checkVerify() {
|
|
@@ -1221,7 +1221,7 @@ export class WdFdc {
|
|
|
1221
1221
|
if (isCrcError) this._statusRegister |= Status.crcError;
|
|
1222
1222
|
// Unlike the 8271, read address returns just a single record. It is also not synchronized
|
|
1223
1223
|
// to the index pulse.
|
|
1224
|
-
// EMU TODO
|
|
1224
|
+
// EMU TODO(#1059) it's likely that timing is generally off for most states,
|
|
1225
1225
|
// i.e. the 1770 takes various numbers of internal clock cycles before it
|
|
1226
1226
|
// delivers the CRC error, before it goes not busy, etc.
|
|
1227
1227
|
// EMU NOTE: must not clear busy flag right away. The 1770 delivers the
|
|
@@ -1435,7 +1435,7 @@ export class WdFdc {
|
|
|
1435
1435
|
}
|
|
1436
1436
|
|
|
1437
1437
|
export class NoiseAwareWdFdc extends WdFdc {
|
|
1438
|
-
// TODO
|
|
1438
|
+
// TODO(#1061) consider deduplicating with the IntelFdc equivalent.
|
|
1439
1439
|
constructor(cpu, ddNoise, scheduler, debugFlags) {
|
|
1440
1440
|
super(cpu, scheduler, undefined, debugFlags);
|
|
1441
1441
|
let nextSeekTime = 0;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GamepadSource } from "../gamepad-source.js";
|
|
2
|
-
import { MicrophoneInput } from "
|
|
2
|
+
import { MicrophoneInput } from "./microphone-input.js";
|
|
3
3
|
import { MouseJoystickSource } from "../mouse-joystick-source.js";
|
|
4
4
|
import { calculateMouseCoordinates } from "../mouse-coordinates.js";
|
|
5
5
|
import { toast } from "./toast.js";
|
|
@@ -12,10 +12,9 @@ const AdcChannelCount = 4;
|
|
|
12
12
|
* routed to whichever of them wants it.
|
|
13
13
|
*/
|
|
14
14
|
export class AnalogueInputs {
|
|
15
|
-
constructor({ processor, screenCanvas, getGamepads,
|
|
15
|
+
constructor({ processor, screenCanvas, getGamepads, settings, audioHandler }) {
|
|
16
16
|
this.processor = processor;
|
|
17
|
-
this.
|
|
18
|
-
this.config = config;
|
|
17
|
+
this.settings = settings;
|
|
19
18
|
|
|
20
19
|
this.gamepadSource = new GamepadSource(getGamepads);
|
|
21
20
|
// Create MicrophoneInput but don't enable by default
|
|
@@ -38,7 +37,7 @@ export class AnalogueInputs {
|
|
|
38
37
|
if (processor.touchScreen) processor.touchScreen.onMouse(x, y, evt.buttons);
|
|
39
38
|
|
|
40
39
|
// Handle mouse joystick if enabled
|
|
41
|
-
if (
|
|
40
|
+
if (settings.mouseJoystickEnabled && this.mouseJoystickSource.isEnabled()) {
|
|
42
41
|
// Use the API methods instead of direct manipulation
|
|
43
42
|
this.mouseJoystickSource.onMouseMove(x, y);
|
|
44
43
|
|
|
@@ -89,8 +88,7 @@ export class AnalogueInputs {
|
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
clearMicrophoneChannel() {
|
|
92
|
-
this.
|
|
93
|
-
this.urlState.set({ microphoneChannel: undefined });
|
|
91
|
+
this.settings.set({ microphoneChannel: undefined });
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
async ensureMicrophoneRunning() {
|
|
@@ -109,7 +107,7 @@ export class AnalogueInputs {
|
|
|
109
107
|
|
|
110
108
|
async setupMicrophone() {
|
|
111
109
|
// The channel can have been turned off between the request and now.
|
|
112
|
-
if (this.
|
|
110
|
+
if (this.settings.microphoneChannel === undefined) return;
|
|
113
111
|
const micPermissionStatus = document.getElementById("micPermissionStatus");
|
|
114
112
|
micPermissionStatus.textContent = "Requesting microphone access...";
|
|
115
113
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { runningInNode } from "../loader.js";
|
|
2
|
+
|
|
3
|
+
export function noteEvent(category, type, label) {
|
|
4
|
+
if (
|
|
5
|
+
!runningInNode &&
|
|
6
|
+
(window.location.host.endsWith(".godbolt.org") || window.location.host.endsWith(".xania.org"))
|
|
7
|
+
) {
|
|
8
|
+
// Only note events on the public site
|
|
9
|
+
/*global gtag*/
|
|
10
|
+
gtag("event", category, { type, label });
|
|
11
|
+
}
|
|
12
|
+
console.log("event noted:", category, type, label);
|
|
13
|
+
}
|
package/src/web/audio-handler.js
CHANGED
|
@@ -2,8 +2,8 @@ import { FakeSoundChip, SoundChip, AtomSoundChip } from "../soundchip.js";
|
|
|
2
2
|
import { DdNoise, FakeDdNoise } from "../ddnoise.js";
|
|
3
3
|
import { RelayNoise, FakeRelayNoise } from "../relaynoise.js";
|
|
4
4
|
import { Music5000, FakeMusic5000 } from "../music5000.js";
|
|
5
|
-
import { createAudioContext } from "
|
|
6
|
-
import { toggle, fadeIn, fadeOut } from "
|
|
5
|
+
import { createAudioContext } from "./audio-utils.js";
|
|
6
|
+
import { toggle, fadeIn, fadeOut } from "./dom-utils.js";
|
|
7
7
|
import { toast } from "./toast.js";
|
|
8
8
|
|
|
9
9
|
import rendererUrl from "./audio-renderer.js?worker&url";
|
package/src/web/autoboot.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as utils from "../utils.js";
|
|
2
|
-
import * as utils_atom from "../utils_atom.js";
|
|
3
1
|
import * as tokeniser from "../basic-tokenise.js";
|
|
4
|
-
import {
|
|
2
|
+
import { installBasic } from "../basic-loader.js";
|
|
3
|
+
import { noteEvent } from "./analytics.js";
|
|
4
|
+
import { BBC } from "../keymap.js";
|
|
5
5
|
|
|
6
6
|
/** Booting and typing for the machine at startup: shift-break, *TAPE incantations and BASIC programs. */
|
|
7
7
|
export class Autoboot {
|
|
@@ -12,16 +12,13 @@ export class Autoboot {
|
|
|
12
12
|
this.sendKeys = sendKeys;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/** Convert text to machine-appropriate key sequences (BBC or Atom) */
|
|
16
15
|
stringToMachineKeys(text) {
|
|
17
|
-
return this.model.
|
|
16
|
+
return this.model.stringToKeys(text);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
boot(image) {
|
|
21
|
-
const BBC = utils.BBC;
|
|
22
|
-
|
|
23
20
|
console.log("Autobooting disc");
|
|
24
|
-
|
|
21
|
+
noteEvent("init", "autoboot", image);
|
|
25
22
|
|
|
26
23
|
// Shift-break simulation, hold SHIFT for 1000ms.
|
|
27
24
|
this.sendKeys([BBC.SHIFT, 1000], false);
|
|
@@ -29,7 +26,7 @@ export class Autoboot {
|
|
|
29
26
|
|
|
30
27
|
type(keys) {
|
|
31
28
|
console.log("Auto typing '" + keys + "'");
|
|
32
|
-
|
|
29
|
+
noteEvent("init", "autochain");
|
|
33
30
|
|
|
34
31
|
const bbcKeys = this.stringToMachineKeys(keys);
|
|
35
32
|
this.sendKeys([1000].concat(bbcKeys), false);
|
|
@@ -37,7 +34,7 @@ export class Autoboot {
|
|
|
37
34
|
|
|
38
35
|
chainTape() {
|
|
39
36
|
console.log("Auto Chaining Tape");
|
|
40
|
-
|
|
37
|
+
noteEvent("init", "autochain");
|
|
41
38
|
|
|
42
39
|
const bbcKeys = this.stringToMachineKeys('*TAPE\nCH.""\n');
|
|
43
40
|
this.sendKeys([1000].concat(bbcKeys), false);
|
|
@@ -45,7 +42,7 @@ export class Autoboot {
|
|
|
45
42
|
|
|
46
43
|
runTape() {
|
|
47
44
|
console.log("Auto Running Tape");
|
|
48
|
-
|
|
45
|
+
noteEvent("init", "autorun");
|
|
49
46
|
|
|
50
47
|
const bbcKeys = this.stringToMachineKeys("*TAPE\n*/\n");
|
|
51
48
|
this.sendKeys([1000].concat(bbcKeys), false);
|
|
@@ -53,7 +50,7 @@ export class Autoboot {
|
|
|
53
50
|
|
|
54
51
|
runBasic() {
|
|
55
52
|
console.log("Auto Running basic");
|
|
56
|
-
|
|
53
|
+
noteEvent("init", "autorunbasic");
|
|
57
54
|
|
|
58
55
|
const bbcKeys = this.stringToMachineKeys("RUN\n");
|
|
59
56
|
this.sendKeys([1000].concat(bbcKeys), false);
|
|
@@ -69,7 +66,7 @@ export class Autoboot {
|
|
|
69
66
|
const tokenised = await t.tokenise(prog);
|
|
70
67
|
|
|
71
68
|
const { processor } = this;
|
|
72
|
-
const idleAddr =
|
|
69
|
+
const idleAddr = this.model.idleAddress;
|
|
73
70
|
const hook = processor.debugInstruction.add((addr) => {
|
|
74
71
|
if (addr !== idleAddr) return;
|
|
75
72
|
installBasic(tokenised, {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { XbrFilter } from "./video-filters/xbr-filter.js";
|
|
1
|
+
import webglDebug from "../lib/webgl-debug.js";
|
|
2
|
+
import { PALCompositeFilter } from "../video-filters/pal-composite.js";
|
|
3
|
+
import { PassthroughFilter } from "../video-filters/passthrough-filter.js";
|
|
4
|
+
import { XbrFilter } from "../video-filters/xbr-filter.js";
|
|
6
5
|
|
|
7
6
|
const DISPLAY_MODE_FILTERS = {
|
|
8
7
|
pal: PALCompositeFilter,
|