# Heli-Audio — Konzept V2

Stand: 2026-05-31 · Autor: Atlas · Status: Diskussion mit Thomas

## Thomas-Direktiven (2026-05-31)

> Wir brauchen ein geschicktes Konzept, dass nicht irgendwo ewige Stille ist. Gleichzeitig gibt es zentral wichtige Ansagen/Geräusche (Unfall, Warnung, Missionsinfo) und weniger wichtige (geografische Info) — die aber natürlich auch gespielt werden sollen, wenn möglich.
>
> Ducking: ja. Crossfade weglassen. Geo-Audios werden von Notfall-Zwischenrufen überlagert bzw. gestoppt. Alles andere (Environmental, Reverb, Spatial): machen.

## Audio-Schichten (Architektur-Bild)

```
              ┌───────────────────────────────────────────┐
SCHICHT 1     │   AMBIENT (loop, Volume 0.1)              │   immer da
(Hintergrund) │   • Wind/Wasser/Verkehr je Szenario       │   nie still
              │   • Heli-Rotor (nur Flight-Phase)         │
              │   • Musik (nur Mission/Results-Phase)     │
              └───────────────────────────────────────────┘
                              ▲ Ducking: 0.10 → 0.04 wenn Stimme spielt
              ┌───────────────────────────────────────────┐
SCHICHT 2     │   SPRACH-PIPE (seriell, prioritätssortiert)│
(Stimmen)     │                                            │
              │   CRITICAL (1) — unterbricht alles         │
              │     • Crash-Warnung, Boden-Alarm           │
              │     • Tower-Notfunk, Pilot-Übernahme       │
              │     • Stop!-Ansagen                        │
              │                                            │
              │   HIGH (2)   — Standard-Funk-Pipe          │
              │     • Tower-Wegpunkt-Anweisungen           │
              │     • Mission-Briefing                     │
              │     • Pilot-Antworten                      │
              │                                            │
              │   MEDIUM (3) — wenn Pipe leer ist          │
              │     • Geografische Hintergrund-Infos       │
              │     • Stimmung („Sicht klar", „Wind ruhig")│
              │     • Stadt-/Berg-Fakten beim Überflug     │
              │                                            │
              │   FILLER (4) — Anti-Stille                 │
              │     • Wenn Pipe >12 s leer: Filler-Item    │
              │     • Cockpit-Geräusche, Pilot-Murmeln     │
              └───────────────────────────────────────────┘
                              │
                              ▼
              ┌───────────────────────────────────────────┐
SCHICHT 3     │   FEEDBACK-SOUNDS (kurz, ohne Pipe)        │   sofort
(UI/Feedback) │   • Klick-OK, Klick-Fehler                 │   parallel
              │   • Wegpunkt-Treffer-Glocke                │   zur Pipe
              └───────────────────────────────────────────┘
```

## Prioritäten-Logik

Aktuell hat die Pipe nur: **seriell + 10s-Gap + `priority:'now'`-Bypass**. Wir brauchen ein **vollwertiges Prioritäten-System** mit 4 Levels.

### Pipe-Verhalten pro Priorität

