Deathtrack Sound Effect Format
| Format type | Sound |
|---|---|
| Hardware | PC Speaker |
| Number of sounds | 11 |
| Sampling rate | N/A |
| Channel count | 1 |
| Bits per sample | Unknown |
| Compressed? | Yes |
| Tags? | None |
| Games |
The eleven sound effects of Deathtrack are small programs for a PC-speaker interpreter, stored in the SND: container at the start of PALS.BLK. They are not samples. Even when AdLib or MT-32 music is selected, effects play on the PC speaker; only the interpreter's tick rate differs between the devices.
The container is the resource chunk format.
File format
PALS.BLK starts with the following chunks:
| Chunk | Contents |
|---|---|
SND:INF: (6 bytes) |
Three UINT16LE words |
SND:IBM: (506 bytes) |
Enveloped bank, 501 bytes after decoding |
SND:ITM: |
Optional second bank; not present in the shipped file |
SND:INF:
| Data type | Name | Description |
|---|---|---|
| UINT16LE | unused | Discarded by the loader (0x59D9)
|
| UINT16LE | count | Number of effects (11) |
| UINT16LE | reserved | 0 |
SND:IBM: (decoded)
| Data type | Name | Description |
|---|---|---|
| UINT32LE[count] | offsets | Offset of each effect stream from the start of the decoded block. The game adds the block's address to every non-zero entry |
| STREAM[count] | streams | Effect streams |
Stream
| Data type | Name | Description |
|---|---|---|
| UINT16LE | type | 0 in all effects |
| UINT16LE | length | Length field (see the source code for how it is used) |
| UINT32LE | reserved | 0 |
| COMMAND[] | commands | Voice program, ends when a duration command with value 0 is executed |
Commands
A command is a byte whose value is masked with 0x7E to give the opcode; all commands except hold are followed by a UINT16LE operand. The effect interpreter is a four-voice priority system that runs on every timer interrupt.
| Opcode | Name | Operand |
|---|---|---|
| 0x02 | Duration | UINT16LE; a value of 0 ends the voice |
| 0x04 | PIT divisor | UINT16LE |
| 0x06 | Pitch slide | UINT16LE (signed) |
| 0x0A | Gate | UINT16LE |
| 0x0C | Gate slide | UINT16LE |
| 0x1C | Phase step | UINT16LE |
| 0x1E | Wave depth | UINT16LE |
| 0x20 | Loop counter | UINT16LE |
| 0x2E | Wave select | UINT16LE |
| 0x7A | Relative call | UINT16LE (relative offset) |
| 0x7C | Hold | none |
| 0x7E | Relative loop | UINT16LE (relative offset) |
The names are those used by the port's analysis; the exact per-tick behaviour of each field is defined by the translated interpreter in src/game/sfx.js and the notes in docs/re/input-timer-sound.md.
Comparisons and wrap-around are signed or unsigned depending on the opcode, so an exact implementation should follow the translated interpreter. The wave-select command reads a table (ITM) that is missing in the shipped bank; the original reads bytes relocated from the IBM block instead.
All eleven effects end. Streams 7 and 10 have trailing unreachable bytes. The tick rate depends on the music device: PC speaker 1193182/4096 Hz, AdLib 1193182/8192 Hz, MT-32 1193182/512 Hz.
Source code
From re-deathtrack, the open-source browser port:
src/game/sfx.js– translated interpreter (334e)src/viewer/decode.js–soundBank()command listingsrc/viewer/effects.js– effect audition and WAV exportdocs/re/resources.md– loader32e3:009adocs/re/input-timer-sound.md– effect tick
Credits
This file format was reverse engineered by VorticonCmdr. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)