I know next to nothing about audio.
But I do know that when I change the playback speed on YouTube videos to 2x or 0.5x, the voices sound fine.
When I do the same with <audio>
, the voices sound distorted.
Apperantly, there's an algorithm that can fix it.
A JavaScript implementation of such an algorithm is available on GitHub named soundtouch-js but there is no documentation, it has not been updated in 4 years, and the demo does not work out of the box.
Here is how I made it work.
test.html
has a link to the underscore
library that no longer works. I removed everything else since I could not figure out what it does.
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="src/js/core.js"></script>
<script src="src/js/pipe.js"></script>
<script src="src/js/rate-transposer.js"></script>
<script src="src/js/buffer.js"></script>
<script src="src/js/filter.js"></script>
<script src="src/js/stretch.js"></script>
<script src="src/js/soundtouch.js"></script>
<script src="test.js"></script>
test.js
has been modified to look like this:
var t = new RateTransposer(true);
var s = new Stretch(true);
s.tempo = 0.75;
//t.rate = 1;
var context = new AudioContext();
var buffer;
function loadSample(url) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function() {
context.decodeAudioData(request.response, function(data) {
buffer = data;
play();
})
}
request.send();
}
loadSample('track.mp3')
var BUFFER_SIZE = 1024;
var samples = new Float32Array(BUFFER_SIZE * 2);
var node = context.createScriptProcessor(BUFFER_SIZE, 2, 2);
node.onaudioprocess = function (e) {
var l = e.outputBuffer.getChannelData(0);
var r = e.outputBuffer.getChannelData(1);
var framesExtracted = f.extract(samples, BUFFER_SIZE);
if (framesExtracted == 0) {
pause();
}
for (var i = 0; i < framesExtracted; i++) {
l[i] = samples[i * 2];
r[i] = samples[i * 2 + 1];
}
};
function play() {
node.connect(context.destination);
}
function pause() {
node.disconnect();
}
var source = {
extract: function (target, numFrames, position) {
var l = buffer.getChannelData(0);
var r = buffer.getChannelData(1);
for (var i = 0; i < numFrames; i++) {
target[i * 2] = l[i + position];
target[i * 2 + 1] = r[i + position];
}
return Math.min(numFrames, l.length - position);
}
};
f = new SimpleFilter(source, s);
It'll load track.mp3
with XMLHttpRequest
, then call the play()
function which will play the audio.
You can change the playback rate with s.tempo = 0.5
.