Spaces:
Build error
Build error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Sample Generator</title> | |
| <style> | |
| *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } | |
| html, body { | |
| height: 100%; | |
| background: #c0c0c0; | |
| font-family: 'Courier New', Courier, monospace; | |
| font-size: 13px; | |
| color: #000; | |
| } | |
| body { | |
| display: flex; | |
| flex-direction: column; | |
| padding: 6px; | |
| } | |
| /* βββ Window fills viewport βββββββββββββββββββββββββββββββββββββ */ | |
| .window { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| border: 3px solid; | |
| border-color: #fff #404040 #404040 #fff; | |
| min-height: 0; | |
| } | |
| /* βββ Title bar βββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .title-bar { | |
| background: #1a44d8; | |
| color: #fff; | |
| padding: 5px 8px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| user-select: none; | |
| flex-shrink: 0; | |
| } | |
| .title-bar-title { font-weight: bold; font-size: 14px; letter-spacing: 0.5px; } | |
| .title-bar-btns { display: flex; gap: 3px; } | |
| .tbtn { | |
| width: 24px; height: 20px; | |
| background: #c0c0c0; | |
| border: 2px solid; border-color: #fff #404040 #404040 #fff; | |
| font-size: 10px; font-weight: bold; color: #000; | |
| cursor: pointer; display: flex; align-items: center; justify-content: center; | |
| font-family: 'Courier New', monospace; | |
| } | |
| .tbtn:active { border-color: #404040 #fff #fff #404040; } | |
| /* βββ Nav βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .nav { | |
| background: #c0c0c0; | |
| padding: 7px 18px; | |
| display: flex; gap: 44px; | |
| flex-shrink: 0; | |
| } | |
| .nav-item { font-size: 13px; cursor: pointer; color: #000; } | |
| .nav-item.active { font-weight: bold; } | |
| /* βββ White content box β fills remaining space βββββββββββββββββ */ | |
| .content-wrap { | |
| flex: 1; | |
| padding: 6px 7px 0; | |
| display: flex; | |
| flex-direction: column; | |
| min-height: 0; | |
| } | |
| .content { | |
| flex: 1; | |
| background: #fff; | |
| border: 2px solid; border-color: #404040 #fff #fff #404040; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| overflow: hidden; | |
| } | |
| /* βββ Pages (all fill content box) βββββββββββββββββββββββββββββ */ | |
| .page { | |
| display: none; | |
| width: 100%; height: 100%; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .page.active { display: flex; } | |
| /* βββ HOME ββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .home-inner { text-align: center; } | |
| .home-eyebrow { font-size: 14px; letter-spacing: 2px; margin-bottom: 6px; } | |
| .home-big { | |
| font-size: 96px; font-weight: bold; | |
| color: #1a44d8; line-height: 1; | |
| letter-spacing: -4px; margin-bottom: 4px; | |
| } | |
| .home-sub { font-size: 15px; letter-spacing: 6px; margin-bottom: 34px; } | |
| /* βββ GENERATE β centered narrow column βββββββββββββββββββββββββ */ | |
| .form-inner { | |
| width: 480px; | |
| max-width: calc(100% - 40px); | |
| } | |
| .field { margin-bottom: 18px; } | |
| .field-lbl { | |
| display: block; font-size: 11px; letter-spacing: 1px; | |
| color: #666; margin-bottom: 6px; | |
| } | |
| .win-input { | |
| width: 100%; padding: 5px 7px; | |
| font-family: 'Courier New', monospace; font-size: 13px; | |
| border: 0; border-bottom: 1px solid #999; | |
| background: transparent; outline: none; color: #000; | |
| } | |
| .win-input:focus { border-bottom-color: #1a44d8; } | |
| .input-row { display: flex; gap: 8px; align-items: flex-end; } | |
| .input-row .win-input { flex: 1; } | |
| .win-select { | |
| padding: 4px 6px; | |
| font-family: 'Courier New', monospace; font-size: 12px; | |
| border: 1px solid #999; | |
| background: #fff; cursor: pointer; color: #000; outline: none; | |
| } | |
| .win-select:focus { border-color: #1a44d8; } | |
| /* Tags */ | |
| .tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; } | |
| .tag { | |
| background: #c0c0c0; | |
| border: 1px solid; border-color: #fff #808080 #808080 #fff; | |
| padding: 2px 9px; font-size: 11px; cursor: pointer; | |
| } | |
| .tag:hover { background: #1a44d8; color: #fff; border-color: #1a44d8; } | |
| .tag:active { border-color: #808080 #fff #fff #808080; } | |
| .options-row { | |
| display: flex; gap: 24px; flex-wrap: wrap; | |
| margin-bottom: 22px; align-items: flex-end; | |
| } | |
| .options-row .field { margin-bottom: 0; } | |
| /* Buttons */ | |
| .btn { | |
| background: #c0c0c0; | |
| border: 2px solid; border-color: #fff #404040 #404040 #fff; | |
| padding: 4px 18px; | |
| font-family: 'Courier New', monospace; font-size: 13px; | |
| cursor: pointer; color: #000; | |
| } | |
| .btn:active { border-color: #404040 #fff #fff #404040; padding: 5px 17px 3px 19px; } | |
| .btn:disabled { color: #808080; cursor: default; } | |
| .btn.default { font-weight: bold; border-width: 3px; padding: 5px 24px; } | |
| .btn.ghost { | |
| background: transparent; border: 1px solid #999; | |
| font-size: 12px; padding: 4px 14px; | |
| } | |
| .btn.ghost:hover { background: #f0f0f0; } | |
| .btn.ghost:active { border-color: #404040; } | |
| .action-row { display: flex; gap: 8px; align-items: center; } | |
| /* Progress */ | |
| .progress-wrap { display: none; margin-top: 18px; } | |
| .progress-lbl { font-size: 11px; color: #666; margin-bottom: 5px; } | |
| .progress-track { | |
| width: 100%; height: 4px; | |
| background: #e0e0e0; overflow: hidden; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: repeating-linear-gradient( | |
| 90deg, #1a44d8 0px, #1a44d8 60%, #c0d0ff 60%, #c0d0ff 100% | |
| ); | |
| background-size: 200% 100%; | |
| animation: slide 1.2s linear infinite; | |
| } | |
| @keyframes slide { to { background-position: -200% 0; } } | |
| /* Result */ | |
| .result-box { display: none; margin-top: 18px; } | |
| audio { width: 100%; display: block; margin-bottom: 10px; } | |
| .result-meta { font-size: 10px; color: #888; margin-top: 8px; } | |
| /* βββ HISTORY βββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .history-inner { width: 600px; max-width: calc(100% - 40px); max-height: 80%; overflow-y: auto; } | |
| .history-empty { text-align: center; color: #999; padding: 40px 0; font-size: 13px; } | |
| .h-item { | |
| border-bottom: 1px solid #eee; | |
| padding: 8px 0; | |
| display: flex; align-items: center; gap: 10px; flex-wrap: wrap; | |
| } | |
| .h-item:first-child { border-top: 1px solid #eee; } | |
| .h-num { font-size: 10px; color: #aaa; width: 20px; } | |
| .h-desc { flex: 1; font-size: 12px; min-width: 120px; } | |
| .h-dur { font-size: 10px; color: #999; white-space: nowrap; } | |
| .h-audio audio { height: 24px; width: 180px; } | |
| /* βββ ABOUT βββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .about-inner { | |
| width: 420px; max-width: calc(100% - 40px); | |
| font-size: 13px; line-height: 1.85; color: #222; | |
| text-align: center; | |
| } | |
| .about-name { | |
| font-size: 28px; font-weight: bold; | |
| color: #1a44d8; letter-spacing: -1px; | |
| margin-bottom: 4px; | |
| } | |
| .about-role { | |
| font-size: 11px; letter-spacing: 3px; | |
| color: #888; margin-bottom: 22px; | |
| text-transform: uppercase; | |
| } | |
| .about-divider { | |
| border: none; border-top: 1px solid #ddd; | |
| margin: 18px 0; | |
| } | |
| .about-bio { | |
| font-size: 13px; color: #444; | |
| line-height: 1.9; margin-bottom: 22px; | |
| } | |
| .about-links { | |
| display: flex; flex-direction: column; gap: 8px; | |
| align-items: center; | |
| } | |
| .about-link { | |
| display: inline-flex; align-items: center; gap: 8px; | |
| font-size: 12px; color: #1a44d8; | |
| text-decoration: none; letter-spacing: 0.3px; | |
| } | |
| .about-link:hover { text-decoration: underline; } | |
| .about-link-icon { font-size: 13px; } | |
| /* βββ Scrollbar βββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .scrollbar { | |
| background: #c0c0c0; height: 20px; | |
| display: flex; align-items: stretch; flex-shrink: 0; | |
| margin-top: 0; | |
| } | |
| .sb-arrow { | |
| width: 20px; flex-shrink: 0; | |
| border: 2px solid; border-color: #fff #404040 #404040 #fff; | |
| display: flex; align-items: center; justify-content: center; | |
| font-size: 9px; cursor: default; background: #c0c0c0; | |
| } | |
| .sb-arrow:active { border-color: #404040 #fff #fff #404040; } | |
| .sb-track { | |
| flex: 1; position: relative; | |
| background: repeating-linear-gradient( | |
| 45deg, #b8b8b8 0, #b8b8b8 1px, #c0c0c0 1px, #c0c0c0 4px | |
| ); | |
| } | |
| .sb-thumb { | |
| position: absolute; left: 38%; top: 0; | |
| width: 56px; height: 100%; | |
| border: 2px solid; border-color: #fff #404040 #404040 #fff; | |
| background: #c0c0c0; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="window"> | |
| <!-- Title bar --> | |
| <div class="title-bar"> | |
| <span class="title-bar-title">Sample Generator</span> | |
| <div class="title-bar-btns"> | |
| <button class="tbtn">_</button> | |
| <button class="tbtn">β‘</button> | |
| <button class="tbtn">Γ</button> | |
| </div> | |
| </div> | |
| <!-- Nav --> | |
| <div class="nav"> | |
| <span class="nav-item active" id="nav-home" onclick="show('home')">Home</span> | |
| <span class="nav-item" id="nav-generate" onclick="show('generate')">Generate</span> | |
| <span class="nav-item" id="nav-history" onclick="show('history')">History</span> | |
| <span class="nav-item" id="nav-about" onclick="show('about')">About</span> | |
| </div> | |
| <!-- White content box --> | |
| <div class="content-wrap"> | |
| <div class="content"> | |
| <!-- HOME --> | |
| <div class="page active" id="page-home"> | |
| <div class="home-inner"> | |
| <p class="home-eyebrow">Generate a</p> | |
| <div class="home-big">SAMPLE</div> | |
| <p class="home-sub">FROM SCRATCH</p> | |
| <button class="btn default" onclick="show('generate')">Start</button> | |
| </div> | |
| </div> | |
| <!-- GENERATE --> | |
| <div class="page" id="page-generate"> | |
| <div class="form-inner"> | |
| <div class="field"> | |
| <label class="field-lbl">DESCRIPTION</label> | |
| <div class="input-row"> | |
| <input type="text" class="win-input" id="desc" | |
| placeholder="e.g. lo-fi hip hop, ambient, jazz piano..." | |
| value="lo-fi hip hop with rain and vinyl crackle"> | |
| <button class="btn ghost" onclick="randomPrompt()">π²</button> | |
| </div> | |
| <div class="tags" id="tagRow"></div> | |
| </div> | |
| <div class="options-row"> | |
| <div class="field"> | |
| <label class="field-lbl">DURATION</label> | |
| <select class="win-select" id="dur"> | |
| <option value="5">5 seconds</option> | |
| <option value="8" selected>8 seconds</option> | |
| <option value="15">15 seconds</option> | |
| <option value="30">30 seconds</option> | |
| </select> | |
| </div> | |
| <div class="field"> | |
| <label class="field-lbl">MODEL</label> | |
| <select class="win-select" id="modelSel"> | |
| <option value="facebook/musicgen-small" selected>musicgen-small</option> | |
| <option value="facebook/musicgen-medium">musicgen-medium</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="action-row"> | |
| <button class="btn default" id="genBtn" onclick="generate()">βΊ Generate</button> | |
| <button class="btn ghost" onclick="clearResult()">Clear</button> | |
| </div> | |
| <div class="progress-wrap" id="progressWrap"> | |
| <div class="progress-lbl" id="progressLbl">Generatingβ¦</div> | |
| <div class="progress-track"><div class="progress-fill"></div></div> | |
| </div> | |
| <div class="result-box" id="resultBox"> | |
| <audio id="player" controls></audio> | |
| <div class="action-row"> | |
| <button class="btn ghost" onclick="dlSample()">β Download WAV</button> | |
| <button class="btn ghost" onclick="generate()">βΊ Regenerate</button> | |
| </div> | |
| <div class="result-meta" id="resultMeta"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- HISTORY --> | |
| <div class="page" id="page-history"> | |
| <div class="history-inner"> | |
| <div id="historyList"> | |
| <div class="history-empty">No samples generated yet.</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ABOUT --> | |
| <div class="page" id="page-about"> | |
| <div class="about-inner"> | |
| <div class="about-name">M - A</div> | |
| <div class="about-role">Applied AI Engineer & Artist</div> | |
| <p class="about-bio"> | |
| Designing and developing AI solutions for all kinds of use cases β | |
| and also a song-writer, musician and painter.<br> | |
| This sample generator is one small piece of that creative chaos π€ | |
| </p> | |
| <hr class="about-divider"> | |
| <div class="about-links"> | |
| <a class="about-link" href="https://m-aofficialmusic.org/" target="_blank"> | |
| <span class="about-link-icon">π</span> m-aofficialmusic.org | |
| </a> | |
| <a class="about-link" href="https://open.spotify.com/artist/5v8fSIV6a2nJwHNHWHHHa5" target="_blank"> | |
| <span class="about-link-icon">π΅</span> Spotify | |
| </a> | |
| <a class="about-link" href="https://www.youtube.com/channel/UCFuuOTOczyOkD5aUUBfKy0A" target="_blank"> | |
| <span class="about-link-icon">βΆ</span> YouTube | |
| </a> | |
| <a class="about-link" href="https://www.instagram.com/maofficialmusique" target="_blank"> | |
| <span class="about-link-icon">β</span> Instagram | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Scrollbar --> | |
| <div class="scrollbar"> | |
| <div class="sb-arrow">β</div> | |
| <div class="sb-track"><div class="sb-thumb"></div></div> | |
| <div class="sb-arrow">βΊ</div> | |
| </div> | |
| </div> | |
| <script> | |
| const PROMPTS = [ | |
| "lo-fi hip hop with rain and vinyl crackle", | |
| "epic orchestral film score with choir", | |
| "happy 8-bit chiptune adventure", | |
| "dark minimal techno with heavy bass", | |
| "smooth jazz piano trio late night", | |
| "acoustic folk guitar campfire song", | |
| "ambient drone meditation", | |
| "upbeat synthpop 80s dance floor", | |
| "heavy metal guitar riff", | |
| "bossa nova guitar and piano", | |
| "deep house groove with soft pads", | |
| "funky slap bass groove", | |
| "melancholic piano ballad", | |
| "tropical reggaeton with steel drums", | |
| "blues harmonica and acoustic guitar", | |
| ]; | |
| const TAGS = ["lo-fi hip hop", "ambient", "jazz piano", "techno", "orchestral", "chiptune", "folk guitar", "EDM"]; | |
| (function() { | |
| const row = document.getElementById('tagRow'); | |
| TAGS.forEach(t => { | |
| const el = document.createElement('span'); | |
| el.className = 'tag'; el.textContent = t; | |
| el.onclick = () => document.getElementById('desc').value = t; | |
| row.appendChild(el); | |
| }); | |
| })(); | |
| let currentBlob = null, history = []; | |
| function show(page) { | |
| document.querySelectorAll('.page').forEach(p => p.classList.remove('active')); | |
| document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active')); | |
| document.getElementById('page-' + page).classList.add('active'); | |
| document.getElementById('nav-' + page).classList.add('active'); | |
| } | |
| function randomPrompt() { | |
| document.getElementById('desc').value = PROMPTS[Math.floor(Math.random() * PROMPTS.length)]; | |
| } | |
| async function generate() { | |
| const desc = document.getElementById('desc').value.trim(); | |
| const dur = parseInt(document.getElementById('dur').value); | |
| const model = document.getElementById('modelSel').value; | |
| if (!desc) { alert('Please enter a description!'); return; } | |
| const btn = document.getElementById('genBtn'); | |
| const progressWrap = document.getElementById('progressWrap'); | |
| const progressLbl = document.getElementById('progressLbl'); | |
| const resultBox = document.getElementById('resultBox'); | |
| btn.disabled = true; | |
| progressWrap.style.display = 'block'; | |
| resultBox.style.display = 'none'; | |
| progressLbl.textContent = 'Loading model & generating⦠(first run may take a few minutes)'; | |
| try { | |
| const res = await fetch('/api/generate', { | |
| method: 'POST', headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ description: desc, duration: dur, model }) | |
| }); | |
| if (!res.ok) { | |
| let msg = 'Generation failed'; | |
| try { msg = (await res.json()).error || msg; } catch(_) {} | |
| throw new Error(msg); | |
| } | |
| const blob = await res.blob(); | |
| currentBlob = blob; | |
| document.getElementById('player').src = URL.createObjectURL(blob); | |
| const ts = new Date().toLocaleTimeString(); | |
| document.getElementById('resultMeta').textContent = '"' + desc + '" Β· ' + dur + 's Β· ' + ts; | |
| resultBox.style.display = 'block'; | |
| progressLbl.textContent = 'Complete!'; | |
| history.unshift({ desc, dur, url: URL.createObjectURL(blob), ts }); | |
| renderHistory(); | |
| } catch(e) { | |
| progressLbl.textContent = 'Error: ' + e.message; | |
| } finally { | |
| btn.disabled = false; | |
| setTimeout(() => { progressWrap.style.display = 'none'; }, 3000); | |
| } | |
| } | |
| function dlSample() { | |
| if (!currentBlob) return; | |
| const a = document.createElement('a'); | |
| a.href = URL.createObjectURL(currentBlob); | |
| const slug = document.getElementById('desc').value.replace(/[^a-z0-9 ]/gi,'').trim().replace(/\s+/g,'_').toLowerCase().slice(0,40); | |
| a.download = 'sample_' + slug + '.wav'; a.click(); | |
| } | |
| function clearResult() { | |
| document.getElementById('resultBox').style.display = 'none'; | |
| document.getElementById('player').src = ''; currentBlob = null; | |
| } | |
| function renderHistory() { | |
| const el = document.getElementById('historyList'); | |
| if (!history.length) { el.innerHTML = '<div class="history-empty">No samples generated yet.</div>'; return; } | |
| el.innerHTML = history.map((item, i) => ` | |
| <div class="h-item"> | |
| <span class="h-num">#${i+1}</span> | |
| <span class="h-desc">${item.desc}</span> | |
| <span class="h-dur">${item.dur}s Β· ${item.ts}</span> | |
| <div class="h-audio"><audio controls src="${item.url}"></audio></div> | |
| <a href="${item.url}" download="sample_${i+1}.wav"><button class="btn ghost" style="font-size:11px;padding:2px 8px;">β</button></a> | |
| </div>`).join(''); | |
| } | |
| document.getElementById('desc').addEventListener('keydown', e => { if (e.key === 'Enter') generate(); }); | |
| </script> | |
| </body> | |
| </html> | |