Files
novafarma/src/systems/AudioManager.js
David Kotnik 23bf7ac119 🔊💎 COMPLETE AUDIO AUDIT + AudioManager System
 AUDIO AUDIT (AUDIO_AUDIT_COMPLETE.md):
- 410+ files mapped to scenes
- 10 music tracks (Kevin MacLeod)
- 45 voiceover files (EN + SL)
- 355 SFX files (estimated)

📊 SCENE MAPPING:
- SplashScene: Logo only
- IntroScene: All voices + ambient
- StoryScene: Main theme
- GameScene: All music/SFX

🎵 MUSIC TRACKS:
- farm_ambient → Grassland biome
- forest_ambient → Forest biome
- night_theme → Night time (8pm-6am)
- town_theme → Town areas
- combat_theme → Enemy detected
- ana_theme → Memory scenes
- raid_warning → Zombie raid
- victory_theme → Quest complete

🎤 VOICEOVER:
- Kai: 12 EN + 12 SL (Christopher Neural)
- Ana: 8 EN + 8 SL (Aria Neural)
- Gronk: 1 EN + 1 SL (Ryan Neural UK)

 AUDIO MANAGER (AudioManager.js):
- Singleton pattern
- Debug logging mode
- Console output format:
  🎵 [MUSIC] Playing: farm_ambient.mp3
     Scene: GameScene
     Volume: 0.7
     Loop: true

FEATURES:
- playMusic(key, options)
- playVoice(key, subtitle)
- playSFX(key, options)
- crossfadeMusic(newKey, duration)
- Volume controls (music/voice/sfx)
- Mute/unmute all
- Kevin MacLeod attribution tracking
- Priority system
- Helper methods:
  - playUI(action)
  - playFarming(action)
  - playAnimal(type)
  - playSpecial(event)

🐛 DEBUG MODE:
- Set debugMode = true/false
- Logs every playback to console
- Shows: file, scene, volume, duration, trigger
- Easy debugging of audio issues

📝 ATTRIBUTION:
- Kevin MacLeod (CC BY 3.0)
- Microsoft Azure Edge TTS voices
- Auto-tracked in AudioManager

🎯 USAGE:
import audioManager from './systems/AudioManager.js';
audioManager.init(this);
audioManager.playMusic('farm');
audioManager.playSFX('harvest', { trigger: 'wheat' });

TOTAL: 410+ files organized & ready!
2026-01-10 22:19:21 +01:00

