Generate Notes

by soulwire

JavaScript

const SAMPLE_RATE = 44100;
const TIME_INTERVAL = 1;
const A4 = 440;

const notes = [];
for(let i = -4; i < 4; i++){
  let a = A4 * Math.pow(2, i);
  for (let n = -8; n <= 2; n++) {
  	const b = a * Math.pow(2, n / 12);
    notes.push(b);
  }
}
	
notes.forEach((freq, index) => {
	console.log(`${Math.round(index * TIME_INTERVAL)}s => ${Math.round(freq)}`);
});


const duration = notes.length * TIME_INTERVAL;
const context = new OfflineAudioContext(2, duration * SAMPLE_RATE, SAMPLE_RATE);
const oscillator = context.createOscillator();

let time = context.currentTime;
notes.forEach(freq => {
	oscillator.frequency.setValueAtTime(freq, time);
  time += TIME_INTERVAL
});

oscillator.type = 'sine';
oscillator.connect(context.destination);
oscillator.start();

context.startRendering().then(buffer => {
	const el = new Audio();
  el.controls = true;
  el.src = URL.createObjectURL(bufferToWAV(buffer));
  document.body.appendChild(el);
});

function bufferToWAV(buffer, samples) {
	const length = buffer.length * buffer.numberOfChannels * 2 + 44;
  const output = new ArrayBuffer(length);
  const view = new DataView(output);
  const channels = [];

	let offset = 0;
	let pos = 0;
  
	const setUint16 = data => {
	  view.setUint32(pos, data, true);
    pos += 2;
  }
  
  const setUint32 = data => {
	  view.setUint32(pos, data, true);
    pos += 4;
  }
  
  // "RIFF"
  setUint32(0x46464952);

  // file length - 8
  setUint32(length - 8);

  // "WAVE"
  setUint32(0x45564157);


  // "fmt " chunk
  setUint32(0x20746d66);

  // length = 16
  setUint32(16)

  // PCM (uncompressed)
  setUint16(1);

  setUint16(buffer.numberOfChannels)
  setUint32(buffer.sampleRate)

  // avg. bytes/sec
  setUint32(buffer.sampleRate * 2 * buffer.numberOfChannels);

  // block-align
  setUint16(buffer.numberOfChannels * 2);

  // 16-bit (hardcoded in this demo)
  setUint16(16)

  // "data" - chunk
  setUint32(0x61746164);

  // chunk length
  setUint32(length - pos - 4);
  
  // write interleaved data
 ...