Lied 3: Noa zeigt sofort aufs zweite Ding, wenn es gesungen wird; Vorlage: Leuchten/Karte enden 2,5 s nach der Zeile (kein Kreis im Zwischenspiel)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
704bdcd0a3
commit
490444123c
3 changed files with 32 additions and 18 deletions
|
|
@ -34,6 +34,7 @@ const FPS = 30;
|
|||
const s = (t: number) => Math.round(t * FPS);
|
||||
export const PLATZ = {pino: 760, noa: 980, lulu: 1300};
|
||||
const WEIT = {x: 1030, y: 640, z: 1.2};
|
||||
const NACHHALL = 2.5;
|
||||
|
||||
export const wortZeit = (z: Zeile, wort: number) => zerlege(z.zeile)[wort]?.t ?? z.start;
|
||||
|
||||
|
|
@ -118,7 +119,14 @@ const Garten: React.FC<{daten: LiedDaten; szene: (i: number, z: Zeile) => Bild}>
|
|||
if (z.start - 0.25 <= t) i = k;
|
||||
});
|
||||
const bilder = zeilen.map((z, k) => szene(k, z));
|
||||
const b = bilder[i];
|
||||
// Nach dem letzten Wort einer Zeile gilt ihr Bild noch NACHHALL Sekunden – in langen Zwischenspielen
|
||||
// kehrt alles zur Ausgangslage zurück (kein Leuchten ohne Text)
|
||||
const letztes = (k: number) => {
|
||||
const w = zerlege(zeilen[k].zeile);
|
||||
return w[w.length - 1].t;
|
||||
};
|
||||
const aktiv = t <= letztes(i) + NACHHALL;
|
||||
const b = aktiv ? bilder[i] : {};
|
||||
const imRefrain = zeilen[i].abschnitt.toLowerCase().startsWith('chorus') && !b.ruhig;
|
||||
// Kamera: jedes Zeilen-Bild gilt ab Zeilenbeginn, 0,7 s Fahrt dorthin
|
||||
const punkte: [number, number, number, number][] = [[0, WEIT.x, WEIT.y, WEIT.z]];
|
||||
|
|
@ -127,12 +135,17 @@ const Garten: React.FC<{daten: LiedDaten; szene: (i: number, z: Zeile) => Bild}>
|
|||
const fr = s(z.start - 0.25);
|
||||
const vorher = punkte[punkte.length - 1];
|
||||
punkte.push([Math.max(vorher[0] + 1, fr - 21), vorher[1], vorher[2], vorher[3]], [Math.max(vorher[0] + 2, fr), ziel.x, ziel.y, ziel.z]);
|
||||
const naechste = zeilen[k + 1]?.start ?? Infinity;
|
||||
if (naechste - letztes(k) > NACHHALL + 2 && ziel !== WEIT) {
|
||||
const ende = s(letztes(k) + NACHHALL);
|
||||
punkte.push([ende, ziel.x, ziel.y, ziel.z], [ende + 21, WEIT.x, WEIT.y, WEIT.z]);
|
||||
}
|
||||
});
|
||||
const kam = kamFahrt(f, punkte);
|
||||
const takt = (f / FPS) * (96 / 60) * Math.PI;
|
||||
const wipp = (phase: number, h: number) => (imRefrain ? -Math.abs(Math.sin(takt + phase)) * h : 0);
|
||||
const schwank = (phase: number, g: number) => (imRefrain ? Math.sin(takt / 2 + phase) * g : 0);
|
||||
const zeilenEnde = (k: number) => s(zeilen[k + 1]?.start ?? zeilen[k].start + 4) - 6;
|
||||
const zeilenEnde = (k: number) => Math.min(s(zeilen[k + 1]?.start ?? zeilen[k].start + 4) - 6, s(letztes(k) + NACHHALL));
|
||||
const strecken = b.luluStreckt ? interpolate(f, [s(zeilen[i].start - 0.2), s(zeilen[i].start + 0.4)], [1, 1.08], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}) : 1;
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ const NOA: Posenfolge = [
|
|||
[s(T.v4z[2]), 'noa_A5_blick-links'],
|
||||
] as Posenfolge)
|
||||
: []),
|
||||
// Mitmach-Teil: Noa zeigt erst auf das Gefragte, bei der Antwort auf das andere (Seite: siehe noaZeigtLinks)
|
||||
...KARTEN.flatMap((c) => c.zeigt.map((seite, k) => [s(k ? c.antwort - 0.2 : c.frage), seite === 'keins' ? 'noa_A3_lachen' : 'noa_B_zeigen'] as [number, string])),
|
||||
// Mitmach-Teil: Noa zeigt erst auf das Gefragte, dann auf das zweite Ding, sobald es gesungen wird (Seite: siehe noaZeigtLinks)
|
||||
...KARTEN.flatMap((c) => c.zeigt.map((seite, k) => [s(k ? c.wechsel - 0.15 : c.frage), seite === 'keins' ? 'noa_A3_lachen' : 'noa_B_zeigen'] as [number, string])),
|
||||
[s(T.gag), 'noa_A4_staunen'],
|
||||
[s(T.gag + 4.5), 'noa_A3_lachen'],
|
||||
...klatschen(T.c4, T.cz(T.c4)[3]),
|
||||
|
|
@ -249,7 +249,7 @@ const Garten: React.FC = () => {
|
|||
const pinoDuck = v4 && T.juchhe !== undefined ? interpolate(f, [s(v4[3]), s(v4[3] + 0.3), s(T.juchhe), s(T.juchhe + 0.15)], [1, 0.8, 0.8, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}) : 1;
|
||||
// Noa zeigt im Mitmach-Teil nach links (gespiegelt), wenn die Antwort bzw. Frage Pino oder die Blume meint
|
||||
const karte = KARTEN.findIndex((c, k) => f >= s(c.frage) && f < s(KARTEN[k + 1]?.frage ?? T.gag));
|
||||
const noaZeigtLinks = karte >= 0 && KARTEN[karte].zeigt[f >= s(KARTEN[karte].antwort - 0.2) ? 1 : 0] === 'links';
|
||||
const noaZeigtLinks = karte >= 0 && KARTEN[karte].zeigt[f >= s(KARTEN[karte].wechsel - 0.15) ? 1 : 0] === 'links';
|
||||
// Strophe 1: Pino pflückt die Blume – sie fliegt in seine Pfote und bleibt dort bis in Refrain 2
|
||||
const pflueck = interpolate(f, [s(T.v1z[3]), s(T.v1z[3] + 0.8)], [0, 1], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
|
||||
// … und legt sie zu Beginn von Refrain 2 neben sich ins Gras, wo sie verblasst
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ type Zeiten = {
|
|||
};
|
||||
// zeigt: wohin Noa bei Frage und Antwort zeigt (rechts = Lulu/Apfel, links = Pino/Blume, keins = sie selbst)
|
||||
type Seite = 'rechts' | 'links' | 'keins';
|
||||
type Karte = {frage: number; antwort: number; wort: string; gegenteil: string; zeigt: [Seite, Seite]};
|
||||
// wechsel: wann Noa auf die zweite Seite zeigt – sobald das zweite Ding gesungen wird („… und Pino ist …“)
|
||||
type Karte = {frage: number; antwort: number; wort: string; gegenteil: string; zeigt: [Seite, Seite]; wechsel: number};
|
||||
|
||||
const REFRAIN = (z: [number, number, number, number]) => z;
|
||||
const C_DE = {
|
||||
|
|
@ -53,10 +54,10 @@ const DAUER_DE = Math.round(139.68 * 30);
|
|||
|
||||
// Mitmach-Teil: Frage beginnt, Antwort des Chors
|
||||
const KARTEN_DE: Karte[] = [
|
||||
{frage: 74.22, antwort: 78.95, wort: 'groß', gegenteil: 'klein', zeigt: ['rechts', 'links']},
|
||||
{frage: 79.05, antwort: 83.59, wort: 'oben', gegenteil: 'unten', zeigt: ['rechts', 'links']},
|
||||
{frage: 83.74, antwort: 88.22, wort: 'laut', gegenteil: 'leise', zeigt: ['keins', 'keins']},
|
||||
{frage: 88.38, antwort: 92.9, wort: 'schnell', gegenteil: 'langsam', zeigt: ['links', 'rechts']}, // ~ Antwortzeit
|
||||
{frage: 74.22, antwort: 78.95, wort: 'groß', gegenteil: 'klein', zeigt: ['rechts', 'links'], wechsel: 76.61},
|
||||
{frage: 79.05, antwort: 83.59, wort: 'oben', gegenteil: 'unten', zeigt: ['rechts', 'links'], wechsel: 81.32},
|
||||
{frage: 83.74, antwort: 88.22, wort: 'laut', gegenteil: 'leise', zeigt: ['keins', 'keins'], wechsel: 85.78},
|
||||
{frage: 88.38, antwort: 92.9, wort: 'schnell', gegenteil: 'langsam', zeigt: ['links', 'rechts'], wechsel: 90.59}, // ~ Antwortzeit
|
||||
];
|
||||
|
||||
const refrain = (z: [number, number, number, number], d = [0, 0, 0, 0]): LiedZeile[] => [
|
||||
|
|
@ -151,10 +152,10 @@ const T_ES: Zeiten = {
|
|||
cz: (start: number) => Object.values(C_ES).find((z) => z[0] === start)!,
|
||||
};
|
||||
const KARTEN_ES: Karte[] = [
|
||||
{frage: 61.07, antwort: 64.71, wort: 'pequeño', gegenteil: 'grande', zeigt: ['links', 'rechts']},
|
||||
{frage: 65.85, antwort: 69.39, wort: 'arriba', gegenteil: 'abajo', zeigt: ['rechts', 'links']},
|
||||
{frage: 70.73, antwort: 73.79, wort: 'fuerte', gegenteil: 'bajito', zeigt: ['keins', 'links']},
|
||||
{frage: 75.19, antwort: 78.59, wort: 'rápido', gegenteil: 'despacio', zeigt: ['links', 'rechts']},
|
||||
{frage: 61.07, antwort: 64.71, wort: 'pequeño', gegenteil: 'grande', zeigt: ['links', 'rechts'], wechsel: 63.41},
|
||||
{frage: 65.85, antwort: 69.39, wort: 'arriba', gegenteil: 'abajo', zeigt: ['rechts', 'links'], wechsel: 68.05},
|
||||
{frage: 70.73, antwort: 73.79, wort: 'fuerte', gegenteil: 'bajito', zeigt: ['keins', 'links'], wechsel: 72.53},
|
||||
{frage: 75.19, antwort: 78.59, wort: 'rápido', gegenteil: 'despacio', zeigt: ['links', 'rechts'], wechsel: 77.41},
|
||||
];
|
||||
const ZEILEN_ES: LiedZeile[] = [
|
||||
'Uno@5.03 pequeño,@6.25 una@6.99 grande,@7.13',
|
||||
|
|
@ -222,10 +223,10 @@ const T_FR: Zeiten = {
|
|||
cz: (start: number) => Object.values(C_FR).find((z) => z[0] === start)!,
|
||||
};
|
||||
const KARTEN_FR: Karte[] = [
|
||||
{frage: 66.29, antwort: 69.47, wort: 'petit', gegenteil: 'grande', zeigt: ['links', 'rechts']},
|
||||
{frage: 71.01, antwort: 74.53, wort: 'en haut', gegenteil: 'en bas', zeigt: ['rechts', 'links']},
|
||||
{frage: 76.01, antwort: 78.43, wort: 'fort', gegenteil: 'doucement', zeigt: ['keins', 'links']},
|
||||
{frage: 80.13, antwort: 82.69, wort: 'vite', gegenteil: 'lentement', zeigt: ['links', 'rechts']},
|
||||
{frage: 66.29, antwort: 69.47, wort: 'petit', gegenteil: 'grande', zeigt: ['links', 'rechts'], wechsel: 68.77},
|
||||
{frage: 71.01, antwort: 74.53, wort: 'en haut', gegenteil: 'en bas', zeigt: ['rechts', 'links'], wechsel: 73.59},
|
||||
{frage: 76.01, antwort: 78.43, wort: 'fort', gegenteil: 'doucement', zeigt: ['keins', 'links'], wechsel: 77.85},
|
||||
{frage: 80.13, antwort: 82.69, wort: 'vite', gegenteil: 'lentement', zeigt: ['links', 'rechts'], wechsel: 82.05},
|
||||
];
|
||||
const ZEILEN_FR: LiedZeile[] = [
|
||||
'Un@11.33 tout@11.95 petit,@12.21 un@12.89 très@12.93 grand,@13.09',
|
||||
|
|
|
|||
Loading…
Reference in a new issue