diff --git a/doc/BufStats.rst b/doc/BufStats.rst index e176cc4..1cfad35 100644 --- a/doc/BufStats.rst +++ b/doc/BufStats.rst @@ -1,71 +1,81 @@ -:digest: Computing Statistics on Buffers as Series. +:digest: Statistically summarise a time series in a Buffer :species: buffer-proc :sc-categories: Libraries>FluidDecomposition, UGens>Buffer :sc-related: Guides/FluidCorpusManipulationToolkit :see-also: BufLoudness, BufPitch, BufMelBands, BufMFCC, BufSpectralShape :description: Statistical analysis on buffer channels. :discussion: - Typically, a buffer would hold various time series (i.e. descriptors over time), and BufStats allows this series to be described statistically. - The process returns a buffer where each channel of the source buffer has been reduced to 7 statistics: mean, standard deviation, skewness, kurtosis, followed by 3 percentiles, by default the minimum value, the median, and the maximum value. Moreover, it is possible to request the same 7 stats to be applied to derivative of the input. These are useful to describe statistically the rate of change of the time series. The stats buffer will grow accordingly, yielding the seven same statistical description of the n requested derivatives. Therefore, the stats buffer will have as many channel as the input buffer, and as many frames as 7 times the requested numDerivs (stats of derivatives). + :fluid-obj:`BufStats` statistically summarises a time-series (or any values) that is in a buffer, returning seven statistics for each channel: the buffer channel's mean, standard deviation, skewness, kurtosis, low, middle, and high values. See the ``low``, ``middle`` and ``high`` parameters below for more description on these values. + + For a detailed explanation of :fluid-obj:`BufStats` features visit http://learn.flucoma.org/reference/bufstats. + + The ``stats`` output buffer of :fluid-obj:`BufStats` will have the same number of channels as the input buffer, each one containing the statistics of its corresponding channel in the input buffer. Because the dimension of time is summarised statistically, the frames in the ``stats`` buffer do not represent time as they normally would. The first seven frames in every channel of the ``stats`` buffer will have the seven statistics computed on the input buffer channel. After these first seven frames, there will be seven more frames for each derivative requested, each containing the seven statistical summaries for the corresponding derivative. + + For example if the input to :fluid-obj:`BufStats` is a three-channel buffer and ``numDerivs`` = 1 the output ``stats`` buffer would contain: + + ========= ============ ============= ============= ======== =========== ======== ================= ==================== ===================== ===================== ================ =================== ================ + ch 0 mean ch 0 std dev ch 0 skewness ch 0 kurtosis ch 0 min ch 0 median ch 0 max ch 0 deriv 1 mean ch 0 deriv 1 std dev ch 0 deriv 1 skewness ch 0 deriv 1 kurtosis ch 0 deriv 1 min ch 0 deriv 1 median ch 0 deriv 1 max + ch 1 mean ch 1 std dev ch 1 skewness ch 1 kurtosis ch 1 min ch 1 median ch 1 max ch 1 deriv 1 mean ch 1 deriv 1 std dev ch 1 deriv 1 skewness ch 1 deriv 1 kurtosis ch 1 deriv 1 min ch 1 deriv 1 median ch 1 deriv 1 max + ch 2 mean ch 2 std dev ch 2 skewness ch 2 kurtosis ch 2 min ch 2 median ch 2 max ch 2 deriv 1 mean ch 2 deriv 1 std dev ch 2 deriv 1 skewness ch 2 deriv 1 kurtosis ch 2 deriv 1 min ch 2 deriv 1 median ch 2 deriv 1 max + ========= ============ ============= ============= ======== =========== ======== ================= ==================== ===================== ===================== ================ =================== ================ -:process: This is the method that calls for the slicing to be calculated on a given source buffer. -:output: Nothing, as the destination buffer is declared in the function call. +:process: This is the method that calls for the statistical analysis to be calculated on ``source``. +:output: Nothing, as the ``stats`` buffer is declared in the function call. :control source: - The index of the buffer to use as the source material to be processed. The different channels of multichannel buffers will be considered independently as time series. + The buffer to statistically summarise. Each channel of multichannel buffers will be computed independently. :control startFrame: - The starting point (in samples) from which to copy in the source buffer. + The position (in frames) to begin the statistical analysis. :fluid-obj:`BufStats` is unaware of what kind of time-series is in ``source`` and what the sample rate might be (whether it is audio samples or audio descriptors). It will begin analysis at the indicated frame index in ``source``. The default is 0. :control numFrames: - The duration (in samples) to copy from the source buffer. The default (-1) copies the full lenght of the buffer. + The number of frames to use in the statistical analysis. The default of -1 indicates to use all the frames from ``startFrame`` through the end of the ``source`` buffer. :control startChan: - The first channel from which to copy in the source buffer. + The channel from which to begin computing statistics for. The default is 0. :control numChans: - The number of channels from which to copy in the source buffer. This parameter will wrap around the number of channels in the source buffer. The default (-1) copies all of the buffer's channel. + The number of channels to compute statistics for. The default of -1 indicates to compute statistics through the last channel in the ``source`` buffer. :control stats: - The index of the buffer to write the statistics to. Each channel is the fruit of the statistical computations on the same channel number of the source buffer. + The buffer to write the statistical summary into. :control numDerivs: - The number of derivatives of the original time series for the statistic to be computed on. By default, none are computed. This will influence the number of frames the stats buffer will have. + The number of derivatives of the original time-series to compute statistics on. The default of 0 will compute statistics on no derivates, only the original time-series itself. Setting this parameter > 0 (maximum of 2) will return the same seven statistics computed on consecutive derivatives of the channel's time-series. (``numDerivs`` = 1 will return the channel's statistics and the statistics of the first derivative, ``numDerivs`` = 2 will return the channel's statistics and the statistics of the first and second derivatives.) The derivative statistics are useful to describe the rate of change of the time series. :control low: - The components requested for the first percentile value. By default, it is percentile 0.0, which is the minimum of the given channel of the source buffer. + The value at this percentile (indicated as 0.0-100.0) will be written into frame 4 (zero-counting). By default, it is percentile 0.0, which is the minimum value of the channel. :control middle: - The components requested for the second percentile value. By default, it is percentile 50.0, which is the median of the given channel of the source buffer. + The value at this percentile (indicated as 0.0-100.0) will be written into frame 5 (zero-counting). By default, it is percentile 50.0, which is the median value of the channel. :control high: - The components requested for the third percentile value. By default, it is percentile 100.0, which is the maximum of the given channel of the source buffer. + The value at this percentile (indicated as 0.0-100.0) will be written into frame 6 (zero-counting). By default, it is percentile 100.0, which is the maximum value of the channel. :control outliersCutoff: - A ratio of the inter quantile range (IQR) that defines a range outside of which data will be rejected. It is run on each channel independently and a single channel being flagged as outlier removes the whole frame (on all channels). The default (-1) bypasses this function, keeping all frames in the statistical measurements. For more information on this statistical process, please refer to the concept of IQR and how the whiskers of a box plot are computed here (https://en.wikipedia.org/wiki/Box_plot) + A ratio of the inter quantile range (IQR) that defines a range from the median, outside of which data will be considered an outlier and not used to compute the statistical summary. For each frame, if a single value in any channel of that frame is considered an outlier (when compared to the rest of the values in it's channel), the whole frame (on all channels) will not be used for statistical calculations. The default of -1 bypasses this function, keeping all frames in the statistical measurements. :control weights: - A buffer to provide relative weighting of the source material. Not providing one will not apply weighting and consider all frames equally. The provided buffer has to satisfy all of the following conditions: + A buffer to provide relative weighting of each frame in the ``source`` buffer when computing the statistics. Not providing a ``weights`` buffer will cause all the frames to be considered equally. This may be useful for weighting certain descriptors by the value of other descriptors (such as the loudness or pitch confidence of the sound). The provided buffer has to satisfy all of the following conditions: - * a single-channel, that will be applied to all channels of source; - * exactly the same amount of frames as ‘source’; - * weights must be positive (anything lower than 0 will be rejected). + * a single-channel + * exactly the same amount of frames as ``source`` + * all values must be positive (anything lower than 0 will be rejected) :control 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 stats as an argument. - + A Function to be evaluated once the offline process has finished and all the |buffer| variables have been updated on the client side. The function will be passed ``stats`` as an argument. diff --git a/example-code/sc/BufStats.scd b/example-code/sc/BufStats.scd index ba32d8a..955caaa 100644 --- a/example-code/sc/BufStats.scd +++ b/example-code/sc/BufStats.scd @@ -1,8 +1,211 @@ +CODE:: -STRONG::A didactic example:: +~src = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav")); +//split in various chunks, collecting the indices in an array +( +~indices = Buffer.new(s); +FluidBufOnsetSlice.processBlocking(s,~src,threshold:5,indices:~indices); // find slice points in src buffer +~indices.loadToFloatArray(action:{ + arg fa; + var features = Buffer(s); + var stats = Buffer(s); + var flat = Buffer(s); + ~ds = FluidDataSet(s); + fa.doAdjacentPairs{ // take in turn, the start and stop point of every slice + arg start, end, i; + FluidBufMFCC.processBlocking(s,~src,start,end-start,features:features); // doo the mfcc analysis + FluidBufStats.processBlocking(s,features,stats:stats); // get the statistical summary of the mfcc analysis + FluidBufFlatten.processBlocking(s,stats,startFrame:5,numFrames:1,destination:flat); // copy out the median mfcc values into a flattened buffer + ~ds.addPoint("slice-%".format(i),flat); // add those values to the dataset + }; + ~ds.print; +}); +) + +:: +STRONG::Derivative:: CODE:: +// this example sorts some tones by the derivative of the pitch analysis +// this provides a sorted order of the tones with the tones that "glissando down" on one end +// and the tones that "glissando up" on the other end + +// we'll record into this buffer +~src = Buffer.alloc(s,s.sampleRate*10); + +( +// a simple random sliding bell synth, +// let this play for all 10 seconds, as it's recording into a buffer! +{ + var trig = Impulse.ar(1.5); + var freq = Lag.ar(TRand.ar(trig: trig),TRand.ar(0.5, trig: trig)).exprange(333,666); + var mul = Decay.ar(trig * TRand.ar(0.1,10,trig),TRand.ar(0.5,1.1,trig)); + var sig = SinOsc.ar(freq,mul:mul).atan * 0.1; + var env = EnvGen.kr(Env([0,1,1,0],[0.03,~src.duration-0.06,0.03])); + RecordBuf.ar(sig,~src,loop:0,doneAction:2); + sig; +}.play; +) + +//split in various chunks, collecting the indices in an array +( +~indices = Buffer.new(s); +FluidBufOnsetSlice.processBlocking(s,~src,threshold:0.01,indices:~indices,action:{ + ~indices.loadToFloatArray(action:{ + arg array; + ~indices_array = array.add(~src.numFrames); + "found % slice points: ".format(~indices_array.size).post; + ~indices_array.postln; + }); +}); +) + +// analyze them for pitch including the first derivative, save the median of the 1st derivative +( +~pitch_analysis = Buffer(s); +~stats = Buffer(s); +~deriv_1_medians = Buffer(s); +~indices_array.doAdjacentPairs{ + arg start, end, i; // get the start and end of each slice + + // analyze that slice for pitch + FluidBufPitch.processBlocking(s,~src,start,end-start,features:~pitch_analysis); + + // get the stats of the analysis and the stats of the derivative (only channel 1 though, not pitch conf) + FluidBufStats.processBlocking(s,~pitch_analysis,numChans:1,stats:~stats,numDerivs:1); + + // write the median of the derivative (index 12) into the derivative medians buffer + FluidBufCompose.processBlocking(s,~stats,startFrame:12,numFrames:1,destination:~deriv_1_medians,destStartFrame:i); +}; +~deriv_1_medians.loadToFloatArray(action:{ + arg fa; + "here are the % median values of derivative 1 of the pitch analysis".format(fa.size).postln; + fa.postln; + ~sorted_indices = fa.order; // the the indexes in a sorted order + ~sorted_indices.postln; +}); +) + +//play in loop the slice in order of pitch direction (the median of the slice's pitch variation) - mouse on the left should be descending, in the middle should be more stable, and it should be ascending on the right. +( +Buffer.loadCollection(s,~sorted_indices,action:{ + arg buf; + { + var which = MouseX.kr(0,BufFrames.kr(buf)-1).floor; + var index = Index.kr(buf,which); + var start = BufRd.kr(1,~indices,index,0,1); + var end = BufRd.kr(1,~indices,index+1,0,1); + var phs = Phasor.ar(0,1,start,end); + BufRd.ar(1,~src,phs); + }.play; +};) +) + +:: +STRONG::Weights:: +CODE:: +// consider trying to extract the pitch from this recording +~src = Buffer.read(s,FluidFilesPath("Tremblay-ASWINE-ScratchySynth-M.wav"),0,44100 * 6); + +~src.play; + +// a look at the pitch analysis shows a quite eratic time series +( +~pitch_analysis = Buffer(s); +FluidBufPitch.processBlocking(s,~src,features:~pitch_analysis); +FluidWaveform(~src,featuresBuffer:~pitch_analysis,stackFeatures:true,bounds:Rect(0,0,1600,400)); +) + +// if one were to take the average pitch from this time series it doesn't sound right; +( +~stats = Buffer(s); +FluidBufStats.processBlocking(s,~pitch_analysis,stats:~stats,numChans:1); +{ + var sig = SinOsc.ar(BufRd.kr(1,~stats,0),0,-20.dbamp); + var srcsig = PlayBuf.ar(1,~src,BufRateScale.ir(~src)); + var env = EnvGen.kr(Env([0,1,1,0],[0.03,~src.duration-0.06,0.03]),doneAction:2); + sig = sig + srcsig; + sig = sig * env; + sig.dup; +}.play; +) + +( +// we'll use the pitch confidence to weight the statistical analysis so our mean will be a weighted mean based on how confident the pitch algorithm is in the pitch it is returning; +~conf = Buffer(s); +FluidBufCompose.processBlocking(s,~pitch_analysis,startChan:1,numChans:1,destination:~conf); +FluidBufStats.processBlocking(s,~pitch_analysis,stats:~stats,weights:~conf); +{ + var sig = SinOsc.ar(BufRd.kr(1,~stats,0),0,-20.dbamp); + var srcsig = PlayBuf.ar(1,~src,BufRateScale.ir(~src)); + var env = EnvGen.kr(Env([0,1,1,0],[0.03,~src.duration-0.06,0.03]),doneAction:2); + sig = sig + srcsig; + sig = sig * env; + sig.dup; +}.play; +) + +// now it's too low, how about we threshold the conf +( +~threshed = Buffer(s); +FluidBufThresh.processBlocking(s,~conf,destination:~threshed,threshold:0.97); +FluidBufStats.processBlocking(s,~pitch_analysis,stats:~stats,weights:~threshed); +{ + var sig = SinOsc.ar(BufRd.kr(1,~stats,0),0,-20.dbamp); + var srcsig = PlayBuf.ar(1,~src,BufRateScale.ir(~src)); + var env = EnvGen.kr(Env([0,1,1,0],[0.03,~src.duration-0.06,0.03]),doneAction:2); + sig = sig + srcsig; + sig = sig * env; + sig.dup; +}.play; +) +:: +STRONG::Outliers: +CODE:: + +// let's try to find the pitch of this trombone tone +~src = Buffer.read(s,FluidFilesPath("Olencki-TenTromboneLongTones-M.wav"),numFrames:44100*3.3); + +~src.play; + +// do the pitch analysis +( +~pitch_analysis = Buffer(s); +FluidBufPitch.processBlocking(s,~src,features:~pitch_analysis); +) + +// find the mean frequency... seems quite wrong, perhaps because there is some silence in the sound file with erroneous pitch values +( +~stats = Buffer(s); +FluidBufStats.processBlocking(s,~pitch_analysis,stats:~stats,numChans:1); +{ + var sig = LFTri.ar(BufRd.kr(1,~stats,0),0,-20.dbamp); + var srcsig = PlayBuf.ar(1,~src,BufRateScale.ir(~src)); + var env = EnvGen.kr(Env([0,1,1,0],[0.03,~src.duration-0.06,0.03]),doneAction:2); + sig = sig + srcsig; + sig = sig * env; + sig.dup; +}.play; +) + +( +// using outliersCutoff, we can first remove any outliers, then take the mean pitch value...much more accurate! +~stats = Buffer(s); +FluidBufStats.processBlocking(s,~pitch_analysis,stats:~stats,outliersCutoff:1,numChans:1); +{ + var sig = LFTri.ar(BufRd.kr(1,~stats,0),0,-20.dbamp); + var srcsig = PlayBuf.ar(1,~src,BufRateScale.ir(~src)); + var env = EnvGen.kr(Env([0,1,1,0],[0.03,~src.duration-0.06,0.03]),doneAction:2); + sig = sig + srcsig; + sig = sig * env; + sig.dup; +}.play; +) + +:: +STRONG::A didactic example:: +CODE:: // make a buffer of known length b = Buffer.alloc(s,101); @@ -15,9 +218,9 @@ c = Buffer.new(s); //run the process on them ( Routine{ - t = Main.elapsedTime; - FluidBufStats.process(s, b, stats:c, numDerivs:1).wait; - (Main.elapsedTime - t).postln; + t = Main.elapsedTime; + FluidBufStats.process(s, b, stats:c, numDerivs:1).wait; + (Main.elapsedTime - t).postln; }.play ) @@ -50,167 +253,4 @@ b.plot // run the process and read the values FluidBufStats.process(s, b, stats:c, numDerivs:1, action:{c.getn(0,c.numFrames,{|item|item.postln;})}); -:: - -STRONG::A musical example:: - -CODE:: -// create some buffers -( -// a simple random sliding bell synth -b = { - var trig = Impulse.ar(1.5); - SinOsc.ar( - Lag.ar(TRand.ar(trig: trig), - TRand.ar(0.5, trig: trig)).exprange(333,666), - mul: Decay.ar( - trig * TRand.ar(0.1,10,trig), - TRand.ar(0.5,1.1,trig) - ) - ).atan * 0.1; -}.asBuffer(20); -c = Buffer.new(s); -d = Buffer.new(s); -i = Buffer.new(s); -) - -//play the source -b.play; - -//split in various chunks, collecting the indices in an array -FluidBufOnsetSlice.process(s,b, threshold: 0.01, indices: c, action:{c.loadToFloatArray(action: {|array| e = array.add(b.numFrames);e.size.postln;e.postln;})}); - -//describe the whole input too, here using pitch, and collecting the values in an array, dismissing the (interleaved) confidence. -FluidBufPitch.process(s,b,features:d, windowSize: 4096, hopSize: 512, padding:2, action:{d.loadToFloatArray(action: {|array| f = array.unlace(2)[0]; f.postln;})}); - -// iterate through each slice, taking the median of the first derivative of the pitch of each -( -g= Array.new; -Routine({ - var nb = e.size; - e.doAdjacentPairs({ - arg start,end; - FluidBufStats.processBlocking(s,d,(start/512).asInteger,((end-start)/512).asInteger + 3,0,1,i,1, action: { - i.loadToFloatArray( action: { - arg array; - g = g.add(array[12]); - "% % %\n".postf((start/512).asInteger,((end-start)/512).asInteger + 3, array[12]);//adding some of the overlap but not more to not capture too much of the next attack - nb = nb - 1; - if (nb == 1, {"Done".postln;});//check if we've done all the pairs - }) - }).wait; - }); -}).play; -) - -//play in loop the slice in order of pitch direction (the median of the slice's pitch variation) - mouse on the left should be descending, in the middle should be more stable, and it should be ascending on the right. - -( -Buffer.sendCollection(s,g.order,action: {|x| { - var which = BufRd.kr(1, x, MouseX.kr(0, BufFrames.kr(x) - 1), 0, 1); - BufRd.ar(1, b, - Phasor.ar(0,1, - BufRd.kr(1,c,which,0,1), - BufRd.kr(1,c,which + 1,0,1), - BufRd.kr(1,c,which,0,1))); - }.play; - };) -) - -:: - - -STRONG::Stereo Input Behaviour:: - -CODE:: -// make a buffer of known lenght -b = Buffer.alloc(s,101,2); - -// add known values - here, a ramp up on the left and negative random values on the right -b.setn(0, Array.fill(101,{|i|[i / 100,-1.0.rand]}).flat); - -// plot to confirm -b.plot.plotMode_(\points) - -// create a new buffer as destinations -c = Buffer.new(s); - -// run the stats and send back the values -FluidBufStats.process(s, b, stats:c, numDerivs:1, action:{c.getn(0,c.numFrames * c.numChannels,{|item|d = item; d.postln})}); - -//looking at the result is not easy to grasp, since it is interleaved: first number is mean of L, second is mean of R, third is stddev of L, fourth is stddev or R -//this will make it tidier - the first value of each line is Left, the second is Right -d.reshape(14,2).do({|x,i|["mean\t\t","stddev\t\t","skew\t\t", "kurtosis\t\t", "min\t\t\t", "median\t\t", "max\t\t\t","d-mean\t","d-stddev\t","d-skew\t\t", "d-kurtosis", "d-min\t\t", "d-median\t", "d-max\t\t"].at(i).post;x.round(0.01).postln});"".postln; -:: - -STRONG::Outliers and Weights:: - -CODE:: -// example 1a -// make a buffer of known qualities -b = Buffer.loadCollection(s,[1, 8, 9, 10, 11, 12, 99]); - -// plot to confirm -b.plot.plotMode = \points; - -// create a new buffer as destinations -c = Buffer.new(s); - -// run the stats and send back the values -FluidBufStats.process(s, b, stats:c, numDerivs:1, action:{c.getn(0,c.numFrames,{|item|item.postln})}); -// run the same array with outliers rejected if outside of 1.5 times the IQR - observe the new minimum and maximum to see -FluidBufStats.process(s, b, stats:c, numDerivs:1, outliersCutoff: 1.5, action:{c.getn(0,c.numFrames,{|item| item.postln})}); - -// example 1b (run the stats above, and change the value of some elements in the array too) -b = Buffer.loadCollection(s,[1, 8, 9, 10, 11, 12, 16, 99].scramble); - -// example 1c (multichannel in behaviour is greedy) -// This mean that an outlier in any channel will dismiss the whole frame. -// For instance here the outlier is 99 (frame 8) in channel 0, and 1001 in channel 1 (frame 0) -// The final stats therefore has minima of [2,10002] and maxima of [8,10008] -e = [(1..8)++99, [1001] ++ 10002.series(10003,10009)].flop.scramble.flat -b = Buffer.loadCollection(s,e,2); -FluidBufStats.process(s, b, stats:c, numDerivs:1, outliersCutoff: 1.5, action:{c.getn(0,c.numFrames * c.numChannels,{|item| f =item.postln})}); - -//More readable format -f.reshape(14,2).do({|x,i|["mean\t\t","stddev\t\t","skew\t\t\t", "kurtosis\t", "min\t\t\t", "median\t\t", "max\t\t\t","d-mean\t","d-stddev\t","d-skew\t\t", "d-kurtosis", "d-min\t\t", "d-median\t", "d-max\t\t"].at(i).post;x.round(0.01).postln});"".postln; - -////////////// -// example 2a - -// make an array of 9 values, with known weigths. Scramble them pairwise for good measure (that should not change any stats) -e = [(1..9), 1.0.series(0.9,0.2)].flop.scramble.flop; -b = Buffer.loadCollection(s,e[0]); -c = Buffer.loadCollection(s,e[1]); -d = Buffer.new(s); -// run the stats and send back the values -FluidBufStats.process(s, b, stats:d, numDerivs:1, action:{d.getn(0,d.numFrames,{|item|item.postln})}); -// run the same array with the weights -FluidBufStats.process(s, b, stats:d, numDerivs:1, weights: c, action:{d.getn(0,d.numFrames * d.numChannels,{|item|item.postln})}); - -// example 2b -e = [(1..9), 0.series(-10,-80)].flop.scramble.flop; -b = Buffer.loadCollection(s,e[0]); -c = Buffer.loadCollection(s,e[1]); -FluidBufStats.process(s, b, stats:d, numDerivs:1, weights: c, action:{d.getn(0,d.numFrames * d.numChannels,{|item|item.postln})}); -// this has only negative weights, so it bails out, outputing all 0s. It also publishes a warning if the server options on verbosity are on. -// but if we scale them up -g = Buffer(s) -FluidBufScale.process(s,c,destination: g,inputLow: -100,inputHigh: 0) -// look at the new values - because 0->1 and -100->0 we get the same weights as example 2a -g.getn(0,9,{|x|x.postln}) -// run the stats - same results as example 2a -FluidBufStats.process(s, b, stats:d, numDerivs:1, weights: g, action:{d.getn(0,d.numFrames * d.numChannels,{|item|item.postln})}); - -//example 2c (stereo input but mono weigths - works like a charm) -e = [(1..9), (101..109), 1.0.series(0.9,0.2)].flop.scramble.flop; -b = Buffer.loadCollection(s,e[0..1].flop.flat,2); -b.plot(separately: true).plotMode = \points; -c = Buffer.loadCollection(s,e[2]); -FluidBufStats.process(s, b, stats:d, numDerivs:1, weights: c, action:{d.getn(0,d.numFrames * d.numChannels,{|item|f = item.postln})}); - -//More readable format -f.reshape(14,2).do({|x,i|["mean\t\t","stddev\t\t","skew\t\t\t", "kurtosis\t", "min\t\t\t", "median\t\t", "max\t\t\t","d-mean\t","d-stddev\t","d-skew\t\t", "d-kurtosis", "d-min\t\t", "d-median\t", "d-max\t\t"].at(i).post;x.round(0.01).postln});"".postln; - -//see the example folder for 2 musical comparisons: 1) weighted MFCCs providing different nearest neighbours, and 2) pitch manipulations -:: +:: \ No newline at end of file