Files
tony-remake/tools/tfmx_audio.py
T
ARIAandClaude Sonnet 5 6d196020db Pfade relativ statt hardcodiert + Rauser/Titelbild-Korrektur per Pixelvermessung
- intro_sequence.py, tfmx_audio.py, render_assets.py, dat_extract.py:
  /home/aria/kellogg_remake/... durch PROJECT_ROOT-relative Pfade ersetzt
  (mit optionalem ENV-Override), laeuft jetzt von jedem Checkout aus.
- Rauser-Logo: war 1:1 nativ gezeichnet (viel zu gross) und weisser statt
  grauer Hintergrund. Per frischer DOSBox-Referenzaufnahme (Xvfb-Screenshot-
  Serie) neu vermessen: 0.5x Skalierung, Slide-in-Animation (RAUSER-Box von
  links, "!"-Box von oben, ADVERTAINMENT steht fest), Sound-Sting (TITEL2,
  einmalig) bei Ankunft, danach Fade weiss->grau.
- Titelbild: fruehere Annahme (4 Quadranten voll gestapelt = 320x480) war
  falsch und die Ursache fuer den gemeldeten Squish-Bug. Per Pixel-
  Rekonstruktion verifiziert: jeder Quadrant wird nur auf halber HOEHE
  (160x120 statt 160x240, Breite bleibt nativ) im 2x2-Raster angeordnet ->
  korrektes 320x240-Bild. Zusaetzlich neu entdeckter und nachgebauter
  Scanline/Graustufen-Reveal-Effekt auf der unteren Bildhaelfte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 21:01:47 +00:00

75 lines
2.9 KiB
Python

"""
Duenner Python-Wrapper um das MIT-lizenzierte `tfmx_player/render_tfmx`
(siehe tools/tfmx_player/LICENSE, Copyright Peter Fors) -- rendert ein
TFMX-Musikstueck (<NAME>.TFX + <NAME>.SAM aus PCKELL.DAT) einmalig zu einer
WAV-Datei und cached das Ergebnis, damit die Intro-Sequenz die Musik per
pygame.mixer abspielen kann.
Warum Subprocess statt reinem Python-Reimplement: TFMX ist ein komplexes
Amiga-Tracker-Format (siehe tfmx.h, ~95KB reine Player-Logik). Das
MIT-lizenzierte C-Referenztool duerfen wir direkt nutzen (im Gegensatz zum
lizenzlosen C#-Referenzprojekt fuer PCX/BOB/MAP, das wir bewusst nur als
Format-Wissen genommen und selbst neu geschrieben haben). Fuer Musik-
Wiedergabe ist der Sound selbst das Ziel, nicht der Decoder-Code -- ein
funktionierender, korrekt lizenzierter Renderer ist hier der pragmatische
Weg statt TFMX in Python neu zu erfinden.
Gerenderte WAVs sind urheberrechtlich das Original-Spielmaterial (Musik),
werden daher NICHT ins Git-Repo committed (wie raw/ und extracted_dat/) --
liegen nur lokal im Cache-Verzeichnis.
"""
import os
import subprocess
import sys
_HERE = os.path.dirname(os.path.abspath(__file__))
_PLAYER_DIR = os.path.join(_HERE, 'tfmx_player')
_BINARY = os.path.join(_PLAYER_DIR, 'render_tfmx')
_PROJECT_ROOT = os.path.dirname(_HERE)
_CACHE_DIR = os.environ.get('KELLOGG_AUDIO_CACHE', os.path.join(_PROJECT_ROOT, 'audio_cache'))
def _ensure_binary():
if os.path.exists(_BINARY):
return
subprocess.run(['bash', os.path.join(_PLAYER_DIR, 'build.sh')], check=True)
def render_wav(container, name, seconds=20, force=False):
"""container: kellogg_formats.DATContainer; name: z.B. 'TITEL' (ohne Extension).
Gibt den Pfad zur gerenderten (gecachten) WAV-Datei zurueck."""
os.makedirs(_CACHE_DIR, exist_ok=True)
out_path = os.path.join(_CACHE_DIR, f'{name}.wav')
if os.path.exists(out_path) and not force:
return out_path
_ensure_binary()
tfx_data = container.entries[name + '.TFX']
sam_data = container.entries[name + '.SAM']
tfx_path = os.path.join(_CACHE_DIR, f'{name}.TFX')
sam_path = os.path.join(_CACHE_DIR, f'{name}.SAM')
with open(tfx_path, 'wb') as f:
f.write(tfx_data)
with open(sam_path, 'wb') as f:
f.write(sam_data)
result = subprocess.run(
[_BINARY, tfx_path, sam_path, out_path, str(seconds)],
capture_output=True, text=True
)
if result.returncode != 0:
raise RuntimeError(f'render_tfmx failed for {name}: {result.stderr}')
return out_path
if __name__ == '__main__':
# Kleiner CLI-Test: python3 tfmx_audio.py <DAT_PATH> <NAME> [seconds]
sys.path.insert(0, _HERE)
from kellogg_formats import DATContainer
dat_path = sys.argv[1]
name = sys.argv[2]
seconds = int(sys.argv[3]) if len(sys.argv) > 3 else 20
c = DATContainer(dat_path)
path = render_wav(c, name, seconds, force=True)
print(f'rendered: {path}')