Skip to content

Instantly share code, notes, and snippets.

@literallylara
Last active April 27, 2022 18:52
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save literallylara/7ece1983fab47365108c47119afb51c7 to your computer and use it in GitHub Desktop.
Save literallylara/7ece1983fab47365108c47119afb51c7 to your computer and use it in GitHub Desktop.
/**
* RIFF WAVE PCM file generator
* Reference: www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
*
* @author Lara Sophie Schütt (@literallylara)
* @license CC0
*/
const DUR = 5 // duration in seconds
const NCH = 1 // number of channels
const SPS = 44100 // samples per second
const BPS = 1 // bytes per sample
const SIZE = DUR * NCH * SPS * BPS
// PCM Data
// --------------------------------------------
// Field | Bytes | Content
// --------------------------------------------
// ckID | 4 | "fmt "
// cksize | 4 | 0x0000010 (16)
// wFormatTag | 2 | 0x0001 (PCM)
// nChannels | 2 | NCH
// nSamplesPerSec | 4 | SPS
// nAvgBytesPerSec | 4 | NCH * BPS * SPS
// nBlockAlign | 2 | NCH * BPS * NCH
// wBitsPerSample | 2 | BPS * 8
// data_size = DUR * NCH * SPS * BPS
// file_size = 44 (Header) + data_size
function dec2hex(n, l)
{
n = n.toString(16)
return new Array(l*2-n.length+1).join("0") + n
}
function hex2str(hex)
{
let str = []
if (hex.length % 2)
{
throw new Error("hex2str(\"" + hex + "\"): invalid input (# of digits must be divisible by 2)")
}
for (let i = 0; i < hex.length; i+=2)
{
str.push(String.fromCharCode(parseInt(hex.substr(i,2),16)))
}
return str.reverse().join("")
}
function put(n, l)
{
return hex2str(dec2hex(n,l))
}
let data = "RIFF" + put(44 + SIZE, 4) + "WAVEfmt " + put(16, 4)
data += put(1 , 2) // wFormatTag (pcm)
data += put(NCH , 2) // nChannels
data += put(SPS , 4) // nSamplesPerSec
data += put(NCH * BPS * SPS, 4) // nAvgBytesPerSec
data += put(NCH * BPS , 2) // nBlockAlign
data += put(BPS * 8 , 2) // wBitsPerSample
data += "data" + put(SIZE, 4)
for (let i = 0; i < DUR; i++)
{
for (let j = 0; j < SPS; j++)
{
data += put(Math.floor((Math.sin(j/SPS * Math.PI * 2 * 440) + 1) / 2 * Math.pow(2, BPS * 8)), BPS)
}
}
const WAV = new Audio("data:Audio/WAV;base64," + btoa(data))
WAV.setAttribute("controls","controls");
WAV.play();
document.body.appendChild(WAV)
/* Minified version with pre-defined header
// RIFF WAVE PCM | Mono | 44100Hz | 8 bit
for(i=44100*DUR,d="";i--;)d+=String.fromCharCode(~~((Math.sin(i/44100*6.283*440)+1)*128))
new Audio("data:Audio/WAV;base64,"+btoa("RIFFdataWAVEfmt "+atob("EAAAAAEAAQBErAAARKwAAAEACABkYXRh/////w==")+d)).play()
// Web Audio API equivalent (assumes 44100 kHz sample rate)
a=new AudioContext()
s=a.createScriptProcessor(t=b=4096,1,1)
s.connect(a.destination)
s.onaudioprocess=function(e){for(i=0;i<b;)e.outputBuffer.getChannelData(0)[i++]=Math.sin(t++/44100*6.283*440)}
*/
@cveto
Copy link

cveto commented May 19, 2020

Line 65 returns 256 if I put an odd number as a frequency, which ends up translating to x100 (xFF +1). Probably beacuse if sin retruns 1, it's multuplied by 256 instead of 255.

I replaced:
data += put(Math.floor((Math.sin(j/SPS * Math.PI * 2 * 440) + 1) / 2 * Math.pow(2, BPS * 8)), BPS);

with

data += put(Math.round((Math.sin(j/SPS * Math.PI * 2 * 440) + 1) / 2 * (Math.pow(2, BPS * 8)-1) ), BPS);

@aahedi
Copy link

aahedi commented Jun 11, 2020

how do you combine multiple audio files into a single wav file?

@guest271314
Copy link

@aahedi You can read each file and merge the channels.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment