all post-refactor post-renaming interface change in classes, help and examples

nix
Pierre Alexandre Tremblay 7 years ago
parent 6e6b6772cd
commit 87e960a0f6

@ -1,19 +1,19 @@
FluidBufCompose{ FluidBufCompose{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, srcGain = 1, dstBufNum, dstStartAt = 0, dstStartChan = 0, dstGain = 0, action; *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, gain = 1, destination, destStartFrame = 0, destStartChan = 0, destGain = 0, action;
srcBufNum = srcBufNum.asUGenInput; source = source.asUGenInput;
dstBufNum = dstBufNum.asUGenInput; destination = destination.asUGenInput;
if(srcBufNum.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw}; if(source.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw};
if(dstBufNum.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw}; if(destination.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default; server = server ? Server.default;
forkIfNeeded{ forkIfNeeded{
server.sendMsg(\cmd, \BufCompose, srcBufNum, startAt, nFrames, startChan, nChans, srcGain, dstBufNum, dstStartAt, dstStartChan, dstGain); server.sendMsg(\cmd, \BufCompose, source, startFrame, numFrames, startChan, numChans, gain, destination, destStartFrame, destStartChan, destGain);
server.sync; server.sync;
dstBufNum = server.cachedBufferAt(dstBufNum); dstBufNum.updateInfo; server.sync; destination = server.cachedBufferAt(destination); destination.updateInfo; server.sync;
action.value(dstBufNum); action.value(destination);
}; };
} }
} }

@ -1,30 +1,30 @@
FluidBufHPSS{ FluidBufHPSS{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, harmBufNum, percBufNum, resBufNum, hFiltSize = 17, pFiltSize = 31, modeFlag, htf1 = 0.1, hta1 = 0, htf2 = 0.5, hta2 = 0, ptf1 = 0.1, pta1 = 0, ptf2 = 0.5, pta2 = 0, winSize = 1024, hopSize = -1, fftSize = -1, action; *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, harmonic, percussive, residual, harmFilterSize = 17, percFilterSize = 31, maskingMode, harmThreshFreq1 = 0.1, harmThreshAmp1 = 0, harmThreshFreq2 = 0.5, harmThreshAmp2 = 0, percThreshFreq1 = 0.1, percThreshAmp1 = 0, percThreshFreq2 = 0.5, percThreshAmp2 = 0, winSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize}; var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize};
srcBufNum = srcBufNum.asUGenInput; source = source.asUGenInput;
harmBufNum = harmBufNum.asUGenInput; harmonic = harmonic.asUGenInput;
percBufNum = percBufNum.asUGenInput; percussive = percussive.asUGenInput;
resBufNum = resBufNum.asUGenInput; residual = residual.asUGenInput;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default; server = server ? Server.default;
harmBufNum = harmBufNum ? -1; harmonic = harmonic ? -1;
percBufNum = percBufNum ? -1; percussive = percussive ? -1;
resBufNum = resBufNum ? -1; residual = residual ? -1;
//NB For wrapped versions of NRT classes, we set the params for maxima to //NB For wrapped versions of NRT classes, we set the params for maxima to
//whatever has been passed in language-side (e.g maxFFTSize still exists as a parameter for the server plugin, but makes less sense here: it just needs to be set to a legal value) //whatever has been passed in language-side (e.g maxFFTSize still exists as a parameter for the server plugin, but makes less sense here: it just needs to be set to a legal value)
forkIfNeeded{ forkIfNeeded{
server.sendMsg(\cmd, \BufHPSS, srcBufNum, startAt, nFrames, startChan, nChans, harmBufNum, percBufNum, resBufNum, hFiltSize, pFiltSize, modeFlag, htf1, hta1, htf2, hta2, ptf1, pta1, ptf2, pta2, winSize, hopSize, fftSize, maxFFTSize, hFiltSize, pFiltSize); server.sendMsg(\cmd, \BufHPSS, source, startFrame, numFrames, startChan, numChans, harmonic, percussive, residual, harmFilterSize, percFilterSize, maskingMode, harmThreshFreq1, harmThreshAmp1, harmThreshFreq2, harmThreshAmp2, percThreshFreq1, percThreshAmp1, percThreshFreq2, percThreshAmp2, winSize, hopSize, fftSize, maxFFTSize, harmFilterSize, percFilterSize);
server.sync; server.sync;
if (harmBufNum != -1) {harmBufNum = server.cachedBufferAt(harmBufNum); harmBufNum.updateInfo; server.sync;} {harmBufNum = nil}; if (harmonic != -1) {harmonic = server.cachedBufferAt(harmonic); harmonic.updateInfo; server.sync;} {harmonic = nil};
if (percBufNum != -1) {percBufNum = server.cachedBufferAt(percBufNum); percBufNum.updateInfo; server.sync;} {percBufNum = nil}; if (percussive != -1) {percussive = server.cachedBufferAt(percussive); percussive.updateInfo; server.sync;} {percussive = nil};
if (resBufNum != -1) {resBufNum = server.cachedBufferAt(resBufNum); resBufNum.updateInfo;server.sync;} {resBufNum = nil}; if (residual != -1) {residual = server.cachedBufferAt(residual); residual.updateInfo; server.sync;} {residual = nil};
action.value(harmBufNum, percBufNum, resBufNum); action.value(harmonic, percussive, residual);
}; };
} }
} }

@ -1,27 +1,27 @@
FluidBufNMF { FluidBufNMF {
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, dstBufNum, dictBufNum, dictFlag = 0, actBufNum, actFlag = 0, rank = 1, nIter = 100, sortFlag = 0, winSize = 1024, hopSize = -1, fftSize = -1, winType = 0, randSeed = -1, action; *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, destination, bases, basesMode = 0, activations, actMode = 0, rank = 1, numIter = 100, winSize = 1024, hopSize = -1, fftSize = -1, winType = 0, randSeed = -1, action;
srcBufNum = srcBufNum.asUGenInput; source = source.asUGenInput;
dstBufNum = dstBufNum.asUGenInput; destination = destination.asUGenInput;
dictBufNum = dictBufNum.asUGenInput; bases = bases.asUGenInput;
actBufNum = actBufNum.asUGenInput; activations = activations.asUGenInput;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default; server = server ? Server.default;
dstBufNum = dstBufNum ? -1; destination = destination ? -1;
dictBufNum = dictBufNum ? -1; bases = bases ? -1;
actBufNum = actBufNum ? -1; activations = activations ? -1;
forkIfNeeded{ forkIfNeeded{
server.sendMsg(\cmd, \BufNMF, srcBufNum, startAt, nFrames, startChan, nChans, dstBufNum, dictBufNum, dictFlag, actBufNum, actFlag, rank, nIter, winSize, hopSize, fftSize); server.sendMsg(\cmd, \BufNMF, source, startFrame, numFrames, startChan, numChans, destination, bases, basesMode, activations, actMode, rank, numIter, winSize, hopSize, fftSize);
server.sync; server.sync;
if (dstBufNum != -1) {dstBufNum = server.cachedBufferAt(dstBufNum); dstBufNum.updateInfo; server.sync;} {dstBufNum = nil}; if (destination != -1) {destination = server.cachedBufferAt(destination); destination.updateInfo; server.sync;} {destination = nil};
if (dictBufNum != -1) {dictBufNum = server.cachedBufferAt(dictBufNum); dictBufNum.updateInfo;server.sync;} {dictBufNum = nil}; if (bases != -1) {bases = server.cachedBufferAt(bases); bases.updateInfo; server.sync;} {bases = nil};
if (actBufNum != -1) {actBufNum = server.cachedBufferAt(actBufNum); actBufNum.updateInfo;server.sync;} {actBufNum = nil}; if (activations != -1) {activations = server.cachedBufferAt(activations); activations.updateInfo; server.sync;} {activations = nil};
action.value(dstBufNum,dictBufNum,actBufNum); action.value(destination, bases, activations);
}; };
} }
} }

@ -1,21 +1,21 @@
FluidBufNoveltySlice{ FluidBufNoveltySlice{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, indBufNum, kernSize = 3, thresh = 0.8, filtSize = 1, winSize = 1024, hopSize = -1, fftSize = -1, action; *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, kernSize = 3, threshold = 0.8, filterSize = 1, winSize = 1024, hopSize = -1, fftSize = -1, action;
//var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize}; //ready for when we need it from the RT wrapper //var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize}; //ready for when we need it from the RT wrapper
srcBufNum = srcBufNum.asUGenInput; source = source.asUGenInput;
indBufNum = indBufNum.asUGenInput; indices = indices.asUGenInput;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
if(indBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(indices.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default; server = server ? Server.default;
forkIfNeeded{ forkIfNeeded{
server.sendMsg(\cmd, \BufNoveltySlice, srcBufNum, startAt, nFrames, startChan, nChans, indBufNum, kernSize, thresh, filtSize, winSize, hopSize, fftSize); server.sendMsg(\cmd, \BufNoveltySlice, source, startFrame, numFrames, startChan, numChans, indices, kernSize, threshold, filterSize, winSize, hopSize, fftSize);
server.sync; server.sync;
indBufNum = server.cachedBufferAt(indBufNum); indBufNum.updateInfo; server.sync; indices = server.cachedBufferAt(indices); indices.updateInfo; server.sync;
action.value(indBufNum); action.value(indices);
}; };
} }
} }

@ -1,13 +1,13 @@
FluidBufOnsetSlice{ FluidBufOnsetSlice{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, indBufNum, function = 0, threshold = 0.1, debounce = 2, filterSize = 5, frameDelta = 0, winSize = 1024, hopSize = -1, fftSize = -1, action; *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, function = 0, threshold = 0.1, debounce = 2, filterSize = 5, frameDelta = 0, winSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize}; var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize};
srcBufNum = srcBufNum.asUGenInput; source = source.asUGenInput;
indBufNum = indBufNum.asUGenInput; indices = indices.asUGenInput;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
if(indBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(indices.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default; server = server ? Server.default;
@ -15,10 +15,10 @@ FluidBufOnsetSlice{
//whatever has been passed in language-side (e.g maxFFTSize still exists as a parameter for the server plugin, but makes less sense here: it just needs to be set to a legal value) //whatever has been passed in language-side (e.g maxFFTSize still exists as a parameter for the server plugin, but makes less sense here: it just needs to be set to a legal value)
forkIfNeeded{ forkIfNeeded{
server.sendMsg(\cmd, \BufOnsetSlice, srcBufNum, startAt, nFrames, startChan, nChans, indBufNum, function, threshold, debounce, filterSize, frameDelta, winSize, hopSize, fftSize, maxFFTSize); server.sendMsg(\cmd, \BufOnsetSlice, source, startFrame, numFrames, startChan, numChans, indices, function, threshold, debounce, filterSize, frameDelta, winSize, hopSize, fftSize, maxFFTSize);
server.sync; server.sync;
indBufNum = server.cachedBufferAt(indBufNum); indBufNum.updateInfo; server.sync; indices = server.cachedBufferAt(indices); indices.updateInfo; server.sync;
action.value(indBufNum); action.value(indices);
}; };
} }
} }

@ -1,27 +1,27 @@
FluidBufSines{ FluidBufSines{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, sineBufNum, resBufNum, bw = 76, thresh = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1, winSize = 1024, hopSize = -1, fftSize = -1, action; *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, sines, residual, bandwidth = 76, threshold = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1, winSize = 1024, hopSize = -1, fftSize = -1, action;
var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize}; var maxFFTSize = if (fftSize == -1) {winSize.nextPowerOfTwo} {fftSize};
srcBufNum = srcBufNum.asUGenInput; source = source.asUGenInput;
sineBufNum = sineBufNum.asUGenInput; sines = sines.asUGenInput;
resBufNum = resBufNum.asUGenInput; residual = residual.asUGenInput;
if(srcBufNum.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw}; if(source.isNil) {Error("Invalid Buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default; server = server ? Server.default;
sineBufNum = sineBufNum ? -1; sines = sines ? -1;
resBufNum = resBufNum ? -1; residual = residual ? -1;
//NB For wrapped versions of NRT classes, we set the params for maxima to //NB For wrapped versions of NRT classes, we set the params for maxima to
//whatever has been passed in language-side (e.g maxFFTSize still exists as a parameter for the server plugin, but makes less sense here: it just needs to be set to a legal value) //whatever has been passed in language-side (e.g maxFFTSize still exists as a parameter for the server plugin, but makes less sense here: it just needs to be set to a legal value)
forkIfNeeded{ forkIfNeeded{
server.sendMsg(\cmd, \BufSines, srcBufNum, startAt, nFrames, startChan, nChans, sineBufNum, resBufNum, bw, thresh, minTrackLen, magWeight, freqWeight, winSize, hopSize, fftSize, maxFFTSize); server.sendMsg(\cmd, \BufSines, source, startFrame, numFrames, startChan, numChans, sines, residual, bandwidth, threshold, minTrackLen, magWeight, freqWeight, winSize, hopSize, fftSize, maxFFTSize);
server.sync; server.sync;
if (sineBufNum != -1) {sineBufNum = server.cachedBufferAt(sineBufNum); sineBufNum.updateInfo; server.sync;} {sineBufNum = nil}; if (sines != -1) {sines = server.cachedBufferAt(sines); sines.updateInfo; server.sync;} {sines = nil};
if (resBufNum != -1) {resBufNum = server.cachedBufferAt(resBufNum); resBufNum.updateInfo;server.sync;} {resBufNum = nil}; if (residual != -1) {residual = server.cachedBufferAt(residual); residual.updateInfo; server.sync;} {residual = nil};
action.value(sineBufNum,resBufNum); action.value(sines, residual);
}; };
} }
} }

@ -1,19 +1,19 @@
FluidBufTransientSlice{ FluidBufTransientSlice{
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, indBufNum, order = 20, blockSize = 256, padSize = 128, skew = 0, threshFwd = 2, threshBack = 1.1, winSize = 14, debounce = 25, minSlice = 1000, action; *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, order = 20, blockSize = 256, padSize = 128, skew = 0, threshFwd = 2, threshBack = 1.1, winSize = 14, debounce = 25, minSlice = 1000, action;
srcBufNum = srcBufNum.asUGenInput; source = source.asUGenInput;
indBufNum = indBufNum.asUGenInput; indices = indices.asUGenInput;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
if(indBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(indices.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default; server = server ? Server.default;
forkIfNeeded{ forkIfNeeded{
server.sendMsg(\cmd, \BufTransientSlice, srcBufNum, startAt, nFrames, startChan, nChans, indBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce, minSlice); server.sendMsg(\cmd, \BufTransientSlice, source, startFrame, numFrames, startChan, numChans, indices, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce, minSlice);
server.sync; server.sync;
indBufNum = server.cachedBufferAt(indBufNum); indBufNum.updateInfo; server.sync; indices = server.cachedBufferAt(indices); indices.updateInfo; server.sync;
action.value(indBufNum); action.value(indices);
}; };
} }
} }

@ -1,22 +1,22 @@
FluidBufTransients { FluidBufTransients {
*process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, resBufNum, order = 20, blockSize = 256, padSize = 128, skew = 0, threshFwd = 2, threshBack = 1.1, winSize = 14, debounce = 25, action; *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, transients, residual, order = 20, blockSize = 256, padSize = 128, skew = 0, threshFwd = 2, threshBack = 1.1, winSize = 14, debounce = 25, action;
srcBufNum = srcBufNum.asUGenInput; source = source.asUGenInput;
transBufNum = transBufNum.asUGenInput; transients = transients.asUGenInput;
resBufNum = resBufNum.asUGenInput; residual = residual.asUGenInput;
if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(source.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw};
server = server ? Server.default; server = server ? Server.default;
transBufNum = transBufNum ? -1; transients = transients ? -1;
resBufNum = resBufNum ? -1; residual = residual ? -1;
forkIfNeeded{ forkIfNeeded{
server.sendMsg(\cmd, \BufTransients, srcBufNum, startAt, nFrames, startChan, nChans, transBufNum, resBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce); server.sendMsg(\cmd, \BufTransients, source, startFrame, numFrames, startChan, numChans, transients, residual, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce);
server.sync; server.sync;
if (transBufNum != -1) {transBufNum = server.cachedBufferAt(transBufNum); transBufNum.updateInfo; server.sync;} {transBufNum = nil}; if (transients != -1) {transients = server.cachedBufferAt(transients); transients.updateInfo; server.sync;} {transients = nil};
if (resBufNum != -1) {resBufNum = server.cachedBufferAt(resBufNum); resBufNum.updateInfo;server.sync;} {resBufNum = nil}; if (residual != -1) {residual = server.cachedBufferAt(residual); residual.updateInfo; server.sync;} {residual = nil};
action.value(transBufNum,resBufNum); action.value(transients, residual);
}; };
} }
} }

@ -1,6 +1,6 @@
FluidHPSS : MultiOutUGen { FluidHPSS : MultiOutUGen {
*ar { arg in = 0, hFiltSize=17, pFiltSize = 31, modeFlag=0, htf1 = 0.1, hta1 = 0, htf2 = 0.5, hta2 = 0, ptf1 = 0.1, pta1 = 0, ptf2 = 0.5, pta2 = 0, winSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize = 16384, maxHFlitSize = 101, maxPFiltSize = 101; *ar { arg in = 0, harmFilterSize=17, percFilterSize = 31, maskingMode=0, harmThreshFreq1 = 0.1, harmThreshAmp1 = 0, harmThreshFreq2 = 0.5, harmThreshAmp2 = 0, percThreshFreq1 = 0.1, percThreshAmp1 = 0, percThreshFreq2 = 0.5, percThreshAmp2 = 0, winSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize = 16384, maxHarmFilterSize = 101, maxPercFilterSize = 101;
^this.multiNew('audio', in.asAudioRateInput(this), hFiltSize, pFiltSize, modeFlag, htf1, hta1, htf2, hta2, ptf1, pta1, ptf2, pta2, winSize, hopSize, fftSize, maxFFTSize, maxHFlitSize, maxPFiltSize) ^this.multiNew('audio', in.asAudioRateInput(this), harmFilterSize, percFilterSize, maskingMode, harmThreshFreq1, harmThreshAmp1, harmThreshFreq2, harmThreshAmp2, percThreshFreq1, percThreshAmp1, percThreshFreq2, percThreshAmp2, winSize, hopSize, fftSize, maxFFTSize, maxHarmFilterSize, maxPercFilterSize)
} }
init { arg ... theInputs; init { arg ... theInputs;
inputs = theInputs; inputs = theInputs;
@ -16,10 +16,10 @@ FluidHPSS : MultiOutUGen {
^(": maxFFTSize cannot be modulated."); ^(": maxFFTSize cannot be modulated.");
}; };
if(inputs.at(16).rate != 'scalar') { if(inputs.at(16).rate != 'scalar') {
^(": maxHFlitSize cannot be modulated."); ^(": maxHarmFilterSize cannot be modulated.");
}; };
if(inputs.at(17).rate != 'scalar') { if(inputs.at(17).rate != 'scalar') {
^(": maxPFiltSize cannot be modulated."); ^(": maxPercFilterSize cannot be modulated.");
}; };
^this.checkValidInputs; ^this.checkValidInputs;
} }

@ -1,7 +1,7 @@
FluidNMFMatch : MultiOutUGen { FluidNMFMatch : MultiOutUGen {
*kr { arg in = 0, dictBufNum, maxRank = 1, nIter = 10, winSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384; *kr { arg in = 0, bases, maxRank = 1, numIter = 10, winSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('control', in.asAudioRateInput(this), dictBufNum, maxRank, nIter, winSize, hopSize, fftSize, maxFFTSize); ^this.multiNew('control', in.asAudioRateInput(this), bases, maxRank, numIter, winSize, hopSize, fftSize, maxFFTSize);
} }
init {arg ...theInputs; init {arg ...theInputs;
@ -10,9 +10,12 @@ FluidNMFMatch : MultiOutUGen {
} }
checkInputs { checkInputs {
if(inputs.at(7).rate != 'scalar') { if(inputs.at(2).rate != 'scalar') {
^(": maxFFTSize cannot be modulated."); ^(": maxRank cannot be modulated.");
}; };
if(inputs.at(7).rate != 'scalar') {
^(": maxFFTSize cannot be modulated.");
};
^this.checkValidInputs; ^this.checkValidInputs;
} }
} }

@ -1,6 +1,6 @@
FluidOnsetSlice : UGen { FluidOnsetSlice : UGen {
*ar { arg in = 0, function = 0, thresh = 0.5, debounce = 2, filtSize = 5, frameDelta = 0, winSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384; *ar { arg in = 0, function = 0, threshold = 0.5, debounce = 2, filterSize = 5, frameDelta = 0, winSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = 16384;
^this.multiNew('audio', in.asAudioRateInput(this), function, thresh, debounce, filtSize, winSize, hopSize, frameDelta, fftSize, maxFFTSize) ^this.multiNew('audio', in.asAudioRateInput(this), function, threshold, debounce, filterSize, frameDelta, winSize, hopSize, fftSize, maxFFTSize)
} }
checkInputs { checkInputs {
if(inputs.at(9).rate != 'scalar') { if(inputs.at(9).rate != 'scalar') {

@ -1,6 +1,6 @@
FluidSines : MultiOutUGen { FluidSines : MultiOutUGen {
*ar { arg in = 0, bw = 76, thresh = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1.0, winSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize=16384; *ar { arg in = 0, bandwidth = 76, threshold = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1.0, winSize= 1024, hopSize= -1, fftSize= -1, maxFFTSize=16384;
^this.multiNew('audio', in.asAudioRateInput(this), bw, thresh, minTrackLen, magWeight, freqWeight ,winSize, hopSize, fftSize, maxFFTSize) ^this.multiNew('audio', in.asAudioRateInput(this), bandwidth, threshold, minTrackLen, magWeight, freqWeight ,winSize, hopSize, fftSize, maxFFTSize)
} }
init { arg ... theInputs; init { arg ... theInputs;
inputs = theInputs; inputs = theInputs;

@ -8,7 +8,7 @@ DESCRIPTION::
A FluidBufCompose object provides a flexible utility for combining the contents of buffers on the server. It can be used for thing like mixing down multichannel buffers, or converting from left-right stereo to mid-side. It is used extensively in all the example code of LINK::Guides/FluidDecomposition:: as part of the FluCoMa project. footnote:: A FluidBufCompose object provides a flexible utility for combining the contents of buffers on the server. It can be used for thing like mixing down multichannel buffers, or converting from left-right stereo to mid-side. It is used extensively in all the example code of LINK::Guides/FluidDecomposition:: as part of the FluCoMa project. footnote::
This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).:: This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).::
At its most simple, the object copies the content of a source buffer into a destination buffer. The flexibility comes from the various flags controlling which portions and channels of the sources to use, and by applying gains (which can be positive or negative) to the source data and the portion of the destination that would be overwritten. At its most simple, the object copies the content of a source buffer into a destination buffer. The flexibility comes from the various flags controlling which portions and channels of the source to use, and by applying gains (which can be positive or negative) to the source data and the portion of the destination that would be overwritten.
The algorithm takes a srcBuf, and writes the information at the provided dstBuf. These buffer arguments can all point to the same buffer, which gives great flexibility in transforming and reshaping. The algorithm takes a srcBuf, and writes the information at the provided dstBuf. These buffer arguments can all point to the same buffer, which gives great flexibility in transforming and reshaping.
@ -21,38 +21,38 @@ METHOD:: process
ARGUMENT:: server ARGUMENT:: server
The server on which the buffers to be processed are allocated. The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum ARGUMENT:: source
The bufNum of the source buffer. The bufNum of the source buffer.
ARGUMENT:: startAt ARGUMENT:: startFrame
The starting point (in samples) from which to copy in the source buffer. The starting point (in samples) from which to copy in the source buffer.
ARGUMENT:: nFrames ARGUMENT:: numFrames
The duration (in samples) to copy from the source buffer. The default (-1) copies the full lenght of the buffer. The duration (in samples) to copy from the source buffer. The default (-1) copies the full lenght of the buffer.
ARGUMENT:: startChan ARGUMENT:: startChan
The first channel from which to copy in the source buffer. The first channel from which to copy in the source buffer.
ARGUMENT:: nChans ARGUMENT:: numChans
The number of channels from which to copy in the source buffer. This parameter will wrap around the number of channels in the source buffer. The default (-1) copies all of the buffer's channel. The number of channels from which to copy in the source buffer. This parameter will wrap around the number of channels in the source buffer. The default (-1) copies all of the buffer's channel.
ARGUMENT:: srcGain ARGUMENT:: gain
The gain applied to the samples to be copied from the source buffer. The gain applied to the samples to be copied from the source buffer.
ARGUMENT:: dstBufNum ARGUMENT:: destination
The bufNum of the destination buffer. The bufNum of the destination buffer.
ARGUMENT:: dstStartAt ARGUMENT:: destStartFrame
The time offset (in samples) in the destination buffer to start writing the source at. The destination buffer will be resized if the portion to copy is overflowing. The time offset (in samples) in the destination buffer to start writing the source at. The destination buffer will be resized if the portion to copy is overflowing.
ARGUMENT:: dstStartChan ARGUMENT:: destStartChan
The channel offest in the destination buffer to start writing the source at. The destination buffer will be resized if the number of channels to copy is overflowing. The channel offest in the destination buffer to start writing the source at. The destination buffer will be resized if the number of channels to copy is overflowing.
ARGUMENT:: dstGain ARGUMENT:: destGain
The gain applied to the samples in the region of the destination buffer over which the source is to be copied. The default value (0) will overwrite that section of the destination buffer, and a value of 1.0 would sum the source to the material that was present. The gain applied to the samples in the region of the destination buffer over which the source is to be copied. The default value (0) will overwrite that section of the destination buffer, and a value of 1.0 would sum the source to the material that was present.
ARGUMENT:: action ARGUMENT:: action
A Function to be evaluated once the offline process has finished and dstBufNum instance variables have been updated on the client side. The function will be passed dstBufNum as an argument. A Function to be evaluated once the offline process has finished and destination instance variables have been updated on the client side. The function will be passed destination as an argument.
RETURNS:: RETURNS::
Nothing, as the destination buffer is declared in the function call. Nothing, as the destination buffer is declared in the function call.
@ -71,29 +71,29 @@ d = Buffer.new(s);
) )
// with basic params (basic summing of each full buffer in all dimensions) // with basic params (basic summing of each full buffer in all dimensions)
FluidBufCompose.process(s, srcBufNum: b.bufnum, dstBufNum: d.bufnum); FluidBufCompose.process(s, source: b.bufnum, destination: d.bufnum);
FluidBufCompose.process(s, srcBufNum: c.bufnum, dstBufNum: d.bufnum, dstGain: 1.0); FluidBufCompose.process(s, source: c.bufnum, destination: d.bufnum, destGain: 1.0);
d.query; d.query;
d.play; d.play;
//constructing a mono buffer, with a quiet punch from the synth, with a choked piano resonance from the left channel //constructing a mono buffer, with a quiet punch from the synth, with a choked piano resonance from the left channel
d.free; d = Buffer.new(s); d.free; d = Buffer.new(s);
FluidBufCompose.process(s, srcBufNum: b.bufnum, nFrames: 9000, srcGain: 0.5, dstBufNum: d.bufnum); FluidBufCompose.process(s, source: b.bufnum, numFrames: 9000, gain: 0.5, destination: d.bufnum);
FluidBufCompose.process(s, srcBufNum: c.bufnum, startAt:30000, nFrames:44100, nChans:1, srcGain:0.9, dstBufNum: d.bufnum, dstGain: 1.0); FluidBufCompose.process(s, source: c.bufnum, startFrame:30000, numFrames:44100, numChans:1, gain:0.9, destination: d.bufnum, destGain: 1.0);
d.query; d.query;
d.play; d.play;
//constructing a stereo buffer, with the end of the mono synth in both channels, with a piano resonance in swapped stereo //constructing a stereo buffer, with the end of the mono synth in both channels, with a piano resonance in swapped stereo
d.free; d = Buffer.new(s); d.free; d = Buffer.new(s);
FluidBufCompose.process(s, srcBufNum: b.bufnum, startAt: 441000, nChans: 2, srcGain: 0.6, dstBufNum: d.bufnum); FluidBufCompose.process(s, source: b.bufnum, startFrame: 441000, numChans: 2, gain: 0.6, destination: d.bufnum);
FluidBufCompose.process(s, srcBufNum: c.bufnum, nFrames: 78000, startChan: 1, nChans: 2, srcGain: 0.5, dstStartAt: 22050, dstBufNum: d.bufnum, dstGain: 1.0); FluidBufCompose.process(s, source: c.bufnum, numFrames: 78000, startChan: 1, numChans: 2, gain: 0.5, destStartFrame: 22050, destination: d.bufnum, destGain: 1.0);
d.query; d.query;
d.play; d.play;
//constructing a one second buffer: the first second of each buffer, the mono synth on the right, the piano on the left //constructing a one second buffer: the first second of each buffer, the mono synth on the right, the piano on the left
d.free; d = Buffer.new(s); d.free; d = Buffer.new(s);
FluidBufCompose.process(s, srcBufNum: b.bufnum, nFrames: 44100, nChans: 1, dstStartChan: 1, dstBufNum: d.bufnum); FluidBufCompose.process(s, source: b.bufnum, numFrames: 44100, numChans: 1, destStartChan: 1, destination: d.bufnum);
FluidBufCompose.process(s, srcBufNum: c.bufnum, nFrames:44100, nChans:1, dstBufNum: d.bufnum, dstGain: 1.0); FluidBufCompose.process(s, source: c.bufnum, numFrames:44100, numChans:1, destination: d.bufnum, destGain: 1.0);
d.query; d.query;
d.play; d.play;
:: ::
@ -112,10 +112,10 @@ f = Buffer.new(s);
// encode the mid (in c) and the side (in d) // encode the mid (in c) and the side (in d)
( (
FluidBufCompose.process(s,b.bufnum, nChans: 1, srcGain: -3.0.dbamp, dstBufNum: c.bufnum); FluidBufCompose.process(s,b.bufnum, numChans: 1, gain: -3.0.dbamp, destination: c.bufnum);
FluidBufCompose.process(s,b.bufnum, nChans: 1, srcGain: -3.0.dbamp, dstBufNum: d.bufnum); FluidBufCompose.process(s,b.bufnum, numChans: 1, gain: -3.0.dbamp, destination: d.bufnum);
FluidBufCompose.process(s,b.bufnum, nChans: 1, srcGain: -3.0.dbamp, startChan: 1, dstBufNum: c.bufnum, dstGain: 1.0); FluidBufCompose.process(s,b.bufnum, numChans: 1, gain: -3.0.dbamp, startChan: 1, destination: c.bufnum, destGain: 1.0);
FluidBufCompose.process(s,b.bufnum, nChans: 1, srcGain: -3.0.dbamp * -1.0, startChan: 1, dstBufNum: d.bufnum, dstGain: 1.0); FluidBufCompose.process(s,b.bufnum, numChans: 1, gain: -3.0.dbamp * -1.0, startChan: 1, destination: d.bufnum, destGain: 1.0);
) )
// (optional) compare auraly the stereo with the MS // (optional) compare auraly the stereo with the MS
@ -131,7 +131,7 @@ b.play;
e.free; e = Buffer.new(s); e.free; e = Buffer.new(s);
( (
[1.0, -1.0].do({ arg x,y; [1.0, -1.0].do({ arg x,y;
FluidBufCompose.process(s, d.bufnum, srcGain: x, dstStartAt: y, dstBufNum: e.bufnum, dstGain: 1.0); FluidBufCompose.process(s, d.bufnum, gain: x, destStartFrame: y, destination: e.bufnum, destGain: 1.0);
}); });
) )
@ -139,7 +139,7 @@ e.free; e = Buffer.new(s);
e.free; e = Buffer.new(s); e.free; e = Buffer.new(s);
( (
[0.8, -0.32, -0.24, -0.16, -0.08].do({ arg x,y; [0.8, -0.32, -0.24, -0.16, -0.08].do({ arg x,y;
FluidBufCompose.process(s, d.bufnum, srcGain: x, dstStartAt: y, dstBufNum: e.bufnum, dstGain: 1.0); FluidBufCompose.process(s, d.bufnum, gain: x, destStartFrame: y, destination: e.bufnum, destGain: 1.0);
}); });
) )
@ -147,7 +147,7 @@ e.free; e = Buffer.new(s);
e.free; e = Buffer.new(s); e.free; e = Buffer.new(s);
( (
[0.982494, -0.066859, -0.064358, -0.061897, -0.059477, -0.057098, -0.054761, -0.052466, -0.050215, -0.048007, -0.045843, -0.043724, -0.041649, -0.03962, -0.037636, -0.035697, -0.033805, -0.031959, -0.030159, -0.028406, -0.026699, -0.025038, -0.023425, -0.021857, -0.020337].do({ arg x,y; [0.982494, -0.066859, -0.064358, -0.061897, -0.059477, -0.057098, -0.054761, -0.052466, -0.050215, -0.048007, -0.045843, -0.043724, -0.041649, -0.03962, -0.037636, -0.035697, -0.033805, -0.031959, -0.030159, -0.028406, -0.026699, -0.025038, -0.023425, -0.021857, -0.020337].do({ arg x,y;
FluidBufCompose.process(s, d.bufnum, srcGain: x, dstStartAt: y, dstBufNum: e.bufnum, dstGain: 1.0); FluidBufCompose.process(s, d.bufnum, gain: x, destStartFrame: y, destination: e.bufnum, destGain: 1.0);
}); });
) )
@ -158,9 +158,9 @@ e.play;
// decode the MS back to stereo // decode the MS back to stereo
( (
FluidBufCompose.process(s,c.bufnum, nChans: 2, srcGain: -3.0.dbamp, dstBufNum: f.bufnum); FluidBufCompose.process(s,c.bufnum, numChans: 2, gain: -3.0.dbamp, destination: f.bufnum);
FluidBufCompose.process(s,e.bufnum, srcGain: -3.0.dbamp, dstBufNum: f.bufnum, dstGain: 1.0); FluidBufCompose.process(s,e.bufnum, gain: -3.0.dbamp, destination: f.bufnum, destGain: 1.0);
FluidBufCompose.process(s,e.bufnum, srcGain: -3.0.dbamp * -1.0, dstBufNum: f.bufnum, dstStartChan: 1, dstGain: 1.0); FluidBufCompose.process(s,e.bufnum, gain: -3.0.dbamp * -1.0, destination: f.bufnum, destStartChan: 1, destGain: 1.0);
) )
// query and play // query and play

@ -14,7 +14,7 @@ Driedger, Jonathan, Meinard Uller, and Sascha Disch. 2014. Extending Harmonic
The algorithm takes a buffer in, and divides it into two or three outputs, depending on the mode: LIST:: The algorithm takes a buffer in, and divides it into two or three outputs, depending on the mode: LIST::
## an harmonic component; ## an harmonic component;
## a percussive component; ## a percussive component;
## a residual of the previous two if the flag is set to inter-dependant thresholds. See the modeFlag below.:: ## a residual of the previous two if the flag is set to inter-dependant thresholds. See the maskingMode below.::
It is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote:: It is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote::
This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899). This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).
@ -31,68 +31,68 @@ This is the method that calls for the HPSS to be calculated on a given source bu
ARGUMENT:: server ARGUMENT:: server
The server on which the buffers to be processed are allocated. The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum ARGUMENT:: source
The index of the buffer to use as the source material. The channels of multichannel buffers will be processed sequentially. The index of the buffer to use as the source material. The channels of multichannel buffers will be processed sequentially.
ARGUMENT:: startAt ARGUMENT:: startFrame
Where in the srcBuf should the NMF process start, in samples. Where in the srcBuf should the HPSS process start, in samples.
ARGUMENT:: nFrames ARGUMENT:: numFrames
How many frames should be processed. How many frames should be processed.
ARGUMENT:: startChan ARGUMENT:: startChan
For multichannel srcBuf, which channel to start processing at. For multichannel srcBuf, which channel to start processing at.
ARGUMENT:: nChans ARGUMENT:: numChans
For multichannel srcBuf, how many channels should be processed. For multichannel srcBuf, how many channels should be processed.
ARGUMENT:: harmBufNum ARGUMENT:: harmonic
The index of the buffer where the extracted harmonic component will be reconstructed. The index of the buffer where the extracted harmonic component will be reconstructed.
ARGUMENT:: percBufNum ARGUMENT:: percussive
The index of the buffer where the extracted percussive component will be reconstructed. The index of the buffer where the extracted percussive component will be reconstructed.
ARGUMENT:: resBufNum ARGUMENT:: residual
The index of the buffer where the residual component will be reconstructed in mode 2. The index of the buffer where the residual component will be reconstructed in mode 2.
ARGUMENT:: hFiltSize ARGUMENT:: harmFilterSize
The size, in spectral frames, of the median filter for the harmonic component. Must be an odd number, >= 3. The size, in spectral frames, of the median filter for the harmonic component. Must be an odd number, >= 3.
ARGUMENT:: pFiltSize ARGUMENT:: percFilterSize
The size, in spectral bins, of the median filter for the percussive component. Must be an odd number, >=3 The size, in spectral bins, of the median filter for the percussive component. Must be an odd number, >=3
ARGUMENT:: modeFlag ARGUMENT:: maskingMode
The way the masking is applied to the original spectrogram. (0,1,2) The way the masking is applied to the original spectrogram. (0,1,2)
table:: table::
## 0 || The traditional soft mask used in Fitzgerald's original method of 'Wiener-inspired' filtering. Complimentary, soft masks are made for the harmonic and percussive parts by allocating some fraction of a point in time-frequency to each. This provides the fewest artefacts, but the weakest separation. The two resulting buffers will sum to exactly the original material. ## 0 || The traditional soft mask used in Fitzgerald's original method of 'Wiener-inspired' filtering. Complimentary, soft masks are made for the harmonic and percussive parts by allocating some fraction of a point in time-frequency to each. This provides the fewest artefacts, but the weakest separation. The two resulting buffers will sum to exactly the original material.
## 1 || Relative mode - Better separation, with more artefacts. The harmonic mask is constructed using a binary decision, based on whether a threshold is exceeded at a given time-frequency point (these are set using htf1, hta1, htf2, hta2, see below). The percussive mask is then formed as the inverse of the harmonic one, meaning that as above, the two components will sum to the original sound. ## 1 || Relative mode - Better separation, with more artefacts. The harmonic mask is constructed using a binary decision, based on whether a threshold is exceeded at a given time-frequency point (these are set using harmThreshFreq1, harmThreshAmp1, harmThreshFreq2, harmThreshAmp2, see below). The percussive mask is then formed as the inverse of the harmonic one, meaning that as above, the two components will sum to the original sound.
## 2 || Inter-dependent mode - Thresholds can be varied independently, but are coupled in effect. Binary masks are made for each of the harmonic and percussive components, and the masks are converted to soft at the end so that everything null sums even if the params are independent, that is what makes it harder to control. These aren't guranteed to cover the whole sound; in this case the 'leftovers' will placed into a third buffer. ## 2 || Inter-dependent mode - Thresholds can be varied independently, but are coupled in effect. Binary masks are made for each of the harmonic and percussive components, and the masks are converted to soft at the end so that everything null sums even if the params are independent, that is what makes it harder to control. These aren't guranteed to cover the whole sound; in this case the 'leftovers' will placed into a third buffer.
:: ::
ARGUMENT:: htf1 ARGUMENT:: harmThreshFreq1
In modes 1 and 2, the frequency of the low part of the threshold for the harmonic filter (0-1) In modes 1 and 2, the frequency of the low part of the threshold for the harmonic filter (0-1)
ARGUMENT:: hta1 ARGUMENT:: harmThreshAmp1
In modes 1 and 2, the threshold of the low part for the harmonic filter. That threshold applies to all frequencies up to htf1: how much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic. In modes 1 and 2, the threshold of the low part for the harmonic filter. That threshold applies to all frequencies up to harmThreshFreq1: how much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: htf2 ARGUMENT:: harmThreshFreq2
In modes 1 and 2, the frequency of the hight part of the threshold for the harmonic filter. (0-1) In modes 1 and 2, the frequency of the hight part of the threshold for the harmonic filter. (0-1)
ARGUMENT:: hta2 ARGUMENT:: harmThreshAmp2
In modes 1 and 2, the threshold of the high part for the harmonic filter. That threshold applies to all frequencies above htf2. The threshold between htf1 and htf2 is interpolated between hta1 and hta2. How much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic. In modes 1 and 2, the threshold of the high part for the harmonic filter. That threshold applies to all frequencies above harmThreshFreq2. The threshold between harmThreshFreq1 and harmThreshFreq2 is interpolated between harmThreshAmp1 and harmThreshAmp2. How much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: ptf1 ARGUMENT:: percThreshFreq1
In mode 2, the frequency of the low part of the threshold for the percussive filter. (0-1) In mode 2, the frequency of the low part of the threshold for the percussive filter. (0-1)
ARGUMENT:: pta1 ARGUMENT:: percThreshAmp1
In mode 2, the threshold of the low part for the percussive filter. That threshold applies to all frequencies up to ptf1. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive. In mode 2, the threshold of the low part for the percussive filter. That threshold applies to all frequencies up to percThreshFreq1. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: ptf2 ARGUMENT:: percThreshFreq2
In mode 2, the frequency of the hight part of the threshold for the percussive filter. (0-1) In mode 2, the frequency of the hight part of the threshold for the percussive filter. (0-1)
ARGUMENT:: pta2 ARGUMENT:: percThreshAmp2
In mode 2, the threshold of the high part for the percussive filter. That threshold applies to all frequencies above ptf2. The threshold between ptf1 and ptf2 is interpolated between pta1 and pta2. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive. In mode 2, the threshold of the high part for the percussive filter. That threshold applies to all frequencies above percThreshFreq2. The threshold between percThreshFreq1 and percThreshFreq2 is interpolated between percThreshAmp1 and percThreshAmp2. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: winSize ARGUMENT:: winSize
The window size in samples. As HPSS relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty The window size in samples. As HPSS relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty
@ -104,7 +104,7 @@ ARGUMENT:: fftSize
The inner FFT/IFFT size. It should be at least 4 samples long; at least the size of the window; and a power of 2. Making it larger than the window size provides interpolation in frequency. The inner FFT/IFFT size. It should be at least 4 samples long; at least the size of the window; and a power of 2. Making it larger than the window size provides interpolation in frequency.
ARGUMENT:: action ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [harmBufNum, percBufNum, resBufNum] as an argument. A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [harmonic, percussive, residual] as an argument.
RETURNS:: RETURNS::
Nothing, as the various destination buffers are declared in the function call. Nothing, as the various destination buffers are declared in the function call.
@ -112,7 +112,7 @@ RETURNS::
Discussion:: Discussion::
HPSS works by using median filters on the spectral magnitudes of a sound. It hinges on a simple modelling assumption that tonal components will tend to yield concentrations of energy across time, spread out in frequency, and percussive components will manifest as concentrations of energy across frequency, spread out in time. By using median filters across time and frequency respectively, we get initial esitmates of the tonal-ness / transient-ness of a point in time and frequency. These are then combined into 'masks' that are applied to the orginal spectral data in order to produce a separation. HPSS works by using median filters on the spectral magnitudes of a sound. It hinges on a simple modelling assumption that tonal components will tend to yield concentrations of energy across time, spread out in frequency, and percussive components will manifest as concentrations of energy across frequency, spread out in time. By using median filters across time and frequency respectively, we get initial esitmates of the tonal-ness / transient-ness of a point in time and frequency. These are then combined into 'masks' that are applied to the orginal spectral data in order to produce a separation.
The modeFlag parameter provides different approaches to combinging estimates and producing masks. Some settings (especially in modes 1 & 2) will provide better separation but with more artefacts. These can, in principle, be ameliorated by applying smoothing filters to the masks before transforming back to the time-domain (not yet implemented). The maskingMode parameter provides different approaches to combinging estimates and producing masks. Some settings (especially in modes 1 & 2) will provide better separation but with more artefacts. These can, in principle, be ameliorated by applying smoothing filters to the masks before transforming back to the time-domain (not yet implemented).
EXAMPLES:: EXAMPLES::
@ -129,17 +129,15 @@ code::
( (
Routine{ Routine{
t = Main.elapsedTime; t = Main.elapsedTime;
FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum); FluidBufHPSS.process(s, b.bufnum, harmonic: c.bufnum, percussive: d.bufnum);
s.sync; s.sync;
(Main.elapsedTime - t).postln; (Main.elapsedTime - t).postln;
}.play }.play
) )
//query and play the harmonic //play the harmonic
c.query;
c.play; c.play;
//querry and play the percussive //play the percussive
d.query;
d.play; d.play;
//nullsumming tests //nullsumming tests
@ -149,20 +147,17 @@ d.play;
( (
Routine{ Routine{
t = Main.elapsedTime; t = Main.elapsedTime;
FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum, resBufNum:e.bufnum, hFiltSize:31, modeFlag:2, htf1: 0.005, hta1: 7.5, htf2: 0.168, hta2: 7.5, ptf1: 0.004, pta1: 26.5, ptf2: 0.152, pta2: 26.5); FluidBufHPSS.process(s, b, harmonic: c, percussive: d, residual:e, harmFilterSize:31, maskingMode:2, harmThreshFreq1: 0.005, harmThreshAmp1: 7.5, harmThreshFreq2: 0.168, harmThreshAmp2: 7.5, percThreshFreq1: 0.004, percThreshAmp1: 26.5, percThreshFreq2: 0.152, percThreshAmp2: 26.5,winSize:4096,hopSize:512);
s.sync; s.sync;
(Main.elapsedTime - t).postln; (Main.elapsedTime - t).postln;
}.play }.play
) )
//query and play the harmonic //play the harmonic
c.query;
c.play; c.play;
//query and play the percussive //play the percussive
d.query;
d.play; d.play;
//query and play the residual //play the residual
e.query;
e.play; e.play;
//still nullsumming //still nullsumming

@ -8,19 +8,19 @@ DESCRIPTION::
The FluidBufNMF object decomposes the spectrum of a sound into a number of components using Non-Negative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. Learning the Parts of Objects by Non-Negative Matrix Factorization. Nature 401 (6755): 78891. https://doi.org/10.1038/44565. The FluidBufNMF object decomposes the spectrum of a sound into a number of components using Non-Negative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. Learning the Parts of Objects by Non-Negative Matrix Factorization. Nature 401 (6755): 78891. https://doi.org/10.1038/44565.
::. NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored. ::. NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored.
The algorithm takes a buffer in and divides it into a number of components, determined by the rank argument. It works iteratively, by trying to find a combination of spectral templates ('dictionaries') and envelopes ('activations') that yield the original magnitude spectrogram when added together. By and large, there is no unique answer to this question (i.e. there are different ways of accounting for an evolving spectrum in terms of some set of templates and envelopes). In its basic form, NMF is a form of unsupervised learning: it starts with some random data and then converges towards something that minimizes the distance between its generated data and the original:it tends to converge very quickly at first and then level out. Fewer iterations mean less processing, but also less predictable results. The algorithm takes a buffer in and divides it into a number of components, determined by the rank argument. It works iteratively, by trying to find a combination of spectral templates ('bases') and envelopes ('activations') that yield the original magnitude spectrogram when added together. By and large, there is no unique answer to this question (i.e. there are different ways of accounting for an evolving spectrum in terms of some set of templates and envelopes). In its basic form, NMF is a form of unsupervised learning: it starts with some random data and then converges towards something that minimizes the distance between its generated data and the original:it tends to converge very quickly at first and then level out. Fewer iterations mean less processing, but also less predictable results.
The object can return either or all of the following: LIST:: The object can return either or all of the following: LIST::
## a spectral contour of each component in the form of a magnitude spectrogram (called a dictionary in NMF lingo); ## a spectral contour of each component in the form of a magnitude spectrogram (called a basis in NMF lingo);
## an amplitude envelope of each component in the form of gains for each consecutive frame of the underlying spectrogram (called an activation in NMF lingo); ## an amplitude envelope of each component in the form of gains for each consecutive frame of the underlying spectrogram (called an activation in NMF lingo);
## an audio reconstruction of each components in the time domain. :: ## an audio reconstruction of each components in the time domain. ::
The dictionaries and activations can be used to make a kind of vocoder based on what NMF has 'learned' from the original data. Alternatively, taking the matrix product of a dictionary and an activation will yield a synthetic magnitude spectrogram of a component (which could be reconsructed, given some phase informaiton from somewhere). The bases and activations can be used to make a kind of vocoder based on what NMF has 'learned' from the original data. Alternatively, taking the matrix product of a basis and an activation will yield a synthetic magnitude spectrogram of a component (which could be reconsructed, given some phase informaiton from somewhere).
Some additional options and flexibility can be found through combinations of the dictFlag and actFlag arguments. If these flags are set to 1, the object expects to be supplied with pre-formed spectra (or envelopes) that will be used as seeds for the decomposition, providing more guided results. When set to 2, the supplied buffers won't be updated, so become templates to match against instead. Note that having both dictionaries and activations set to 2 doesn't make sense, so the object will complain. Some additional options and flexibility can be found through combinations of the basesMode and actMode arguments. If these flags are set to 1, the object expects to be supplied with pre-formed spectra (or envelopes) that will be used as seeds for the decomposition, providing more guided results. When set to 2, the supplied buffers won't be updated, so become templates to match against instead. Note that having both bases and activations set to 2 doesn't make sense, so the object will complain.
If supplying pre-formed data, it's up to the user to make sure that the supplied buffers are the right size: LIST:: If supplying pre-formed data, it's up to the user to make sure that the supplied buffers are the right size: LIST::
## dictionaries must be STRONG::(fft size / 2) + 1:: frames and STRONG::(rank * input channels):: channels ## bases must be STRONG::(fft size / 2) + 1:: frames and STRONG::(rank * input channels):: channels
## activations must be STRONG::(input frames / hopSize) + 1:: frames and STRONG::(rank * input channels):: channels ## activations must be STRONG::(input frames / hopSize) + 1:: frames and STRONG::(rank * input channels):: channels
:: ::
@ -41,39 +41,39 @@ This is the method that calls for the factorisation to be calculated on a given
ARGUMENT:: server ARGUMENT:: server
The server on which the buffers to be processed are allocated. The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum ARGUMENT:: source
The index of the buffer to use as the source material to be decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially. The index of the buffer to use as the source material to be decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startAt ARGUMENT:: startFrame
Where in the srcBuf should the NMF process start, in sample. Where in the srcBuf should the NMF process start, in sample.
ARGUMENT:: nFrames ARGUMENT:: numFrames
How many frames should be processed. How many frames should be processed.
ARGUMENT:: startChan ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first. For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: nChans ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed. For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: dstBufNum ARGUMENT:: destination
The index of the buffer where the different reconstructed ranks will be reconstructed. The buffer will be resized to STRONG::rank * numChannelsProcessed:: channels and STRONG::sourceDuration:: lenght. If STRONG::nil:: is provided, the reconstruction will not happen. The index of the buffer where the different reconstructed ranks will be reconstructed. The buffer will be resized to STRONG::rank * numChannelsProcessed:: channels and STRONG::sourceDuration:: lenght. If STRONG::nil:: is provided, the reconstruction will not happen.
ARGUMENT:: dictBufNum ARGUMENT:: bases
The index of the buffer where the different dictionaries will be written to and/or read from: the behaviour is set in the following argument. If STRONG::nil:: is provided, no dictionary will be returned. The index of the buffer where the different bases will be written to and/or read from: the behaviour is set in the following argument. If STRONG::nil:: is provided, no bases will be returned.
ARGUMENT:: dictFlag ARGUMENT:: basesMode
This flag decides of how the dictionnary buffer passed as the previous argument is treated. This flag decides of how the basis buffer passed as the previous argument is treated.
table:: table::
## 0 || The dictionaries are seeded randomly, and the resulting ones will be written after the process in the passed buffer. The buffer is resized to STRONG::rank * numChannelsProcessed:: channels and STRONG::(fftSize / 2 + 1):: lenght. ## 0 || The bases are seeded randomly, and the resulting ones will be written after the process in the passed buffer. The buffer is resized to STRONG::rank * numChannelsProcessed:: channels and STRONG::(fftSize / 2 + 1):: lenght.
## 1 || The passed buffer is considered as seed for the dictionaries. Its dimensions should match the values above. The resulting dictionaries will replace the seed ones. ## 1 || The passed buffer is considered as seed for the bases. Its dimensions should match the values above. The resulting bases will replace the seed ones.
## 2 || The passed buffer is considered as a template for the dictionaries, and will therefore not change. Its dictionaries should match the values above. ## 2 || The passed buffer is considered as a template for the bases, and will therefore not change. Its bases should match the values above.
:: ::
ARGUMENT:: actBufNum ARGUMENT:: activations
The index of the buffer where the different activations will be written to and/or read from: the behaviour is set in the following argument. If STRONG::nil:: is provided, no activation will be returned. The index of the buffer where the different activations will be written to and/or read from: the behaviour is set in the following argument. If STRONG::nil:: is provided, no activation will be returned.
ARGUMENT:: actFlag ARGUMENT:: actMode
This flag decides of how the activation buffer passed as the previous argument is treated. This flag decides of how the activation buffer passed as the previous argument is treated.
table:: table::
## 0 || The activations are seeded randomly, and the resulting ones will be written after the process in the passed buffer. The buffer is resized to STRONG::rank * numChannelsProcessed:: channels and STRONG::(sourceDuration / hopsize + 1):: lenght. ## 0 || The activations are seeded randomly, and the resulting ones will be written after the process in the passed buffer. The buffer is resized to STRONG::rank * numChannelsProcessed:: channels and STRONG::(sourceDuration / hopsize + 1):: lenght.
@ -84,12 +84,9 @@ ARGUMENT:: actFlag
ARGUMENT:: rank ARGUMENT:: rank
The number of elements the NMF algorithm will try to divide the spectrogram of the source in. The number of elements the NMF algorithm will try to divide the spectrogram of the source in.
ARGUMENT:: nIter ARGUMENT:: numIter
The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU expensive, lower numbers will be more unpredictable in quality. The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU expensive, lower numbers will be more unpredictable in quality.
ARGUMENT:: sortFlag
This allows to choose between the different methods of sorting the ranks in order to get similar sonic qualities on a given rank (not implemented yet)
ARGUMENT:: winSize ARGUMENT:: winSize
The window size. As NMF relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty The window size. As NMF relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty
@ -106,7 +103,7 @@ ARGUMENT:: randSeed
The NMF process needs to seed its starting point. If specified, the same values will be used. The default of -1 will randomly assign them. (not implemented yet) The NMF process needs to seed its starting point. If specified, the same values will be used. The default of -1 will randomly assign them. (not implemented yet)
ARGUMENT:: action ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [dstBufNum, dictBufNum, actBufNum] as an argument. A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [destination, bases, activations] as an argument.
RETURNS:: RETURNS::
Nothing, as the various destination buffers are declared in the function call. Nothing, as the various destination buffers are declared in the function call.
@ -131,8 +128,8 @@ Routine {
b.sine2([500],[1], false, false); b.sine2([500],[1], false, false);
c.sine2([5000],[1],false, false); c.sine2([5000],[1],false, false);
s.sync; s.sync;
FluidBufCompose.process(s,b.bufnum, dstBufNum:d.bufnum); FluidBufCompose.process(s,b.bufnum, destination:d.bufnum);
FluidBufCompose.process(s,c.bufnum, dstStartAt:44100, dstBufNum:d.bufnum, dstGain:1); FluidBufCompose.process(s,c.bufnum, destStartFrame:44100, destination:d.bufnum, destGain:1);
s.sync; s.sync;
d.query; d.query;
}.play; }.play;
@ -145,7 +142,7 @@ d.play //////(beware !!!! loud!!!)
( (
// separate them in 2 ranks // separate them in 2 ranks
Routine { Routine {
FluidBufNMF.process(s, d.bufnum, dstBufNum:e.bufnum, dictBufNum: f.bufnum, actBufNum:g.bufnum, rank:2); FluidBufNMF.process(s, d.bufnum, destination:e.bufnum, bases: f.bufnum, activations:g.bufnum, rank:2);
s.sync; s.sync;
e.query; e.query;
f.query; f.query;
@ -156,7 +153,7 @@ Routine {
// look at the resynthesised separated signal // look at the resynthesised separated signal
e.plot; e.plot;
// look at the dictionaries signal for 2 spikes // look at the bases signal for 2 spikes
f.plot; f.plot;
// look at the activations // look at the activations
@ -177,26 +174,20 @@ y = Buffer.new(s);
~fft_size = 1024; ~fft_size = 1024;
~frame_size = 512; ~frame_size = 512;
~hop_size = 256; ~hop_size = 256;
~which_rank = 3; ~which_rank = 1;
) )
// matrix factorisation, requesting everything // matrix factorisation, requesting everything
( (
Routine{ Routine{
t = Main.elapsedTime; t = Main.elapsedTime;
FluidBufNMF.process(s,b.bufnum, 0,-1,0,-1,c.bufnum,x.bufnum,0,y.bufnum,0,5,100,0,~frame_size,~hop_size,~fft_size); FluidBufNMF.process(s,b, 0,-1,0,-1,c,x,0,y,0,5,100,~frame_size,~hop_size,~fft_size);
s.sync; s.sync;
(Main.elapsedTime - t).postln; (Main.elapsedTime - t).postln;
s.sync;
c.query;
s.sync;
x.query;
s.sync;
y.query;
}.play }.play
) )
//look at the resynthesised ranks, the dictionaries and the activations //look at the resynthesised ranks, the bases and the activations
c.plot;x.plot; y.plot; c.plot;x.plot; y.plot;
//null test of the sum of sources //null test of the sum of sources
{(PlayBuf.ar(5,c.bufnum,doneAction:2).sum)+(-1*PlayBuf.ar(1,b.bufnum,doneAction:2))}.play {(PlayBuf.ar(5,c.bufnum,doneAction:2).sum)+(-1*PlayBuf.ar(1,b.bufnum,doneAction:2))}.play
@ -207,7 +198,7 @@ c.plot;x.plot; y.plot;
//play a single source //play a single source
{PlayBuf.ar(5,c.bufnum,doneAction:2)[~which_rank].dup}.play {PlayBuf.ar(5,c.bufnum,doneAction:2)[~which_rank].dup}.play
//play noise using one of the dictionaries as filter. //play noise using one of the bases as filter.
( (
{ {
var chain; var chain;
@ -239,7 +230,7 @@ c.plot;x.plot; y.plot;
) )
:: ::
STRONG::Fixed Dictionnaries:: The process can be trained, and the learnt dictionaries or activations can be used as templates. STRONG::Fixed Bases: The process can be trained, and the learnt bases or activations can be used as templates.::
CODE:: CODE::
@ -263,28 +254,28 @@ Routine {
// find the rank that has the picking sound by changing which channel to listen to // find the rank that has the picking sound by changing which channel to listen to
( (
~element = 4; ~element = 1;
{PlayBuf.ar(10,c.bufnum)[~element]}.play {PlayBuf.ar(10,c.bufnum)[~element]}.play
) )
// copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel // copy all the other ranks on itself and the picking basis as the sole component of the 1st channel
( (
Routine{ Routine{
z = (0..9); z = (0..9);
FluidBufCompose.process(s, x.bufnum, startChan: z.removeAt(~element), nChans: 1, dstBufNum: e.bufnum); FluidBufCompose.process(s, x.bufnum, startChan: z.removeAt(~element), numChans: 1, destination: e.bufnum);
s.sync; s.sync;
e.query; e.query;
s.sync; s.sync;
z.do({|chan| FluidBufCompose.process(s, x.bufnum, startChan:chan, nChans: 1, dstStartChan: 1, dstBufNum: e.bufnum, dstGain:1)}); z.do({|chan| FluidBufCompose.process(s, x.bufnum, startChan:chan, numChans: 1, destStartChan: 1, destination: e.bufnum, destGain:1)});
s.sync; s.sync;
e.query; e.query;
}.play; }.play;
) )
//process the whole file, splitting it with the 2 trained dictionnaries //process the whole file, splitting it with the 2 trained bases
( (
Routine{ Routine{
FluidBufNMF.process(s, b.bufnum, dstBufNum: c.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum: y.bufnum, rank:2); FluidBufNMF.process(s, b, destination: c, bases: e, basesMode: 2, activations: y, rank:2);
s.sync; s.sync;
c.query; c.query;
}.play; }.play;
@ -297,7 +288,7 @@ c.play
{(PlayBuf.ar(2,c.bufnum,doneAction:2).sum)-(PlayBuf.ar(1,b.bufnum,doneAction:2))}.play {(PlayBuf.ar(2,c.bufnum,doneAction:2).sum)-(PlayBuf.ar(1,b.bufnum,doneAction:2))}.play
:: ::
STRONG::Updating Dictionnaries:: The process can update dictionaries provided as seed. STRONG::Updating Bases: The process can update bases provided as seed.::
CODE:: CODE::
( (
@ -316,8 +307,8 @@ Routine {
b.sine2([500],[1], false, false); b.sine2([500],[1], false, false);
c.sine2([5000],[1],false, false); c.sine2([5000],[1],false, false);
s.sync; s.sync;
FluidBufCompose.process(s,b.bufnum, dstBufNum:d.bufnum); FluidBufCompose.process(s,b.bufnum, destination:d.bufnum);
FluidBufCompose.process(s,c.bufnum, dstStartAt:44100, dstBufNum:d.bufnum, dstGain:1); FluidBufCompose.process(s,c.bufnum, destStartFrame:44100, destination:d.bufnum, destGain:1);
s.sync; s.sync;
d.query; d.query;
}.play; }.play;
@ -328,7 +319,7 @@ d.plot
d.play //////(beware !!!! loud!!!) d.play //////(beware !!!! loud!!!)
( (
//make a seeding dictionary of 3 ranks: //make a seeding basis of 3 ranks:
var highpass, lowpass, direct; var highpass, lowpass, direct;
highpass = Array.fill(513,{|i| (i < 50).asInteger}); highpass = Array.fill(513,{|i| (i < 50).asInteger});
lowpass = 1 - highpass; lowpass = 1 - highpass;
@ -336,14 +327,14 @@ direct = Array.fill(513,0.1);
e.setn(0,[highpass, lowpass, direct].flop.flat); e.setn(0,[highpass, lowpass, direct].flop.flat);
) )
//check the dictionary: a steep lowpass, a steep highpass, and a small DC //check the basis: a steep lowpass, a steep highpass, and a small DC
e.plot e.plot
e.query e.query
( (
// use the seeding dictionary, without updating // use the seeding basis, without updating
Routine { Routine {
FluidBufNMF.process(s, d.bufnum, dstBufNum:f.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum:g.bufnum, rank:3); FluidBufNMF.process(s, d, destination:f, bases: e, basesMode: 2, activations:g, rank:3);
s.sync; s.sync;
e.query; e.query;
f.query; f.query;
@ -354,16 +345,16 @@ Routine {
// look at the resynthesised separated signal // look at the resynthesised separated signal
f.plot; f.plot;
// look at the dictionaries that have not changed // look at the bases that have not changed
e.plot; e.plot;
// look at the activations // look at the activations
g.plot; g.plot;
( (
// use the seeding dictionary, with updating this time // use the seeding bases, with updating this time
Routine { Routine {
FluidBufNMF.process(s, d.bufnum, dstBufNum:f.bufnum, dictBufNum: e.bufnum, dictFlag: 1, actBufNum:g.bufnum, rank:3); FluidBufNMF.process(s, d, destination:f, bases: e, basesMode: 1, activations:g, rank:3);
s.sync; s.sync;
e.query; e.query;
f.query; f.query;
@ -374,9 +365,9 @@ Routine {
// look at the resynthesised separated signal // look at the resynthesised separated signal
f.plot; f.plot;
// look at the dictionaries that have now updated in place (with the 3rd channel being more focused // look at the bases that have now updated in place (with the 3rd channel being more focused
e.plot; e.plot;
// look at the activations (sharper 3rd rank at transitions) // look at the activations (sharper 3rd rank at transitions)
g.plot; g.plot;
:: ::

@ -18,31 +18,31 @@ This is the method that calls for the slicing to be calculated on a given source
ARGUMENT:: server ARGUMENT:: server
The server on which the buffers to be processed are allocated. The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum ARGUMENT:: source
The index of the buffer to use as the source material to be sliced through novelty identification. The different channels of multichannel buffers will be summed. The index of the buffer to use as the source material to be sliced through novelty identification. The different channels of multichannel buffers will be summed.
ARGUMENT:: startAt ARGUMENT:: startFrame
Where in the srcBuf should the slicing process start, in sample. Where in the srcBuf should the slicing process start, in sample.
ARGUMENT:: nFrames ARGUMENT:: numFrames
How many frames should be processed. How many frames should be processed.
ARGUMENT:: startChan ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed. For multichannel srcBuf, which channel should be processed.
ARGUMENT:: nChans ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be summed. For multichannel srcBuf, how many channel should be summed.
ARGUMENT:: indBufNum ARGUMENT:: indices
The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis. The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis.
ARGUMENT:: kernSize ARGUMENT:: kernSize
The granularity of the window in which the algorithm looks for change, in samples. A small number will be sensitive to short term changes, and a large number should look for long term changes. The granularity of the window in which the algorithm looks for change, in samples. A small number will be sensitive to short term changes, and a large number should look for long term changes.
ARGUMENT:: thresh ARGUMENT:: threshold
The normalised threshold, between 0 an 1, on the novelty curve to consider it a segmentation point. The normalised threshold, between 0 an 1, on the novelty curve to consider it a segmentation point.
ARGUMENT:: filtSize ARGUMENT:: filterSize
The size of a smoothing filter that is applied on the novelty curve. A larger filter filter size allows for cleaner cuts on very sharp changes. The size of a smoothing filter that is applied on the novelty curve. A larger filter filter size allows for cleaner cuts on very sharp changes.
ARGUMENT:: winSize ARGUMENT:: winSize
@ -55,7 +55,7 @@ ARGUMENT:: fftSize
The inner FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision. The inner FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision.
ARGUMENT:: action ARGUMENT:: action
A Function to be evaluated once the offline process has finished and indBufNum instance variables have been updated on the client side. The function will be passed indBufNum as an argument. A Function to be evaluated once the offline process has finished and indices instance variables have been updated on the client side. The function will be passed indices as an argument.
RETURNS:: RETURNS::
Nothing, as the various destination buffers are declared in the function call. Nothing, as the various destination buffers are declared in the function call.
@ -74,7 +74,7 @@ c = Buffer.new(s);
// with basic params // with basic params
Routine{ Routine{
t = Main.elapsedTime; t = Main.elapsedTime;
FluidBufNoveltySlice.process(s,b.bufnum, indBufNum: c.bufnum, thresh:0.6); FluidBufNoveltySlice.process(s,b.bufnum, indices: c.bufnum, threshold:0.6);
s.sync; s.sync;
(Main.elapsedTime - t).postln; (Main.elapsedTime - t).postln;
}.play }.play
@ -108,7 +108,7 @@ c = Buffer.new(s);
) )
// process with a given filterSize // process with a given filterSize
FluidBufNoveltySlice.process(s,b.bufnum, indBufNum: c.bufnum, kernSize:31, thresh:0.3, filtSize:0) FluidBufNoveltySlice.process(s,b.bufnum, indices: c.bufnum, kernSize:31, threshold:0.3, filterSize:0)
//check the number of slices: it is the number of frames in the transBuf minus the boundary index. //check the number of slices: it is the number of frames in the transBuf minus the boundary index.
c.query; c.query;

@ -0,0 +1,115 @@
TITLE:: FluidBufOnsetSlice
SUMMARY:: Spectral Difference-Based Audio Buffer Slicer
CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
This class implements many spectral-based onset detection functions, most of them taken from the literature. (http://www.dafx.ca/proceedings/papers/p_133.pdf) Some are already available in SuperCollider's LINK::Classes/Onsets:: object yet not as offline processes. It is part of the Fluid Decomposition Toolkit of the FluCoMa project.footnote::This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).::
The process will return a buffer which contains indices (in sample) of estimated starting points of different slices.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the slicing to be calculated on a given source buffer.
ARGUMENT:: server
The server on which the buffers to be processed are allocated.
ARGUMENT:: source
The index of the buffer to use as the source material to be sliced through novelty identification. The different channels of multichannel buffers will be summed.
ARGUMENT:: startFrame
Where in the srcBuf should the slicing process start, in sample.
ARGUMENT:: numFrames
How many frames should be processed.
ARGUMENT:: startChan
For multichannel sources, which channel should be processed.
ARGUMENT:: numChans
For multichannel sources, how many channel should be summed.
ARGUMENT:: indices
The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis.
ARGUMENT:: function
The function used to derive a difference curve between spectral frames. It can be any of the following:
TABLE::
##0 || Energy || thresholds on (sum of squares of magnitudes / nBins) (like Onsets \power)
##1 || HFC || thresholds on (sum of (squared magnitudes * binNum) / nBins)
##2 || SpectralFlux || thresholds on (diffence in magnitude between consecutive frames, half rectified)
##3 || MKL || thresholds on (sum of log of magnitude ratio per bin) (or equivalent: sum of difference of the log magnitude per bin) (like Onsets \mkl)
##4 || IS || (WILL PROBABLY BE REMOVED) Itakura - Saito divergence (see literature)
##5 || Cosine || thresholds on (cosine distance between comparison frames)
##6 || PhaseDev || takes the past 2 frames, projects to the current, as anticipated if it was a steady state, then compute the sum of the differences, on which it thresholds (like Onsets \phase)
##7 || WPhaseDev || same as PhaseDev, but weighted by the magnitude in order to remove chaos noise floor (like Onsets \wphase)
##8 || ComplexDev || same as PhaseDev, but in the complex domain - the anticipated amp is considered steady, and the phase is projected, then a complex subtraction is done with the actual present frame. The sum of magnitudes is used to threshold (like Onsets \complex)
##9 || RComplexDev || same as above, but rectified (like Onsets \rcomplex)
::
ARGUMENT:: threshold
The thresholding of a new slice. Value ranges are different for each function, from 0 upwards.
ARGUMENT:: debounce
The minimum duration of a slice in number of hopSize.
ARGUMENT:: filterSize
The size of a smoothing filter that is applied on the novelty curve. A larger filter filter size allows for cleaner cuts on very sharp changes.
ARGUMENT:: frameDelta
For certain functions (HFC, SpectralFlux, MKL, Cosine), the distance does not have to be computed between consecutive frames. By default (0) it is, otherwise this sets the distane between the comparison window in samples.
ARGUMENT:: winSize
The window size. As spectral differencing relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty
ARGUMENT:: hopSize
The window hope size. As spectral differencing relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of winSize (overlap of 2).
ARGUMENT:: fftSize
The inner FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision. The -1 default value will default to windowSize.
ARGUMENT:: action
A Function to be evaluated once the offline process has finished and indices instance variables have been updated on the client side. The function will be passed indices as an argument.
RETURNS::
Nothing, as the various destination buffers are declared in the function call.
EXAMPLES::
CODE::
(
//prep some buffers
b = Buffer.read(s,File.realpath(FluidBufOnsetSlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
c = Buffer.new(s);
)
(
// with basic params
Routine{
t = Main.elapsedTime;
FluidBufOnsetSlice.process(s,b.bufnum, indices: c.bufnum, threshold:0.5);
s.sync;
(Main.elapsedTime - t).postln;
}.play
)
//check the number of slices: it is the number of frames in the transBuf minus the boundary index.
c.query;
//loops over a splice with the MouseX
(
{
BufRd.ar(1, b.bufnum,
Phasor.ar(0,1,
BufRd.kr(1, c.bufnum,
MouseX.kr(0, BufFrames.kr(c.bufnum) - 1), 0, 1),
BufRd.kr(1, c.bufnum,
MouseX.kr(1, BufFrames.kr(c.bufnum)), 0, 1),
BufRd.kr(1,c.bufnum,
MouseX.kr(0, BufFrames.kr(c.bufnum) - 1), 0, 1)), 0, 1);
}.play;
)
::

@ -21,31 +21,31 @@ METHOD:: process
ARGUMENT:: server ARGUMENT:: server
The server on which the buffers to be processed are allocated. The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum ARGUMENT:: source
The index of the buffer to use as the source material to be decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially. The index of the buffer to use as the source material to be decomposed through the sinusoidal modelling process. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startAt ARGUMENT:: startFrame
Where in the srcBuf should the process start, in sample. Where in the srcBuf should the process start, in sample.
ARGUMENT:: nFrames ARGUMENT:: numFrames
How many frames should be processed. How many frames should be processed.
ARGUMENT:: startChan ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first. For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: nChans ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed. For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: sineBufNum ARGUMENT:: sines
The index of the buffer where the extracted sinusoidal component will be reconstructed. The index of the buffer where the extracted sinusoidal component will be reconstructed.
ARGUMENT:: resBufNum ARGUMENT:: residual
The index of the buffer where the residual of the sinusoidal component will be reconstructed. The index of the buffer where the residual of the sinusoidal component will be reconstructed.
ARGUMENT:: bw ARGUMENT:: bandwidth
The width in bins of the fragment of the fft window that is considered a normal deviation for a potential continuous sinusoidal track. It has an effect on CPU cost: the widest is more accurate but more computationally expensive. The width in bins of the fragment of the fft window that is considered a normal deviation for a potential continuous sinusoidal track. It has an effect on CPU cost: the widest is more accurate but more computationally expensive.
ARGUMENT:: thresh ARGUMENT:: threshold
The normalised threshold, between 0 an 1, to consider a peak as a sinusoidal component from the normalized cross-correlation. The normalised threshold, between 0 an 1, to consider a peak as a sinusoidal component from the normalized cross-correlation.
ARGUMENT:: minTrackLen ARGUMENT:: minTrackLen
@ -67,7 +67,7 @@ ARGUMENT:: fftSize
The inner FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision. The inner FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision.
ARGUMENT:: action ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [sineBufNum, resBufNum] as an argument. A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [sines, residual] as an argument.
RETURNS:: RETURNS::
Nothing, as the various destination buffers are declared in the function call. Nothing, as the various destination buffers are declared in the function call.
@ -86,7 +86,7 @@ d = Buffer.new(s);
( (
Routine{ Routine{
t = Main.elapsedTime; t = Main.elapsedTime;
FluidBufSines.process(s, b.bufnum, sineBufNum: c.bufnum, resBufNum:d.bufnum); FluidBufSines.process(s, b.bufnum, sines: c.bufnum, residual:d.bufnum);
s.sync; s.sync;
(Main.elapsedTime - t).postln; (Main.elapsedTime - t).postln;
}.play }.play

@ -18,22 +18,22 @@ METHOD:: process
ARGUMENT:: server ARGUMENT:: server
The server on which the buffers to be processed are allocated. The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum ARGUMENT:: source
The index of the buffer to use as the source material to be sliced through transient identification. The different channels of multichannel buffers will be summed. The index of the buffer to use as the source material to be sliced through transient identification. The different channels of multichannel buffers will be summed.
ARGUMENT:: startAt ARGUMENT:: startFrame
Where in the srcBuf should the slicing process start, in sample. Where in the srcBuf should the slicing process start, in sample.
ARGUMENT:: nFrames ARGUMENT:: numFrames
How many frames should be processed. How many frames should be processed.
ARGUMENT:: startChan ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed. For multichannel srcBuf, which channel should be processed.
ARGUMENT:: nChans ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be summed. For multichannel srcBuf, how many channel should be summed.
ARGUMENT:: indBufNum ARGUMENT:: indices
The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis. The index of the buffer where the indices (in sample) of the estimated starting points of slices will be written. The first and last points are always the boundary points of the analysis.
ARGUMENT:: order ARGUMENT:: order
@ -64,7 +64,7 @@ ARGUMENT:: minSlice
The minimum duration of a slice in samples. The minimum duration of a slice in samples.
ARGUMENT:: action ARGUMENT:: action
A Function to be evaluated once the offline process has finished and indBufNum instance variables have been updated on the client side. The function will be passed indBufNum as an argument. A Function to be evaluated once the offline process has finished and indices instance variables have been updated on the client side. The function will be passed indices as an argument.
RETURNS:: RETURNS::
Nothing, as the destination buffer is declared in the function call. Nothing, as the destination buffer is declared in the function call.
@ -83,7 +83,7 @@ c = Buffer.new(s);
( (
Routine{ Routine{
t = Main.elapsedTime; t = Main.elapsedTime;
FluidBufTransientSlice.process(s,b.bufnum, transBufNum:c.bufnum); FluidBufTransientSlice.process(s,b, indices:c);
s.sync; s.sync;
(Main.elapsedTime - t).postln; (Main.elapsedTime - t).postln;
}.play }.play
@ -106,12 +106,11 @@ c.query;
}.play; }.play;
) )
// with everything changed to make it much better, at the cost of computation time (only 10 seconds are processed here) // with everything changed to make it much better, at the cost of computation time (only 10 seconds are processed here)
( (
Routine{ Routine{
t = Main.elapsedTime; t = Main.elapsedTime;
FluidBufTransientSlice.process(s,b.bufnum, 0, 220500, 0, 1, c.bufnum, 200, 2048, 1024, 1, 3, 1, 15, 30, 4410); FluidBufTransientSlice.process(s,b, 0, 220500, 0, 1, c, 200, 2048, 1024, 1, 3, 1, 15, 30, 4410);
s.sync; s.sync;
(Main.elapsedTime - t).postln; (Main.elapsedTime - t).postln;
}.play }.play

@ -23,25 +23,25 @@ This is the method that calls for the transient extraction to be performed on a
ARGUMENT:: server ARGUMENT:: server
The server on which the buffers to be processed are allocated. The server on which the buffers to be processed are allocated.
ARGUMENT:: srcBufNum ARGUMENT:: source
The index of the buffer to use as the source material to be decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially. The index of the buffer to use as the source material to be decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially.
ARGUMENT:: startAt ARGUMENT:: startFrame
Where in the srcBuf should the NMF process start, in sample. Where in the srcBuf should the NMF process start, in sample.
ARGUMENT:: nFrames ARGUMENT:: numFrames
How many frames should be processed. How many frames should be processed.
ARGUMENT:: startChan ARGUMENT:: startChan
For multichannel srcBuf, which channel should be processed first. For multichannel srcBuf, which channel should be processed first.
ARGUMENT:: nChans ARGUMENT:: numChans
For multichannel srcBuf, how many channel should be processed. For multichannel srcBuf, how many channel should be processed.
ARGUMENT:: transBufNum ARGUMENT:: transients
The index of the buffer where the extracted transient component will be reconstructed. The index of the buffer where the extracted transient component will be reconstructed.
ARGUMENT:: resBufNum ARGUMENT:: residual
The index of the buffer where the estimated continuous component will be reconstructed. The index of the buffer where the estimated continuous component will be reconstructed.
ARGUMENT:: order ARGUMENT:: order
@ -69,7 +69,7 @@ ARGUMENT:: debounce
The window size in sample within which positive detections will be clumped together to avoid overdetecting in time. The window size in sample within which positive detections will be clumped together to avoid overdetecting in time.
ARGUMENT:: action ARGUMENT:: action
A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [transBufNum, resBufNum] as an argument. A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [transients, residual] as an argument.
RETURNS:: RETURNS::
Nothing, as the various destination buffers are declared in the function call. Nothing, as the various destination buffers are declared in the function call.
@ -88,7 +88,7 @@ d = Buffer.new(s);
( (
Routine{ Routine{
t = Main.elapsedTime; t = Main.elapsedTime;
FluidBufTransients.process(s,b.bufnum, transBufNum:c.bufnum, resBufNum:d.bufnum); FluidBufTransients.process(s,b.bufnum, transients:c.bufnum, residual:d.bufnum);
s.sync; s.sync;
(Main.elapsedTime - t).postln; (Main.elapsedTime - t).postln;
}.play }.play

@ -24,7 +24,7 @@ RETURNS::
EXAMPLES:: EXAMPLES::
Summing with the inverse (gain of -1) with a delay of the latency gives us CPU-expensive silence. Summing with the inverse (gain of -1) gives us CPU-expensive silence.
CODE:: CODE::
{ var source = PinkNoise.ar(0.1); source + FluidGain.ar(source,-1); }.play { var source = PinkNoise.ar(0.1); source + FluidGain.ar(source,-1); }.play
:: ::

@ -12,7 +12,7 @@ Driedger, Jonathan, Meinard Uller, and Sascha Disch. 2014. Extending Harmonic
The algorithm takes an audio in, and divides it into two or three outputs, depending on the mode: LIST:: The algorithm takes an audio in, and divides it into two or three outputs, depending on the mode: LIST::
## an harmonic component; ## an harmonic component;
## a percussive component; ## a percussive component;
## a residual of the previous two if the flag is set to inter-dependant thresholds. See the modeFlag below.:: ## a residual of the previous two if the flag is set to inter-dependant thresholds. See the maskingMode below.::
It is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote:: It is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote::
This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899). This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).
@ -27,44 +27,44 @@ METHOD:: ar
ARGUMENT:: in ARGUMENT:: in
The input to be processed. The input to be processed.
ARGUMENT:: hFiltSize ARGUMENT:: harmFilterSize
The size, in spectral frames, of the median filter for the harmonic component. Must be an odd number, >= 3. The size, in spectral frames, of the median filter for the harmonic component. Must be an odd number, >= 3.
ARGUMENT:: pFiltSize ARGUMENT:: percFilterSize
The size, in spectral bins, of the median filter for the percussive component. Must be an odd number, >=3 The size, in spectral bins, of the median filter for the percussive component. Must be an odd number, >=3
ARGUMENT:: modeFlag ARGUMENT:: maskingMode
The way the masking is applied to the original spectrogram. (0,1,2) The way the masking is applied to the original spectrogram. (0,1,2)
table:: table::
## 0 || The traditional soft mask used in Fitzgerald's original method of 'Wiener-inspired' filtering. Complimentary, soft masks are made for the harmonic and percussive parts by allocating some fraction of a point in time-frequency to each. This provides the fewest artefacts, but the weakest separation. The two resulting buffers will sum to exactly the original material. ## 0 || The traditional soft mask used in Fitzgerald's original method of 'Wiener-inspired' filtering. Complimentary, soft masks are made for the harmonic and percussive parts by allocating some fraction of a point in time-frequency to each. This provides the fewest artefacts, but the weakest separation. The two resulting buffers will sum to exactly the original material.
## 1 || Relative mode - Better separation, with more artefacts. The harmonic mask is constructed using a binary decision, based on whether a threshold is exceeded at a given time-frequency point (these are set using htf1, hta1, htf2, hta2, see below). The percussive mask is then formed as the inverse of the harmonic one, meaning that as above, the two components will sum to the original sound. ## 1 || Relative mode - Better separation, with more artefacts. The harmonic mask is constructed using a binary decision, based on whether a threshold is exceeded at a given time-frequency point (these are set using harmThreshFreq1, harmThreshAmp1, harmThreshFreq2, harmThreshAmp2, see below). The percussive mask is then formed as the inverse of the harmonic one, meaning that as above, the two components will sum to the original sound.
## 2 || Inter-dependent mode - Thresholds can be varied independently, but are coupled in effect. Binary masks are made for each of the harmonic and percussive components, and the masks are converted to soft at the end so that everything null sums even if the params are independent, that is what makes it harder to control. These aren't guranteed to cover the whole sound; in this case the 'leftovers' will placed into a third buffer. ## 2 || Inter-dependent mode - Thresholds can be varied independently, but are coupled in effect. Binary masks are made for each of the harmonic and percussive components, and the masks are converted to soft at the end so that everything null sums even if the params are independent, that is what makes it harder to control. These aren't guranteed to cover the whole sound; in this case the 'leftovers' will placed into a third buffer.
:: ::
ARGUMENT:: htf1 ARGUMENT:: harmThreshFreq1
In modes 1 and 2, the frequency of the low part of the threshold for the harmonic filter (0-1) In modes 1 and 2, the frequency of the low part of the threshold for the harmonic filter (0-1)
ARGUMENT:: hta1 ARGUMENT:: harmThreshAmp1
In modes 1 and 2, the threshold of the low part for the harmonic filter. That threshold applies to all frequencies up to htf1: how much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic. In modes 1 and 2, the threshold of the low part for the harmonic filter. That threshold applies to all frequencies up to harmThreshFreq1: how much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: htf2 ARGUMENT:: harmThreshFreq2
In modes 1 and 2, the frequency of the hight part of the threshold for the harmonic filter. (0-1) In modes 1 and 2, the frequency of the hight part of the threshold for the harmonic filter. (0-1)
ARGUMENT:: hta2 ARGUMENT:: harmThreshAmp2
In modes 1 and 2, the threshold of the high part for the harmonic filter. That threshold applies to all frequencies above htf2. The threshold between htf1 and htf2 is interpolated between hta1 and hta2. How much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic. In modes 1 and 2, the threshold of the high part for the harmonic filter. That threshold applies to all frequencies above harmThreshFreq2. The threshold between harmThreshFreq1 and harmThreshFreq2 is interpolated between harmThreshAmp1 and harmThreshAmp2. How much more powerful (in dB) the harmonic median filter needs to be than the percussive median filter for this bin to be counted as harmonic.
ARGUMENT:: ptf1 ARGUMENT:: percThreshFreq1
In mode 2, the frequency of the low part of the threshold for the percussive filter. (0-1) In mode 2, the frequency of the low part of the threshold for the percussive filter. (0-1)
ARGUMENT:: pta1 ARGUMENT:: percThreshAmp1
In mode 2, the threshold of the low part for the percussive filter. That threshold applies to all frequencies up to ptf1. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive. In mode 2, the threshold of the low part for the percussive filter. That threshold applies to all frequencies up to percThreshFreq1. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: ptf2 ARGUMENT:: percThreshFreq2
In mode 2, the frequency of the hight part of the threshold for the percussive filter. (0-1) In mode 2, the frequency of the hight part of the threshold for the percussive filter. (0-1)
ARGUMENT:: pta2 ARGUMENT:: percThreshAmp2
In mode 2, the threshold of the high part for the percussive filter. That threshold applies to all frequencies above ptf2. The threshold between ptf1 and ptf2 is interpolated between pta1 and pta2. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive. In mode 2, the threshold of the high part for the percussive filter. That threshold applies to all frequencies above percThreshFreq2. The threshold between percThreshFreq1 and percThreshFreq2 is interpolated between percThreshAmp1 and percThreshAmp2. How much more powerful (in dB) the percussive median filter needs to be than the harmonic median filter for this bin to be counted as percussive.
ARGUMENT:: winSize ARGUMENT:: winSize
The window size. As sinusoidal estimation relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty The window size. As sinusoidal estimation relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty
@ -78,21 +78,21 @@ ARGUMENT:: fftSize
ARGUMENT:: maxFFTSize ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated. How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
ARGUMENT::maxHFlitSize ARGUMENT::maxHarmFilterSize
How large can the harmonic filter be modulated to (hFiltSize), by allocating memory at instantiation time. This cannot be modulated. How large can the harmonic filter be modulated to (harmFilterSize), by allocating memory at instantiation time. This cannot be modulated.
ARGUMENT:: maxPFiltSize ARGUMENT:: maxPercFilterSize
How large can the percussive filter be modulated to (pFiltSize), by allocating memory at instantiation time. This cannot be modulated. How large can the percussive filter be modulated to (percFilterSize), by allocating memory at instantiation time. This cannot be modulated.
RETURNS:: RETURNS::
An array of three audio streams: [0] is the harmonic part extracted, [1] is the percussive part extracted, [2] is the rest. The latency between the input and the output is ((hFiltSize - 1) * hopSize) + winSize) samples. An array of three audio streams: [0] is the harmonic part extracted, [1] is the percussive part extracted, [2] is the rest. The latency between the input and the output is ((harmFilterSize - 1) * hopSize) + winSize) samples.
Discussion:: Discussion::
HPSS works by using median filters on the spectral magnitudes of a sound. It hinges on a simple modelling assumption that tonal components will tend to yield concentrations of energy across time, spread out in frequency, and percussive components will manifest as concentrations of energy across frequency, spread out in time. By using median filters across time and frequency respectively, we get initial esitmates of the tonal-ness / transient-ness of a point in time and frequency. These are then combined into 'masks' that are applied to the orginal spectral data in order to produce a separation. HPSS works by using median filters on the spectral magnitudes of a sound. It hinges on a simple modelling assumption that tonal components will tend to yield concentrations of energy across time, spread out in frequency, and percussive components will manifest as concentrations of energy across frequency, spread out in time. By using median filters across time and frequency respectively, we get initial esitmates of the tonal-ness / transient-ness of a point in time and frequency. These are then combined into 'masks' that are applied to the orginal spectral data in order to produce a separation.
The modeFlag parameter provides different approaches to combinging estimates and producing masks. Some settings (especially in modes 1 & 2) will provide better separation but with more artefacts. These can, in principle, be ameliorated by applying smoothing filters to the masks before transforming back to the time-domain (not yet implemented). The maskingMode parameter provides different approaches to combinging estimates and producing masks. Some settings (especially in modes 1 & 2) will provide better separation but with more artefacts. These can, in principle, be ameliorated by applying smoothing filters to the masks before transforming back to the time-domain (not yet implemented).
EXAMPLES:: EXAMPLES::
@ -102,7 +102,7 @@ CODE::
b = Buffer.read(s,File.realpath(FluidHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); b = Buffer.read(s,File.realpath(FluidHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
// run with basic parameters (left is harmonic, right is percussive) // run with basic parameters (left is harmonic, right is percussive)
{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),maxFFTSize:LFNoise0.kr())}.play {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1))}.play
// run in mode 1 // run in mode 1
{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,51,1,0.05,40,0.1,-40)}.play {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,51,1,0.05,40,0.1,-40)}.play
@ -115,6 +115,6 @@ b = Buffer.read(s,File.realpath(FluidHPSS.class.filenameSymbol).dirname.withTrai
// the residual stream // the residual stream
{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),15,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[2].dup}.play {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),15,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[2].dup}.play
// null test (the process add a latency of ((hFiltSize - 1) * hopSize) + winSize) samples // null test (the process add a latency of ((harmFilterSize - 1) * hopSize) + winSize) samples
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidHPSS.ar(sig, 17, 31).sum - DelayN.ar(sig, 1, ((((17 - 1) * 512) + 1024) / s.sampleRate))]}.play {var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidHPSS.ar(sig, 17, 31).sum - DelayN.ar(sig, 1, ((((17 - 1) * 512) + 1024) / s.sampleRate))]}.play
:: ::

@ -1,5 +1,5 @@
TITLE:: FluidNMFMatch TITLE:: FluidNMFMatch
SUMMARY:: Real-Time Non-Negative Matrix Factorisation with Fixed Dictionaries SUMMARY:: Real-Time Non-Negative Matrix Factorisation with Fixed Bases
CATEGORIES:: Libraries>FluidDecomposition CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF
@ -20,18 +20,18 @@ FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. f
CLASSMETHODS:: CLASSMETHODS::
METHOD:: kr METHOD:: kr
The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::maxRank:: . If the dictionary buffer has fewer than maxRank channels, the remaining outputs will be zeroed. The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::maxRank:: . If the bases buffer has fewer than maxRank channels, the remaining outputs will be zeroed.
ARGUMENT:: in ARGUMENT:: in
The signal input to the factorisation process. The signal input to the factorisation process.
ARGUMENT:: dictBufNum ARGUMENT:: bases
The server index of the buffer containing the different dictionaries that the input signal will be matched against. Dictionaries must be STRONG::(fft size / 2) + 1:: frames. If the buffer has more than STRONG::maxRank:: channels, the excess will be ignored. The server index of the buffer containing the different bases that the input signal will be matched against. Bases must be STRONG::(fft size / 2) + 1:: frames. If the buffer has more than STRONG::maxRank:: channels, the excess will be ignored.
ARGUMENT::maxRank ARGUMENT::maxRank
The maximum number of elements the NMF algorithm will try to divide the spectrogram of the source in. This dictates the number of output channelsfor the ugen. This cannot be modulated. The maximum number of elements the NMF algorithm will try to divide the spectrogram of the source in. This dictates the number of output channelsfor the ugen. This cannot be modulated.
ARGUMENT:: nIter ARGUMENT:: numIter
The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU intensive, lower numbers will be more unpredictable in quality. The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU intensive, lower numbers will be more unpredictable in quality.
ARGUMENT:: winSize ARGUMENT:: winSize
@ -47,7 +47,7 @@ ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated. How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS:: RETURNS::
A multichannel kr output, giving for each dictionary component the activation amount. A multichannel kr output, giving for each basis component the activation amount.
EXAMPLES:: EXAMPLES::
@ -67,8 +67,8 @@ Routine {
b.sine2([500],[1], false, false); b.sine2([500],[1], false, false);
c.sine2([5000],[1],false, false); c.sine2([5000],[1],false, false);
s.sync; s.sync;
FluidBufCompose.process(s,b.bufnum, dstBufNum:d.bufnum); FluidBufCompose.process(s,b.bufnum, destination:d.bufnum);
FluidBufCompose.process(s,c.bufnum, dstStartAt:44100, dstBufNum:d.bufnum, dstGain:1); FluidBufCompose.process(s,c.bufnum, destStartFrame:44100, destination:d.bufnum, destGain:1);
s.sync; s.sync;
d.query; d.query;
}.play; }.play;
@ -81,7 +81,7 @@ d.play //////(beware !!!! loud!!!)
( (
// separate them in 2 ranks // separate them in 2 ranks
Routine { Routine {
FluidBufNMF.process(s, d.bufnum, dictBufNum: e.bufnum, rank:2); FluidBufNMF.process(s, d.bufnum, bases: e.bufnum, rank:2);
s.sync; s.sync;
e.query; e.query;
}.play }.play
@ -125,15 +125,15 @@ Routine {
{PlayBuf.ar(10,c.bufnum)[~element]}.play {PlayBuf.ar(10,c.bufnum)[~element]}.play
) )
// copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel // copy all the other ranks on itself and the picking basis as the sole component of the 1st channel
( (
Routine{ Routine{
z = (0..9); z = (0..9);
FluidBufCompose.process(s, x.bufnum, startChan: z.removeAt(~element), nChans: 1, dstBufNum: e.bufnum); FluidBufCompose.process(s, x.bufnum, startChan: z.removeAt(~element), numChans: 1, destination: e.bufnum);
s.sync; s.sync;
e.query; e.query;
s.sync; s.sync;
z.do({|chan| FluidBufCompose.process(s, x.bufnum, startChan:chan, nChans: 1, dstStartChan: 1, dstBufNum: e.bufnum, dstGain:1)}); z.do({|chan| FluidBufCompose.process(s, x.bufnum, startChan:chan, numChans: 1, destStartChan: 1, destination: e.bufnum, destGain:1)});
s.sync; s.sync;
e.query; e.query;
}.play; }.play;
@ -141,7 +141,7 @@ Routine{
e.plot; e.plot;
//using this trained dictionary we can see the envelop (activations) of each rank //using this trained basis we can see the envelop (activations) of each rank
{FluidNMFMatch.kr(PlayBuf.ar(1,b.bufnum),e.bufnum,2,fftSize:2048)}.plot(1); {FluidNMFMatch.kr(PlayBuf.ar(1,b.bufnum),e.bufnum,2,fftSize:2048)}.plot(1);
// the left/top activations are before, the pick before the sustain. // the left/top activations are before, the pick before the sustain.
@ -160,7 +160,7 @@ e.plot;
// compress the signal to send to the delays // compress the signal to send to the delays
todelay = DelayN.ar(source,0.1, 800/44100, //delaying it to compensate for FluidNMFMatch's latency todelay = DelayN.ar(source,0.1, 800/44100, //delaying it to compensate for FluidNMFMatch's latency
LagUD.ar(K2A.ar(FluidNMFMatch.kr(source,e.bufnum,2,fftSize:2048)[0]), //reading the channel of the activations on the pick dictionary LagUD.ar(K2A.ar(FluidNMFMatch.kr(source,e.bufnum,2,fftSize:2048)[0]), //reading the channel of the activations on the pick basis
80/44100, // lag uptime (compressor's attack) 80/44100, // lag uptime (compressor's attack)
1000/44100, // lag downtime (compressor's decay) 1000/44100, // lag downtime (compressor's decay)
(1/(2.dbamp) // compressor's threshold inverted (1/(2.dbamp) // compressor's threshold inverted
@ -215,17 +215,17 @@ Routine {
// copy at least one other rank to a third rank, a sort of left-over channel // copy at least one other rank to a third rank, a sort of left-over channel
( (
Routine{ Routine{
FluidBufCompose.process(s, x.bufnum, startChan:~dog, nChans: 1, dstBufNum: e.bufnum); FluidBufCompose.process(s, x.bufnum, startChan:~dog, numChans: 1, destination: e.bufnum);
FluidBufCompose.process(s, x.bufnum, startChan:~bird, nChans: 1, dstStartChan: 1, dstBufNum: e.bufnum, dstGain:1); FluidBufCompose.process(s, x.bufnum, startChan:~bird, numChans: 1, destStartChan: 1, destination: e.bufnum, destGain:1);
s.sync; s.sync;
(0..9).removeAll([~dog,~bird]).do({|chan|FluidBufCompose.process(s,x.bufnum, startChan:chan, nChans: 1, dstStartChan: 2, dstBufNum: e.bufnum, dstGain:1)}); (0..9).removeAll([~dog,~bird]).do({|chan|FluidBufCompose.process(s,x.bufnum, startChan:chan, numChans: 1, destStartChan: 2, destination: e.bufnum, destGain:1)});
s.sync; s.sync;
e.query; e.query;
}.play; }.play;
) )
e.plot; e.plot;
//using this trained dictionary we can then see the activation... //using this trained basis we can then see the activation...
( (
{ {
var source, blips; var source, blips;
@ -260,7 +260,7 @@ e.plot;
:: ::
STRONG::Pretrained piano:: STRONG::Pretrained piano::
CODE:: CODE::
//load in the sound in and a pretrained dictionary //load in the sound in and a pretrained basis
( (
b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav"); b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav");
c = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/filters/piano-dicts.wav"); c = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/filters/piano-dicts.wav");
@ -268,7 +268,7 @@ e.plot;
b.play b.play
c.query c.query
//use the pretrained dictionary to compute activations of each notes to drive the amplitude of a resynth //use the pretrained bases to compute activations of each notes to drive the amplitude of a resynth
( (
{ {
var source, resynth; var source, resynth;

@ -4,9 +4,7 @@ CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION:: DESCRIPTION::
This class implements many spectral based onset detection algorythms, most of them taken from the literature (http://www.dafx.ca/proceedings/papers/p_133.pdf) This class implements many spectral based onset detection functions, most of them taken from the literature. (http://www.dafx.ca/proceedings/papers/p_133.pdf) Some are already available in SuperCollider's LINK::Classes/Onsets:: object. It is part of the Fluid Decomposition Toolkit of the FluCoMa project.footnote::This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).::
It is part of the Fluid Decomposition Toolkit of the FluCoMa project.footnote::This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).::
The process will return an audio steam with sample-long impulses at estimated starting points of the different slices. The process will return an audio steam with sample-long impulses at estimated starting points of the different slices.
@ -20,28 +18,31 @@ ARGUMENT:: in
The audio to be processed. The audio to be processed.
ARGUMENT:: function ARGUMENT:: function
0 - Energy =sum of squares of magnitudes / nBins (\power) The function used to derive a difference curve between spectral frames. It can be any of the following:
1 - HFC = sum of (squared mag * binNum) / nBins TABLE::
2 - SpectralFlux = dif in mag between consecutive frames (half rectified) ##0 || Energy || thresholds on (sum of squares of magnitudes / nBins) (like Onsets \power)
3 - MKL = sum of log of mag ratio per bin (or: sum of dif of log mag per bin) (\mkl) ##1 || HFC || thresholds on (sum of (squared magnitudes * binNum) / nBins)
4 - IS = itakura - saito divergence ##2 || SpectralFlux || thresholds on (diffence in magnitude between consecutive frames, half rectified)
5 - Cosine = cosine distance ##3 || MKL || thresholds on (sum of log of magnitude ratio per bin) (or equivalent: sum of difference of the log magnitude per bin) (like Onsets \mkl)
6 - PhaseDev = takes 2 past frames, project to current, as anticipated steady state, then compute differences (sums) (\phase) ##4 || IS || (WILL PROBABLY BE REMOVED) Itakura - Saito divergence (see literature)
7 - WPhaseDev = same, but weighted by magnitude to remove chaos noise floor (\wphase) ##5 || Cosine || thresholds on (cosine distance between comparison frames)
8 - ComplexDev = same as kPhaseDev, but in the complex domain - anticipated amp(steady) and phase(projected) - complex subtraction -> sum of mag (\complex) ##6 || PhaseDev || takes the past 2 frames, projects to the current, as anticipated if it was a steady state, then compute the sum of the differences, on which it thresholds (like Onsets \phase)
9 - RComplexDev =same as above, but rectified (\rcomplex) ##7 || WPhaseDev || same as PhaseDev, but weighted by the magnitude in order to remove chaos noise floor (like Onsets \wphase)
##8 || ComplexDev || same as PhaseDev, but in the complex domain - the anticipated amp is considered steady, and the phase is projected, then a complex subtraction is done with the actual present frame. The sum of magnitudes is used to threshold (like Onsets \complex)
ARGUMENT:: thresh ##9 || RComplexDev || same as above, but rectified (like Onsets \rcomplex)
diff for each... ::
ARGUMENT:: threshold
The thresholding of a new slice. Value ranges are different for each function, from 0 upwards.
ARGUMENT:: debounce ARGUMENT:: debounce
The minimum duration of a slice in number of hops. The minimum duration of a slice in number of hopSize.
ARGUMENT:: filtSize ARGUMENT:: filterSize
The size of a smoothing filter that is applied on the novelty curve. A larger filter filter size allows for cleaner cuts on very sharp changes. The size of a smoothing filter that is applied on the novelty curve. A larger filter filter size allows for cleaner cuts on very sharp changes.
ARGUMENT:: frameDelta ARGUMENT:: frameDelta
distance in samples between the comparison window (flux,mkl,kls,cosine) For certain functions (HFC, SpectralFlux, MKL, Cosine), the distance does not have to be computed between consecutive frames. By default (0) it is, otherwise this sets the distane between the comparison window in samples.
ARGUMENT:: winSize ARGUMENT:: winSize
The window size. As sinusoidal estimation relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty The window size. As sinusoidal estimation relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty
@ -56,10 +57,29 @@ ARGUMENT:: maxFFTSize
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated. How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
RETURNS:: RETURNS::
An audio stream with impulses at detected transients. The latency between the input and the output is winSize. An audio stream with impulses at detected transients. The latency between the input and the output is winSize at maximum.
EXAMPLES:: EXAMPLES::
code:: code::
(some example code) //load some sounds
b = Buffer.read(s,File.realpath(FluidOnsetSlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
// basic param (the process add a latency of winSize samples
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidOnsetSlice.ar(sig) * 0.5, DelayN.ar(sig, 1, 1024/ s.sampleRate)]}.play
// other parameters
{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidOnsetSlice.ar(sig, 2, 0.06, 55, 7, 0, 128, 64) * 0.5, DelayN.ar(sig, 1, (128)/ s.sampleRate)]}.play
// more musical trans-trigged autopan
(
{
var sig, trig, syncd, pan;
sig = PlayBuf.ar(1,b.bufnum,loop:1);
trig = FluidOnsetSlice.ar(sig, 1, 1.8, 100, 8, 0, 128);
syncd = DelayN.ar(sig, 1, ( 128 / s.sampleRate));
pan = TRand.ar(-1,1,trig);
Pan2.ar(syncd,pan);
}.play
)
:: ::

@ -21,10 +21,10 @@ METHOD:: ar
ARGUMENT:: in ARGUMENT:: in
The input to be processed The input to be processed
ARGUMENT:: bw ARGUMENT:: bandwidth
The width in bins of the fragment of the fft window that is considered a normal deviation for a potential continuous sinusoidal track. It has an effect on CPU cost: the widest is more accurate but more computationally expensive. The width in bins of the fragment of the fft window that is considered a normal deviation for a potential continuous sinusoidal track. It has an effect on CPU cost: the widest is more accurate but more computationally expensive.
ARGUMENT:: thresh ARGUMENT:: threshold
The normalised threshold, between 0 an 1, to consider a peak as a sinusoidal component from the normalized cross-correlation. The normalised threshold, between 0 an 1, to consider a peak as a sinusoidal component from the normalized cross-correlation.
ARGUMENT:: minTrackLen ARGUMENT:: minTrackLen
@ -59,7 +59,7 @@ CODE::
b = Buffer.read(s,File.realpath(FluidSines.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); b = Buffer.read(s,File.realpath(FluidSines.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
// run with large parameters - left is sinusoidal model, right is residual // run with large parameters - left is sinusoidal model, right is residual
{FluidSines.ar(PlayBuf.ar(1,b.bufnum,loop:1),thresh: 0.2, minTrackLen: 2, winSize: 2048, fftSize: 8192)}.play {FluidSines.ar(PlayBuf.ar(1,b.bufnum,loop:1),threshold: 0.2, minTrackLen: 2, winSize: 2048, fftSize: 8192)}.play
// interactive parameters with a narrower bandwidth // interactive parameters with a narrower bandwidth
{FluidSines.ar(PlayBuf.ar(1,b.bufnum,loop:1),30,MouseX.kr(), 5, winSize: 1000, hopSize: 200, fftSize: 4096)}.play {FluidSines.ar(PlayBuf.ar(1,b.bufnum,loop:1),30,MouseX.kr(), 5, winSize: 1000, hopSize: 200, fftSize: 4096)}.play

@ -216,7 +216,7 @@ x = {
s.freqscope s.freqscope
// at first it seems quite centred, but then flipped the argument FrqScl to lin(ear) and observe how high the spectrum goes. If we set it to a brickwall spectral filter tuned on the same frequencies: // at first it seems quite centred, but then flip the argument FrqScl to lin(ear) and observe how high the spectrum goes. If we set it to a brickwall spectral filter tuned on the same frequencies:
x.set(\type, 1) x.set(\type, 1)
@ -235,7 +235,7 @@ x = {
}.play; }.play;
) )
// this example shows a similar result to the brickwall bspectral bandpass above. If we move the central frequency nearer the half-Nyquist: // this example shows a similar result to the brickwall spectral bandpass above. If we move the central frequency nearer the half-Nyquist:
x.set(\freq, 8800) x.set(\freq, 8800)

@ -0,0 +1,42 @@
//this patch requests a folder and will iterate through all accepted audiofiles and concatenate them in the destination buffer. It will also yield an array with the numFrame where files start in the new buffer.
(
var tempbuf,dest=0, fileNames;
c = [0];
FileDialog.new({|selection|
var total, totaldur = 0, maxchans = 0;
t = Main.elapsedTime;
fileNames = PathName.new(selection[0])
.entries
.select({|f|
[\wav, \WAV, \mp3,\aif].includes(f.extension.asSymbol);});
total = fileNames.size();
fileNames.do({arg fp;
SoundFile.use(fp.asAbsolutePath , {
arg file;
var dur = file.numFrames;
c = c.add(dur);
totaldur = totaldur + dur;
maxchans = maxchans.max(file.numChannels);
});
});
Routine{
b = Buffer.alloc(s,totaldur,maxchans);
s.sync;
fileNames.do{|f, i|
f.postln;
("Loading"+(i+1)+"of"+total).postln;
tempbuf = Buffer.read(s, f.asAbsolutePath,action:{FluidBufCompose.process(s,tempbuf,destStartFrame:c[i],destination:b);});
s.sync;
};
("loading buffers done in" + (Main.elapsedTime - t).round(0.1) + "seconds.").postln;
}.play;
}, fileMode:2);
)
b.plot
c.postln
b.play
Buffer.freeAll

@ -1,6 +1,8 @@
//destination buffer //destination buffer
b = Buffer.alloc(s,1); (
b = Buffer.new();
c = Array.new(); c = Array.new();
)
//this patch requests a folder and will iterate through all accepted audiofiles and concatenate them in the destination buffer. It will also yield an array with the numFrame where files start in the new buffer. //this patch requests a folder and will iterate through all accepted audiofiles and concatenate them in the destination buffer. It will also yield an array with the numFrame where files start in the new buffer.
@ -9,25 +11,24 @@ var tempbuf,dest=0, fileNames;
FileDialog.new({|selection| FileDialog.new({|selection|
var total; var total;
t = Main.elapsedTime;
fileNames = PathName.new(selection[0]) fileNames = PathName.new(selection[0])
.entries .entries
.select({|f| .select({|f|
[\wav, \WAV, \mp3,\aif].includes(f.extension.asSymbol);}); [\wav, \WAV, \mp3,\aif].includes(f.extension.asSymbol);});
total = fileNames.size() - 1; total = fileNames.size();
Routine{ Routine{
fileNames.do{|f, i| fileNames.do{|f, i|
f.postln; f.postln;
("Loading"+i+"of"+total).postln; ("Loading"+(i+1)+"of"+total).postln;
tempbuf = Buffer.read(s,f.asAbsolutePath); tempbuf = Buffer.read(s,f.asAbsolutePath);
s.sync; s.sync;
c = c.add(dest); c = c.add(dest);
FluidBufCompose.process(s,tempbuf.bufnum,dstStartAtA:dest,srcBufNumB:b.bufnum,dstBufNum:b.bufnum); FluidBufCompose.process(s,tempbuf,destStartFrame:dest,destination:b);
s.sync;
b.updateInfo();
s.sync; s.sync;
dest = b.numFrames; dest = b.numFrames;
}; };
"load buffers done".postln; ("loading buffers done in" + (Main.elapsedTime - t).round(0.1) + "seconds.").postln;
}.play; }.play;
}, fileMode:2); }, fileMode:2);
) )

@ -47,9 +47,9 @@ y = Synth(\becauseIcan,[\bufnum, b.bufnum, \nmfa, c.bufnum, \nmfb, d.bufnum, \in
( (
w = OSCFunc({ arg msg; w = OSCFunc({ arg msg;
if(msg[3]== 1, { if(msg[3]== 1, {
FluidBufNMF.process(s, b.bufnum, nFrames: 22500, dstBufNum: c.bufnum, rank: 3, fftSize: 1024, winSize: 512, hopSize: 256); FluidBufNMF.process(s, b, numFrames: 22500, destination: c.bufnum, rank: 3, fftSize: 1024, winSize: 512, hopSize: 256);
}, { }, {
FluidBufNMF.process(s, b.bufnum, 22050, 22500, dstBufNum: d.bufnum, rank: 3, fftSize: 1024, winSize: 512, hopSize: 256); FluidBufNMF.process(s, b, 22050, 22500, destination: d.bufnum, rank: 3, fftSize: 1024, winSize: 512, hopSize: 256);
});}, '/processplease', s.addr); });}, '/processplease', s.addr);
) )

Loading…
Cancel
Save