Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/AilaScott/Riverwriter. Riverwriter- Focused Songwriting! riverwriter.neocities.org
music songwriting writing
Something went wrong. Try again.
6.2 kB · 214 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215export function createRecorder(container, onRecordingChange) { const recBtn = container.querySelector('#rec-btn'); const playBtn = container.querySelector('#play-btn'); const dlBtn = container.querySelector('#dl-btn'); const playback = container.querySelector('#record-playback'); const waveform = container.querySelector('#record-waveform'); const bars = waveform.querySelectorAll('span'); const playbackBar = container.querySelector('#playback-bar'); const playbackTime = container.querySelector('#playback-time'); const playbackFill = container.querySelector('#playback-fill');
let mediaRecorder = null; let stream = null; let chunks = []; let recording = false; let currentBlob = null; let audioContext = null; let analyser = null; let animFrameId = null; let dataArray = null;
function updateControlStates() { const hasBlob = currentBlob !== null; playBtn.disabled = !hasBlob; if (hasBlob) { playBtn.classList.add('enabled'); dlBtn.disabled = false; } else { playBtn.classList.remove('enabled'); dlBtn.disabled = true; } }
function draw() { analyser.getByteFrequencyData(dataArray); const binSize = dataArray.length / bars.length; for (let i = 0; i < bars.length; i++) { let sum = 0; for (let j = 0; j < binSize; j++) { sum += dataArray[i * binSize + j]; } const avg = sum / binSize; const height = Math.max(4, (avg / 255) * 40); bars[i].style.height = height + 'px'; } animFrameId = requestAnimationFrame(draw); }
function resetWaveform() { for (const bar of bars) { bar.style.height = '4px'; } }
function formatTime(secs) { const m = Math.floor(secs / 60); const s = Math.floor(secs % 60).toString().padStart(2, '0'); return `${m}:${s}`; }
function updatePlaybackUI() { if (!playback.duration) return; const cur = playback.currentTime; const dur = playback.duration; playbackTime.textContent = `${formatTime(cur)} / ${formatTime(dur)}`; playbackFill.style.width = `${(cur / dur) * 100}%`; }
function setupPlaybackEvents() { playback.addEventListener('loadedmetadata', updatePlaybackUI); playback.addEventListener('timeupdate', updatePlaybackUI); playback.addEventListener('ended', () => { playback.currentTime = 0; updatePlaybackUI(); }); }
async function startRecording() { try { stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); chunks = [];
audioContext = new AudioContext(); const source = audioContext.createMediaStreamSource(stream); analyser = audioContext.createAnalyser(); analyser.fftSize = 64; dataArray = new Uint8Array(analyser.frequencyBinCount); source.connect(analyser);
mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) chunks.push(e.data); };
mediaRecorder.onstop = () => { currentBlob = new Blob(chunks, { type: 'audio/webm' }); const url = URL.createObjectURL(currentBlob); playback.src = url; playbackBar.style.display = 'flex'; playbackTime.textContent = '0:00 / 0:00'; playbackFill.style.width = '0%'; setupPlaybackEvents(); updateControlStates();
stream.getTracks().forEach(t => t.stop()); stream = null;
if (onRecordingChange) onRecordingChange(currentBlob); };
mediaRecorder.start(); recording = true; recBtn.textContent = '■'; recBtn.classList.add('active'); playBtn.disabled = true; dlBtn.disabled = true; playbackBar.style.display = 'none'; draw(); } catch (err) { console.warn('Recording error:', err); } }
function stopRecording() { if (animFrameId) { cancelAnimationFrame(animFrameId); animFrameId = null; } if (audioContext) { audioContext.close(); audioContext = null; } analyser = null; dataArray = null; if (mediaRecorder && mediaRecorder.state !== 'inactive') { mediaRecorder.stop(); } recording = false; recBtn.textContent = '●'; recBtn.classList.remove('active'); resetWaveform(); }
function toggleRecording() { if (recording) { stopRecording(); } else { startRecording(); } }
function playRecording() { if (playback.src) { playback.currentTime = 0; playback.play().catch(() => {}); } }
function downloadRecording() { if (!currentBlob) return; const url = URL.createObjectURL(currentBlob); const a = document.createElement('a'); a.href = url; a.download = 'recording.webm'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }
recBtn.addEventListener('click', toggleRecording); playBtn.addEventListener('click', playRecording); dlBtn.addEventListener('click', downloadRecording);
return { isRecording: () => recording, getBlob: () => currentBlob, hasBlob: () => currentBlob !== null, loadBlob(blob) { if (currentBlob) { URL.revokeObjectURL(playback.src); } currentBlob = blob || null; if (currentBlob) { const url = URL.createObjectURL(currentBlob); playback.src = url; playbackBar.style.display = 'flex'; playbackTime.textContent = '0:00 / 0:00'; playbackFill.style.width = '0%'; setupPlaybackEvents(); } else { playback.removeAttribute('src'); playbackBar.style.display = 'none'; playbackTime.textContent = ''; playbackFill.style.width = '0%'; } updateControlStates(); }, clearBlob() { if (playback.src) { URL.revokeObjectURL(playback.src); } playback.removeAttribute('src'); currentBlob = null; playbackBar.style.display = 'none'; playbackTime.textContent = ''; playbackFill.style.width = '0%'; updateControlStates(); }, setDownloadName(name) { dlBtn._name = name; }, };}