431 lines
11 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* AUDIO MANAGER - Centralized Audio System
* Handles all music, voiceover, and SFX playback
* Includes DEBUG MODE for tracking what plays where
*/
class AudioManager {
constructor() {
this.scene = null;
this.debugMode = true; // SET TO FALSE TO DISABLE LOGGING
// Current playback tracking
this.currentMusic = null;
this.currentVoice = null;
this.musicVolume = 0.7;
this.voiceVolume = 0.8;
this.sfxVolume = 0.5;
// Audio mapping
this.audioMap = {
music: {
intro: 'intro_ambient',
menu: 'main_theme',
farm: 'farm_ambient',
forest: 'forest_ambient',
night: 'night_theme',
town: 'town_theme',
wilderness: 'wilderness_theme',
combat: 'combat_theme',
ana: 'ana_theme',
raid: 'raid_warning',
victory: 'victory_theme'
},
voice: {
kai_en: Array.from({ length: 12 }, (_, i) => `kai_en_${String(i + 1).padStart(2, '0')}`),
ana_en: Array.from({ length: 8 }, (_, i) => `ana_en_${String(i + 1).padStart(2, '0')}`),
gronk_en: ['gronk_en_01']
},
sfx: {
ui_click: 'ui_click',
ui_hover: 'ui_hover',
plant: 'plant_seed',
water: 'water_crops',
harvest: 'harvest_crop',
cat_meow: 'cat_meow',
dog_bark: 'dog_bark',
susi_bark: 'susi_bark',
memory_found: 'memory_found',
level_up: 'level_up'
}
};
// License attribution
this.attribution = {
music: 'Music by Kevin MacLeod (incompetech.com)\nLicensed under Creative Commons: By Attribution 3.0\nhttp://creativecommons.org/licenses/by/3.0/',
voices: 'Voices generated using Microsoft Azure Edge TTS\nVoices: en-US-ChristopherNeural, en-US-AriaNeural, en-GB-RyanNeural'
};
}
/**
* INITIALIZE AUDIO MANAGER
*/
init(scene) {
this.scene = scene;
this.log('AudioManager initialized', 'INIT');
}
/**
* DEBUG LOGGER
*/
log(message, type = 'INFO', details = {}) {
if (!this.debugMode) return;
const emoji = {
'MUSIC': '🎵',
'VOICE': '🎤',
'SFX': '🔊',
'INIT': '🎛️',
'STOP': '⏹️',
'ERROR': '❌',
'INFO': ''
};
const timestamp = new Date().toLocaleTimeString();
console.log(`${emoji[type] || '📢'} [${type}] ${timestamp} - ${message}`);
if (Object.keys(details).length > 0) {
console.log(' Details:', details);
}
}
/**
* PLAY MUSIC
*/
playMusic(key, options = {}) {
if (!this.scene) {
this.log('Scene not initialized!', 'ERROR');
return null;
}
// Stop current music
if (this.currentMusic) {
this.stopMusic();
}
// Get file key from map
const fileKey = this.audioMap.music[key] || key;
// Check if audio exists
if (!this.scene.cache.audio.exists(fileKey)) {
this.log(`Music not found: ${fileKey}`, 'ERROR');
return null;
}
// Play music
const config = {
volume: options.volume || this.musicVolume,
loop: options.loop !== undefined ? options.loop : true,
...options
};
this.currentMusic = this.scene.sound.add(fileKey, config);
this.currentMusic.play();
// Debug log
this.log(`Playing: ${fileKey}.mp3`, 'MUSIC', {
scene: this.scene.scene.key,
volume: config.volume,
loop: config.loop,
attribution: 'Kevin MacLeod (incompetech.com)'
});
return this.currentMusic;
}
/**
* STOP MUSIC
*/
stopMusic(fadeOut = true) {
if (!this.currentMusic) return;
this.log(`Stopping: ${this.currentMusic.key}`, 'STOP');
if (fadeOut) {
this.scene.tweens.add({
targets: this.currentMusic,
volume: 0,
duration: 1000,
onComplete: () => {
if (this.currentMusic) {
this.currentMusic.stop();
this.currentMusic = null;
}
}
});
} else {
this.currentMusic.stop();
this.currentMusic = null;
}
}
/**
* CROSSFADE MUSIC
*/
crossfadeMusic(newKey, duration = 2000) {
const oldMusic = this.currentMusic;
// Start new music at volume 0
const newMusic = this.playMusic(newKey, { volume: 0 });
if (!newMusic) return;
// Fade out old, fade in new
if (oldMusic) {
this.scene.tweens.add({
targets: oldMusic,
volume: 0,
duration: duration,
onComplete: () => {
oldMusic.stop();
}
});
}
this.scene.tweens.add({
targets: newMusic,
volume: this.musicVolume,
duration: duration
});
this.log(`Crossfading to: ${newKey}`, 'MUSIC', {
duration: `${duration}ms`,
from: oldMusic ? oldMusic.key : 'none'
});
this.currentMusic = newMusic;
}
/**
* PLAY VOICE
*/
playVoice(key, subtitleText = '') {
if (!this.scene) {
this.log('Scene not initialized!', 'ERROR');
return null;
}
// Stop current voice
if (this.currentVoice && this.currentVoice.isPlaying) {
this.currentVoice.stop();
}
// Check if audio exists
if (!this.scene.cache.audio.exists(key)) {
this.log(`Voice not found: ${key}`, 'ERROR');
return null;
}
// Play voice
this.currentVoice = this.scene.sound.add(key, {
volume: this.voiceVolume
});
this.currentVoice.play();
// Debug log
this.log(`Playing: ${key}.mp3`, 'VOICE', {
scene: this.scene.scene.key,
subtitle: subtitleText || '(no subtitle)',
volume: this.voiceVolume,
duration: `~3s`
});
return this.currentVoice;
}
/**
* PLAY SFX
*/
playSFX(key, options = {}) {
if (!this.scene) {
this.log('Scene not initialized!', 'ERROR');
return null;
}
// Get file key from map
const fileKey = this.audioMap.sfx[key] || key;
// Check if audio exists
if (!this.scene.cache.audio.exists(fileKey)) {
this.log(`SFX not found: ${fileKey}`, 'ERROR');
return null;
}
// Play SFX
const config = {
volume: options.volume || this.sfxVolume,
loop: options.loop || false,
detune: options.detune || 0,
...options
};
const sfx = this.scene.sound.add(fileKey, config);
sfx.play();
// Debug log
this.log(`Playing: ${fileKey}.wav`, 'SFX', {
scene: this.scene.scene.key,
trigger: options.trigger || 'manual',
volume: config.volume,
loop: config.loop
});
return sfx;
}
/**
* PLAY UI SOUND
*/
playUI(action) {
const sounds = {
click: 'ui_click',
hover: 'ui_hover',
open: 'ui_open',
close: 'ui_close',
error: 'ui_error'
};
if (sounds[action]) {
this.playSFX(sounds[action], {
trigger: `UI ${action}`,
volume: this.sfxVolume * 0.8
});
}
}
/**
* PLAY FARMING SOUND
*/
playFarming(action) {
const sounds = {
plant: 'plant',
water: 'water',
harvest: 'harvest',
hoe: 'hoe_dirt'
};
if (sounds[action]) {
this.playSFX(sounds[action], {
trigger: `Farming: ${action}`,
volume: this.sfxVolume
});
}
}
/**
* PLAY ANIMAL SOUND
*/
playAnimal(type) {
const sounds = {
cat: 'cat_meow',
dog: 'dog_bark',
susi: 'susi_bark'
};
if (sounds[type]) {
this.playSFX(sounds[type], {
trigger: `Animal: ${type}`,
volume: this.sfxVolume * 0.7,
detune: Phaser.Math.Between(-200, 200) // Vary pitch
});
}
}
/**
* PLAY SPECIAL EFFECT
*/
playSpecial(event) {
const sounds = {
memory: 'memory_found',
levelup: 'level_up',
quest: 'quest_complete',
unlock: 'companion_unlock'
};
if (sounds[event]) {
this.playSFX(sounds[event], {
trigger: `Special: ${event}`,
volume: this.sfxVolume * 1.2 // Louder for important events
});
}
}
/**
* SET VOLUMES
*/
setMusicVolume(volume) {
this.musicVolume = Phaser.Math.Clamp(volume, 0, 1);
if (this.currentMusic) {
this.currentMusic.setVolume(this.musicVolume);
}
this.log(`Music volume set to: ${this.musicVolume}`, 'INFO');
}
setVoiceVolume(volume) {
this.voiceVolume = Phaser.Math.Clamp(volume, 0, 1);
this.log(`Voice volume set to: ${this.voiceVolume}`, 'INFO');
}
setSFXVolume(volume) {
this.sfxVolume = Phaser.Math.Clamp(volume, 0, 1);
this.log(`SFX volume set to: ${this.sfxVolume}`, 'INFO');
}
/**
* MUTE ALL AUDIO
*/
muteAll() {
if (this.scene) {
this.scene.sound.mute = true;
this.log('All audio muted', 'INFO');
}
}
/**
* UNMUTE ALL AUDIO
*/
unmuteAll() {
if (this.scene) {
this.scene.sound.mute = false;
this.log('All audio unmuted', 'INFO');
}
}
/**
* STOP ALL AUDIO
*/
stopAll() {
if (this.scene) {
this.scene.sound.stopAll();
this.currentMusic = null;
this.currentVoice = null;
this.log('All audio stopped', 'STOP');
}
}
/**
* GET ATTRIBUTION TEXT
*/
getAttribution(type = 'all') {
if (type === 'music') {
return this.attribution.music;
} else if (type === 'voices') {
return this.attribution.voices;
} else {
return `${this.attribution.music}\n\n${this.attribution.voices}`;
}
}
/**
* ENABLE/DISABLE DEBUG MODE
*/
setDebugMode(enabled) {
this.debugMode = enabled;
this.log(`Debug mode ${enabled ? 'enabled' : 'disabled'}`, 'INFO');
}
}
// Singleton export
const audioManager = new AudioManager();
export default audioManager;