You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

131 lines
7.0 KiB
Plaintext

(
Window.closeAll;
s.waitForBoot{
Task{
var buf = Buffer.read(s,File.realpath(FluidBufPitch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav");
var slicepoints = Buffer(s); // FluidBufAmpSlice will write into this buffer the samples at which slices are detected.
var features_buf = Buffer(s); // a buffer for writing the analysis from FluidSpectralShape into
var stats_buf = Buffer(s); // a buffer for writing the statistic analyses into
var point_buf = Buffer(s,2); // a buffer that will be used to add points to the dataset - the analyses will be written into this buffer first
var ds = FluidDataSet(s); // a data set for storing the analysis of each slice (mean centroid & mean loudness)
var scaler = FluidNormalize(s); // a tool for normalizing a dataset (making it all range between zero and one)
var kdtree = FluidKDTree(s); // a kdtree for fast nearest neighbour lookup
s.sync;
FluidBufAmpSlice.process(s,buf,indices:slicepoints,fastRampUp:10,fastRampDown:2205,slowRampUp:4410,slowRampDown:4410,onThreshold:10,offThreshold:5,floor:-40,minSliceLength:4410,highPassFreq:20).wait;
// slice the drums buffer based on amplitude
// the samples at which slices are detected will be written into the "slicepoints" buffer
FluidWaveform(buf,slicepoints,Rect(0,0,1600,400));
// plot the drums buffer with the slicepoints overlayed
slicepoints.loadToFloatArray(action:{ // bring the values in the slicepoints buffer from the server to the language as a float array
arg slicepoints_fa; // fa stands for float array
slicepoints_fa.postln;
slicepoints_fa.doAdjacentPairs{
/*
take each of the adjacent pairs and pass them to this function as an array of 2 values
nb. for example [0,1,2,3,4] will execute this function 4 times, passing these 2 value arrays:
[0,1]
[1,2]
[2,3]
[3,4]
this will give us each slice point *and* the next slice point so that we
can tell the analyzers where to start analyzing and how many frames to analyze
*/
arg start_samps, end_samps, slice_i;
var num_samps = end_samps - start_samps; // the next slice point minus the current one will give us the difference how many slices to analyze)
slice_i.postln; // post which slice index we're currently analyzing
// the ".wait"s will pause the Task (that this whole things is in) until the analysis is done;
FluidBufSpectralShape.process(s,buf,start_samps,num_samps,features:features_buf).wait;
/* analyze the drum buffer starting at `start_samps` and for `num_samps` samples
this returns a buffer (feautres_buf) that is 7 channels wide (for the 7 spectral analyses, see helpfile) and
however many frames long as there are fft frames in the slice */
FluidBufStats.process(s,features_buf,numChans:1,stats:stats_buf).wait;
/* perform a statistical analysis the spectral analysis, doing only the first channel (specified by `numChans:1`)
this will return just one channel because we asked it to analyze only 1 channel. that one channel will have 7 frames
corresponding to the 7 statistical analyses that it performs */
FluidBufCompose.process(s,stats_buf,0,1,destination:point_buf,destStartFrame:0).wait;
/* FluidBufCompose is essentially a "buf copy" operation. this will copy just the zeroth frame from `stats_buf` (mean)
into the zeroth buf of `point_buf` which is what we'll evenutally use to add the data to the dataset */
FluidBufLoudness.process(s,buf,start_samps,num_samps,features:features_buf).wait;
// do a loudness analysis
FluidBufStats.process(s,features_buf,numChans:1,stats:stats_buf).wait;
// see above
FluidBufCompose.process(s,stats_buf,0,1,destination:point_buf,destStartFrame:1).wait;
/* see above, but this time the mean loudnessi s being copied into the 1st frame of `point_buf` so that it doesn't overwrite the mean centroid */
ds.addPoint("point-%".format(slice_i),point_buf);
/* now that we've added the mean centroid and mean loudness into `point_buf`, we can use that buf to add the data that is in it to the dataset.
we also need to give it an identifer. here we're calling it "point-%", where the "%" is replaced by the index of the slice */
s.sync;
};
});
scaler.fitTransform(ds,ds,{
/* scale the dataset so that each dimension is scaled to between 0 and 1. this will do this operation "in place", so that once the
scaling is done on the dataset "ds" it will overwrite that dataset with the normalized values. that is why both the "sourceDataSet" and
"destDataSet" are the same here
*/
kdtree.fit(ds,{ // fit the kdtree to the (now) normalized dataset
ds.dump({ // dump out that dataset to dictionary so that we can use it with the plotter!
arg ds_dict;// the dictionary version of this dataset
var previous = nil; // a variable for checking if the currently passed nearest neighbour is the same or different from the previous one
FluidPlotter(bounds:Rect(0,0,800,800),dict:ds_dict,mouseMoveAction:{
/* make a FluidPlotter. nb. the dict is the dict from a FluidDataSet.dump. the mouseMoveAction is a callback function that is called
anytime the mouseDownAction or mouseMoveAction function is called on this view. i.e., anytime you click or drag on this plotter */
arg view, x, y, modifiers;
/* the function is passed:
(1) itself
(2) mouse x position (scaled to what the view's scales are)
(3) mouse y position (scaled to what the view's scales are)
(4) modifier keys that are pressed while clicking or dragging
*/
point_buf.setn(0,[x,y]); // write the x y position into a buffer so that we can use it to...
kdtree.kNearest(point_buf,{ // look up the nearest slice to that x y position
arg nearest; // this is reported back as a symbol, so...
nearest = nearest.asString; // we'll convert it to a string here
if(nearest != previous,{
/* if it's not the last one that was found, we can do something with it. this
is kind of like a debounce. we just don't want to retrigger this action each time a drag
happens if it is actually the same nearest neighbor*/
var index = nearest.split($-)[1].interpret;
// split at the hyphen and interpret the integer on the end to find out what slice index it is
{
var startPos = Index.kr(slicepoints,index); // look up the start sample based on the index
var endPos = Index.kr(slicepoints,index + 1); // look up the end sample based on the index
var dur_secs = (endPos - startPos) / BufSampleRate.ir(buf); // figure out how long it is in seconds to create an envelope
var env = EnvGen.kr(Env([0,1,1,0],[0.03,dur_secs-0.06,0.03]),doneAction:2);
var sig = PlayBuf.ar(1,buf,BufRateScale.ir(buf),startPos:startPos);
sig.dup * env;
}.play; // play it!
view.highlight_(nearest); // make this point a little bit bigger in the plot
previous = nearest;
});
});
});
});
});
});
}.play(AppClock);
}
)