Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sound Drivers and MIDPAK Timbres

Installed driver set

SETSOUND.BAT preserves the original installer names used to create the otherwise anonymous SOUND.* files:

Installed fileBytesInstaller sourceRole
SOUND.14,824soundrv.comDIGPAK Sound Blaster 16 resident driver, version 3.40.
SOUND.216,263midpak.advMiles/Creative Sound Blaster Pro FM MIDPAK hardware driver.
SOUND.313,312tmidpak.comMIDPAK loader and resident service, version 3.0 family.
SOUND.43,622midpak.adAudio Interface Library global OPL timbre library.
SOUND.54installer outputGame configuration and Bible-translation lock words.

The identities do not depend only on the batch file. SOUND.1 contains the signatures DIGPAK, Sound Blaster 16, John W. Ratcliff’s credit, and a version 3.40 identification string. SOUND.3 contains MIDPAK, MIDI Sound Package, and the Audio Solution credits. SOUND.2 begins with a Miles Design copyright string and identifies Creative Labs Sound Blaster Pro FM internally.

Interrupt interface

Both resident packages hook software interrupt 66h. Captain Bible contains 34 literal CD 66 instructions: 31 in small API wrappers, one in the driver detection routine, and two in bootstrap paths. The request number is the complete AX value. The recovered names agree with the contemporary Ralf Brown Interrupt List’s INT 66 catalog.

DIGPAK wrappers

Load offsetAXRecovered wrapperContract
0x8F080688digpak_play_8bit_soundPlay the DS:SI sound structure.
0x8F280689digpak_report_statusReturn 0 idle or 1 playing in AX.
0x8F2E068Adigpak_preformat_soundConvert the DS:SI structure for the output device.
0x8F41068Bdigpak_play_preformatted_soundPlay the preformatted DS:SI structure.
0x8F540694digpak_play_preformatted_dataNewer preformatted-data entry point.
0x8F67068Cdigpak_report_capabilitiesReturn capability mask, fixed rate, and ID pointer.
0x8F6D068Cdigpak_get_driver_idCopy the NUL-terminated ID returned in BX:CX.
0x8F95068Ddigpak_report_current_sample_addressReport approximate playback position.
0x8F9B068Edigpak_set_callback_addressSet callback BX:DX and callback DS.
0x8FAD068Fdigpak_stop_current_soundTerminate current digital playback.
0x8FB30690digpak_set_audio_hardwareSet IRQ, base address, and device value.
0x8FC80691digpak_report_callback_addressReturn callback and original caller DS.
0x8FCE0689digpak_wait_until_idlePoll status until AX becomes zero.
0x8FD80695digpak_post_audio_pendingStart or queue a sound structure.
0x8FEB0696digpak_get_audio_pending_statusReport playing/pending combination.
0x8FF10697digpak_set_stereo_panningSet DX from 0 right through 127 left.

The documented DIGPAK sound structure is 12 bytes: a far audio-data pointer, 16-bit byte count, far playback-status pointer, and 16-bit frequency. This exactly explains the live structure at DS:A0DE used by play_sound_effect_resource. The game first invokes 068A to preformat the independently decoded ABT PCM and then 068B to play it.

MIDPAK wrappers

Load offsetAXRecovered wrapperContract
0x8CFC0701midpak_get_digitized_capabilitiesProbe the companion digital driver.
0x8C7A0702midpak_play_sequencePlay sequence BX.
0x8C890703midpak_segue_sequenceSegue to BX using activation CX.
0x8C9B0704midpak_register_xmidiRegister data at CX:BX, length DI:SI.
0x8CB70705midpak_stop_midiStop MIDI playback.
0x8CBD0706midpak_remap_channelObsolete sequence/channel remap.
0x8D020707midpak_report_trigger_countReturn trigger count and ID.
0x8D080708midpak_reset_trigger_countReset the trigger counter.
0x8D0E0709midpak_sleepObsolete MIDI sleep.
0x8D14070Amidpak_awakeObsolete MIDI wake.
0x8D1A070Bmidpak_resumeResume playback.
0x8D20070Cmidpak_get_sequence_statusReturn stopped, playing, or done.
0x8D3E070Dmidpak_register_xmidi_fileRegister the filename at CX:BX.
0x8D26070Emidpak_get_relative_volumeReturn relative volume.
0x8D2C070Fmidpak_set_relative_volumeSet BX volume over CX time.

