b88475882b
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
185 lines
4.6 KiB
Dart
185 lines
4.6 KiB
Dart
import 'dart:developer';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:audioplayers/audioplayers.dart';
|
|
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
|
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
|
import 'package:wolf_3d_dart/wolf_3d_synth.dart';
|
|
import 'package:wolf_3d_flutter/audio/debug_music_player.dart';
|
|
|
|
class WolfAudio implements EngineAudio, DebugMusicPlayer {
|
|
@override
|
|
Future<void> debugSoundTest() async {
|
|
for (int i = 0; i < 50; i++) {
|
|
Future.delayed(Duration(seconds: i * 2), () {
|
|
log('[AUDIO] Testing Sound ID: $i');
|
|
playSoundEffectId(i);
|
|
});
|
|
}
|
|
}
|
|
|
|
bool _isInitialized = false;
|
|
final AudioPlayer _musicPlayer = AudioPlayer();
|
|
|
|
static const int _maxSfxChannels = 8;
|
|
final List<AudioPlayer> _sfxPlayers = [];
|
|
int _currentSfxIndex = 0;
|
|
|
|
@override
|
|
WolfensteinData? activeGame;
|
|
|
|
@override
|
|
Future<void> init() async {
|
|
if (_isInitialized) return;
|
|
|
|
try {
|
|
await _musicPlayer.setPlayerMode(PlayerMode.mediaPlayer);
|
|
|
|
for (int i = 0; i < _maxSfxChannels; i++) {
|
|
final player = AudioPlayer();
|
|
await player.setPlayerMode(PlayerMode.lowLatency);
|
|
await player.setReleaseMode(ReleaseMode.stop);
|
|
_sfxPlayers.add(player);
|
|
}
|
|
|
|
_isInitialized = true;
|
|
log(
|
|
'[AUDIO] AudioPlayers initialized successfully with $_maxSfxChannels SFX channels.',
|
|
);
|
|
} catch (e) {
|
|
log('[AUDIO] Failed to initialize AudioPlayers - $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
stopAllAudio();
|
|
_musicPlayer.dispose();
|
|
|
|
for (final player in _sfxPlayers) {
|
|
player.stop();
|
|
player.dispose();
|
|
}
|
|
_sfxPlayers.clear();
|
|
|
|
_isInitialized = false;
|
|
}
|
|
|
|
@override
|
|
Future<void> playMusic(ImfMusic track, {bool looping = true}) async {
|
|
if (!_isInitialized) return;
|
|
await stopMusic();
|
|
|
|
try {
|
|
final pcmSamples = ImfRenderer.render(track);
|
|
final wavBytes = ImfRenderer.createWavFile(pcmSamples);
|
|
|
|
await _musicPlayer.setReleaseMode(
|
|
looping ? ReleaseMode.loop : ReleaseMode.stop,
|
|
);
|
|
await _musicPlayer.play(BytesSource(wavBytes));
|
|
} catch (e) {
|
|
log('[AUDIO] Error playing music track - $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> stopMusic() async {
|
|
if (!_isInitialized) return;
|
|
await _musicPlayer.stop();
|
|
}
|
|
|
|
@override
|
|
Future<void> stopAllAudio() async {
|
|
if (!_isInitialized) return;
|
|
|
|
await _musicPlayer.stop();
|
|
for (final player in _sfxPlayers) {
|
|
await player.stop();
|
|
}
|
|
}
|
|
|
|
Future<void> pauseMusic() async {
|
|
if (_isInitialized) await _musicPlayer.pause();
|
|
}
|
|
|
|
Future<void> resumeMusic() async {
|
|
if (_isInitialized) await _musicPlayer.resume();
|
|
}
|
|
|
|
@override
|
|
Future<void> playMenuMusic() async {
|
|
final data = activeGame;
|
|
final trackIndex = data == null
|
|
? null
|
|
: Music.menuTheme.trackIndexFor(data.version);
|
|
if (data == null || trackIndex == null || trackIndex >= data.music.length) {
|
|
return;
|
|
}
|
|
await playMusic(data.music[trackIndex]);
|
|
}
|
|
|
|
@override
|
|
Future<void> playLevelMusic(Music music) async {
|
|
final data = activeGame;
|
|
if (data == null || data.music.isEmpty) return;
|
|
|
|
final index = music.trackIndexFor(data.version) ?? 0;
|
|
if (index < data.music.length) {
|
|
await playMusic(data.music[index]);
|
|
} else {
|
|
log('[AUDIO] Warning - Track index $index out of bounds.');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> playSoundEffect(SoundEffect effect) async {
|
|
final data = activeGame;
|
|
if (data == null) return;
|
|
|
|
final resolved = data.registry.sfx.resolve(effect);
|
|
if (resolved != null) {
|
|
await playSoundEffectId(resolved.slotIndex);
|
|
return;
|
|
}
|
|
|
|
if (data.version == GameVersion.spearOfDestinyDemo) {
|
|
return;
|
|
}
|
|
|
|
await playSoundEffectId(effect.idFor(data.version));
|
|
}
|
|
|
|
@override
|
|
Future<void> playSoundEffectId(int sfxId) async {
|
|
log('[AUDIO] Playing sfx id $sfxId');
|
|
|
|
final data = activeGame;
|
|
if (data == null) return;
|
|
|
|
final soundsList = data.sounds;
|
|
if (sfxId < 0 || sfxId >= soundsList.length) return;
|
|
|
|
final raw8bitBytes = soundsList[sfxId].bytes;
|
|
if (raw8bitBytes.isEmpty) return;
|
|
|
|
final Int16List converted16bit = Int16List(raw8bitBytes.length);
|
|
for (int i = 0; i < raw8bitBytes.length; i++) {
|
|
converted16bit[i] = (raw8bitBytes[i] - 128) * 256;
|
|
}
|
|
|
|
final wavBytes = ImfRenderer.createWavFile(
|
|
converted16bit,
|
|
sampleRate: 7000,
|
|
);
|
|
|
|
try {
|
|
final player = _sfxPlayers[_currentSfxIndex];
|
|
_currentSfxIndex = (_currentSfxIndex + 1) % _maxSfxChannels;
|
|
await player.play(BytesSource(wavBytes));
|
|
} catch (e) {
|
|
log('[AUDIO] SFX Error - $e');
|
|
}
|
|
}
|
|
}
|