<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Simple Live Audio Editor with Download</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
  body { font-family: system-ui, -apple-system, Roboto, Arial; margin: 20px; color:#111 }
  h1 { font-size: 20px; margin-bottom: 8px; }
  .controls { display:flex; flex-wrap:wrap; gap:12px; }
  .control { min-width: 220px; flex:1 1 240px; background:#f7f7f7; padding:10px; border-radius:6px; box-shadow:0 1px 2px rgba(0,0,0,0.04); }
  label { display:block; font-size:13px; margin-bottom:6px; color:#333 }
  input[type="range"] { width:100%; }
  .row { display:flex; gap:8px; align-items:center; }
  .val { min-width:60px; text-align:right; font-family:monospace; font-size:13px; color:#222 }
  .topbar { display:flex; gap:8px; align-items:center; margin-bottom:12px; flex-wrap: wrap; }
  button { padding:8px 12px; border-radius:6px; border:1px solid #ccc; background:white; cursor:pointer; }
  input[type=file] { display:block; }
  .small { font-size:13px; color:#666; margin-top:8px; }
  footer { margin-top:14px; color:#666; font-size:13px; }
</style>
</head>
<body>
  <h1>Simple Live Audio Editor</h1>
  <div class="topbar">
    <input id="file" type="file" accept="audio/*">
    <button id="play">Play</button>
    <button id="pause" disabled>Pause</button>
    <button id="stop" disabled>Stop</button>
    <button id="download" disabled>Download WAV</button>
    <div id="info" style="margin-left:8px;font-size:13px;color:#444"></div>
  </div>

  <div class="controls">
    <div class="control">
      <label>High-pass: <span id="hp-val" class="val">20 Hz</span></label>
      <input id="hp" type="range" min="20" max="1200" value="20">
    </div>
    <div class="control">
      <label>Low-pass: <span id="lp-val" class="val">20000 Hz</span></label>
      <input id="lp" type="range" min="500" max="20000" value="20000">
    </div>
    <div class="control">
      <label>Bass (low shelf): <span id="bass-val" class="val">0 dB</span></label>
      <input id="bass" type="range" min="-15" max="15" value="0" step="0.5">
    </div>
    <div class="control">
      <label>Treble (high shelf): <span id="treb-val" class="val">0 dB</span></label>
      <input id="treb" type="range" min="-15" max="15" value="0" step="0.5">
    </div>
    <div class="control">
      <label>Volume: <span id="vol-val" class="val">1.00×</span></label>
      <input id="vol" type="range" min="0" max="2" value="1" step="0.01">
    </div>
    <div class="control">
      <label>Reverb mix: <span id="rv-mix-val" class="val">0%</span></label>
      <input id="rv-mix" type="range" min="0" max="100" value="0">
      <label style="margin-top:10px">Reverb decay: <span id="rv-decay-val" class="val">1.2 s</span></label>
      <input id="rv-decay" type="range" min="0.1" max="6" value="1.2" step="0.1">
    </div>
  </div>

  <footer>Load audio, adjust sliders live, and click Download to export.</footer>

<script>
(async function(){
  const fileEl = document.getElementById('file');
  const playBtn = document.getElementById('play');
  const pauseBtn = document.getElementById('pause');
  const stopBtn = document.getElementById('stop');
  const downloadBtn = document.getElementById('download');
  const info = document.getElementById('info');

  const hp = document.getElementById('hp'), hpVal = document.getElementById('hp-val');
  const lp = document.getElementById('lp'), lpVal = document.getElementById('lp-val');
  const bass = document.getElementById('bass'), bassVal = document.getElementById('bass-val');
  const treb = document.getElementById('treb'), trebVal = document.getElementById('treb-val');
  const vol = document.getElementById('vol'), volVal = document.getElementById('vol-val');
  const rvMix = document.getElementById('rv-mix'), rvMixVal = document.getElementById('rv-mix-val');
  const rvDecay = document.getElementById('rv-decay'), rvDecayVal = document.getElementById('rv-decay-val');

  let audioCtx = null, buffer = null, source = null;
  let startTime = 0, pausedAt = 0, isPlaying = false;
  let hpFilter, lpFilter, bassShelf, trebleShelf, dryGain, wetGain, convolver, overallGain;

  function ensureAudioContext() {
    if (!audioCtx) {
      audioCtx = new (window.AudioContext || window.webkitAudioContext)();
      setupNodes();
    } else if (audioCtx.state === 'suspended') {
      audioCtx.resume();
    }
  }

  function setupNodes() {
    hpFilter = audioCtx.createBiquadFilter(); hpFilter.type = 'highpass'; hpFilter.frequency.value = hp.value;
    lpFilter = audioCtx.createBiquadFilter(); lpFilter.type = 'lowpass'; lpFilter.frequency.value = lp.value;
    bassShelf = audioCtx.createBiquadFilter(); bassShelf.type = 'lowshelf'; bassShelf.frequency.value = 200; bassShelf.gain.value = bass.value;
    trebleShelf = audioCtx.createBiquadFilter(); trebleShelf.type = 'highshelf'; trebleShelf.frequency.value = 3500; trebleShelf.gain.value = treb.value;
    convolver = audioCtx.createConvolver();
    dryGain = audioCtx.createGain(); wetGain = audioCtx.createGain(); overallGain = audioCtx.createGain();
    dryGain.gain.value = 1; wetGain.gain.value = 0; overallGain.gain.value = vol.value;
    convolver.buffer = createReverbImpulse(audioCtx, rvDecay.value);
  }

  function createReverbImpulse(ctx, decay) {
    const rate = ctx.sampleRate, length = Math.floor(rate * decay);
    const buf = ctx.createBuffer(2, length, rate);
    for (let c = 0; c < 2; c++) {
      const data = buf.getChannelData(c);
      for (let i = 0; i < length; i++) {
        data[i] = (Math.random()*2-1)*Math.pow(1 - i/length, 3)*Math.exp(-i/(rate*decay));
      }
    }
    return buf;
  }

  function createSource(offset=0) {
    if (!buffer) return null;
    const s = audioCtx.createBufferSource();
    s.buffer = buffer;
    s.onended = ()=>{isPlaying=false; pausedAt=0; playBtn.disabled=false; pauseBtn.disabled=true; stopBtn.disabled=true;};
    s.connect(hpFilter);
    hpFilter.connect(lpFilter);
    lpFilter.connect(bassShelf);
    bassShelf.connect(trebleShelf);
    trebleShelf.connect(dryGain);
    trebleShelf.connect(convolver);
    convolver.connect(wetGain);
    dryGain.connect(overallGain);
    wetGain.connect(overallGain);
    overallGain.connect(audioCtx.destination);
    return s;
  }

  fileEl.addEventListener('change', async () => {
    const f = fileEl.files[0]; if (!f) return;
    info.textContent = 'Loading...'; ensureAudioContext();
    const array = await f.arrayBuffer();
    buffer = await audioCtx.decodeAudioData(array);
    info.textContent = `${f.name} — ${Math.round(buffer.duration)}s`;
    pausedAt = 0; downloadBtn.disabled = false;
  });

  playBtn.addEventListener('click', async ()=>{
    if (!buffer) return alert('Load a file first');
    ensureAudioContext();
    if (!isPlaying) {
      source = createSource(pausedAt);
      startTime = audioCtx.currentTime - pausedAt;
      source.start(0, pausedAt);
      isPlaying = true;
      playBtn.disabled = true; pauseBtn.disabled = false; stopBtn.disabled = false;
    }
  });
  pauseBtn.addEventListener('click', ()=>{
    if (!isPlaying) return;
    pausedAt = audioCtx.currentTime - startTime;
    if (source) source.stop();
    isPlaying=false; playBtn.disabled=false; pauseBtn.disabled=true;
  });
  stopBtn.addEventListener('click', ()=>{
    if (source) source.stop();
    pausedAt=0; isPlaying=false; playBtn.disabled=false; pauseBtn.disabled=true; stopBtn.disabled=true;
  });

  // slider handlers
  hp.oninput = ()=>{hpVal.textContent = hp.value+' Hz'; if(hpFilter) hpFilter.frequency.value=hp.value;};
  lp.oninput = ()=>{lpVal.textContent = lp.value+' Hz'; if(lpFilter) lpFilter.frequency.value=lp.value;};
  bass.oninput = ()=>{bassVal.textContent = bass.value+' dB'; if(bassShelf) bassShelf.gain.value=bass.value;};
  treb.oninput = ()=>{trebVal.textContent = treb.value+' dB'; if(trebleShelf) trebleShelf.gain.value=treb.value;};
  vol.oninput = ()=>{volVal.textContent = parseFloat(vol.value).toFixed(2)+'×'; if(overallGain) overallGain.gain.value=vol.value;};
  rvMix.oninput = ()=>{rvMixVal.textContent = rvMix.value+'%'; if(dryGain&&wetGain){let m=rvMix.value/100; dryGain.gain.value=1-m; wetGain.gain.value=m;}};
  rvDecay.oninput = ()=>{rvDecayVal.textContent = rvDecay.value+' s'; if(convolver) convolver.buffer=createReverbImpulse(audioCtx, rvDecay.value);};

  // ---- Download ----
  downloadBtn.addEventListener('click', async ()=>{
    if (!buffer) return;
    const duration = buffer.duration;
    const offline = new OfflineAudioContext(buffer.numberOfChannels, buffer.sampleRate*duration, buffer.sampleRate);

    // nodes
    const src = offline.createBufferSource();
    src.buffer = buffer;

    const hpF = offline.createBiquadFilter(); hpF.type='highpass'; hpF.frequency.value=hp.value;
    const lpF = offline.createBiquadFilter(); lpF.type='lowpass'; lpF.frequency.value=lp.value;
    const bassF = offline.createBiquadFilter(); bassF.type='lowshelf'; bassF.frequency.value=200; bassF.gain.value=bass.value;
    const trebF = offline.createBiquadFilter(); trebF.type='highshelf'; trebF.frequency.value=3500; trebF.gain.value=treb.value;

    const conv = offline.createConvolver(); conv.buffer = createReverbImpulse(offline, rvDecay.value);
    const dry = offline.createGain(); const wet = offline.createGain();
    const gain = offline.createGain(); gain.gain.value = vol.value;

    const mix = rvMix.value/100; dry.gain.value=1-mix; wet.gain.value=mix;

    src.connect(hpF); hpF.connect(lpF); lpF.connect(bassF); bassF.connect(trebF);
    trebF.connect(dry); trebF.connect(conv);
    conv.connect(wet);
    dry.connect(gain); wet.connect(gain); gain.connect(offline.destination);

    src.start();
    const rendered = await offline.startRendering();

    const wav = bufferToWav(rendered);
    const blob = new Blob([wav], {type:'audio/wav'});
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url; a.download = (fileEl.files[0]?.name?.replace(/\.[^/.]+$/, '') || 'output') + '_processed.wav';
    a.click();
    URL.revokeObjectURL(url);
  });

  function bufferToWav(buffer){
    const numOfChan = buffer.numberOfChannels, length = buffer.length * numOfChan * 2 + 44;
    const bufferArr = new ArrayBuffer(length), view = new DataView(bufferArr);
    const channels = [], sampleRate = buffer.sampleRate;
    let offset = 0, pos = 0;

    function writeString(s){ for (let i=0;i<s.length;i++) view.setUint8(pos+i,s.charCodeAt(i)); pos+=s.length; }

    writeString('RIFF'); view.setUint32(pos, length - 8, true); pos+=4;
    writeString('WAVE'); writeString('fmt '); view.setUint32(pos, 16, true); pos+=4;
    view.setUint16(pos, 1, true); pos+=2; view.setUint16(pos, numOfChan, true); pos+=2;
    view.setUint32(pos, sampleRate, true); pos+=4; view.setUint32(pos, sampleRate * 2 * numOfChan, true); pos+=4;
    view.setUint16(pos, numOfChan * 2, true); pos+=2; view.setUint16(pos, 16, true); pos+=2;
    writeString('data'); view.setUint32(pos, length - pos - 4, true); pos+=4;

    for (let i = 0; i < numOfChan; i++) channels.push(buffer.getChannelData(i));
    while (pos < length) {
      for (let i = 0; i < numOfChan; i++) {
        let sample = Math.max(-1, Math.min(1, channels[i][offset]));
        view.setInt16(pos, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);
        pos += 2;
      }
      offset++;
    }
    return bufferArr;
  }

})();
</script>
</body>
</html>
/* Add your styles here */

// Add your code here