From a75ade8b33d67f447df71f9b17aefc419c76a059 Mon Sep 17 00:00:00 2001 From: Hans Kokx Date: Sun, 15 Mar 2026 14:54:57 +0100 Subject: [PATCH] Load menu and level audio dynamically Signed-off-by: Hans Kokx --- lib/features/renderer/renderer.dart | 4 +++ lib/features/screens/episode_screen.dart | 6 +--- lib/wolf_3d.dart | 1 + packages/wolf_3d_data/lib/src/wl_parser.dart | 27 ++++++++++++++++++ .../wolf_3d_data_types/lib/src/sound.dart | 2 ++ .../lib/src/wolf_level.dart | 2 ++ .../lib/wolf_3d_data_types.dart | 3 +- .../wolf_3d_synth/lib/src/wolf_3d_audio.dart | 28 +++++++++++++++++++ 8 files changed, 67 insertions(+), 6 deletions(-) diff --git a/lib/features/renderer/renderer.dart b/lib/features/renderer/renderer.dart index 11e020c..1c1f055 100644 --- a/lib/features/renderer/renderer.dart +++ b/lib/features/renderer/renderer.dart @@ -69,11 +69,15 @@ class _WolfRendererState extends State // Grab the specific level from the singleton _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. debugPrint("Loaded Level: ${_currentLevel.name}"); } void _onLevelCompleted() { + Wolf3d.I.audio.stopMusic(); // When the player hits the elevator switch, advance the map setState(() { _currentMapIndex++; diff --git a/lib/features/screens/episode_screen.dart b/lib/features/screens/episode_screen.dart index 93f7489..f56fdfd 100644 --- a/lib/features/screens/episode_screen.dart +++ b/lib/features/screens/episode_screen.dart @@ -22,16 +22,13 @@ class _EpisodeScreenState extends State { @override void initState() { super.initState(); - if (Wolf3d.I.music.isNotEmpty) { - Wolf3d.I.audio.playMusic(Wolf3d.I.music.first); - } + Wolf3d.I.audio.playMenuMusic(); } void _selectEpisode(int index) { Wolf3d.I.setActiveEpisode(index); Navigator.of(context).push( MaterialPageRoute( - // We pass the audio handles so the next screen can stop them when the game starts builder: (context) => DifficultyScreen(), ), ); @@ -39,7 +36,6 @@ class _EpisodeScreenState extends State { @override Widget build(BuildContext context) { - // Determine how many episodes are available (10 levels per episode) final int numberOfEpisodes = (Wolf3d.I.levels.length / 10).floor().clamp( 1, 6, diff --git a/lib/wolf_3d.dart b/lib/wolf_3d.dart index 5b94e47..672647a 100644 --- a/lib/wolf_3d.dart +++ b/lib/wolf_3d.dart @@ -45,6 +45,7 @@ class Wolf3d { // --- Actions --- void setActiveGame(WolfensteinData game) { _activeGame = game; + audio.activeGame = game; } /// Initializes the engine by loading available game data. diff --git a/packages/wolf_3d_data/lib/src/wl_parser.dart b/packages/wolf_3d_data/lib/src/wl_parser.dart index bffca4d..19100b7 100644 --- a/packages/wolf_3d_data/lib/src/wl_parser.dart +++ b/packages/wolf_3d_data/lib/src/wl_parser.dart @@ -4,6 +4,20 @@ import 'dart:typed_data'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; abstract class WLParser { + // --- Original Song Lookup Tables --- + static const List _sharewareMusicMap = [ + 2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Episode 1 + ]; + + static const List _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. /// Provide a [fileFetcher] callback (e.g., Flutter's rootBundle.load) that /// takes a filename and returns its ByteData. @@ -229,6 +243,10 @@ abstract class WLParser { bool isShareware = true, }) { List levels = []; + + // 1. Select the correct map based on the version + final activeMusicMap = isShareware ? _sharewareMusicMap : _retailMusicMap; + int rlewTag = mapHead.getUint16(0, Endian.little); for (int i = 0; i < 100; i++) { @@ -279,11 +297,20 @@ abstract class WLParser { 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( WolfLevel( name: name, wallGrid: wallGrid, objectGrid: objectGrid, + musicIndex: trackIndex, ), ); } diff --git a/packages/wolf_3d_data_types/lib/src/sound.dart b/packages/wolf_3d_data_types/lib/src/sound.dart index b66b438..faa8632 100644 --- a/packages/wolf_3d_data_types/lib/src/sound.dart +++ b/packages/wolf_3d_data_types/lib/src/sound.dart @@ -49,3 +49,5 @@ class ImfMusic { return ImfMusic(instructions); } } + +typedef WolfMusicMap = List; diff --git a/packages/wolf_3d_data_types/lib/src/wolf_level.dart b/packages/wolf_3d_data_types/lib/src/wolf_level.dart index 4390f9d..e405f75 100644 --- a/packages/wolf_3d_data_types/lib/src/wolf_level.dart +++ b/packages/wolf_3d_data_types/lib/src/wolf_level.dart @@ -4,10 +4,12 @@ class WolfLevel { final String name; final Sprite wallGrid; final Sprite objectGrid; + final int musicIndex; const WolfLevel({ required this.name, required this.wallGrid, required this.objectGrid, + required this.musicIndex, }); } diff --git a/packages/wolf_3d_data_types/lib/wolf_3d_data_types.dart b/packages/wolf_3d_data_types/lib/wolf_3d_data_types.dart index 22fb698..48c59cd 100644 --- a/packages/wolf_3d_data_types/lib/wolf_3d_data_types.dart +++ b/packages/wolf_3d_data_types/lib/wolf_3d_data_types.dart @@ -6,7 +6,8 @@ library; export 'src/game_file.dart' show GameFile; export 'src/game_version.dart' show GameVersion; 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/wolf_level.dart' show WolfLevel; export 'src/wolfenstein_data.dart' show WolfensteinData; diff --git a/packages/wolf_3d_synth/lib/src/wolf_3d_audio.dart b/packages/wolf_3d_synth/lib/src/wolf_3d_audio.dart index ee584f5..aab32ab 100644 --- a/packages/wolf_3d_synth/lib/src/wolf_3d_audio.dart +++ b/packages/wolf_3d_synth/lib/src/wolf_3d_audio.dart @@ -9,6 +9,8 @@ class WolfAudio { AudioSource? _currentMusicSource; SoundHandle? _currentMusicHandle; + WolfensteinData? activeGame; + /// Initializes the SoLoud audio engine. Future init() async { if (_isInitialized) return; @@ -89,4 +91,30 @@ class WolfAudio { SoLoud.instance.setPause(_currentMusicHandle!, false); } } + + Future 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 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}.", + ); + } + } }