Skip to content

Instantly share code, notes, and snippets.

@ggorlen
Created April 7, 2025 17:43
  • Select an option

Select an option

Revisions

  1. ggorlen created this gist Apr 7, 2025.
    97 changes: 97 additions & 0 deletions mic-wav.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,97 @@
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>Record Mic to WAV</title>
    </head>
    <body>
    <button id="record">Record Mic</button>

    <script>
    document.getElementById("record").onclick = async () => {
    const stream = await navigator.mediaDevices.getUserMedia({
    audio: true,
    });

    const mediaRecorder = new MediaRecorder(stream);
    const chunks = [];

    mediaRecorder.ondataavailable = (e) => {
    if (e.data.size > 0) {
    chunks.push(e.data);
    }
    };

    mediaRecorder.onstop = async () => {
    const blob = new Blob(chunks, { type: "audio/webm" });

    // Decode audio data
    const arrayBuffer = await blob.arrayBuffer();
    const audioCtx = new AudioContext();
    const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);

    // Convert to WAV
    const wavBlob = encodeWAV(audioBuffer);

    // Download
    const url = URL.createObjectURL(wavBlob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "mic.wav";
    a.click();
    URL.revokeObjectURL(url);
    };

    mediaRecorder.start();

    // Stop after 5 seconds
    setTimeout(() => mediaRecorder.stop(), 5000);
    };

    function encodeWAV(audioBuffer) {
    const numChannels = audioBuffer.numberOfChannels;
    const sampleRate = audioBuffer.sampleRate;
    const numSamples = audioBuffer.length;
    const format = 1; // PCM
    const bitsPerSample = 16;
    const blockAlign = (numChannels * bitsPerSample) / 8;
    const byteRate = sampleRate * blockAlign;
    const wavBuffer = new ArrayBuffer(44 + numSamples * blockAlign);
    const view = new DataView(wavBuffer);

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

    writeString(view, 0, "RIFF");
    view.setUint32(4, 36 + numSamples * blockAlign, true);
    writeString(view, 8, "WAVE");
    writeString(view, 12, "fmt ");
    view.setUint32(16, 16, true); // Subchunk1Size
    view.setUint16(20, format, true); // AudioFormat
    view.setUint16(22, numChannels, true);
    view.setUint32(24, sampleRate, true);
    view.setUint32(28, byteRate, true);
    view.setUint16(32, blockAlign, true);
    view.setUint16(34, bitsPerSample, true);
    writeString(view, 36, "data");
    view.setUint32(40, numSamples * blockAlign, true);

    // Interleave and write PCM samples
    let offset = 44;
    for (let i = 0; i < numSamples; i++) {
    for (let ch = 0; ch < numChannels; ch++) {
    let sample = audioBuffer.getChannelData(ch)[i];
    sample = Math.max(-1, Math.min(1, sample));
    view.setInt16(offset, sample * 0x7fff, true);
    offset += 2;
    }
    }

    return new Blob([view], { type: "audio/wav" });
    }
    </script>
    </body>
    </html>
    50 changes: 50 additions & 0 deletions sine-ogx.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>Sine Wave to WAV</title>
    </head>
    <body>
    <button id="start">Start Recording</button>
    <a id="download" style="pointer-events: none; color: gray;">Download WAV</a>

    <script>
    let audioCtx, oscillator, mediaRecorder, audioChunks = [];

    document.getElementById('start').onclick = async () => {
    // Reset chunks and set up audio context
    audioChunks = [];
    audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    oscillator = audioCtx.createOscillator();
    oscillator.type = 'sine';
    oscillator.frequency.setValueAtTime(440, audioCtx.currentTime); // A4

    const dest = audioCtx.createMediaStreamDestination();
    oscillator.connect(dest);

    mediaRecorder = new MediaRecorder(dest.stream);
    mediaRecorder.ondataavailable = e => audioChunks.push(e.data);
    mediaRecorder.onstop = () => {
    const blob = new Blob(audioChunks, { type: 'audio/wav' }); // type may be ignored
    const url = URL.createObjectURL(blob);

    const downloadLink = document.getElementById('download');
    downloadLink.href = url;
    downloadLink.download = 'sine.wav';
    downloadLink.style.pointerEvents = 'auto';
    downloadLink.style.color = 'blue';
    downloadLink.textContent = 'Click to Download WAV';
    };

    mediaRecorder.start();
    oscillator.start();

    setTimeout(() => {
    oscillator.stop();
    mediaRecorder.stop();
    }, 2000); // Record for 2s
    };
    </script>
    </body>
    </html>

    68 changes: 68 additions & 0 deletions sine-wav.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Generate Sine WAV</title>
    </head>
    <body>
    <!-- these are all from chatgpt https://chatgpt.com/c/67f3f926-e0c0-8012-99e8-7b3341545b31 -->
    <button id="generate">Generate WAV</button>

    <script>
    function generateSineWaveWav({ duration = 2, frequency = 440, sampleRate = 44100, volume = 0.5 }) {
    const numSamples = duration * sampleRate;
    const buffer = new Array(numSamples);

    // Generate raw PCM samples
    for (let i = 0; i < numSamples; i++) {
    buffer[i] = volume * Math.sin(2 * Math.PI * frequency * (i / sampleRate));
    }

    // Convert to 16-bit PCM
    const wavBuffer = new ArrayBuffer(44 + numSamples * 2);
    const view = new DataView(wavBuffer);

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

    // WAV file header
    writeString(view, 0, 'RIFF');
    view.setUint32(4, 36 + numSamples * 2, true); // file size
    writeString(view, 8, 'WAVE');
    writeString(view, 12, 'fmt ');
    view.setUint32(16, 16, true); // subchunk1 size
    view.setUint16(20, 1, true); // audio format (1 = PCM)
    view.setUint16(22, 1, true); // channels
    view.setUint32(24, sampleRate, true);
    view.setUint32(28, sampleRate * 2, true); // byte rate
    view.setUint16(32, 2, true); // block align
    view.setUint16(34, 16, true); // bits per sample
    writeString(view, 36, 'data');
    view.setUint32(40, numSamples * 2, true);

    // Write samples
    let offset = 44;
    for (let i = 0; i < numSamples; i++) {
    const s = Math.max(-1, Math.min(1, buffer[i]));
    view.setInt16(offset, s * 0x7FFF, true);
    offset += 2;
    }

    return new Blob([view], { type: 'audio/wav' });
    }

    document.getElementById('generate').onclick = () => {
    const blob = generateSineWaveWav({ duration: 2, frequency: 440 });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'sine.wav';
    a.click();
    URL.revokeObjectURL(url);
    };
    </script>
    </body>
    </html>