# SBTALKER.EXE

SBTALKER.EXE is a DOS TSR that provides a text to speech engine.

## Segment roles

The observed code segments have distinct responsibilities, although a DOS
segment value alone does not prove an original source-module boundary:

| Segment | Observed role |
| --- | --- |
| `0823` | TSR installer, resident interrupt interface, service dispatcher, and stack/context switching. |
| `0935` | Text and dictionary processing: input conversion, phoneme-chunk construction, and span handling. |
| `0D72` | General support routines used by the text engine, including length-prefixed string operations, byte copying, and bit-set membership tests. |
| `1483` | Phoneme parser and sample renderer. Its far callback table is patched to point at the loaded `.DRV` overlay. |

The speech code also uses `30F7` as its data and driver-stack segment. The
`.DRV` overlay is loaded at a separately calculated segment; the installer
patches the segment halves of five far callback pointers at `1483:0008`,
`000C`, `0010`, `0014`, and `0018` before loading it.

## Load process

The installer at 0823:0CEC frees its DOS environment, checks INT 2Fh for an
existing copy, parses `/P` (four hexadecimal digits, copied to the driver
interface) and `/D` (up to eight characters of a `.DRV` basename), loads a
driver overlay with DOS INT 21h AX=4B03h, and terminates resident with
INT 21h AX=3100h. The loader grows its DOS allocation to fit the overlay.

If EMS is available, installation can copy a portion of the conventional
memory image into EMS and reduce the retained DOS block.

## Resident driver interface

The resident INT 2Fh handler at 0823:0898 recognizes these requests:

- AX=FBFBh: report installation with AX=0 and ES:BX=resident CS:01CEh.
- AX=FB00h: clear eleven state words beginning at CS:01D8h.
- AX=FBFFh: uninstall if this handler is the current INT 2Fh vector;
  otherwise return AX=FFFBh.

Other requests chain to the previous INT 2Fh handler. The returned
interface block contains a far pointer to the service entry (CS:0AE2h)
at ES:BX+4 and the configured `/P` port word at ES:BX+8. The first four
bytes of the block remain unidentified.

The service entry temporarily switches stacks, swaps in a saved interrupt
vector table and optional EMS image, installs temporary INT 09h and INT 15h
vectors, calls the dispatcher at 0823:0F5Bh, and restores the prior context.

### Service operation dispatch

Call the far service pointer at interface-block offset +4 with the operation
number in AL. The dispatcher at 0823:0F5Bh handles 0Ch and 0Dh directly.
For other values it clears AH, doubles AX, and jumps through the word at
`CS:0D8Eh + 2*AL`. The table has exactly ten little-endian near offsets,
covering AL=00h–09h. Code begins immediately after them at 0823:0DA2.
There is no bounds check: AL=0Ah or 0Bh would interpret instructions as
offsets, as would values above 0Dh. Only 0Ch and 0Dh bypass the table.

| AL | Dispatch destination | Known effect |
| --- | --- | --- |
| 00h | `0823:0FDC` | Run the driver text/speech processor once (`0935:3D66`) using the resident buffer pair and CS:03F8 as an argument. |
| 01h | `0823:100B` | Set the resident buffer pointer and driver stack top, then initialize driver state (`0935:0B51`). This is also called during installation. |
| 02h | `0823:1039` | Pass five configuration words from CS:03EE–03F6 to the driver (`0935:0004`). |
| 03h | `0823:105C` | Normalize and insert a resident text-buffer pair into the driver's search tree (`0935:049A`). |
| 04h | `0823:107A` | Process pending dictionary text (`0935:01E6`), controlled by the low bit of CS:03FA. |
| 05h | `0823:108A` | Normalize and remove a text entry from the driver's search tree (`0935:071F`). |
| 06h | `0823:109F` | Fetch a dictionary entry into the resident buffer pair (`0935:0801`); the callee returns an associated far pointer in DX:AX. |
| 07h | `0823:10BD` | Speak text through `0935:3E9D`, then clear the first byte of each resident text buffer. |
| 08h | `0823:0F92` | Pass the resident text buffer and four configuration words to the phoneme/speech routine `1483:013E`. |
| 09h | `0823:0FB7` | Same instruction sequence and effect as operation 08h; its final jump has a different displacement. |
| 0Ah–0Bh | No table entry | The lookup reads instruction bytes following the table as offsets. |
| 0Ch | Direct branch to `0823:10E5` | Pop a far pointer after the caller's return address and register it as the optional resize callback at `CS:0DBBh` |
| 0Dh | Direct branch to `0823:1104` | Pop a far pointer after the caller's return address and register it at `CS:0DBFh` |
| 0Eh and above | No table entry | The lookup reads other code or data as offsets. |

