Skip to content
Permalink
Browse files

Change Tempo while Stretching

  • Loading branch information...
VivekPanyam committed Jan 17, 2016
1 parent ffd150c commit b76707e37d6fc9276b461268154d0334e8347e41
Showing with 102 additions and 26 deletions.
  1. +2 −0 README.md
  2. +46 −12 build/examples/test.js
  3. +1 −1 build/kali.min.js
  4. +16 −6 examples/Test.ts
  5. +28 −6 src/Kali.ts
  6. +9 −1 src/TypedQueue.ts
@@ -39,6 +39,8 @@ Once you're done feeding data, tell Kali to flush its buffers and continue readi

That's it! See `Test.ts` in the examples folder for a complete example of usage.

If you want to change tempo during stretching, you can call `setTempo` (with a new stretch factor) while feeding data. This will let you smoothly change between tempos. See `Test.ts` for an example.

##Documentation

For complete documentation, see [https://kali.readme.io/docs](https://kali.readme.io/docs)
@@ -69,6 +69,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
var Kali = __webpack_require__(2);
var START_RATE = 1.7;
var TARGET_RATE = 1;
// Load audio
var context = new (AudioContext)();
function loadAudio(url, callback) {
@@ -89,15 +91,22 @@
// Create a Kali instance and initialize it
var kali = new Kali(numChannels);
kali.setup(44100, stretchFactor);
// Create an array for the stretched output
var completed = new Float32Array((numInputFrames / stretchFactor) * numChannels + 1);
// Create an array for the stretched output. Note if the rate is changing, this array won't be completely full
var completed = new Float32Array((numInputFrames / Math.min(START_RATE, TARGET_RATE)) * numChannels + 1);
var inputOffset = 0;
var completedOffset = 0;
var loopCount = 0;
var flushed = false;
while (completedOffset < completed.length) {
if (loopCount % 100 == 0) {
console.log("Stretching", completedOffset / completed.length);
while (completedOffset < completed.length && inputOffset < inputData.length) {
if (loopCount % 50 == 0) {
console.log("Stretching", inputOffset / inputData.length);
if (stretchFactor > TARGET_RATE) {
stretchFactor = Math.max(TARGET_RATE, stretchFactor - 0.05);
}
else {
stretchFactor = Math.min(TARGET_RATE, stretchFactor + 0.05);
}
kali.setTempo(stretchFactor);
}
// Read stretched samples into our output array
completedOffset += kali.output(completed.subarray(completedOffset, Math.min(completedOffset + bufsize, completed.length)));
@@ -121,7 +130,7 @@
loadAudio('/test.mp3', function (audiobuffer) {
var inputData = audiobuffer.getChannelData(0);
console.log("Ready to stretch");
var output = doStretch(inputData, 120 / 125);
var output = doStretch(inputData, START_RATE);
var outputAudioBuffer = context.createBuffer(1, output.length, context.sampleRate);
outputAudioBuffer.getChannelData(0).set(output);
var source = context.createBufferSource();
@@ -166,6 +175,8 @@
}
var tempo_t = (function () {
function tempo_t() {
this.is_initialized = false;
this.sample_rate = 44100;
this.channels = 0;
this.quick_search = false;
this.factor = 0;
@@ -276,8 +287,7 @@
t.overlap_buf.set(t.input_fifo.read_ptr(t.channels * (offset + t.segment - t.overlap)).subarray(0, numToCopy));
/* Advance through the input stream */
t.segments_total++;
skip = handleInt(t.factor * (t.segments_total * (t.segment - t.overlap)) + 0.5);
t.skip_total += skip -= t.skip_total;
skip = handleInt(t.factor * (t.segment - t.overlap) + 0.5);
t.input_fifo.read(null, skip);
}
};
@@ -320,6 +330,8 @@
if (search_ms === void 0) { search_ms = null; }
if (overlap_ms === void 0) { overlap_ms = null; }
var profile = 1;
var t = this.t;
t.sample_rate = sample_rate;
if (segment_ms == null) {
segment_ms = Math.max(10, Kali.segments_ms[profile] / Math.max(Math.pow(factor, Kali.segments_pow[profile]), 1));
}
@@ -330,7 +342,6 @@
overlap_ms = segment_ms / Kali.overlaps_div[profile];
}
var max_skip;
var t = this.t;
t.quick_search = quick_search;
t.factor = factor;
t.segment = handleInt(sample_rate * segment_ms / 1000 + .5);
@@ -339,10 +350,28 @@
if (t.overlap * 2 > t.segment) {
t.overlap -= 8;
}
t.overlap_buf = new Float32Array(t.overlap * t.channels);
if (!t.is_initialized) {
t.overlap_buf = new Float32Array(t.overlap * t.channels);
}
else {
var new_overlap = new Float32Array(t.overlap * t.channels);
var start = 0;
if (t.overlap * t.channels < t.overlap_buf.length) {
start = t.overlap_buf.length - (t.overlap * t.channels);
}
new_overlap.set(t.overlap_buf.subarray(start, t.overlap_buf.length));
t.overlap_buf = new_overlap;
}
max_skip = handleInt(Math.ceil(factor * (t.segment - t.overlap)));
t.process_size = Math.max(max_skip + t.overlap, t.segment) + t.search;
t.input_fifo.reserve(t.search / 2);
if (!t.is_initialized) {
t.input_fifo.reserve(handleInt(t.search / 2));
}
t.is_initialized = true;
};
Kali.prototype.setTempo = function (factor) {
var t = this.t;
this.setup(t.sample_rate, factor, t.quick_search);
};
Kali.segments_ms = [82, 82, 35, 20];
Kali.segments_pow = [0, 1, .33, 1];
@@ -423,14 +452,19 @@
return this.buffer.subarray(offset, offset + n);
};
TypedQueue.prototype.read = function (data, n) {
// TODO do checks on n here
if (n + this.begin > this.end) {
console.error("Read out of bounds", n, this.end, this.begin);
}
if (data != null) {
data.set(this.buffer.subarray(this.begin, this.begin + n));
}
this.begin += n;
};
TypedQueue.prototype.read_ptr = function (start, end) {
if (end === void 0) { end = -1; }
if (end > this.occupancy()) {
console.error("Read Pointer out of bounds", end);
}
if (end < 0) {
end = this.occupancy();
}

Some generated files are not rendered by default. Learn more.

@@ -18,6 +18,9 @@

import Kali = require("../src/Kali");

var START_RATE = 1.7;
var TARGET_RATE = 1;

// Load audio
var context = new (AudioContext)();

@@ -44,17 +47,24 @@ function doStretch(inputData : Float32Array, stretchFactor: number, numChannels:
var kali = new Kali(numChannels);
kali.setup(44100, stretchFactor);

// Create an array for the stretched output
var completed = new Float32Array((numInputFrames / stretchFactor) * numChannels + 1);
// Create an array for the stretched output. Note if the rate is changing, this array won't be completely full
var completed = new Float32Array((numInputFrames / Math.min(START_RATE, TARGET_RATE)) * numChannels + 1);

var inputOffset: number = 0;
var completedOffset: number = 0;
var loopCount: number = 0;
var flushed = false;

while (completedOffset < completed.length) {
if (loopCount % 100 == 0) {
console.log("Stretching", completedOffset / completed.length);
while (completedOffset < completed.length && inputOffset < inputData.length) {
if (loopCount % 50 == 0) {
console.log("Stretching", inputOffset / inputData.length);
if (stretchFactor > TARGET_RATE) {
stretchFactor = Math.max(TARGET_RATE, stretchFactor - 0.05);
} else {
stretchFactor = Math.min(TARGET_RATE, stretchFactor + 0.05);
}

kali.setTempo(stretchFactor);
}

// Read stretched samples into our output array
@@ -84,7 +94,7 @@ function play() {
loadAudio('/test.mp3', function(audiobuffer: AudioBuffer) {
var inputData = audiobuffer.getChannelData(0);
console.log("Ready to stretch")
var output = doStretch(inputData, 120/125);
var output = doStretch(inputData, START_RATE);

var outputAudioBuffer = context.createBuffer(1, output.length, context.sampleRate);
outputAudioBuffer.getChannelData(0).set(output);
@@ -41,6 +41,8 @@ type uint64_t = number;


class tempo_t {
public is_initialized: boolean = false;
public sample_rate: size_t = 44100;
public channels: size_t = 0;
public quick_search: boolean = false;
public factor: double = 0;
@@ -175,9 +177,7 @@ class Kali {

/* Advance through the input stream */
t.segments_total++;
skip = handleInt(t.factor * (t.segments_total * (t.segment - t.overlap)) + 0.5);

t.skip_total += skip -= t.skip_total;
skip = handleInt(t.factor * (t.segment - t.overlap) + 0.5);
t.input_fifo.read(null, skip);

}
@@ -231,6 +231,8 @@ class Kali {
overlap_ms: double = null): void {

var profile = 1;
var t = this.t;
t.sample_rate = sample_rate;

if (segment_ms == null) {
segment_ms = Math.max(10, Kali.segments_ms[profile] / Math.max(Math.pow(factor, Kali.segments_pow[profile]), 1));
@@ -245,7 +247,6 @@ class Kali {
}

var max_skip: size_t;
var t = this.t;
t.quick_search = quick_search;
t.factor = factor;
t.segment = handleInt(sample_rate * segment_ms / 1000 + .5);
@@ -255,10 +256,31 @@ class Kali {
t.overlap -= 8;
}

t.overlap_buf = new Float32Array(t.overlap * t.channels);
if (!t.is_initialized) {
t.overlap_buf = new Float32Array(t.overlap * t.channels);
} else {
var new_overlap = new Float32Array(t.overlap * t.channels);
var start = 0;
if (t.overlap * t.channels < t.overlap_buf.length) {
start = t.overlap_buf.length - (t.overlap * t.channels);
}

new_overlap.set(t.overlap_buf.subarray(start, t.overlap_buf.length));
t.overlap_buf = new_overlap;
}

max_skip = handleInt(Math.ceil(factor * (t.segment - t.overlap)));
t.process_size = Math.max(max_skip + t.overlap, t.segment) + t.search;
t.input_fifo.reserve(t.search / 2);
if (!t.is_initialized) {
t.input_fifo.reserve(handleInt(t.search / 2));
}

t.is_initialized = true;
}

public setTempo(factor : double) {
var t = this.t;
this.setup(t.sample_rate, factor, t.quick_search);
}

constructor(channels: size_t) {
@@ -97,7 +97,11 @@ class TypedQueue<T extends TypedArray> {
}

public read(data: T, n: int) : void {
// TODO do checks on n here
if (n + this.begin > this.end) {
console.error("Read out of bounds", n, this.end, this.begin);
}


if (data != null) {
data.set(this.buffer.subarray(this.begin, this.begin + n));
}
@@ -106,6 +110,10 @@ class TypedQueue<T extends TypedArray> {
}

public read_ptr(start: int, end: int = -1) : T {
if (end > this.occupancy()) {
console.error("Read Pointer out of bounds", end);
}

if (end < 0) {
end = this.occupancy();
}

0 comments on commit b76707e

Please sign in to comment.
You can’t perform that action at this time.