Load menu and level audio dynamically

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-15 14:54:57 +01:00
parent 6eb903cbaa
commit a75ade8b33
8 changed files with 67 additions and 6 deletions

View File

@@ -69,11 +69,15 @@ class _WolfRendererState extends State<WolfRenderer>
// Grab the specific level from the singleton // Grab the specific level from the singleton
_currentLevel = Wolf3d.I.levels[mapIndex]; _currentLevel = Wolf3d.I.levels[mapIndex];
// Play the exact track id Software intended for this level!
Wolf3d.I.audio.playLevelMusic(_currentLevel);
// TODO: Initialize player position, spawn enemies based on difficulty, etc. // TODO: Initialize player position, spawn enemies based on difficulty, etc.
debugPrint("Loaded Level: ${_currentLevel.name}"); debugPrint("Loaded Level: ${_currentLevel.name}");
} }
void _onLevelCompleted() { void _onLevelCompleted() {
Wolf3d.I.audio.stopMusic();
// When the player hits the elevator switch, advance the map // When the player hits the elevator switch, advance the map
setState(() { setState(() {
_currentMapIndex++; _currentMapIndex++;

View File

@@ -22,16 +22,13 @@ class _EpisodeScreenState extends State<EpisodeScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (Wolf3d.I.music.isNotEmpty) { Wolf3d.I.audio.playMenuMusic();
Wolf3d.I.audio.playMusic(Wolf3d.I.music.first);
}
} }
void _selectEpisode(int index) { void _selectEpisode(int index) {
Wolf3d.I.setActiveEpisode(index); Wolf3d.I.setActiveEpisode(index);
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
// We pass the audio handles so the next screen can stop them when the game starts
builder: (context) => DifficultyScreen(), builder: (context) => DifficultyScreen(),
), ),
); );
@@ -39,7 +36,6 @@ class _EpisodeScreenState extends State<EpisodeScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Determine how many episodes are available (10 levels per episode)
final int numberOfEpisodes = (Wolf3d.I.levels.length / 10).floor().clamp( final int numberOfEpisodes = (Wolf3d.I.levels.length / 10).floor().clamp(
1, 1,
6, 6,

View File

@@ -45,6 +45,7 @@ class Wolf3d {
// --- Actions --- // --- Actions ---
void setActiveGame(WolfensteinData game) { void setActiveGame(WolfensteinData game) {
_activeGame = game; _activeGame = game;
audio.activeGame = game;
} }
/// Initializes the engine by loading available game data. /// Initializes the engine by loading available game data.

View File

@@ -4,6 +4,20 @@ import 'dart:typed_data';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
abstract class WLParser { abstract class WLParser {
// --- Original Song Lookup Tables ---
static const List<int> _sharewareMusicMap = [
2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Episode 1
];
static const List<int> _retailMusicMap = [
2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Ep 1
8, 9, 10, 11, 8, 9, 11, 10, 6, 12, // Ep 2
13, 14, 15, 16, 13, 14, 15, 16, 17, 18, // Ep 3
2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Ep 4
8, 9, 10, 11, 8, 9, 11, 10, 6, 12, // Ep 5
13, 14, 15, 16, 13, 14, 15, 16, 17, 19, // Ep 6
];
/// Asynchronously discovers the game version and loads all necessary files. /// Asynchronously discovers the game version and loads all necessary files.
/// Provide a [fileFetcher] callback (e.g., Flutter's rootBundle.load) that /// Provide a [fileFetcher] callback (e.g., Flutter's rootBundle.load) that
/// takes a filename and returns its ByteData. /// takes a filename and returns its ByteData.
@@ -229,6 +243,10 @@ abstract class WLParser {
bool isShareware = true, bool isShareware = true,
}) { }) {
List<WolfLevel> levels = []; List<WolfLevel> levels = [];
// 1. Select the correct map based on the version
final activeMusicMap = isShareware ? _sharewareMusicMap : _retailMusicMap;
int rlewTag = mapHead.getUint16(0, Endian.little); int rlewTag = mapHead.getUint16(0, Endian.little);
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
@@ -279,11 +297,20 @@ abstract class WLParser {
objectGrid.add(objectRow); objectGrid.add(objectRow);
} }
// Determine music track index.
// We use 'i' because it represents the absolute map slot (e.g. Map 12).
// If a map exists past the bounds of our lookup table (custom maps),
// we wrap around safely.
int trackIndex = (i < activeMusicMap.length)
? activeMusicMap[i]
: activeMusicMap[i % activeMusicMap.length];
levels.add( levels.add(
WolfLevel( WolfLevel(
name: name, name: name,
wallGrid: wallGrid, wallGrid: wallGrid,
objectGrid: objectGrid, objectGrid: objectGrid,
musicIndex: trackIndex,
), ),
); );
} }

View File

@@ -49,3 +49,5 @@ class ImfMusic {
return ImfMusic(instructions); return ImfMusic(instructions);
} }
} }
typedef WolfMusicMap = List<int>;

View File

@@ -4,10 +4,12 @@ class WolfLevel {
final String name; final String name;
final Sprite wallGrid; final Sprite wallGrid;
final Sprite objectGrid; final Sprite objectGrid;
final int musicIndex;
const WolfLevel({ const WolfLevel({
required this.name, required this.name,
required this.wallGrid, required this.wallGrid,
required this.objectGrid, required this.objectGrid,
required this.musicIndex,
}); });
} }

View File

@@ -6,7 +6,8 @@ library;
export 'src/game_file.dart' show GameFile; export 'src/game_file.dart' show GameFile;
export 'src/game_version.dart' show GameVersion; export 'src/game_version.dart' show GameVersion;
export 'src/image.dart' show VgaImage; export 'src/image.dart' show VgaImage;
export 'src/sound.dart' show PcmSound, AdLibSound, ImfMusic, ImfInstruction; export 'src/sound.dart'
show PcmSound, AdLibSound, ImfMusic, ImfInstruction, WolfMusicMap;
export 'src/sprite.dart' hide Matrix; export 'src/sprite.dart' hide Matrix;
export 'src/wolf_level.dart' show WolfLevel; export 'src/wolf_level.dart' show WolfLevel;
export 'src/wolfenstein_data.dart' show WolfensteinData; export 'src/wolfenstein_data.dart' show WolfensteinData;

View File

@@ -9,6 +9,8 @@ class WolfAudio {
AudioSource? _currentMusicSource; AudioSource? _currentMusicSource;
SoundHandle? _currentMusicHandle; SoundHandle? _currentMusicHandle;
WolfensteinData? activeGame;
/// Initializes the SoLoud audio engine. /// Initializes the SoLoud audio engine.
Future<void> init() async { Future<void> init() async {
if (_isInitialized) return; if (_isInitialized) return;
@@ -89,4 +91,30 @@ class WolfAudio {
SoLoud.instance.setPause(_currentMusicHandle!, false); SoLoud.instance.setPause(_currentMusicHandle!, false);
} }
} }
Future<void> playMenuMusic() async {
final data = activeGame;
// We can't play if data isn't set or doesn't have enough tracks
if (data == null || data.music.length <= 1) return;
// Track 1 is the menu theme in both Shareware and Retail
await playMusic(data.music[1]);
}
/// Plays the specific track assigned to a WolfLevel.
Future<void> playLevelMusic(WolfLevel level) async {
final data = activeGame;
if (data == null || data.music.isEmpty) return;
final index = level.musicIndex;
if (index < data.music.length) {
// 3. FIXED THIS: Call your playMusic method
await playMusic(data.music[index]);
} else {
print(
"WolfAudio: Warning - Track index $index out of bounds for level ${level.name}.",
);
}
}
} }