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.
Files changed (112) hide show
  1. package/README.md +2 -2
  2. package/package.json +16 -11
  3. package/src/6502.js +39 -42
  4. package/src/6502.opcodes.js +198 -123
  5. package/src/6847.js +4 -5
  6. package/src/6847_fontdata.js +0 -2
  7. package/src/acia.js +2 -3
  8. package/src/app/app.js +0 -1
  9. package/src/app/args.js +0 -2
  10. package/src/app/electron.js +4 -6
  11. package/src/app-bench.js +1 -3
  12. package/src/archive.js +243 -0
  13. package/src/basic-loader.js +0 -7
  14. package/src/basic-tokenise.js +19 -6
  15. package/src/bbc-palette.js +0 -2
  16. package/src/bbcdiscs.js +0 -2
  17. package/src/bem-snapshot.js +1 -3
  18. package/src/binary.js +175 -0
  19. package/src/cmos.js +0 -2
  20. package/src/ddnoise.js +0 -1
  21. package/src/debounce.js +7 -0
  22. package/src/disc-surface.js +0 -2
  23. package/src/disc.js +10 -9
  24. package/src/fake6502.js +7 -23
  25. package/src/fdc.js +9 -51
  26. package/src/filestore.js +10 -17
  27. package/src/hex.js +38 -0
  28. package/src/index.js +11 -0
  29. package/src/intel-fdc.js +18 -20
  30. package/src/jsbeeb.css +33 -0
  31. package/src/{utils_atom.js → keymap-atom.js} +2 -18
  32. package/src/{utils.js → keymap.js} +47 -610
  33. package/src/loader.js +63 -0
  34. package/src/machine-session.js +37 -177
  35. package/src/machine-spec.js +59 -0
  36. package/src/main.js +69 -184
  37. package/src/media-resolver.js +68 -0
  38. package/src/mmc.js +3 -2
  39. package/src/models.js +40 -4
  40. package/src/music5000.js +0 -2
  41. package/src/ppia.js +3 -4
  42. package/src/printer.js +7 -14
  43. package/src/relaynoise.js +0 -1
  44. package/src/rewind.js +0 -2
  45. package/src/sample-player.js +2 -3
  46. package/src/serial.js +0 -2
  47. package/src/snapshot-helpers.js +0 -2
  48. package/src/snapshot.js +0 -2
  49. package/src/soundchip.js +1 -1
  50. package/src/state-utils.js +0 -2
  51. package/src/sth.js +2 -4
  52. package/src/tapes.js +10 -4
  53. package/src/teletext.js +2 -3
  54. package/src/teletext_adaptor.js +2 -4
  55. package/src/teletext_data.js +0 -2
  56. package/src/test-machine.js +307 -0
  57. package/src/touchscreen.js +2 -4
  58. package/src/tube.js +5 -6
  59. package/src/typist.js +103 -0
  60. package/src/uef-snapshot.js +0 -2
  61. package/src/url-params.js +4 -5
  62. package/src/vdu-capture.js +113 -0
  63. package/src/via.js +3 -4
  64. package/src/video-filters/pal-composite.js +0 -2
  65. package/src/video-filters/passthrough-filter.js +0 -2
  66. package/src/video-filters/pixel-grid.js +0 -2
  67. package/src/video-filters/shader-program.js +0 -2
  68. package/src/video-filters/xbr-filter.js +0 -2
  69. package/src/video.js +12 -11
  70. package/src/wd-fdc.js +20 -20
  71. package/src/web/analogue-inputs.js +6 -8
  72. package/src/web/analytics.js +13 -0
  73. package/src/web/audio-handler.js +2 -2
  74. package/src/{audio-utils.js → web/audio-utils.js} +0 -2
  75. package/src/web/autoboot.js +10 -13
  76. package/src/{canvas.js → web/canvas.js} +4 -5
  77. package/src/web/config.js +42 -72
  78. package/src/web/console-surface.js +25 -0
  79. package/src/web/debug.js +8 -49
  80. package/src/web/disc-visualiser.js +0 -2
  81. package/src/web/display.js +21 -4
  82. package/src/{dom-utils.js → web/dom-utils.js} +1 -11
  83. package/src/web/drives.js +1 -1
  84. package/src/web/emulation-loop.js +54 -33
  85. package/src/web/front-panel.js +13 -12
  86. package/src/{gamepads.js → web/gamepads.js} +2 -6
  87. package/src/web/google-drive-picker.js +14 -9
  88. package/src/{google-drive.js → web/google-drive.js} +9 -10
  89. package/src/web/hfe-picker.js +5 -4
  90. package/src/web/icons.js +42 -0
  91. package/src/web/keyboard-setup.js +29 -40
  92. package/src/{keyboard.js → web/keyboard.js} +18 -119
  93. package/src/web/layout.js +4 -0
  94. package/src/web/local-disc.js +44 -0
  95. package/src/web/machine.js +27 -20
  96. package/src/web/media-loader.js +38 -147
  97. package/src/{microphone-input.js → web/microphone-input.js} +1 -1
  98. package/src/web/modals.js +6 -9
  99. package/src/web/page-actions.js +88 -0
  100. package/src/web/quick-settings.js +16 -15
  101. package/src/web/reporting.js +0 -7
  102. package/src/{rewind-thumbnail.js → web/rewind-thumbnail.js} +0 -2
  103. package/src/web/rewind-ui.js +29 -29
  104. package/src/web/run-controls.js +39 -0
  105. package/src/web/settings.js +64 -135
  106. package/src/web/snapshot-ui.js +10 -16
  107. package/src/{speech-output.js → web/speech-output.js} +0 -2
  108. package/src/web/sth-picker.js +9 -7
  109. package/src/web/url-state.js +20 -6
  110. package/src/econet/L3FS.dat +0 -0
  111. package/src/econet/scsi.dat +0 -0
  112. 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 or tape images from the query parameters
