← terug naar artikelen

Gratis audiocommentaar genereren voor de tafelvoetbal-app

TLDR

Ik liet chatterbox, een lokaal en gratis voice-clone model, per speler tafelvoetbal-commentaar genereren in de stem van Youri Mulder, via een template en een batchscript. Wat het leuk maakt: het draait lokaal op mijn eigen machine, dus ik kan eindeloos blijven itereren tot het commentaar precies goed klinkt.

Een tafelvoetbaltafel met commentaar van Youri Mulder

Op ons kantoor speelt een select groepje collega’s graag tafelvoetbal. Hiervoor heb ik een score-app gebouwd. In de app tellen we goals, houden we de standen bij en verzamelen we alle statistieken om te zien wie het beste is. Op een gegeven moment dacht ik: wat als de app ook commentaar geeft? Niet een generieke piep bij een goal, maar een stem die de naam van de speler noemt.

Ik koos voor de stem van Youri Mulder, zodat we het commentaar hebben zoals in de vroegere FIFA games. Droge understatement bij een goal, absurd optimisme bij een kansloze achterstand, een knipoog naar Evert ten Napel en af en toe een Cruijff-citaat. Precies dat maakt het grappig. En het draait lokaal, gratis, op mijn Macbookie.

Chatterbox: de stem inladen

Chatterbox is het open-source TTS-model van Resemble AI, MIT-gelicentieerd en volledig lokaal te draaien. In blinde tests verkoos 63,75% het boven ElevenLabs, dus je levert er weinig voor in qua kwaliteit. Ik heb het een clip van een paar seconden uit een interview met Youri gegeven en that’s it. Geen fine-tuning, geen training, gewoon de clip meegeven op het moment van genereren.

Eén stemvoorbeeld, een berg templates, een script dat de rest doet

De tafelvoetbal-app heeft een taxonomie van zo’n zestien buckets: goal, own_goal, equalizer, hattrick, klinker, comeback_completed, etc. De aan een speler gebonden buckets krijgen een {name}, de generieke (match_start, match_end_loser) niet. Per bucket genereer ik tientallen regels in Youri’s stem:

goal:
  "{name} maakt doelpunten alsof het warme broodjes zijn."
  "Prima afgemaakt door {name}. Niks op aan te merken."
  "Doelpunt {name}. De keeper koos links, de bal koos rechts."

own_goal:
  "Eigen doelpunt van {name}. Briljant gewoon."
  "Als {name} dit in z'n achtertuin deed, trok ie de gordijnen dicht."

Zo klinkt dat dan, als ik zelf scoor:

Vervolgens loop ik met een batchscript door alle (bucket, template, speler)-combinaties en synthetiseer per regel een wav. Twee dingen maken dat werkbaar. Het script is idempotent, dus bestaande bestanden slaat ie over:

if out_path.exists():
    return  # al gegenereerd, sla over

Zo kan ik het eindeloos herdraaien terwijl ik de templates tweak, zonder alles opnieuw te renderen. En sequentieel duurde het te lang, dus ik heb het geparallelliseerd in vijf losse workers, die elk hun eigen slice pakken via een modulo-shard:

# elke worker pakt alleen z'n eigen jobs
jobs = [j for i, j in enumerate(jobs)
        if i % worker_count == worker_index]

Elke worker houdt z’n eigen model in het MPS-geheugen van de machine. Ik heb 64GB RAM op mijn Macbookie, dus dat past prima.

Wav is te dik, dus mp3

Na het genereren zit je met een berg wav-bestanden. Lossless, maar enorm. Voor een app op een tablet naast de tafel wil je dat niet. Eén ffmpeg-regel mastert alles meteen luid en comprimeert naar mp3:

ffmpeg -i in.wav \
  -af "acompressor=threshold=-20dB:ratio=4:makeup=11,alimiter=limit=0.85" \
  -ar 48000 -ac 1 -b:a 128k out.mp3

Compressor en limiter erbij, zodat het commentaar boven het achtergrondgeluid van de app uit komt.

De ruwe randjes

De checkpoints van chatterbox zijn op een CUDA-machine opgeslagen; op mijn Mac zonder CUDA moet ik het laden naar CPU forceren en daarna alsnog naar mps duwen, anders faalt het. Het werkt maar wel met een lelijke duct tape:

# checkpoints verwachten CUDA; forceer CPU-mapping op de Mac
torch.load = lambda *a, **k: _orig(*a, **{**k, "map_location": "cpu"})

De audio is functioneel, niet gepolijst: geen intonatie-variatie, geen fallback als een naam raar wordt uitgesproken. En ik moet nog beslissen of de mp3’s in git lfs gaan of gewoon los in de repo blijven, want zelfs gecomprimeerd tikt het aardig aan zodra elke speler meerdere buckets met meerdere regels krijgt.

Maaaaaar, het werkt.

En nu maak ik audiocommentaar alsof het warme broodjes zijn.

Eén keer per maand een mailtje met wat ik schreef. Geen spam, geen gedoe.