Created
April 7, 2025 17:43
-
-
Save ggorlen/006141af3c2b657f4932f0b2a55e7ab0 to your computer and use it in GitHub Desktop.
Converting web audio to wav
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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