These ten words were recovered with `rev mem read --count 96 0823:0D8E`.
Each destination was added to the function database and disassembled with
`rev func new` and `rev func read`. Operations 00h–09h all call the helper at
0823:0DA2 to save the current SP, make SS equal to the already-selected
driver data segment (30F7h), and load the driver SP from CS:0E59. The helper
preserves its near return address across that stack switch. Each handler
later restores the dispatcher stack and returns through its saved return
address. CS:0E57 is the saved SP; operation 01h initializes CS:0E59 to
337Ah, the offset in segment 30F7h equivalent to 3331:0FDA.

### Operation 07h speech path

`0823:10BD` passes the resident input buffer to `0935:3E9D`, then clears
the length bytes of that buffer and its companion at offset +100h. The
driver uses length-prefixed strings. `0935:3D66` starts at a one-based
input cursor, converts characters through class-specific handlers, and
returns one processed phoneme chunk. It uses the active eight-byte settings
record at `30F7:048C + 8*word[30F7:0166]` and applies prosody edits to the
chunk before returning it. A pending overflow fragment at `30F7:0060` is
copied into the new chunk before more input is processed.

`0935:3E9D` requests chunks until it gets an empty one. Within each chunk,
literal `M` and `F` bytes delimit spans. For each nonempty span,
`0935:3E14` passes the span and the active settings record to
`1483:013E`. The `M`/`F` branch changes a local state byte, but both paths
through `0935:3E14` pass the same arguments to the renderer; no different
rendering behavior from that byte is visible in this function. The renderer's
AX status is saved in a local word but is not checked by `0935:3E9D`.

`1483:013E` validates and converts the phoneme text to internal codes,
then renders sample data through callbacks in its code segment. Its helper
at `1483:0628` decodes packed sample records into a PCM buffer, and
`1483:07FE` emits the resulting bytes. The routine can abort on a keypress;
AX is zero on completion, FFFFh on keyboard abort, or an input position on
a parse error. The two useful string helpers on this path are
`0D72:538C` (bounded copy of a length-prefixed string) and `0D72:5909`
(insertion into one).

`0D72:5909` takes five stack arguments, in order: a one-based insertion
position (`[BP+6]`), far destination pointer (`[BP+8]`), maximum payload
length (`[BP+0C]`), far source pointer (`[BP+0E]`), and byte count
(`[BP+12]`). It checks the resulting length and position, shifts the
destination tail backward, copies the source bytes into the gap, and updates
the leading length byte. It has no meaningful return value; `RETF 0Eh`
removes its fourteen argument bytes.

### Sample output path

`1483:056D` indexes two consecutive 48-by-48 word tables at `1483:1490`
and `1483:2690` with the previous and current phoneme codes. A word of
`FFFFh` means no sample record for that pair. `1483:0628` decodes a selected
record using the 256-byte delta and bit-width tables at `1483:3890` and
`1483:3990`, producing waveform bytes in `30F7:0960`.

`1483:07FE` emits a block from that buffer. It builds temporary corrections
at the block edges in `30F7:0A84` to smooth transitions, then clears those
corrections after output. For each byte it adds the correction and maps the
result through one of ten consecutive 256-byte tables at `1483:0942`. A
mode flag selects either the corrected sample or its difference from the
preceding corrected sample as the lookup input. The `.DRV` callback at
`1483:0012` receives the block length, the callback at `1483:0006`
receives each mapped byte, and the callback at `1483:0016` ends the block.
The callbacks at `1483:000A` and `1483:000E` start and stop playback.

`1483:0523` chooses a reload value from the ten-word table at `1483:012A`
after clamping the sum of three timing indexes to 0 through 9. Its caller
`1483:071B` adds that value to a timing accumulator and uses the index to
adjust the remaining sample repeats: indexes 0-4 increment, 5 leaves the
count alone, and 6-9 decrement. When a decrement reaches zero, the helper
skips its caller's return address to end that render path.

Immediately after the output tables, `1483:1342` latches and reads the
16-bit PIT channel 0 counter through ports 43h and 40h.
