🎬🎵 Jan 8 COMPLETE AUDIO PRODUCTION - Intro + Music + SFX

 FULL AUDIO SYSTEM READY (104 FILES):

**🎙️ INTRO CUTSCENE VOICES (4 NEW MP3):**
Generated via Edge TTS:
1. 01_narrator_flyover.mp3 (73KB) - 'They say the world didn't die...'
2. 02_kai_awakening.mp3 (28KB) - 'My head... it hurts. Where am I?'
3. 03_kai_truth_part1.mp3 (67KB) - 'Kai Marković. 14 years old...'
4. 04_kai_truth_part2.mp3 (51KB) - 'I'm coming to find you... Ana.'

**Story Structure:**
- Part 1: The Flyover (0:00-0:45) - Narrator
- Part 2: The Awakening (0:45-1:10) - Kai confused
- Part 3: The Truth (1:10-2:00) - Kai determined

**🎵 MUSIC PLACEHOLDERS (7 WAV - 60MB):**
Simple ambient loops for testing:
1. main_theme.wav (90s) - Menu music
2. farm_ambient.wav (120s) - Farming
3. town_theme.wav (90s) - Town restoration
4. combat_theme.wav (60s) - Battle
5. night_theme.wav (180s) - Nighttime
6. victory_theme.wav (30s) - Quest complete
7. ana_theme.wav (120s) - Emotional/flashback

**🔊 SFX PLACEHOLDERS (23 WAV - 1.5MB):**
Farming (8): plant_seed, water_crop, harvest, dig, scythe, mine, chop, cow
Combat (8): sword, bow, zombie_hit, zombie_death, hurt, shield, explosion, raider
Building (5): chest, door_open, door_close, hammer, repair
Misc (2): coin_collect, level_up

**📊 TOTAL AUDIO INVENTORY:**
- Voice Files: 28 MP3 (24 existing + 4 new intro)
- Voiceover: 43 WAV (prologue cutscenes)
- Sound Effects: 25 WAV (2 existing + 23 new placeholders)
- Music: 8 tracks (1 existing + 7 new placeholders)
- **TOTAL: 104 audio files!**

**🎮 INTEGRATION:**
- Updated PreloadScene with intro voice loading
- All audio keys ready for use in intro cutscene
- BiomeMusicSystem ready for 7-track cross-fade
- AudioTriggerSystem ready for all 23 SFX

**📝 SCRIPTS CREATED:**
1. scripts/generate_intro_cutscene.py - Intro dialogue generation
2. scripts/generate_audio_placeholders.py - Music + SFX placeholders

**🎯 USAGE:**
Intro voices: 'intro_flyover', 'intro_awakening', 'intro_truth_1', 'intro_truth_2'
Music: Load from assets/audio/music/*.wav
SFX: Load from assets/audio/sfx/[category]/*.wav

**⚠️  NOTE:** Music/SFX are PLACEHOLDERS (simple tones)
Replace with real audio from Freesound.org or AI generators later!

🎉 AUDIO SYSTEM 100% FUNCTIONAL FOR TESTING!
This commit is contained in:
2026-01-08 17:05:57 +01:00
parent 340578d3b3
commit 4a5b788ad4
37 changed files with 287 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""
Audio Placeholder Generator
Creates simple placeholder audio files for testing
"""
import numpy as np
from scipy.io import wavfile
from pathlib import Path
SAMPLE_RATE = 44100 # CD quality
OUTPUT_DIR_MUSIC = Path("/Users/davidkotnik/repos/novafarma/assets/audio/music")
OUTPUT_DIR_SFX = Path("/Users/davidkotnik/repos/novafarma/assets/audio/sfx")
def generate_tone(frequency, duration, sample_rate=SAMPLE_RATE):
"""Generate a simple sine wave tone"""
t = np.linspace(0, duration, int(sample_rate * duration))
tone = np.sin(2 * np.pi * frequency * t)
# Convert to 16-bit PCM
tone = (tone * 32767).astype(np.int16)
return tone
def generate_noise(duration, sample_rate=SAMPLE_RATE):
"""Generate white noise"""
noise = np.random.normal(0, 0.1, int(sample_rate * duration))
noise = (noise * 32767).astype(np.int16)
return noise
def generate_ambient_loop(duration=60, base_freq=220):
"""Generate simple ambient loop"""
t = np.linspace(0, duration, int(SAMPLE_RATE * duration))
# Multiple sine waves for ambient feel
ambient = (
0.3 * np.sin(2 * np.pi * base_freq * t) +
0.2 * np.sin(2 * np.pi * (base_freq * 1.5) * t) +
0.15 * np.sin(2 * np.pi * (base_freq * 0.75) * t) +
0.05 * np.random.normal(0, 0.1, len(t)) # Subtle noise
)
# Fade in/out for seamless loop
fade_samples = int(SAMPLE_RATE * 2) # 2 second fade
fade_in = np.linspace(0, 1, fade_samples)
fade_out = np.linspace(1, 0, fade_samples)
ambient[:fade_samples] *= fade_in
ambient[-fade_samples:] *= fade_out
ambient = (ambient * 32767 * 0.5).astype(np.int16)
return ambient
def create_music_placeholders():
"""Create 7 music track placeholders"""
print("\n🎵 CREATING MUSIC PLACEHOLDERS...")
print("="*60)
OUTPUT_DIR_MUSIC.mkdir(parents=True, exist_ok=True)
music_tracks = {
"main_theme": (440, 90), # A note, 1.5 min
"farm_ambient": (220, 120), # A lower, 2 min
"town_theme": (330, 90), # E note
"combat_theme": (523, 60), # C note, 1 min
"night_theme": (196, 180), # G note, 3 min
"victory_theme": (659, 30), # E note, 30 sec
"ana_theme": (247, 120), # B note, 2 min
}
for name, (freq, duration) in music_tracks.items():
print(f"\n🎶 Generating: {name}.wav")
print(f" Frequency: {freq}Hz, Duration: {duration}s")
audio = generate_ambient_loop(duration, freq)
output_path = OUTPUT_DIR_MUSIC / f"{name}.wav"
wavfile.write(output_path, SAMPLE_RATE, audio)
size = output_path.stat().st_size
print(f" ✅ Saved: {size:,} bytes")
print(f"\n✅ Created {len(music_tracks)} music placeholders")
def create_sfx_placeholders():
"""Create 23 SFX placeholders"""
print("\n\n🔊 CREATING SFX PLACEHOLDERS...")
print("="*60)
sfx_sounds = {
# Farming (8)
"farming/plant_seed": (880, 0.3),
"farming/water_crop": (440, 1.0),
"farming/harvest": (1320, 0.5),
"farming/dig": (220, 0.8),
"farming/scythe_swing": (660, 0.6),
"farming/stone_mine": (330, 0.7),
"farming/tree_chop": (165, 0.8),
"farming/cow_moo": (110, 1.5),
# Combat (8)
"combat/sword_slash": (1100, 0.4),
"combat/bow_release": (880, 0.3),
"combat/zombie_hit": (220, 0.5),
"combat/zombie_death": (110, 1.0),
"combat/player_hurt": (330, 0.5),
"combat/shield_block": (440, 0.4),
"combat/explosion": (55, 1.5),
"combat/raider_attack": (165, 0.8),
# Building (5)
"building/chest_open": (660, 0.8),
"building/door_open": (440, 0.6),
"building/door_close": (330, 0.6),
"building/hammer_nail": (1100, 0.3),
"building/repair": (880, 1.0),
# Misc (2)
"misc/coin_collect": (1760, 0.3),
"misc/level_up": (1320, 1.5),
}
for name, (freq, duration) in sfx_sounds.items():
# Create subfolder if needed
sfx_path = OUTPUT_DIR_SFX / name.split('/')[0]
sfx_path.mkdir(parents=True, exist_ok=True)
filename = name.split('/')[1] + ".wav"
output_path = sfx_path / filename
print(f"\n🔊 Generating: {name}.wav")
print(f" Frequency: {freq}Hz, Duration: {duration}s")
audio = generate_tone(freq, duration)
wavfile.write(output_path, SAMPLE_RATE, audio)
size = output_path.stat().st_size
print(f" ✅ Saved: {size:,} bytes")
print(f"\n✅ Created {len(sfx_sounds)} SFX placeholders")
def main():
"""Generate all placeholders"""
print("="*60)
print("🎨 AUDIO PLACEHOLDER GENERATOR")
print("="*60)
print("\nGenerating simple tone-based placeholders for testing...")
print("Replace these with real audio later!")
create_music_placeholders()
create_sfx_placeholders()
print("\n" + "="*60)
print("✅ ALL PLACEHOLDERS GENERATED!")
print("="*60)
print("\nTotal files created: 30 (7 music + 23 SFX)")
print("\n⚠️ NOTE: These are simple BEEP PLACEHOLDERS for testing!")
print("Replace with real audio from Freesound.org or AI generators.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
Complete Intro Cutscene Voice Generation
Generates all dialogue for the intro sequence
"""
import asyncio
import edge_tts
from pathlib import Path
OUTPUT_DIR = Path("/Users/davidkotnik/repos/novafarma/assets/audio/voiceover/intro")
# Voice configurations
NARRATOR_VOICE = "en-US-GuyNeural" # Deep, mysterious
KAI_VOICE = "en-US-AvaNeural" # Young female
async def generate_intro_voices():
"""Generate all intro cutscene dialogue"""
# Create output directory
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
print("🎬 GENERATING INTRO CUTSCENE VOICES...")
print("="*60)
# ========================================
# PART 1: THE FLYOVER (Narrator)
# ========================================
print("\n📍 Part 1: The Flyover (Narrator)")
narrator_flyover = (
"They say the world didn't die with a bang... "
"but with a quiet whisper. "
"The Valley of Death is not just a place. "
"It's a memory that no one wants to have anymore."
)
await generate_voice(
text=narrator_flyover,
voice=NARRATOR_VOICE,
output_path=OUTPUT_DIR / "01_narrator_flyover.mp3",
rate="-10%", # Slower, dramatic
pitch="-5Hz" # Deeper
)
# ========================================
# PART 2: THE AWAKENING (Kai)
# ========================================
print("\n📍 Part 2: The Awakening (Kai)")
kai_awakening = (
"My head... it hurts. Where am I? Who am I...?"
)
await generate_voice(
text=kai_awakening,
voice=KAI_VOICE,
output_path=OUTPUT_DIR / "02_kai_awakening.mp3",
rate="-15%", # Slower, confused
pitch="-3Hz" # Slightly lower
)
# ========================================
# PART 3: THE TRUTH (Kai - multiple lines)
# ========================================
print("\n📍 Part 3: The Truth (Kai)")
# Line 1: Reading ID card
kai_truth_1 = (
"Kai Marković. 14 years old. That's me. "
"But this other girl... why do I feel so... empty when I see her? "
"Like I'm missing half of my heart."
)
await generate_voice(
text=kai_truth_1,
voice=KAI_VOICE,
output_path=OUTPUT_DIR / "03_kai_truth_part1.mp3",
rate="-5%", # Normal pace, emotional
pitch="+0Hz"
)
# Line 2: Final determination
kai_truth_2 = (
"Someone is waiting for me out there. "
"I can't remember the face, but I feel the promise. "
"I'm coming to find you... Ana."
)
await generate_voice(
text=kai_truth_2,
voice=KAI_VOICE,
output_path=OUTPUT_DIR / "04_kai_truth_part2.mp3",
rate="+0%", # Normal pace, determined
pitch="+2Hz" # Slightly higher, hopeful
)
print("\n" + "="*60)
print("✅ ALL INTRO VOICES GENERATED!")
print("="*60)
print(f"\nOutput directory: {OUTPUT_DIR}")
print("\nGenerated files:")
print(" 1. 01_narrator_flyover.mp3 (Narrator - The Flyover)")
print(" 2. 02_kai_awakening.mp3 (Kai - Awakening)")
print(" 3. 03_kai_truth_part1.mp3 (Kai - Reading ID)")
print(" 4. 04_kai_truth_part2.mp3 (Kai - Determination)")
async def generate_voice(text, voice, output_path, rate="+0%", pitch="+0Hz"):
"""Generate single voice line"""
print(f"\n🎙️ Generating: {output_path.name}")
print(f" Voice: {voice}")
print(f" Text: {text[:60]}...")
communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
await communicate.save(str(output_path))
size = output_path.stat().st_size
print(f" ✅ Saved: {size:,} bytes")
if __name__ == "__main__":
asyncio.run(generate_intro_voices())

View File

@@ -61,6 +61,12 @@ class PreloadScene extends Phaser.Scene {
const basePath = 'assets/audio/voices/';
// 🎬 INTRO CUTSCENE VOICES (NEW!)
this.loadAudioSafe('intro_flyover', 'assets/audio/voiceover/intro/01_narrator_flyover.mp3');
this.loadAudioSafe('intro_awakening', 'assets/audio/voiceover/intro/02_kai_awakening.mp3');
this.loadAudioSafe('intro_truth_1', 'assets/audio/voiceover/intro/03_kai_truth_part1.mp3');
this.loadAudioSafe('intro_truth_2', 'assets/audio/voiceover/intro/04_kai_truth_part2.mp3');
// Narrator (cinematic)
this.loadAudioSafe('narrator_intro', basePath + 'narrator/intro_cutscene.mp3');
this.loadAudioSafe('narrator_memory', basePath + 'narrator/kai_memory_ana.mp3');