Created
August 14, 2025 10:20
-
-
Save sloev/1ec025fe22e354fb1df7a7c32c591a0e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// paulstretch.js | |
// Copyright (C) 2014 Sébastien Piquemal | |
// Copyright (C) 2006-2011 Nasca Octavian Paul | |
import * as utils from './utils.js' | |
import * as blockHelpers from './block-helpers.js' | |
import * as arrayHelpers from './array-helpers.js' | |
export class PaulStretch { | |
constructor(numberOfChannels, ratio, winSize) { | |
this.numberOfChannels = numberOfChannels; | |
this.ratio = ratio | |
this.winSize = winSize || 4096 * 4 | |
this.halfWinSize = this.winSize / 2 | |
this.samplesIn = new utils.Samples() | |
this.samplesOut = new utils.Samples() | |
this.setRatio(this.ratio) | |
this.blockIn = blockHelpers.newBlock(this.numberOfChannels, this.winSize) | |
this.blockOut = blockHelpers.newBlock(this.numberOfChannels, this.winSize) | |
this.winArray = utils.createWindow(this.winSize) | |
this.phaseArray = new Float32Array(this.halfWinSize + 1) | |
this.rephase = utils.makeRephaser(this.winSize) | |
} | |
// Sets the stretch ratio. Note that blocks that have already been processed are using the old ratio. | |
setRatio(val) { | |
this.ratio = val | |
this.samplesIn.setDisplacePos((this.winSize * 0.5) / this.ratio) | |
} | |
// Returns the number of frames waiting to be processed | |
writeQueueLength() { return this.samplesIn.getFramesAvailable() } | |
// Returns the number of frames already processed | |
readQueueLength() { return this.samplesOut.getFramesAvailable() } | |
// Reads processed samples to `block`. Returns `block`, or `null` if there wasn't enough processed frames. | |
read(block) { | |
return this.samplesOut.read(block) | |
} | |
// Pushes `block` to the processing queue. Beware! The block is not copied, so make sure not to modify it afterwards. | |
write(block) { | |
this.samplesIn.write(block) | |
} | |
// Process samples from the queue. Returns the number of processed frames that were generated | |
process() { | |
// Read a block to blockIn | |
if (this.samplesIn.read(this.blockIn) === null) return 0 | |
// get the windowed buffer | |
utils.applyWindow(this.blockIn, this.winArray) | |
// Randomize phases for each channel | |
for (let ch = 0; ch < this.numberOfChannels; ch++) { | |
arrayHelpers.map(this.phaseArray, function () { return Math.random() * 2 * Math.PI }) | |
this.rephase(this.blockIn[ch], this.phaseArray) | |
} | |
// overlap-add the output | |
utils.applyWindow(this.blockIn, this.winArray) | |
for (let ch = 0; ch < this.numberOfChannels; ch++) { | |
arrayHelpers.add( | |
this.blockIn[ch].subarray(0, this.halfWinSize), | |
this.blockOut[ch].subarray(this.halfWinSize, this.winSize) | |
) | |
} | |
// Generate the output | |
this.blockOut = this.blockIn.map(function (chArray) { return arrayHelpers.duplicate(chArray) },this) | |
this.samplesOut.write(this.blockOut.map(function (chArray) { return chArray.subarray(0, this.halfWinSize) }, this)) | |
return this.halfWinSize | |
} | |
toString() { | |
return 'PaulStretch(' + numberOfChannels + 'X' + this.winSize + ')' | |
} | |
} | |
export default PaulStretch |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment