1e51ef7def
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/ - 12 code-workspace-Files - STATUS-*.md - viele M/D/R-Änderungen an bereits getrackten Files - .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
277 lines
14 KiB
Markdown
277 lines
14 KiB
Markdown
# 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)
|