Skip to content

Instantly share code, notes, and snippets.

@ggorlen
Created April 7, 2025 17:43
Show Gist options
  • Save ggorlen/006141af3c2b657f4932f0b2a55e7ab0 to your computer and use it in GitHub Desktop.
Save ggorlen/006141af3c2b657f4932f0b2a55e7ab0 to your computer and use it in GitHub Desktop.
Converting web audio to wav
<!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>
<!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>
<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment