import { useEffect, useState } from 'react'; import { AudioPlayer } from './AudioPlayer'; import { Podcast, PodcastTurn } from '../utils/types'; import { parse } from 'yaml'; import { generateAudio, joinAudio, loadWavAndDecode, pickRand, } from '../utils/utils'; interface GenerationStep { turn: PodcastTurn; audioBuffer?: AudioBuffer; } const SPEEDS = [ { name: 'slow AF', value: 0.8 }, { name: 'slow', value: 0.9 }, { name: 'a bit slow', value: 1.0 }, { name: 'most natural', value: 1.1 }, { name: 'a bit fast', value: 1.2 }, { name: 'fast!', value: 1.3 }, { name: 'fast AF', value: 1.4 }, ]; const SPEAKERS = [ { name: 'πŸ‡ΊπŸ‡Έ 🚺 Heart ❀️', value: 'af_heart' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Bella πŸ”₯', value: 'af_bella' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Nicole 🎧', value: 'af_nicole' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Aoede', value: 'af_aoede' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Kore', value: 'af_kore' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Sarah', value: 'af_sarah' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Nova', value: 'af_nova' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Sky', value: 'af_sky' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Alloy', value: 'af_alloy' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 Jessica', value: 'af_jessica' }, { name: 'πŸ‡ΊπŸ‡Έ 🚺 River', value: 'af_river' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Michael', value: 'am_michael' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Fenrir', value: 'am_fenrir' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Puck', value: 'am_puck' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Echo', value: 'am_echo' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Eric', value: 'am_eric' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Liam', value: 'am_liam' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Onyx', value: 'am_onyx' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Santa', value: 'am_santa' }, { name: 'πŸ‡ΊπŸ‡Έ 🚹 Adam', value: 'am_adam' }, { name: 'πŸ‡¬πŸ‡§ 🚺 Emma', value: 'bf_emma' }, { name: 'πŸ‡¬πŸ‡§ 🚺 Isabella', value: 'bf_isabella' }, { name: 'πŸ‡¬πŸ‡§ 🚺 Alice', value: 'bf_alice' }, { name: 'πŸ‡¬πŸ‡§ 🚺 Lily', value: 'bf_lily' }, { name: 'πŸ‡¬πŸ‡§ 🚹 George', value: 'bm_george' }, { name: 'πŸ‡¬πŸ‡§ 🚹 Fable', value: 'bm_fable' }, { name: 'πŸ‡¬πŸ‡§ 🚹 Lewis', value: 'bm_lewis' }, { name: 'πŸ‡¬πŸ‡§ 🚹 Daniel', value: 'bm_daniel' }, ]; const getRandomSpeakerPair = (): { s1: string; s2: string } => { const s1Gender = Math.random() > 0.5 ? '🚺' : '🚹'; const s2Gender = s1Gender === '🚺' ? '🚹' : '🚺'; const s1 = pickRand( SPEAKERS.filter((s) => s.name.includes(s1Gender) && s.name.includes('πŸ‡ΊπŸ‡Έ')) ).value; const s2 = pickRand( SPEAKERS.filter((s) => s.name.includes(s2Gender) && s.name.includes('πŸ‡ΊπŸ‡Έ')) ).value; return { s1, s2 }; }; export const PodcastGenerator = ({ genratedScript, setBusy, busy, }: { genratedScript: string; setBusy: (busy: boolean) => void; busy: boolean; }) => { const [wav, setWav] = useState(null); const [numSteps, setNumSteps] = useState(0); const [numStepsDone, setNumStepsDone] = useState(0); const [script, setScript] = useState(''); const [speaker1, setSpeaker1] = useState(''); const [speaker2, setSpeaker2] = useState(''); const [speed, setSpeed] = useState('1.1'); const setRandSpeaker = () => { const { s1, s2 } = getRandomSpeakerPair(); setSpeaker1(s1); setSpeaker2(s2); }; useEffect(setRandSpeaker, []); useEffect(() => { setScript(genratedScript); }, [genratedScript]); const generatePodcast = async () => { setWav(null); setBusy(true); try { const podcast: Podcast = parse(script); const { speakerNames, turns } = podcast; for (const turn of turns) { // normalize it turn.nextGapMilisecs = Math.max(-600, Math.min(300, turn.nextGapMilisecs)) - 100; turn.text = turn.text .trim() .replace(/’/g, "'") .replace(/β€œ/g, '"') .replace(/”/g, '"'); } const steps: GenerationStep[] = turns.map((turn) => ({ turn })); setNumSteps(steps.length); setNumStepsDone(0); let outputWav: AudioBuffer; for (let i = 0; i < steps.length; i++) { const step = steps[i]; const speakerIdx = speakerNames.indexOf( step.turn.speakerName as string ) as 1 | 0; const speakerVoice = speakerIdx === 0 ? speaker1 : speaker2; const url = await generateAudio( step.turn.text, speakerVoice, parseFloat(speed) ); step.audioBuffer = await loadWavAndDecode(url); if (i === 0) { outputWav = step.audioBuffer; } else { const lastStep = steps[i - 1]; outputWav = joinAudio( outputWav!, step.audioBuffer, lastStep.turn.nextGapMilisecs / 1000 ); } setNumStepsDone(i + 1); } setWav(outputWav! ?? null); } catch (e) { console.error(e); alert(`Error: ${(e as any).message}`); setWav(null); } setBusy(false); setNumStepsDone(0); setNumSteps(0); }; const isGenerating = numSteps > 0; return ( <>

Step 2: Script (YAML format)

{isGenerating && ( )}
{wav && (

Step 3: Listen to your podcast

)} ); };