298
+ * Parse disc images from the query parameters
299
299
  * @param {Object} parsedQuery - The query parameters
300
- * @returns {Object} Object containing disc and tape information
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, tape, mmc } = parsedQuery;
306
+ const { disc, disc1, disc2, mmc } = parsedQuery;
308
307
  const discImage = disc || disc1;
309
308
 
310
- return { discImage, secondDiscImage: disc2, tapeImage: tape, mmcImage: mmc };
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
- "use strict";
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 = utils.getKeyMap(map);
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] = utils.BBC.SHIFT;
639
+ const [shiftCol, shiftRow] = BBC.SHIFT;
641
640
 
642
641
  this.keys[col][row] = val;
643
642
 
@@ -1,5 +1,3 @@
1
- "use strict";
2
-
3
1
  // PAL Composite Video Filter - Approach D: Baseband Chroma Blending
4
2
  //
5
3
  // Simulates PAL composite video artifacts by encoding the framebuffer to a
@@ -1,5 +1,3 @@
1
- "use strict";
2
-
3
1
  import VERT_SHADER from "./shaders/passthrough.vert.glsl?raw";
4
2
  import FRAG_SHADER from "./shaders/passthrough.frag.glsl?raw";
5
3
  import { compileProgram } from "./shader-program.js";
@@ -1,5 +1,3 @@
1
- "use strict";
2
-
3
1
  // Describing the logical pixel grid of jsbeeb's framebuffer, which is a
4
2
  // 1024-wide *raster* rather than a grid of BBC pixels: one logical pixel covers
5
3
  // up to eight texels across and two down. Filters need to know that, and only
@@ -1,5 +1,3 @@
1
- "use strict";
2
-
3
1
  /**
4
2
  * Compile and link a shader program, throwing with the driver's own message if
5
3
  * either step fails.
@@ -1,5 +1,3 @@
1
- "use strict";
2
-
3
1
  // The xBR-lv2 display mode. See shaders/xbr.frag.glsl for the algorithm, and
4
2
  // tests/shader, which runs it in a browser and asserts on what it draws.
5
3
 
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 = utils.makeFast32(fb32_param);
342
- this.collook = utils.makeFast32(
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 = utils.makeFast32(new Uint32Array(16));
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: is this off-by-one? b2 uses regs[0]+1.
1024
- // TODO: does this only hit at the half-scanline or is it a
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: this will be cleaner if we rework skew to have fetch
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: utils.noop,
1232
+ write: noop,
1232
1233
  };
1233
1234
  this.ula = {
1234
1235
  read: function () {
1235
1236
  return 0xff;
1236
1237
  },
1237
- write: utils.noop,
1238
- reset: utils.noop,
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 * as utils from "./utils.js";
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 - if we ever support Master Compact
151
- this._isOpus = false; // TODO - if we ever support Opus
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: the cpu handling of NMIs is bad here. Should update to handle multiple
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 ${utils.hexbyte(val)}`);
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 ${utils.hexbyte(val)} tr ${this._trackRegister} sr ${this._sectorRegister} dr ${this._dataRegister} ` +
376
- `cr ${utils.hexbyte(this._controlRegister)} ` +
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 ${utils.hexbyte(val)} while busy with ${utils.hexbyte(this._command)} - ignoring`);
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 ${utils.hexbyte(val)}`);
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 ${utils.hexbyte(val)} spin up wait disabled, motor was off`);
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 ${utils.hexbyte(this._controlRegister)} ` +
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 ${utils.hexbyte(val)}`,
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: sync to c2 (5224).
969
- // Note than an early, naive attempt had it triggered in in the middle of the sector data,
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: see http://info-coach.fr/atari/documents/_mydoc/WD1772-JLG.pdf
984
- // This suggests that a wider ranges of byte values will function as markers. It may also differ FM vs. MFM.
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 ${utils.hexbyte(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 ${utils.hexbyte(this._statusRegister)}`);
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: it's likely that timing is generally off for most states,
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: consider deduplicating with the IntelFdc equivalent.
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 "../microphone-input.js";
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, urlState, config, audioHandler }) {
15
+ constructor({ processor, screenCanvas, getGamepads, settings, audioHandler }) {
16
16
  this.processor = processor;
17
- this.urlState = urlState;
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 (urlState.params.mouseJoystickEnabled && this.mouseJoystickSource.isEnabled()) {
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.config.setMicrophoneChannel(undefined);
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.urlState.params.microphoneChannel === undefined) return;
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
+ }
@@ -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 "../audio-utils.js";
6
- import { toggle, fadeIn, fadeOut } from "../dom-utils.js";
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";
@@ -1,5 +1,3 @@
1
- "use strict";
2
-
3
1
  /**
4
2
  * Create an AudioContext with a fallback for older WebKit browsers.
5
3
  * @param {AudioContextOptions} [options] - passed to the AudioContext constructor
@@ -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 { basicIdleAddr, installBasic } from "../basic-loader.js";
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.isAtom ? utils_atom.stringToATOMKeys(text) : utils.stringToBBCKeys(text);
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
- utils.noteEvent("init", "autoboot", image);
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
- utils.noteEvent("init", "autochain");
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
- utils.noteEvent("init", "autochain");
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
- utils.noteEvent("init", "autorun");
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
- utils.noteEvent("init", "autorunbasic");
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 = basicIdleAddr(processor.model);
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
- "use strict";
2
- import webglDebug from "./lib/webgl-debug.js";
3
- import { PALCompositeFilter } from "./video-filters/pal-composite.js";
4
- import { PassthroughFilter } from "./video-filters/passthrough-filter.js";
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,