| Level | Verhalten beim Einreihen | Beispiel-Audios |
|---|---|---|
| **1 CRITICAL** | Laufendes Audio per 120ms-Fade abbrechen, gesamte Queue leeren, sofort spielen | „Achtung! Boden zu nah!", „Pilot übernimmt — wir landen jetzt!", „Stop! Falsche Richtung!" |
| **2 HIGH** | Hinten in Queue einreihen, nach Gap (3 s) spielen. Wird von CRITICAL unterbrochen. | Tower-Wegpunkt-Anweisungen, Briefing-Hinweise, Pilot-Antworten |
| **3 MEDIUM** | Nur einreihen wenn keine HIGH-Items in Queue. Wird beim Eintreffen eines HIGH-Items aus der Queue entfernt (verlieren ihre Chance), beim Eintreffen eines CRITICAL auch sofort gestoppt wenn laufend. | Geo-Fakten („Wir überfliegen Schwaz, drittgrößte Stadt Tirols"), Stadt-Kommentare |
| **4 FILLER** | Wird automatisch eingereiht wenn Pipe >12 s leer war. Wird von HIGH/CRITICAL überschrieben. | Cockpit-Klick, Pilot-Räuspern, „Sicht ist klar", „Höhe stabil" |

### Code-Konzept (Pseudo-JS)

```js
// Ersetzt die aktuelle flightAudioQueue durch priority-Queue
var pipe = {
  current: null,           // {src, priority, _fadeGain}
  queue: [],               // sorted by priority
  lastSpokenMs: 0,         // für FILLER-Trigger
  gapMs: { 1: 0, 2: 3000, 3: 5000, 4: 8000 }
};

function enqueueSpeech(path, priority) {
  // 1) CRITICAL: alles wegfegen, sofort spielen
  if (priority === 1) {
    abortCurrentWithFade(120);
    pipe.queue.length = 0;
    pipe.queue.push({path, priority});
    pump();
    return;
  }
  // 2) HIGH: einreihen, MEDIUM-Items davor aus Queue entfernen
  if (priority === 2) {
    pipe.queue = pipe.queue.filter(it => it.priority <= 2);
    pipe.queue.push({path, priority});
    pump();
    return;
  }
  // 3) MEDIUM: nur einreihen wenn keine HIGH in Queue ist
  if (priority === 3) {
    if (pipe.queue.some(it => it.priority <= 2)) return;
    pipe.queue.push({path, priority});
    pump();
    return;
  }
  // 4) FILLER: separate Strategie (siehe unten)
}

function pump() {
  if (pipe.current) return;
  if (!pipe.queue.length) return;
  var now = performance.now();
  // Gap nur ANWENDEN, wenn dasselbe oder höhere Level wartet
  var next = pipe.queue[0];
  var requiredGap = pipe.gapMs[next.priority];
  if (now - pipe.lastSpokenMs < requiredGap) {
    setTimeout(pump, requiredGap - (now - pipe.lastSpokenMs));
    return;
  }
  var item = pipe.queue.shift();
  playWithDucking(item).then(() => {
    pipe.current = null;
    pipe.lastSpokenMs = performance.now();
    pump();
  });
}

// Anti-Stille: alle 2s prüfen, ob FILLER nötig
setInterval(() => {
  if (pipe.current || pipe.queue.length) return;
  if (performance.now() - pipe.lastSpokenMs < 12000) return;
  enqueueFiller();  // wählt Filler je nach Phase + Szenario
}, 2000);
```

## Ducking

Wenn Stimme spielt → **Rotor-Lautstärke von 0.30 auf 0.08 fadrenken** (300 ms attack), nach Stimmenende **3 s linear zurück auf 0.30** (release). Gleiches für Hintergrund-Musik (von 0.20 auf 0.05). Environmental-Loops bleiben unverändert (sie sind schon leise genug).

Code-Skizze:
```js
function duckAmbient(on) {
  var t = audioCtx.currentTime;
  var targetRotor = on ? 0.08 : 0.30;
  var targetMusic = on ? 0.05 : 0.20;
  var rampSec = on ? 0.3 : 3.0;
  rotorGain.gain.cancelScheduledValues(t);
  rotorGain.gain.linearRampToValueAtTime(targetRotor, t + rampSec);
  musicGain.gain.cancelScheduledValues(t);
  musicGain.gain.linearRampToValueAtTime(targetMusic, t + rampSec);
}
```

## „Keine ewige Stille" — FILLER-Strategie

Pool von **15–20 sehr kurzen Filler-Audios** (1–3 s), die je nach Phase + Szenario gewählt werden:

| Phase | Filler-Pool |
|---|---|
| **plan** (Routenplanung) | „Hmm…", „Schauen wir mal", „Lass mich nachdenken", Cockpit-Schalter-Klick |
| **flight** Berg | Pilot: „Wind ruhig", „Sicht klar", Höhenmesser-Beep, „Höhe stabil" |
| **flight** Wasser | Pilot: „Wellen ruhig", „Patient in Sicht", Wasser-Plätschern |
| **flight** Autobahn | Pilot: „Funk-Check", „Verkehr läuft" |
| **landing** | Pilot: „Sinkrate gut", „Ein Meter", „Last steady" |

Plus: **Cockpit-Ambient-Loops** auf Schicht 1 (siehe oben) — die laufen IMMER, also ist nie wirklich Stille.

## Environmental-Loops (Schicht 1, neu)

Pro Szenario ein 30–60 s-Loop, Volume 0.10 (wird bei Stimme auf 0.04 geduckt):

| Szenario | Loop | Beschreibung |
|---|---|---|
| `basis` (Standard) | `amb_general.mp3` | leises Cockpit-Brummen + Wind |
| `wasser` | `amb_water.mp3` | Wellen, Möwen, Wind |
| `berg-fels` | `amb_mountain.mp3` | Wind, leichter Hall |
| `berg-schnee` | `amb_snow.mp3` | Sturm-Säuseln, Schnee |
| `autobahn` | `amb_highway.mp3` | Verkehr, Sirenen entfernt |
| `stadt` | `amb_urban.mp3` | Stadt-Rauschen, Hupen |
| `schlucht` | `amb_canyon.mp3` | Echo-Wind, Wasser |
| `tal-dorf` | `amb_village.mp3` | Vogelzwitschern, Kirchglocke selten |

→ **8 Loops**. Beschaffung: entweder selbst aufnehmen (Wind im Garten, Wasser im Bad…), oder Lizenz-frei (Freesound.org, BBC Sound Effects Library — frei für Bildung).

## Reverb für ausgewählte Missionen

Mit einem `ConvolverNode` und einem kurzen Impulse-Response können wir den Funk-Klang räumlich variieren:

| Mission-Typ | Reverb-IR | Effekt |
|---|---|---|
| Schlucht (M6 Canyoning Mellau) | `ir_canyon_short.wav` (0.8 s) | Funk klingt hallend, eng |
| Berg-Hochalpin (M19, M20, M24 Glockner/Dachstein) | `ir_mountain.wav` (1.2 s) | weit, kalt |
| Tunnel/Autobahn-Tunnel | `ir_tunnel.wav` (1.5 s) | dröhnend |
| Standard (Stadt, Wasser, Wald) | kein Reverb | trocken, klar |

→ **3 Reverb-IRs**. Beschaffung: kostenlose IR-Libraries (z.B. https://www.openair.hosted.york.ac.uk/).

## Spatial Stereo (kleines Feature)

Sprecher-Position als Stereo-Pan:
- **Tower-Lotsin**: pan -0.4 (links — sitzt im Tower, „neben" dem Heli)
- **Pilot:in**: pan +0.3 (rechts — sitzt rechts vom Schüler im Cockpit)
- **Sani / Flugretter**: pan 0 (zentriert, hinten im Cockpit)
- **Briefing-Offizierin**: pan 0 (vor dem Bildschirm, direkt)

Subtil (Pan ±0.4 max), nicht aufdringlich.

## Geo-Audios — wann triggern?

Aktueller Code: Geo-Audios (`r_mission_geo_*`, `r_wp_geo_*`) sind in der Standard-Pipe → konkurrieren mit Tower-Anweisungen → 10s-Gap verzögert alles.

**Neu** (Prioritäten):
- `r_mission_geo_*` (am Kartenflug-Beginn): **MEDIUM** — spielt nur wenn keine Tower-Anweisung wartet
- `r_wp_geo_*` (pro Wegpunkt): **MEDIUM** — wird vom nächsten Tower-Funk abgebrochen
- `r_tower_*` (Wegpunkt-Anweisungen): **HIGH**
- `r_caution_*` (Notfall-Warnung): **CRITICAL**
- `r_pilot_takeover`: **CRITICAL**

Wenn der Schüler in der Plan-Phase Wegpunkte klickt und der Tower antworten muss („Korrekt, weiter über X"), bricht das die laufende Geo-Info ab. Bei richtigem Tempo bekommt er aber so viel Geo wie möglich.

## Übersicht — was wir bauen

| # | Komponente | Aufwand | Wirkung |
|---|---|---|---|
| 1 | **Prioritäten-Pipe** (4 Levels statt eines) | mittel | Kern der ganzen Logik |
| 2 | **Ducking** (Rotor + Musik bei Stimme leiser) | klein | Verständlichkeit massiv besser |
| 3 | **FILLER-Strategie** (Anti-Stille mit Pool) | mittel | Keine toten Phasen mehr |
| 4 | **Environmental-Loops** (8 Loops, 1 pro Szenario) | mittel (Beschaffung + Wiring) | Stimmung, kontinuierlicher Klangteppich |
| 5 | **Audio-Texte umstellen** auf Prioritäten-API | mittel | Skripte sortieren |
| 6 | **Reverb für 3 Mission-Typen** | klein (IR-Files + ConvolverNode) | Räumlichkeit |
| 7 | **Spatial Stereo** | klein | Goldener Schliff |

Reihenfolge bauen: **1 → 2 → 3 → 5 → 4 → 6 → 7**.

## Beziehung zu Heli-Kennzeichen-Konzept

Audio-Texte (8 JSON-Dateien, 366 Stellen mit „Christophorus") werden im Zuge von Schritt 5 auf Platzhalter `{code}` umgestellt. Audio-Regenerierung dann **pro Mission gemeinsam mit Thomas** (siehe Heli-Kennzeichen-Konzept Schritt 7).

## Audio-Re-Generierung — offene Text-Korrekturen (zu erledigen wenn Audios neu gebaut werden)

Die Audio-Skript-JSON-Dateien (`scripts/*-lines.json` + `scripts/audio-texts.json`) wurden bereits textlich korrigiert. Die zugehörigen MP3-Dateien sind aber noch alt und sagen die ursprünglichen Texte. **Beim nächsten Re-Generieren** müssen folgende Audios neu erstellt werden:

### Inhaltliche Korrekturen (2026-05-31)

1. **„Chefpilot" → „Chefpilotin"** (Marlene als weibliche Mentorin im Onboarding etabliert)
   - `r_mission_abort` + `r_mission_abort_m` (Drei Berührungen → die Chefpilotin übernimmt)
   - `r_nav_departure_takeover` (Chefpilotin hat übernommen)
   - `r_tower_handover_c1` bis `r_tower_handover_c16` (14 Tower-Audios: „Heute übernimmt die Chefpilotin")

2. **„Rotorabwind wäre Steinschlag" → „Rotorabwind kann Steinschlag verursachen"** (kausal richtiger Satz)
   - `r_caution_berg_fels` + `r_caution_berg_fels_m` (in M3/M6/M7/M12/M13/M21 etc. — alle berg-fels-Missionen)
   - `r_caution_wasser` ist unverändert sinnvoll, weil dort der Effekt deterministisch ist
   - **Status:** Text in JSON gefixt am 2026-05-31. Alte MP3 spielt noch den alten Wortlaut bis Re-Generierung.

3.5 **ASCII-Umlaut-Fixes in audio-texts.json** (2026-05-31, zweite Runde)
   - `r_nav_plan_correct`, `r_nav_plan_done`, `r_nav_seg_arrive`, `r_nav_altitude_mountain`, `r_nav_altitude_valley`, `r_nav_fuel_half`, `r_nav_route_check`, `r_nav_weather_enter`, `r_land_approach`, `r_land_rescuer_down`, `r_land_winch_down`, `r_land_rescuer_ready`, `r_tower_sea_approach_c9`
   - Vorher: "naechsten", "vollstaendig", "Ueberflughoehe", "moeglich", "Stroemung" etc. — alle MP3s sprechen noch das ASCII-Wort, JSON-Text aber UTF-8-korrekt
   - **Status:** Text in JSON gefixt am 2026-05-31. Alte MP3s warten auf Re-Generierung.

4. **Marlene als zentrale Chefpilotin überall** (Vereinheitlichung 2026-05-31, vorher 4 rotierende Profile)
   - Wirkt sich aus auf alle `pilot_*` und `caution_*`-Audios, die per `pilot_by_helikey` ausgewählt werden
   - **Alle pilot_*_m-Audios (Hannes/Stefan) werden in V1 nicht mehr abgespielt** — bleiben im System für später
   - Bilder + Funk-Pille zeigen ab sofort immer pilot_b_f (Marlene) statt der jeweiligen Stationspilot:in
   - Bei Re-Generierung: nur die weiblichen Varianten (ohne `_m`) brauchen Updates

3. **Marken-Bezeichnungen** (siehe `docs/konzept-heli-kennzeichen.md`):
   - „Christophorus N" → Pool-Kennzeichen oder generisches „R/N-N" (16 + viele weitere Stellen)
   - ggf. nochmal Audio-Workflow gemeinsam mit Thomas pro Mission

### Total beim nächsten Audio-Bau

Ca. 18 Pilot- + Tower-Audios kombiniert mit Marken-Refactor ergeben ~30–40 zu re-generierende MP3s.

---

## Status & nächste Schritte

- [ ] Schritt 1 (Prioritäten-Pipe) — warte auf Thomas-OK
- [ ] Schritt 2 (Ducking) — warte
- [ ] Schritt 3 (FILLER-Strategie) — warte
- [ ] Filler-Texte definieren (Pool von 15–20 kurzen Audios) — gemeinsame Auswahl mit Thomas
- [ ] Environmental-Loops beschaffen oder aufnehmen
- [ ] Audio-Re-Generierung mit korrigiertem Text (siehe oben)
