Remove unnecessary data class
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -363,7 +363,7 @@ abstract class WLParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts AdLib sounds and IMF music tracks from the audio files.
|
/// Extracts AdLib sounds and IMF music tracks from the audio files.
|
||||||
static ({List<AdLibSound> adLib, List<ImfMusic> music}) parseAudio(
|
static ({List<PcmSound> adLib, List<ImfMusic> music}) parseAudio(
|
||||||
ByteData audioHed,
|
ByteData audioHed,
|
||||||
ByteData audioT,
|
ByteData audioT,
|
||||||
GameVersion version,
|
GameVersion version,
|
||||||
@@ -400,9 +400,9 @@ abstract class WLParser {
|
|||||||
// Chunks 174-260: Digitized Sounds
|
// Chunks 174-260: Digitized Sounds
|
||||||
int musicStartIndex = 261;
|
int musicStartIndex = 261;
|
||||||
|
|
||||||
List<AdLibSound> adLib = allAudioChunks
|
List<PcmSound> adLib = allAudioChunks
|
||||||
.take(musicStartIndex)
|
.take(musicStartIndex)
|
||||||
.map((bytes) => AdLibSound(bytes))
|
.map((bytes) => PcmSound(bytes))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
List<ImfMusic> music = allAudioChunks
|
List<ImfMusic> music = allAudioChunks
|
||||||
|
|||||||
@@ -5,11 +5,6 @@ class PcmSound {
|
|||||||
PcmSound(this.bytes);
|
PcmSound(this.bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AdLibSound {
|
|
||||||
final Uint8List bytes;
|
|
||||||
AdLibSound(this.bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ImfInstruction {
|
class ImfInstruction {
|
||||||
final int register;
|
final int register;
|
||||||
final int data;
|
final int data;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class WolfensteinData {
|
|||||||
final List<Sprite> walls;
|
final List<Sprite> walls;
|
||||||
final List<Sprite> sprites;
|
final List<Sprite> sprites;
|
||||||
final List<PcmSound> sounds;
|
final List<PcmSound> sounds;
|
||||||
final List<AdLibSound> adLibSounds;
|
final List<PcmSound> adLibSounds;
|
||||||
final List<ImfMusic> music;
|
final List<ImfMusic> music;
|
||||||
final List<VgaImage> vgaImages;
|
final List<VgaImage> vgaImages;
|
||||||
final List<Episode> episodes;
|
final List<Episode> episodes;
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ 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/map_objects.dart' show MapObject;
|
export 'src/map_objects.dart' show MapObject;
|
||||||
export 'src/sound.dart'
|
export 'src/sound.dart' show PcmSound, ImfMusic, ImfInstruction, WolfMusicMap;
|
||||||
show PcmSound, AdLibSound, ImfMusic, ImfInstruction, WolfMusicMap;
|
|
||||||
export 'src/sprite.dart' hide Matrix;
|
export 'src/sprite.dart' hide Matrix;
|
||||||
export 'src/sprite_frame_range.dart' show SpriteFrameRange;
|
export 'src/sprite_frame_range.dart' show SpriteFrameRange;
|
||||||
export 'src/wolf_level.dart' show WolfLevel;
|
export 'src/wolf_level.dart' show WolfLevel;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class Wolf3d {
|
|||||||
List<Sprite> get walls => activeGame.walls;
|
List<Sprite> get walls => activeGame.walls;
|
||||||
List<Sprite> get sprites => activeGame.sprites;
|
List<Sprite> get sprites => activeGame.sprites;
|
||||||
List<PcmSound> get sounds => activeGame.sounds;
|
List<PcmSound> get sounds => activeGame.sounds;
|
||||||
List<AdLibSound> get adLibSounds => activeGame.adLibSounds;
|
List<PcmSound> get adLibSounds => activeGame.adLibSounds;
|
||||||
List<ImfMusic> get music => activeGame.music;
|
List<ImfMusic> get music => activeGame.music;
|
||||||
List<VgaImage> get vgaImages => activeGame.vgaImages;
|
List<VgaImage> get vgaImages => activeGame.vgaImages;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:audioplayers/audioplayers.dart';
|
import 'package:audioplayers/audioplayers.dart';
|
||||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||||
import 'package:wolf_3d_synth/src/imf_renderer.dart';
|
import 'package:wolf_3d_synth/src/imf_renderer.dart';
|
||||||
@@ -5,20 +7,38 @@ import 'package:wolf_3d_synth/src/imf_renderer.dart';
|
|||||||
class WolfAudio {
|
class WolfAudio {
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
|
|
||||||
|
// --- Music State ---
|
||||||
final AudioPlayer _musicPlayer = AudioPlayer();
|
final AudioPlayer _musicPlayer = AudioPlayer();
|
||||||
|
|
||||||
|
// --- SFX State ---
|
||||||
|
// A pool of players to allow overlapping sound effects.
|
||||||
|
static const int _maxSfxChannels = 8;
|
||||||
|
final List<AudioPlayer> _sfxPlayers = [];
|
||||||
|
int _currentSfxIndex = 0;
|
||||||
|
|
||||||
WolfensteinData? activeGame;
|
WolfensteinData? activeGame;
|
||||||
|
|
||||||
/// Initializes the audio engine.
|
/// Initializes the audio engine and pre-allocates the SFX pool.
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
if (_isInitialized) return;
|
if (_isInitialized) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// audioplayers doesn't require complex global initialization like SoLoud,
|
// Set music player mode
|
||||||
// but setting the audio context can be useful for mobile platforms later.
|
|
||||||
await _musicPlayer.setPlayerMode(PlayerMode.mediaPlayer);
|
await _musicPlayer.setPlayerMode(PlayerMode.mediaPlayer);
|
||||||
|
|
||||||
|
// Initialize the SFX pool
|
||||||
|
for (int i = 0; i < _maxSfxChannels; i++) {
|
||||||
|
final player = AudioPlayer();
|
||||||
|
// lowLatency mode is highly recommended for short game sounds
|
||||||
|
await player.setPlayerMode(PlayerMode.lowLatency);
|
||||||
|
await player.setReleaseMode(ReleaseMode.stop);
|
||||||
|
_sfxPlayers.add(player);
|
||||||
|
}
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
print("WolfAudio: AudioPlayers initialized successfully.");
|
print(
|
||||||
|
"WolfAudio: AudioPlayers initialized successfully with $_maxSfxChannels SFX channels.",
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("WolfAudio: Failed to initialize AudioPlayers - $e");
|
print("WolfAudio: Failed to initialize AudioPlayers - $e");
|
||||||
}
|
}
|
||||||
@@ -28,6 +48,13 @@ class WolfAudio {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
stopMusic();
|
stopMusic();
|
||||||
_musicPlayer.dispose();
|
_musicPlayer.dispose();
|
||||||
|
|
||||||
|
for (final player in _sfxPlayers) {
|
||||||
|
player.stop();
|
||||||
|
player.dispose();
|
||||||
|
}
|
||||||
|
_sfxPlayers.clear();
|
||||||
|
|
||||||
_isInitialized = false;
|
_isInitialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,72 +62,73 @@ class WolfAudio {
|
|||||||
// MUSIC MANAGEMENT
|
// MUSIC MANAGEMENT
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
/// Renders and plays a specific IMF music track.
|
|
||||||
Future<void> playMusic(ImfMusic track, {bool looping = true}) async {
|
Future<void> playMusic(ImfMusic track, {bool looping = true}) async {
|
||||||
if (!_isInitialized) return;
|
if (!_isInitialized) return;
|
||||||
|
|
||||||
// Stop currently playing music to prevent overlap
|
|
||||||
await stopMusic();
|
await stopMusic();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Render hardware instructions into PCM and wrap in WAV
|
|
||||||
final pcmSamples = ImfRenderer.render(track);
|
final pcmSamples = ImfRenderer.render(track);
|
||||||
final wavBytes = ImfRenderer.createWavFile(pcmSamples);
|
final wavBytes = ImfRenderer.createWavFile(pcmSamples);
|
||||||
|
|
||||||
// Configure looping behavior
|
|
||||||
await _musicPlayer.setReleaseMode(
|
await _musicPlayer.setReleaseMode(
|
||||||
looping ? ReleaseMode.loop : ReleaseMode.stop,
|
looping ? ReleaseMode.loop : ReleaseMode.stop,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Play the generated WAV file directly from memory
|
|
||||||
await _musicPlayer.play(BytesSource(wavBytes));
|
await _musicPlayer.play(BytesSource(wavBytes));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("WolfAudio: Error playing music track - $e");
|
print("WolfAudio: Error playing music track - $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Halts playback for the current track.
|
|
||||||
Future<void> stopMusic() async {
|
Future<void> stopMusic() async {
|
||||||
if (!_isInitialized) return;
|
if (!_isInitialized) return;
|
||||||
await _musicPlayer.stop();
|
await _musicPlayer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pauses the current track.
|
|
||||||
Future<void> pauseMusic() async {
|
Future<void> pauseMusic() async {
|
||||||
if (_isInitialized) {
|
if (_isInitialized) await _musicPlayer.pause();
|
||||||
await _musicPlayer.pause();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resumes a paused track.
|
|
||||||
Future<void> resumeMusic() async {
|
Future<void> resumeMusic() async {
|
||||||
if (_isInitialized) {
|
if (_isInitialized) await _musicPlayer.resume();
|
||||||
await _musicPlayer.resume();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> playMenuMusic() async {
|
Future<void> playMenuMusic() async {
|
||||||
final data = activeGame;
|
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;
|
if (data == null || data.music.length <= 1) return;
|
||||||
|
|
||||||
// Track 1 is the menu theme in both Shareware and Retail
|
|
||||||
await playMusic(data.music[1]);
|
await playMusic(data.music[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plays the specific track assigned to a WolfLevel.
|
|
||||||
Future<void> playLevelMusic(WolfLevel level) async {
|
Future<void> playLevelMusic(WolfLevel level) async {
|
||||||
final data = activeGame;
|
final data = activeGame;
|
||||||
if (data == null || data.music.isEmpty) return;
|
if (data == null || data.music.isEmpty) return;
|
||||||
|
|
||||||
final index = level.musicIndex;
|
final index = level.musicIndex;
|
||||||
|
|
||||||
if (index < data.music.length) {
|
if (index < data.music.length) {
|
||||||
await playMusic(data.music[index]);
|
await playMusic(data.music[index]);
|
||||||
} else {
|
} else {
|
||||||
print(
|
print("WolfAudio: Warning - Track index $index out of bounds.");
|
||||||
"WolfAudio: Warning - Track index $index out of bounds for level ${level.name}.",
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// SFX MANAGEMENT
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/// Plays a sound effect from a WAV byte array using the round-robin pool.
|
||||||
|
Future<void> playSfx(Uint8List wavBytes) async {
|
||||||
|
if (!_isInitialized) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Grab the next available player in the pool
|
||||||
|
final player = _sfxPlayers[_currentSfxIndex];
|
||||||
|
|
||||||
|
// Move to the next index, looping back to 0 if we hit the max
|
||||||
|
_currentSfxIndex = (_currentSfxIndex + 1) % _maxSfxChannels;
|
||||||
|
|
||||||
|
// Play the sound (this interrupts whatever this specific channel was playing)
|
||||||
|
await player.play(BytesSource(wavBytes));
|
||||||
|
} catch (e) {
|
||||||
|
print("WolfAudio: Error playing SFX - $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user