Skip to content

Commit

Permalink
Change Tempo while Stretching
Browse files Browse the repository at this point in the history
  • Loading branch information
VivekPanyam committed Jan 17, 2016
1 parent ffd150c commit b76707e
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 26 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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. 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 ##Documentation


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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 16 additions & 6 deletions examples/Test.ts
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@


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


var START_RATE = 1.7;
var TARGET_RATE = 1;

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


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


// Create an array for the stretched output // 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 / stretchFactor) * numChannels + 1); var completed = new Float32Array((numInputFrames / Math.min(START_RATE, TARGET_RATE)) * numChannels + 1);


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


while (completedOffset < completed.length) { while (completedOffset < completed.length && inputOffset < inputData.length) {
if (loopCount % 100 == 0) { if (loopCount % 50 == 0) {
console.log("Stretching", completedOffset / completed.length); 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 // Read stretched samples into our output array
Expand Down Expand Up @@ -84,7 +94,7 @@ function play() {
loadAudio('/test.mp3', function(audiobuffer: AudioBuffer) { loadAudio('/test.mp3', function(audiobuffer: AudioBuffer) {
var inputData = audiobuffer.getChannelData(0); var inputData = audiobuffer.getChannelData(0);
console.log("Ready to stretch") 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); var outputAudioBuffer = context.createBuffer(1, output.length, context.sampleRate);
outputAudioBuffer.getChannelData(0).set(output); outputAudioBuffer.getChannelData(0).set(output);
Expand Down
34 changes: 28 additions & 6 deletions src/Kali.ts
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ type uint64_t = number;




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


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

t.skip_total += skip -= t.skip_total;
t.input_fifo.read(null, skip); t.input_fifo.read(null, skip);


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


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


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


var max_skip: size_t; var max_skip: size_t;
var t = this.t;
t.quick_search = quick_search; t.quick_search = quick_search;
t.factor = factor; t.factor = factor;
t.segment = handleInt(sample_rate * segment_ms / 1000 + .5); t.segment = handleInt(sample_rate * segment_ms / 1000 + .5);
Expand All @@ -255,10 +256,31 @@ class Kali {
t.overlap -= 8; 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))); max_skip = handleInt(Math.ceil(factor * (t.segment - t.overlap)));
t.process_size = Math.max(max_skip + t.overlap, t.segment) + t.search; 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) { constructor(channels: size_t) {
Expand Down
10 changes: 9 additions & 1 deletion src/TypedQueue.ts
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ class TypedQueue<T extends TypedArray> {
} }


public read(data: T, n: int) : void { 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) { if (data != null) {
data.set(this.buffer.subarray(this.begin, this.begin + n)); data.set(this.buffer.subarray(this.begin, this.begin + n));
} }
Expand All @@ -106,6 +110,10 @@ class TypedQueue<T extends TypedArray> {
} }


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

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

0 comments on commit b76707e

Please sign in to comment.