If you haven't heard the "Yanny vs Laurel" story yet, here is an article from New York Times to help you get the background story.
There is no magic behind this. Different ears have different sensitive frequency zones for the same audio clip. Also different speakers have different response to different audio frequencies.
This tutorial will go through the details about how to use Web Audio API and simple Javascript to create a tool helping you hear both "Yanny" and "Laurel", if you really want to know :)
Let's talk about the key part first. In order to hear the different word, you need to somehow increase the volume for a specific frequency range which depends on your ears. Luckily the Web Audio API already got something ready for us: BiquadFilterNode
.
There are different type of BiquadFilterNode
you can use. For this case, we will just go with the bandpass
filter.
A bandpass filter is an electronic device or circuit that allows signals between two specific frequencies to pass, but that discriminates against signals at other frequencies. https://whatis.techtarget.com/definition/bandpass-filter
And for a bandpass filter, most of the time we just need to define the center frequency value we want to boost or cut, instead of the start and the end of the frequency range. We use a Q
value to control the width of the frequency range. The large the Q
is, the narrow the frequency range will be. For more detail, please see wiki.
That's all the knowledge we need to know. Let's write the code now.
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();
// the audio tag in HTML, where holds the original audio clip
const audioTag = document.getElementById('audioTag');
// create audio source in web audio api
const sourceNode = audioContext.createMediaElementSource(audioTag);
const filterNode = audioContext.createBiquadFilter();
filterNode.type = 'bandpass'; // bandpass filter
filterNode.frequency.value = 1000; // set the center frequency
filterNode.gain.value = 100; // set the gain to the frequency range
filterNode.Q.value = 20; // set Q value, 20 will make a fair band width for this case
// connect nodes
sourceNode.connect(filterNode);
filterNode.connect(gainNode);
gainNode.connect(audioContext.destination);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Yanny vs Laurel Web Audio API</title>
</head>
<body>
<div id="container">
<audio id='audioTag' crossorigin="anonymous" src="yanny-laurel.wav" controls loop></audio>
<hr>
<input type="range" min="20" max="10000" value="20" step="1" class="slider" id="freqSlider">
</div>
<script src='script.js'></script>
</body>
</html>
To make it more easy to adjust the center frequency of our bandpass filter, it's good to add a slider to control the value.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Yanny vs Laurel Web Audio API</title>
</head>
<body>
<div id="container">
<audio id='audioTag' crossorigin="anonymous" src="yanny-laurel.wav" controls loop></audio>
<hr>
<input type="range" min="20" max="10000" value="20" step="1" class="slider" id="freqSlider">
<br>
<p id="freqLabel" >Frequency: 20</p>
</div>
<script>
// add event listener for slider to change frequency value
slider.addEventListener('input', e => {
filterNode.frequency.value = e.target.value;
label.innerHTML = `Frequency: ${e.target.value}Hz`;
}, false);
<script src='script.js'></script>
</body>
</html>
I found createMediaElementSource
won't work in iOS safari and chrome. To solve this, you have to use createBufferSource
to create an AudioBufferNode to store and play the audio instead of the HTML audio tag.
Please see the code here for more detail.
Now you made yourself a tool to hear both 'Yanny' and 'Laurel". Just open your browser, play the audio, and try to find the sweet spot while moving the frequency slider.
If you want to just try the tool, it is live HERE.