From b37dd389a62d1258fca11d0d47b062c73b371ecc Mon Sep 17 00:00:00 2001 From: b1rdmania <102524336+b1rdmania@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:52:49 +0000 Subject: [PATCH] chore: remove MP3 download UI Remove the Download MP3 button and all related client-side export logic. Made-with: Cursor --- index.html | 4 -- src/lamejs.d.ts | 7 --- src/main.ts | 149 ------------------------------------------------ ux-test.html | 83 --------------------------- 4 files changed, 243 deletions(-) delete mode 100644 src/lamejs.d.ts diff --git a/index.html b/index.html index 174ab6d..dd3a2ba 100644 --- a/index.html +++ b/index.html @@ -1222,7 +1222,6 @@
-
-
- v1.5 - Download MP3 button, offline rendering for faster exports -
v1.4 - 16-bit to 8-bit audio fixes, improved synthesis quality
diff --git a/src/lamejs.d.ts b/src/lamejs.d.ts deleted file mode 100644 index f022cc2..0000000 --- a/src/lamejs.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module 'lamejs' { - // `lamejs` ships without TypeScript types. - // We only need `Mp3Encoder` for client-side MP3 export. - export const Mp3Encoder: any; - const _default: any; - export default _default; -} diff --git a/src/main.ts b/src/main.ts index 0bfc992..dba4621 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,6 @@ import { MIDIParser } from './midi/MIDIParser'; import { SoundfontMIDIPlayer } from './synthesis/SoundfontMIDIPlayer'; import { getAudioContext, isAudioReady, peekAudioContext, unlockAudio } from './utils/audioUnlock'; import type { NoteEvent } from './types'; -import * as lamejs from 'lamejs'; class MotifApp { private motifEngine: MotifEngine; @@ -56,12 +55,9 @@ class MotifApp { private copyLinkBtn!: HTMLButtonElement; private shareToXBtn!: HTMLButtonElement; - private downloadMp3Btn!: HTMLButtonElement; private shareFallback!: HTMLElement; private shareFallbackInput!: HTMLInputElement; - private downloadMp3BtnLabel = 'Download MP3'; - // Embed snippet UI private embedSection: HTMLElement | null = null; private embedCodeEl: HTMLElement | null = null; @@ -133,8 +129,6 @@ class MotifApp { this.copyLinkBtn = document.getElementById('copyLinkBtn') as HTMLButtonElement; this.shareToXBtn = document.getElementById('shareToXBtn') as HTMLButtonElement; - this.downloadMp3Btn = document.getElementById('downloadMp3Btn') as HTMLButtonElement; - this.downloadMp3BtnLabel = (this.downloadMp3Btn.textContent || '').trim() || 'Download MP3'; this.shareFallback = document.getElementById('shareFallback')!; this.shareFallbackInput = document.getElementById('shareFallbackInput') as HTMLInputElement; @@ -195,7 +189,6 @@ class MotifApp { this.copyLinkBtn.addEventListener('click', () => void this.handleCopyLink()); this.shareToXBtn.addEventListener('click', () => void this.handleShareToX()); - this.downloadMp3Btn.addEventListener('click', () => void this.handleDownloadMp3()); // Embed snippet copy (may be disabled / not-live) this.copyEmbedBtn?.addEventListener('click', () => void this.copyEmbedSnippet()); @@ -824,146 +817,6 @@ class MotifApp { } } - private floatToInt16(input: Float32Array): Int16Array { - const out = new Int16Array(input.length); - for (let i = 0; i < input.length; i++) { - const s = Math.max(-1, Math.min(1, input[i])); - out[i] = s < 0 ? s * 0x8000 : s * 0x7fff; - } - return out; - } - - private async handleDownloadMp3(): Promise { - if (!this.hasGenerated || !this.currentMIDI) return; - - const result = this.searchResults[this.selectedResultIndex]; - const safeTitle = this.cleanSongTitle(result?.title || 'wario-synth').slice(0, 80) || 'wario-synth'; - const fileName = `${safeTitle}.mp3`; - let fileHandle: any = null; - let popup: Window | null = null; - - try { - this.downloadMp3Btn.disabled = true; - this.downloadMp3Btn.textContent = 'Rendering…'; - this.updateStatus('Rendering audio…'); - - // Capture user activation up-front when possible. - const maybeShowSaveFilePicker = (window as any).showSaveFilePicker; - if (typeof maybeShowSaveFilePicker === 'function') { - try { - fileHandle = await maybeShowSaveFilePicker({ - suggestedName: fileName, - types: [{ - description: 'MP3 audio', - accept: { 'audio/mpeg': ['.mp3'] }, - }], - }); - } catch (e: any) { - if (e?.name === 'AbortError') { - this.updateStatus('Download cancelled.'); - return; - } - } - } else { - // Fallback for browsers without file picker: pre-open a window from the click gesture. - popup = window.open('', '_blank'); - if (popup && !popup.closed) { - try { - popup.document.title = 'Preparing MP3'; - popup.document.body.innerHTML = '

Preparing MP3…

'; - } catch { - // ignore cross-window access issues - } - } - } - - // Stop any current playback - this.handleMotifStop(); - - // Render offline (full song) - const sampleRate = 44100; - const audioBuffer = await this.motifEngine.renderOffline( - this.currentMIDI.events, - 'procedural', - sampleRate - ); - - this.downloadMp3Btn.textContent = 'Encoding…'; - this.updateStatus('Encoding MP3…'); - - const Mp3Encoder = (lamejs as any)?.Mp3Encoder; - if (!Mp3Encoder) throw new Error('MP3 encoder not loaded'); - const channels = audioBuffer.numberOfChannels; - const kbps = 128; - const encoder = new (lamejs as any).Mp3Encoder(channels, sampleRate, kbps); - const mp3Chunks: BlobPart[] = []; - - // Get audio data - const leftData = audioBuffer.getChannelData(0); - const rightData = channels > 1 ? audioBuffer.getChannelData(1) : leftData; - const left = this.floatToInt16(leftData); - const right = this.floatToInt16(rightData); - - // Encode in 1152-sample frames - const frameSize = 1152; - for (let i = 0; i < left.length; i += frameSize) { - const l = left.subarray(i, i + frameSize); - const r = right.subarray(i, i + frameSize); - const buf = encoder.encodeBuffer(l, r); - if (buf && buf.length) mp3Chunks.push(buf); - } - - const tail = encoder.flush(); - if (tail && tail.length) mp3Chunks.push(tail); - - const blob = new Blob(mp3Chunks, { type: 'audio/mpeg' }); - if (fileHandle) { - const writable = await fileHandle.createWritable(); - await writable.write(blob); - await writable.close(); - this.updateStatus('MP3 downloaded!'); - setTimeout(() => this.updateStatus(''), 1500); - return; - } - - const url = URL.createObjectURL(blob); - let attemptedDownload = false; - try { - const a = document.createElement('a'); - a.href = url; - a.download = fileName; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - attemptedDownload = true; - } catch { - // fallback below - } - - if (popup && !popup.closed) { - popup.location.href = url; - if (attemptedDownload) { - popup.close(); - } - } else if (!attemptedDownload) { - window.open(url, '_blank'); - } - - if (attemptedDownload) { - this.updateStatus('MP3 downloaded!'); - setTimeout(() => this.updateStatus(''), 1500); - } else { - this.updateStatus('MP3 opened in a new tab. Save it from there.'); - } - setTimeout(() => URL.revokeObjectURL(url), 60_000); - } catch (e) { - this.updateStatus(`MP3 failed: ${e instanceof Error ? e.message : 'Unknown error'}`); - } finally { - this.downloadMp3Btn.disabled = false; - this.downloadMp3Btn.textContent = this.downloadMp3BtnLabel; - } - } - private setState(state: 'idle' | 'results' | 'selected' | 'generated'): void { // results - keep visible in all states except idle so user can pick a different source const hasResults = state === 'results' || state === 'selected' || state === 'generated'; @@ -979,8 +832,6 @@ class MotifApp { this.copyLinkBtn.disabled = !(state === 'generated' && this.hasGenerated); this.shareToXBtn.style.display = state === 'generated' ? 'inline-block' : 'none'; this.shareToXBtn.disabled = !(state === 'generated' && this.hasGenerated); - this.downloadMp3Btn.style.display = state === 'generated' ? 'inline-block' : 'none'; - this.downloadMp3Btn.disabled = !(state === 'generated' && this.hasGenerated); // Hide share fallback when state changes this.shareFallback.style.display = 'none'; diff --git a/ux-test.html b/ux-test.html index e756404..f50b2f2 100644 --- a/ux-test.html +++ b/ux-test.html @@ -512,9 +512,6 @@ -
- -
@@ -562,7 +559,6 @@ const stopBtn = document.getElementById('stopBtn'); const copyLinkBtn = document.getElementById('copyLinkBtn'); const shareXBtn = document.getElementById('shareXBtn'); - const downloadMp3Btn = document.getElementById('downloadMp3Btn'); const shareHint = document.getElementById('shareHint'); const searchBtn = document.getElementById('searchBtn'); const songInput = document.getElementById('songInput'); @@ -718,7 +714,6 @@ playBtn.disabled = false; copyLinkBtn.disabled = false; shareXBtn.disabled = false; - downloadMp3Btn.disabled = false; setStatus('Ready to play'); shareHint.textContent = ''; @@ -761,83 +756,6 @@ stopVisualizer(); } - function downloadBlob(blob, filename) { - const a = document.createElement('a'); - const url = URL.createObjectURL(blob); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - setTimeout(() => URL.revokeObjectURL(url), 1000); - } - - function floatToInt16(input) { - const out = new Int16Array(input.length); - for (let i = 0; i < input.length; i++) { - const s = Math.max(-1, Math.min(1, input[i])); - out[i] = s < 0 ? s * 0x8000 : s * 0x7fff; - } - return out; - } - - async function downloadMp3() { - if (!currentEvents) return; - try { - downloadMp3Btn.disabled = true; - shareHint.textContent = 'Rendering audio…'; - - // Stop any current playback first - stopPlayback(); - - // Ensure Motif engine exists - if (!motifEngine) motifEngine = new MotifEngine(); - - // Render offline (faster than real-time!) - const sampleRate = 44100; - const audioBuffer = await motifEngine.renderOffline(currentEvents, 'procedural', sampleRate); - - shareHint.textContent = 'Encoding MP3…'; - - // Use lamejs from CDN (global) - const Mp3Encoder = window.lamejs?.Mp3Encoder; - if (!Mp3Encoder) throw new Error('MP3 encoder not available - lamejs not loaded'); - - const channels = audioBuffer.numberOfChannels; - const kbps = 128; - const encoder = new Mp3Encoder(channels, sampleRate, kbps); - const mp3Chunks = []; - - // Get audio data from buffer - const leftData = audioBuffer.getChannelData(0); - const rightData = channels > 1 ? audioBuffer.getChannelData(1) : leftData; - const left = floatToInt16(leftData); - const right = floatToInt16(rightData); - - // Encode in 1152-sample frames - const frameSize = 1152; - for (let i = 0; i < left.length; i += frameSize) { - const l = left.subarray(i, i + frameSize); - const r = right.subarray(i, i + frameSize); - const buf = encoder.encodeBuffer(l, r); - if (buf && buf.length) mp3Chunks.push(buf); - } - - const tail = encoder.flush(); - if (tail && tail.length) mp3Chunks.push(tail); - - const blob = new Blob(mp3Chunks, { type: 'audio/mpeg' }); - const safeTitle = cleanTitleForShare(currentTitle || 'motif').slice(0, 80) || 'motif'; - downloadBlob(blob, `${safeTitle}.mp3`); - shareHint.textContent = 'MP3 downloaded.'; - window.setTimeout(() => (shareHint.textContent = ''), 1400); - } catch (e) { - shareHint.textContent = `MP3 failed: ${e && e.message ? e.message : 'Unknown error'}`; - console.error('MP3 download error:', e); - } finally { - downloadMp3Btn.disabled = false; - } - } async function copyShareLink() { if (!currentMidiUrl) return; @@ -958,7 +876,6 @@ stopBtn.addEventListener('click', stopPlayback); copyLinkBtn.addEventListener('click', () => void copyShareLink()); shareXBtn.addEventListener('click', shareOnX); - downloadMp3Btn.addEventListener('click', () => downloadMp3()); // Init createBars();