Added audio loading and decompressing

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-15 11:36:25 +01:00
parent 2db9dad00d
commit 9d1f38752a
8 changed files with 142 additions and 41 deletions

View File

@@ -4,7 +4,9 @@ enum GameFile {
gameMaps('GAMEMAPS'),
vgaDict('VGADICT'),
vgaHead('VGAHEAD'),
vgaGraph('VGAGRAPH')
vgaGraph('VGAGRAPH'),
audioHed('AUDIOHED'),
audioT('AUDIOT')
;
final String baseName;

View File

@@ -4,3 +4,13 @@ class PcmSound {
final Uint8List bytes;
PcmSound(this.bytes);
}
class AdLibSound {
final Uint8List bytes;
AdLibSound(this.bytes);
}
class ImfMusic {
final Uint8List bytes;
ImfMusic(this.bytes);
}

View File

@@ -5,6 +5,8 @@ class WolfensteinData {
final List<Sprite> walls;
final List<Sprite> sprites;
final List<PcmSound> sounds;
final List<AdLibSound> adLibSounds;
final List<ImfMusic> music;
final List<WolfLevel> levels;
final List<VgaImage> vgaImages;
@@ -13,6 +15,8 @@ class WolfensteinData {
required this.walls,
required this.sprites,
required this.sounds,
required this.adLibSounds,
required this.music,
required this.levels,
required this.vgaImages,
});

View File

@@ -62,6 +62,8 @@ Future<Map<GameVersion, WolfensteinData>> discoverInDirectory({
vgaDict: await _readFile(foundFiles[GameFile.vgaDict]!),
vgaHead: await _readFile(foundFiles[GameFile.vgaHead]!),
vgaGraph: await _readFile(foundFiles[GameFile.vgaGraph]!),
audioHed: await _readFile(foundFiles[GameFile.audioHed]!),
audioT: await _readFile(foundFiles[GameFile.audioT]!),
);
loadedVersions[version] = data;

View File

@@ -47,6 +47,8 @@ abstract class WLParser {
vgaDict: await fileFetcher('VGADICT.$ext'),
vgaHead: await fileFetcher('VGAHEAD.$ext'),
vgaGraph: await fileFetcher('VGAGRAPH.$ext'),
audioHed: await fileFetcher('AUDIOHED.$ext'),
audioT: await fileFetcher('AUDIOT.$ext'),
);
}
@@ -61,9 +63,13 @@ abstract class WLParser {
required ByteData vgaDict,
required ByteData vgaHead,
required ByteData vgaGraph,
required ByteData audioHed,
required ByteData audioT,
}) {
final isShareware = version == GameVersion.shareware;
final audio = parseAudio(audioHed, audioT);
return WolfensteinData(
version: version,
walls: parseWalls(vswap),
@@ -71,6 +77,8 @@ abstract class WLParser {
sounds: parseSounds(vswap).map((bytes) => PcmSound(bytes)).toList(),
levels: parseMaps(mapHead, gameMaps, isShareware: isShareware),
vgaImages: parseVgaImages(vgaDict, vgaHead, vgaGraph),
adLibSounds: audio.adLib,
music: audio.music,
);
}
@@ -289,6 +297,56 @@ abstract class WLParser {
return levels;
}
/// Extracts AdLib sounds and IMF music tracks from the audio files.
static ({List<AdLibSound> adLib, List<ImfMusic> music}) parseAudio(
ByteData audioHed,
ByteData audioT,
) {
List<int> offsets = [];
// AUDIOHED is a series of 32-bit unsigned integers
for (int i = 0; i < audioHed.lengthInBytes ~/ 4; i++) {
offsets.add(audioHed.getUint32(i * 4, Endian.little));
}
List<Uint8List> allAudioChunks = [];
for (int i = 0; i < offsets.length - 1; i++) {
int start = offsets[i];
int next = offsets[i + 1];
// 0xFFFFFFFF (or 4294967295) marks an empty slot
if (start == 0xFFFFFFFF || start >= audioT.lengthInBytes) {
allAudioChunks.add(Uint8List(0));
continue;
}
int length = next - start;
if (length <= 0) {
allAudioChunks.add(Uint8List(0));
} else {
allAudioChunks.add(
audioT.buffer.asUint8List(audioT.offsetInBytes + start, length),
);
}
}
// Wolfenstein 3D split:
// Chunks 0-299: AdLib Sounds
// Chunks 300+: IMF Music
List<AdLibSound> adLib = allAudioChunks
.take(300)
.map((bytes) => AdLibSound(bytes))
.toList();
List<ImfMusic> music = allAudioChunks
.skip(300)
.where((chunk) => chunk.isNotEmpty)
.map((bytes) => ImfMusic(bytes))
.toList();
return (adLib: adLib, music: music);
}
// --- ALGORITHM 1: CARMACK EXPANSION ---
static Uint16List _expandCarmack(Uint8List compressed) {
ByteData data = ByteData.sublistView(compressed);

View File

@@ -33,6 +33,8 @@ class WolfensteinLoader {
required ByteData vgaDict,
required ByteData vgaHead,
required ByteData vgaGraph,
required ByteData audioHed,
required ByteData audioT,
}) {
// We just act as a clean pass-through to the core parser
return WLParser.load(
@@ -43,6 +45,8 @@ class WolfensteinLoader {
vgaDict: vgaDict,
vgaHead: vgaHead,
vgaGraph: vgaGraph,
audioHed: audioHed,
audioT: audioT,
);
}
}