import { useState } from "react"; import { useModel } from "./hooks/useModel"; import Waveform from "./components/Waveform"; import PulseBars from "./components/PulseBars"; const PRESETS = [ { name: "Pop Ballad", emoji: "💗", duration: 60, caption: "A gentle pop ballad with piano and soft vocals, key of C major, 80 BPM, emotional and dreamy", lyrics: "[verse]\nUnderneath the stars tonight\nWe dance beneath the pale moonlight\nEvery moment feels so right\nHolding you so close and tight\n\n[chorus]\nThis is where I want to be\nRight here with you next to me\nLet the world just fade away\nIn your arms I want to stay", }, { name: "Rock Anthem", emoji: "🎸", duration: 60, caption: "An energetic rock anthem with electric guitars and powerful drums, key of E minor, 140 BPM, aggressive and intense", lyrics: "[verse]\nFire burning in my veins\nBreaking free from all these chains\nNothing left to hold me back\nRiding down the beaten track\n\n[chorus]\nWe are the ones who rise\nWith thunder in our eyes\nWe'll never be denied\nWe're burning up the sky", }, { name: "Lo-fi Chill", emoji: "☕", duration: 20, caption: "A relaxing lo-fi hip hop beat with jazz piano samples and vinyl crackle, key of F major, 75 BPM, mellow and nostalgic", lyrics: "[instrumental]", }, ]; function WebGPUGate({ children }) { const supported = typeof navigator !== "undefined" && !!navigator.gpu; if (supported) return children; return (
🎹

WebGPU not available

This demo needs WebGPU to run ACE-Step in your browser. Try Chrome 113+, Edge 113+, or Safari 26+ on desktop.

); } function ProgressBar({ progress }) { if (!progress) return null; const pct = Math.max(0, Math.min(100, progress.percent || 0)); return (
{progress.label} {progress.total > 1 && `${(progress.loaded / 1e6).toFixed(0)} / ${(progress.total / 1e6).toFixed(0)} MB · `} {pct.toFixed(0)}%
); } function LoadGate({ onLoad, status, message, progress, error }) { const loading = status === "loading"; return (
🎹

Load models

Loads ~8 GB of ONNX models. Everything runs in your browser — your prompts never leave this device. Built with{" "} 🤗 Transformers.js {" + "} ONNX Runtime Web .

{error ? (
{error}
) : loading ? (
{message && (

{message}

)} {progress && }
) : ( )}
); } function PresetCard({ preset, active, onClick }) { return ( ); } function GenerationStatus({ status, message }) { if (status !== "generating") return null; return (
{message || "Generating…"} this takes 1–4 min
); } function OutputCard({ audioUrl, audioInfo }) { if (!audioUrl) return null; return (
48 kHz · stereo {audioInfo?.totalTime && ` · ${audioInfo.totalTime}s gen`}
⬇ Download WAV
); } export default function App() { const { status, message, progress, audioUrl, audioInfo, error, isLoaded, loadModel, generate } = useModel(); const [activeIdx, setActiveIdx] = useState(0); const [caption, setCaption] = useState(PRESETS[0].caption); const [lyrics, setLyrics] = useState(PRESETS[0].lyrics); const [duration, setDuration] = useState(PRESETS[0].duration); const [shift, setShift] = useState(3.0); const [numSteps, setNumSteps] = useState(8); const isWorking = status === "loading" || status === "generating"; const applyPreset = (i) => { setActiveIdx(i); setCaption(PRESETS[i].caption); setLyrics(PRESETS[i].lyrics); setDuration(PRESETS[i].duration); }; return (
{/* Hero */}

ACE-Step WebGPU

Describe any song. AI writes & produces it.

{!isLoaded ? ( ) : ( <> {/* Presets */}
{PRESETS.map((p, i) => ( applyPreset(i)} /> ))}
{/* Caption */}