commit
39860d14d7
@ -1,44 +1,44 @@
|
||||
FluidBufAmpGate : FluidBufProcessor {
|
||||
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, trig = 1, blocking = 0|
|
||||
|
||||
var maxSize = max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead));
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, trig = 1, blocking = 0|
|
||||
|
||||
var maxSize = max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead));
|
||||
|
||||
source = source.asUGenInput;
|
||||
indices = indices.asUGenInput;
|
||||
|
||||
source = source.asUGenInput;
|
||||
indices = indices.asUGenInput;
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufAmpGateTrigger,-1, source, startFrame, numFrames, startChan, numChans, indices, rampUp, rampDown, onThreshold, offThreshold, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, highPassFreq,maxSize, trig, blocking);
|
||||
}
|
||||
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, freeWhenDone = true, action |
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, freeWhenDone = true, action |
|
||||
|
||||
|
||||
var maxSize = max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead));
|
||||
var maxSize = max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead));
|
||||
|
||||
source = source ? -1;
|
||||
indices = indices ? -1;
|
||||
indices = indices ? -1;
|
||||
|
||||
^this.new(
|
||||
server, nil, [indices]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, indices, rampUp, rampDown, onThreshold, offThreshold, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, highPassFreq, maxSize, 0],freeWhenDone,action
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, freeWhenDone = true, action |
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, indices, rampUp = 10, rampDown = 10, onThreshold = -90, offThreshold = -90, minSliceLength = 1, minSilenceLength = 1, minLengthAbove = 1, minLengthBelow = 1, lookBack = 0, lookAhead = 0, highPassFreq = 85, freeWhenDone = true, action |
|
||||
|
||||
|
||||
var maxSize = max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead));
|
||||
var maxSize = max(minLengthAbove + lookBack, max(minLengthBelow,lookAhead));
|
||||
|
||||
source = source ? -1;
|
||||
indices = indices ? -1;
|
||||
|
||||
source = source ? -1;
|
||||
indices = indices ? -1;
|
||||
^this.new(
|
||||
server, nil, [indices]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, indices, rampUp, rampDown, onThreshold, offThreshold, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, highPassFreq, maxSize, 1],freeWhenDone,action
|
||||
);
|
||||
}
|
||||
|
||||
^this.new(
|
||||
server, nil, [indices]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, indices, rampUp, rampDown, onThreshold, offThreshold, minSliceLength, minSilenceLength, minLengthAbove, minLengthBelow, lookBack, lookAhead, highPassFreq, maxSize, 1],freeWhenDone,action
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
FluidBufAmpGateTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,49 +1,49 @@
|
||||
FluidBufChroma : FluidBufProcessor {
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numChroma = 12, ref = 440, normalize = 0,minFreq = 0,maxFreq = -1, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, trig = 1, blocking = 0|
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numChroma = 12, ref = 440, normalize = 0,minFreq = 0,maxFreq = -1, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, trig = 1, blocking = 0|
|
||||
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufChroma: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufChroma: Invalid features buffer".throw};
|
||||
source.isNil.if {"FluidBufChroma: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufChroma: Invalid features buffer".throw};
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufChromaTrigger,-1, source, startFrame, numFrames, startChan, numChans, features, padding, numChroma, numChroma, ref, normalize, minFreq, maxFreq, windowSize, hopSize, fftSize, maxFFTSize, trig, blocking);
|
||||
}
|
||||
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numChroma = 12, ref = 440, normalize = 0,minFreq = 0,maxFreq = -1, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone = true, action|
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numChroma = 12, ref = 440, normalize = 0,minFreq = 0,maxFreq = -1, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone = true, action|
|
||||
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufChroma: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufChroma: Invalid features buffer".throw};
|
||||
source.isNil.if {"FluidBufChroma: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufChroma: Invalid features buffer".throw};
|
||||
|
||||
^this.new(
|
||||
server, nil, [features]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, features, padding, numChroma, numChroma, ref, normalize, minFreq, maxFreq, windowSize, hopSize, fftSize, maxFFTSize, 0],freeWhenDone,action
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numChroma = 12, ref = 440, normalize = 0,minFreq = 0,maxFreq = -1, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone = true, action|
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numChroma = 12, ref = 440, normalize = 0,minFreq = 0,maxFreq = -1, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone = true, action|
|
||||
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufChroma: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufChroma: Invalid features buffer".throw};
|
||||
source.isNil.if {"FluidBufChroma: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufChroma: Invalid features buffer".throw};
|
||||
|
||||
^this.new(
|
||||
server, nil, [features]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, features, padding, numChroma, numChroma, ref, normalize, minFreq, maxFreq, windowSize, hopSize, fftSize, maxFFTSize, 1],freeWhenDone,action
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
FluidBufChromaTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,39 +1,39 @@
|
||||
FluidBufCompose : FluidBufProcessor {
|
||||
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, gain = 1, destination, destStartFrame = 0, destStartChan = 0, destGain = 0, trig = 1, blocking = 1|
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, gain = 1, destination, destStartFrame = 0, destStartChan = 0, destGain = 0, trig = 1, blocking = 1|
|
||||
|
||||
source.isNil.if {"FluidBufCompose: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufCompose: Invalid destination buffer".throw};
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufComposeTrigger,-1, source, startFrame, numFrames, startChan, numChans, gain, destination, destStartFrame, destStartChan, destGain, trig, blocking);
|
||||
source.isNil.if {"FluidBufCompose: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufCompose: Invalid destination buffer".throw};
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufComposeTrigger,-1, source, startFrame, numFrames, startChan, numChans, gain, destination, destStartFrame, destStartChan, destGain, trig, blocking);
|
||||
}
|
||||
|
||||
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, gain = 1, destination, destStartFrame = 0, destStartChan = 0, destGain = 0, freeWhenDone = true, action|
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufCompose: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufCompose: Invalid destination buffer".throw};
|
||||
|
||||
^this.new( server, nil, [destination]).processList([source, startFrame, numFrames, startChan, numChans, gain, destination, destStartFrame, destStartChan, destGain, 1], freeWhenDone, action);//NB always blocking
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufCompose: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufCompose: Invalid destination buffer".throw};
|
||||
|
||||
^this.new( server, nil, [destination]).processList([source, startFrame, numFrames, startChan, numChans, gain, destination, destStartFrame, destStartChan, destGain, 1], freeWhenDone, action);//NB always blocking
|
||||
}
|
||||
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, gain = 1, destination, destStartFrame = 0, destStartChan = 0, destGain = 0, freeWhenDone = true, action|
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, gain = 1, destination, destStartFrame = 0, destStartChan = 0, destGain = 0, freeWhenDone = true, action|
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
source.isNil.if {"FluidBufCompose: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufCompose: Invalid destination buffer".throw};
|
||||
|
||||
source.isNil.if {"FluidBufCompose: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufCompose: Invalid destination buffer".throw};
|
||||
|
||||
^this.new(
|
||||
server, nil, [destination]
|
||||
).processList([source, startFrame, numFrames, startChan, numChans, gain, destination, destStartFrame, destStartChan, destGain, 1], freeWhenDone, action);
|
||||
^this.new(
|
||||
server, nil, [destination]
|
||||
).processList([source, startFrame, numFrames, startChan, numChans, gain, destination, destStartFrame, destStartChan, destGain, 1], freeWhenDone, action);
|
||||
}
|
||||
}
|
||||
FluidBufComposeTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,47 +1,47 @@
|
||||
FluidBufFlatten : FluidBufProcessor {
|
||||
|
||||
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, destination, axis = 1, trig = 1, blocking = 1|
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, destination, axis = 1, trig = 1, blocking = 1|
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufFlatten: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufFlatten: Invalid destination buffer".throw};
|
||||
source.isNil.if {"FluidBufFlatten: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufFlatten: Invalid destination buffer".throw};
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufFlattenTrigger,-1, source, startFrame, numFrames, startChan, numChans, destination, axis, trig, blocking);
|
||||
^FluidProxyUgen.kr(\FluidBufFlattenTrigger,-1, source, startFrame, numFrames, startChan, numChans, destination, axis, trig, blocking);
|
||||
}
|
||||
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, destination, axis = 1, freeWhenDone = true, action|
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufFlatten: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufFlatten: Invalid destination buffer".throw};
|
||||
source.isNil.if {"FluidBufFlatten: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufFlatten: Invalid destination buffer".throw};
|
||||
|
||||
^this.new(
|
||||
server, nil, [destination],
|
||||
^this.new(
|
||||
server, nil, [destination],
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, destination, axis,0],freeWhenDone,action
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, destination, axis = 1, freeWhenDone = true, action|
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, destination, axis = 1, freeWhenDone = true, action|
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufFlatten: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufFlatten: Invalid destination buffer".throw};
|
||||
source.isNil.if {"FluidBufFlatten: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufFlatten: Invalid destination buffer".throw};
|
||||
|
||||
^this.new(
|
||||
server, nil, [destination],
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, destination, axis,1],freeWhenDone,action
|
||||
);
|
||||
^this.new(
|
||||
server, nil, [destination],
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, destination, axis,1],freeWhenDone,action
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
FluidBufFlattenTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,76 +1,76 @@
|
||||
FluidBufLoudness : FluidBufProcessor{
|
||||
|
||||
const <features=#[\loudness, \peak];
|
||||
classvar featuresLookup;
|
||||
|
||||
*initClass {
|
||||
featuresLookup = Dictionary.with(*this.features.collect{|x,i| x->(1<<i)});
|
||||
}
|
||||
|
||||
*prWarnUnrecognised {|sym| ("WARNING: FluidLoudness -" + sym + "is not a recognised option").postln}
|
||||
|
||||
*prProcessSelect {|a|
|
||||
var bits;
|
||||
a.asBag.countsDo{|item,count,i|
|
||||
if(count > 1) { ("Option '" ++ item ++ "' is repeated").warn};
|
||||
};
|
||||
bits = a.collect{ |sym|
|
||||
(featuresLookup[sym.asSymbol] !? {|x| x} ?? {this.prWarnUnrecognised(sym); 0})
|
||||
}.reduce{|x,y| x | y};
|
||||
^bits
|
||||
}
|
||||
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, select, kWeighting = 1, truePeak = 1, windowSize = 1024, hopSize = 512, padding = 1, trig = 1, blocking = 0|
|
||||
|
||||
var maxwindowSize = windowSize.nextPowerOfTwo;
|
||||
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufPitch: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufPitch: Invalid features buffer".throw};
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufLoudnessTrigger, -1, source, startFrame, numFrames, startChan, numChans, features, padding, selectbits, kWeighting, truePeak, windowSize, hopSize, maxwindowSize, trig, blocking);
|
||||
}
|
||||
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, select, kWeighting = 1, truePeak = 1, windowSize = 1024, hopSize = 512, padding = 1, freeWhenDone = true, action|
|
||||
|
||||
var maxwindowSize = windowSize.nextPowerOfTwo;
|
||||
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufPitch: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufPitch: Invalid features buffer".throw};
|
||||
|
||||
const <features=#[\loudness, \peak];
|
||||
classvar featuresLookup;
|
||||
|
||||
*initClass {
|
||||
featuresLookup = Dictionary.with(*this.features.collect{|x,i| x->(1<<i)});
|
||||
}
|
||||
|
||||
*prWarnUnrecognised {|sym| ("WARNING: FluidLoudness -" + sym + "is not a recognised option").postln}
|
||||
|
||||
*prProcessSelect {|a|
|
||||
var bits;
|
||||
a.asBag.countsDo{|item,count,i|
|
||||
if(count > 1) { ("Option '" ++ item ++ "' is repeated").warn};
|
||||
};
|
||||
bits = a.collect{ |sym|
|
||||
(featuresLookup[sym.asSymbol] !? {|x| x} ?? {this.prWarnUnrecognised(sym); 0})
|
||||
}.reduce{|x,y| x | y};
|
||||
^bits
|
||||
}
|
||||
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, select, kWeighting = 1, truePeak = 1, windowSize = 1024, hopSize = 512, padding = 1, trig = 1, blocking = 0|
|
||||
|
||||
var maxwindowSize = windowSize.nextPowerOfTwo;
|
||||
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"%: Invalid source buffer".format(this.class.name).throw};
|
||||
features.isNil.if {"%: Invalid features buffer".format(this.class.name).throw};
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufLoudnessTrigger, -1, source, startFrame, numFrames, startChan, numChans, features, padding, selectbits, kWeighting, truePeak, windowSize, hopSize, maxwindowSize, trig, blocking);
|
||||
}
|
||||
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, select, kWeighting = 1, truePeak = 1, windowSize = 1024, hopSize = 512, padding = 1, freeWhenDone = true, action|
|
||||
|
||||
var maxwindowSize = windowSize.nextPowerOfTwo;
|
||||
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"%: Invalid source buffer".format(this.class.name).throw};
|
||||
features.isNil.if {"%: Invalid features buffer".format(this.class.name).throw};
|
||||
|
||||
^this.new(
|
||||
server, nil, [features]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, features, padding, selectbits, kWeighting, truePeak, windowSize, hopSize, maxwindowSize,0],freeWhenDone,action
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, select, kWeighting = 1, truePeak = 1, windowSize = 1024, hopSize = 512, padding = 1, freeWhenDone = true, action|
|
||||
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, select, kWeighting = 1, truePeak = 1, windowSize = 1024, hopSize = 512, padding = 1, freeWhenDone = true, action|
|
||||
var maxwindowSize = windowSize.nextPowerOfTwo;
|
||||
|
||||
var maxwindowSize = windowSize.nextPowerOfTwo;
|
||||
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufPitch: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufPitch: Invalid features buffer".throw};
|
||||
source.isNil.if {"%: Invalid source buffer".format(this.class.name).throw};
|
||||
features.isNil.if {"%: Invalid features buffer".format(this.class.name).throw};
|
||||
|
||||
^this.new(
|
||||
server, nil, [features]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, features,padding, selectbits, kWeighting, truePeak, windowSize, hopSize, maxwindowSize,1],freeWhenDone,action
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
FluidBufLoudnessTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,46 +1,46 @@
|
||||
FluidBufMFCC : FluidBufProcessor{
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numCoeffs = 13, numBands = 40, startCoeff = 0, minFreq = 20, maxFreq = 20000, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, trig = 1, blocking = 0|
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numCoeffs = 13, numBands = 40, startCoeff = 0, minFreq = 20, maxFreq = 20000, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, trig = 1, blocking = 0|
|
||||
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufMFCC: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMFCC: Invalid features buffer".throw};
|
||||
source.isNil.if {"FluidBufMFCC: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMFCC: Invalid features buffer".throw};
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufMFCCTrigger, -1, source, startFrame, numFrames, startChan, numChans, features, padding, numCoeffs, numCoeffs, numBands, startCoeff, minFreq, maxFreq, windowSize, hopSize, fftSize, maxFFTSize,trig, blocking);
|
||||
}
|
||||
^FluidProxyUgen.kr(\FluidBufMFCCTrigger, -1, source, startFrame, numFrames, startChan, numChans, features, padding, numCoeffs, numCoeffs, numBands, numBands, startCoeff, minFreq, maxFreq, windowSize, hopSize, fftSize, maxFFTSize,trig, blocking);
|
||||
}
|
||||
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numCoeffs = 13, numBands = 40, startCoeff = 0, minFreq = 20, maxFreq = 20000, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone=true, action |
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numCoeffs = 13, numBands = 40, startCoeff = 0, minFreq = 20, maxFreq = 20000, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone=true, action |
|
||||
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufMFCC: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMFCC: Invalid features buffer".throw};
|
||||
source.isNil.if {"FluidBufMFCC: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMFCC: Invalid features buffer".throw};
|
||||
|
||||
^this.new(
|
||||
server, nil,[features]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, features, padding, numCoeffs, numCoeffs, numBands, startCoeff, minFreq, maxFreq, windowSize, hopSize, fftSize, maxFFTSize,0],freeWhenDone,action
|
||||
[source, startFrame, numFrames, startChan, numChans, features, padding, numCoeffs, numCoeffs, numBands, numBands, startCoeff, minFreq, maxFreq, windowSize, hopSize, fftSize, maxFFTSize,0],freeWhenDone,action
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numCoeffs = 13, numBands = 40, startCoeff = 0, minFreq = 20, maxFreq = 20000, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone=true, action |
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numCoeffs = 13, numBands = 40, startCoeff = 0, minFreq = 20, maxFreq = 20000, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone=true, action |
|
||||
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufMFCC: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMFCC: Invalid features buffer".throw};
|
||||
source.isNil.if {"FluidBufMFCC: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMFCC: Invalid features buffer".throw};
|
||||
|
||||
^this.new(
|
||||
server, nil,[features]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, features, padding, numCoeffs, numCoeffs, numBands, startCoeff, minFreq, maxFreq, windowSize, hopSize, fftSize, maxFFTSize,1],freeWhenDone,action
|
||||
[source, startFrame, numFrames, startChan, numChans, features, padding, numCoeffs, numCoeffs, numBands, numBands, startCoeff, minFreq, maxFreq, windowSize, hopSize, fftSize, maxFFTSize,1],freeWhenDone,action
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
FluidBufMFCCTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,51 +1,51 @@
|
||||
FluidBufMelBands : FluidBufProcessor {
|
||||
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numBands = 40, minFreq = 20, maxFreq = 20000, normalize = 1, scale = 0, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, trig = 1, blocking = 0|
|
||||
*kr { |source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numBands = 40, minFreq = 20, maxFreq = 20000, normalize = 1, scale = 0, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, trig = 1, blocking = 0|
|
||||
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufMelBands: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMelBands: Invalid features buffer".throw};
|
||||
source.isNil.if {"FluidBufMelBands: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMelBands: Invalid features buffer".throw};
|
||||
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufMelBandsTrigger,-1, source, startFrame, numFrames, startChan, numChans, features, padding, numBands, numBands, minFreq, maxFreq, normalize, scale, windowSize, hopSize, fftSize, maxFFTSize, trig, blocking);
|
||||
}
|
||||
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numBands = 40, minFreq = 20, maxFreq = 20000, normalize = 1, scale = 0, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone = true, action|
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numBands = 40, minFreq = 20, maxFreq = 20000, normalize = 1, scale = 0, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone = true, action|
|
||||
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufMelBands: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMelBands: Invalid features buffer".throw};
|
||||
source.isNil.if {"FluidBufMelBands: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMelBands: Invalid features buffer".throw};
|
||||
|
||||
^this.new(
|
||||
server, nil, [features]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, features, padding, numBands, numBands, minFreq, maxFreq, normalize, scale, windowSize, hopSize, fftSize, maxFFTSize, 0],freeWhenDone,action
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numBands = 40, minFreq = 20, maxFreq = 20000, normalize = 1, scale = 0, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone = true, action|
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, numBands = 40, minFreq = 20, maxFreq = 20000, normalize = 1, scale = 0, windowSize = 1024, hopSize = -1, fftSize = -1, padding = 1, freeWhenDone = true, action|
|
||||
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
var maxFFTSize = if (fftSize == -1) {windowSize.nextPowerOfTwo} {fftSize};
|
||||
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
features = features.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufMelBands: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMelBands: Invalid features buffer".throw};
|
||||
source.isNil.if {"FluidBufMelBands: Invalid source buffer".throw};
|
||||
features.isNil.if {"FluidBufMelBands: Invalid features buffer".throw};
|
||||
|
||||
^this.new(
|
||||
server, nil, [features]
|
||||
).processList(
|
||||
[source, startFrame, numFrames, startChan, numChans, features, padding, numBands, numBands, minFreq, maxFreq, normalize, scale, windowSize, hopSize, fftSize, maxFFTSize, 1],freeWhenDone,action
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
FluidBufMelBandsTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,37 +1,37 @@
|
||||
FluidBufNMF : FluidBufProcessor
|
||||
{
|
||||
*kr {|source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, resynth, resynthMode = 0, bases, basesMode = 0, activations, actMode = 0, components = 1, iterations = 100, windowSize = 1024, hopSize = -1, fftSize = -1, trig = 1, blocking = 0|
|
||||
*kr {|source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, resynth, resynthMode = 0, bases, basesMode = 0, activations, actMode = 0, components = 1, iterations = 100, windowSize = 1024, hopSize = -1, fftSize = -1, trig = 1, blocking = 0|
|
||||
|
||||
source.isNil.if {"FluidBufNMF: Invalid source buffer".throw};
|
||||
resynth = resynth ? -1;
|
||||
bases = bases ? -1;
|
||||
activations = activations ? -1;
|
||||
source.isNil.if {"FluidBufNMF: Invalid source buffer".throw};
|
||||
resynth = resynth ? -1;
|
||||
bases = bases ? -1;
|
||||
activations = activations ? -1;
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufNMFTrigger,-1,source.asUGenInput, startFrame, numFrames, startChan, numChans, resynth.asUGenInput, resynthMode, bases.asUGenInput, basesMode, activations.asUGenInput, actMode, components, iterations, windowSize, hopSize, fftSize, fftSize, trig, blocking);
|
||||
}
|
||||
^FluidProxyUgen.kr(\FluidBufNMFTrigger,-1,source.asUGenInput, startFrame, numFrames, startChan, numChans, resynth.asUGenInput, resynthMode, bases.asUGenInput, basesMode, activations.asUGenInput, actMode, components, iterations, windowSize, hopSize, fftSize, fftSize, trig, blocking);
|
||||
}
|
||||
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, resynth = -1, resynthMode = 0, bases = -1, basesMode = 0, activations = -1, actMode = 0, components = 1, iterations = 100, windowSize = 1024, hopSize = -1, fftSize = -1,freeWhenDone = true, action|
|
||||
*process { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, resynth = -1, resynthMode = 0, bases = -1, basesMode = 0, activations = -1, actMode = 0, components = 1, iterations = 100, windowSize = 1024, hopSize = -1, fftSize = -1,freeWhenDone = true, action|
|
||||
|
||||
source.isNil.if {"FluidBufNMF: Invalid source buffer".throw};
|
||||
resynth = resynth ? -1;
|
||||
bases = bases ? -1;
|
||||
activations = activations ? -1;
|
||||
source.isNil.if {"FluidBufNMF: Invalid source buffer".throw};
|
||||
resynth = resynth ? -1;
|
||||
bases = bases ? -1;
|
||||
activations = activations ? -1;
|
||||
|
||||
^this.new(
|
||||
server,nil,[resynth, bases, activations].select{|x| x!= -1}
|
||||
).processList([source, startFrame, numFrames, startChan, numChans, resynth, resynthMode, bases, basesMode, activations, actMode, components,iterations, windowSize, hopSize, fftSize, fftSize, 0],freeWhenDone,action);
|
||||
}
|
||||
^this.new(
|
||||
server,nil,[resynth, bases, activations].select{|x| x!= -1}
|
||||
).processList([source, startFrame, numFrames, startChan, numChans, resynth, resynthMode, bases, basesMode, activations, actMode, components,iterations, windowSize, hopSize, fftSize, fftSize, 0],freeWhenDone,action);
|
||||
}
|
||||
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, resynth = -1, resynthMode = 0, bases = -1, basesMode = 0, activations = -1, actMode = 0, components = 1, iterations = 100, windowSize = 1024, hopSize = -1, fftSize = -1,freeWhenDone = true, action|
|
||||
*processBlocking { |server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, resynth = -1, resynthMode = 0, bases = -1, basesMode = 0, activations = -1, actMode = 0, components = 1, iterations = 100, windowSize = 1024, hopSize = -1, fftSize = -1,freeWhenDone = true, action|
|
||||
|
||||
source.isNil.if {"FluidBufNMF: Invalid source buffer".throw};
|
||||
resynth = resynth ? -1;
|
||||
bases = bases ? -1;
|
||||
activations = activations ? -1;
|
||||
source.isNil.if {"FluidBufNMF: Invalid source buffer".throw};
|
||||
resynth = resynth ? -1;
|
||||
bases = bases ? -1;
|
||||
activations = activations ? -1;
|
||||
|
||||
^this.new(
|
||||
server,nil,[resynth, bases, activations].select{|x| x!= -1}
|
||||
).processList([source, startFrame, numFrames, startChan, numChans, resynth, resynthMode, bases, basesMode, activations, actMode, components,iterations, windowSize, hopSize, fftSize, fftSize, 1],freeWhenDone,action);
|
||||
}
|
||||
^this.new(
|
||||
server,nil,[resynth, bases, activations].select{|x| x!= -1}
|
||||
).processList([source, startFrame, numFrames, startChan, numChans, resynth, resynthMode, bases, basesMode, activations, actMode, components,iterations, windowSize, hopSize, fftSize, fftSize, 1],freeWhenDone,action);
|
||||
}
|
||||
}
|
||||
FluidBufNMFTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,62 +1,62 @@
|
||||
FluidBufSelect : FluidBufProcessor {
|
||||
|
||||
*kr { |source, destination, indices=#[-1], channels=#[-1], trig = 1, blocking = 1|
|
||||
*kr { |source, destination, indices=#[-1], channels=#[-1], trig = 1, blocking = 1|
|
||||
|
||||
var params;
|
||||
var params;
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
indices = indices.asArray;
|
||||
channels = channels.asArray;
|
||||
indices = indices.asArray;
|
||||
channels = channels.asArray;
|
||||
|
||||
indices = [indices.size] ++ indices;
|
||||
channels = [channels.size] ++ channels;
|
||||
indices = [indices.size] ++ indices;
|
||||
channels = [channels.size] ++ channels;
|
||||
|
||||
source.isNil.if {"FluidBufSelect: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufSelect: Invalid destination buffer".throw};
|
||||
source.isNil.if {"FluidBufSelect: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufSelect: Invalid destination buffer".throw};
|
||||
|
||||
params = indices ++ channels ++ [trig, blocking]
|
||||
params = indices ++ channels ++ [trig, blocking]
|
||||
|
||||
^FluidProxyUgen.kr(\FluidBufSelectTrigger,-1, source, destination, *params);
|
||||
^FluidProxyUgen.kr(\FluidBufSelectTrigger,-1, source, destination, *params);
|
||||
}
|
||||
|
||||
|
||||
*process { |server, source, destination, indices=#[-1], channels=#[-1], freeWhenDone = true, action|
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufSelect: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufSelect: Invalid destination buffer".throw};
|
||||
source.isNil.if {"FluidBufSelect: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufSelect: Invalid destination buffer".throw};
|
||||
|
||||
indices = indices.asArray;
|
||||
channels = channels.asArray;
|
||||
indices = indices.asArray;
|
||||
channels = channels.asArray;
|
||||
|
||||
indices = [indices.size] ++ indices;
|
||||
channels = [channels.size] ++ channels;
|
||||
channels = [channels.size] ++ channels;
|
||||
|
||||
^this.new(server, nil, [destination]).processList([source, destination]++ indices ++ channels ++ [1], freeWhenDone, action);//NB always blocking
|
||||
^this.new(server, nil, [destination]).processList([source, destination]++ indices ++ channels ++ [1], freeWhenDone, action);//NB always blocking
|
||||
}
|
||||
|
||||
*processBlocking { |server, source, destination, indices=#[-1], channels=#[-1], freeWhenDone = true, action|
|
||||
*processBlocking { |server, source, destination, indices=#[-1], channels=#[-1], freeWhenDone = true, action|
|
||||
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
source = source.asUGenInput;
|
||||
destination = destination.asUGenInput;
|
||||
|
||||
source.isNil.if {"FluidBufSelect: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufSelect: Invalid destination buffer".throw};
|
||||
source.isNil.if {"FluidBufSelect: Invalid source buffer".throw};
|
||||
destination.isNil.if {"FluidBufSelect: Invalid destination buffer".throw};
|
||||
|
||||
indices = indices.asArray;
|
||||
channels = channels.asArray;
|
||||
indices = indices.asArray;
|
||||
channels = channels.asArray;
|
||||
|
||||
indices = [indices.size] ++ indices;
|
||||
channels = [channels.size] ++ channels;
|
||||
indices = [indices.size] ++ indices;
|
||||
channels = [channels.size] ++ channels;
|
||||
|
||||
|
||||
^this.new(
|
||||
server, nil, [destination]
|
||||
).processList([source, destination]++ indices ++ channels ++ [1], freeWhenDone, action);//NB always blocking
|
||||
^this.new(
|
||||
server, nil, [destination]
|
||||
).processList([source, destination]++ indices ++ channels ++ [1], freeWhenDone, action);//NB always blocking
|
||||
}
|
||||
}
|
||||
FluidBufSelectTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,85 +1,84 @@
|
||||
|
||||
FluidDataSetQuery : FluidDataObject {
|
||||
*new{|server| ^super.new(server) }
|
||||
*new{|server| ^super.new(server) }
|
||||
|
||||
addColumnMsg { |column|
|
||||
^this.prMakeMsg(\addColumn,id,column);
|
||||
}
|
||||
addColumnMsg { |column|
|
||||
^this.prMakeMsg(\addColumn,id,column);
|
||||
}
|
||||
|
||||
addColumn{|column, action|
|
||||
actions[\addColumn] = [nil,action];
|
||||
this.prSendMsg(this.addColumnMsg(column));
|
||||
actions[\addColumn] = [nil,action];
|
||||
this.prSendMsg(this.addColumnMsg(column));
|
||||
}
|
||||
|
||||
addRangeMsg{|start,count|
|
||||
^this.prMakeMsg(\addRange,id,start,count);
|
||||
}
|
||||
addRangeMsg{|start,count|
|
||||
^this.prMakeMsg(\addRange,id,start,count);
|
||||
}
|
||||
|
||||
addRange{|start, count, action|
|
||||
actions[\addRange] = [nil, action];
|
||||
actions[\addRange] = [nil, action];
|
||||
this.prSendMsg(this.addRangeMsg(start, count));
|
||||
}
|
||||
|
||||
filterMsg{|column, condition, value, action|
|
||||
^this.prMakeMsg(\filter,id,column,condition.asSymbol,value);
|
||||
}
|
||||
filterMsg{|column, condition, value, action|
|
||||
^this.prMakeMsg(\filter,id,column,condition.asSymbol,value);
|
||||
}
|
||||
|
||||
filter{|column, condition, value, action|
|
||||
actions[\filter] = [nil, action];
|
||||
actions[\filter] = [nil, action];
|
||||
this.prSendMsg(this.filterMsg(column, condition, value));
|
||||
}
|
||||
|
||||
andMsg{ |column, condition, value|
|
||||
^this.prMakeMsg(\and,id,column, condition.asSymbol, value);
|
||||
}
|
||||
andMsg{ |column, condition, value|
|
||||
^this.prMakeMsg(\and,id,column, condition.asSymbol, value);
|
||||
}
|
||||
|
||||
and{|column, condition, value, action|
|
||||
actions[\and] = [nil, action];
|
||||
actions[\and] = [nil, action];
|
||||
this.prSendMsg(this.andMsg(column,condition,value));
|
||||
}
|
||||
|
||||
orMsg{|column, condition, value|
|
||||
^this.prMakeMsg(\or,id,column, condition.asSymbol, value)
|
||||
}
|
||||
orMsg{|column, condition, value|
|
||||
^this.prMakeMsg(\or,id,column, condition.asSymbol, value)
|
||||
}
|
||||
|
||||
or{|column, condition, value, action|
|
||||
actions[\or] = [nil,action];
|
||||
actions[\or] = [nil,action];
|
||||
this.prSendMsg(this.orMsg(column, condition, value));
|
||||
}
|
||||
|
||||
clearMsg{
|
||||
^this.prMakeMsg(\clear,id);
|
||||
}
|
||||
clearMsg{
|
||||
^this.prMakeMsg(\clear,id);
|
||||
}
|
||||
|
||||
clear{|action|
|
||||
actions[\clear] = [nil, action];
|
||||
actions[\clear] = [nil, action];
|
||||
this.prSendMsg(this.clearMsg);
|
||||
}
|
||||
|
||||
limitMsg{|rows|
|
||||
^this.prMakeMsg(\limit,id,rows);
|
||||
}
|
||||
limitMsg{|rows|
|
||||
^this.prMakeMsg(\limit,id,rows);
|
||||
}
|
||||
|
||||
limit{|rows, action|
|
||||
actions[\limit] = [nil,action];
|
||||
actions[\limit] = [nil,action];
|
||||
this.prSendMsg(this.limitMsg(rows));
|
||||
}
|
||||
|
||||
transformMsg{|sourceDataSet, destDataSet|
|
||||
^this.prMakeMsg(\transform,id,sourceDataSet.id,destDataSet.id);
|
||||
}
|
||||
transformMsg{|sourceDataSet, destDataSet|
|
||||
^this.prMakeMsg(\transform,id,sourceDataSet.id,destDataSet.id);
|
||||
}
|
||||
|
||||
transform{|sourceDataSet, destDataSet, action|
|
||||
actions[\transform] = [nil,action];
|
||||
this.prSendMsg(this.transformMsg(sourceDataSet,destDataSet));
|
||||
}
|
||||
transform{|sourceDataSet, destDataSet, action|
|
||||
actions[\transform] = [nil,action];
|
||||
this.prSendMsg(this.transformMsg(sourceDataSet,destDataSet));
|
||||
}
|
||||
|
||||
transformJoinMsg{|source1DataSet, source2DataSet, destDataSet|
|
||||
^this.prMakeMsg(\transformJoin,id,source1DataSet.id, source2DataSet.id, destDataSet.id);
|
||||
}
|
||||
transformJoinMsg{|source1DataSet, source2DataSet, destDataSet|
|
||||
^this.prMakeMsg(\transformJoin,id,source1DataSet.id, source2DataSet.id, destDataSet.id);
|
||||
}
|
||||
|
||||
transformJoin{|source1DataSet, source2DataSet, destDataSet, action|
|
||||
actions[\transformJoin] = [nil,action];
|
||||
this.prSendMsg(this.transformJoinMsg(source1DataSet, source2DataSet, destDataSet));
|
||||
}
|
||||
actions[\transformJoin] = [nil,action];
|
||||
this.prSendMsg(this.transformJoinMsg(source1DataSet, source2DataSet, destDataSet));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
FluidDataSetWr : FluidBufProcessor {
|
||||
*kr { |dataset,idPrefix = "", idNumber = 0,buf, trig=1, blocking = 1|
|
||||
var args;
|
||||
buf ?? {(this.class.name ++ ": No input buffer provided").error};
|
||||
|
||||
idNumber = idNumber !? {[2,1,idNumber.asInteger.asUGenInput]} ?? {[2,0,0]};
|
||||
idPrefix = idPrefix !? {[idPrefix.asString.size] ++ idPrefix.asString.ascii} ?? {0};
|
||||
var args;
|
||||
buf ?? {(this.class.name ++ ": No input buffer provided").error};
|
||||
|
||||
args = [-1] ++ dataset.asUGenInput ++idPrefix ++ idNumber ++ buf.asUGenInput ++ trig ++ blocking;
|
||||
idNumber = idNumber !? {[2,1,idNumber.asInteger.asUGenInput]} ?? {[2,0,0]};
|
||||
idPrefix = idPrefix !? {[idPrefix.asString.size] ++ idPrefix.asString.ascii} ?? {0};
|
||||
|
||||
^FluidProxyUgen.kr(\FluidDataSetWrTrigger,*args);
|
||||
args = [-1] ++ dataset.asUGenInput ++idPrefix ++ idNumber ++ buf.asUGenInput ++ trig ++ blocking;
|
||||
|
||||
^FluidProxyUgen.kr(\FluidDataSetWrTrigger,*args);
|
||||
}
|
||||
}
|
||||
|
||||
FluidDataSetWrTrigger : FluidProxyUgen {}
|
||||
FluidDataSetWrTrigger : FluidProxyUgen {}
|
||||
|
||||
@ -1,59 +1,59 @@
|
||||
FluidKNNRegressor : FluidModelObject {
|
||||
|
||||
var <>numNeighbours, <>weight;
|
||||
var <>numNeighbours, <>weight;
|
||||
|
||||
*new {|server, numNeighbours = 3, weight = 1|
|
||||
^super.new(server,[numNeighbours,weight])
|
||||
.numNeighbours_(numNeighbours)
|
||||
.weight_(weight);
|
||||
.numNeighbours_(numNeighbours)
|
||||
.weight_(weight);
|
||||
}
|
||||
|
||||
prGetParams{^[this.id,this.numNeighbours,this.weight,-1,-1];}
|
||||
prGetParams{^[this.id,this.numNeighbours,this.weight,-1,-1];}
|
||||
|
||||
fitMsg{|sourceDataSet, targetDataSet|
|
||||
^this.prMakeMsg(\fit,this.id,sourceDataSet.id,targetDataSet.id)
|
||||
}
|
||||
fitMsg{|sourceDataSet, targetDataSet|
|
||||
^this.prMakeMsg(\fit,this.id,sourceDataSet.id,targetDataSet.id)
|
||||
}
|
||||
|
||||
fit{|sourceDataSet, targetDataSet, action|
|
||||
actions[\fit] = [nil,action];
|
||||
this.prSendMsg(this.fitMsg(sourceDataSet, targetDataSet));
|
||||
actions[\fit] = [nil,action];
|
||||
this.prSendMsg(this.fitMsg(sourceDataSet, targetDataSet));
|
||||
}
|
||||
|
||||
predictMsg{ |sourceDataSet, targetDataSet|
|
||||
^this.prMakeMsg(\predict,this.id,sourceDataSet.id,targetDataSet.id)
|
||||
}
|
||||
predictMsg{ |sourceDataSet, targetDataSet|
|
||||
^this.prMakeMsg(\predict,this.id,sourceDataSet.id,targetDataSet.id)
|
||||
}
|
||||
|
||||
predict{ |sourceDataSet, targetDataSet,action|
|
||||
actions[\predict] = [nil, action];
|
||||
actions[\predict] = [nil, action];
|
||||
this.prSendMsg(this.predictMsg(sourceDataSet, targetDataSet));
|
||||
}
|
||||
|
||||
predictPointMsg { |buffer|
|
||||
^this.prMakeMsg(\predictPoint,id, this.prEncodeBuffer(buffer));
|
||||
}
|
||||
predictPointMsg { |buffer|
|
||||
^this.prMakeMsg(\predictPoint,id, this.prEncodeBuffer(buffer));
|
||||
}
|
||||
|
||||
predictPoint { |buffer, action|
|
||||
actions[\predictPoint] = [number(FluidMessageResponse,_,_),action];
|
||||
this.prSendMsg(this.predictPointMsg(buffer));
|
||||
}
|
||||
|
||||
kr{|trig, inputBuffer,outputBuffer|
|
||||
^FluidKNNRegressorQuery.kr(K2A.ar(trig),
|
||||
this, this.numNeighbours, this.weight,
|
||||
this.prEncodeBuffer(inputBuffer),
|
||||
this.prEncodeBuffer(outputBuffer));
|
||||
}
|
||||
kr{|trig, inputBuffer,outputBuffer|
|
||||
^FluidKNNRegressorQuery.kr(K2A.ar(trig),
|
||||
this, this.numNeighbours, this.weight,
|
||||
this.prEncodeBuffer(inputBuffer),
|
||||
this.prEncodeBuffer(outputBuffer));
|
||||
}
|
||||
}
|
||||
|
||||
FluidKNNRegressorQuery : FluidRTMultiOutUGen {
|
||||
|
||||
*kr{ |trig, model,numNeighbours = 3, weight = 1,inputBuffer, outputBuffer |
|
||||
^this.multiNew('control',trig, model.asUGenInput,
|
||||
numNeighbours,weight,
|
||||
inputBuffer.asUGenInput, outputBuffer.asUGenInput)
|
||||
}
|
||||
*kr{ |trig, model,numNeighbours = 3, weight = 1,inputBuffer, outputBuffer |
|
||||
^this.multiNew('control',trig, model.asUGenInput,
|
||||
numNeighbours,weight,
|
||||
inputBuffer.asUGenInput, outputBuffer.asUGenInput)
|
||||
}
|
||||
|
||||
init { arg ... theInputs;
|
||||
init { arg ... theInputs;
|
||||
inputs = theInputs;
|
||||
^this.initOutputs(1, rate);
|
||||
}
|
||||
|
||||
@ -1,43 +1,43 @@
|
||||
FluidLoudness : FluidRTMultiOutUGen {
|
||||
|
||||
const <features=#[\loudness, \peak];
|
||||
classvar featuresLookup;
|
||||
|
||||
*initClass {
|
||||
featuresLookup = Dictionary.with(*this.features.collect{|x,i| x->(1<<i)});
|
||||
}
|
||||
|
||||
*prWarnUnrecognised {|sym| ("WARNING: FluidLoudness -" + sym + "is not a recognised option").postln}
|
||||
|
||||
*prProcessSelect {|a|
|
||||
var bits;
|
||||
a.asBag.countsDo{|item,count,i|
|
||||
if(count > 1) { ("Option '" ++ item ++ "' is repeated").warn};
|
||||
};
|
||||
bits = a.collect{ |sym|
|
||||
(featuresLookup[sym.asSymbol] !? {|x| x} ?? {this.prWarnUnrecognised(sym); 0})
|
||||
}.reduce{|x,y| x | y};
|
||||
^bits
|
||||
}
|
||||
const <features=#[\loudness, \peak];
|
||||
classvar featuresLookup;
|
||||
|
||||
*initClass {
|
||||
featuresLookup = Dictionary.with(*this.features.collect{|x,i| x->(1<<i)});
|
||||
}
|
||||
|
||||
*prWarnUnrecognised {|sym| ("WARNING: FluidLoudness -" + sym + "is not a recognised option").postln}
|
||||
|
||||
*prProcessSelect {|a|
|
||||
var bits;
|
||||
a.asBag.countsDo{|item,count,i|
|
||||
if(count > 1) { ("Option '" ++ item ++ "' is repeated").warn};
|
||||
};
|
||||
bits = a.collect{ |sym|
|
||||
(featuresLookup[sym.asSymbol] !? {|x| x} ?? {this.prWarnUnrecognised(sym); 0})
|
||||
}.reduce{|x,y| x | y};
|
||||
^bits
|
||||
}
|
||||
|
||||
*kr { arg in = 0, select, kWeighting = 1, truePeak = 1, windowSize = 1024, hopSize = 512, maxWindowSize = 16384;
|
||||
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
|
||||
^this.multiNew('control', in.asAudioRateInput(this), selectbits, kWeighting, truePeak, windowSize, hopSize, maxWindowSize);
|
||||
}
|
||||
|
||||
init {arg ...theInputs;
|
||||
var numChannels;
|
||||
init {arg ...theInputs;
|
||||
var numChannels;
|
||||
inputs = theInputs;
|
||||
numChannels = inputs.at(1).asBinaryDigits.sum;
|
||||
numChannels = inputs.at(1).asBinaryDigits.sum;
|
||||
^this.initOutputs(numChannels,rate);
|
||||
}
|
||||
|
||||
checkInputs {
|
||||
if(inputs.at(6).rate != 'scalar') {
|
||||
^(": maxwindowSize cannot be modulated.");
|
||||
};
|
||||
};
|
||||
^this.checkValidInputs;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,41 +1,41 @@
|
||||
FluidMessageResponse : Object
|
||||
{
|
||||
//selectors is an array of functions
|
||||
//my cunning thought is that those that need extra data (e..g numbers()) can
|
||||
//use partial applicaiton
|
||||
*collectArgs{ |selectors,a|
|
||||
var response = [];
|
||||
var idx = 0;
|
||||
selectors.do{ |selector|
|
||||
var newThings;
|
||||
# newThings,idx = selector.value(a, idx);
|
||||
response = response ++ newThings;
|
||||
};
|
||||
//selectors is an array of functions
|
||||
//my cunning thought is that those that need extra data (e..g numbers()) can
|
||||
//use partial applicaiton
|
||||
*collectArgs{ |selectors,a|
|
||||
var response = [];
|
||||
var idx = 0;
|
||||
selectors.do{ |selector|
|
||||
var newThings;
|
||||
# newThings,idx = selector.value(a, idx);
|
||||
response = response ++ newThings;
|
||||
};
|
||||
|
||||
if(response.size == 1,
|
||||
{^response[0]},{^response})
|
||||
}
|
||||
if(response.size == 1,
|
||||
{^response[0]},{^response})
|
||||
}
|
||||
|
||||
*string{ |a, offset|
|
||||
^[a]
|
||||
}
|
||||
*string{ |a, offset|
|
||||
^[a]
|
||||
}
|
||||
|
||||
*strings {|a,offset|
|
||||
//TODO add an n argument as with numbers() to make this less omnivorous
|
||||
^[a.drop(offset)];
|
||||
}
|
||||
*strings {|a,offset|
|
||||
//TODO add an n argument as with numbers() to make this less omnivorous
|
||||
^[a.drop(offset)];
|
||||
}
|
||||
|
||||
*numbers{ |a, n, offset|
|
||||
n = n ? a.size - offset; //send n = nil to consume everything
|
||||
^[a.copyRange(offset, offset + n),offset + n]
|
||||
}
|
||||
*numbers{ |a, n, offset|
|
||||
n = n ? a.size - offset; //send n = nil to consume everything
|
||||
^[a.copyRange(offset, offset + n),offset + n]
|
||||
}
|
||||
|
||||
*number{ |a,offset|
|
||||
^[a[offset]];
|
||||
}
|
||||
*number{ |a,offset|
|
||||
^[a[offset]];
|
||||
}
|
||||
|
||||
*buffer{ |a,server,offset|
|
||||
server = server ? Server.default ;
|
||||
^[Buffer.cachedBufferAt(server, a[offset]), offset + 1]
|
||||
}
|
||||
*buffer{ |a,server,offset|
|
||||
server = server ? Server.default ;
|
||||
^[Buffer.cachedBufferAt(server, a[offset]), offset + 1]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,103 +1,103 @@
|
||||
FluidNormalize : FluidModelObject {
|
||||
|
||||
var <>min, <>max;
|
||||
var <>min, <>max;
|
||||
|
||||
*new {|server, min = 0, max = 1|
|
||||
^super.new(server,[min,max])
|
||||
.min_(min).max_(max);
|
||||
.min_(min).max_(max);
|
||||
}
|
||||
|
||||
prGetParams{
|
||||
^[this.id, this.min,this.max,-1,-1];
|
||||
}
|
||||
prGetParams{
|
||||
^[this.id, this.min,this.max,-1,-1];
|
||||
}
|
||||
|
||||
|
||||
fitMsg{|dataSet|
|
||||
^this.prMakeMsg(\fit,id,dataSet.id)
|
||||
}
|
||||
fitMsg{|dataSet|
|
||||
^this.prMakeMsg(\fit,id,dataSet.id)
|
||||
}
|
||||
|
||||
fit{|dataSet, action|
|
||||
actions[\fit] = [nil,action];
|
||||
actions[\fit] = [nil,action];
|
||||
this.prSendMsg(this.fitMsg(dataSet));
|
||||
}
|
||||
|
||||
transformMsg{|sourceDataSet, destDataSet|
|
||||
^this.prMakeMsg(\transform,id,sourceDataSet.id,destDataSet.id);
|
||||
}
|
||||
transformMsg{|sourceDataSet, destDataSet|
|
||||
^this.prMakeMsg(\transform,id,sourceDataSet.id,destDataSet.id);
|
||||
}
|
||||
|
||||
transform{|sourceDataSet, destDataSet, action|
|
||||
actions[\transform] = [nil,action];
|
||||
this.prSendMsg(this.transformMsg(sourceDataSet, destDataSet));
|
||||
this.prSendMsg(this.transformMsg(sourceDataSet, destDataSet));
|
||||
}
|
||||
|
||||
fitTransformMsg{|sourceDataSet, destDataSet|
|
||||
^this.prMakeMsg(\fitTransform,id,sourceDataSet.id,destDataSet.id)
|
||||
}
|
||||
fitTransformMsg{|sourceDataSet, destDataSet|
|
||||
^this.prMakeMsg(\fitTransform,id,sourceDataSet.id,destDataSet.id)
|
||||
}
|
||||
|
||||
fitTransform{|sourceDataSet, destDataSet, action|
|
||||
actions[\fitTransform] = [nil,action];
|
||||
actions[\fitTransform] = [nil,action];
|
||||
this.prSendMsg(this.fitTransformMsg(sourceDataSet, destDataSet));
|
||||
}
|
||||
|
||||
transformPointMsg{|sourceBuffer, destBuffer|
|
||||
^this.prMakeMsg(\transformPoint,id,
|
||||
this.prEncodeBuffer(sourceBuffer),
|
||||
this.prEncodeBuffer(destBuffer),
|
||||
["/b_query",destBuffer.asUGenInput]
|
||||
);
|
||||
}
|
||||
transformPointMsg{|sourceBuffer, destBuffer|
|
||||
^this.prMakeMsg(\transformPoint,id,
|
||||
this.prEncodeBuffer(sourceBuffer),
|
||||
this.prEncodeBuffer(destBuffer),
|
||||
["/b_query",destBuffer.asUGenInput]
|
||||
);
|
||||
}
|
||||
|
||||
transformPoint{|sourceBuffer, destBuffer, action|
|
||||
actions[\transformPoint] = [nil,{action.value(destBuffer)}];
|
||||
this.prSendMsg(this.transformPointMsg(sourceBuffer, destBuffer));
|
||||
actions[\transformPoint] = [nil,{action.value(destBuffer)}];
|
||||
this.prSendMsg(this.transformPointMsg(sourceBuffer, destBuffer));
|
||||
}
|
||||
|
||||
inverseTransformMsg{|sourceDataSet, destDataSet|
|
||||
^this.prMakeMsg(\inverseTransform,id,sourceDataSet.id,destDataSet.id);
|
||||
}
|
||||
inverseTransformMsg{|sourceDataSet, destDataSet|
|
||||
^this.prMakeMsg(\inverseTransform,id,sourceDataSet.id,destDataSet.id);
|
||||
}
|
||||
|
||||
inverseTransform{|sourceDataSet, destDataSet, action|
|
||||
actions[\inverseTransform] = [nil,action];
|
||||
this.prSendMsg(this.inverseTransformMsg(sourceDataSet, destDataSet));
|
||||
this.prSendMsg(this.inverseTransformMsg(sourceDataSet, destDataSet));
|
||||
}
|
||||
|
||||
inverseTransformPointMsg{|sourceBuffer, destBuffer|
|
||||
^this.prMakeMsg(\inverseTransformPoint,id,
|
||||
this.prEncodeBuffer(sourceBuffer),
|
||||
this.prEncodeBuffer(destBuffer),
|
||||
["/b_query",destBuffer.asUGenInput]
|
||||
);
|
||||
}
|
||||
inverseTransformPointMsg{|sourceBuffer, destBuffer|
|
||||
^this.prMakeMsg(\inverseTransformPoint,id,
|
||||
this.prEncodeBuffer(sourceBuffer),
|
||||
this.prEncodeBuffer(destBuffer),
|
||||
["/b_query",destBuffer.asUGenInput]
|
||||
);
|
||||
}
|
||||
|
||||
inverseTransformPoint{|sourceBuffer, destBuffer, action|
|
||||
actions[\inverseTransformPoint] = [nil,{action.value(destBuffer)}];
|
||||
this.prSendMsg(this.inverseTransformPointMsg(sourceBuffer, destBuffer));
|
||||
actions[\inverseTransformPoint] = [nil,{action.value(destBuffer)}];
|
||||
this.prSendMsg(this.inverseTransformPointMsg(sourceBuffer, destBuffer));
|
||||
}
|
||||
|
||||
kr{|trig, inputBuffer,outputBuffer,min = 0 ,max = 1,invert = 0|
|
||||
kr{|trig, inputBuffer,outputBuffer,min = 0 ,max = 1,invert = 0|
|
||||
|
||||
min = min ? this.min;
|
||||
max = max ? this.max;
|
||||
min = min ? this.min;
|
||||
max = max ? this.max;
|
||||
|
||||
this.min_(min).max_(max);
|
||||
this.min_(min).max_(max);
|
||||
|
||||
^FluidNormalizeQuery.kr(trig,
|
||||
this, this.prEncodeBuffer(inputBuffer), this.prEncodeBuffer(outputBuffer), this.min, this.max, invert);
|
||||
}
|
||||
^FluidNormalizeQuery.kr(trig,
|
||||
this, this.prEncodeBuffer(inputBuffer), this.prEncodeBuffer(outputBuffer), this.min, this.max, invert);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
FluidNormalizeQuery : FluidRTMultiOutUGen {
|
||||
|
||||
*kr{ |trig, model,inputBuffer,outputBuffer,min = 0 ,max = 1,invert = 0|
|
||||
*kr{ |trig, model,inputBuffer,outputBuffer,min = 0 ,max = 1,invert = 0|
|
||||
// inputBuffer.asUGenInput.postln;
|
||||
^this.multiNew('control',trig, model.asUGenInput,
|
||||
min,max,invert,
|
||||
inputBuffer.asUGenInput, outputBuffer.asUGenInput)
|
||||
}
|
||||
^this.multiNew('control',trig, model.asUGenInput,
|
||||
min,max,invert,
|
||||
inputBuffer.asUGenInput, outputBuffer.asUGenInput)
|
||||
}
|
||||
|
||||
init { arg ... theInputs;
|
||||
init { arg ... theInputs;
|
||||
inputs = theInputs;
|
||||
^this.initOutputs(1, rate);
|
||||
}
|
||||
|
||||
@ -1,44 +1,44 @@
|
||||
FluidPitch : FluidRTMultiOutUGen {
|
||||
|
||||
const <features=#[\pitch, \confidence];
|
||||
classvar featuresLookup;
|
||||
const <features=#[\pitch, \confidence];
|
||||
classvar featuresLookup;
|
||||
|
||||
*initClass {
|
||||
featuresLookup = Dictionary.with(*this.features.collect{|x,i| x->(1<<i)});
|
||||
}
|
||||
|
||||
*prWarnUnrecognised {|sym| ("WARNING: FluidPitch -" + sym + "is not a recognised option").postln}
|
||||
*initClass {
|
||||
featuresLookup = Dictionary.with(*this.features.collect{|x,i| x->(1<<i)});
|
||||
}
|
||||
|
||||
*prProcessSelect {|a|
|
||||
var bits;
|
||||
a.asBag.countsDo{|item,count,i|
|
||||
if(count > 1) { ("Option '" ++ item ++ "' is repeated").warn};
|
||||
};
|
||||
bits = a.collect{ |sym|
|
||||
(featuresLookup[sym.asSymbol] !? {|x| x} ?? {this.prWarnUnrecognised(sym); 0})
|
||||
}.reduce{|x,y| x | y};
|
||||
^bits
|
||||
}
|
||||
*prWarnUnrecognised {|sym| ("WARNING: FluidPitch -" + sym + "is not a recognised option").postln}
|
||||
|
||||
*prProcessSelect {|a|
|
||||
var bits;
|
||||
a.asBag.countsDo{|item,count,i|
|
||||
if(count > 1) { ("Option '" ++ item ++ "' is repeated").warn};
|
||||
};
|
||||
bits = a.collect{ |sym|
|
||||
(featuresLookup[sym.asSymbol] !? {|x| x} ?? {this.prWarnUnrecognised(sym); 0})
|
||||
}.reduce{|x,y| x | y};
|
||||
^bits
|
||||
}
|
||||
|
||||
|
||||
*kr { arg in = 0, select, algorithm = 2, minFreq = 20, maxFreq = 10000, unit = 0, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = -1;
|
||||
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
|
||||
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
|
||||
^this.multiNew('control', in.asAudioRateInput(this), selectbits, algorithm, minFreq, maxFreq, unit, windowSize, hopSize, fftSize, maxFFTSize);
|
||||
}
|
||||
|
||||
init {arg ...theInputs;
|
||||
var numChannels;
|
||||
var numChannels;
|
||||
inputs = theInputs;
|
||||
numChannels = inputs.at(1).asBinaryDigits.sum;
|
||||
numChannels = inputs.at(1).asBinaryDigits.sum;
|
||||
^this.initOutputs(numChannels,rate);
|
||||
}
|
||||
|
||||
checkInputs {
|
||||
if(inputs.at(9).rate != 'scalar') {
|
||||
^(": maxFFTSize cannot be modulated.");
|
||||
};
|
||||
};
|
||||
^this.checkValidInputs;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,346 +1,346 @@
|
||||
FluidServerObject
|
||||
{
|
||||
classvar serverCaches;
|
||||
classvar count;
|
||||
classvar persistent = true;
|
||||
var <server,<id;
|
||||
|
||||
*version{|server|
|
||||
server ?? {server = Server.default};
|
||||
server.sendMsg("/cmd","/"++this.objectClassName++'/version');
|
||||
}
|
||||
|
||||
*initClass {
|
||||
serverCaches = IdentityDictionary.new;
|
||||
count = 0;
|
||||
ServerBoot.add({serverCaches[this]!?{serverCaches[this].cache.put(Server.internal,nil);}},Server.internal);
|
||||
}
|
||||
|
||||
*initCache {|server|
|
||||
serverCaches[this] ?? { serverCaches[this] = FluidServerCache.new};
|
||||
|
||||
if(server === Server.internal and: serverCaches[this].cache[Server.internal].isNil)
|
||||
{
|
||||
this.flush(Server.internal)
|
||||
};
|
||||
|
||||
serverCaches[this].initCache(server);
|
||||
NotificationCenter.register(server,\newAllocators,this,{ count = 0; });
|
||||
}
|
||||
|
||||
*newMsg{|id, params|
|
||||
params = params !? {params.collect(_.asUGenInput)};
|
||||
^this.prMakeMsg(\new,id,*params);
|
||||
}
|
||||
|
||||
*new{ |server, id, params, action, callNew = true|
|
||||
var newObj;
|
||||
server ?? {server = Server.default};
|
||||
if(server.serverRunning.not){"Server not running".warn};
|
||||
id !? { id = id.asInteger }
|
||||
?? { id = count; count = count + 1; };
|
||||
newObj = super.newCopyArgs(server,id,action);
|
||||
// params.postln;
|
||||
if(callNew) {server.listSendMsg(this.newMsg(id,params))};
|
||||
^newObj.cache
|
||||
}
|
||||
|
||||
cache {
|
||||
this.class.initCache(server);
|
||||
serverCaches[this.class].put(server,this.id,this);
|
||||
}
|
||||
|
||||
uncache{
|
||||
serverCaches[this.class].remove(server,id);
|
||||
}
|
||||
|
||||
*prMakeMsg{|msg,id...args|
|
||||
var commandName = "%/%".format(this.objectClassName,msg);
|
||||
^['/cmd', this.objectClassName,commandName,id].addAll(args);
|
||||
}
|
||||
|
||||
prMakeMsg{|msg,id...args| ^this.class.prMakeMsg(msg,id,*args) }
|
||||
|
||||
freeMsg {
|
||||
var msg;
|
||||
id ?? {" % already freed".format(this.class.name).warn; ^nil};
|
||||
this.uncache;
|
||||
msg = this.prMakeMsg(\free,id);
|
||||
id = nil;
|
||||
^msg;
|
||||
}
|
||||
|
||||
free{
|
||||
var msg = this.freeMsg;
|
||||
msg !? {server.listSendMsg(msg)} ?? {^nil};
|
||||
}
|
||||
|
||||
*freeAll{|server|
|
||||
serverCaches[this] !? {|cache|
|
||||
cache.clearCache(server ? Server.default);
|
||||
};
|
||||
count = 0;
|
||||
}
|
||||
|
||||
asUGenInput{ ^id }
|
||||
|
||||
asString {
|
||||
^"%(%)".format(this.class.name,id).asString;
|
||||
}
|
||||
|
||||
asSymbol {
|
||||
^id.asSymbol
|
||||
}
|
||||
|
||||
*objectClassName { ^this.name.asSymbol }
|
||||
|
||||
*flushMsg { ^['/cmd',this.objectClassName ++ '/flush'] }
|
||||
|
||||
*flush {|server| server.listSendMsg(this.flushMsg)}
|
||||
classvar serverCaches;
|
||||
classvar count;
|
||||
classvar persistent = true;
|
||||
var <server,<id;
|
||||
|
||||
*version{|server|
|
||||
server ?? {server = Server.default};
|
||||
server.sendMsg("/cmd","/"++this.objectClassName++'/version');
|
||||
}
|
||||
|
||||
*initClass {
|
||||
serverCaches = IdentityDictionary.new;
|
||||
count = 0;
|
||||
ServerBoot.add({serverCaches[this]!?{serverCaches[this].cache.put(Server.internal,nil);}},Server.internal);
|
||||
}
|
||||
|
||||
*initCache {|server|
|
||||
serverCaches[this] ?? { serverCaches[this] = FluidServerCache.new};
|
||||
|
||||
if(server === Server.internal and: serverCaches[this].cache[Server.internal].isNil)
|
||||
{
|
||||
this.flush(Server.internal)
|
||||
};
|
||||
|
||||
serverCaches[this].initCache(server);
|
||||
NotificationCenter.register(server,\newAllocators,this,{ count = 0; });
|
||||
}
|
||||
|
||||
*newMsg{|id, params|
|
||||
params = params !? {params.collect(_.asUGenInput)};
|
||||
^this.prMakeMsg(\new,id,*params);
|
||||
}
|
||||
|
||||
*new{ |server, id, params, action, callNew = true|
|
||||
var newObj;
|
||||
server ?? {server = Server.default};
|
||||
if(server.serverRunning.not){"Server not running".warn};
|
||||
id !? { id = id.asInteger }
|
||||
?? { id = count; count = count + 1; };
|
||||
newObj = super.newCopyArgs(server,id,action);
|
||||
// params.postln;
|
||||
if(callNew) {server.listSendMsg(this.newMsg(id,params))};
|
||||
^newObj.cache
|
||||
}
|
||||
|
||||
cache {
|
||||
this.class.initCache(server);
|
||||
serverCaches[this.class].put(server,this.id,this);
|
||||
}
|
||||
|
||||
uncache{
|
||||
serverCaches[this.class].remove(server,id);
|
||||
}
|
||||
|
||||
*prMakeMsg{|msg,id...args|
|
||||
var commandName = "%/%".format(this.objectClassName,msg);
|
||||
^['/cmd', this.objectClassName,commandName,id].addAll(args);
|
||||
}
|
||||
|
||||
prMakeMsg{|msg,id...args| ^this.class.prMakeMsg(msg,id,*args) }
|
||||
|
||||
freeMsg {
|
||||
var msg;
|
||||
id ?? {" % already freed".format(this.class.name).warn; ^nil};
|
||||
this.uncache;
|
||||
msg = this.prMakeMsg(\free,id);
|
||||
id = nil;
|
||||
^msg;
|
||||
}
|
||||
|
||||
free{
|
||||
var msg = this.freeMsg;
|
||||
msg !? {server.listSendMsg(msg)} ?? {^nil};
|
||||
}
|
||||
|
||||
*freeAll{|server|
|
||||
serverCaches[this] !? {|cache|
|
||||
cache.clearCache(server ? Server.default);
|
||||
};
|
||||
count = 0;
|
||||
}
|
||||
|
||||
asUGenInput{ ^id }
|
||||
|
||||
asString {
|
||||
^"%(%)".format(this.class.name,id).asString;
|
||||
}
|
||||
|
||||
asSymbol {
|
||||
^id.asSymbol
|
||||
}
|
||||
|
||||
*objectClassName { ^this.name.asSymbol }
|
||||
|
||||
*flushMsg { ^['/cmd',this.objectClassName ++ '/flush'] }
|
||||
|
||||
*flush {|server| server.listSendMsg(this.flushMsg)}
|
||||
}
|
||||
|
||||
FluidBufProcessor : FluidServerObject
|
||||
{
|
||||
var <processAction;
|
||||
var <outputBuffers;
|
||||
var <freeWhenDone;
|
||||
classvar responder;
|
||||
classvar count;
|
||||
|
||||
*cmdPeriod {
|
||||
serverCaches[this] !? {|cache|
|
||||
cache.doAll{|processor| processor !? { processor.free;} };
|
||||
serverCaches[this] = nil;
|
||||
};
|
||||
count = 0;
|
||||
}
|
||||
|
||||
*initCache {|server|
|
||||
// "initcache".postln;
|
||||
// this.done.postln;
|
||||
super.initCache(server);
|
||||
CmdPeriod.add(this);
|
||||
if(serverCaches[this].includesKey(server,\processResponder).not)
|
||||
{
|
||||
serverCaches[this].put(server,\processResponder,OSCFunc({|m|
|
||||
var id = m.last.asInteger;
|
||||
// "I'm in the pizza hut".postln;
|
||||
serverCaches[this].at(server,id) !? {|p|
|
||||
// "I'm in the taco bell".postln ;
|
||||
p!?{
|
||||
p.processAction!?{|a|
|
||||
var bufs = p.outputBuffers;
|
||||
|
||||
bufs = bufs.collect{|b|
|
||||
if(b.isKindOf(Buffer))
|
||||
{b}
|
||||
{Buffer.cachedBufferAt(server,b)};
|
||||
};
|
||||
a.valueArray(valueArray(bufs));
|
||||
};
|
||||
if(p.freeWhenDone){p.free};
|
||||
}
|
||||
}
|
||||
},this.done ,server.addr).fix)
|
||||
}
|
||||
}
|
||||
|
||||
*new {|server,id,outputBuffers|
|
||||
^super.new(server,id, nil, nil,false).init(outputBuffers);
|
||||
}
|
||||
|
||||
init{ |ob|
|
||||
outputBuffers = ob;
|
||||
}
|
||||
|
||||
*done {
|
||||
^"/%/process".format(this.objectClassName);
|
||||
}
|
||||
|
||||
wait {
|
||||
var condition = Condition.new;
|
||||
id ?? {Error("% already freed".format(this.class.name)).throw};
|
||||
OSCFunc({
|
||||
condition.unhang;
|
||||
},this.class.done,server.addr,argTemplate:[nil,id]).oneShot;
|
||||
condition.hang;
|
||||
}
|
||||
|
||||
processMsg {|params|
|
||||
var msg;
|
||||
var completionMsg = outputBuffers !? {
|
||||
[["/sync"]] ++ outputBuffers.collect{|b| ["/b_query", b.asUGenInput]}
|
||||
} ?? {[]};
|
||||
|
||||
// completionMsg.postln;
|
||||
id ?? {Error("% already freed".format(this.class.name)).throw};
|
||||
msg = this.prMakeMsg(\processNew,id).addAll(params).add(completionMsg);
|
||||
// msg.postln;
|
||||
^msg;
|
||||
}
|
||||
|
||||
processList { |params,shouldFree,action|
|
||||
freeWhenDone = shouldFree;
|
||||
processAction = action;
|
||||
params = params.collect(_.asUGenInput);
|
||||
server.listSendMsg(this.processMsg(params));
|
||||
}
|
||||
|
||||
cancelMsg{
|
||||
id ?? {Error("% already freed".format(this.class.name)).throw};
|
||||
^this.prMakeMsg(\cancel, id);
|
||||
}
|
||||
|
||||
cancel{
|
||||
server.listSendMsg(this.cancelMsg);
|
||||
}
|
||||
|
||||
kr{ ^FluidProxyUgen.kr(this.class.objectClassName ++ "Monitor",id) }
|
||||
var <processAction;
|
||||
var <outputBuffers;
|
||||
var <freeWhenDone;
|
||||
classvar responder;
|
||||
classvar count;
|
||||
|
||||
*cmdPeriod {
|
||||
serverCaches[this] !? {|cache|
|
||||
cache.doAll{|processor| processor !? { processor.free;} };
|
||||
serverCaches[this] = nil;
|
||||
};
|
||||
count = 0;
|
||||
}
|
||||
|
||||
*initCache {|server|
|
||||
// "initcache".postln;
|
||||
// this.done.postln;
|
||||
super.initCache(server);
|
||||
CmdPeriod.add(this);
|
||||
if(serverCaches[this].includesKey(server,\processResponder).not)
|
||||
{
|
||||
serverCaches[this].put(server,\processResponder,OSCFunc({|m|
|
||||
var id = m.last.asInteger;
|
||||
// "I'm in the pizza hut".postln;
|
||||
serverCaches[this].at(server,id) !? {|p|
|
||||
// "I'm in the taco bell".postln ;
|
||||
p!?{
|
||||
p.processAction!?{|a|
|
||||
var bufs = p.outputBuffers;
|
||||
|
||||
bufs = bufs.collect{|b|
|
||||
if(b.isKindOf(Buffer))
|
||||
{b}
|
||||
{Buffer.cachedBufferAt(server,b)};
|
||||
};
|
||||
a.valueArray(valueArray(bufs));
|
||||
};
|
||||
if(p.freeWhenDone){p.free};
|
||||
}
|
||||
}
|
||||
},this.done ,server.addr).fix)
|
||||
}
|
||||
}
|
||||
|
||||
*new {|server,id,outputBuffers|
|
||||
^super.new(server,id, nil, nil,false).init(outputBuffers);
|
||||
}
|
||||
|
||||
init{ |ob|
|
||||
outputBuffers = ob;
|
||||
}
|
||||
|
||||
*done {
|
||||
^"/%/process".format(this.objectClassName);
|
||||
}
|
||||
|
||||
wait {
|
||||
var condition = Condition.new;
|
||||
id ?? {Error("% already freed".format(this.class.name)).throw};
|
||||
OSCFunc({
|
||||
condition.unhang;
|
||||
},this.class.done,server.addr,argTemplate:[nil,id]).oneShot;
|
||||
condition.hang;
|
||||
}
|
||||
|
||||
processMsg {|params|
|
||||
var msg;
|
||||
var completionMsg = outputBuffers !? {
|
||||
[["/sync"]] ++ outputBuffers.collect{|b| ["/b_query", b.asUGenInput]}
|
||||
} ?? {[]};
|
||||
|
||||
// completionMsg.postln;
|
||||
id ?? {Error("% already freed".format(this.class.name)).throw};
|
||||
msg = this.prMakeMsg(\processNew,id).addAll(params).add(completionMsg);
|
||||
// msg.postln;
|
||||
^msg;
|
||||
}
|
||||
|
||||
processList { |params,shouldFree,action|
|
||||
freeWhenDone = shouldFree;
|
||||
processAction = action;
|
||||
params = params.collect(_.asUGenInput);
|
||||
server.listSendMsg(this.processMsg(params));
|
||||
}
|
||||
|
||||
cancelMsg{
|
||||
id ?? {Error("% already freed".format(this.class.name)).throw};
|
||||
^this.prMakeMsg(\cancel, id);
|
||||
}
|
||||
|
||||
cancel{
|
||||
server.listSendMsg(this.cancelMsg);
|
||||
}
|
||||
|
||||
kr{ ^FluidProxyUgen.kr(this.class.objectClassName ++ "Monitor",id) }
|
||||
}
|
||||
|
||||
FluidOSCPatternInversion : OSCMessageDispatcher
|
||||
{
|
||||
value {|msg, time, addr, recvPort|
|
||||
var msgpath = msg[0].asSymbol;
|
||||
active.keysValuesDo({|key, func|
|
||||
if(msgpath.matchOSCAddressPattern(key), {func.value(msg, time, addr, recvPort);});
|
||||
})
|
||||
}
|
||||
value {|msg, time, addr, recvPort|
|
||||
var msgpath = msg[0].asSymbol;
|
||||
active.keysValuesDo({|key, func|
|
||||
if(msgpath.matchOSCAddressPattern(key), {func.value(msg, time, addr, recvPort);});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FluidDataObject : FluidServerObject
|
||||
{
|
||||
classvar postResponse;
|
||||
|
||||
var <actions;
|
||||
|
||||
*initClass{
|
||||
postResponse = _.postln;
|
||||
}
|
||||
|
||||
*initCache{ |server|
|
||||
super.initCache(server);
|
||||
if(serverCaches[this].includesKey(server,\messageResponder).not)
|
||||
{
|
||||
serverCaches[this].put(server,\messageResponder,OSCFunc.new({|m|
|
||||
var id = m[1].asInteger;
|
||||
var method;
|
||||
serverCaches[this].at(server,id) !? { |p|
|
||||
method = m[0].asString.findRegexp("/"++this.name++"/(.*)")[1][1].asSymbol;
|
||||
p.actions[method] !? {|a|
|
||||
//two items: parser and action
|
||||
var parser = a[0];
|
||||
var action = a[1];
|
||||
var result = FluidMessageResponse.collectArgs(parser,m[2..]);
|
||||
action.value(result);
|
||||
}
|
||||
}
|
||||
},'/' ++ this.objectClassName ++ '/*',server.addr, dispatcher:FluidOSCPatternInversion.new).fix)
|
||||
}
|
||||
}
|
||||
|
||||
*new{|server...args|
|
||||
// args.flatten.postln;
|
||||
^super.new(server,params:args.flatten).init;
|
||||
}
|
||||
|
||||
*cachedInstanceAt{|server,id|
|
||||
this.initCache(server);
|
||||
^serverCaches[this].at(server,id);
|
||||
}
|
||||
|
||||
init {
|
||||
actions = IdentityDictionary.new;
|
||||
}
|
||||
|
||||
prEncodeBuffer { |buf| buf !? {^buf.asUGenInput} ?? {^-1} }
|
||||
|
||||
prSendMsg {|msg| server !? {server.listSendMsg(msg)};}
|
||||
|
||||
colsMsg { ^this.prMakeMsg(\cols,id);}
|
||||
|
||||
cols{ |action=(postResponse)|
|
||||
actions[\cols] = [numbers(FluidMessageResponse,_,1,_),action];
|
||||
this.prSendMsg(this.colsMsg)
|
||||
}
|
||||
|
||||
readMsg { |filename| ^this.prMakeMsg(\read,id,filename.asString);}
|
||||
|
||||
read{|filename, action|
|
||||
actions[\read] = [nil,action];
|
||||
this.prSendMsg(this.readMsg(filename));
|
||||
}
|
||||
|
||||
writeMsg {|filename|
|
||||
// ^['/cmd',this.class.name ++ '/write',id,filename.asString]
|
||||
^this.prMakeMsg(\write,id,filename.asString);
|
||||
}
|
||||
|
||||
write{|filename, action|
|
||||
actions[\write] = [nil,action];
|
||||
this.prSendMsg(this.writeMsg(filename));
|
||||
}
|
||||
|
||||
sizeMsg{
|
||||
// ^['/cmd',this.class.name ++ '/size',id]
|
||||
^this.prMakeMsg(\size,id);
|
||||
}
|
||||
|
||||
size {|action=(postResponse)|
|
||||
actions[\size] = [numbers(FluidMessageResponse,_,1,_),action];
|
||||
this.prSendMsg(this.sizeMsg);
|
||||
}
|
||||
classvar postResponse;
|
||||
|
||||
var <actions;
|
||||
|
||||
*initClass{
|
||||
postResponse = _.postln;
|
||||
}
|
||||
|
||||
*initCache{ |server|
|
||||
super.initCache(server);
|
||||
if(serverCaches[this].includesKey(server,\messageResponder).not)
|
||||
{
|
||||
serverCaches[this].put(server,\messageResponder,OSCFunc.new({|m|
|
||||
var id = m[1].asInteger;
|
||||
var method;
|
||||
serverCaches[this].at(server,id) !? { |p|
|
||||
method = m[0].asString.findRegexp("/"++this.name++"/(.*)")[1][1].asSymbol;
|
||||
p.actions[method] !? {|a|
|
||||
//two items: parser and action
|
||||
var parser = a[0];
|
||||
var action = a[1];
|
||||
var result = FluidMessageResponse.collectArgs(parser,m[2..]);
|
||||
action.value(result);
|
||||
}
|
||||
}
|
||||
},'/' ++ this.objectClassName ++ '/*',server.addr, dispatcher:FluidOSCPatternInversion.new).fix)
|
||||
}
|
||||
}
|
||||
|
||||
*new{|server...args|
|
||||
// args.flatten.postln;
|
||||
^super.new(server,params:args.flatten).init;
|
||||
}
|
||||
|
||||
*cachedInstanceAt{|server,id|
|
||||
this.initCache(server);
|
||||
^serverCaches[this].at(server,id);
|
||||
}
|
||||
|
||||
init {
|
||||
actions = IdentityDictionary.new;
|
||||
}
|
||||
|
||||
prEncodeBuffer { |buf| buf !? {^buf.asUGenInput} ?? {^-1} }
|
||||
|
||||
prSendMsg {|msg| server !? {server.listSendMsg(msg)};}
|
||||
|
||||
colsMsg { ^this.prMakeMsg(\cols,id);}
|
||||
|
||||
cols{ |action=(postResponse)|
|
||||
actions[\cols] = [numbers(FluidMessageResponse,_,1,_),action];
|
||||
this.prSendMsg(this.colsMsg)
|
||||
}
|
||||
|
||||
readMsg { |filename| ^this.prMakeMsg(\read,id,filename.asString);}
|
||||
|
||||
read{|filename, action|
|
||||
actions[\read] = [nil,action];
|
||||
this.prSendMsg(this.readMsg(filename));
|
||||
}
|
||||
|
||||
writeMsg {|filename|
|
||||
// ^['/cmd',this.class.name ++ '/write',id,filename.asString]
|
||||
^this.prMakeMsg(\write,id,filename.asString);
|
||||
}
|
||||
|
||||
write{|filename, action|
|
||||
actions[\write] = [nil,action];
|
||||
this.prSendMsg(this.writeMsg(filename));
|
||||
}
|
||||
|
||||
sizeMsg{
|
||||
// ^['/cmd',this.class.name ++ '/size',id]
|
||||
^this.prMakeMsg(\size,id);
|
||||
}
|
||||
|
||||
size {|action=(postResponse)|
|
||||
actions[\size] = [numbers(FluidMessageResponse,_,1,_),action];
|
||||
this.prSendMsg(this.sizeMsg);
|
||||
}
|
||||
}
|
||||
|
||||
FluidModelObject : FluidDataObject
|
||||
{
|
||||
prGetParams{
|
||||
"Subclass should provide this".throw;
|
||||
}
|
||||
|
||||
prUpdateStateMsg{
|
||||
var params = this.prGetParams.value.collect(_.asUGenInput);
|
||||
^this.prMakeMsg(\setParams,id) ++ params;
|
||||
}
|
||||
|
||||
prSendMsg {|msg|
|
||||
//These need to happen sequentially, but not simultaneously
|
||||
//sending as a bundle makes reasoning about timing w/r/t other
|
||||
//commands more awkward, unless we set the offet to 0 (in which case,
|
||||
//noisy 'late' messages)
|
||||
super.prSendMsg(this.prUpdateStateMsg);
|
||||
super.prSendMsg(msg);
|
||||
}
|
||||
prGetParams{
|
||||
"Subclass should provide this".throw;
|
||||
}
|
||||
|
||||
prUpdateStateMsg{
|
||||
var params = this.prGetParams.value.collect(_.asUGenInput);
|
||||
^this.prMakeMsg(\setParams,id) ++ params;
|
||||
}
|
||||
|
||||
prSendMsg {|msg|
|
||||
//These need to happen sequentially, but not simultaneously
|
||||
//sending as a bundle makes reasoning about timing w/r/t other
|
||||
//commands more awkward, unless we set the offet to 0 (in which case,
|
||||
//noisy 'late' messages)
|
||||
super.prSendMsg(this.prUpdateStateMsg);
|
||||
super.prSendMsg(msg);
|
||||
}
|
||||
}
|
||||
|
||||
FluidRealTimeModel : FluidModelObject
|
||||
{
|
||||
*new{ |server, params|
|
||||
^super.new(server,params++[-1,-1]);
|
||||
}
|
||||
*new{ |server, params|
|
||||
^super.new(server,params++[-1,-1]);
|
||||
}
|
||||
}
|
||||
|
||||
FluidRTQuery : FluidProxyUgen
|
||||
{
|
||||
*kr{ |trig,obj...args|
|
||||
^super.kr(this.name,trig,obj.asUGenInput, *args)
|
||||
}
|
||||
*kr{ |trig,obj...args|
|
||||
^super.kr(this.name,trig,obj.asUGenInput, *args)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FluidRTUGen : UGen
|
||||
{
|
||||
*version{|server|
|
||||
server ?? {server = Server.default};
|
||||
server.sendMsg("/cmd","/"++this.name++'/version');
|
||||
}
|
||||
*version{|server|
|
||||
server ?? {server = Server.default};
|
||||
server.sendMsg("/cmd","/"++this.name++'/version');
|
||||
}
|
||||
}
|
||||
|
||||
FluidRTMultiOutUGen : MultiOutUGen
|
||||
{
|
||||
*version{|server|
|
||||
server ?? {server = Server.default};
|
||||
server.sendMsg("/cmd","/"++this.name++'/version');
|
||||
}
|
||||
*version{|server|
|
||||
server ?? {server = Server.default};
|
||||
server.sendMsg("/cmd","/"++this.name++'/version');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,44 +1,44 @@
|
||||
FluidSpectralShape : FluidRTMultiOutUGen {
|
||||
|
||||
const <features=#[\centroid,\spread,\skewness,\kurtosis,\rolloff,\flatness,\crest];
|
||||
classvar featuresLookup;
|
||||
const <features=#[\centroid,\spread,\skewness,\kurtosis,\rolloff,\flatness,\crest];
|
||||
classvar featuresLookup;
|
||||
|
||||
*initClass {
|
||||
featuresLookup = Dictionary.with(*this.features.collect{|x,i| x->(1<<i)});
|
||||
}
|
||||
|
||||
*prWarnUnrecognised {|sym| ("WARNING: FluidSpectralShape -" + sym + "is not a recognised option").postln}
|
||||
*initClass {
|
||||
featuresLookup = Dictionary.with(*this.features.collect{|x,i| x->(1<<i)});
|
||||
}
|
||||
|
||||
*prProcessSelect {|a|
|
||||
var bits;
|
||||
a.asBag.countsDo{|item,count,i|
|
||||
if(count > 1) { ("Option '" ++ item ++ "' is repeated").warn};
|
||||
};
|
||||
bits = a.collect{ |sym|
|
||||
(featuresLookup[sym.asSymbol] !? {|x| x} ?? {this.prWarnUnrecognised(sym); 0})
|
||||
}.reduce{|x,y| x | y};
|
||||
^bits
|
||||
}
|
||||
*prWarnUnrecognised {|sym| ("WARNING: FluidSpectralShape -" + sym + "is not a recognised option").postln}
|
||||
|
||||
*prProcessSelect {|a|
|
||||
var bits;
|
||||
a.asBag.countsDo{|item,count,i|
|
||||
if(count > 1) { ("Option '" ++ item ++ "' is repeated").warn};
|
||||
};
|
||||
bits = a.collect{ |sym|
|
||||
(featuresLookup[sym.asSymbol] !? {|x| x} ?? {this.prWarnUnrecognised(sym); 0})
|
||||
}.reduce{|x,y| x | y};
|
||||
^bits
|
||||
}
|
||||
|
||||
|
||||
*kr { arg in = 0, select, minFreq = 0, maxFreq = -1, rolloffPercent = 95, unit = 0, power = 0, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = -1;
|
||||
*kr { arg in = 0, select, minFreq = 0, maxFreq = -1, rolloffPercent = 95, unit = 0, power = 0, windowSize = 1024, hopSize = -1, fftSize = -1, maxFFTSize = -1;
|
||||
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
var selectbits = select !? {this.prProcessSelect(select)} ?? {this.prProcessSelect(this.features)};
|
||||
|
||||
^this.multiNew('control', in.asAudioRateInput(this), selectbits, minFreq, maxFreq, rolloffPercent, unit, power, windowSize, hopSize, fftSize, maxFFTSize);
|
||||
^this.multiNew('control', in.asAudioRateInput(this), selectbits, minFreq, maxFreq, rolloffPercent, unit, power, windowSize, hopSize, fftSize, maxFFTSize);
|
||||
}
|
||||
|
||||
init {arg ...theInputs;
|
||||
var numChannels;
|
||||
var numChannels;
|
||||
inputs = theInputs;
|
||||
numChannels = inputs.at(1).asBinaryDigits.sum;
|
||||
numChannels = inputs.at(1).asBinaryDigits.sum;
|
||||
^this.initOutputs(numChannels,rate);
|
||||
}
|
||||
|
||||
checkInputs {
|
||||
if(inputs.at(10).rate != 'scalar') {
|
||||
^(": maxFFTSize cannot be modulated.");
|
||||
};
|
||||
};
|
||||
^this.checkValidInputs;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
TITLE:: FluidAudioTransport
|
||||
summary:: Interpolate between sounds
|
||||
categories:: Libraries>FluidCorpusManipulation
|
||||
related:: Classes/FluidBufAudioTransport
|
||||
|
||||
DESCRIPTION::
|
||||
Interpolates between the spectra of two sounds using the Optimal Transport algorithm
|
||||
|
||||
See
|
||||
Henderson and Solomonm (2019) AUDIO TRANSPORT: A GENERALIZED PORTAMENTO VIA OPTIMAL TRANSPORT, DaFx
|
||||
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: ar
|
||||
Process incoming audio signals
|
||||
|
||||
ARGUMENT:: in
|
||||
Source A
|
||||
|
||||
ARGUMENT:: in2
|
||||
Source B
|
||||
|
||||
ARGUMENT:: interpolation
|
||||
The amount to interpolate between A and B (0-1, 0 = A, 1 = B)
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
The window size in samples. As AudioTransport 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 hop size in samples. As AudioTransport relies on spectral frames, we need to move the window forward. It can be any size but low overlap may create audible artefacts. The -1 default value will default to half of windowSize (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 than the window size provides interpolation in frequency. The -1 default value will use the next power of 2 equal or above the windowSize.
|
||||
|
||||
ARGUMENT:: maxFFTSize
|
||||
How large can the FFT be, by allocating memory at instantiation time. This cannot be modulated.
|
||||
|
||||
RETURNS::
|
||||
An audio stream with the interpolated spectrum of the inputs.
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
//didactic - the mouse X axis interpolates between the two sinewaves
|
||||
{FluidAudioTransport.ar(SinOsc.ar(220,mul: 0.1),SinOsc.ar(440,mul: 0.02),MouseX.kr())}.play;
|
||||
|
||||
//notice how the interpolation quantizes to the FFT bins. Like most spectral processes, it benefits from oversampling the fft... at the cost of CPU power, obviously.
|
||||
{FluidAudioTransport.ar(SinOsc.ar(220,mul: 0.1),SinOsc.ar(440,mul: 0.02),MouseX.kr(),fftSize: 8192)}.play;
|
||||
|
||||
// when the signal is steady, larger hopSize can be accommodated to save back on the CPU
|
||||
{FluidAudioTransport.ar(SinOsc.ar(220,mul: 0.1),SinOsc.ar(440,mul: 0.02),MouseX.kr(),windowSize: 8192)}.play; // here we get a default hop of half the window so 8 times less than above.
|
||||
|
||||
//if you CPU can cope, try this setting, almost smooth, but attacks would smear (the Y axis mixes some in to hear the effect)
|
||||
{var attacks = Impulse.ar(1,mul: MouseY.kr(-40,10).dbamp); FluidAudioTransport.ar(SinOsc.ar(220,mul: 0.1,add: attacks),SinOsc.ar(440,mul: 0.02,add: attacks),MouseX.kr(),windowSize: 16000)}.play;
|
||||
|
||||
//richer with complex spectra
|
||||
//load 2 files
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-CEL-GlitchyMusicBoxMelo.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Tremblay-CF-ChurchBells.wav"));
|
||||
)
|
||||
//listen to them
|
||||
b.play
|
||||
c.play
|
||||
//stereo cross!
|
||||
{FluidAudioTransport.ar(PlayBuf.ar(2,b,loop: 1),PlayBuf.ar(2,c,loop: 1),MouseX.kr())}.play;
|
||||
|
||||
::
|
||||
@ -1,344 +0,0 @@
|
||||
TITLE:: FluidBufAudioTransport
|
||||
SUMMARY:: Interpolate between buffers
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Classes/FluidAudioTransport
|
||||
DESCRIPTION::
|
||||
|
||||
|
||||
Interpolates between the spectra of two sounds using the Optimal Transport algorithm
|
||||
|
||||
See Henderson and Solomonm (2019) AUDIO TRANSPORT: A GENERALIZED PORTAMENTO VIA OPTIMAL TRANSPORT, DaFx
|
||||
|
||||
|
||||
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
Processs the source LINK::Classes/Buffer:: on the LINK::Classes/Server::. CODE::processBlocking:: will execute directly in the server command FIFO, whereas CODE::process:: will delegate to a separate worker thread. The latter is generally only worthwhile for longer-running jobs where you don't wish to tie up the server.
|
||||
|
||||
ARGUMENT:: server
|
||||
The LINK::Classes/Server:: on which the buffers to be processed are allocated.
|
||||
|
||||
ARGUMENT:: sourceA
|
||||
|
||||
|
||||
The first source buffer
|
||||
|
||||
|
||||
ARGUMENT:: startFrameA
|
||||
|
||||
|
||||
offset into the first source buffer (samples)
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numFramesA
|
||||
|
||||
|
||||
number of samples to use from first source buffer
|
||||
|
||||
|
||||
ARGUMENT:: startChanA
|
||||
|
||||
|
||||
starting channel of first source buffer
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numChansA
|
||||
|
||||
|
||||
number of channels to process in first source buffer
|
||||
|
||||
|
||||
ARGUMENT:: sourceB
|
||||
|
||||
|
||||
the second source buffer
|
||||
|
||||
|
||||
ARGUMENT:: startFrameB
|
||||
|
||||
|
||||
offset into the second source buffer (samples)
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numFramesB
|
||||
|
||||
|
||||
number of samples to process from second buffer
|
||||
|
||||
|
||||
ARGUMENT:: startChanB
|
||||
|
||||
|
||||
starting channel for second buffer
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numChansB
|
||||
|
||||
|
||||
number of channels to process in second buffer
|
||||
|
||||
|
||||
ARGUMENT:: destination
|
||||
|
||||
|
||||
buffer for interpolated audio
|
||||
|
||||
|
||||
ARGUMENT:: interpolation
|
||||
|
||||
|
||||
The amount to interpolate between A and B (0-1, 0 = A, 1 = B)
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0.0
|
||||
|
||||
##
|
||||
Maximum: 1.0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
|
||||
|
||||
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. LINK::http://www.subsurfwiki.org/wiki/Gabor_uncertainty::
|
||||
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
|
||||
|
||||
The window hop size. As sinusoidal estimation 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 windowSize (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 use the next power of 2 equal or above the highest of windowSize and (bandwidth - 1) * 2.
|
||||
|
||||
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default CODE::true::
|
||||
|
||||
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 CODE::[features]:: as an argument.
|
||||
|
||||
RETURNS:: An instance of the processor
|
||||
|
||||
METHOD:: kr
|
||||
Trigger the equivalent behaviour to CODE::processBlocking / process:: from a LINK::Classes/Synth::. Can be useful for expressing a sequence of buffer and data processing jobs to execute. Note that the work still executes on the server command FIFO (not the audio thread), and it is the caller's responsibility to manage the sequencing, using the CODE::done:: status of the various UGens.
|
||||
ARGUMENT:: sourceA
|
||||
|
||||
|
||||
The first source buffer
|
||||
|
||||
|
||||
ARGUMENT:: startFrameA
|
||||
|
||||
|
||||
offset into the first source buffer (samples)
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numFramesA
|
||||
|
||||
|
||||
number of samples to use from first source buffer
|
||||
|
||||
|
||||
ARGUMENT:: startChanA
|
||||
|
||||
|
||||
starting channel of first source buffer
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numChansA
|
||||
|
||||
|
||||
number of channels to process in first source buffer
|
||||
|
||||
|
||||
ARGUMENT:: sourceB
|
||||
|
||||
|
||||
the second source buffer
|
||||
|
||||
|
||||
ARGUMENT:: startFrameB
|
||||
|
||||
|
||||
offset into the second source buffer (samples)
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numFramesB
|
||||
|
||||
|
||||
number of samples to process from second buffer
|
||||
|
||||
|
||||
ARGUMENT:: startChanB
|
||||
|
||||
|
||||
starting channel for second buffer
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numChansB
|
||||
|
||||
|
||||
number of channels to process in second buffer
|
||||
|
||||
|
||||
ARGUMENT:: destination
|
||||
|
||||
|
||||
buffer for interpolated audio
|
||||
|
||||
|
||||
ARGUMENT:: interpolation
|
||||
|
||||
|
||||
The amount to interpolate between A and B (0-1, 0 = A, 1 = B)
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0.0
|
||||
|
||||
##
|
||||
Maximum: 1.0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
|
||||
|
||||
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. LINK::http://www.subsurfwiki.org/wiki/Gabor_uncertainty::
|
||||
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
|
||||
|
||||
The window hop size. As sinusoidal estimation 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 windowSize (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 use the next power of 2 equal or above the highest of windowSize and (bandwidth - 1) * 2.
|
||||
|
||||
|
||||
|
||||
ARGUMENT:: trig
|
||||
A CODE::kr:: signal that will trigger execution
|
||||
|
||||
ARGUMENT:: blocking
|
||||
Whether to execute this process directly on the server command FIFO or delegate to a worker thread. See CODE::processBlocking/process:: for caveats.
|
||||
|
||||
|
||||
INSTANCEMETHODS::
|
||||
METHOD:: kr
|
||||
Returns a UGen that reports the progress of the running task when executing in a worker thread. Calling code::scope:: with this can be used for a convinient progress monitor
|
||||
|
||||
METHOD:: cancel
|
||||
Cancels non-blocking processing
|
||||
|
||||
METHOD:: wait
|
||||
When called in the context of a LINK::Classes/Routine:: (it won't work otherwise), will block execution until the processor has finished. This can be convinient for writing sequences of processes more linearly than using lots of nested actions.
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
//Didactic:
|
||||
//Make 2 sinewave sources to be interpolated
|
||||
(
|
||||
b = Buffer.loadCollection(s, FloatArray.fill(44100, {|a|(a / pi).sin * 0.1}));
|
||||
c = Buffer.loadCollection(s, FloatArray.fill(44100, {|a|(a / pi / 2).sin * 0.02}));
|
||||
d = Buffer.new
|
||||
)
|
||||
|
||||
//make an sound interpolating their spectrum
|
||||
FluidBufAudioTransport.process(s,b,source2:c,destination:d,interpolation:0.5,action:{"Ding".postln})
|
||||
|
||||
// listen to the source and the result
|
||||
b.play
|
||||
c.play
|
||||
d.play
|
||||
|
||||
// note that the process is quantized by the spectral bins. For an example of the pros and cons of these settings on this given process, please see the real-time FluidAudioTransport helpfile.
|
||||
|
||||
// more interesting sources: two cardboard bowing gestures
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Green-Box641.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Green-Box639.wav"));
|
||||
d = Buffer.new
|
||||
)
|
||||
|
||||
// listen to the source
|
||||
b.play
|
||||
c.play
|
||||
|
||||
// process and listen
|
||||
FluidBufAudioTransport.process(s,b,source2:c,destination:d,interpolation:0.5,action:{"Ding".postln})
|
||||
d.play
|
||||
// try various interpolation factors (0.1 and 0.9 are quite good
|
||||
::
|
||||
@ -1,376 +0,0 @@
|
||||
TITLE:: FluidBufChroma
|
||||
SUMMARY:: A histogram of pitch classes on a Buffer
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Classes/FluidChroma,Classes/FluidBufPitch,Classes/FluidBufLoudness,Classes/FluidBufMFCC,Classes/FluidBufSpectralShape,Classes/FluidBufStats,Guides/FluidCorpusManipulationToolkit,Classes/FluidBufMFCC
|
||||
DESCRIPTION::
|
||||
|
||||
|
||||
This class computes a histogram of the energy contained for each pitch class across the analysis frequency range.
|
||||
|
||||
|
||||
|
||||
Also known as a chromagram, this typically allows you to get a contour of how much each semitone is represented in the spectrum over time. The number of chroma bins (and, thus, pitch classes) and the central reference frequency can be adjusted.
|
||||
|
||||
The process will return a single multichannel buffer of STRONG::numChroma:: per input channel. Each frame represents a value, which is every hopSize.
|
||||
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
Processs the source LINK::Classes/Buffer:: on the LINK::Classes/Server::. CODE::processBlocking:: will execute directly in the server command FIFO, whereas CODE::process:: will delegate to a separate worker thread. The latter is generally only worthwhile for longer-running jobs where you don't wish to tie up the server.
|
||||
|
||||
ARGUMENT:: server
|
||||
The LINK::Classes/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 analysed. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
|
||||
|
||||
Where in the srcBuf should the process start, in sample.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
|
||||
|
||||
How many frames should be processed.
|
||||
|
||||
|
||||
ARGUMENT:: startChan
|
||||
|
||||
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numChans
|
||||
|
||||
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
|
||||
ARGUMENT:: features
|
||||
|
||||
|
||||
The destination buffer for the STRONG::numChroma:: to be written to.
|
||||
|
||||
|
||||
ARGUMENT:: numChroma
|
||||
|
||||
|
||||
The number of chroma bins per octave. It will determine how many channels are output per input channel.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 2
|
||||
|
||||
##
|
||||
Maximum: CODE::maxNumChroma::
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: ref
|
||||
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
##
|
||||
Maximum: 22000
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: normalize
|
||||
|
||||
|
||||
This flag enables the scaling of the output. It is off (0) by default. (1) will normalise each frame to sum to 1. (2) normalises each frame relative to the loudest chroma bin being 1.
|
||||
|
||||
|
||||
ARGUMENT:: minFreq
|
||||
|
||||
|
||||
The lower frequency included in the analysis, in Hz.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: maxFreq
|
||||
|
||||
|
||||
The highest frequency included in the analysis, in Hz.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: -1
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
|
||||
|
||||
The window size. As chroma description relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. LINK::http://www.subsurfwiki.org/wiki/Gabor_uncertainty::
|
||||
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
|
||||
|
||||
The window hop size. As chroma description relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
ARGUMENT:: padding
|
||||
|
||||
|
||||
Controls the zero-padding added to either end of the source buffer or segment. Possible values are 0 (no padding), 1 (default, half the window size), or 2 (window size - hop size). Padding ensures that all input samples are completely analysed: with no padding, the first analysis window starts at time 0, and the samples at either end will be tapered by the STFT windowing function. Mode 1 has the effect of centering the first sample in the analysis window and ensuring that the very start and end of the segment are accounted for in the analysis. Mode 2 can be useful when the overlap factor (window size / hop size) is greater than 2, to ensure that the input samples at either end of the segment are covered by the same number of analysis frames as the rest of the analysed material.
|
||||
|
||||
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default CODE::true::
|
||||
|
||||
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 CODE::[features]:: as an argument.
|
||||
|
||||
RETURNS:: An instance of the processor
|
||||
|
||||
METHOD:: kr
|
||||
Trigger the equivalent behaviour to CODE::processBlocking / process:: from a LINK::Classes/Synth::. Can be useful for expressing a sequence of buffer and data processing jobs to execute. Note that the work still executes on the server command FIFO (not the audio thread), and it is the caller's responsibility to manage the sequencing, using the CODE::done:: status of the various UGens.
|
||||
ARGUMENT:: source
|
||||
|
||||
|
||||
The index of the buffer to use as the source material to be analysed. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
|
||||
|
||||
Where in the srcBuf should the process start, in sample.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
|
||||
|
||||
How many frames should be processed.
|
||||
|
||||
|
||||
ARGUMENT:: startChan
|
||||
|
||||
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numChans
|
||||
|
||||
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
|
||||
ARGUMENT:: features
|
||||
|
||||
|
||||
The destination buffer for the STRONG::numChroma:: to be written to.
|
||||
|
||||
|
||||
ARGUMENT:: numChroma
|
||||
|
||||
|
||||
The number of chroma bins per octave. It will determine how many channels are output per input channel.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 2
|
||||
|
||||
##
|
||||
Maximum: CODE::maxNumChroma::
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: ref
|
||||
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
##
|
||||
Maximum: 22000
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: normalize
|
||||
|
||||
|
||||
This flag enables the scaling of the output. It is off (0) by default. (1) will normalise each frame to sum to 1. (2) normalises each frame relative to the loudest chroma bin being 1.
|
||||
|
||||
|
||||
ARGUMENT:: minFreq
|
||||
|
||||
|
||||
The lower frequency included in the analysis, in Hz.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: maxFreq
|
||||
|
||||
|
||||
The highest frequency included in the analysis, in Hz.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: -1
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
|
||||
|
||||
The window size. As chroma description relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. LINK::http://www.subsurfwiki.org/wiki/Gabor_uncertainty::
|
||||
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
|
||||
|
||||
The window hop size. As chroma description relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
ARGUMENT:: padding
|
||||
|
||||
|
||||
Controls the zero-padding added to either end of the source buffer or segment. Possible values are 0 (no padding), 1 (default, half the window size), or 2 (window size - hop size). Padding ensures that all input samples are completely analysed: with no padding, the first analysis window starts at time 0, and the samples at either end will be tapered by the STFT windowing function. Mode 1 has the effect of centering the first sample in the analysis window and ensuring that the very start and end of the segment are accounted for in the analysis. Mode 2 can be useful when the overlap factor (window size / hop size) is greater than 2, to ensure that the input samples at either end of the segment are covered by the same number of analysis frames as the rest of the analysed material.
|
||||
|
||||
|
||||
|
||||
ARGUMENT:: trig
|
||||
A CODE::kr:: signal that will trigger execution
|
||||
|
||||
ARGUMENT:: blocking
|
||||
Whether to execute this process directly on the server command FIFO or delegate to a worker thread. See CODE::processBlocking/process:: for caveats.
|
||||
|
||||
|
||||
INSTANCEMETHODS::
|
||||
METHOD:: kr
|
||||
Returns a UGen that reports the progress of the running task when executing in a worker thread. Calling code::scope:: with this can be used for a convinient progress monitor
|
||||
|
||||
METHOD:: cancel
|
||||
Cancels non-blocking processing
|
||||
|
||||
METHOD:: wait
|
||||
When called in the context of a LINK::Classes/Routine:: (it won't work otherwise), will block execution until the processor has finished. This can be convinient for writing sequences of processes more linearly than using lots of nested actions.
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// create some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-SlideChoirAdd-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
// run the process with basic parameters
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufChroma.process(s, b, features: c, windowSize: 4096).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// listen to the source and look at the buffer
|
||||
b.play;
|
||||
c.plot
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// load two very different files
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-SA-UprightPianoPedalWide.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
)
|
||||
|
||||
// composite one on left one on right as test signals
|
||||
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
|
||||
b.play
|
||||
|
||||
// create a buffer as destinations
|
||||
c = Buffer.new(s);
|
||||
|
||||
//run the process on them
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufChroma.process(s, b, features: c, windowSize: 4096).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the buffer: 12 chroma bins for left, then 12 chroma bins for right
|
||||
c.plot(separately:true)
|
||||
::
|
||||
@ -1,112 +0,0 @@
|
||||
TITLE:: FluidBufCompose
|
||||
SUMMARY:: Buffer Compositing Utility
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation, UGens>Buffer
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading, Classes/Buffer
|
||||
|
||||
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/FluidCorpusManipulation:: as part of the FluCoMa project. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
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.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This method triggers the compositing.
|
||||
|
||||
ARGUMENT:: server
|
||||
The server on which the buffers to be processed are allocated.
|
||||
|
||||
ARGUMENT:: source
|
||||
The bufNum of the source buffer.
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
The starting point (in samples) from which to copy in the source buffer.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
The duration (in samples) to copy from the source buffer. The default (-1) copies the full lenght of the buffer.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
The first channel from which to copy in the source buffer.
|
||||
|
||||
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.
|
||||
|
||||
ARGUMENT:: gain
|
||||
The gain applied to the samples to be copied from the source buffer.
|
||||
|
||||
ARGUMENT:: destination
|
||||
The bufNum of the destination buffer.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
ARGUMENT:: action
|
||||
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:: an instance of the processor
|
||||
|
||||
DISCUSSION::
|
||||
It is important to understand the rules used for determining the final desintinaiton buffer dimensions to get the most out of this object. If needs be, the destination buffer will be resized to the maxima of the requsted source numFrames and numChannels. Frames will be written up to the limit of actually available samples (meaning you can create zero padding); channels will be written modulo the available channels, taking into account the channel offsets, meaning you can have channels repeat or loop into the source buffer's channels. See the examples below.
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// load some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-AaS-SynthTwoVoices-M.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Tremblay-SA-UprightPianoPedalWide.wav"));
|
||||
d = Buffer.new(s);
|
||||
)
|
||||
|
||||
// with basic params (basic summing of each full buffer in all dimensions)
|
||||
(
|
||||
Routine{
|
||||
FluidBufCompose.process(s, source: b, destination: d);
|
||||
FluidBufCompose.process(s, source: c, destination: d, destGain: 1.0);
|
||||
s.sync;
|
||||
d.query;
|
||||
d.play;
|
||||
}.play;
|
||||
)
|
||||
//constructing a mono buffer, with a quiet punch from the synth, with a choked piano resonance from the left channel
|
||||
(
|
||||
Routine{
|
||||
d.free; d = Buffer.new(s);
|
||||
FluidBufCompose.process(s, source: b, numFrames: 9000, gain: 0.5, destination: d);
|
||||
FluidBufCompose.process(s, source: c, startFrame:30000, numFrames:44100, numChans:1, gain:0.9, destination: d, destGain: 1.0).wait;
|
||||
d.query;
|
||||
d.play;
|
||||
}.play
|
||||
)
|
||||
//constructing a stereo buffer, with the end of the mono synth in both channels, with a piano resonance in swapped stereo
|
||||
(
|
||||
Routine{
|
||||
d.free; d = Buffer.new(s);
|
||||
FluidBufCompose.process(s, source: b, startFrame: 441000, numChans: 2, gain: 0.6, destination: d);
|
||||
FluidBufCompose.process(s, source: c, numFrames: 78000, startChan: 1, numChans: 2, gain: 0.5, destStartFrame: 22050, destination: d, destGain: 1.0).wait;
|
||||
d.query;
|
||||
d.play;
|
||||
}.play
|
||||
)
|
||||
//constructing a one second buffer: the first second of each buffer, the mono synth on the right, the piano on the left
|
||||
(
|
||||
Routine{
|
||||
d.free; d = Buffer.new(s);
|
||||
FluidBufCompose.process(s, source: b, numFrames: 44100, numChans: 1, destStartChan: 1, destination: d);
|
||||
FluidBufCompose.process(s, source: c, numFrames:44100, numChans:1, destination: d, destGain: 1.0).wait;
|
||||
d.query;
|
||||
d.play;
|
||||
}.play
|
||||
)
|
||||
::
|
||||
@ -1,120 +0,0 @@
|
||||
TITLE:: FluidBufFlatten
|
||||
summary:: Flatten a multichannel buffer on the server
|
||||
categories:: Libraries>FluidCorpusManipulation
|
||||
related:: Classes/Buffer, Classes/FluidBufCompose, Classes/FluidBufSelect, Classes/FluidBufSelectEvery
|
||||
|
||||
DESCRIPTION::
|
||||
Flatten a multichannel link::Classes/Buffer:: to a single channel. This can be useful for constructing n-dimensional data points for use with link::Classes/FluidDataSet::
|
||||
|
||||
The code::axis:: determines how the flattening is arranged. The default value, 1, flattens channel-wise, such that (if we imagine channels are rows, time positions are columns):
|
||||
|
||||
table::
|
||||
## a 1 || a 2 || a 3
|
||||
## b 1 || b 2 || b 3
|
||||
## c 1 || c 2 || c 3
|
||||
::
|
||||
|
||||
becomes
|
||||
|
||||
table::
|
||||
## a 1 || b 1 || c 1 || a 2 || b 2 || c 2 || a 3 || b 3 || c 3
|
||||
::
|
||||
|
||||
whereas with code::axis = 0:: we get
|
||||
|
||||
table::
|
||||
## a 1 || a 2 || a 3 || b 1 || b 2 || b 3 || c 1 || c 2 || c 3
|
||||
::
|
||||
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
private::new1
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
|
||||
Run the process on the given sever, and perfrom code::action:: when done
|
||||
|
||||
ARGUMENT:: server
|
||||
The link::Classes/Server:: on which to run
|
||||
|
||||
ARGUMENT:: source
|
||||
The link::Classes/Buffer:: to flatten
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
Where in the source should the flattening process start, in samples.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
How many frames should be processed.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
For multichannel source buffers, which channel to start processing at.
|
||||
|
||||
ARGUMENT:: numChans
|
||||
For multichannel source buffers, how many channels should be processed.
|
||||
|
||||
ARGUMENT:: destination
|
||||
The link::Classes/Buffer:: to write the flattened data to
|
||||
|
||||
ARGUMENT:: axis
|
||||
Whether to group points channel-wise or frame-wise
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
ARGUMENT:: action
|
||||
Runs when processing is complete
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
//FluidBufPitch is useful to illustrate the effect of this, because the pitch and confidence values are easily distinguishable
|
||||
|
||||
(
|
||||
~path = FluidFilesPath();
|
||||
~randomsoundfile = SoundFile.collect(~path +/+ '*').choose;
|
||||
b = Buffer.read(s,~randomsoundfile.path,action:{"Sound Loaded".postln});
|
||||
~pitchdata = Buffer.new;
|
||||
~flatdata = Buffer.new;
|
||||
)
|
||||
|
||||
//Pitch analysis, writes pitches as frequencies to chan 0, confidences [0-1] to chan 1
|
||||
FluidBufPitch.process(s,b,numFrames:512 * 10,numChans:1,features:~pitchdata,action:{"Pitch Analysis Done".postln});
|
||||
|
||||
// Flatten and print the flat buffer. We expect to see larger numbers (20-2000) interleaved with smaller (0-1)
|
||||
(
|
||||
FluidBufFlatten.process(s,~pitchdata, destination: ~flatdata, axis:1, action:{
|
||||
~flatdata.loadToFloatArray(action:{ |a|
|
||||
a.postln;
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
//changing the axis, we see all large numbers first
|
||||
(
|
||||
FluidBufFlatten.process(s,~pitchdata, destination:~flatdata, axis:0, action:{
|
||||
~flatdata.loadToFloatArray(action:{ |a|
|
||||
a.postln;
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
//adding the source range make this processor very powerful, but can be quite confusing
|
||||
//here we take only one frame starting at the second one (0-counting)
|
||||
(
|
||||
FluidBufFlatten.process(s,~pitchdata,startFrame: 1,numFrames: 1, destination:~flatdata, action:{
|
||||
~flatdata.loadToFloatArray(action:{ |a|
|
||||
a.postln;
|
||||
})
|
||||
})
|
||||
)
|
||||
//and here we take only the confidences
|
||||
(
|
||||
FluidBufFlatten.process(s,~pitchdata, startChan: 1, destination:~flatdata, action:{
|
||||
~flatdata.loadToFloatArray(action:{ |a|
|
||||
a.postln;
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
::
|
||||
@ -1,124 +0,0 @@
|
||||
TITLE:: FluidBufLoudness
|
||||
SUMMARY:: A Loudness and True-Peak Descriptor on a Buffer
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading
|
||||
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements two loudness descriptors, computing the true peak of the signal as well as applying the filters proposed by broadcasting standards to emulate the perception of amplitude. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The process will return a multichannel buffer with two channels per input channel, one for loudness and one for the true peak value of the frame, both in dBfs. More information on broadcasting standardisation of loudness measurement is available at the reference page FOOTNOTE::https://tech.ebu.ch/docs/tech/tech3341.pdf:: and in more musician-friendly explantions here FOOTNOTE::http://designingsound.org/2013/02/06/loudness-and-metering-part-1/::. Each sample represents a value, which is every hopSize. Its sampling rate is STRONG::sourceSR / hopSize::.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the loudness descriptor 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 described. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
Where in the srcBuf should the process start, in sample.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
How many frames should be processed.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
ARGUMENT:: numChans
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
ARGUMENT:: features
|
||||
The destination buffer for the loudness descriptors.
|
||||
|
||||
ARGUMENT:: kWeighting
|
||||
A flag to switch the perceptual model of loudness. On by default, removing it makes the algorithm more CPU efficient by reverting to a simple RMS of the frame.
|
||||
|
||||
ARGUMENT:: truePeak
|
||||
A flag to switch the computation of TruePeak. On by default, removing it makes the algorithm more CPU efficient by reverting to a simple absolute peak of the frame.
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
The size of the window on which the computation is done. By default 1024 to be similar with all other FluCoMa objects, the EBU specifies other values as per the examples below.
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
How much the buffered window moves forward, in samples. By default 512 to be similar with all other FluCoMa objects, the EBU specifies other values as per the examples below.
|
||||
|
||||
ARGUMENT:: padding
|
||||
Controls the zero-padding added to either end of the source buffer or segment. Possible values are 0 (no padding), 1 (default, half the window size), or 2 (window size - hop size). Padding ensures that all input samples are completely analysed: with no padding, the first analysis window starts at time 0, and the samples at either end will be tapered by the STFT windowing function. Mode 1 has the effect of centering the first sample in the analysis window and ensuring that the very start and end of the segment are accounted for in the analysis. Mode 2 can be useful when the overlap factor (window size / hop size) is greater than 2, to ensure that the input samples at either end of the segment are covered by the same number of analysis frames as the rest of the analysed material.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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 [features] as an argument.
|
||||
|
||||
returns:: an instance of the processor
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// create a buffer with a short clicking sinusoidal burst (220Hz) starting at frame 8192 for 1024 frames
|
||||
(
|
||||
b = Buffer.sendCollection(s, (Array.fill(8192,{0}) ++ (Signal.sineFill(1203,[0,0,0,0,0,1],[0,0,0,0,0,0.5pi]).takeThese({|x,i|i>1023})) ++ Array.fill(8192,{0})));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
// listen to the source and look at the buffer
|
||||
b.play; b.plot;
|
||||
|
||||
// run the process with basic parameters
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufLoudness.process(s, source:b, features: c).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the analysis
|
||||
c.plot(minval:-130, maxval:6)
|
||||
|
||||
// The values are interleaved [loudness,truepeak] in the buffer as they are on 2 channels: to get to the right frame, divide the SR of the input by the hopSize, then multiply by 2 because of the channel interleaving
|
||||
// here we are querying from one frame before (the signal starts at 8192, which is frame 16 (8192/512), therefore starting the query at frame 15, which is index 30.
|
||||
|
||||
c.getn(30,10,{|x|x.postln})
|
||||
|
||||
// observe that the first frame is silent, as expected. We can appreciate the overshoot of TruePeak of a full range sinewave starting on the second sample (fourth item in the list).
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// load two very different files
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-SA-UprightPianoPedalWide.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
)
|
||||
|
||||
// composite one on left one on right as test signals
|
||||
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
|
||||
b.play
|
||||
|
||||
// create a buffer as destinations
|
||||
c = Buffer.new(s);
|
||||
|
||||
//run the process on them with EBU standard Instant Loudness of
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufLoudness.process(s, b, features: c, windowSize: 17640, hopSize:4410).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the buffer: [loudness,truepeak] for left then [loudness,truepeak] for right
|
||||
c.plot(minval:-40, maxval:0)
|
||||
::
|
||||
@ -1,126 +0,0 @@
|
||||
TITLE:: FluidBufMFCC
|
||||
SUMMARY:: Mel-Frequency Cepstral Coefficients as Spectral Descriptors on a Buffer
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading, Classes/FluidBufMelBands
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements a classic spectral descriptor, the Mel-Frequency Cepstral Coefficients (https://en.wikipedia.org/wiki/Mel-frequency_cepstrum). The input is first filtered in to STRONG::numBands:: perceptually-spaced bands, as in LINK::Classes/FluidMelBands::. It is then analysed into STRONG::numCoeffs:: number of cepstral coefficients. It has the advantage of being amplitude invariant, except for the first coefficient. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The process will return a single multichannel buffer of STRONG::numCoeffs:: per input channel. Each frame represents a value, which is every hopSize.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the spectral shape descriptors 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 described through the various descriptors. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
Where in the srcBuf should the process start, in sample.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
How many frames should be processed.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
ARGUMENT:: numChans
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
ARGUMENT:: features
|
||||
The destination buffer for the numCoeffs coefficients describing the spectral shape.
|
||||
|
||||
ARGUMENT:: numCoeffs
|
||||
The number of cepstral coefficients to be outputed. It will decide how many channels are produce per channel of the source.
|
||||
|
||||
ARGUMENT:: numBands
|
||||
The number of bands that will be perceptually equally distributed between STRONG::minFreq:: and STRONG::maxFreq::.
|
||||
|
||||
ARGUMENT:: startCoeff
|
||||
The lowest index of the output cepstral coefficient, zero-counting.
|
||||
|
||||
ARGUMENT:: minFreq
|
||||
The lower boundary of the lowest band of the model, in Hz.
|
||||
|
||||
ARGUMENT:: maxFreq
|
||||
The highest boundary of the highest band of the model, in Hz.
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
The window size. As MFCC computation 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 hop size. As MFCC computation 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 windowSize (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 use the next power of 2 equal or above the windowSize.
|
||||
|
||||
ARGUMENT:: padding
|
||||
Controls the zero-padding added to either end of the source buffer or segment. Possible values are 0 (no padding), 1 (default, half the window size), or 2 (window size - hop size). Padding ensures that all input samples are completely analysed: with no padding, the first analysis window starts at time 0, and the samples at either end will be tapered by the STFT windowing function. Mode 1 has the effect of centering the first sample in the analysis window and ensuring that the very start and end of the segment are accounted for in the analysis. Mode 2 can be useful when the overlap factor (window size / hop size) is greater than 2, to ensure that the input samples at either end of the segment are covered by the same number of analysis frames as the rest of the analysed material.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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 [features] as an argument.
|
||||
|
||||
returns:: an instance of the processor
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// create some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
// run the process with basic parameters
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufMFCC.process(s, b, features: c).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// listen to the source and look at the buffer
|
||||
b.play;
|
||||
c.plot(separately:true)
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// load two very different files
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-SA-UprightPianoPedalWide.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
)
|
||||
|
||||
// composite one on left one on right as test signals
|
||||
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
|
||||
b.play
|
||||
|
||||
// create a buffer as destinations
|
||||
c = Buffer.new(s);
|
||||
|
||||
//run the process on them
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufMFCC.process(s, b, numCoeffs:5, features: c).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the buffer: 5 coefs for left, then 5 coefs for right (the first of each is linked to the loudness)
|
||||
c.plot(separately:true)
|
||||
::
|
||||
@ -1,127 +0,0 @@
|
||||
TITLE:: FluidBufMelBands
|
||||
SUMMARY:: A Perceptually Spread Spectral Contour Descriptor on a Buffer
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading, Classes/FluidBufMFCC
|
||||
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements a spectral shape descriptor where the amplitude is given for a number of equally spread perceptual bands. The spread is based on the Mel scale (https://en.wikipedia.org/wiki/Mel_scale) which is one of the first attempt to mimic pitch perception scientifically. This implementation allows to select the range and number of bands dynamically. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The process will return a single multichannel buffer of STRONG::numBands:: per input channel. Each frame represents a value, which is every hopSize.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the spectral shape descriptors 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 described through the various descriptors. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
Where in the srcBuf should the process start, in sample.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
How many frames should be processed.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
ARGUMENT:: numChans
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
ARGUMENT:: features
|
||||
The destination buffer for the STRONG::numBands:: amplitudes describing the spectral shape.
|
||||
|
||||
ARGUMENT:: numBands
|
||||
The number of bands that will be perceptually equally distributed between STRONG::minFreq:: and STRONG::maxFreq::. It will decide how many channels are produce per channel of the source.
|
||||
|
||||
ARGUMENT:: minFreq
|
||||
The lower boundary of the lowest band of the model, in Hz.
|
||||
|
||||
ARGUMENT:: maxFreq
|
||||
The highest boundary of the highest band of the model, in Hz.
|
||||
|
||||
ARGUMENT:: normalize
|
||||
This flag enables the scaling of the output to preserve the energy of the window. It is on (1) by default.
|
||||
|
||||
ARGUMENT:: scale
|
||||
This flag sets the scaling of the output value. It is either linear (0, by default) or in dB (1).
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
The window size. As spectral description 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 hop size. As spectral description 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 windowSize (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 use the next power of 2 equal or above the windowSize.
|
||||
|
||||
ARGUMENT:: padding
|
||||
Controls the zero-padding added to either end of the source buffer or segment. Possible values are 0 (no padding), 1 (default, half the window size), or 2 (window size - hop size). Padding ensures that all input samples are completely analysed: with no padding, the first analysis window starts at time 0, and the samples at either end will be tapered by the STFT windowing function. Mode 1 has the effect of centering the first sample in the analysis window and ensuring that the very start and end of the segment are accounted for in the analysis. Mode 2 can be useful when the overlap factor (window size / hop size) is greater than 2, to ensure that the input samples at either end of the segment are covered by the same number of analysis frames as the rest of the analysed material.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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 [features] as an argument.
|
||||
|
||||
returns:: an instance of the processor
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// create some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
// run the process with basic parameters
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufMelBands.process(s, b, features: c, numBands:10).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// listen to the source and look at the buffer
|
||||
b.play;
|
||||
c.plot
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// load two very different files
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-SA-UprightPianoPedalWide.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
)
|
||||
|
||||
// composite one on left one on right as test signals
|
||||
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
|
||||
b.play
|
||||
|
||||
// create a buffer as destinations
|
||||
c = Buffer.new(s);
|
||||
|
||||
//run the process on them
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufMelBands.process(s, b, features: c, numBands:10).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the buffer: 10 bands for left, then 10 bands for right
|
||||
c.plot(separately:true)
|
||||
::
|
||||
@ -1,629 +0,0 @@
|
||||
TITLE:: FluidBufNMF
|
||||
SUMMARY:: Buffer-Based Non-Negative Matrix Factorisation on Spectral Frames
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Classes/FluidNMFMatch,Classes/FluidNMFFilter,Guides/FluidCorpusManipulationToolkit,Classes/FluidNMFMatch,Classes/FluidNMFFilter
|
||||
DESCRIPTION::
|
||||
|
||||
|
||||
Decomposes the spectrum of a sound into a number of components using Non-Negative Matrix Factorisation (NMF)
|
||||
|
||||
|
||||
|
||||
NMF has been a popular technique in signal processing research for things like source separation and transcription (see 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 components 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.
|
||||
|
||||
DEFINITIONLIST::
|
||||
## 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 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 audio reconstruction of each components in the time domain.
|
||||
|
||||
::
|
||||
::
|
||||
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 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.
|
||||
|
||||
DEFINITIONLIST::
|
||||
## If supplying pre-formed data, it's up to the user to make sure that the supplied buffers are the right size:
|
||||
||
|
||||
LIST::
|
||||
##
|
||||
bases must be frames and channels
|
||||
|
||||
##
|
||||
activations must be frames and channels
|
||||
|
||||
::
|
||||
::
|
||||
In this implementation, the components are reconstructed by masking the original spectrum, such that they will sum to yield the original sound.
|
||||
|
||||
The whole process can be related to a channel vocoder where, instead of fixed bandpass filters, we get more complex filter shapes that are learned from the data, and the activations correspond to channel envelopes.
|
||||
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
Processs the source LINK::Classes/Buffer:: on the LINK::Classes/Server::. CODE::processBlocking:: will execute directly in the server command FIFO, whereas CODE::process:: will delegate to a separate worker thread. The latter is generally only worthwhile for longer-running jobs where you don't wish to tie up the server.
|
||||
|
||||
ARGUMENT:: server
|
||||
The LINK::Classes/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 decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
|
||||
|
||||
Where in the srcBuf should the NMF process start, in sample.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
|
||||
|
||||
How many frames should be processed.
|
||||
|
||||
|
||||
ARGUMENT:: startChan
|
||||
|
||||
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numChans
|
||||
|
||||
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
|
||||
ARGUMENT:: resynth
|
||||
|
||||
|
||||
The index of the buffer where the different reconstructed components will be reconstructed. The buffer will be resized to channels and lenght. If is provided, the reconstruction will not happen.
|
||||
|
||||
|
||||
ARGUMENT:: bases
|
||||
|
||||
|
||||
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 is provided, no bases will be returned.
|
||||
|
||||
|
||||
ARGUMENT:: basesMode
|
||||
|
||||
|
||||
This flag decides of how the basis buffer passed as the previous argument is treated.
|
||||
|
||||
|
||||
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 is provided, no activation will be returned.
|
||||
|
||||
|
||||
ARGUMENT:: actMode
|
||||
|
||||
|
||||
This flag decides of how the activation buffer passed as the previous argument is treated.
|
||||
|
||||
|
||||
ARGUMENT:: components
|
||||
|
||||
|
||||
The number of elements the NMF algorithm will try to divide the spectrogram of the source in.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 1
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: iterations
|
||||
|
||||
|
||||
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.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 1
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
|
||||
|
||||
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. LINK::http://www.subsurfwiki.org/wiki/Gabor_uncertainty::
|
||||
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
|
||||
|
||||
The window hop size. As NMF relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default CODE::true::
|
||||
|
||||
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 CODE::[features]:: as an argument.
|
||||
|
||||
RETURNS:: An instance of the processor
|
||||
|
||||
METHOD:: kr
|
||||
Trigger the equivalent behaviour to CODE::processBlocking / process:: from a LINK::Classes/Synth::. Can be useful for expressing a sequence of buffer and data processing jobs to execute. Note that the work still executes on the server command FIFO (not the audio thread), and it is the caller's responsibility to manage the sequencing, using the CODE::done:: status of the various UGens.
|
||||
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.
|
||||
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
|
||||
|
||||
Where in the srcBuf should the NMF process start, in sample.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
|
||||
|
||||
How many frames should be processed.
|
||||
|
||||
|
||||
ARGUMENT:: startChan
|
||||
|
||||
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 0
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: numChans
|
||||
|
||||
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
|
||||
ARGUMENT:: resynth
|
||||
|
||||
|
||||
The index of the buffer where the different reconstructed components will be reconstructed. The buffer will be resized to channels and lenght. If is provided, the reconstruction will not happen.
|
||||
|
||||
|
||||
ARGUMENT:: bases
|
||||
|
||||
|
||||
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 is provided, no bases will be returned.
|
||||
|
||||
|
||||
ARGUMENT:: basesMode
|
||||
|
||||
|
||||
This flag decides of how the basis buffer passed as the previous argument is treated.
|
||||
|
||||
|
||||
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 is provided, no activation will be returned.
|
||||
|
||||
|
||||
ARGUMENT:: actMode
|
||||
|
||||
|
||||
This flag decides of how the activation buffer passed as the previous argument is treated.
|
||||
|
||||
|
||||
ARGUMENT:: components
|
||||
|
||||
|
||||
The number of elements the NMF algorithm will try to divide the spectrogram of the source in.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 1
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: iterations
|
||||
|
||||
|
||||
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.
|
||||
|
||||
STRONG::Constraints::
|
||||
|
||||
LIST::
|
||||
##
|
||||
Minimum: 1
|
||||
|
||||
::
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
|
||||
|
||||
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. LINK::http://www.subsurfwiki.org/wiki/Gabor_uncertainty::
|
||||
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
|
||||
|
||||
The window hop size. As NMF relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
ARGUMENT:: trig
|
||||
A CODE::kr:: signal that will trigger execution
|
||||
|
||||
ARGUMENT:: blocking
|
||||
Whether to execute this process directly on the server command FIFO or delegate to a worker thread. See CODE::processBlocking/process:: for caveats.
|
||||
|
||||
|
||||
INSTANCEMETHODS::
|
||||
METHOD:: kr
|
||||
Returns a UGen that reports the progress of the running task when executing in a worker thread. Calling code::scope:: with this can be used for a convinient progress monitor
|
||||
|
||||
METHOD:: cancel
|
||||
Cancels non-blocking processing
|
||||
|
||||
METHOD:: wait
|
||||
When called in the context of a LINK::Classes/Routine:: (it won't work otherwise), will block execution until the processor has finished. This can be convinient for writing sequences of processes more linearly than using lots of nested actions.
|
||||
|
||||
EXAMPLES::
|
||||
STRONG::A didactic example::
|
||||
CODE::
|
||||
|
||||
// =============== decompose some sounds ===============
|
||||
|
||||
// let's decompose the drum loop that comes with the FluCoMa extension:
|
||||
~drums = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav"));
|
||||
|
||||
// hear the original mono sound file to know what we're working with
|
||||
~drums.play;
|
||||
|
||||
// an empty buffer for the decomposed components to be written into:
|
||||
~resynth = Buffer(s);
|
||||
|
||||
// how many components we want FluidBufNMF to try to decompose the buffer into:
|
||||
~n_components = 2;
|
||||
|
||||
// process it:
|
||||
FluidBufNMF.processBlocking(s,~drums,resynth:~resynth,components:~n_components,action:{"done".postln;});
|
||||
|
||||
// once it is done, play the separated components one by one (with a second of silence in between)
|
||||
(
|
||||
fork{
|
||||
~n_components.do{
|
||||
arg i;
|
||||
"decomposed part #%".format(i+1).postln;
|
||||
{
|
||||
PlayBuf.ar(~n_components,~resynth,BufRateScale.ir(~resynth),doneAction:2)[i].dup;
|
||||
}.play;
|
||||
(~drums.duration + 1).wait;
|
||||
}
|
||||
};
|
||||
)
|
||||
|
||||
// ======== now let's try it with three components. =========
|
||||
// make a guess as to what you think you'll hear
|
||||
|
||||
~n_components = 3;
|
||||
// process it:
|
||||
FluidBufNMF.processBlocking(s,~drums,resynth:~resynth,components:~n_components,action:{"done".postln;});
|
||||
|
||||
(
|
||||
fork{
|
||||
~n_components.do{
|
||||
arg i;
|
||||
"decomposed part #%".format(i+1).postln;
|
||||
{
|
||||
PlayBuf.ar(~n_components,~resynth,BufRateScale.ir(~resynth),doneAction:2)[i].dup;
|
||||
}.play;
|
||||
(~drums.duration + 1).wait;
|
||||
}
|
||||
};
|
||||
)
|
||||
|
||||
// you may have guessed that it would separate out the three components into: (1) snare, (2) hihat, and (3) kick
|
||||
// and it might have worked! but it may not have, and it won't provide the same result every time because it
|
||||
// starts each process from a stochastic state (you can seed this state if you want...see below).
|
||||
|
||||
// ====== bases and activations ========
|
||||
|
||||
// first, let's make two new buffers called...
|
||||
~bases = Buffer(s);
|
||||
~activations = Buffer(s);
|
||||
~n_components = 2; // return to 2 components for this example
|
||||
|
||||
// and we'll explicitly pass these into the process
|
||||
FluidBufNMF.processBlocking(s,~drums,bases:~bases,activations:~activations,resynth:~resynth,components:~n_components,action:{"done".postln;});
|
||||
|
||||
// now we can plot them (because this process starts from a stochastic state, your results may vary!):
|
||||
FluidWaveform(~drums,featureBuffer:~activations,bounds:Rect(0,0,1200,300));
|
||||
// the bases are a like a spectral template that FluidBufNMF has found in the source buffer
|
||||
// in one you should see one spectrum that resembles a snare spectrum (the resonant tone of the snare
|
||||
// in the mid range) and another that resembles the kick + hihat we heard earlier (a large peak in the very
|
||||
// low register and some shimmery higher stuff)
|
||||
|
||||
FluidWaveform(featureBuffer:~bases,bounds:Rect(0,0,1200,300));
|
||||
// the activations are the corresponding loudness envelope of each base above. It should like an amplitude
|
||||
// envelope follower of the drum hits in the corresponding bases.
|
||||
|
||||
// FluidBufNMF then uses the individual bases with their corresponding activations to resynthesize the sound of just
|
||||
// component.
|
||||
// the buffer passed to `resynth` will have one channel for each component you've requested
|
||||
|
||||
~resynth.numChannels
|
||||
~resynth.play;
|
||||
|
||||
// ======== to further understand NMF's bases and activations, consider one more object: FluidNMFFilter ==========
|
||||
// FluidNMFFilter will use the bases (spectral templates) of a FluidBufNMF analysis to filter (i.e., decompose) real-time audio
|
||||
|
||||
// for example, if we use the bases from the ~drums analysis above, it will separate the snare from the kick & hi hat like before
|
||||
// this time you'll hear one in each stereo channel (again, results may vary)
|
||||
|
||||
(
|
||||
{
|
||||
var src = PlayBuf.ar(1,~drums,BufRateScale.ir(~drums),doneAction:2);
|
||||
var sig = FluidNMFFilter.ar(src,~bases,2);
|
||||
sig;
|
||||
}.play;
|
||||
)
|
||||
|
||||
// if we play a different source through FluidNMFFilter, it will try to decompose that real-time signal according to the bases
|
||||
// it is given (in our case the bases from the drum loop)
|
||||
~song = Buffer.readChannel(s,FluidFilesPath("Tremblay-BeatRemember.wav"),channels:[0]);
|
||||
|
||||
(
|
||||
{
|
||||
var src = PlayBuf.ar(1,~song,BufRateScale.ir(~song),doneAction:2);
|
||||
var sig = FluidNMFFilter.ar(src,~bases,2);
|
||||
sig;
|
||||
}.play;
|
||||
)
|
||||
|
||||
// ========= the activations could also be used as an envelope through time ===========
|
||||
(
|
||||
{
|
||||
var activation = PlayBuf.ar(2,~activations,BufRateScale.ir(~activations),doneAction:2);
|
||||
var sig = WhiteNoise.ar(0.dbamp) * activation;
|
||||
sig;
|
||||
}.play;
|
||||
)
|
||||
|
||||
// note that the samplerate of the ~activations buffer is not a usual one...
|
||||
~activations.sampleRate;
|
||||
// this is because each frame in this buffer doesn't correspond to one audio sample, but instead to one
|
||||
// hopSize, since these values are derived from an FFT analysis
|
||||
// so it is important to use BufRateScale (as seen above) in order to make sure they play back at the
|
||||
// correct rate
|
||||
|
||||
// if we control the amplitude of the white noise *and* send it through FluidNMFFilter, we'll get something
|
||||
// somewhat resembles both the spectral template and loudness envelope of the bases of the original
|
||||
// (of course it's also good to note that the combination of the *actual* bases and activations is how
|
||||
// FluidBufNMF creates the channels in the resynth buffer which will sound much better than this
|
||||
// filtered WhiteNoise version)
|
||||
(
|
||||
{
|
||||
var activation = PlayBuf.ar(2,~activations,BufRateScale.ir(~activations),doneAction:2);
|
||||
var sig = WhiteNoise.ar(0.dbamp);
|
||||
sig = FluidNMFFilter.ar(sig,~bases,2) * activation;
|
||||
sig;
|
||||
}.play;
|
||||
)
|
||||
|
||||
::
|
||||
STRONG::Fixed Bases: The process can be trained, and the learnt bases or activations can be used as templates.::
|
||||
|
||||
CODE::
|
||||
|
||||
//set some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
|
||||
~originalNMF = Buffer.new(s);
|
||||
~bases = Buffer.new(s);
|
||||
~trainedBases = Buffer.new(s);
|
||||
~activations = Buffer.new(s);
|
||||
~final = Buffer.new(s);
|
||||
~spectralshapes = Buffer.new(s);
|
||||
~stats = Buffer.new(s);
|
||||
~sortedNMF = Buffer.new(s);
|
||||
)
|
||||
|
||||
b.play
|
||||
|
||||
// train using the first 2 seconds of the sound file
|
||||
(
|
||||
Routine {
|
||||
FluidBufNMF.process(s,b,0,44100*5,0,1, ~originalNMF, ~bases, components:10).wait;
|
||||
~originalNMF.query;
|
||||
}.play;
|
||||
)
|
||||
|
||||
// listen to the 10 components across the stereo image
|
||||
{Splay.ar(PlayBuf.ar(10, ~originalNMF))}.play
|
||||
|
||||
// plot the bases
|
||||
~bases.plot
|
||||
|
||||
// find the component that has the picking sound by checking the median spectral centroid
|
||||
(
|
||||
FluidBufSpectralShape.process(s, ~originalNMF, features: ~spectralshapes, action:{
|
||||
|shapes|FluidBufStats.process(s,shapes,stats:~stats, action:{
|
||||
|stats|stats.getn(0, (stats.numChannels * stats.numFrames) ,{
|
||||
|x| ~centroids = x.select({
|
||||
|item, index| (index.mod(7) == 0) && (index.div(70) == 5);
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
)
|
||||
|
||||
//what is happening there? We run the spectralshapes on the buffer of 10 components from the nmf. See the structure of that buffer:
|
||||
~originalNMF.query
|
||||
//10 channel are therefore giving 70 channels: the 7 shapes of component0, then 7 shapes of compoenent1, etc
|
||||
~spectralshapes.query
|
||||
// we then run the bufstats on them. Each channel, which had a time series (an envelope) of each descriptor, is reduced to 7 frames
|
||||
~stats.query
|
||||
// we then need to retrieve the values that are where we want: the first of every 7 for the centroid, and the 6th frame of them as we want the median. Because we retrieve the values in an interleave format, the select function gets a bit tricky but we get the following values:
|
||||
~centroids.postln
|
||||
|
||||
// we then copy the basis with the highest median centroid to a channel, and all the other bases to the other channel, of a 2-channel bases for decomposition
|
||||
(
|
||||
z = (0..9);
|
||||
[z.removeAt(~centroids.maxIndex)].do{|chan|FluidBufCompose.process(s, ~bases, startChan: chan, numChans: 1, destination: ~trainedBases, destGain:1)};
|
||||
z.postln;
|
||||
z.do({|chan| FluidBufCompose.process(s, ~bases, startChan:chan, numChans: 1, destStartChan: 1, destination: ~trainedBases, destGain:1)});
|
||||
)
|
||||
~trainedBases.plot
|
||||
|
||||
//process the whole file, splitting it with the 2 trained bases
|
||||
(
|
||||
Routine{
|
||||
FluidBufNMF.process(s, b, resynth: ~sortedNMF, bases: ~trainedBases, basesMode: 2, components:2).wait;
|
||||
~originalNMF.query;
|
||||
}.play;
|
||||
)
|
||||
|
||||
// play the result: pick on the left, sustain on the right!
|
||||
{PlayBuf.ar(2,~sortedNMF)}.play
|
||||
|
||||
// it even null-sums
|
||||
{(PlayBuf.ar(2,~sortedNMF,doneAction:2).sum)-(PlayBuf.ar(1,b,doneAction:2))}.play
|
||||
::
|
||||
|
||||
STRONG::Updating Bases: The process can update bases provided as seed.::
|
||||
|
||||
CODE::
|
||||
(
|
||||
// create buffers
|
||||
b = Buffer.alloc(s,44100);
|
||||
c = Buffer.alloc(s, 44100);
|
||||
d = Buffer.new(s);
|
||||
e = Buffer.alloc(s,513,3);
|
||||
f = Buffer.new(s);
|
||||
g = Buffer.new(s);
|
||||
)
|
||||
|
||||
(
|
||||
// fill them with 2 clearly segregated sine waves and composite a buffer where they are consecutive
|
||||
Routine {
|
||||
b.sine2([500],[1], false, false);
|
||||
c.sine2([5000],[1],false, false);
|
||||
s.sync;
|
||||
FluidBufCompose.process(s,b, destination:d);
|
||||
FluidBufCompose.process(s,c, destStartFrame:44100, destination:d, destGain:1);
|
||||
s.sync;
|
||||
d.query;
|
||||
}.play;
|
||||
)
|
||||
|
||||
// check
|
||||
d.plot
|
||||
d.play //////(beware !!!! loud!!!)
|
||||
|
||||
(
|
||||
//make a seeding basis of 3 components:
|
||||
var highpass, lowpass, direct;
|
||||
highpass = Array.fill(513,{|i| (i < 50).asInteger});
|
||||
lowpass = 1 - highpass;
|
||||
direct = Array.fill(513,0.1);
|
||||
e.setn(0,[highpass, lowpass, direct].flop.flat);
|
||||
)
|
||||
|
||||
//check the basis: a steep lowpass, a steep highpass, and a small DC
|
||||
e.plot
|
||||
e.query
|
||||
|
||||
(
|
||||
// use the seeding basis, without updating
|
||||
Routine {
|
||||
FluidBufNMF.process(s, d, resynth:f, bases: e, basesMode: 2, activations:g, components:3).wait;
|
||||
e.query;
|
||||
f.query;
|
||||
g.query;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the resynthesised separated signal
|
||||
f.plot;
|
||||
|
||||
// look at the bases that have not changed
|
||||
e.plot;
|
||||
|
||||
// look at the activations
|
||||
g.plot;
|
||||
|
||||
(
|
||||
// use the seeding bases, with updating this time
|
||||
Routine {
|
||||
FluidBufNMF.process(s, d, resynth:f, bases: e, basesMode: 1, activations:g, components:3).wait;
|
||||
e.query;
|
||||
f.query;
|
||||
g.query;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the resynthesised separated signal
|
||||
f.plot;
|
||||
|
||||
// look at the bases that have now updated in place (with the 3rd channel being more focused
|
||||
e.plot;
|
||||
|
||||
// look at the activations (sharper 3rd component at transitions)
|
||||
g.plot;
|
||||
::
|
||||
@ -1,100 +0,0 @@
|
||||
TITLE:: FluidBufNMFSeed
|
||||
summary:: Non-Negative Double Singular Value Decomposition on a Buffer
|
||||
categories:: Libraries>FluidCorpusManipulation
|
||||
related:: Classes/FluidBufNMF
|
||||
|
||||
DESCRIPTION::
|
||||
Find Initial Bases and Activations for FluidBufNMF via Non-Negative Double Singular Value Decomposition .
|
||||
|
||||
See http://nimfa.biolab.si/nimfa.methods.seeding.nndsvd.html
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the decomposition 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 decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
ARGUMENT:: bases
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
ARGUMENT:: minComponents
|
||||
Minimum number of estimated components
|
||||
|
||||
ARGUMENT:: maxComponents
|
||||
Maximum number of estimated components
|
||||
|
||||
ARGUMENT:: coverage
|
||||
Fraction (0 to 1) of information preserved in the decomposition
|
||||
|
||||
ARGUMENT:: method
|
||||
The method used for the decomposition. Options are:
|
||||
|
||||
table::
|
||||
## 0 || NMF-SVD || faster
|
||||
## 1 || NNDSVDar || more accurate, fill in the zero elements with random values
|
||||
## 2 || NNDSVDa || fill in the zero elements with the average
|
||||
## 3 || NNDSVD || leave zero elements as zero, works better for sparse spectrograms
|
||||
::
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
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 hop size. As sinusoidal estimation 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 windowSize (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 use the next power of 2 equal or above the highest of windowSize and (bandwidth - 1) * 2.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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 [destination] as an argument.
|
||||
|
||||
returns:: an instance of the processor
|
||||
|
||||
|
||||
INSTANCEMETHODS::
|
||||
|
||||
private:: synth, server
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav"));
|
||||
~bases = Buffer.new(s);
|
||||
~activations = Buffer.new(s);
|
||||
~resynth = Buffer.new(s);
|
||||
)
|
||||
|
||||
//how many bases do I need to decompose the buffer with 90% accuracy
|
||||
(
|
||||
Routine{
|
||||
FluidBufNMFSeed.process(s, b, ~bases, ~activations, coverage: 0.9, method: 1).wait;
|
||||
"% bases".format(~bases.numChannels).postln;
|
||||
}.play;
|
||||
)
|
||||
//check how many bases we are returned:
|
||||
|
||||
|
||||
//try the same process with less accuracy
|
||||
(
|
||||
Routine{
|
||||
FluidBufNMFSeed.process(s, b, ~bases, ~activations, coverage: 0.5).wait;
|
||||
"% bases".format(~bases.numChannels).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
//use the bases to run NMF on
|
||||
FluidBufNMF.process(s, b, resynth: ~resynth, bases: ~bases, activations: ~activations,actMode: 2, components: ~bases.numChannels, action: {\done.postln;})
|
||||
{PlayBuf.ar(~resynth.numChannels, ~resynth)[2]}.play
|
||||
::
|
||||
@ -1,179 +0,0 @@
|
||||
TITLE:: FluidBufNoveltySlice
|
||||
SUMMARY:: Buffer-Based Novelty-Based Slicer
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation, UGens>Buffer
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements a non-real-time slicer using an algorithm assessing novelty in the signal to estimate the slicing points. A novelty curve is being derived from running a kernel across the diagonal of the similarity matrix, and looking for peak of changes. It implements the seminal results published in 'Automatic Audio Segmentation Using a Measure of Audio Novelty' by J Foote. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The process will return a buffer which contains indices (in sample) of estimated starting points of different slices.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
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 srcBuf, which channel should be processed.
|
||||
|
||||
ARGUMENT:: numChans
|
||||
For multichannel srcBuf, 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:: feature
|
||||
The feature on which novelty is computed.
|
||||
table::
|
||||
##0 || Spectrum || The magnitude of the full spectrum.
|
||||
##1 || MFCC || 13 Mel-Frequency Cepstrum Coefficients.
|
||||
##2 || Chroma || The contour of a 12 band Chromagram.
|
||||
##3 || Pitch || The pitch and its confidence.
|
||||
##4 || Loudness || The TruePeak and Loudness.
|
||||
::
|
||||
ARGUMENT:: kernelSize
|
||||
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:: threshold
|
||||
The normalised threshold, between 0 an 1, on the novelty curve to consider it a segmentation point.
|
||||
|
||||
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:: minSliceLength
|
||||
The minimum duration of a slice in number of hopSize.
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
The window size. As novelty 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
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
The window hop size. As novelty estimation 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 windowSize (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 use the next power of 2 equal or above the windowSize.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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:: an instance of the processor
|
||||
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// load some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
(
|
||||
// with basic params, with a minimum slight length to avoid over
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufNoveltySlice.process(s,b, indices: c, threshold:0.4,filterSize: 4, minSliceLength: 8).wait;
|
||||
(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,
|
||||
Phasor.ar(0,1,
|
||||
BufRd.kr(1, c,
|
||||
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1),
|
||||
BufRd.kr(1, c,
|
||||
MouseX.kr(1, BufFrames.kr(c)), 0, 1),
|
||||
BufRd.kr(1,c,
|
||||
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1)), 0, 1);
|
||||
}.play;
|
||||
)
|
||||
::
|
||||
|
||||
STRONG::Examples of the impact of the filterSize::
|
||||
|
||||
CODE::
|
||||
// load some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
// process with a given filterSize
|
||||
(
|
||||
Routine{
|
||||
FluidBufNoveltySlice.process(s,b, indices: c, kernelSize:31, threshold:0.1, filterSize:1).wait;
|
||||
//check the number of slices: it is the number of frames in the transBuf minus the boundary index.
|
||||
c.query;
|
||||
}.play;
|
||||
)
|
||||
//play slice number 3
|
||||
(
|
||||
{
|
||||
BufRd.ar(1, b,
|
||||
Line.ar(
|
||||
BufRd.kr(1, c, DC.kr(3), 0, 1),
|
||||
BufRd.kr(1, c, DC.kr(4), 0, 1),
|
||||
(BufRd.kr(1, c, DC.kr(4)) - BufRd.kr(1, c, DC.kr(3), 0, 1) + 1) / s.sampleRate),
|
||||
0,1);
|
||||
}.play;
|
||||
)
|
||||
|
||||
// change the filterSize in the code above to 4. Then to 12. Listen in between to the differences.
|
||||
|
||||
// What's happening? In the first instance (filterSize = 1), the novelty line is jittery and therefore overtriggers on the arpegiated guitar. We also can hear attacks at the end of the segment. Setting the threshold higher (like in the 'Basic Example' pane) misses some more subtle variations.
|
||||
|
||||
// So in the second settings (filterSize = 4), we smooth the novelty line a little, which allows us to catch small differences that are not jittery. It also corrects the ending cutting by the same trick: the averaging of the sharp pick is sliding up, crossing the threshold slightly earlier.
|
||||
|
||||
// If we smooth too much, like the third settings (filterSize = 12), we start to loose precision and miss attacks. Have fun with different values of theshold then will allow you to find the perfect segment for your signal.
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// make a stereo buffer
|
||||
b = Buffer.alloc(s,88200,2);
|
||||
|
||||
// add some stereo clicks and listen to them
|
||||
((0..3)*22050+11025).do({|item,index| b.set(item+(index%2), 1.0)});
|
||||
b.play
|
||||
|
||||
// create a new buffer as destinations
|
||||
c = Buffer.new(s);
|
||||
|
||||
//run the process on them
|
||||
(
|
||||
// with basic params
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufNoveltySlice.process(s,b, indices: c, threshold:0.3).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// list the indicies of detected attacks - the two input channels have been summed
|
||||
c.getn(0,c.numFrames,{|item|(item * 2).postln;})
|
||||
::
|
||||
@ -1,146 +0,0 @@
|
||||
TITLE:: FluidBufOnsetSlice
|
||||
SUMMARY:: Spectral Difference-Based Audio Buffer Slicer
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements many spectral-based onset detection metrics, 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 LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The process will return a buffer which contains indices (in samples) of estimated starting points of different slices. If a startFrame argument is supplied, the starting point indices are still specified in terms of their location in the original buffer; they are not offset by the number startFrames.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
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:: metric
|
||||
The metric 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 metric, from 0 upwards.
|
||||
|
||||
ARGUMENT:: minSliceLength
|
||||
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 metrics (HFC, SpectralFlux, MKL, Cosine), the distance does not have to be computed between consecutive frames. By default (0) it is, otherwise this sets the distance between the comparison window in samples.
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
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 hop 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 windowSize (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 use the next power of 2 equal or above the windowSize.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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:: an instance of the processor
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
CODE::
|
||||
(
|
||||
//prep some buffers
|
||||
b = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
(
|
||||
// with basic params
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufOnsetSlice.process(s,b, indices: c, threshold:0.5).wait;
|
||||
(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,
|
||||
Phasor.ar(0,1,
|
||||
BufRd.kr(1, c,
|
||||
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1),
|
||||
BufRd.kr(1, c,
|
||||
MouseX.kr(1, BufFrames.kr(c)), 0, 1),
|
||||
BufRd.kr(1,c,
|
||||
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1)), 0, 1);
|
||||
}.play;
|
||||
)
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// make a stereo buffer
|
||||
b = Buffer.alloc(s,88200,2);
|
||||
|
||||
// add some stereo clicks and listen to them
|
||||
((0..3)*22050+11025).do({|item,index| b.set(item+(index%2), 1.0)})
|
||||
b.play
|
||||
|
||||
// create a new buffer as destinations
|
||||
c = Buffer.new(s);
|
||||
|
||||
//run the process on them
|
||||
(
|
||||
// with basic params
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufOnsetSlice.process(s,b, indices: c, threshold:0.00001).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// list the indicies of detected attacks - the two input channels have been summed
|
||||
c.getn(0,c.numFrames,{|item|(item * 2).postln;})
|
||||
::
|
||||
@ -1,193 +0,0 @@
|
||||
TITLE:: FluidBufPitch
|
||||
SUMMARY:: A Selection of Pitch Descriptors on a Buffer
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading, Classes/SpecCentroid, Classes/SpecFlatness, Classes/SpecCentroid, Classes/SpecPcile
|
||||
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements three popular pitch descriptors, computed as frequency and the confidence in its value. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The process will return a multichannel buffer with two channels per input channel, one for pitch and one for the pitch tracking confidence. A pitch of 0 Hz is yield (or -999.0 when the unit is in MIDI note) when the algorithm cannot find a fundamental at all. Each sample represents a value, which is every hopSize. Its sampling rate is sourceSR / hopSize.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the pitch descriptor 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 pitch-tracked. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
Where in the srcBuf should the process start, in sample.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
How many frames should be processed.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
ARGUMENT:: numChans
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
ARGUMENT:: features
|
||||
The destination buffer for the pitch descriptors.
|
||||
|
||||
ARGUMENT:: algorithm
|
||||
The algorithm to estimate the pitch. The options are:
|
||||
TABLE::
|
||||
## 0 || Cepstrum: Returns a pitch estimate as the location of the second highest peak in the Cepstrum of the signal (after DC).
|
||||
## 1 || Harmonic Product Spectrum: Implements the Harmonic Product Spectrum algorithm for pitch detection . See e.g. FOOTNOTE:: A. Lerch, "An Introduction to Audio Content Analysis: Applications in Signal Processing and Music Informatics." John Wiley & Sons, 2012.https://onlinelibrary.wiley.com/doi/book/10.1002/9781118393550 ::
|
||||
## 2 || YinFFT: Implements the frequency domain version of the YIN algorithm, as described in FOOTNOTE::P. M. Brossier, "Automatic Annotation of Musical Audio for Interactive Applications." QMUL, London, UK, 2007. :: See also https://essentia.upf.edu/documentation/reference/streaming_PitchYinFFT.html
|
||||
::
|
||||
|
||||
ARGUMENT:: minFreq
|
||||
The minimum frequency that the algorithm will search for an estimated fundamental. This sets the lowest value that will be generated.
|
||||
|
||||
ARGUMENT:: maxFreq
|
||||
The maximum frequency that the algorithm will search for an estimated fundamental. This sets the highest value that will be generated.
|
||||
|
||||
ARGUMENT:: unit
|
||||
The unit of the estimated value. The default of 0 is in Hz. A value of 1 will convert to MIDI note values.
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
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
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
The window hop size. As sinusoidal estimation 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 windowSize (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 use the next power of 2 equal or above the windowSize.
|
||||
|
||||
ARGUMENT:: padding
|
||||
Controls the zero-padding added to either end of the source buffer or segment. Possible values are 0 (no padding), 1 (default, half the window size), or 2 (window size - hop size). Padding ensures that all input samples are completely analysed: with no padding, the first analysis window starts at time 0, and the samples at either end will be tapered by the STFT windowing function. Mode 1 has the effect of centering the first sample in the analysis window and ensuring that the very start and end of the segment are accounted for in the analysis. Mode 2 can be useful when the overlap factor (window size / hop size) is greater than 2, to ensure that the input samples at either end of the segment are covered by the same number of analysis frames as the rest of the analysed material.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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 [features] as an argument.
|
||||
|
||||
returns:: an instance of the processor
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// create a buffer with a short clicking sinusoidal burst (220Hz) starting at frame 8192 for 1024 frames
|
||||
(
|
||||
b = Buffer.sendCollection(s, (Array.fill(8192,{0}) ++ (Signal.sineFill(1203,[0,0,0,0,0,1],[0,0,0,0,0,0.5pi]).takeThese({|x,i|i>1023})) ++ Array.fill(8192,{0})));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
// listen to the source and look at the buffer
|
||||
b.play; b.plot;
|
||||
|
||||
// run the process with basic parameters
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufPitch.process(s, b, features: c).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the analysis
|
||||
c.plot(separately:true)
|
||||
|
||||
// The values are interleaved [pitch,confidence] in the buffer as they are on 2 channels: to get to the right frame, divide the SR of the input by the hopSize, then multiply by 2 because of the channel interleaving
|
||||
// here we are querying from one frame before (the signal starts at 8192, which is frame 16 (8192/512), therefore starting the query at frame 15, which is index 30.
|
||||
c.getn(30,10,{|x|x.postln})
|
||||
|
||||
// observe that the first frame is silent, as expected. The next frame's confidence is low-ish, because the window is half full (window of 1024, overlap of 512). Then a full window is analysed, with strong confidence. Then another half full window, then silence, as expected.
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// load two very different files
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-SA-UprightPianoPedalWide.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
)
|
||||
|
||||
// composite one on left one on right as test signals
|
||||
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
|
||||
b.play
|
||||
|
||||
// create a buffer as destinations
|
||||
c = Buffer.new(s);
|
||||
|
||||
//run the process on them, with limited bandwidth and units in MIDI notes
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufPitch.process(s, b, features: c, minFreq:200, maxFreq:2000, unit:1).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the buffer: [pitch,confidence] for left then [pitch,confidence] for right
|
||||
c.plot(separately:true)
|
||||
::
|
||||
|
||||
STRONG::A musical example.::
|
||||
code::
|
||||
// create some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-ASWINE-ScratchySynth-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
// run the process with basic parameters and retrieve the array in the langage side
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufPitch.process(s, b, features: c,action:{c.loadToFloatArray(action: {|x| d = x.reshape((x.size()/2).asInteger, 2)})}).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
//look at the retrieved formatted array of [pitch,confidence] values
|
||||
d.postln
|
||||
|
||||
//iterate and make an array of the indices which are fitting the conditions
|
||||
(
|
||||
e = Array.new;
|
||||
d.do({
|
||||
arg val, i;
|
||||
if ((val[0] > 500) && (val[1] > 0.98)) {e = e.add(i)}; // if pitch is greater than 500Hz and confidence higher than 0.98, keep the index
|
||||
});
|
||||
)
|
||||
e.postln;
|
||||
|
||||
//granulate only the frames that are in our buffer
|
||||
// We need to convert our indices to frame start. Their position was (index * hopSize) - (hopSize) in FluidBufPitch
|
||||
f = e.collect({arg i; (i * 512) - 512});
|
||||
|
||||
// define a basic grain synth
|
||||
(
|
||||
SynthDef(\grain,
|
||||
{ arg out=0, buf =0 , ind = 0, pan = 0;
|
||||
var env;
|
||||
env = EnvGen.kr(Env.new([0,1,0],[512/s.sampleRate].dup,\sine), doneAction: Done.freeSelf);
|
||||
Out.ar(out, Pan2.ar(PlayBuf.ar(1,buf,startPos:ind),pan));
|
||||
}).add;
|
||||
)
|
||||
|
||||
// start the sequence
|
||||
(
|
||||
a = Pxrand(f, inf).asStream;
|
||||
Routine({
|
||||
loop({
|
||||
Synth(\grain, [\buf, b, \ind, a.next, \pan, (2.0.rand - 1)]);
|
||||
(256/s.sampleRate).wait;
|
||||
})
|
||||
}).play;
|
||||
)
|
||||
::
|
||||
@ -1,109 +0,0 @@
|
||||
TITLE:: FluidBufSTFT
|
||||
summary:: Perform a Short-Time Fourier Transform on one channel of a buffer
|
||||
categories:: Libraries>FluidCorpusManipulation
|
||||
related:: Classes/Buffer
|
||||
|
||||
DESCRIPTION::
|
||||
Performs either a forward or inverse Short-Time Fourier Transform (STFT) on a single channel source buffer~. In the forward case, resulting magnitudes and phases can be written to output buffers. In the inverse case, these buffers can be used to reconstruct the original source into a new buffer.
|
||||
|
||||
The magntude and phase buffers are laid out as (number of hops, number of bins). The number of hops is a function of the source length and the hop size. The number of bins is (1 + (fft size / 2)).
|
||||
|
||||
The object is restricted to analysing a single source channel, because the channel counts of the magntude and phase buffers would quickly get out of hand otherwise.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
private::new1
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
Run the process on the given sever, and perfrom code::action:: when done
|
||||
|
||||
ARGUMENT:: server
|
||||
The link::Classes/Server:: on which to run
|
||||
|
||||
ARGUMENT:: source
|
||||
The link::Classes/Buffer:: to use for the forward STFT
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
The starting point for analysis in the source (in samples)
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
The duration (in samples) to analyse
|
||||
|
||||
ARGUMENT:: startChan
|
||||
The channel to analyse
|
||||
|
||||
ARGUMENT:: magnitude
|
||||
The link::Classes/Buffer:: to write magnitudes to in the forward case, or read from in the inverse case. This is optional for the forward transform, mandatory for the inverse.
|
||||
|
||||
ARGUMENT:: phase
|
||||
The link::Classes/Buffer:: to write phases to in the forward case, or read from in the inverse case. This is optional for the forward transform, mandatory for the inverse.
|
||||
|
||||
ARGUMENT:: resynth
|
||||
The link::Classes/Buffer:: to write re-synthesised data to in the inverse case. Ignored for the forward transform. Mandatory in the inverse case.
|
||||
|
||||
ARGUMENT:: inverse
|
||||
When set to 1, an inverse STFT is performed, and the resynthesised data is written to the resynthesis buffer using overlap-add.
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
The number of source samples that are analysed at once.
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
How many samples there are in-between analysis windows. The -1 default value will default to half of windowSize (overlap of 2).
|
||||
|
||||
ARGUMENT:: fftSize
|
||||
The 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 use the next power of 2 equal or above the windowSize. For this object it is effectively capped at 65536.
|
||||
|
||||
ARGUMENT:: padding
|
||||
Controls the zero-padding added to either end of the source buffer or segment. Possible values are 0 (no padding), 1 (default, half the window size), or 2 (window size - hop size). Padding ensures that all input samples are completely analysed: with no padding, the first analysis window starts at time 0, and the samples at either end will be tapered by the STFT windowing function. Mode 1 has the effect of centering the first sample in the analysis window and ensuring that the very start and end of the segment are accounted for in the analysis. Mode 2 can be useful when the overlap factor (window size / hop size) is greater than 2, to ensure that the input samples at either end of the segment are covered by the same number of analysis frames as the rest of the analysed material.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
ARGUMENT:: action
|
||||
Runs when processing is complete
|
||||
|
||||
INSTANCEMETHODS::
|
||||
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
s.reboot
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav"));
|
||||
m = Buffer.new;
|
||||
p = Buffer.new;
|
||||
r = Buffer.new;
|
||||
)
|
||||
|
||||
(
|
||||
fork{
|
||||
FluidBufSTFT.process(s,source:b,magnitude:m,phase:p).wait;
|
||||
FluidBufSTFT.process(s,magnitude:m,phase:p,resynth:r,inverse:1).wait;
|
||||
"Done".postln;
|
||||
}
|
||||
)
|
||||
|
||||
{ PlayBuf.ar(1,r); }.play
|
||||
|
||||
//nullsum
|
||||
{ PlayBuf.ar(1,r) - PlayBuf(1,b); }.play
|
||||
|
||||
//draw the magnitudes as a greyscale spectrogram
|
||||
// make the image
|
||||
i = Image.new(m.numFrames, m.numChannels)
|
||||
|
||||
//retreive the image and assign to pixels
|
||||
(
|
||||
m.loadToFloatArray(action: {|x|
|
||||
var mod = m.numChannels;
|
||||
{
|
||||
x.do{
|
||||
|val, index|
|
||||
i.setColor(Color.gray(val), index.div(mod), mod - 1 - index.mod(mod));
|
||||
};
|
||||
i.plot("spectrogram", showInfo: false);
|
||||
}.fork(AppClock)
|
||||
});
|
||||
)
|
||||
::
|
||||
@ -1,112 +0,0 @@
|
||||
TITLE:: FluidBufScale
|
||||
SUMMARY:: A Scaling Processor for Buffers
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading
|
||||
|
||||
This class implements a simple Buffer preprocessor, by scaling its values. It draws a simple translation from inputLow to outputLow, and from inputHigh to outputHigh. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The process will return a buffer with the same size and shape than the requested range.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the scaling to be calculated on a given source buffer.
|
||||
|
||||
ARGUMENT:: server
|
||||
The server on which the buffer to be processed is allocated.
|
||||
|
||||
ARGUMENT:: source
|
||||
The index of the buffer to use as the source material to be processed.
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
The starting point (in samples) from which to copy in the source buffer.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
The duration (in samples) to copy from the source buffer. The default (-1) copies the full lenght of the buffer.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
The first channel from which to copy in the source buffer.
|
||||
|
||||
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.
|
||||
|
||||
ARGUMENT:: destination
|
||||
The index of the buffer to use as the destination for the processed material.
|
||||
|
||||
ARGUMENT:: inputLow
|
||||
The low reference point of the input. it will be scaled to yield outputLow at the output
|
||||
|
||||
ARGUMENT:: inputHigh
|
||||
The high reference point of the input. it will be scaled to yield outputHigh at the output
|
||||
|
||||
ARGUMENT:: outputLow
|
||||
The output value when the input is inputLow
|
||||
|
||||
ARGUMENT:: outputHigh
|
||||
The output value when the input is inputHigh
|
||||
|
||||
ARGUMENT:: clipping
|
||||
Optional clipping of the input (and therefore of the output). 0 is none. 1 caps the lowest input at inputLow. 2 caps the highest input at inputHigh, 3 caps both input low and high value within the described range.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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 metric will be passed indices as an argument.
|
||||
|
||||
returns:: an instance of the processor
|
||||
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
(
|
||||
Routine{
|
||||
// make a buffer of known qualities
|
||||
b = Buffer.sendCollection(s,1.0.series(1.1,2.0));
|
||||
// and a destination buffer
|
||||
c = Buffer(s);
|
||||
// play with the scaling
|
||||
FluidBufScale.process(s, b, destination: c, inputLow: 0, inputHigh: 1, outputLow: 20, outputHigh:10).wait;
|
||||
// retrieve the buffer and enjoy the results.
|
||||
c.getn(0,10,{|x|x.round(0.000001).postln;})
|
||||
}.play
|
||||
)
|
||||
|
||||
// also works in multichannel - explore the following buffer
|
||||
|
||||
//process
|
||||
(
|
||||
Routine{
|
||||
b = Buffer.sendCollection(s,-10.0.series(-9,10.0).scramble,2);
|
||||
c = Buffer(s);
|
||||
s.sync;
|
||||
defer{b.plot(bounds:Rect(400,400,400,400)).plotMode_(\points).bounds};
|
||||
FluidBufScale.process(s, b, destination: c, inputLow: -20, inputHigh: 20, outputLow: 0, outputHigh:1).wait;
|
||||
//enjoy - same shape, different range
|
||||
defer{c.plot(bounds:Rect(800,400,400,400)).plotMode_(\points)};
|
||||
}.play;
|
||||
)
|
||||
|
||||
//also works with a subset of the input, resizing the output
|
||||
(
|
||||
Routine{
|
||||
b = Buffer.sendCollection(s,0.0.series(0.1,3.0).reshape(3,10).flop.flat,3);
|
||||
c = Buffer(s);
|
||||
s.sync;
|
||||
defer{b.plot(separately: true,bounds:Rect(400,400,400,400)).plotMode_(\points)};
|
||||
//process
|
||||
FluidBufScale.process(s, b, startFrame: 3,numFrames: 4,startChan: 1,numChans: 1, destination: c, inputLow: 0, inputHigh: 3, outputLow: 0, outputHigh:1).wait;
|
||||
//enjoy
|
||||
c.query;
|
||||
c.getn(0,4,{|x|x.postln;});
|
||||
defer{c.plot(separately: true,bounds:Rect(800,400,400,400)).plotMode_(\points)};
|
||||
}.play
|
||||
)
|
||||
::
|
||||
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
TITLE:: FluidBufSelect
|
||||
summary:: Cherry pick values from a buffer
|
||||
categories:: Libraries>FluidCorpusManipulation
|
||||
related:: Classes/FluidBufSelectEvery
|
||||
|
||||
DESCRIPTION::
|
||||
Pick sets of values from a buffer, described in terms of a list of frame indices and channel numbers.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
private::new1
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
Run the process on the given sever, and perfrom code::action:: when done
|
||||
|
||||
ARGUMENT:: server
|
||||
The link::Classes/Server:: on which to run
|
||||
|
||||
ARGUMENT:: source
|
||||
The link::Classes/Buffer:: to select values from
|
||||
|
||||
ARGUMENT:: destination
|
||||
The link::Classes/Buffer:: to write the selected data to
|
||||
|
||||
ARGUMENT:: indices
|
||||
A 0-based list of frame indices to recover. Default is [-1], meaning all frames
|
||||
|
||||
ARGUMENT:: channels
|
||||
A 0-based list of channel numbers to recover. Default is [-1], meaning all frames
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
ARGUMENT:: action
|
||||
Runs when processing is complete
|
||||
|
||||
EXAMPLES::
|
||||
code::
|
||||
//send a known collection where the value of each frame in each channel is encoded
|
||||
//chan
|
||||
b = Buffer.sendCollection(s,30.collect{|x| x.mod(6) + (x.div(6) * 0.1)},6)
|
||||
//check the ranges (thus showing a plotter error...)
|
||||
b.plot(separately: true).plotMode_(\points)
|
||||
//you can also check the collection itself if in doubt
|
||||
b.getToFloatArray(action: {|x|x.round(0.1).postln;});
|
||||
|
||||
//let's make a destination buffer
|
||||
c = Buffer(s);
|
||||
|
||||
//using default values, we copy everything:
|
||||
FluidBufSelect.process(s,b,c,action: {c.query});
|
||||
c.getToFloatArray(action: {|x|x.round(0.1).postln;});
|
||||
|
||||
//more powerful copying, resizing the destination accordingly
|
||||
FluidBufSelect.process(s,b,c, indices: [1,3], channels: [2,4], action: {c.query});
|
||||
c.getToFloatArray(action: {|x|x.round(0.1).postln;});
|
||||
|
||||
//observe the order can be anything, and -1 (default) passes everything in that dimension
|
||||
FluidBufSelect.process(s,b,c, indices: [ -1 ] , channels: [3, 1, 4], action: {c.query});
|
||||
c.getToFloatArray(action: {|x|x.round(0.1).postln;});
|
||||
::
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
TITLE:: FluidBufSelectEvery
|
||||
summary:: Extract every N samples / channels from a buffer
|
||||
categories:: Libraries>FluidCorpusManipulation
|
||||
related:: Classes/FluidBufSelect
|
||||
|
||||
DESCRIPTION::
|
||||
Pick every N frames and / or channels from a buffer, described in terms of independent hop sizes for frames and channels
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
private::new1
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
Run the process on the given sever, and perfrom code::action:: when done
|
||||
|
||||
ARGUMENT:: server
|
||||
The link::Classes/Server:: on which to run
|
||||
|
||||
ARGUMENT:: source
|
||||
The link::Classes/Buffer:: to select values from
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
The starting point (in samples) from which to copy in the source buffer.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
The duration (in samples) to copy from the source buffer. The default (-1) copies the full lenght of the buffer.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
The first channel from which to copy in the source buffer.
|
||||
|
||||
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.
|
||||
|
||||
ARGUMENT:: destination
|
||||
The link::Classes/Buffer:: to write the selected data to
|
||||
|
||||
ARGUMENT:: frameHop
|
||||
Take every `frameHop` frames. Default = 1 = all frames (where 2 would be every other frame, etc.)
|
||||
|
||||
ARGUMENT:: chanHop
|
||||
Take every `chanHop` channels. Default = 1 = all channels (where 2 would be every other channel, etc.)
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
ARGUMENT:: action
|
||||
Runs when processing is complete
|
||||
|
||||
EXAMPLES::
|
||||
Didactic
|
||||
code::
|
||||
//send a known collection where the value of each frame in each channel is encoded
|
||||
//chan
|
||||
b = Buffer.sendCollection(s,30.collect{|x| x.mod(6) + (x.div(6) * 0.1)},6)
|
||||
//check the ranges (thus showing a plotter error...)
|
||||
b.plot(separately: true).plotMode_(\points)
|
||||
//you can also check the collection itself if in doubt
|
||||
b.getToFloatArray(action: {|x|x.round(0.1).postln;});
|
||||
|
||||
//let's make a destination buffer
|
||||
c = Buffer(s);
|
||||
|
||||
//using default values, we copy everything:
|
||||
FluidBufSelectEvery.process(s,b, destination: c, action: {c.query});
|
||||
c.getToFloatArray(action: {|x|x.round(0.1).postln;});
|
||||
|
||||
//more powerful copying, resizing the destination accordingly
|
||||
FluidBufSelectEvery.process(s,b, destination: c, frameHop: 2, chanHop: 3, action: {c.query});
|
||||
c.getToFloatArray(action: {|x|x.round(0.1).postln;});
|
||||
|
||||
//source buffer boundaries still apply before the hopping selection
|
||||
FluidBufSelectEvery.process(s,b, startFrame: 1, numFrames: 3, startChan: 2, numChans: 3, destination: c, frameHop: 1, chanHop: 2, action: {c.query});
|
||||
c.getToFloatArray(action: {|x|x.round(0.1).postln;});
|
||||
::
|
||||
@ -1,148 +0,0 @@
|
||||
TITLE:: FluidBufSines
|
||||
SUMMARY:: Buffer-Based Sinusoidal Modelling and Resynthesis
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation, UGens>Buffer
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading, Guides/FluidBufMultiThreading
|
||||
|
||||
|
||||
DESCRIPTION::
|
||||
This class triggers a Sinusoidal Modelling process on buffers on the non-real-time thread of the server. It implements a mix of algorithms taken from classic papers. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The algorithm will take a buffer in, and will divide it in two parts: LIST::
|
||||
## a reconstruction of what it detects as sinusoidal;
|
||||
## a residual derived from the previous buffer to allow null-summing::
|
||||
|
||||
The whole process is based on the assumption that signal is made of pitched steady components that have a long-enough duration and are periodic enough to be perceived as such, that can be tracked, resynthesised and removed from the original, leaving behind what is considered as non-pitched, noisy, and/or transient. It first tracks the peaks, then checks if they are the continuation of a peak in previous spectral frames, by assigning them a track.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the sinusoidal estimation to be calculated on a given source buffer and to be resynthesised.
|
||||
|
||||
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 decomposed through the sinusoidal modelling process. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
Where in the srcBuf should the process start, in sample.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
How many frames should be processed.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
ARGUMENT:: numChans
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
ARGUMENT:: sines
|
||||
The index of the buffer where the extracted sinusoidal component will be reconstructed.
|
||||
|
||||
ARGUMENT:: residual
|
||||
The index of the buffer where the residual of the sinusoidal component will be reconstructed.
|
||||
|
||||
ARGUMENT:: bandwidth
|
||||
The number of bins used to resynthesises a peak. It has an effect on CPU cost: the widest is more accurate but more computationally expensive. It is capped at (fftSize / 2) + 1.
|
||||
|
||||
ARGUMENT:: detectionThreshold
|
||||
The threshold in dB above which a magnitude peak is considered to be a sinusoidal component.
|
||||
|
||||
ARGUMENT:: birthLowThreshold
|
||||
The threshold in dB above which to consider a peak to start a sinusoidal component tracking, for the low end of the spectrum. It is interpolated across the spectrum until birthHighThreshold at half-Nyquist.
|
||||
|
||||
ARGUMENT:: birthHighThreshold
|
||||
The threshold in dB above which to consider a peak to start a sinusoidal component tracking, for the high end of the spectrum. It is interpolated across the spectrum until birthLowThreshold at DC.
|
||||
|
||||
ARGUMENT:: minTrackLen
|
||||
The minimum duration, in spectral frames, for a sinusoidal track to be accepted as a partial. It allows to remove bubbly pitchy artefacts, but is more CPU intensive and might reject quick pitch material.
|
||||
|
||||
ARGUMENT:: trackingMethod
|
||||
The algorithm used to track the sinusoidal continuity between spectral frames. 0 is the default, "Greedy", and 1 is a more expensive "Hungarian" one. footnote::Neri, J., and Depalle, P., "Fast Partial Tracking of Audio with Real-Time Capability through Linear Programming". Proceedings of DAFx-2018.::
|
||||
|
||||
ARGUMENT:: trackMagRange
|
||||
The amplitude difference allowed for a track to diverge between frames, in dB.
|
||||
|
||||
ARGUMENT:: trackFreqRange
|
||||
The frequency difference allowed for a track to diverge between frames, in Hertz.
|
||||
|
||||
ARGUMENT:: trackProb
|
||||
The probability of the tracking algorithm to find a track.
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
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 hop size. As sinusoidal estimation 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 windowSize (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 use the next power of 2 equal or above the highest of windowSize and (bandwidth - 1) * 2.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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 [sines, residual] as an argument.
|
||||
|
||||
returns:: an instance of the processor
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// create some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-AaS-SynthTwoVoices-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
d = Buffer.new(s);
|
||||
)
|
||||
|
||||
// run the process with basic parameters
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufSines.process(s, b, sines: c, residual:d).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// listen to each component
|
||||
c.play;
|
||||
d.play;
|
||||
|
||||
//nullsumming tests
|
||||
{(PlayBuf.ar(1, c)) + (PlayBuf.ar(1,d)) - (PlayBuf.ar(1,b,doneAction:2))}.play
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// load two very different files
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-SA-UprightPianoPedalWide.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
)
|
||||
|
||||
// composite one on left one on right as test signals
|
||||
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
|
||||
b.play
|
||||
|
||||
// create 2 new buffers as destinations
|
||||
d = Buffer.new(s); e = Buffer.new(s);
|
||||
|
||||
//run the process on them
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufSines.process(s, b, sines: d, residual:e, windowSize: 2048, hopSize: 256, fftSize: 16384).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
//listen: stereo preserved!
|
||||
d.play
|
||||
e.play
|
||||
::
|
||||
@ -1,142 +0,0 @@
|
||||
TITLE:: FluidBufSpectralShape
|
||||
SUMMARY:: Seven Spectral Shape Descriptors on a Buffer
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading, Classes/FluidSpectralShape, Classes/SpecCentroid, Classes/SpecFlatness, Classes/SpecCentroid, Classes/SpecPcile
|
||||
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements seven of the most popular spectral shape descriptors, computed on a linear scale for both amplitude and frequency. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The descriptors are:
|
||||
LIST::
|
||||
##the four first statistical moments (https://en.wikipedia.org/wiki/Moment_(mathematics) ), more commonly known as:
|
||||
LIST::
|
||||
## the spectral centroid (1) in Hz. This is the point that splits the spectrum in 2 halves of equal energy. It is the weighted average of the magnitude spectrum.
|
||||
## the spectral spread (2) in Hz. This is the standard deviation of the spectrum envelop, or the average of the distance to the centroid.
|
||||
## the normalised skewness (3) as ratio. This indicates how tilted is the spectral curve in relation to the middle of the spectral frame, i.e. half of the Nyquist frequency. If it is below that frequency, i.e. the central bin of the magnitude spectrum, it is positive.
|
||||
## the normalised kurtosis (4) as ratio. This indicates how focused is the spectral curve. If it is peaky, it is high.
|
||||
::
|
||||
## the rolloff (5) in Hz. This indicates the frequency under which 95% of the energy is included.
|
||||
## the flatness (6) in dB. This is the ratio of geometric mean of the magnitude, over the arithmetic mean of the magnitudes. It yields a very approximate measure on how noisy a signal is.
|
||||
## the crest (7) in dB. This is the ratio of the loudest magnitude over the RMS of the whole frame. A high number is an indication of a loud peak poking out from the overal spectral curve.::
|
||||
|
||||
The drawings in Peeters 2003 (http://recherche.ircam.fr/anasyn/peeters/ARTICLES/Peeters_2003_cuidadoaudiofeatures.pdf) are useful, as are the commented examples below. For the mathematically-inclined reader, the tutorials and code offered here (https://www.audiocontentanalysis.org/) are interesting to further the understanding. For examples of the impact of computing the moments in power magnitudes, and/or in exponential frequency scale, please refer to the LINK::Classes/FluidSpectralShape:: helpfile.
|
||||
|
||||
The process will return a multichannel buffer with the seven channels per input channel, each containing the 7 shapes. Each sample represents a value, which is every hopSize.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the spectral shape descriptors 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 described through the various descriptors. The different channels of multichannel buffers will be processing sequentially.
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
Where in the srcBuf should the process start, in sample.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
How many frames should be processed.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
For multichannel srcBuf, which channel should be processed first.
|
||||
|
||||
ARGUMENT:: numChans
|
||||
For multichannel srcBuf, how many channel should be processed.
|
||||
|
||||
ARGUMENT:: features
|
||||
The destination buffer for the 7 spectral features describing the spectral shape.
|
||||
|
||||
ARGUMENT:: minFreq
|
||||
The minimum frequency that the algorithm will consider for computing the spectral shape. Frequencies below will be ignored. The default of 0 goes down to DC when possible.
|
||||
|
||||
ARGUMENT:: maxFreq
|
||||
The maximum frequency that the algorithm will consider for computing the spectral shape. Frequencies above will be ignored. The default of -1 goes up to Nyquist.
|
||||
|
||||
ARGUMENT:: rolloffPercent
|
||||
This sets the percentage of the frame's energy that will be reported as the rolloff frequency. The default is 95%.
|
||||
|
||||
ARGUMENT:: unit
|
||||
The frequency unit for the spectral shapes to be computed upon, and outputted at. The default (0) is in Hertz and computes the moments on a linear spectrum. The alternative is in MIDI note numbers(1), which compute the moments on an exponential spectrum.
|
||||
|
||||
ARGUMENT:: power
|
||||
This flag sets the scaling of the magnitudes in the moment calculation. It uses either its amplitude (0, by default) or its power (1).
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
The window size. As spectral shape 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
|
||||
|
||||
ARGUMENT:: hopSize
|
||||
The window hop size. As spectral shape estimation 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 windowSize (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 use the next power of 2 equal or above the windowSize.
|
||||
|
||||
ARGUMENT:: padding
|
||||
Controls the zero-padding added to either end of the source buffer or segment. Possible values are 0 (no padding), 1 (default, half the window size), or 2 (window size - hop size). Padding ensures that all input samples are completely analysed: with no padding, the first analysis window starts at time 0, and the samples at either end will be tapered by the STFT windowing function. Mode 1 has the effect of centering the first sample in the analysis window and ensuring that the very start and end of the segment are accounted for in the analysis. Mode 2 can be useful when the overlap factor (window size / hop size) is greater than 2, to ensure that the input samples at either end of the segment are covered by the same number of analysis frames as the rest of the analysed material.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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 [features] as an argument.
|
||||
|
||||
returns:: an instance of the processor
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// create some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
// run the process with basic parameters
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufSpectralShape.process(s, b, features: c).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// listen to the source and look at the buffer
|
||||
b.play;
|
||||
c.plot(separately:true)
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// load two very different files
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-SA-UprightPianoPedalWide.wav"));
|
||||
c = Buffer.read(s,FluidFilesPath("Tremblay-AaS-AcousticStrums-M.wav"));
|
||||
)
|
||||
|
||||
// composite one on left one on right as test signals
|
||||
FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b)
|
||||
b.play
|
||||
|
||||
// create a buffer as destinations
|
||||
c = Buffer.new(s);
|
||||
|
||||
//run the process on them
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufSpectralShape.process(s, b, features: c).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// look at the buffer: 7shapes for left, then 7 shapes for right
|
||||
c.plot(separately:true)
|
||||
::
|
||||
@ -1,71 +0,0 @@
|
||||
TITLE:: FluidBufThreadDemo
|
||||
SUMMARY:: A Tutorial Object to Experiment with Multithreading in FluidBuf* Objects
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements a simple tutorial object to illustrate and experiment with the behaviour of the FluidBuf* objects. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
It simply starts a process that will, upon completion of its task, write a single value to the destination buffer. This is the general behaviour of much of the CPU consuming FluidBuf* objects. In this case, as a toy example, the task is simply just wait and do nothing, to simulate a task that would actually take that long in the background.
|
||||
|
||||
The process will, after waiting for STRONG::time:: millisecond, return its delay lenght as a Float writen at index [0] of the specified destination buffer.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the job to be done. In this case, simply waiting STRONG::time:: millisecond before writing a value in the destination buffer.
|
||||
|
||||
ARGUMENT:: server
|
||||
The server on which the destination buffer is declared.
|
||||
|
||||
ARGUMENT:: result
|
||||
The destination buffer, where the value should be written at the end of the process.
|
||||
|
||||
ARGUMENT:: time
|
||||
The duration in milliseconds of the delay that the background thread will wait for before yielding the value to the destination buffer.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
ARGUMENT:: action
|
||||
A function that will be executed upon completion. It is passed the destination buffer as argument.
|
||||
|
||||
returns::The instance of FluidNRTProcess which can be used to cancel the job.
|
||||
|
||||
|
||||
METHOD:: kr
|
||||
This is the UGen that is holding the link with the background thread. It is called by the 'process' method and can be used directly to monitor the progress of the job. For examples of such usage, please refer to the tutorial on link::Guides/FluidBufMultiThreading::.
|
||||
|
||||
ARGUMENT:: result
|
||||
The destination buffer, where the value should be written at the end of the process.
|
||||
|
||||
ARGUMENT:: time
|
||||
The duration of the delay that the background thread will wait for before yielding the value to the destination buffer.
|
||||
|
||||
ARGUMENT:: trig
|
||||
The trigger to start the job.
|
||||
|
||||
ARGUMENT:: blocking
|
||||
The thread on which the job is processed.
|
||||
|
||||
returns::It report the approximate job progress, from 0 to 1.
|
||||
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
For a thorough explanation, please refer to the tutorial on link::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CODE::
|
||||
// define a destination buffer
|
||||
b=Buffer.alloc(s,1);
|
||||
|
||||
// a simple call, where we query the destination buffer upon completion with the action message.
|
||||
FluidBufThreadDemo.process(s, b, 1000, action:{|x|x.get(0,{|y|y.postln});});
|
||||
|
||||
// as the 'process' returns its instance, we can cancel the process easily
|
||||
c = FluidBufThreadDemo.process(s, b, 100000, action: {|x|x.get(0,{|y|y.postln});});
|
||||
c.cancel;
|
||||
|
||||
// if a simple call to the UGen is used, the progress can be monitored. The usual cmd-period will cancel the job by freeing the synth.
|
||||
{c = FluidBufThreadDemo.kr(b,10000).poll; FreeSelfWhenDone.kr(c)}.scope;
|
||||
::
|
||||
@ -1,81 +0,0 @@
|
||||
TITLE:: FluidBufThresh
|
||||
SUMMARY:: A Gate Processor for Buffers
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements a simple Buffer preprocessor, by replacing values under a threshold by 0s. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The process will return a buffer with the same size and shape than the requested range.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
This is the method that calls for the thresholding to be calculated on a given source buffer.
|
||||
|
||||
ARGUMENT:: server
|
||||
The server on which the buffer to be processed is allocated.
|
||||
|
||||
ARGUMENT:: source
|
||||
The index of the buffer to use as the source material to be processed.
|
||||
|
||||
ARGUMENT:: startFrame
|
||||
The starting point (in samples) from which to copy in the source buffer.
|
||||
|
||||
ARGUMENT:: numFrames
|
||||
The duration (in samples) to copy from the source buffer. The default (-1) copies the full lenght of the buffer.
|
||||
|
||||
ARGUMENT:: startChan
|
||||
The first channel from which to copy in the source buffer.
|
||||
|
||||
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.
|
||||
|
||||
ARGUMENT:: destination
|
||||
The index of the buffer to use as the destination for the processed material.
|
||||
|
||||
ARGUMENT:: threshold
|
||||
The threshold under which values will be zeroed
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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 metric will be passed indices as an argument.
|
||||
|
||||
returns:: an instance of the processor
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// make a buffer of know qualities
|
||||
b = Buffer.sendCollection(s,0.0.series(0.1,1.0))
|
||||
// and a destination buffer
|
||||
c = Buffer(s)
|
||||
// play with the threshold
|
||||
FluidBufThresh.process(s, b, destination: c, threshold: 0.5)
|
||||
// retrieve the buffer and enjoy the results.
|
||||
c.getn(0,11,{|x|x.round(0.000001).postln;})
|
||||
|
||||
// also works in multichannel - explore the following buffer
|
||||
b = Buffer.sendCollection(s,0.0.series(0.1,2.0).scramble,2)
|
||||
b.plot.plotMode_(\points)
|
||||
//process and keep just the top values
|
||||
FluidBufThresh.process(s, b, destination: c, threshold: 1.6)
|
||||
//enjoy
|
||||
c.plot.plotMode_(\points)
|
||||
|
||||
//also works with a subset of the input, resizing the output
|
||||
b = Buffer.sendCollection(s,0.0.series(0.1,3.0).reshape(3,10).flop.flat,3)
|
||||
b.plot(separately: true).plotMode_(\points)
|
||||
//process and keep just the top values
|
||||
FluidBufThresh.process(s, b,startFrame: 3,numFrames: 4,startChan: 1,numChans: 1,destination: c, threshold: 1.6)
|
||||
//enjoy
|
||||
c.plot(separately: true).plotMode_(\points)
|
||||
c.query
|
||||
c.getn(0,4,{|x|x.round(0.000001).postln;})
|
||||
::
|
||||
@ -1,150 +0,0 @@
|
||||
TITLE:: FluidBufTransientSlice
|
||||
SUMMARY:: Buffer-Based Transient-Based Slicer
|
||||
CATEGORIES:: Libraries>FluidCorpusManipulation, UGens>Buffer
|
||||
RELATED:: Guides/FluidCorpusManipulation, Guides/FluidBufMultiThreading, Classes/FluidBufTransients
|
||||
|
||||
DESCRIPTION::
|
||||
This class implements a non-real-time transient-based slice extractor relying on the same algorithm than Classes/FluidBufTransients using clicks/transients/derivation/anomaly in the signal to estimate the slicing points. It is part of the LINK:: Guides/FluidCorpusManipulation##Fluid Corpus Manipulation Toolkit::. For more explanations, learning material, and discussions on its musicianly uses, visit http://www.flucoma.org/
|
||||
|
||||
The process will return a buffer which contains indices (in sample) of estimated starting points of the different slices.
|
||||
|
||||
STRONG::Threading::
|
||||
|
||||
By default, this UGen spawns a new thread to avoid blocking the server command queue, so it is free to go about with its business. For a more detailed discussion of the available threading and monitoring options, including the two undocumented Class Methods below (.processBlocking and .kr) please read the guide LINK::Guides/FluidBufMultiThreading::.
|
||||
|
||||
CLASSMETHODS::
|
||||
|
||||
METHOD:: process, processBlocking
|
||||
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 transient 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 srcBuf, which channel should be processed.
|
||||
|
||||
ARGUMENT:: numChans
|
||||
For multichannel srcBuf, 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:: order
|
||||
The order in samples of the impulse response filter used to model the estimated continuous signal. It is how many previous samples are used by the algorithm to predict the next one as reference for the model. The higher the order, the more accurate is its spectral definition, not unlike fft, improving low frequency resolution, but it differs in that it is not conected to its temporal resolution.
|
||||
|
||||
ARGUMENT:: blockSize
|
||||
The size in samples of frame on which it the algorithm is operating. High values are more cpu intensive, and also determines the maximum transient size, which will not be allowed to be more than half that lenght in size.
|
||||
|
||||
ARGUMENT:: padSize
|
||||
The size of the handles on each sides of the block simply used for analysis purpose and avoid boundary issues.
|
||||
|
||||
ARGUMENT:: skew
|
||||
The nervousness of the bespoke detection function with values from -10 to 10. It allows to decide how peaks are amplified or smoothed before the thresholding. High values increase the sensitivity to small variations.
|
||||
|
||||
ARGUMENT:: threshFwd
|
||||
The threshold of the onset of the smoothed error function. It allows tight start of the identification of the anomaly as it proceeds forward.
|
||||
|
||||
ARGUMENT:: threshBack
|
||||
The threshold of the offset of the smoothed error function. As it proceeds backwards in time, it allows tight ending of the identification of the anomaly.
|
||||
|
||||
ARGUMENT:: windowSize
|
||||
The averaging window of the error detection function. It needs smoothing as it is very jittery. The longer the window, the less precise, but the less false positives.
|
||||
|
||||
ARGUMENT:: clumpLength
|
||||
The window size in sample within which positive detections will be clumped together to avoid overdetecting in time.
|
||||
|
||||
ARGUMENT:: minSliceLength
|
||||
The minimum duration of a slice in samples.
|
||||
|
||||
ARGUMENT:: freeWhenDone
|
||||
Free the server instance when processing complete. Default true
|
||||
|
||||
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:: an instance of the processor
|
||||
|
||||
|
||||
EXAMPLES::
|
||||
|
||||
code::
|
||||
// load some buffers
|
||||
(
|
||||
b = Buffer.read(s,FluidFilesPath("Tremblay-AaS-SynthTwoVoices-M.wav"));
|
||||
c = Buffer.new(s);
|
||||
)
|
||||
|
||||
// with basic parameters (wait for the computation time to appear)
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufTransientSlice.process(s,b, indices:c).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
//check the number of slices
|
||||
c.query;
|
||||
|
||||
//loops over a splice
|
||||
(
|
||||
{
|
||||
BufRd.ar(1, b,
|
||||
Phasor.ar(0,1,
|
||||
BufRd.kr(1, c,
|
||||
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1),
|
||||
BufRd.kr(1, c,
|
||||
MouseX.kr(1, BufFrames.kr(c)), 0, 1),
|
||||
BufRd.kr(1,c,
|
||||
MouseX.kr(0, BufFrames.kr(c) - 1), 0, 1)), 0, 1);
|
||||
}.play;
|
||||
)
|
||||
|
||||
// with everything changed to make it much better, at the cost of computation time (only 5 seconds are processed here, again wait for the (longer) computation time to appear)
|
||||
(
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufTransientSlice.process(s,b, 0, 220500, 0, 1, c, 200, 2048, 1024, 1, 3, 1, 15, 30, 4410).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// play with the same player above to hear the segmentation difference
|
||||
::
|
||||
|
||||
STRONG::A stereo buffer example.::
|
||||
CODE::
|
||||
|
||||
// make a stereo buffer
|
||||
b = Buffer.alloc(s,88200,2);
|
||||
|
||||
// add some stereo clicks and listen to them
|
||||
((0..3)*22050+11025).do({|item,index| b.set(item+(index%2), 1.0)})
|
||||
|
||||
b.play
|
||||
|
||||
// create a new buffer as destinations
|
||||
c = Buffer.new(s);
|
||||
|
||||
//run the process on them
|
||||
(
|
||||
// with basic params
|
||||
Routine{
|
||||
t = Main.elapsedTime;
|
||||
FluidBufTransientSlice.process(s,b, indices: c, threshFwd: 1.2).wait;
|
||||
(Main.elapsedTime - t).postln;
|
||||
}.play
|
||||
)
|
||||
|
||||
// list the indicies of detected attacks - the two input channels have been summed
|
||||
c.getn(0,c.numFrames,{|item|(item*2).postln;})
|
||||
::
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue