# Audio extraction: first playable exports

2026-09-18. [Open the listening gallery](../analysis/audio/index.html).

For later work, see the [hardware and Roland investigation](music-hardware-variants.md),
including custom instruments, missing percussion, independent loop timing and
[hand-corrected FLAC references](../analysis/audio/roland-gm-percussion-bank/README.md).

Six Roland SCC-1 arrangements now export as **standard MIDI**, with **Ogg Vorbis
listening previews**. The gallery offers both plus source event/loop metadata.
These are the six music groups' SCC-1 variants, not 30 distinct songs. Their original song titles remain unknown. Scene assignments and chained briefing
selections are now listed in the [scene and music catalogue](../analysis/presentations/music.html).
Group 1 is startup and menu music (see below).

The Ogg files are now rendered by **FluidSynth with FluidR3 GM**, not recordings of
Darker or emulation of the SCC-1's exact instrument set. Their purpose is to hear
the recovered arrangements. The preview provenance records the soundfont hash,
sample rate and encoding. Original hardware timbre, game volume/fades, interrupt
quantisation and repeated playback are not claimed.

## Correction: DC saturation in the first preview render

Listening to the startup preview showed that it deteriorated around 11 seconds and
provided an Audacity screenshot showing its left channel driven up to full scale.
Decoded Ogg measurements confirmed this. By the latter part of the old render,
the output was almost entirely positive full-scale DC. Smaller drifting offsets
were also present in several other tracks.

Controlled rerenders of **the same MIDI and soundfont** reproduced the problem
with TiMidity even with reverb and chorus disabled. FluidSynth did not reproduce
the saturation. This isolates a preview-synthesis-path problem; the precise
TiMidity failure is not yet diagnosed. The music decoder was not changed to
reinterpret volume as pitch or vice versa: the original driver's command 95
emits MIDI pitch bend and command 96 emits controller 7 (volume), as already
checked against its instruction output.

All six listening copies were regenerated with FluidSynth and encoded directly
to Ogg Vorbis, with no DC blocker or other corrective filtering. MIDI and
source-event metadata remain unchanged. The synthesis settings and absence of
filtering are recorded in `variants/previews.json`.

The individual `variants/group-N-scc1-check.json` reports contain both source PCM
and final Ogg measurements, per channel in five-second windows. Checks guard
against large DC offsets and clipping; small residual synthesis offsets are
retained. These checks detect this failure class; they do not validate musical
interpretation or original hardware timbre.
The gallery includes a content-hash query on each audio URL to avoid stale
cached previews after regeneration.

## Other hardware variants

All thirty native sequences and their five drivers now have a dedicated
[export and comparison page](../analysis/audio/variants/index.html), including
source command inventories. See [hardware differences and conversion limits](music-hardware-variants.md).
Only the SCC-1 versions currently have verified standard MIDI conversions.
The other four hardware variants now have provisional MIDI/GM auditions,
selectable in the gallery; their hardware-specific command mappings remain
unverified.

## What the executable establishes

The game selects driver resource `33 + driver_index` at `9C66–9C74`. Music loader
`0AAE–0AC1` selects `38 + 5*music_group + driver_index`, appends it to the loaded
driver and invokes driver API function 4 with song number 1 (`0AC4–0AC7`).
Thus the five-way grouping is established by code, not merely similar headers.

| Group | SCC-1 resource | Sequence tracks | Initial BPM | Division | Loop-end tick |
| --- | --- | ---: | ---: | ---: | ---: |
| 1 | 00/39 | 15 | 98 | 96 | 12,672 |
| 2 | 00/44 | 15 | 90 | 96 | 4,704 |
| 3 | 00/49 | 9 | 92 | 96 | 4,704 |
| 4 | 00/54 | 13 | 90 | 96 | 4,704 |
| 5 | 00/59 | 11 | 90 | 96 | 4,704 |
| 6 | 00/64 | 12 | 100 | 96 | 6,144 |

Within each arrangement, every track reaches its loop at the same tick. The
MIDI exports contain one initial traversal of each track, then added all-notes-off
messages so a finite audition can end cleanly. Source loop targets are preserved
in JSON: repetition may start after introductory setup, not at the file beginning.
No scene names have been invented for these tracks.

## Startup music identification

The opening music is group 0 in the engine's zero-based selector: **group 1 in
our gallery**, SCC-1 resource **00/39** (Sound Blaster variant 00/38).
Startup `9CBD–9CBF` calls `D8D6` with AL=1, selecting stored record 1 from
resource 04/15. Its scene script contains the music command `40 00`; opcode 40
routes through `C067` to `0A99`, selecting music group zero. The return-to-menu
path independently does `XOR AL,AL; CALL 0A99` at `3F47–3F49`.
The original song title remains unknown; “Startup music” is a functional label.

## Container and sequence format

Offsets here are within resource 00/34, the SCC-1 driver, unless stated otherwise.
Its public entry at 0000 dispatches by AH through the table at `026A`.
Function 3 services the player at `02A3`; function 4 selects the song.

Music resource layout recovered from `03C0–046E`:

- Byte 0: song count (one in each supplied music resource).
- Word at 1: offset of the song-pointer table, relative to the resource base.
- A selected table word points to the song descriptor, also base-relative.
- Descriptor bytes: division, initial tempo, track count, followed by a u16
  resource-relative stream offset for each track.
- Events consist of a variable-length delta followed by an event and its operands.
  Deltas use high-bit continuation and seven payload bits per byte, most
  significant group first. `05FE–0667` is the original decoder; a five-byte
  unterminated value becomes FFFFFFFF, used as a long/infinite delay sentinel.

