Skip to content

Commit

Permalink
implemented PitchShifter.js
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubfiala committed Jun 16, 2015
1 parent 6cb8e8a commit 6eef432
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 81 deletions.
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = function(grunt) {
},
dist: {
// the files to concatenate
src: ['src/*.js', 'soundtouch.js'],
src: ['src/buffer.js','src/core.js','src/filter.js','src/pipe.js','src/rate-transposer.js','src/soundtouch.js','src/stretch.js','src/pitchshifter.js'],
// the location of the resulting JS file
dest: 'tmp/soundtouch.concat.js'
}
Expand Down
36 changes: 23 additions & 13 deletions example/example.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
<script src="http://documentcloud.github.com/underscore/underscore.js"></script>
<script src="../soundtouch.min.js"></script>
<script src="example.js"></script>
<script>
var count = 0;
<!DOCTYPE HTML>
<html>
<head>
<title>soundtouch.js demo</title>
<script src="../soundtouch.min.js"></script>
<style type="text/css">
body {
background: grey;
line-height: 30px;
}

function increment() {
console.log('increment')
if (count++ > 100) {
pause();
throw Error('whoa');
}
}
</script>
input, button {
display:block;
}
</style>
</head>
<body>
<button onclick="play()">Play</button>
<button onclick="pause()">Pause</button>
Rate: <input type="range" autofocus="true" min="0.1" max="4.0" name="tempoSlider" defaultValue="1.0" id="tempoSlider" step="0.01">
Pitch: <input type="range" autofocus="true" min="0.1" max="2.0" name="pitchSlider" defaultValue="1.0" id="pitchSlider" step="0.01">
</body>
<script src="example.js"></script>
</html>
98 changes: 35 additions & 63 deletions example/example.js
Original file line number Diff line number Diff line change
@@ -1,75 +1,47 @@
var t = new RateTransposer(true);
var s = new Stretch(true);
//s.tempo = .5;
t.rate = 2;
var context = new webkitAudioContext();

var buffer;

loadSample = function(url) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
//AC polyfill
window.AudioContext = window.AudioContext ||
window.webkitAudioContext ||
window.mozAudioContext ||
window.oAudioContext ||
window.msAudioContext

request.onload = function() {
console.log('url loaded');
createBuffer(request.response);
}

console.log('reading url');
request.send();
}
var context = new webkitAudioContext();

function createBuffer(arrayBuffer) {
offset = 0;
startTime = 0;
var start = new Date();
// NOTE the second parameter is required, or a TypeError is thrown
buffer = context.createBuffer(arrayBuffer, false);
console.log('loaded audio in ' + (new Date() - start));
var pitchshifter, buffer;

//GET AUDIO FILE
var request = new XMLHttpRequest();
request.open('GET', 'sound1.wav', true);
request.responseType = 'arraybuffer';

request.onload = function() {
console.log('url loaded');
context.decodeAudioData(request.response, function(buf) {
//we now have the audio data
buffer = buf;
console.log('decoded');
pitchshifter = new PitchShifter(context, buffer, 1024);
pitchshifter.tempo = 0.75;
});
}

//loadSample('badromance.mp3')
loadSample('track.mp3')

var BUFFER_SIZE = 1024;

var node = context.createScriptProcessor(BUFFER_SIZE, 2, 2);

var samples = new Float32Array(BUFFER_SIZE * 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];
}
};
console.log('reading url');
request.send();

//PLAYBACK
function play() {
node.connect(context.destination);
pitchshifter.connect(context.destination);
console.log("play")
}

function pause() {
node.disconnect();
pitchshifter.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);
}
};

document.getElementById('tempoSlider').addEventListener('input', function(){
pitchshifter.tempo = this.value;
});

f = new SimpleFilter(source, s);
document.getElementById('pitchSlider').addEventListener('input', function(){
pitchshifter.pitch = this.value;
});
Binary file added example/sound1.wav
Binary file not shown.
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var core = require('./src/core')
var buffer = require('./src/buffer')
var filter = require('./src/filter')
var pipe = require('./src/pipe')
var rateTransposer = require('./src/rate-transposer')
var soundtouch = require('./src/soundtouch')
var stretch = require('./src/stretch')
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "soundtouch-js",
"version": "1.0.0",
"description": "Javascript audio time-stretching and pitch-shifting library",
"main": "soundtouch.js",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand Down
2 changes: 1 addition & 1 deletion soundtouch.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,4 @@ FifoSampleBuffer.prototype = {
this._position = 0;
}
}
};
};
25 changes: 25 additions & 0 deletions src/pitchshifter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
function PitchShifter (ctx, buffer, bufSize) {
this._st = new SoundTouch();
this._f = new SimpleFilter(new WebAudioBufferSource(buffer), this._st, bufSize);
this._node = getWebAudioNode(ctx, this._f);
}

PitchShifter.prototype.connect = function(toNode) {
this._node.connect(toNode);
}

PitchShifter.prototype.disconnect = function(toNode) {
this._node.disconnect();
}

extend(PitchShifter.prototype, {
set pitch(p) {
this._st.pitch = p;
},
set rate(r) {
this._st.rate = r;
},
set tempo(t) {
this._st.tempo = t;
}
});
4 changes: 3 additions & 1 deletion src/rate-transposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@

function RateTransposer(createBuffers) {
AbstractFifoSamplePipe.call(this, createBuffers);
this._reset();
this.slopeCount = 0;
this.prevSampleL = 0;
this.prevSampleR = 0;
this.rate = 1;
}

Expand Down
36 changes: 36 additions & 0 deletions soundtouch.js → src/soundtouch.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ extend(SoundTouch.prototype, {
},

_calculateEffectiveRateAndTempo: function () {
console.log("calculating");
var previousTempo = this._tempo;
var previousRate = this._rate;

Expand Down Expand Up @@ -142,3 +143,38 @@ extend(SoundTouch.prototype, {
}
}
});

function WebAudioBufferSource(buffer) {
this.buffer = buffer;
}
WebAudioBufferSource.prototype = {
extract: function(target, numFrames, position) {
var l = this.buffer.getChannelData(0);
var r;
if (buffer.numberOfChannels > 1) r = this.buffer.getChannelData(1);
for (var i = 0; i < numFrames; i++) {
target[i * 2] = l[i + position];
if (buffer.numberOfChannels > 1) target[i * 2 + 1] = r[i + position];
}
return Math.min(numFrames, l.length - position);
}
};

function getWebAudioNode(context, filter, bufSize) {
var BUFFER_SIZE = bufSize || 1024;
var node = context.createScriptProcessor ? context.createScriptProcessor(BUFFER_SIZE, 2, 2) : context.createJavascriptNode(BUFFER_SIZE, 2, 2),
samples = new Float32Array(BUFFER_SIZE * 2);
node.onaudioprocess = function(e) {
var l = e.outputBuffer.getChannelData(0),
r = e.outputBuffer.getChannelData(1);
var framesExtracted = filter.extract(samples, BUFFER_SIZE);
if (framesExtracted === 0) {
node.disconnect(); // Pause.
}
for (var i = 0; i < framesExtracted; i++) {
l[i] = samples[i * 2];
r[i] = samples[i * 2 + 1];
}
};
return node;
}

0 comments on commit 6eef432

Please sign in to comment.