The bootstrap call at 0xABBB uses service 0710: BX is the segment of the loaded SOUND.2 .ADV, CX is zero, and DX:SI points to SOUND.4. This matches the MIDPAK load-driver contract exactly. play_music_resource then stops playback, sets volume, brackets registration with sleep/wake, registers the XMI byte buffer with 0704, and plays sequence zero with 0702.

The options path performs a separate stop/restart lifecycle. Turning music off calls the stop wrapper; turning it back on wakes MIDPAK and starts sequence zero from the retained registration. play_music_resource returns immediately when asked for the already current identifier, and identifier zero stops without registering another resource.

The outer game loop at 0x8C35 calls play_music_resource(0) every time main_menu_and_game_loop returns. A pending scene name or state flag 40 makes the inner loop return through 0x8740, so normal scene transitions stop music and clear the current identifier before the successor is initialized. This matters even when two consecutive scenes request the same music number: the second request is no longer rejected as an already-current no-op.

The order of checks in play_music_resource gives the disabled case different semantics. It compares the requested and current identifiers first, then returns before updating the identifier when the music enable word at 0x48 is zero. A transition while muted consequently retains the preceding registration. This is consistent with the options path, which can restart that registration when music is turned on again.

QEMU call and return evidence

The QEMU plugin now recognizes both int 21h and int 66h and reads the live EAX register at entry and return. QEMU represents the first x86 register with opaque handle value zero; treating that value as a valid handle was necessary to recover result AX rather than falling back to instruction inference. Return records also include the other general and segment registers.

A visible, host-muted startup capture produced this capability exchange:

CALL ... pc=0627:ABED int=66 AX=068C ...
RET  ... from=0627:ABED int=66 AX=0FC1 BX=2E88 CX=010C ...
         result="Sound Blaster 16"

The same query through wrapper 0x8F67 returns the same mask and ID pointer. Service 0710 returns AX zero after loading the FM driver and timbres. Observed DIGPAK status calls return both 0 and 1, matching idle and playing; MIDPAK 070C returns 1 while the title sequence is active. All captured driver returns have carry clear.

The current bounded trace contains 159 DOS calls and 7,844 driver calls. Most driver traffic is deliberate polling: 7,831 calls to DIGPAK status while the slowed one-instruction translation mode runs. Its ignored artifact is build/qemu-trace/dos-calls.log, SHA-256 ae9d9b5952171f35d9dff8a75548e399a15fb01ee27d4058b2800891766404a6.

SOUND.4 global timbre library

The file consists of a six-byte directory entry repeated until FF FF, then the timbres at their absolute offsets:

Directory offsetSizeMeaning
+01MIDI patch number.
+11Bank; 0x7F denotes percussion.
+24Absolute little-endian timbre offset.

The terminator ends at 0x440, which is also the first timbre offset. There are 128 bank-zero melodic entries for patches 0 through 127 and 53 percussion entries for notes 35 through 87 in bank 0x7F.

Every timbre is exactly 14 bytes and every directory offset is contiguous:

Timbre offsetSizeMeaning
+02Record length including this word: 14.
+21Signed melodic transpose, or percussion note.
+35Modulator OPL registers: AVEKM, KSL/TL, attack/decay, sustain/release, waveform.
+81Feedback/connection register.
+95Carrier registers in the same order.

This layout agrees byte-for-byte with the AIL structures recovered from Miles source by OPL3BankEditor’s pinned AIL parser. No padding or unreferenced trailer remains: 181 * 14 bytes extend from 0x440 to the exact 3,622-byte end of file.

Validate or list the bank with:

tools/inspect_midpak_ad.py CB/SOUND.4
tools/inspect_midpak_ad.py CB/SOUND.4 --list

The parser rejects a missing directory terminator, out-of-range or duplicate patches, a gap or overlap between records, unsupported record lengths, and a truncated final record.