Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 1x 10x 10x 10x 13x 13x 2x 2x 851x 846x 1x 1x 845x 1x 1x 9x 9x 835x 831x 831x 1x 10x 10x 10x 9x | /**
* RS-423 handler that routes transmitted bytes to the Web Speech API.
*
* BBC programs use *FX3,1 (or *FX3,3) to send OSWRCH output to the RS-423
* serial port, which on real hardware fed a Votrax Type 'N Talk synthesiser.
* We intercept at the ACIA hardware boundary and route to speechSynthesis.
*
* Byte handling is based on the Votrax Type 'N Talk Operator's Manual (1981):
* - Printable ASCII 0x20–0x7E: accumulated into the text buffer.
* - CR (0x0D): "TALK-CLR" — speaks the buffer and clears it. Multiple CR-
* terminated lines queue naturally via speechSynthesis.speak(). A future
* improvement could heuristically combine lines that arrive within a single
* frame (~20 ms) into one utterance, which would give modern TTS engines a
* better sentence to work with — but the simple per-line queue works well
* enough and has no surprising pauses or dropped output.
* - LF (0x0A): explicitly listed as null data in the manual; ignored.
* - ESC (0x1B): unit-select prefix for daisy-chained TNT units. ESC plus
* the following byte are consumed silently (not passed to speechSynthesis).
* - All other bytes: null data per the manual; ignored.
*/
// From the TNT Operator's Manual: "The input buffer can hold more than 750
// characters".
export const MAX_BUFFER = 750;
export class SpeechOutput {
constructor() {
this._buffer = "";
this._escapeNext = false;
this._enabled = false;
}
get enabled() {
return this._enabled;
}
set enabled(value) {
this._enabled = !!value;
if (!this._enabled) {
this._buffer = "";
Eif (typeof speechSynthesis !== "undefined") speechSynthesis.cancel();
}
}
/** RS-423 handler interface: called for each byte the BBC transmits. */
onTransmit(byte) {
if (!this._enabled) return;
if (this._escapeNext) {
this._escapeNext = false;
return;
}
switch (byte) {
case 0x1b: // ESC — next byte is a unit-select code, not text.
this._escapeNext = true;
return;
case 0x0d: // CR — TALK-CLR: speak current buffer and clear it.
this._flush();
return;
default:
if (byte >= 0x20 && byte <= 0x7e) {
this._buffer += String.fromCharCode(byte);
if (this._buffer.length >= MAX_BUFFER) this._flush();
}
// Everything else is null data — silently ignored.
}
}
/** RS-423 handler interface: nothing to send back to the BBC. */
tryReceive() {
return -1;
}
_flush() {
const text = this._buffer.trim();
this._buffer = "";
if (!text || typeof speechSynthesis === "undefined") return;
speechSynthesis.speak(new SpeechSynthesisUtterance(text));
}
}
|