Audio
Basics
Intro to the web audio API.
Tags
Audio
Web Audio API not an element but an API for generating sounds in the browser with JS
<audio> element for embedding an audio player for streamed audio
Attributes
controls
loop replay when end is reached
preload="none" best practice is to set preload to none for mobile
<audio controls="controls">
<source src="someClip.mp3" type="audio/mp3"/>
</audio>
Libraries
howler.js
sound.js
Web Audio
Firefox has a web audio debugger found in settings. Useful for visualizing nodes.
Elements
<audio> add attribute crossorigin="anonymous" if source file is on a different domain
AudioContext
Properties
sampleRate
currentTime
destination
Nodes
.createGain() volume
.createStereoPanner() balance
.createBiquadFilter() frequency , detune , Q , lowpass , highpass , peaking
.createDynamicsCompressor() make louder,richer,fuller sound , prevents clipping
.connect() connect one node to another
ctx = window.AudioContext || window.webkitAudioContext; set up context
someAudioCTX = new ctx(); new context
someAudioCTX.createMediaElementSource(someAudioObject); to set up nodes
Simple Music Player
HTML
<section>
<audio id="microStream" src="butternow.mp3" controls="true"></audio>
<label for="gainSlider">Gain</label>
<input id="gainSlider" type="range" min="0" max="1" step="0.1" value="1"/>
</section>
JavaScript
var ctx = window.AudioContext || window.webkitAudioContext;
var audioContext;
var microstream;
var gainSlider;
var gainNode;
window.onload = streamit;
function streamit() {
audioContext = new ctx();
microsource = document.querySelector('#microStream');
gainSlider = document.querySelector('#gainSlider');
buildAudioGraph();
gainSlider.oninput = function(e) {
gainNode.gain.value = e.target.value;
};
};
function buildAudioGraph() {
var gainMediaElementSource = audioContext.createMediaElementSource(microsource);
gainNode = audioContext.createGain();
gainMediaElementSource.connect(gainNode);
gainNode.connect(audioContext.destination);
}
EQ
HTML
<section>
<audio id="microStream" src="butternow.mp3" controls="true"></audio>
</section>
<section id="eq">
<div class="control">
<label> 60Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,0);"/>
<output id="gain0">0 dB</output>
</div>
<div class="control">
<label> 170Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,1);"/>
<output id="gain1">0 dB</output>
</div>
<div class="control">
<label> 350Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,2);"/>
<output id="gain2">0 dB</output>
</div>
<div class="control">
<label> 1000Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,3);"/>
<output id="gain3">0 dB</output>
</div>
<div class="control">
<label> 3500Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,4);"/>
<output id="gain4">0 dB</output>
</div>
<div class="control">
<label>10000Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,5);"/>
<output id="gain5">0 dB</output>
</div>
</section>
CSS
body {background: rgb(50,50,50);}
h1 {color: rgb(250,250,250);}
p {color: rgb(240,240,240);}
#eq {
width: 250px;
padding: 10px;
border-radius: 10px;
background: rgb(100,100,100);
}
JavaScript
var ctx = window.AudioContext || window.webkitAudioContext;
var audioContext;
window.onload = streamit;
function streamit() {
audioContext = new ctx();
microsource = document.querySelector('#microStream');
sourceNode = audioContext.createMediaElementSource(microsource);
buildEq();
};
function buildEq() {
filters = [];
[60,170,350,1000,3500,10000].forEach(function(freq,i) {
var eq = audioContext.createBiquadFilter();
eq.frequency.value = freq;
eq.type = "peaking";
eq.gain.value = 0;
filters.push(eq);
});
sourceNode.connect(filters[0]);
for(var i = 0;i < filters.length - 1; i++) {
filters[i].connect(filters[i+1]);
}
filters[filters.length - 1].connect(audioContext.destination);
}
function changeGain(sliderVal,nbFilter) {
var value = parseFloat(sliderVal);
filters[nbFilter].gain.value = value;
var output = document.querySelector("#gain"+nbFilter);
output.value = value + " dB";
}
Waveform
.getByteTimeDomainData() returns waveform data
HTML
<section>
<audio id="microStream" src="butternow.mp3" controls="true"></audio>
</section>
<canvas id="visualizerCanvas" width="270px" height="100px"></canvas>
CSS
body {background: rgb(50,50,50);}
h1 {color: rgb(250,250,250);}
p {color: rgb(240,240,240);}
#visualizerCanvas {
background: black;
margin-bottom: 2px;
}
JavaScript
var ctx = window.AudioContext || window.webkitAudioContext;
var audioContext;
var canvasContext;
window.onload = streamit;
function streamit() {
audioContext = new ctx();
canvas = document.querySelector('#visualizerCanvas');
width = canvas.width;
height = canvas.height;
canvasContext = canvas.getContext('2d');
buildAudioGraph();
requestAnimationFrame(visualize);
buildEq();
/*microsource = document.querySelector('#microStream');
sourceNode = audioContext.createMediaElementSource(microsource);*/
};
function buildAudioGraph() {
var mediaElement = document.querySelector('#microStream');
var sourceNode = audioContext.createMediaElementSource(mediaElement);
analyser = audioContext.createAnalyser();
analyser.fftSize = 1024; // precision
bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
sourceNode.connect(analyser);
analyser.connect(audioContext.destination);
}
function visualize() { // waveform animation
canvasContext.fillStyle = 'rgba(0,0,0,0.5)'; // clear canvas with blur
canvasContext.fillRect(0,0,width,height);
analyser.getByteTimeDomainData(dataArray); // analyser data
canvasContext.lineWidth = 2;
canvasContext.strokeStyle = 'orange';
canvasContext.beginPath();
var sliceWidth = width / bufferLength;
var x = 0;
for(var i = 0; i < bufferLength; i++) {
var v = dataArray[i] / 255; // values between 0 and 255 become normalized between 0 and 1
var y = v * height;
if(i === 0) {
canvasContext.moveTo(x,y);
} else {
canvasContext.lineTo(x,y);
}
x += sliceWidth;
}
canvasContext.lineTo(canvas.width,canvas.height/2);
canvasContext.stroke();
requestAnimationFrame(visualize);
}
function buildEq() {
filters = [];
[60,170,350,1000,3500,10000].forEach(function(freq,i) {
var eq = audioContext.createBiquadFilter();
eq.frequency.value = freq;
eq.type = "peaking";
eq.gain.value = 0;
filters.push(eq);
});
sourceNode.connect(filters[0]);
for(var i = 0;i < filters.length - 1; i++) {
filters[i].connect(filters[i+1]);
}
filters[filters.length - 1].connect(audioContext.destination);
}
function changeGain(sliderVal,nbFilter) {
var value = parseFloat(sliderVal);
filters[nbFilter].gain.value = value;
var output = document.querySelector("#gain"+nbFilter);
output.value = value + " dB";
}
Waveform & EQ
HTML
<section>
<audio id="microStream" src="butternow.mp3" controls="true"></audio>
</section>
<canvas id="visualizerCanvas" width="270px" height="100px"></canvas>
<section id="eq">
<div class="control">
<label> 60Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,0);"/>
<output id="gain0">0 dB</output>
</div>
<div class="control">
<label> 170Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,1);"/>
<output id="gain1">0 dB</output>
</div>
<div class="control">
<label> 350Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,2);"/>
<output id="gain2">0 dB</output>
</div>
<div class="control">
<label> 1000Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,3);"/>
<output id="gain3">0 dB</output>
</div>
<div class="control">
<label> 3500Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,4);"/>
<output id="gain4">0 dB</output>
</div>
<div class="control">
<label>10000Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,5);"/>
<output id="gain5">0 dB</output>
</div>
</section>
CSS
body {background: rgb(50,50,50);}
h1 {color: rgb(250,250,250);}
p {color: rgb(240,240,240);}
#visualizerCanvas {
background: black;
margin-bottom: 2px;
}
#eq {
width: 250px;
padding: 10px;
border-radius: 10px;
background: rgb(100,100,100);
}
JavaScript
var audioCtx = window.AudioContext || window.webkitAudioContext;
var audioContext;
var canvasContext;
var filters = [];
var canvas;
var analyser;
var width;
var height;
var dataArray;
var bufferLength;
window.onload = streamit;
function streamit() {
audioContext = new audioCtx();
canvas = document.querySelector('#visualizerCanvas');
width = canvas.width;
height = canvas.height;
canvasContext = canvas.getContext('2d');
buildAudioGraph();
requestAnimationFrame(visualize);
};
function buildAudioGraph() {
var mediaElement = document.querySelector('#microStream');
mediaElement.onplay = (e) => {audioContext.resume();}
mediaElement.addEventListener('play',() => audioContext.resume());
sourceNode = audioContext.createMediaElementSource(mediaElement);
analyser = audioContext.createAnalyser();
analyser.fftSize = 1024; // precision
bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
buildEq();
analyser.connect(audioContext.destination);
}
function visualize() { // waveform animation
canvasContext.fillStyle = 'rgba(0,0,0,0.5)'; // clear canvas with blur
canvasContext.fillRect(0,0,width,height);
analyser.getByteTimeDomainData(dataArray); // analyser data
canvasContext.lineWidth = 2;
canvasContext.strokeStyle = 'orange';
canvasContext.beginPath();
var sliceWidth = width / bufferLength;
var x = 0;
for(var i = 0; i < bufferLength; i++) {
var v = dataArray[i] / 255; // values between 0 and 255 become normalized between 0 and 1
var y = v * height;
if(i === 0) {
canvasContext.moveTo(x,y);
} else {
canvasContext.lineTo(x,y);
}
x += sliceWidth;
}
canvasContext.lineTo(canvas.width,canvas.height/2);
canvasContext.stroke();
requestAnimationFrame(visualize);
}
function buildEq() {
filters = [];
[60,170,350,1000,3500,10000].forEach(function(freq,i) {
var eq = audioContext.createBiquadFilter();
eq.frequency.value = freq;
eq.type = "peaking";
eq.gain.value = 0;
filters.push(eq);
});
sourceNode.connect(filters[0]);
for(var i = 0;i < filters.length - 1; i++) {
filters[i].connect(filters[i+1]);
}
filters[filters.length - 1].connect(analyser);
}
function changeGain(sliderVal,nbFilter) {
var value = parseFloat(sliderVal);
filters[nbFilter].gain.value = value;
var output = document.querySelector("#gain"+nbFilter);
output.value = value + " dB";
}
Frequency
Number of bars is equal to fftSize / 2.
Bar frequency is equal to sample rate / fftSize.
.getByteFrequencyData() returns frequency data for bar graphs
JavaScript
var audioCtx = window.AudioContext || window.webkitAudioContext;
var audioContext;
var canvasContext;
var filters = [];
var canvas;
var analyser;
var width;
var height;
var dataArray;
var bufferLength;
window.onload = streamit;
function streamit() {
audioContext = new audioCtx();
canvas = document.querySelector('#visualizerCanvas');
width = canvas.width;
height = canvas.height;
canvasContext = canvas.getContext('2d');
buildAudioGraph();
requestAnimationFrame(visualize);
};
function buildAudioGraph() {
var mediaElement = document.querySelector('#microStream');
mediaElement.onplay = (e) => {audioContext.resume();}
mediaElement.addEventListener('play',() => audioContext.resume());
sourceNode = audioContext.createMediaElementSource(mediaElement);
analyser = audioContext.createAnalyser();
analyser.fftSize = 256; // precision
bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
buildEq();
analyser.connect(audioContext.destination);
}
function visualize() { // frequency animation
canvasContext.clearRect(0,0,width,height);
analyser.getByteFrequencyData(dataArray); // analyser data
var barWidth = width / bufferLength;
var barHeight;
var x = 0;
heightScale = height / 128;
for(var i = 0; i < bufferLength; i++) {
barHeight = dataArray[i]; // values between 0 and 255 become normalized between 0 and 1
canvasContext.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)'; // red bars
barHeight *= heightScale; // scale from 0-255 to 0-height
canvasContext.fillRect(x,height-barHeight/2,barWidth,barHeight/2);
x += barWidth + 1; // distance between bars 1px
}
canvasContext.lineTo(canvas.width,canvas.height/2);
canvasContext.stroke();
requestAnimationFrame(visualize);
}
function buildEq() {
filters = [];
[60,170,350,1000,3500,10000].forEach(function(freq,i) {
var eq = audioContext.createBiquadFilter();
eq.frequency.value = freq;
eq.type = "peaking";
eq.gain.value = 0;
filters.push(eq);
});
sourceNode.connect(filters[0]);
for(var i = 0;i < filters.length - 1; i++) {
filters[i].connect(filters[i+1]);
}
filters[filters.length - 1].connect(analyser);
}
function changeGain(sliderVal,nbFilter) {
var value = parseFloat(sliderVal);
filters[nbFilter].gain.value = value;
var output = document.querySelector("#gain"+nbFilter);
output.value = value + " dB";
}
Waveform Frequency EQ Main Volume
HTML
<section>
<audio id="microStream" src="butternow.mp3" controls="true"></audio>
</section>
<section id="visualizers">
<canvas id="visualizer1Canvas" width="270px" height="100px"></canvas>
<canvas id="visualizerCanvas" width="270px" height="100px"></canvas>
</section>
<section id="eq">
<div class="control">
<label> 60Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,0);"/>
<output id="gain0">0 dB</output>
</div>
<div class="control">
<label> 170Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,1);"/>
<output id="gain1">0 dB</output>
</div>
<div class="control">
<label> 350Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,2);"/>
<output id="gain2">0 dB</output>
</div>
<div class="control">
<label> 1000Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,3);"/>
<output id="gain3">0 dB</output>
</div>
<div class="control">
<label> 3500Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,4);"/>
<output id="gain4">0 dB</output>
</div>
<div class="control">
<label>10000Hz</label>
<input type="range" value="0" step="1" min="-30" max="30" oninput="changeGain(this.value,5);"/>
<output id="gain5">0 dB</output>
</div>
</section>
<section id="masterVolume">
<label>Master Volume</label>
<input type="range" value="10" step="0.1" min="0" max="10" oninput="changeMasterGain(this.value);"/>
<output id="masterGainOutput">10</output>
</section>
CSS
body {background: rgb(50,50,50);}
h1 {color: rgb(250,250,250);}
p {color: rgb(240,240,240);}
#visualizers {
display: grid;
}
#visualizer1Canvas {
background: black;
margin-bottom: 1px;
}
#visualizerCanvas {
background: black;
margin-bottom: 8px;
}
#eq,#masterVolume {
width: 250px;
padding: 10px;
border-radius: 10px;
background: rgb(100,100,100);
}
#masterVolume {
margin-top: 5px;
font-size: 12px;
}
JavaScript
var audioCtx = window.AudioContext || window.webkitAudioContext;
var audioContext;
var canvasContext;
var filters = [];
var canvas;
var analyser;
var width;
var height;
var dataArray;
var bufferLength;
window.onload = streamit;
function streamit() {
audioContext = new audioCtx();
canvas = document.querySelector('#visualizerCanvas');
width = canvas.width;
height = canvas.height;
canvasContext = canvas.getContext('2d');
buildAudioGraph();
requestAnimationFrame(visualize);
requestAnimationFrame(visualize2);
};
function buildAudioGraph() {
var mediaElement = document.querySelector('#microStream');
mediaElement.onplay = (e) => {audioContext.resume();}
mediaElement.addEventListener('play',() => audioContext.resume());
sourceNode = audioContext.createMediaElementSource(mediaElement);
analyser = audioContext.createAnalyser();
analyser.fftSize = 256; // precision
bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
buildEq();
masterGain = audioContext.createGain(); // main volume
masterGain.value = 1;
analyser.connect(masterGain); // connect nodes
masterGain.connect(audioContext.destination); // connect to final output
}
function visualize2() { // waveform animation
canvasContext.fillStyle = 'rgba(0,0,0,0.5)'; // clear canvas with blur
canvasContext.fillRect(0,0,width,height);
analyser.getByteTimeDomainData(dataArray); // analyser data
canvasContext.lineWidth = 2;
canvasContext.strokeStyle = 'orange';
canvasContext.beginPath();
var sliceWidth = width / bufferLength;
var x = 0;
for(var i = 0; i < bufferLength; i++) {
var v = dataArray[i] / 255; // values between 0 and 255 become normalized between 0 and 1
var y = v * height;
if(i === 0) {
canvasContext.moveTo(x,y);
} else {
canvasContext.lineTo(x,y);
}
x += sliceWidth;
}
canvasContext.lineTo(canvas.width,canvas.height/2);
canvasContext.stroke();
requestAnimationFrame(visualize2);
}
function visualize() { // frequency animation
canvasContext.clearRect(0,0,width,height);
analyser.getByteFrequencyData(dataArray); // analyser data
var barWidth = width / bufferLength;
var barHeight;
var x = 0;
heightScale = height / 128;
for(var i = 0; i < bufferLength; i++) {
barHeight = dataArray[i]; // values between 0 and 255 become normalized between 0 and 1
canvasContext.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)'; // red bars
barHeight *= heightScale; // scale from 0-255 to 0-height
canvasContext.fillRect(x,height-barHeight/2,barWidth,barHeight/2);
x += barWidth + 1; // distance between bars 1px
}
canvasContext.lineTo(canvas.width,canvas.height/2);
canvasContext.stroke();
requestAnimationFrame(visualize);
}
function buildEq() {
filters = [];
[60,170,350,1000,3500,10000].forEach(function(freq,i) {
var eq = audioContext.createBiquadFilter();
eq.frequency.value = freq;
eq.type = "peaking";
eq.gain.value = 0;
filters.push(eq);
});
sourceNode.connect(filters[0]);
for(var i = 0;i < filters.length - 1; i++) {
filters[i].connect(filters[i+1]);
}
filters[filters.length - 1].connect(analyser);
}
function changeGain(sliderVal,nbFilter) {
var value = parseFloat(sliderVal);
filters[nbFilter].gain.value = value;
var output = document.querySelector("#gain"+nbFilter);
output.value = value + " dB";
}
var masterGain;
function changeMasterGain(sliderVal) {
var value = parseFloat(sliderVal);
masterGain.gain.value = value/10;
var output = document.querySelector('#masterGainOutput');
output.value = value;
} notes navigation
Current URL: /notes/10Audio/00-audio-basics/
total notes 36