diff --git a/release-packaging/Classes/FluidBufNMF.sc b/release-packaging/Classes/FluidBufNMF.sc index d0e8b80..5f42b73 100644 --- a/release-packaging/Classes/FluidBufNMF.sc +++ b/release-packaging/Classes/FluidBufNMF.sc @@ -1,5 +1,5 @@ FluidBufNMF { - *process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, dstBufNum, dictBufNum, dictFlag = 0, actBufNum, actFlag = 0, rank = 1, nIter = 100, sortFlag = 0, winSize = 1024, hopSize = 256, fftSize = -1, winType = 0, randSeed = -1; + *process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, dstBufNum, dictBufNum, dictFlag = 0, actBufNum, actFlag = 0, rank = 1, nIter = 100, sortFlag = 0, winSize = 1024, hopSize = 512, fftSize = -1, winType = 0, randSeed = -1; if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; diff --git a/release-packaging/Classes/FluidBufNoveltySlice.sc b/release-packaging/Classes/FluidBufNoveltySlice.sc index 09fbb1f..9a0bb4d 100644 --- a/release-packaging/Classes/FluidBufNoveltySlice.sc +++ b/release-packaging/Classes/FluidBufNoveltySlice.sc @@ -1,5 +1,5 @@ FluidBufNoveltySlice{ - *process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, kernelSize = 3, thresh = 0.8, filterSize = 11, winSize = 1024, hopSize = 512, fftSize = 2048, maxWindowSize=16384; + *process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, kernelSize = 3, thresh = 0.8, filterSize = 0, winSize = 1024, hopSize = 512, fftSize = 2048; if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(transBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; diff --git a/release-packaging/Classes/FluidBufTransientSlice.sc b/release-packaging/Classes/FluidBufTransientSlice.sc index bdda702..fd8c4bb 100644 --- a/release-packaging/Classes/FluidBufTransientSlice.sc +++ b/release-packaging/Classes/FluidBufTransientSlice.sc @@ -1,11 +1,11 @@ FluidBufTransientSlice{ - *process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, order = 200, blockSize = 2048, padSize = 1024, skew = 0, threshFwd = 3, threshBack = 1.1, winSize = 14, debounce = 25; + *process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, order = 200, blockSize = 2048, padSize = 1024, skew = 0, threshFwd = 3, threshBack = 1.1, winSize = 14, debounce = 25, minSlice = 1000; if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(transBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; server = server ? Server.default; - server.sendMsg(\cmd, \BufTransientSlice, srcBufNum, startAt, nFrames, startChan, nChans, transBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce); + server.sendMsg(\cmd, \BufTransientSlice, srcBufNum, startAt, nFrames, startChan, nChans, transBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce, minSlice); } } diff --git a/release-packaging/Classes/FluidNMFMatch.sc b/release-packaging/Classes/FluidNMFMatch.sc index 8c257e3..6fffd9e 100644 --- a/release-packaging/Classes/FluidNMFMatch.sc +++ b/release-packaging/Classes/FluidNMFMatch.sc @@ -1,7 +1,7 @@ FluidNMFMatch : MultiOutUGen { - *kr { arg in = 0, dictBufNum, rank = 1, nIter = 10, winSize = 1024, hopSize = 256, fftSize = -1; - ^this.multiNew('control', in, dictBufNum, rank, nIter, winSize, hopSize, fftSize,16384); + *kr { arg in = 0, dictBufNum, maxRank = 1, nIter = 10, winSize = 1024, hopSize = 512, fftSize = -1; + ^this.multiNew('control', in, dictBufNum, maxRank, nIter, winSize, hopSize, fftSize); } init {arg ...theInputs; diff --git a/release-packaging/Classes/FluidSTFTPass.sc b/release-packaging/Classes/FluidSTFTPass.sc index 5171e63..6b86f5e 100644 --- a/release-packaging/Classes/FluidSTFTPass.sc +++ b/release-packaging/Classes/FluidSTFTPass.sc @@ -1,6 +1,5 @@ FluidSTFTPass : UGen { - *ar { arg in = 0, windowSize= 1024, hopSize= 256, fftSize= -1, maxWinSize= 16384; - ^this.multiNew('audio', in.asAudioRateInput(this),windowSize, hopSize, fftSize, maxWinSize) + *ar { arg in = 0, windowSize= 1024, hopSize= 256, fftSize= -1; + ^this.multiNew('audio', in.asAudioRateInput(this),windowSize, hopSize, fftSize) } } -// \ No newline at end of file diff --git a/release-packaging/Classes/FluidSines.sc b/release-packaging/Classes/FluidSines.sc index d44a7fa..f2c377b 100644 --- a/release-packaging/Classes/FluidSines.sc +++ b/release-packaging/Classes/FluidSines.sc @@ -1,13 +1,13 @@ FluidSines : MultiOutUGen { *ar { arg in = 0, bandwidth = 76, thresh = 0.7, minTrackLen = 15, magWeight = 0.1, freqWeight = 1.0, winSize= 2048, hopSize= 512, fftSize= 8192; - ^this.multiNew('audio', in.asAudioRateInput(this), bandwidth, thresh, minTrackLen, magWeight,freqWeight ,winSize, hopSize, fftSize,16384) + ^this.multiNew('audio', in.asAudioRateInput(this), bandwidth, thresh, minTrackLen, magWeight,freqWeight ,winSize, hopSize, fftSize) } init { arg ... theInputs; inputs = theInputs; channels = [ OutputProxy(rate, this, 0), OutputProxy(rate, this, 1) - ] + ]; ^channels } checkInputs { ^this.checkNInputs(1) } diff --git a/release-packaging/Classes/FluidTransientSlice.sc b/release-packaging/Classes/FluidTransientSlice.sc index 5ef269a..00ae369 100644 --- a/release-packaging/Classes/FluidTransientSlice.sc +++ b/release-packaging/Classes/FluidTransientSlice.sc @@ -1,5 +1,5 @@ FluidTransientSlice : UGen { - *ar { arg in = 0, order = 20, blockSize = 256, padSize = 128, skew = 0.0, threshFwd = 3.0, threshBack = 1.1, winSize=14, debounce=25; - ^this.multiNew('audio', in.asAudioRateInput(this), order, blockSize, padSize, skew, threshFwd ,threshBack, winSize, debounce) + *ar { arg in = 0, order = 20, blockSize = 256, padSize = 128, skew = 0.0, threshFwd = 3.0, threshBack = 1.1, winSize=14, debounce=25, minSlice = 1000; + ^this.multiNew('audio', in.asAudioRateInput(this), order, blockSize, padSize, skew, threshFwd ,threshBack, winSize, debounce, minSlice) } } \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidBufCompose.schelp b/release-packaging/HelpSource/Classes/FluidBufCompose.schelp index 43e9b31..01c6493 100644 --- a/release-packaging/HelpSource/Classes/FluidBufCompose.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufCompose.schelp @@ -183,4 +183,4 @@ f.play; // compare with the original b.play; - :: \ No newline at end of file +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp b/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp index dae40e5..57e9398 100644 --- a/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp @@ -115,55 +115,54 @@ Discussion:: EXAMPLES:: code:: - //load buffers - ( - b = Buffer.read(s,File.realpath(FluidBufHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); - c = Buffer.new(s); - d = Buffer.new(s); - e = Buffer.new(s); - ) - - // run with basic parameters - ( - Routine{ - t = Main.elapsedTime; - FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum); +//load buffers +( + b = Buffer.read(s,File.realpath(FluidBufHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); + c = Buffer.new(s); + d = Buffer.new(s); + e = Buffer.new(s); +) + +// run with basic parameters +( + Routine{ + t = Main.elapsedTime; + FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum); + s.sync; + (Main.elapsedTime - t).postln; + }.play +) + +//query and play the harmonic +c.query; +c.play; +//querry and play the percussive +d.query; +d.play; + +//nullsumming tests +{(PlayBuf.ar(1,c.bufnum))+(PlayBuf.ar(1,d.bufnum))+(-1*PlayBuf.ar(1,b.bufnum,doneAction:2))}.play + +//more daring parameters, in mode 2 +( + Routine{ + t = Main.elapsedTime; + FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum, resBufNum:e.bufnum, harmFiltSize:31, modeFlag:2, htf1: 0.005, hta1: 7.5, htf2: 0.168, hta2: 7.5, ptf1: 0.004, pta1: 26.5, ptf2: 0.152, pta2: 26.5); s.sync; - (Main.elapsedTime - t).postln; - }.play - ) - - //query and play the harmonic - c.query; - c.play; - //querry and play the percussive - d.query; - d.play; - - //nullsumming tests - {(PlayBuf.ar(1,c.bufnum))+(PlayBuf.ar(1,d.bufnum))+(-1*PlayBuf.ar(1,b.bufnum,doneAction:2))}.play - - //more daring parameters, in mode 2 - ( - Routine{ - t = Main.elapsedTime; - FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum, resBufNum:e.bufnum, harmFiltSize:31, modeFlag:2, htf1: 0.005, hta1: 7.5, htf2: 0.168, hta2: 7.5, ptf1: 0.004, pta1: 26.5, ptf2: 0.152, pta2: 26.5); - s.sync; - (Main.elapsedTime - t).postln; - }.play - ) - - //query and play the harmonic - c.query; - c.play; - //query and play the percussive - d.query; - d.play; - //query and play the residual - e.query; - e.play; - - //still nullsumming - {PlayBuf.ar(1,c.bufnum) + PlayBuf.ar(1,d.bufnum) + PlayBuf.ar(1,e.bufnum) - PlayBuf.ar(1,b.bufnum,doneAction:2)}.play; - :: - \ No newline at end of file + (Main.elapsedTime - t).postln; + }.play +) + +//query and play the harmonic +c.query; +c.play; +//query and play the percussive +d.query; +d.play; +//query and play the residual +e.query; +e.play; + +//still nullsumming +{PlayBuf.ar(1,c.bufnum) + PlayBuf.ar(1,d.bufnum) + PlayBuf.ar(1,e.bufnum) - PlayBuf.ar(1,b.bufnum,doneAction:2)}.play; +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp index 36d3dc0..5c61110 100644 --- a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp @@ -31,10 +31,7 @@ The whole process can be related to a channel vocoder where, instead of fixed ba More information on possible musicianly uses of NMF are availabe in LINK::Guides/FluCoMa:: overview file. FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote:: -This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899). -:: - - +This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899). :: CLASSMETHODS:: @@ -113,6 +110,58 @@ RETURNS:: EXAMPLES:: +STRONG::A didactic example:: +CODE:: +( +// create buffers +b = Buffer.alloc(s,44100); +c = Buffer.alloc(s, 44100); +d = Buffer.new(s); +e = Buffer.new(s); +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,srcBufNumA:b.bufnum, srcBufNumB:c.bufnum,dstStartAtB:44100,dstBufNum:d.bufnum); + s.sync; + d.query; +}.play; +) + +// check +d.plot +d.play //////(beware !!!! loud!!!) + +( +// separate them in 2 ranks +Routine { + FluidBufNMF.process(s, d.bufnum, dstBufNum:e.bufnum, dictBufNum: f.bufnum, actBufNum:g.bufnum, rank:2); + s.sync; + e.query; + f.query; + g.query; +}.play +) + +// look at the resynthesised separated signal +e.plot; + +// look at the dictionaries signal for 2 spikes +f.plot; + +// look at the activations +g.plot; + +//trying running the same process on superimposed sinewaves instead of consecutive in the source and see how it fails. +:: + +STRONG::Basic musical examples:: code:: // set some buffers and parameters @@ -186,11 +235,11 @@ c.plot;x.plot; y.plot; ) :: - STRONG::Fixed Dictionnaries:: The process can be trained, and the learnt dictionaries or activations can be used as templates. +STRONG::Fixed Dictionnaries:: The process can be trained, and the learnt dictionaries or activations can be used as templates. - CODE:: +CODE:: - //set some buffers +//set some buffers ( b = Buffer.read(s,File.realpath(FluidBufNMF.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav"); c = Buffer.new(s); @@ -199,7 +248,7 @@ e = Buffer.alloc(s,1,1); y = Buffer.alloc(s,1,1); ) - // train only 2 seconds +// train only 2 seconds ( Routine { FluidBufNMF.process(s,b.bufnum,0,88200,0,1, c.bufnum, x.bufnum, rank:10); @@ -210,27 +259,28 @@ Routine { // find the rank that has the picking sound by changing which channel to listen to ( - ~element = 0; + ~element = 9; {PlayBuf.ar(10,c.bufnum)[~element]}.play ) // copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel ( Routine{ - (0..9).remove(~element).do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); - s.sync; - e.query; - s.sync; - FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: ~element, nChansA: 1, srcBufNumB: e.bufnum, dstStartChanB: 1, dstBufNum: e.bufnum); - s.sync; - e.query; + z = (0..9); + FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: z.removeAt(~element), nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum); + s.sync; + e.query; + s.sync; + z.do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, dstStartChanA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); + s.sync; + e.query; }.play; ) //process the whole file, splitting it with the 2 trained dictionnaries ( Routine{ - FluidBufNMF.process(s, b.bufnum, dstBufNum: c.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum:y.bufnum, rank:2); + FluidBufNMF.process(s, b.bufnum, dstBufNum: c.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum: y.bufnum, rank:2); s.sync; c.query; }.play; @@ -242,4 +292,86 @@ c.play // it even null-sums {(PlayBuf.ar(2,c.bufnum,doneAction:2).sum)-(PlayBuf.ar(1,b.bufnum,doneAction:2))}.play :: - \ No newline at end of file + +STRONG::Updating Dictionnaries:: The process can update dictionaries 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,srcBufNumA:b.bufnum, srcBufNumB:c.bufnum,dstStartAtB:44100,dstBufNum:d.bufnum); + s.sync; + d.query; +}.play; +) + +// check +d.plot +d.play //////(beware !!!! loud!!!) + +( +//make a seeding dictionary of 3 ranks: +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 dictionary: a steep lowpass, a steep highpass, and a small DC +e.plot +e.query + +( +// use the seeding dictionary, without updating +Routine { + FluidBufNMF.process(s, d.bufnum, dstBufNum:f.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum:g.bufnum, rank:3); + s.sync; + e.query; + f.query; + g.query; +}.play +) + +// look at the resynthesised separated signal +f.plot; + +// look at the dictionaries that have not changed +e.plot; + +// look at the activations +g.plot; + +( +// use the seeding dictionary, with updating this time +Routine { + FluidBufNMF.process(s, d.bufnum, dstBufNum:f.bufnum, dictBufNum: e.bufnum, dictFlag: 1, actBufNum:g.bufnum, rank:3); + s.sync; + e.query; + f.query; + g.query; +}.play +) + +// look at the resynthesised separated signal +f.plot; + +// look at the dictionaries that have now updated in place (with the 3rd channel being more focused +e.plot; + +// look at the activations (sharper 3rd rank at transitions) +g.plot; +:: \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp b/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp index 777b609..c7a4dde 100644 --- a/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp @@ -40,7 +40,10 @@ 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:: thresh - The normalised threshold, between 0 an 1, to consider a peak as a sinusoidal component from the in the novelty curve. + 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:: winSize 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 @@ -91,3 +94,39 @@ c.query; }.play; ) :: + +STRONG::Examples of the impact of the filterSize:: + + CODE:: +// load some buffers +( +b = Buffer.read(s,File.realpath(FluidBufNoveltySlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav"); +c = Buffer.new(s); +) + +// process with a given filterSize +FluidBufNoveltySlice.process(s,b.bufnum, transBufNum: c.bufnum, kernelSize:31, thresh:0.35, filterSize:0) + +//check the number of slices: it is the number of frames in the transBuf minus the boundary index. +c.query; + +//play slice number 2 +( +{ + BufRd.ar(1, b.bufnum, + Line.ar( + BufRd.kr(1, c.bufnum, DC.kr(2), 0, 1), + BufRd.kr(1, c.bufnum, DC.kr(3), 0, 1), + (BufRd.kr(1, c.bufnum, DC.kr(3)) - BufRd.kr(1, c.bufnum, DC.kr(2), 0, 1) + 1) / s.sampleRate), + 0,1); +}.play; +) + +// change the filterSize in the code above to 4. Then to 8. Listen in between to the differences. + +// What's happening? In the first instance (filterSize = 0), 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 = 8), we start to loose precision. Have fun with different values of theshold then will allow you to find the perfect segment for your signal. +:: \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp b/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp index d693f36..16af951 100644 --- a/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp @@ -58,7 +58,10 @@ ARGUMENT:: winSize 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:: debounce - The window size in sample within which positive detections will be clumped together to avoid overdetecting in time. No slice will be shorter than this duration. + The window size in sample within which positive detections will be clumped together to avoid overdetecting in time. + +ARGUMENT:: minSlice + The minimum duration of a slice in samples. RETURNS:: Nothing, as the destination buffer is declared in the function call. @@ -77,7 +80,7 @@ c = Buffer.new(s); ( Routine{ t = Main.elapsedTime; - FluidBufTransientSlice.process(s,b.bufnum, transBufNum:c.bufnum, order:80, debounce:4410); + FluidBufTransientSlice.process(s,b.bufnum, transBufNum:c.bufnum, order:80, minSlice:4410); s.sync; (Main.elapsedTime - t).postln; }.play @@ -105,7 +108,7 @@ c.query; ( Routine{ t = Main.elapsedTime; - FluidBufTransients.process(s,b.bufnum, 44100, 44100, 0, 0, c.bufnum, d.bufnum, 100, 512,256,1,2,1,12,20); + FluidBufTransients.process(s,b.bufnum, 44100, 44100, 0, 0, c.bufnum, d.bufnum, 100, 512,256,1,2,1,12,20,441); s.sync; (Main.elapsedTime - t).postln; }.play diff --git a/release-packaging/HelpSource/Classes/FluidBufTransients.schelp b/release-packaging/HelpSource/Classes/FluidBufTransients.schelp index 812e9a6..1e5b3dd 100644 --- a/release-packaging/HelpSource/Classes/FluidBufTransients.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufTransients.schelp @@ -109,7 +109,4 @@ Routine{ (Main.elapsedTime - t).postln; }.play ) - :: - - - \ No newline at end of file +:: diff --git a/release-packaging/HelpSource/Classes/FluidGain.schelp b/release-packaging/HelpSource/Classes/FluidGain.schelp index eb54159..b039a81 100644 --- a/release-packaging/HelpSource/Classes/FluidGain.schelp +++ b/release-packaging/HelpSource/Classes/FluidGain.schelp @@ -28,14 +28,14 @@ RETURNS:: EXAMPLES:: Summing with the inverse (gain of -1) with a delay of the latency gives us CPU-expensive silence. - CODE:: - { var source = PinkNoise.ar(0.1); DelayN.ar(source,delaytime:1000/s.sampleRate) + FluidGain.ar(source,1000,-1); }.play - :: - Varying the gain at audio rate. - CODE:: - { FluidGain.ar(PinkNoise.ar(0.1), gain:LFTri.ar(1)) }.play - :: - Varying the gain at comtrol rate, in beautiful stereo. - CODE:: - { FluidGain.ar(SinOsc.ar([222,333],mul:0.1), gain:LFTri.kr([0.5,0.7])) }.play - :: +CODE:: +{ var source = PinkNoise.ar(0.1); DelayN.ar(source,delaytime:1000/s.sampleRate) + FluidGain.ar(source,1000,-1); }.play +:: +Varying the gain at audio rate. +CODE:: +{ FluidGain.ar(PinkNoise.ar(0.1), gain:LFTri.ar(1)) }.play +:: +Varying the gain at comtrol rate, in beautiful stereo. +CODE:: +{ FluidGain.ar(SinOsc.ar([222,333],mul:0.1), gain:LFTri.kr([0.5,0.7])) }.play +:: diff --git a/release-packaging/HelpSource/Classes/FluidHPSS.schelp b/release-packaging/HelpSource/Classes/FluidHPSS.schelp index deff4aa..418cbd4 100644 --- a/release-packaging/HelpSource/Classes/FluidHPSS.schelp +++ b/release-packaging/HelpSource/Classes/FluidHPSS.schelp @@ -89,24 +89,23 @@ Discussion:: EXAMPLES:: CODE:: - //load a soundfile to play - b = Buffer.read(s,File.realpath(FluidHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); +//load a soundfile to play +b = Buffer.read(s,File.realpath(FluidHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); - // run with basic parameters (left is harmonic, right is percussive) - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1))}.play +// run with basic parameters (left is harmonic, right is percussive) +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1))}.play - // run in mode 1 - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,1,0.05,40,0.1,-40)}.play +// run in mode 1 +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,1,0.05,40,0.1,-40)}.play - // run in mode 2m listening to - //the harmonic stream - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[0].dup}.play - // the percussive stream - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[1].dup}.play - // the residual stream - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[2].dup}.play +// run in mode 2m listening to +//the harmonic stream +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[0].dup}.play +// the percussive stream +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[1].dup}.play +// the residual stream +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[2].dup}.play - // null test (the process add a latency of ((harmFiltSize + (winSize / hopSize) - 1) * hopSize) samples - {var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidHPSS.ar(sig,17,31, winSize:1024,hopSize:512,fftSize:2048).sum - DelayN.ar(sig, 1, ((31 + 1) * 512 / s.sampleRate))]}.play +// null test (the process add a latency of ((harmFiltSize + (winSize / hopSize) - 1) * hopSize) samples +{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidHPSS.ar(sig,17,31, winSize:1024,hopSize:512,fftSize:2048).sum - DelayN.ar(sig, 1, ((31 + 1) * 512 / s.sampleRate))]}.play :: - \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp index a08e9cc..df68113 100644 --- a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp +++ b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp @@ -1,55 +1,313 @@ TITLE:: FluidNMFMatch -SUMMARY:: Real-Time Non-Negative Matrix Factorisation on Buffered Dictionaries +SUMMARY:: Real-Time Non-Negative Matrix Factorisation with Fixed Dictionaries CATEGORIES:: Libraries>FluidDecomposition RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF DESCRIPTION:: -The FluidBufNMF object provides the activation (linked to amplitude) for each pre-defined dictionaries (similar to spectra) predefined in a buffer. These dictionaries would have usually be computed through an offline Non-Negative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. ‘Learning the Parts of Objects by Non-Negative Matrix Factorization’. Nature 401 (6755): 788–91. https://doi.org/10.1038/44565 :: with the link::Classes/FluidBufNMF:: UGen. NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored. +The FluidNMFMatch object matches an incoming audio signal against a set of spectral templates using an slimmed-down version of Nonnegative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. ‘Learning the Parts of Objects by Non-Negative Matrix Factorization’. Nature 401 (6755): 788–91. https://doi.org/10.1038/44565. :: -The algorithm takes a buffer in which provides a spectral definition of a number of components, determined by the rank argument and the dictionary buffer channel count. It works iteratively, by trying to find a combination of amplitudes ('activations') that yield the original magnitude spectrogram of the audio input 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. +It outputs at kr the degree of detected match for each template (the activation amount, in NMF-terms). The spectral templates are presumed to have been produced by the offline NMF process (link::Classes/FluidBufNMF::), and must be the correct size with respect to the FFT settings being used (FFT size / 2 + 1 frames long). The rank of the decomposition is determined by the number of channels in the supplied buffer of templates, up to a maximum set by the STRONG::maxRank:: parameter. + +NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored. It works iteratively, by trying to find a combination of amplitudes ('activations') that yield the original magnitude spectrogram of the audio input when added together. By and large, there is no unique answer to this question (i.e. there are different ways of accounting for an evolving spectrum in terms of some set of templates and envelopes). In its basic form, NMF is a form of unsupervised learning: it starts with some random data and then converges towards something that minimizes the distance between its generated data and the original:it tends to converge very quickly at first and then level out. Fewer iterations mean less processing, but also less predictable results. The whole process can be related to a channel vocoder where, instead of fixed bandpass filters, we get more complex filter shapes and the activations correspond to channel envelopes. More information on possible musicianly uses of NMF are availabe in LINK::Guides/FluCoMa:: overview file. -FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote:: -This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899). -:: +FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote::This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899). :: CLASSMETHODS:: METHOD:: kr -The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::rank::. +The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::maxRank:: . If the dictionary buffer has fewer than maxRank channels, the remaining outputs will be zeroed. ARGUMENT:: in -The input to the factorisation process. +The signal input to the factorisation process. ARGUMENT:: dictBufNum - The index of the buffer where the different dictionaries will be matched against. Dictionaries must be STRONG::(fft size / 2) + 1:: frames and STRONG::rank:: channels + The server index of the buffer containing the different dictionaries that the input signal will be matched against. Dictionaries must be STRONG::(fft size / 2) + 1:: frames. If the buffer has more than STRONG::maxRank:: channels, the excess will be ignored. -ARGUMENT:: rank - The number of elements the NMF algorithm will try to divide the spectrogram of the source in. This should match the number of channels of the dictBuf defined above. +ARGUMENT::maxRank + The maximum number of elements the NMF algorithm will try to divide the spectrogram of the source in. This dictates the number of output channelsfor the ugen. ARGUMENT:: nIter - The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU expensive, lower numbers will be more unpredictable in quality. + The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU intensive, lower numbers will be more unpredictable in quality. ARGUMENT:: winSize - The window size. As NMF relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty + The number of samples that are analysed at a time. A lower number yields greater temporal resolution, at the expense of spectral resoultion, and vice-versa. ARGUMENT:: hopSize - The window hope 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. + The window hope 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. Default = winSize / 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 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. Default = winSize -returns:: - A multichannel array, giving for each dictionary the activation value. +RETURNS:: + A multichannel kr output, giving for each dictionary component the activation amount. EXAMPLES:: -yes +STRONG::A didactic example:: + CODE:: +( +// create buffers +b= Buffer.alloc(s,44100); +c = Buffer.alloc(s, 44100); +d = Buffer.new(s); +e= 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,srcBufNumA:b.bufnum, srcBufNumB:c.bufnum,dstStartAtB:44100,dstBufNum:d.bufnum); + s.sync; + d.query; +}.play; +) + +// check +d.plot +d.play //////(beware !!!! loud!!!) + +( +// separate them in 2 ranks +Routine { + FluidBufNMF.process(s, d.bufnum, dictBufNum: e.bufnum, rank:2); + s.sync; + e.query; +}.play +) + +// check for 2 spikes in the spectra +e.query +e.plot + +// test the activations values with test one, another, or both ideal material +{FluidNMFMatch.kr(SinOsc.ar(500),e.bufnum,2, hopSize:512)}.plot(1) + +{FluidNMFMatch.kr(SinOsc.ar(5000),e.bufnum,2, hopSize:512)}.plot(1) + +{FluidNMFMatch.kr(SinOsc.ar([500,5000]).sum,e.bufnum,2, hopSize:512)}.plot(1) +:: + +STRONG::A pick compressor:: + CODE:: +//set some buffers +( +b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav"); +c = Buffer.new(s); +x = Buffer.new(s); +e = Buffer.alloc(s,1,1); +) + +// train only 2 seconds +( +Routine { + FluidBufNMF.process(s,b.bufnum,0,88200,0,1, c.bufnum, x.bufnum, rank:10,fftSize:2048); + s.sync; + c.query; +}.play; +) + +// wait for the query to print +// then find the rank that has the picking sound by changing which channel to listen to +( + ~element = 8; + {PlayBuf.ar(10,c.bufnum)[~element]}.play +) + +// copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel +( +Routine{ + z = (0..9); + FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: z.removeAt(~element), nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum); + s.sync; + e.query; + s.sync; + z.do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, dstStartChanA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); + s.sync; + e.query; +}.play; +) +e.plot; + +//using this trained dictionary we can see the envelop (activations) of each rank +{FluidNMFMatch.kr(PlayBuf.ar(1,b.bufnum),e.bufnum,2,fftSize:2048)}.plot(1); +// the left/top activations are before, the pick before the sustain. + +//we can then use the activation value to sidechain a compression patch that is sent in a delay +( +{ + var source, todelay, delay1, delay2, delay3, feedback, mod1, mod2, mod3, mod4; + //read the source + source = PlayBuf.ar(1, b.bufnum); + + // generate modulators that are coprime in frequency + mod1 = SinOsc.ar(1, 0, 0.001); + mod2 = SinOsc.ar(((617 * 181) / (461 * 991)), 0, 0.001); + mod3 = SinOsc.ar(((607 * 193) / (491 * 701)), 0, 0.001); + mod4 = SinOsc.ar(((613 * 191) / (463 * 601)), 0, 0.001); + + // compress the signal to send to the delays + todelay = DelayN.ar(source,0.1, 800/44100, //delaying it to compensate for FluidNMFMatch's latency + LagUD.ar(K2A.ar(FluidNMFMatch.kr(source,e.bufnum,2,fftSize:2048)[0]), //reading the channel of the activations on the pick dictionary + 80/44100, // lag uptime (compressor's attack) + 1000/44100, // lag downtime (compressor's decay) + (1/(2.dbamp) // compressor's threshold inverted + )).clip(1,1000).pow((8.reciprocal)-1)); //clipping it so we only affect above threshold, then ratio(8) becomes the exponent of that base + + // delay network + feedback = LocalIn.ar(3);// take the feedback in for the delays + delay1 = DelayC.ar(BPF.ar(todelay+feedback[1]+(feedback[2] * 0.3), 987, 6.7,0.8),0.123,0.122+(mod1*mod2)); + delay2 = DelayC.ar(BPF.ar(todelay+feedback[0]+(feedback[2] * 0.3), 1987, 6.7,0.8),0.345,0.344+(mod3*mod4)); + delay3 = DelayC.ar(BPF.ar(todelay+feedback[1], 1456, 6.7,0.8),0.567,0.566+(mod1*mod3),0.6); + LocalOut.ar([delay1,delay2, delay3]); // write the feedback for the delays + + //listen to the delays only by uncommenting the following line + // [delay1+delay3,delay2+delay3] + source.dup + ([delay1+delay3,delay2+delay3]*(-3.dbamp)) +}.play; +) + +:: +STRONG::Object finder:: + CODE:: +/set some buffers +( +b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-BaB-SoundscapeGolcarWithDog.wav"); +c = Buffer.new(s); +x = Buffer.new(s); +e = Buffer.alloc(s,1,1); +) + +// train where all objects are present +( +Routine { + FluidBufNMF.process(s,b.bufnum,130000,150000,0,1, c.bufnum, x.bufnum, rank:10); + s.sync; + c.query; +}.play; +) + +// wait for the query to print +// then find a rank for each item you want to find. You could also sum them. Try to find a rank with a good object-to-rest ratio +( + ~dog =0; + {PlayBuf.ar(10,c.bufnum)[~dog]}.play +) + +( + ~bird = 5; + {PlayBuf.ar(10,c.bufnum)[~bird]}.play +) + + +// copy at least one other rank to a third rank, a sort of left-over channel +( +Routine{ + FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:~dog, nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum); + FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:~bird, nChansA: 1, dstStartChanA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum); + s.sync; + (0..9).removeAll([~dog,~bird]).do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, dstStartChanA: 2, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); + s.sync; + e.query; +}.play; +) +e.plot; + +//using this trained dictionary we can then see the activation... +( +{ + var source, blips; + //read the source + source = PlayBuf.ar(2, b.bufnum); + blips = FluidNMFMatch.kr(source.sum,e.bufnum,3); + }.plot(10); +) + +// ...and use some threshold to 'find' objects... +( +{ + var source, blips; + //read the source + source = PlayBuf.ar(2, b.bufnum); + blips = Schmidt.kr(FluidNMFMatch.kr(source.sum,e.bufnum,3),0.5,[10,1,1000]); + }.plot(10); +) + +// ...and use these to sonify them +( +{ + var source, blips, dogs, birds; + //read the source + source = PlayBuf.ar(2, b.bufnum); + blips = Schmidt.kr(FluidNMFMatch.kr(source.sum,e.bufnum,3),0.5,[10,1,1000]); + dogs = SinOsc.ar(100,0,Lag.kr(blips[0],0.05,0.15)); + birds = SinOsc.ar(1000,0,Lag.kr(blips[1],0.05,0.05)); + [dogs, birds] + source; + }.play; +) +:: + STRONG::Pretrained piano:: + CODE:: +//load in the sound in and a pretrained dictionary +( + b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav"); + c = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/filters/piano-dicts.wav"); +) +b.play +c.query + +//use the pretrained dictionary to compute activations of each notes to drive the amplitude of a resynth +( +{ + var source, resynth; + source = PlayBuf.ar(2, b.bufnum,loop:1).sum; + resynth = SinOsc.ar((21..108).midicps, 0, FluidNMFMatch.kr(source,c.bufnum,88,10,4096).madd(0.002)).sum; + [source, resynth] +}.play +) + + +//now sample and hold the same stream to get notes identified, played and sent back via osc +( +{ + var source, resynth, chain, trig, acts; + source = PlayBuf.ar(2,b.bufnum,loop:1).sum; + + // built in attack detection, delayed until the stable part of the sound + chain = FFT(LocalBuf(256), source); + trig = TDelay.kr(Onsets.kr(chain, 0.5),0.1); + + // samples and holds activation values that are scaled and capped, in effect thresholding them + acts = Latch.kr(FluidNMFMatch.kr(source,c.bufnum,88,10,4096).linlin(15,20,0,0.1),trig); + + // resynths as in the previous example, with the values sent back to the language + resynth = SinOsc.ar((21..108).midicps, 0, acts).sum; + SendReply.kr(trig, '/activations', acts); + [source, resynth] + // [source, T2A.ar(trig)] + // resynth +}.play +) + +// define a receiver for the activations +( + OSCdef(\listener, {|msg| + var data = msg[3..]; + // removes the silent and spits out the indicies as midinote number + data.collect({arg item, i; if (item > 0.01, {i + 21})}).reject({arg item; item.isNil}).postln; + }, '/activations'); +) + +:: + STRONG::Strange Resonators:: CODE:: - //indeed + //to be completed :: \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp b/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp index 67c6806..1bc8260 100644 --- a/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp +++ b/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp @@ -31,19 +31,19 @@ RETURNS:: EXAMPLES:: - Summing with the inverse (gain of -1) with a delay of the latency gives us CPU-expensive silence. - CODE:: - { var source = PinkNoise.ar(0.1); DelayN.ar(source, delaytime:1024/s.sampleRate, mul: -1) + FluidSTFTPass.ar(source, 1024, 256, 1024); }.play - :: - Larger, oversampled, FFT - CODE:: - { FluidSTFTPass.ar(PinkNoise.ar(0.1), 2048, 128, 8192) }.play - :: - Stereo Input Tests. - CODE:: - { FluidSTFTPass.ar([SinOsc.ar(222,mul: 0.1), PinkNoise.ar(Decay.ar(Impulse.ar(0.666,mul: 0.2), 0.5))], fftSize:1024)}.play - :: - Stereo Parameter Tests. - CODE:: - { FluidSTFTPass.ar(SinOsc.ar(222,mul: 0.1), [1024,8192],256,8192)}.play - :: +Summing with the inverse (gain of -1) with a delay of the latency gives us CPU-expensive silence. +CODE:: +{ var source = PinkNoise.ar(0.1); DelayN.ar(source, delaytime:1024/s.sampleRate, mul: -1) + FluidSTFTPass.ar(source, 1024, 256, 1024); }.play +:: +Larger, oversampled, FFT +CODE:: +{ FluidSTFTPass.ar(PinkNoise.ar(0.1), 2048, 128, 8192) }.play +:: +Stereo Input Tests. +CODE:: +{ FluidSTFTPass.ar([SinOsc.ar(222,mul: 0.1), PinkNoise.ar(Decay.ar(Impulse.ar(0.666,mul: 0.2), 0.5))], fftSize:1024)}.play +:: +Stereo Parameter Tests. +CODE:: +{ FluidSTFTPass.ar(SinOsc.ar(222,mul: 0.1), [1024,8192],256,8192)}.play +:: diff --git a/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp b/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp index fd1ea88..326f75b 100644 --- a/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp @@ -38,7 +38,10 @@ ARGUMENT:: winSize 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:: debounce - The window size in sample within with positive detections will be clumped together to avoid overdetecting in time. No slice will be shorter than this duration. + The window size in sample within with positive detections will be clumped together to avoid overdetecting in time. + +ARGUMENT:: minSlice + The minimum duration of a slice in samples. RETURNS:: An audio stream with impulses at detected transients. The latency between the input and the output is (blockSize + padSize - order) samples. @@ -54,18 +57,17 @@ b = Buffer.read(s,File.realpath(FluidTransientSlice.class.filenameSymbol).dirnam {var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 20)/ s.sampleRate))]}.play // other parameters -{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig,order:80,debounce:2205)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 80)/ s.sampleRate))]}.play +{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig,order:80,minSlice:2205)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 80)/ s.sampleRate))]}.play // more musical trans-trigged autopan ( { var sig, trig, syncd, pan; sig = PlayBuf.ar(1,b.bufnum,loop:1); - trig = FluidTransientSlice.ar(sig,order:10,debounce:2205); + trig = FluidTransientSlice.ar(sig,order:10,minSlice:4410); syncd = DelayN.ar(sig, 1, ((256 + 128 - 10)/ s.sampleRate)); pan = TRand.ar(-1,1,trig); Pan2.ar(syncd,pan); }.play ) :: -