Spaces:
Running
Running
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Double Jeopardy Sound Maker</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| background: #111; | |
| color: white; | |
| text-align: center; | |
| padding: 20px; | |
| } | |
| input { | |
| margin: 5px; | |
| padding: 5px; | |
| } | |
| button { | |
| margin-top: 10px; | |
| padding: 10px 20px; | |
| font-size: 16px; | |
| background: gold; | |
| border: none; | |
| cursor: pointer; | |
| } | |
| button:hover { | |
| background: orange; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🎶 Double Jeopardy Sound Maker</h1> | |
| <p>Pick your fanfare notes and durations, then click Play or Download!</p> | |
| <div> | |
| <label>Note 1 (Hz): <input id="note1" type="number" value="440"></label> | |
| <label>Duration (ms): <input id="dur1" type="number" value="300"></label> | |
| </div> | |
| <div> | |
| <label>Note 2 (Hz): <input id="note2" type="number" value="660"></label> | |
| <label>Duration (ms): <input id="dur2" type="number" value="300"></label> | |
| </div> | |
| <div> | |
| <label>Note 3 (Hz): <input id="note3" type="number" value="880"></label> | |
| <label>Duration (ms): <input id="dur3" type="number" value="300"></label> | |
| </div> | |
| <button onclick="playFanfare()">▶ Play Fanfare</button> | |
| <button onclick="downloadMP3()">💾 Download MP3</button> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.0/lame.min.js"></script> | |
| <script> | |
| function generateAudioBuffer(notes) { | |
| const sampleRate = 44100; | |
| const audioCtx = new OfflineAudioContext(1, sampleRate * 5, sampleRate); | |
| let time = 0; | |
| notes.forEach(([freq, dur]) => { | |
| const osc = audioCtx.createOscillator(); | |
| const gain = audioCtx.createGain(); | |
| osc.type = 'square'; | |
| osc.frequency.setValueAtTime(freq, time); | |
| gain.gain.setValueAtTime(0.3, time); | |
| osc.connect(gain); | |
| gain.connect(audioCtx.destination); | |
| osc.start(time); | |
| osc.stop(time + dur / 1000); | |
| time += dur / 1000 + 0.1; // gap between notes | |
| }); | |
| return audioCtx.startRendering(); | |
| } | |
| function playFanfare() { | |
| const notes = getNotes(); | |
| const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | |
| let time = audioCtx.currentTime; | |
| notes.forEach(([freq, dur]) => { | |
| const osc = audioCtx.createOscillator(); | |
| const gain = audioCtx.createGain(); | |
| osc.type = 'square'; | |
| osc.frequency.setValueAtTime(freq, time); | |
| gain.gain.setValueAtTime(0.3, time); | |
| osc.connect(gain); | |
| gain.connect(audioCtx.destination); | |
| osc.start(time); | |
| osc.stop(time + dur / 1000); | |
| time += dur / 1000 + 0.1; | |
| }); | |
| } | |
| function getNotes() { | |
| return [ | |
| [parseFloat(document.getElementById('note1').value), parseInt(document.getElementById('dur1').value)], | |
| [parseFloat(document.getElementById('note2').value), parseInt(document.getElementById('dur2').value)], | |
| [parseFloat(document.getElementById('note3').value), parseInt(document.getElementById('dur3').value)] | |
| ]; | |
| } | |
| async function downloadMP3() { | |
| const notes = getNotes(); | |
| const renderedBuffer = await generateAudioBuffer(notes); | |
| const channelData = renderedBuffer.getChannelData(0); | |
| const mp3encoder = new lamejs.Mp3Encoder(1, renderedBuffer.sampleRate, 128); | |
| const samples = new Int16Array(channelData.length); | |
| for (let i = 0; i < channelData.length; i++) { | |
| samples[i] = channelData[i] * 32767.5; | |
| } | |
| const mp3Data = []; | |
| const chunkSize = 1152; | |
| for (let i = 0; i < samples.length; i += chunkSize) { | |
| const sampleChunk = samples.subarray(i, i + chunkSize); | |
| const mp3buf = mp3encoder.encodeBuffer(sampleChunk); | |
| if (mp3buf.length > 0) mp3Data.push(mp3buf); | |
| } | |
| const endBuf = mp3encoder.flush(); | |
| if (endBuf.length > 0) mp3Data.push(endBuf); | |
| const blob = new Blob(mp3Data, { type: 'audio/mp3' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'daily-double.mp3'; | |
| a.click(); | |
| } | |
| </script> | |
| </body> | |
| </html> | |