Ace / index.html
Transfer Bot
Moved to Hugging Face automatically
fa1466c
Raw
History Blame Contribute Delete
16.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ACE Studio — say the song, hear it back</title>
<meta name="description" content="Describe a song in plain English. ACE Studio writes the lyrics and produces the track — powered by our own fast ACE-Step turbo model.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{
--bg-0:#150f1e;
--bg-1:#1c1428;
--surface: rgba(255,255,255,0.045);
--surface-2: rgba(255,255,255,0.075);
--line: rgba(246,241,234,0.10);
--line-strong: rgba(246,241,234,0.20);
--ink:#f6f1ea;
--ink-dim:#a89bc2;
--ink-faint:#6f6386;
--coral:#ff6b4a;
--coral-dim:#8a3a29;
--gold:#ffd166;
--gold-dim:#8a6f2f;
--radius:16px;
}
*{box-sizing:border-box;}
html{scroll-behavior:smooth;}
body{
margin:0; min-height:100vh;
background:
radial-gradient(ellipse 65% 45% at 18% -8%, rgba(255,107,74,0.14), transparent 60%),
radial-gradient(ellipse 55% 40% at 88% 6%, rgba(255,209,102,0.08), transparent 60%),
linear-gradient(180deg, var(--bg-0) 0%, var(--bg-1) 100%);
color:var(--ink);
font-family:'Inter', system-ui, sans-serif;
-webkit-font-smoothing:antialiased;
}
a{color:inherit;}
::selection{background:var(--coral); color:#1a1006;}
::-webkit-scrollbar{width:10px;}
::-webkit-scrollbar-thumb{background:linear-gradient(180deg,var(--coral),var(--gold)); border-radius:6px;}
.wrap{max-width:760px; margin:0 auto; padding:0 24px;}
/* ── Top bar ─────────────────────────────────────────────────────────── */
.topbar{display:flex; align-items:center; justify-content:space-between; padding:22px 0;}
.wordmark{font-family:'Fraunces', serif; font-weight:600; font-size:18px; letter-spacing:0.2px;}
.wordmark .dot{color:var(--coral);}
.topbar nav{display:flex; gap:22px; font-size:13px; color:var(--ink-dim);}
.topbar nav a{text-decoration:none; transition:color .2s ease;}
.topbar nav a:hover{color:var(--gold);}
/* ── Hero ─────────────────────────────────────────────────────────────── */
.hero{padding:48px 0 8px 0; text-align:center;}
.eyebrow{
display:inline-block; font-family:'IBM Plex Mono', monospace; font-size:12px;
letter-spacing:1.4px; text-transform:uppercase; color:var(--gold);
background:rgba(255,209,102,0.08); border:1px solid rgba(255,209,102,0.25);
padding:6px 14px; border-radius:999px; margin-bottom:26px;
}
h1.headline{
font-family:'Fraunces', serif; font-weight:600; font-style:normal;
font-size:clamp(34px, 5.6vw, 56px); line-height:1.12; margin:0 0 16px 0; letter-spacing:-0.3px;
}
h1.headline em{font-style:italic; color:var(--coral); font-weight:500;}
p.sub{font-size:16.5px; color:var(--ink-dim); max-width:480px; margin:0 auto 40px auto; line-height:1.6;}
/* ── Composer ─────────────────────────────────────────────────────────── */
.composer{
background:var(--surface); border:1px solid var(--line); border-radius:var(--radius);
padding:6px 6px 18px 6px; text-align:left; backdrop-filter:blur(8px);
}
.composer textarea{
width:100%; background:transparent; border:none; outline:none; resize:none;
color:var(--ink); font-family:'Fraunces', serif; font-weight:500; font-size:19px;
line-height:1.5; padding:20px 18px 8px 18px; height:108px;
}
.composer textarea::placeholder{color:var(--ink-faint); font-style:italic;}
.composer-row{display:flex; align-items:center; justify-content:space-between; padding:6px 12px 4px 18px; gap:12px; flex-wrap:wrap;}
.duration-pill{
display:inline-flex; align-items:center; gap:8px; font-family:'IBM Plex Mono', monospace; font-size:12.5px; color:var(--ink-dim);
}
.duration-pill select{
background:var(--surface-2); border:1px solid var(--line); color:var(--ink);
border-radius:8px; padding:6px 10px; font-family:'IBM Plex Mono', monospace; font-size:12.5px;
}
.generate-btn{
font-family:'Inter', sans-serif; font-weight:700; font-size:15px;
background:linear-gradient(120deg, var(--coral), #ff8c6b); color:#210c06;
border:none; border-radius:11px; padding:13px 26px; cursor:pointer;
box-shadow:0 10px 28px rgba(255,107,74,0.25); transition:transform .15s ease, box-shadow .15s ease;
}
.generate-btn:hover{transform:translateY(-2px); box-shadow:0 14px 34px rgba(255,107,74,0.35);}
.generate-btn:disabled{opacity:0.55; cursor:not-allowed; transform:none;}
.hint{margin:14px 4px 0 4px; font-size:12px; color:var(--ink-faint); font-family:'IBM Plex Mono', monospace;}
/* ── Signature element: spinning reel ────────────────────────────────── */
.status-row{display:flex; align-items:center; gap:14px; margin:26px 0 0 0; min-height:34px;}
.reel{width:34px; height:34px; flex-shrink:0; display:none;}
.reel.spin{display:block; animation:reel-spin 2.2s linear infinite;}
@keyframes reel-spin{ to{ transform:rotate(360deg); } }
.status-text{font-family:'IBM Plex Mono', monospace; font-size:12.5px; color:var(--ink-dim);}
.status-text.error{color:#ff8b7a;}
/* ── Result card ──────────────────────────────────────────────────────── */
.result{
display:none; margin-top:34px; background:var(--surface); border:1px solid var(--line);
border-radius:var(--radius); padding:26px 26px 22px 26px;
}
.result.show{display:block;}
.result-head{display:flex; align-items:center; gap:16px; margin-bottom:18px;}
.reel-lg{width:56px; height:56px; flex-shrink:0;}
.reel-lg.spin{animation:reel-spin 3s linear infinite;}
.result-title{font-family:'Fraunces', serif; font-weight:600; font-size:22px; margin:0 0 6px 0;}
.result-tags{display:flex; gap:6px; flex-wrap:wrap;}
.tag-pill{
font-family:'IBM Plex Mono', monospace; font-size:11px; color:var(--gold);
background:rgba(255,209,102,0.08); border:1px solid rgba(255,209,102,0.22);
padding:3px 9px; border-radius:999px;
}
audio{width:100%; margin:6px 0 16px 0; border-radius:10px;}
.lyrics-toggle{
background:none; border:none; color:var(--ink-dim); font-family:'IBM Plex Mono', monospace;
font-size:12px; cursor:pointer; padding:0; text-decoration:underline dotted var(--ink-faint);
}
.lyrics-toggle:hover{color:var(--gold);}
.lyrics-body{
display:none; margin-top:14px; padding:16px 18px; background:rgba(0,0,0,0.18);
border-radius:10px; border:1px solid var(--line);
font-family:'Fraunces', serif; font-size:15px; line-height:1.75; white-space:pre-wrap; color:var(--ink);
}
.lyrics-body.show{display:block;}
.download-row{margin-top:14px;}
.download-link{
font-family:'IBM Plex Mono', monospace; font-size:12px; color:var(--ink-dim);
text-decoration:none; border-bottom:1px dashed var(--ink-faint);
}
.download-link:hover{color:var(--gold); border-color:var(--gold-dim);}
/* ── Footer ───────────────────────────────────────────────────────────── */
footer{
margin-top:60px; padding:24px 0 44px 0; border-top:1px solid var(--line);
display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px;
font-size:12px; color:var(--ink-faint); font-family:'IBM Plex Mono', monospace;
}
footer a{text-decoration:none; color:var(--ink-faint); transition:color .2s ease;}
footer a:hover{color:var(--coral);}
footer .links{display:flex; gap:16px; flex-wrap:wrap;}
@media (max-width:560px){
.topbar nav{display:none;}
.composer-row{align-items:flex-start;}
}
@media (prefers-reduced-motion: reduce){
.reel.spin, .reel-lg.spin{animation:none;}
}
</style>
</head>
<body>
<div class="wrap">
<div class="topbar">
<div class="wordmark">ACE <span class="dot">Studio</span></div>
<nav>
<a href="https://github.com/ACE-Step/ACE-Step-1.5" target="_blank">GitHub</a>
<a href="https://huggingface.co/collections/ACE-Step/ace-step-15" target="_blank">Model</a>
</nav>
</div>
<section class="hero">
<span class="eyebrow">Fast turbo model &middot; ZeroGPU</span>
<h1 class="headline">Say the song.<br>Hear it back <em>in seconds</em>.</h1>
<p class="sub">Describe a mood, a story, a genre — anything. We write the lyrics and produce the track for you.</p>
</section>
<div class="composer">
<textarea id="desc" placeholder="a bittersweet indie-folk song about leaving your hometown, gentle acoustic guitar, warm female vocals..."></textarea>
<div class="composer-row">
<label class="duration-pill">
Length
<select id="duration">
<option value="30">30s</option>
<option value="60" selected>60s</option>
<option value="90">90s</option>
<option value="120">120s</option>
</select>
</label>
<button class="generate-btn" id="generate-btn">Write &amp; Produce →</button>
</div>
</div>
<p class="hint">⌘/Ctrl + Enter to generate &middot; first run may take a little longer while a GPU is allocated</p>
<div class="status-row" id="status-row">
<svg class="reel" id="reel-sm" viewBox="0 0 40 40" aria-hidden="true">
<circle cx="20" cy="20" r="17" fill="none" stroke="#8a3a29" stroke-width="2.5"/>
<circle cx="20" cy="20" r="17" fill="none" stroke="#ff6b4a" stroke-width="2.5" stroke-dasharray="20 87" stroke-linecap="round"/>
<circle cx="20" cy="20" r="5" fill="#ffd166"/>
<circle cx="20" cy="9" r="2.2" fill="#1c1428"/>
<circle cx="9" cy="26" r="2.2" fill="#1c1428"/>
<circle cx="31" cy="26" r="2.2" fill="#1c1428"/>
</svg>
<span class="status-text" id="status-text"></span>
</div>
<div class="result" id="result">
<div class="result-head">
<svg class="reel-lg" id="reel-lg" viewBox="0 0 40 40" aria-hidden="true">
<circle cx="20" cy="20" r="17" fill="none" stroke="#8a3a29" stroke-width="2.5"/>
<circle cx="20" cy="20" r="17" fill="none" stroke="#ff6b4a" stroke-width="2.5" stroke-dasharray="20 87" stroke-linecap="round"/>
<circle cx="20" cy="20" r="5" fill="#ffd166"/>
<circle cx="20" cy="9" r="2.2" fill="#1c1428"/>
<circle cx="9" cy="26" r="2.2" fill="#1c1428"/>
<circle cx="31" cy="26" r="2.2" fill="#1c1428"/>
</svg>
<div>
<h3 class="result-title" id="result-title"></h3>
<div class="result-tags" id="result-tags"></div>
</div>
</div>
<audio id="result-audio" controls></audio>
<div>
<button class="lyrics-toggle" id="lyrics-toggle">Show lyrics</button>
<div class="lyrics-body" id="lyrics-body"></div>
</div>
<div class="download-row">
<a class="download-link" id="download-link" download="song.wav">Download WAV ↓</a>
</div>
</div>
<footer>
<span>ACE Studio &middot; built on ACE-Step v1.5</span>
<div class="links">
<a href="https://github.com/ACE-Step/ACE-Step-1.5" target="_blank">GitHub</a>
<a href="https://huggingface.co/collections/ACE-Step/ace-step-15" target="_blank">Hugging Face</a>
<a href="https://discord.gg/PeWDxrkdj7" target="_blank">Discord</a>
</div>
</footer>
</div>
<script type="module">
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client@1.15.2/dist/index.min.js";
const descEl = document.getElementById("desc");
const durationEl = document.getElementById("duration");
const btnEl = document.getElementById("generate-btn");
const statusRow = document.getElementById("status-row");
const reelSm = document.getElementById("reel-sm");
const statusText = document.getElementById("status-text");
const resultEl = document.getElementById("result");
const reelLg = document.getElementById("reel-lg");
const titleEl = document.getElementById("result-title");
const tagsEl = document.getElementById("result-tags");
const audioEl = document.getElementById("result-audio");
const lyricsToggle = document.getElementById("lyrics-toggle");
const lyricsBody = document.getElementById("lyrics-body");
const downloadLink = document.getElementById("download-link");
let client = null;
async function getClient() {
if (!client) client = await Client.connect(window.location.origin);
return client;
}
function cleanErrorText(msg) {
return String(msg || "").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
}
function setStatus(msg, isError) {
statusText.textContent = msg || "";
statusText.classList.toggle("error", !!isError);
}
function setBusy(busy) {
btnEl.disabled = busy;
btnEl.textContent = busy ? "Producing…" : "Write & Produce →";
reelSm.classList.toggle("spin", busy);
reelLg.classList.toggle("spin", busy);
}
lyricsToggle.addEventListener("click", () => {
const show = !lyricsBody.classList.contains("show");
lyricsBody.classList.toggle("show", show);
lyricsToggle.textContent = show ? "Hide lyrics" : "Show lyrics";
});
async function generate() {
const description = descEl.value.trim();
if (!description) { descEl.focus(); return; }
setBusy(true);
setStatus("Waking up the studio…");
resultEl.classList.remove("show");
try {
const c = await getClient();
const job = c.submit("/create", {
description,
audio_duration: parseFloat(durationEl.value),
seed: -1,
});
for await (const msg of job) {
if (msg.type === "status") {
if (msg.stage === "error") {
throw new Error(msg.message || "Generation failed on the server.");
} else if (msg.queue_size > 0) {
const eta = msg.eta != null ? ` · ~${Math.round(msg.eta)}s` : "";
setStatus(`In queue (${msg.position}/${msg.queue_size})${eta}`);
} else {
setStatus("Writing lyrics & producing the track…");
}
} else if (msg.type === "data") {
const raw = msg.data && msg.data[0];
if (!raw) throw new Error("No data returned");
const result = JSON.parse(raw);
titleEl.textContent = result.title || "Untitled";
tagsEl.innerHTML = "";
(result.tags || "").split(",").map(t => t.trim()).filter(Boolean).forEach(t => {
const span = document.createElement("span");
span.className = "tag-pill";
span.textContent = t;
tagsEl.appendChild(span);
});
audioEl.src = result.audio;
downloadLink.href = result.audio;
lyricsBody.textContent = result.lyrics || "";
lyricsBody.classList.remove("show");
lyricsToggle.textContent = "Show lyrics";
resultEl.classList.add("show");
setStatus("Done.");
}
}
if (!resultEl.classList.contains("show")) {
throw new Error("The studio stopped responding before finishing. Please try again.");
}
} catch (err) {
console.error(err);
setStatus(`Something went wrong: ${cleanErrorText(err.message)}`, true);
} finally {
setBusy(false);
}
}
btnEl.addEventListener("click", generate);
descEl.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { e.preventDefault(); generate(); }
});
getClient().catch(console.error);
</script>
</body>
</html>