The driver keeps 16 track states of 23 bytes each at `008A`. The first word is
the stream cursor; +2/+4 is the countdown, +6 the loop cursor, +8 the selected
channel, +9 the last note, and +A the activity/mode byte.

| Event | SCC-1 handler meaning |
| --- | --- |
| `00–7F` | Note-on; event byte is note, following byte is velocity |
| `80–8F` | Select channel; no MIDI event by itself |
| `90 nn` | Note-off represented as note-on `nn` with zero velocity |
| `91` | Stop track and silence channel |
| `92 pp` | Program change |
| `93 tt` | Change tempo |
| `94` | No-op in this driver |
| `95 bb` | Pitch bend, low seven bits zero, high seven bits `bb` |
| `96 vv` | Channel volume, scaled by master volume |
| `97 xx` | Update driver track field +14; retained in command metadata |
| `98` | Return to saved loop cursor |
| `99` | Release last note on channels 0–11; channels 12–15 use the driver's digital-voice stop branch |
| `9A` | Park stream at the driver's sentinel delay |
| `9B pp` | Pan controller |
| `9C` | Save loop cursor at the following delta |
| `9D cc vv` | MIDI controller/value pair |
| `FF` | No event; proceed to the next delta |

The extracted songs exercise channel selection, notes, explicit/last-note
release, instrument changes, volume, pan, pitch bend, controller commands and
loops. The less common terminal/tempo/park cases above are identified statically;
they are not all exercised by these six arrangements.

`0668` computes sequence progress from the timer divisor and the two tempo bytes:
the constant `0444:6390` is 71,590,800 (1,193,180 × 60). This establishes the
musical division/tempo interpretation. Conversion to MIDI retains musical tick
time; it does not reproduce the driver's quantised interrupt delivery. The game
sets the driver's timer-divisor parameter to `5D24` at `9CA4–9CA9`.

## Original-code verification

[`verify_audio.py`](../tools/verify_audio.py) executes each exported event through
`031A` and each preceding delta through `05FE`, in the original SCC-1 driver.
It intercepts MIDI output at `0796/079F` before hardware polling, retaining full
status bytes instead of the hardware path's running-status suppression. Driver
state persists across events in a track, and master volume is set to unity.

**20,545 event commands and 20,545 delta decodes match**, including stream cursor
positions and first-loop destinations, across all 75 tracks. See
[scc1-verification.json](../analysis/audio/variants/scc1-verification.json).

The check does not run hardware initialization, the full periodic scheduler,
MIDI synthesis or the complete game. The driver has digital-voice branches for
some commands; those are not converted into fabricated MIDI notes or sample
assets. Exact source command records remain available for subsequent work.

## Sound effects are a separate investigation

The main executable itself contains a direct FM register-write path:
`1185` sends a register index to port `0388`, and the following code sends its
value to `0389`, using PIT reads for delays. `113E–1181` programs operator and
channel registers. `0EDB` onward maintains effect voice state, and `0E82` has a
special interaction with the Sound Blaster music driver's internal tables.

This is evidence for a **procedural FM effects path**, not a bank of ordinary
WAV files waiting to be renamed. The music drivers also contain digital-audio
support; that does not by itself establish sample payloads used by this game.
The next step is to recover the effect definitions and their evolving register
writes, then render them through an appropriate OPL implementation with verified
timing. A subsequent [effects extraction pass](sound-effects.md) now exports the embedded
FM definitions and playable isolated auditions. Full live modulation remains
separate from those previews.

## Reproduction

```sh
python3 tools/extract_audio.py
PYTHONPATH=/tmp/darker-python python3 tools/verify_audio.py
python3 tools/render_audio_previews.py
```

Extraction uses Python's standard library. Verification requires Unicorn.
Preview rendering requires libfluidsynth.so.3, ffmpeg and the locally installed FluidR3
GM soundfont; it does not bundle the soundfont with the extracted assets.

## Earlier Sound Images investigation


2026-09-18. We examined the
[Aminet Sound Images EaglePlayer package](https://aminet.net/package/mus/play/EP_SoundImages).
The downloaded LHA contains an **Amiga executable** replayer and ripper, plus
readmes; no source code. Its readme says version 2 adds Sound Images Generation 2
support and exposes sample-saving through EaglePlayer 2. This is useful prior
work, but not evidence that it directly accepts Darker's DOS resource format.
“PC relative” in this Amiga context means program-counter-relative, not PC/DOS.

The ripper contains the attribution `Tony 'Tiny' Williams, adapted by Wanted Team`.
Darker's resources 00/39–41, 44–46, 49–51, 54–56, 59–61 and 64–66 contain
`Tiny` at byte offset 4. The sound drivers already explicitly identify Tony
Williams / Sound Images Generation 2 v1.11x. These are consistent provenance
clues, not proof of binary compatibility between Amiga and DOS songs.

Resources 00/38–67 show six repeating groups of five related stream variants,
consistent with the five extracted hardware drivers. Headers start with `01`
and a little-endian word, followed by command-like bytes. Neither raw PCM nor
standard MIDI/WAV framing is established. The exact length/table semantics,
timing, command set and instrument/sample banks still need tracing through the
DOS drivers. Do not rename these resources `.mid` or `.wav` and expect playback.

All thirty candidate streams and five drivers have already been losslessly
extracted into `analysis/resources/`. At that stage there was no playable audio export.
The next useful step is tracing one driver's event decoder, then separating
sequence conversion from sample-bank extraction. Emulator recording is another
route to a listening copy, but would not by itself explain the format.
