Folge 1: Geräusche synchron, Lied in DE/ES/FR gesungen (Suno)
- Geräusche (baukasten/geraeusche, werkzeuge/geraeusche.mts): Stille am Anfang abgeschnitten, F01-Töne auf Bildereignissen und leiser; Ton.laenge blendet aus - Szene 6: Blume und Noas Arme gehen früher runter - Lied: je Sprache eigenes Suno-Lied (SUNO_LIEDER in lied.tsx), ES/FR-Zeiten per faster-whisper gemessen (+0,45 s, am DE-Lied geeicht); Ton in public/musik/lieder (nicht im Git) - F02 DE vertont Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0d19174c4a
commit
0e34d998a5
9 changed files with 692 additions and 41 deletions
1
remotion/.gitignore
vendored
1
remotion/.gitignore
vendored
|
|
@ -3,3 +3,4 @@ out/
|
||||||
public/bibliothek/clips
|
public/bibliothek/clips
|
||||||
public/stimmen
|
public/stimmen
|
||||||
public/musik
|
public/musik
|
||||||
|
public/geraeusche
|
||||||
|
|
|
||||||
22
remotion/src/baukasten/geraeusche.json
Normal file
22
remotion/src/baukasten/geraeusche.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"garten_tag": {"sekunden": 20, "loop": true, "beschreibung": "Calm autumn garden ambience in daytime: soft breeze, occasional dry leaves rustling, a few distant small birds chirping. Gentle and peaceful. No voices, no music."},
|
||||||
|
"garten_abend": {"sekunden": 20, "loop": true, "beschreibung": "Calm autumn garden at sunset: a blackbird singing softly in the distance, light breeze, very peaceful. No voices, no music."},
|
||||||
|
"garten_nacht": {"sekunden": 20, "loop": true, "beschreibung": "Peaceful autumn night in a garden: very soft wind, one distant owl hoot now and then, quiet and cozy. No voices, no music."},
|
||||||
|
"zimmer": {"sekunden": 15, "loop": true, "beschreibung": "Quiet cozy children's room ambience: faint warm room tone, soft ticking of a clock far away. No voices, no music."},
|
||||||
|
"buch_auf": {"sekunden": 2, "beschreibung": "A big picture book being opened gently, soft paper page flip, warm and cozy."},
|
||||||
|
"buch_zu": {"sekunden": 1.5, "beschreibung": "A big picture book being closed gently, soft soft thump of paper and cardboard."},
|
||||||
|
"laub_sprung": {"sekunden": 2, "beschreibung": "A child jumping into a big pile of dry autumn leaves, loud happy crunchy rustle and whoosh."},
|
||||||
|
"rascheln": {"sekunden": 2.5, "beschreibung": "A small animal rustling and moving inside a pile of dry autumn leaves, soft and close."},
|
||||||
|
"hopser": {"sekunden": 1.5, "beschreibung": "Two tiny soft cartoon hops of a small hedgehog, light bouncy boing, cute."},
|
||||||
|
"giraffe_schritte": {"sekunden": 3, "beschreibung": "Slow, big but gentle footsteps of a large animal walking on soft grass, friendly cartoon feel."},
|
||||||
|
"apfel": {"sekunden": 1, "beschreibung": "Crisp snap of an apple stem being picked from a branch, small and clear."},
|
||||||
|
"strecken": {"sekunden": 2, "beschreibung": "Funny cartoon stretching sound, a long neck bending down slowly, soft comic creak, gentle."},
|
||||||
|
"schnuppern": {"sekunden": 1.5, "beschreibung": "Gentle cartoon sniffing of a flower, two soft sniffs, cute."},
|
||||||
|
"schnarchen": {"sekunden": 3, "beschreibung": "One tiny, cute, soft cartoon snore of a small sleepy hedgehog, with a gentle little whistle on the out-breath."},
|
||||||
|
"blaetter_sammeln": {"sekunden": 1.5, "beschreibung": "Soft crunchy rustle of a handful of dry autumn leaves being picked up and dropped on a pile."},
|
||||||
|
"blaetter_regen": {"sekunden": 3, "beschreibung": "A gentle shower of dry autumn leaves falling from a shaken tree branch, soft whoosh and rustling."},
|
||||||
|
"zugvoegel": {"sekunden": 5, "beschreibung": "A flock of wild geese honking high in the sky, flying away into the distance, gentle and far."},
|
||||||
|
"malen": {"sekunden": 4, "beschreibung": "Crayon scribbling and drawing on paper on a wooden table, close and soft."},
|
||||||
|
"kribbeln": {"sekunden": 2, "beschreibung": "Cute magical sparkle twinkle, soft glockenspiel shimmer, cartoon, happy."},
|
||||||
|
"schlaff": {"sekunden": 2, "beschreibung": "Soft short descending cartoon slide whistle, a little sad but cute and gentle."}
|
||||||
|
}
|
||||||
71
remotion/src/baukasten/geraeusche.tsx
Normal file
71
remotion/src/baukasten/geraeusche.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Audio, Sequence, getInputProps, getStaticFiles, interpolate, staticFile, useVideoConfig} from 'remotion';
|
||||||
|
import type {Geplant} from './zeit';
|
||||||
|
|
||||||
|
// Geräusche und Hintergrundklang (Atmo) je Szene.
|
||||||
|
// Die Klänge stehen in src/baukasten/geraeusche.json und werden mit
|
||||||
|
// npx tsx werkzeuge/geraeusche.mts
|
||||||
|
// nach public/geraeusche/<name>.mp3 erzeugt (ElevenLabs Sound Effects, für alle Folgen).
|
||||||
|
// Ein Ton hängt an einer Zeile der Szene (`bei`, auch reine Bildzeilen wie 'buch')
|
||||||
|
// plus `versatz` in Sekunden. In FolgeNN.tsx: K: mitKlang(Szene2, S2, {atmo: 'garten_tag', toene: [...]})
|
||||||
|
// Abschalten beim Rendern: --props='{"geraeusche": false}'
|
||||||
|
|
||||||
|
const VORHANDEN = new Set(getStaticFiles().map((d) => d.name));
|
||||||
|
const pfad = (name: string) => `geraeusche/${name}.mp3`;
|
||||||
|
|
||||||
|
// laenge: Ton nach so vielen Sekunden weich ausblenden (sonst läuft die Datei ganz durch)
|
||||||
|
export type Ton = {name: string; bei: string; versatz?: number; lautstaerke?: number; laenge?: number};
|
||||||
|
export type Klang = {atmo?: string; atmoLaut?: number; toene?: Ton[]};
|
||||||
|
type Plan = {dauer: number; liste: Geplant[]};
|
||||||
|
|
||||||
|
export const Geraeusche: React.FC<Klang & {plan: Plan}> = ({atmo, atmoLaut = 0.12, toene = [], plan}) => {
|
||||||
|
const {fps} = useVideoConfig();
|
||||||
|
if (getInputProps().geraeusche === false) return null;
|
||||||
|
const {dauer, liste} = plan;
|
||||||
|
const atmoDa = atmo !== undefined && VORHANDEN.has(pfad(atmo));
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{atmoDa && (
|
||||||
|
<Audio
|
||||||
|
src={staticFile(pfad(atmo!))}
|
||||||
|
loop
|
||||||
|
volume={(f) =>
|
||||||
|
atmoLaut *
|
||||||
|
Math.min(
|
||||||
|
interpolate(f, [0, fps], [0, 1], {extrapolateRight: 'clamp'}),
|
||||||
|
interpolate(f, [dauer - fps * 1.5, dauer], [1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{toene.map((t, i) => {
|
||||||
|
const zeile = liste.find((l) => l.id === t.bei);
|
||||||
|
if (!zeile || !VORHANDEN.has(pfad(t.name))) return null;
|
||||||
|
const von = Math.max(0, Math.round(zeile.start + (t.versatz ?? 0) * fps));
|
||||||
|
if (von >= dauer) return null;
|
||||||
|
const laut = t.lautstaerke ?? 0.4;
|
||||||
|
const bis = t.laenge ? Math.min(dauer - von, Math.round(t.laenge * fps)) : dauer - von;
|
||||||
|
const aus = Math.min(10, bis);
|
||||||
|
return (
|
||||||
|
<Sequence key={`${t.name}-${i}`} from={von} durationInFrames={bis} layout="none">
|
||||||
|
<Audio
|
||||||
|
src={staticFile(pfad(t.name))}
|
||||||
|
volume={(f) => (t.laenge ? laut * interpolate(f, [bis - aus, bis], [1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}) : laut)}
|
||||||
|
/>
|
||||||
|
</Sequence>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Szene + Geräusche zusammen (baukasten/folge.tsx bleibt unverändert)
|
||||||
|
export const mitKlang = (K: React.FC, plan: Plan, klang: Klang): React.FC => {
|
||||||
|
const M: React.FC = () => (
|
||||||
|
<>
|
||||||
|
<K />
|
||||||
|
<Geraeusche {...klang} plan={plan} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return M;
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {INTRO_OPTIONEN, Intro, OUTRO, Outro, introZeilen} from '../baukasten/buch';
|
import {INTRO_OPTIONEN, Intro, OUTRO, Outro, introZeilen} from '../baukasten/buch';
|
||||||
import {baueFolge, type SzenenEintrag} from '../baukasten/folge';
|
import {baueFolge, type SzenenEintrag} from '../baukasten/folge';
|
||||||
import {LIED, Lied} from './lied';
|
import {mitKlang} from '../baukasten/geraeusche';
|
||||||
|
import {GESUNGEN, LIED, LIED_AB, LIED_DATEI, Lied} from './lied';
|
||||||
import {TITEL, plane} from './plan';
|
import {TITEL, plane} from './plan';
|
||||||
import {S2, Szene2} from './szene2';
|
import {S2, Szene2} from './szene2';
|
||||||
import {S3, Szene3} from './szene3';
|
import {S3, Szene3} from './szene3';
|
||||||
|
|
@ -11,19 +12,95 @@ import {S6, Szene6} from './szene6';
|
||||||
import {S7, Szene7} from './szene7';
|
import {S7, Szene7} from './szene7';
|
||||||
|
|
||||||
// Folge 1 „Zu klein, zu groß“ – wie Folge 2 per Code aus der Figuren-Bibliothek.
|
// Folge 1 „Zu klein, zu groß“ – wie Folge 2 per Code aus der Figuren-Bibliothek.
|
||||||
|
// Musik aus public/musik/folge01, Geräusche und Atmo über baukasten/geraeusche.tsx.
|
||||||
|
|
||||||
const INTRO = plane('intro', introZeilen(TITEL), INTRO_OPTIONEN);
|
const INTRO = plane('intro', introZeilen(TITEL), INTRO_OPTIONEN);
|
||||||
|
|
||||||
export const SZENEN: SzenenEintrag[] = [
|
export const SZENEN: SzenenEintrag[] = [
|
||||||
{name: 'Intro', datei: 'Folge01.tsx', plan: INTRO, K: () => <Intro plan={INTRO} titel={TITEL} posen={['pino_A3_lachen', 'noa_A3_lachen', 'lulu_A3_lachen']} />, musik: {datei: 'musik/folge01/intro.mp3', lautstaerke: 0.35, rein: 0.2, raus: 1.2}},
|
{
|
||||||
{name: 'Wer raschelt da', datei: 'szene2.tsx', plan: S2, K: Szene2},
|
name: 'Intro',
|
||||||
{name: 'Der Apfel ist zu hoch', datei: 'szene3.tsx', plan: S3, K: Szene3},
|
datei: 'Folge01.tsx',
|
||||||
{name: 'Ein Baum mit Flecken', datei: 'szene4.tsx', plan: S4, K: Szene4},
|
plan: INTRO,
|
||||||
{name: 'Die Blume ist zu tief', datei: 'szene5.tsx', plan: S5, K: Szene5},
|
K: mitKlang(() => <Intro plan={INTRO} titel={TITEL} posen={['pino_A3_lachen', 'noa_A3_lachen', 'lulu_A3_lachen']} />, INTRO, {
|
||||||
{name: 'Zusammen gehts', datei: 'szene6.tsx', plan: S6, K: Szene6},
|
toene: [{name: 'buch_auf', bei: 'buch', versatz: 0.7, lautstaerke: 0.35}],
|
||||||
{name: 'Lied', datei: 'lied.tsx', plan: LIED, K: Lied, musik: {datei: 'musik/folge01/lied.mp3', lautstaerke: 0.4, ducken: 0.75, rein: 0.3, raus: 1.2}},
|
}),
|
||||||
{name: 'Pino ist muede', datei: 'szene7.tsx', plan: S7, K: Szene7, musik: {datei: 'musik/folge01/schlaflied.mp3', lautstaerke: 0.3, rein: 3, raus: 0.1}},
|
musik: {datei: 'musik/folge01/intro.mp3', lautstaerke: 0.35, rein: 0.2, raus: 1.2},
|
||||||
{name: 'Outro', datei: 'Folge01.tsx', plan: OUTRO, K: () => <Outro posen={['pino_B3_schlafen', 'noa_A_arm-hoch-winken', 'lulu_A3_lachen']} />, musik: {datei: 'musik/folge01/schlaflied.mp3', lautstaerke: 0.3, ab: S7.dauer / 30, rein: 0.1, raus: 2}},
|
},
|
||||||
|
{
|
||||||
|
name: 'Wer raschelt da',
|
||||||
|
datei: 'szene2.tsx',
|
||||||
|
plan: S2,
|
||||||
|
K: mitKlang(Szene2, S2, {
|
||||||
|
atmo: 'garten_tag',
|
||||||
|
toene: [
|
||||||
|
// Landung im Haufen: sprung = n1 + 4 Bilder, landung = sprung + 22 (szene2.tsx)
|
||||||
|
{name: 'laub_sprung', bei: 'n1', versatz: 0.75, lautstaerke: 0.2, laenge: 1.1},
|
||||||
|
// Haufen wackelt ab bild2, bis Pino auftaucht (ca. 1,5 s)
|
||||||
|
{name: 'rascheln', bei: 'bild2', versatz: 0.1, lautstaerke: 0.15, laenge: 1.5},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Der Apfel ist zu hoch',
|
||||||
|
datei: 'szene3.tsx',
|
||||||
|
plan: S3,
|
||||||
|
K: mitKlang(Szene3, S3, {atmo: 'garten_tag', toene: [
|
||||||
|
// zwei Hüpfer: p2 + 2 und p2 + 32 Bilder (szene3.tsx)
|
||||||
|
{name: 'hopser', bei: 'p2', versatz: 0.07, lautstaerke: 0.25, laenge: 0.45},
|
||||||
|
{name: 'hopser', bei: 'p2', versatz: 1.07, lautstaerke: 0.25, laenge: 0.45},
|
||||||
|
]}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ein Baum mit Flecken',
|
||||||
|
datei: 'szene4.tsx',
|
||||||
|
plan: S4,
|
||||||
|
K: mitKlang(Szene4, S4, {
|
||||||
|
atmo: 'garten_tag',
|
||||||
|
toene: [
|
||||||
|
// Lulu stakst ab Szenenbeginn herein, bis bild0-Ende + 10 Bilder
|
||||||
|
{name: 'giraffe_schritte', bei: 'bild0', versatz: 0.4, lautstaerke: 0.25, laenge: 3.2},
|
||||||
|
// Lulu pflückt den Apfel bei bild2 + 10 Bilder
|
||||||
|
{name: 'apfel', bei: 'bild2', versatz: 0.4, lautstaerke: 0.3},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Die Blume ist zu tief',
|
||||||
|
datei: 'szene5.tsx',
|
||||||
|
plan: S5,
|
||||||
|
K: mitKlang(Szene5, S5, {atmo: 'garten_tag', toene: [{name: 'strecken', bei: 'bild1', versatz: 0, lautstaerke: 0.2, laenge: 1.8}]}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Zusammen gehts',
|
||||||
|
datei: 'szene6.tsx',
|
||||||
|
plan: S6,
|
||||||
|
K: mitKlang(Szene6, S6, {atmo: 'garten_tag', toene: [{name: 'schnuppern', bei: 'l1', versatz: 0.1, lautstaerke: 0.3}]}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Lied',
|
||||||
|
datei: 'lied.tsx',
|
||||||
|
plan: LIED,
|
||||||
|
K: Lied,
|
||||||
|
musik: GESUNGEN
|
||||||
|
? {datei: LIED_DATEI, ab: LIED_AB, lautstaerke: 0.9, rein: 0.2, raus: 1.2}
|
||||||
|
: {datei: 'musik/folge01/lied.mp3', lautstaerke: 0.4, ducken: 0.75, rein: 0.3, raus: 1.2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Pino ist muede',
|
||||||
|
datei: 'szene7.tsx',
|
||||||
|
plan: S7,
|
||||||
|
K: mitKlang(Szene7, S7, {atmo: 'garten_abend', atmoLaut: 0.1}),
|
||||||
|
musik: {datei: 'musik/folge01/schlaflied.mp3', lautstaerke: 0.3, rein: 3, raus: 0.1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Outro',
|
||||||
|
datei: 'Folge01.tsx',
|
||||||
|
plan: OUTRO,
|
||||||
|
K: mitKlang(() => <Outro posen={['pino_B3_schlafen', 'noa_A_arm-hoch-winken', 'lulu_A3_lachen']} />, OUTRO, {
|
||||||
|
toene: [{name: 'buch_zu', bei: 'buch', versatz: 3.1, lautstaerke: 0.35}],
|
||||||
|
}),
|
||||||
|
musik: {datei: 'musik/folge01/schlaflied.mp3', lautstaerke: 0.3, ab: S7.dauer / 30, rein: 0.1, raus: 2},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const F01 = baueFolge('F01', SZENEN);
|
export const F01 = baueFolge('F01', SZENEN);
|
||||||
|
|
|
||||||
|
|
@ -5,32 +5,90 @@ import {Blende, Vignette, klatschFolge} from '../baukasten/helfer';
|
||||||
import {Blume, Laubhaufen} from '../baukasten/natur';
|
import {Blume, Laubhaufen} from '../baukasten/natur';
|
||||||
import {BODEN, Ort} from '../baukasten/orte';
|
import {BODEN, Ort} from '../baukasten/orte';
|
||||||
import {SCHRIFT, Untertitel} from '../baukasten/untertitel';
|
import {SCHRIFT, Untertitel} from '../baukasten/untertitel';
|
||||||
|
import {SPRACHE} from '../baukasten/sprache';
|
||||||
|
import {plane as planeOhneStimme} from '../baukasten/zeit';
|
||||||
import {TEXTE, plane} from './plan';
|
import {TEXTE, plane} from './plan';
|
||||||
import {HAUFEN_X} from './szene2';
|
import {HAUFEN_X} from './szene2';
|
||||||
|
|
||||||
// Lied „Einer klein und einer groß“: Refrain (alle), Strophe (Erzählstimme), Refrain.
|
// Lied „Einer klein und einer groß“: Refrain (alle), Strophe (Erzählstimme), Refrain.
|
||||||
// Die drei stehen nebeneinander – klein, mittel, groß – und wippen im Takt.
|
// Die drei stehen nebeneinander – klein, mittel, groß – und wippen im Takt.
|
||||||
|
|
||||||
const TAKT = 19; // Frames pro Schlag (ca. 95 bpm)
|
// Gesungen von Suno, gekürzt auf Refrain – Strophe 1 – Refrain (die ersten 12 Liedzeilen).
|
||||||
|
// Die Zeilen liegen genau auf den gemessenen Suno-Zeiten, ohne Sprachaufnahmen.
|
||||||
|
// Fehlt für eine Sprache der Eintrag, wird sie gesprochen über das Instrumental (95 BPM).
|
||||||
|
type SunoLied = {
|
||||||
|
datei: string; // in public/, auf −14 LUFS
|
||||||
|
bpm: number;
|
||||||
|
zeiten: number[]; // Beginn der Zeilen r1–r4, s1–s4, w1–w4 in Sekunden
|
||||||
|
ende: number; // letzte Zeile ausgesungen
|
||||||
|
vor: number; // Sekunden Vorspiel vor der ersten Zeile
|
||||||
|
};
|
||||||
|
const SUNO_LIEDER: Partial<Record<typeof SPRACHE, SunoLied>> = {
|
||||||
|
// „Noa in der Mitte“ 25385c27, Zeiten aus aligned_lyrics (src/lieder/lied03/zeiten.ts)
|
||||||
|
de: {
|
||||||
|
datei: 'musik/lieder/lied03-de.mp3',
|
||||||
|
bpm: 103,
|
||||||
|
zeiten: [9.43, 11.53, 13.84, 16.03, 18.41, 20.84, 23.04, 25.34, 27.59, 30.01, 32.33, 34.49],
|
||||||
|
ende: 36.6,
|
||||||
|
vor: 1.5,
|
||||||
|
},
|
||||||
|
// ES/FR: Zeiten am 04.11.2026 lokal mit faster-whisper (Modell small, Wortzeiten) gemessen und um
|
||||||
|
// +0,45 s verschoben – so weit lag Whisper beim deutschen Lied im Mittel vor den Suno-Zeiten.
|
||||||
|
// Genauigkeit etwa ±0,3 s; beim Ansehen prüfen.
|
||||||
|
// „Uno pequeño, una grande (ES Favorit-Stil v2)“ 0821f657
|
||||||
|
es: {
|
||||||
|
datei: 'musik/lieder/lied03-es.mp3',
|
||||||
|
bpm: 104,
|
||||||
|
zeiten: [5.03, 7.51, 9.47, 12.29, 14.93, 17.51, 19.77, 21.83, 24.85, 27.03, 28.97, 31.85],
|
||||||
|
ende: 33.3,
|
||||||
|
vor: 1.5,
|
||||||
|
},
|
||||||
|
// „Un tout petit, un très grand (FR Favorit-Stil)“ 52b8359a
|
||||||
|
fr: {
|
||||||
|
datei: 'musik/lieder/lied03-fr.mp3',
|
||||||
|
bpm: 104,
|
||||||
|
zeiten: [11.35, 13.79, 16.33, 18.61, 20.39, 22.65, 24.89, 27.19, 29.51, 31.95, 34.21, 36.61],
|
||||||
|
ende: 38.6,
|
||||||
|
vor: 1.5,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const SUNO_LIED = SUNO_LIEDER[SPRACHE];
|
||||||
|
export const GESUNGEN = !!SUNO_LIED;
|
||||||
|
const TAKT = SUNO_LIED ? (30 * 60) / SUNO_LIED.bpm : 19; // Frames pro Schlag
|
||||||
|
|
||||||
export const LIED = plane(
|
const IDS = ['r1', 'r2', 'r3', 'r4', 's1', 's2', 's3', 's4', 'w1', 'w2', 'w3', 'w4'];
|
||||||
'lied',
|
export const LIED_DATEI = SUNO_LIED?.datei ?? '';
|
||||||
[
|
export const LIED_AB = SUNO_LIED ? SUNO_LIED.zeiten[0] - SUNO_LIED.vor : 0; // ab hier läuft die Suno-Datei
|
||||||
{id: 'r1', wer: 'ALLE', dauer: 3.2},
|
|
||||||
{id: 'r2', wer: 'ALLE', dauer: 3.2},
|
export const LIED = SUNO_LIED
|
||||||
{id: 'r3', wer: 'ALLE', dauer: 3.2},
|
? planeOhneStimme(
|
||||||
{id: 'r4', wer: 'ALLE', dauer: 3.6},
|
IDS.map((id, i) => ({
|
||||||
{id: 's1', wer: 'ERZ', dauer: 3.2},
|
id,
|
||||||
{id: 's2', wer: 'ERZ', dauer: 3.2},
|
wer: 'ALLE' as const,
|
||||||
{id: 's3', wer: 'ERZ', dauer: 3.2},
|
text: TEXTE.zeilen[`lied/${id}`],
|
||||||
{id: 's4', wer: 'ERZ', dauer: 3.6},
|
// Lücke von 0,35 s zwischen den Zeilen wird vom Planer eingefügt
|
||||||
{id: 'w1', wer: 'ALLE', dauer: 3.2},
|
dauer: (i + 1 < IDS.length ? SUNO_LIED.zeiten[i + 1] : SUNO_LIED.ende + 0.35) - SUNO_LIED.zeiten[i] - 0.35,
|
||||||
{id: 'w2', wer: 'ALLE', dauer: 3.2},
|
})),
|
||||||
{id: 'w3', wer: 'ALLE', dauer: 3.2},
|
{vorlauf: SUNO_LIED.vor, nachlauf: 1.2},
|
||||||
{id: 'w4', wer: 'ALLE', dauer: 3.6},
|
)
|
||||||
],
|
: plane(
|
||||||
{vorlauf: 1.0, nachlauf: 1.0},
|
'lied',
|
||||||
);
|
[
|
||||||
|
{id: 'r1', wer: 'ALLE', dauer: 3.2},
|
||||||
|
{id: 'r2', wer: 'ALLE', dauer: 3.2},
|
||||||
|
{id: 'r3', wer: 'ALLE', dauer: 3.2},
|
||||||
|
{id: 'r4', wer: 'ALLE', dauer: 3.6},
|
||||||
|
{id: 's1', wer: 'ERZ', dauer: 3.2},
|
||||||
|
{id: 's2', wer: 'ERZ', dauer: 3.2},
|
||||||
|
{id: 's3', wer: 'ERZ', dauer: 3.2},
|
||||||
|
{id: 's4', wer: 'ERZ', dauer: 3.6},
|
||||||
|
{id: 'w1', wer: 'ALLE', dauer: 3.2},
|
||||||
|
{id: 'w2', wer: 'ALLE', dauer: 3.2},
|
||||||
|
{id: 'w3', wer: 'ALLE', dauer: 3.2},
|
||||||
|
{id: 'w4', wer: 'ALLE', dauer: 3.6},
|
||||||
|
],
|
||||||
|
{vorlauf: 1.0, nachlauf: 1.0},
|
||||||
|
);
|
||||||
|
|
||||||
const Note: React.FC<{x: number; y: number; s: number; farbe: string}> = ({x, y, s, farbe}) => (
|
const Note: React.FC<{x: number; y: number; s: number; farbe: string}> = ({x, y, s, farbe}) => (
|
||||||
<svg width={60} height={80} viewBox="0 0 60 80" style={{position: 'absolute', left: x, top: y, transform: `scale(${s})`}}>
|
<svg width={60} height={80} viewBox="0 0 60 80" style={{position: 'absolute', left: x, top: y, transform: `scale(${s})`}}>
|
||||||
|
|
|
||||||
|
|
@ -104,9 +104,10 @@ export const Szene6: React.FC = () => {
|
||||||
[0, 'noa_A3_lachen'],
|
[0, 'noa_A3_lachen'],
|
||||||
[hocke, 'noa_B_hocken'],
|
[hocke, 'noa_B_hocken'],
|
||||||
[aufstehen, 'noa_A_arme-hoch'],
|
[aufstehen, 'noa_A_arme-hoch'],
|
||||||
[z.e3.start, 'noa_A3_lachen'],
|
[z.l1.start - 20, 'noa_A3_lachen'], // Blume ist jetzt bei Lulu (Nahaufnahme), Arme runter
|
||||||
];
|
];
|
||||||
const blumeImBild = f < z.e3.start;
|
// nach der Nahaufnahme hält Noa die Blume nicht mehr hoch
|
||||||
|
const blumeImBild = f < z.l1.start - 20;
|
||||||
|
|
||||||
// Nahaufnahme: Lulu riecht an der Blume (die Blume kommt von unten ins Bild)
|
// Nahaufnahme: Lulu riecht an der Blume (die Blume kommt von unten ins Bild)
|
||||||
const nahA = z.l1.start - 20;
|
const nahA = z.l1.start - 20;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {INTRO_OPTIONEN, Intro, OUTRO, Outro, introZeilen} from '../baukasten/buch';
|
import {INTRO_OPTIONEN, Intro, OUTRO, Outro, introZeilen} from '../baukasten/buch';
|
||||||
import {baueFolge, type SzenenEintrag} from '../baukasten/folge';
|
import {baueFolge, type SzenenEintrag} from '../baukasten/folge';
|
||||||
|
import {mitKlang} from '../baukasten/geraeusche';
|
||||||
import {LIED, Lied} from './lied';
|
import {LIED, Lied} from './lied';
|
||||||
import {TITEL, plane} from './plan';
|
import {TITEL, plane} from './plan';
|
||||||
import {S2, Szene2} from './szene2';
|
import {S2, Szene2} from './szene2';
|
||||||
|
|
@ -11,19 +12,76 @@ import {S6, Szene6} from './szene6';
|
||||||
import {S7, Szene7} from './szene7';
|
import {S7, Szene7} from './szene7';
|
||||||
|
|
||||||
// Folge 2 „Pino ist müde“ – komplett per Code aus der Figuren-Bibliothek.
|
// Folge 2 „Pino ist müde“ – komplett per Code aus der Figuren-Bibliothek.
|
||||||
|
// Musik wie Folge 1 (gleiche Stücke aus public/musik/folge01), dazu Geräusche
|
||||||
|
// und Atmo (baukasten/geraeusche.tsx).
|
||||||
|
|
||||||
const INTRO = plane('intro', introZeilen(TITEL), INTRO_OPTIONEN);
|
const INTRO = plane('intro', introZeilen(TITEL), INTRO_OPTIONEN);
|
||||||
|
|
||||||
|
const sammeln = ['c1', 'c2', 'c3', 'c4', 'c5'].map((bei) => ({name: 'blaetter_sammeln', bei, versatz: -0.2, lautstaerke: 0.45}));
|
||||||
|
|
||||||
export const SZENEN: SzenenEintrag[] = [
|
export const SZENEN: SzenenEintrag[] = [
|
||||||
{name: 'Intro', datei: 'Folge02.tsx', plan: INTRO, K: () => <Intro plan={INTRO} titel={TITEL} posen={['pino_A2_augen-halb', 'noa_A3_lachen', 'lulu_A3_lachen']} />},
|
{
|
||||||
{name: 'Gähnen überall', datei: 'szene2.tsx', plan: S2, K: Szene2},
|
name: 'Intro',
|
||||||
{name: 'Lulu sieht es von oben', datei: 'szene3.tsx', plan: S3, K: Szene3},
|
datei: 'Folge02.tsx',
|
||||||
{name: 'Ein Bett aus Blättern', datei: 'szene4.tsx', plan: S4, K: Szene4},
|
plan: INTRO,
|
||||||
{name: 'Lied', datei: 'lied.tsx', plan: LIED, K: Lied},
|
K: mitKlang(() => <Intro plan={INTRO} titel={TITEL} posen={['pino_A2_augen-halb', 'noa_A3_lachen', 'lulu_A3_lachen']} />, INTRO, {
|
||||||
{name: 'Pino will nicht schlafen', datei: 'szene5.tsx', plan: S5, K: Szene5},
|
toene: [{name: 'buch_auf', bei: 'buch', versatz: 0, lautstaerke: 0.5}],
|
||||||
{name: 'Noas Idee', datei: 'szene6.tsx', plan: S6, K: Szene6},
|
}),
|
||||||
{name: 'Gute Nacht, Pino', datei: 'szene7.tsx', plan: S7, K: Szene7},
|
musik: {datei: 'musik/folge01/intro.mp3', lautstaerke: 0.35, rein: 0.2, raus: 1.2},
|
||||||
{name: 'Outro', datei: 'Folge02.tsx', plan: OUTRO, K: () => <Outro />},
|
},
|
||||||
|
{
|
||||||
|
name: 'Gähnen überall',
|
||||||
|
datei: 'szene2.tsx',
|
||||||
|
plan: S2,
|
||||||
|
K: mitKlang(Szene2, S2, {atmo: 'garten_tag', toene: [{name: 'schnarchen', bei: 'n2', versatz: -1.6, lautstaerke: 0.5}]}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Lulu sieht es von oben',
|
||||||
|
datei: 'szene3.tsx',
|
||||||
|
plan: S3,
|
||||||
|
K: mitKlang(Szene3, S3, {
|
||||||
|
atmo: 'garten_tag',
|
||||||
|
toene: [
|
||||||
|
{name: 'zugvoegel', bei: 'l2', versatz: 0.8, lautstaerke: 0.3},
|
||||||
|
{name: 'schlaff', bei: 'e4', versatz: 0.3, lautstaerke: 0.4},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ein Bett aus Blättern',
|
||||||
|
datei: 'szene4.tsx',
|
||||||
|
plan: S4,
|
||||||
|
K: mitKlang(Szene4, S4, {atmo: 'garten_tag', toene: [...sammeln, {name: 'blaetter_regen', bei: 'l2', versatz: -1.2, lautstaerke: 0.5}]}),
|
||||||
|
},
|
||||||
|
{name: 'Lied', datei: 'lied.tsx', plan: LIED, K: Lied, musik: {datei: 'musik/folge01/lied.mp3', lautstaerke: 0.4, ducken: 0.75, rein: 0.3, raus: 1.2}},
|
||||||
|
{name: 'Pino will nicht schlafen', datei: 'szene5.tsx', plan: S5, K: mitKlang(Szene5, S5, {atmo: 'garten_tag'})},
|
||||||
|
{
|
||||||
|
name: 'Noas Idee',
|
||||||
|
datei: 'szene6.tsx',
|
||||||
|
plan: S6,
|
||||||
|
K: mitKlang(Szene6, S6, {
|
||||||
|
atmo: 'zimmer',
|
||||||
|
atmoLaut: 0.1,
|
||||||
|
toene: [
|
||||||
|
{name: 'malen', bei: 'l1', versatz: -0.5, lautstaerke: 0.35},
|
||||||
|
{name: 'kribbeln', bei: 'p2', versatz: 0, lautstaerke: 0.45},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Gute Nacht, Pino',
|
||||||
|
datei: 'szene7.tsx',
|
||||||
|
plan: S7,
|
||||||
|
K: mitKlang(Szene7, S7, {atmo: 'garten_nacht', atmoLaut: 0.1}),
|
||||||
|
musik: {datei: 'musik/folge01/schlaflied.mp3', lautstaerke: 0.3, rein: 3, raus: 0.1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Outro',
|
||||||
|
datei: 'Folge02.tsx',
|
||||||
|
plan: OUTRO,
|
||||||
|
K: mitKlang(() => <Outro />, OUTRO, {toene: [{name: 'buch_zu', bei: 'buch', versatz: 3.2, lautstaerke: 0.45}]}),
|
||||||
|
musik: {datei: 'musik/folge01/schlaflied.mp3', lautstaerke: 0.3, ab: S7.dauer / 30, rein: 0.1, raus: 2},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const F02 = baueFolge('F02', SZENEN);
|
export const F02 = baueFolge('F02', SZENEN);
|
||||||
|
|
|
||||||
|
|
@ -1 +1,292 @@
|
||||||
{}
|
{
|
||||||
|
"intro/t1": {
|
||||||
|
"datei": "intro-t1.mp3",
|
||||||
|
"dauer": 1.88,
|
||||||
|
"text": "Pinos kleine Welt."
|
||||||
|
},
|
||||||
|
"intro/t2": {
|
||||||
|
"datei": "intro-t2.mp3",
|
||||||
|
"dauer": 2.93,
|
||||||
|
"text": "Heute: Pino ist müde."
|
||||||
|
},
|
||||||
|
"s2/e1": {
|
||||||
|
"datei": "s2-e1.mp3",
|
||||||
|
"dauer": 2.85,
|
||||||
|
"text": "Es ist kalt geworden in Noas Garten."
|
||||||
|
},
|
||||||
|
"s2/e2": {
|
||||||
|
"datei": "s2-e2.mp3",
|
||||||
|
"dauer": 3.24,
|
||||||
|
"text": "Die Bäume haben fast alle Blätter verloren."
|
||||||
|
},
|
||||||
|
"s2/p1": {
|
||||||
|
"datei": "s2-p1.mp3",
|
||||||
|
"dauer": 1.23,
|
||||||
|
"text": "Hoaaah."
|
||||||
|
},
|
||||||
|
"s2/n1": {
|
||||||
|
"datei": "s2-n1.mp3",
|
||||||
|
"dauer": 2.77,
|
||||||
|
"text": "Guten Morgen, Pino! Wollen wir spielen?"
|
||||||
|
},
|
||||||
|
"s2/p2": {
|
||||||
|
"datei": "s2-p2.mp3",
|
||||||
|
"dauer": 4.6,
|
||||||
|
"text": "Ja! Ich bin ganz wach. Gaaanz … wach …"
|
||||||
|
},
|
||||||
|
"s2/n2": {
|
||||||
|
"datei": "s2-n2.mp3",
|
||||||
|
"dauer": 1.88,
|
||||||
|
"text": "Pino? Pino!"
|
||||||
|
},
|
||||||
|
"s2/p3": {
|
||||||
|
"datei": "s2-p3.mp3",
|
||||||
|
"dauer": 2.93,
|
||||||
|
"text": "Was? Ich hab nicht geschlafen!"
|
||||||
|
},
|
||||||
|
"s2/e3": {
|
||||||
|
"datei": "s2-e3.mp3",
|
||||||
|
"dauer": 1.15,
|
||||||
|
"text": "Hat er doch."
|
||||||
|
},
|
||||||
|
"s2/e4": {
|
||||||
|
"datei": "s2-e4.mp3",
|
||||||
|
"dauer": 3.63,
|
||||||
|
"text": "Was meinst du: Warum ist Pino so müde?"
|
||||||
|
},
|
||||||
|
"s3/l1": {
|
||||||
|
"datei": "s3-l1.mp3",
|
||||||
|
"dauer": 1.8,
|
||||||
|
"text": "Guten Morgen, ihr zwei!"
|
||||||
|
},
|
||||||
|
"s3/l2": {
|
||||||
|
"datei": "s3-l2.mp3",
|
||||||
|
"dauer": 5.64,
|
||||||
|
"text": "Von hier oben sehe ich: Die Vögel fliegen weg, ganz weit in den Süden."
|
||||||
|
},
|
||||||
|
"s3/l3": {
|
||||||
|
"datei": "s3-l3.mp3",
|
||||||
|
"dauer": 3.63,
|
||||||
|
"text": "Und die Bäume sind kahl. Der Winter kommt."
|
||||||
|
},
|
||||||
|
"s3/e1": {
|
||||||
|
"datei": "s3-e1.mp3",
|
||||||
|
"dauer": 4.13,
|
||||||
|
"text": "Wenn es kalt wird, finden Igel kaum noch etwas zu fressen."
|
||||||
|
},
|
||||||
|
"s3/e2": {
|
||||||
|
"datei": "s3-e2.mp3",
|
||||||
|
"dauer": 4.6,
|
||||||
|
"text": "Darum machen sie etwas ganz Besonderes. Sie schlafen."
|
||||||
|
},
|
||||||
|
"s3/e3": {
|
||||||
|
"datei": "s3-e3.mp3",
|
||||||
|
"dauer": 5.15,
|
||||||
|
"text": "Den ganzen Winter lang, bis zum Frühling. Das heißt Winterschlaf."
|
||||||
|
},
|
||||||
|
"s3/p1": {
|
||||||
|
"datei": "s3-p1.mp3",
|
||||||
|
"dauer": 4.52,
|
||||||
|
"text": "Den ganzen Winter? Aber dann verpasse ich ja das Winterfest!"
|
||||||
|
},
|
||||||
|
"s3/e4": {
|
||||||
|
"datei": "s3-e4.mp3",
|
||||||
|
"dauer": 4.05,
|
||||||
|
"text": "Schau mal, Pinos Stacheln hängen ganz schlaff herunter."
|
||||||
|
},
|
||||||
|
"s3/e5": {
|
||||||
|
"datei": "s3-e5.mp3",
|
||||||
|
"dauer": 2.19,
|
||||||
|
"text": "Wie fühlt sich Pino jetzt?"
|
||||||
|
},
|
||||||
|
"s3/e6": {
|
||||||
|
"datei": "s3-e6.mp3",
|
||||||
|
"dauer": 2.77,
|
||||||
|
"text": "Ja. Pino ist traurig."
|
||||||
|
},
|
||||||
|
"s3/n1": {
|
||||||
|
"datei": "s3-n1.mp3",
|
||||||
|
"dauer": 4.75,
|
||||||
|
"text": "Ach, Pino. Bis zum Winterfest ist es doch noch ganz lange hin."
|
||||||
|
},
|
||||||
|
"s4/n1": {
|
||||||
|
"datei": "s4-n1.mp3",
|
||||||
|
"dauer": 3.63,
|
||||||
|
"text": "Wenn du so müde bist, brauchst du ein warmes Bett."
|
||||||
|
},
|
||||||
|
"s4/e1": {
|
||||||
|
"datei": "s4-e1.mp3",
|
||||||
|
"dauer": 2.93,
|
||||||
|
"text": "Igel bauen sich für den Winter ein Nest."
|
||||||
|
},
|
||||||
|
"s4/e2": {
|
||||||
|
"datei": "s4-e2.mp3",
|
||||||
|
"dauer": 4.91,
|
||||||
|
"text": "Aus ganz vielen Blättern, unter einer Hecke oder einem Holzstapel."
|
||||||
|
},
|
||||||
|
"s4/e3": {
|
||||||
|
"datei": "s4-e3.mp3",
|
||||||
|
"dauer": 2.27,
|
||||||
|
"text": "Da ist es trocken und warm."
|
||||||
|
},
|
||||||
|
"s4/n2": {
|
||||||
|
"datei": "s4-n2.mp3",
|
||||||
|
"dauer": 1.23,
|
||||||
|
"text": "Zählst du mit?"
|
||||||
|
},
|
||||||
|
"s4/c1": {
|
||||||
|
"datei": "s4-c1.mp3",
|
||||||
|
"dauer": 0.99,
|
||||||
|
"text": "Eins …"
|
||||||
|
},
|
||||||
|
"s4/c2": {
|
||||||
|
"datei": "s4-c2.mp3",
|
||||||
|
"dauer": 0.91,
|
||||||
|
"text": "Zwei …"
|
||||||
|
},
|
||||||
|
"s4/c3": {
|
||||||
|
"datei": "s4-c3.mp3",
|
||||||
|
"dauer": 1.07,
|
||||||
|
"text": "Drei …"
|
||||||
|
},
|
||||||
|
"s4/c4": {
|
||||||
|
"datei": "s4-c4.mp3",
|
||||||
|
"dauer": 0.91,
|
||||||
|
"text": "Vier …"
|
||||||
|
},
|
||||||
|
"s4/c5": {
|
||||||
|
"datei": "s4-c5.mp3",
|
||||||
|
"dauer": 0.84,
|
||||||
|
"text": "Fünf!"
|
||||||
|
},
|
||||||
|
"s4/l1": {
|
||||||
|
"datei": "s4-l1.mp3",
|
||||||
|
"dauer": 3.87,
|
||||||
|
"text": "Hier oben hängen noch welche! Die erreicht sonst keiner."
|
||||||
|
},
|
||||||
|
"s4/l2": {
|
||||||
|
"datei": "s4-l2.mp3",
|
||||||
|
"dauer": 2.04,
|
||||||
|
"text": "Oh! Entschuldigung!"
|
||||||
|
},
|
||||||
|
"s4/n3": {
|
||||||
|
"datei": "s4-n3.mp3",
|
||||||
|
"dauer": 3.24,
|
||||||
|
"text": "Macht nichts. Jetzt haben wir noch mehr Blätter."
|
||||||
|
},
|
||||||
|
"s4/e4": {
|
||||||
|
"datei": "s4-e4.mp3",
|
||||||
|
"dauer": 3.24,
|
||||||
|
"text": "Ein Igelnest braucht ganz viele Blätter."
|
||||||
|
},
|
||||||
|
"s4/e5": {
|
||||||
|
"datei": "s4-e5.mp3",
|
||||||
|
"dauer": 6.03,
|
||||||
|
"text": "Wenn ihr im Garten einen Laubhaufen liegen lasst, schenkt ihr einem Igel vielleicht ein Winterbettchen."
|
||||||
|
},
|
||||||
|
"lied/r1": {
|
||||||
|
"datei": "lied-r1.mp3",
|
||||||
|
"dauer": 2.85,
|
||||||
|
"text": "Einer klein und einer groß,"
|
||||||
|
},
|
||||||
|
"lied/r2": {
|
||||||
|
"datei": "lied-r2.mp3",
|
||||||
|
"dauer": 3.16,
|
||||||
|
"text": "und dazwischen – na, wer bloß?"
|
||||||
|
},
|
||||||
|
"lied/r3": {
|
||||||
|
"datei": "lied-r3.mp3",
|
||||||
|
"dauer": 2.19,
|
||||||
|
"text": "Noa steht in der Mitte drin,"
|
||||||
|
},
|
||||||
|
"lied/r4": {
|
||||||
|
"datei": "lied-r4.mp3",
|
||||||
|
"dauer": 2.35,
|
||||||
|
"text": "zusammen kriegen wir alles hin!"
|
||||||
|
},
|
||||||
|
"s5/p1": {
|
||||||
|
"datei": "s5-p1.mp3",
|
||||||
|
"dauer": 4.21,
|
||||||
|
"text": "Nein! Ich will nicht schlafen, bevor das Winterfest da ist!"
|
||||||
|
},
|
||||||
|
"s5/l1": {
|
||||||
|
"datei": "s5-l1.mp3",
|
||||||
|
"dauer": 3.4,
|
||||||
|
"text": "Aber Pino, du bist doch so müde!"
|
||||||
|
},
|
||||||
|
"s5/e1": {
|
||||||
|
"datei": "s5-e1.mp3",
|
||||||
|
"dauer": 6.43,
|
||||||
|
"text": "Hmm. Was können Noa und Lulu tun, damit Pino bis zum Winterfest wach bleibt?"
|
||||||
|
},
|
||||||
|
"s5/e2": {
|
||||||
|
"datei": "s5-e2.mp3",
|
||||||
|
"dauer": 1.41,
|
||||||
|
"text": "Was meinst du?"
|
||||||
|
},
|
||||||
|
"s5/n1": {
|
||||||
|
"datei": "s5-n1.mp3",
|
||||||
|
"dauer": 1.65,
|
||||||
|
"text": "Ich hab eine Idee!"
|
||||||
|
},
|
||||||
|
"s6/l1": {
|
||||||
|
"datei": "s6-l1.mp3",
|
||||||
|
"dauer": 1.96,
|
||||||
|
"text": "Was malst du da, Noa?"
|
||||||
|
},
|
||||||
|
"s6/n1": {
|
||||||
|
"datei": "s6-n1.mp3",
|
||||||
|
"dauer": 3.71,
|
||||||
|
"text": "Einen Kalender! Mit 24 Türchen."
|
||||||
|
},
|
||||||
|
"s6/n2": {
|
||||||
|
"datei": "s6-n2.mp3",
|
||||||
|
"dauer": 6.35,
|
||||||
|
"text": "Wir öffnen jeden Tag eins. Und hinter jedem Türchen steckt etwas, das Pino wach hält."
|
||||||
|
},
|
||||||
|
"s6/p1": {
|
||||||
|
"datei": "s6-p1.mp3",
|
||||||
|
"dauer": 0.99,
|
||||||
|
"text": "Was denn?"
|
||||||
|
},
|
||||||
|
"s6/n3": {
|
||||||
|
"datei": "s6-n3.mp3",
|
||||||
|
"dauer": 4.75,
|
||||||
|
"text": "Etwas zum Entdecken, zum Basteln, zum Backen und zum Singen."
|
||||||
|
},
|
||||||
|
"s6/n4": {
|
||||||
|
"datei": "s6-n4.mp3",
|
||||||
|
"dauer": 3.97,
|
||||||
|
"text": "Und wenn das letzte Türchen offen ist, ist Winterfest!"
|
||||||
|
},
|
||||||
|
"s6/p2": {
|
||||||
|
"datei": "s6-p2.mp3",
|
||||||
|
"dauer": 2.19,
|
||||||
|
"text": "Meine Stacheln kribbeln!"
|
||||||
|
},
|
||||||
|
"s7/n1": {
|
||||||
|
"datei": "s7-n1.mp3",
|
||||||
|
"dauer": 2.59,
|
||||||
|
"text": "Schlaf gut, Pino. Nur bis morgen."
|
||||||
|
},
|
||||||
|
"s7/e1": {
|
||||||
|
"datei": "s7-e1.mp3",
|
||||||
|
"dauer": 6.19,
|
||||||
|
"text": "Ab dem 1. Dezember öffnen Noa, Lulu und Pino jeden Tag ein Türchen."
|
||||||
|
},
|
||||||
|
"s7/e2": {
|
||||||
|
"datei": "s7-e2.mp3",
|
||||||
|
"dauer": 4.6,
|
||||||
|
"text": "Und du kannst mitmachen! Schaffst du es mit Pino bis zum Winterfest?"
|
||||||
|
},
|
||||||
|
"s7/t1": {
|
||||||
|
"datei": "s7-t1.mp3",
|
||||||
|
"dauer": 1.65,
|
||||||
|
"text": "Tschüss! Bis bald!"
|
||||||
|
},
|
||||||
|
"s7/p1": {
|
||||||
|
"datei": "s7-p1.mp3",
|
||||||
|
"dauer": 1.15,
|
||||||
|
"text": "… bis bald …"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
72
remotion/werkzeuge/geraeusche.mts
Normal file
72
remotion/werkzeuge/geraeusche.mts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Erzeugt Geräusche und Hintergrundklänge (Atmo) mit ElevenLabs Sound Effects
|
||||||
|
// (sprachunabhängig, für alle Folgen). Die Klänge stehen in src/baukasten/geraeusche.json
|
||||||
|
// ({name: {sekunden, beschreibung, loop?}}), die Dateien landen in
|
||||||
|
// public/geraeusche/<name>.mp3 (nicht im Git). Nur fehlende oder geänderte
|
||||||
|
// Klänge werden neu erzeugt; wo sie erklingen, steht in FolgeNN.tsx (mitKlang).
|
||||||
|
//
|
||||||
|
// npx tsx werkzeuge/geraeusche.mts
|
||||||
|
// … --klang malen nur einen Klang
|
||||||
|
// … --neu alle neu erzeugen
|
||||||
|
// … --probe nur anzeigen (kostet nichts)
|
||||||
|
|
||||||
|
import {execFileSync} from 'node:child_process';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
// Einzelgeräusche beginnen bei ElevenLabs oft mit bis zu 1 s Stille – abschneiden,
|
||||||
|
// damit `versatz` in FolgeNN.tsx genau den Einsatz des Geräuschs trifft.
|
||||||
|
export const stilleWeg = (pfad: string) => {
|
||||||
|
const tmp = pfad.replace(/\.mp3$/, '.tmp.mp3');
|
||||||
|
execFileSync('ffmpeg', ['-hide_banner', '-loglevel', 'error', '-y', '-i', pfad, '-af', 'silenceremove=start_periods=1:start_threshold=-40dB', '-ar', '48000', '-b:a', '128k', tmp]);
|
||||||
|
fs.renameSync(tmp, pfad);
|
||||||
|
};
|
||||||
|
import path from 'node:path';
|
||||||
|
import {fileURLToPath} from 'node:url';
|
||||||
|
import {Readable} from 'node:stream';
|
||||||
|
import {ElevenLabsClient} from '@elevenlabs/elevenlabs-js';
|
||||||
|
|
||||||
|
const WURZEL = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
process.chdir(WURZEL);
|
||||||
|
const envDatei = path.join(WURZEL, '..', '.env.local');
|
||||||
|
if (!process.env.ELEVENLABS_API_KEY && fs.existsSync(envDatei)) process.loadEnvFile(envDatei);
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const wert = (n: string) => (args.includes(n) ? args[args.indexOf(n) + 1] : undefined);
|
||||||
|
const nurKlang = wert('--klang');
|
||||||
|
const neu = args.includes('--neu');
|
||||||
|
const probe = args.includes('--probe');
|
||||||
|
|
||||||
|
type Eintrag = {sekunden: number; beschreibung: string; loop?: boolean};
|
||||||
|
const klaenge = JSON.parse(fs.readFileSync('src/baukasten/geraeusche.json', 'utf8')) as Record<string, Eintrag>;
|
||||||
|
const ordner = 'public/geraeusche';
|
||||||
|
fs.mkdirSync(ordner, {recursive: true});
|
||||||
|
const merkDatei = path.join(ordner, 'erzeugt.json');
|
||||||
|
const erzeugt: Record<string, string> = fs.existsSync(merkDatei) ? JSON.parse(fs.readFileSync(merkDatei, 'utf8')) : {};
|
||||||
|
|
||||||
|
const client = probe ? null : new ElevenLabsClient();
|
||||||
|
let sekunden = 0;
|
||||||
|
for (const [name, k] of Object.entries(klaenge)) {
|
||||||
|
if (nurKlang && name !== nurKlang) continue;
|
||||||
|
const pfad = path.join(ordner, `${name}.mp3`);
|
||||||
|
const kennung = `${k.sekunden}|${k.loop ? 'loop|' : ''}${k.beschreibung}`;
|
||||||
|
if (!neu && erzeugt[name] === kennung && fs.existsSync(pfad)) continue;
|
||||||
|
sekunden += k.sekunden;
|
||||||
|
if (probe) {
|
||||||
|
console.log(`~ ${name} (${k.sekunden} s${k.loop ? ', Schleife' : ''}): ${k.beschreibung}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const audio = await client!.textToSoundEffects.convert({
|
||||||
|
text: k.beschreibung,
|
||||||
|
durationSeconds: k.sekunden,
|
||||||
|
promptInfluence: 0.5,
|
||||||
|
loop: k.loop ?? false,
|
||||||
|
outputFormat: 'mp3_44100_128' as never,
|
||||||
|
});
|
||||||
|
const teile: Buffer[] = [];
|
||||||
|
for await (const t of Readable.fromWeb(audio as never)) teile.push(Buffer.from(t));
|
||||||
|
fs.writeFileSync(pfad, Buffer.concat(teile));
|
||||||
|
if (!k.loop) stilleWeg(pfad);
|
||||||
|
erzeugt[name] = kennung;
|
||||||
|
fs.writeFileSync(merkDatei, JSON.stringify(erzeugt, null, '\t') + '\n');
|
||||||
|
console.log(`✓ ${name} → ${pfad}`);
|
||||||
|
}
|
||||||
|
console.log(`\n${probe ? 'Würde erzeugen' : 'Erzeugt'}: ${sekunden} Sekunden`);
|
||||||
Loading…
Reference in a new issue