In my local SuperCollider user’s group, one of our members requested this so I’m sharing with the community.
It’s derived from @tedmoore’s tutorial but traversing the KdTree using a synth instead of using a visual plot.
I’ve been hacking it adding filters and fx. If anyone wants to try a modification to the synth it could be a fun thing to try.
// the folder containing the corpus
~folder = FluidFilesPath();
// load into a buffer
~loader = FluidLoadFolder(~folder).play(s,{"done loading folder".postln;});
// sum to mono (if not mono)
(
if(~loader.buffer.numChannels > 1){
~src = Buffer(s);
~loader.buffer.numChannels.do{
arg chan_i;
FluidBufCompose.processBlocking(s,
~loader.buffer,
startChan:chan_i,
numChans:1,
gain:~loader.buffer.numChannels.reciprocal,
destination:~src,
destGain:1,
action:{"copied channel: %".format(chan_i).postln}
);
};
}{
"loader buffer is already mono".postln;
~src = ~loader.buffer;
};
)
// slice the buffer in non real-time
(
~indices = Buffer(s);
FluidBufOnsetSlice.processBlocking(s,~src,metric:9,threshold:0.05,indices:~indices,action:{
"found % slice points".format(~indices.numFrames).postln;
"average duration per slice: %".format(~src.duration / (~indices.numFrames+1)).postln;
});
)
// analysis
(
~analyses = FluidDataSet(s);
~indices.loadToFloatArray(action:{
arg fa;
var mfccs = Buffer(s);
var stats = Buffer(s);
var flat = Buffer(s);
var playback_info_dict = Dictionary.newFrom([
"cols",2,
"data",Dictionary.new;
]);
fa.doAdjacentPairs{
arg start, end, i;
var num = end - start;
// add playback info for this slice to this dict
playback_info_dict["data"][i] = [start,num];
FluidBufMFCC.processBlocking(s,~src,start,num,features:mfccs,numCoeffs:13,startCoeff:1);
FluidBufStats.processBlocking(s,mfccs,stats:stats,select:[\mean]);
FluidBufFlatten.processBlocking(s,stats,destination:flat);
~analyses.addPoint(i,flat);
"analyzing slice % / %".format(i+1,fa.size-1).postln;
if((i%100) == 99){s.sync;}
};
s.sync;
~ds_playback = FluidDataSet(s).load(playback_info_dict);
~analyses.print;
});
)
(
~umapped = FluidDataSet(s);
FluidUMAP(s,numNeighbours:15,minDist:0.9).fitTransform(~analyses,~umapped,action:{"umap done".postln});
)
// normalize
(
~normed = FluidDataSet(s);
FluidNormalize(s).fitTransform(~umapped,~normed);
)
// fit a kdtree
~tree = FluidKDTree(s).fit(~normed);
~point = Buffer.alloc(s,2);
~playbufinfo = Buffer.alloc(s,2);
(
a = {
var start, num;
var env, phs, sig, dursecs, trig;
var snd;
FluidKrToBuf.kr(LFSaw.kr([0.009,0.7],[1.0.rand,1.0.rand]).range(0,1), ~point);
trig = \t_trig.kr(0) + Impulse.kr(LFNoise0.ar(3).range(0.1,3.1));
~tree.kr(trig, ~point, ~playbufinfo, 1, ~ds_playback);
# start, num = FluidBufToKr.kr(~playbufinfo);
dursecs = num / BufSampleRate.ir(~src);
phs = Phasor.ar(0,BufRateScale.ir(~src),start,start+num);
env = EnvGen.ar(Env.asr(0.03, releaseTime: dursecs-0.03), trig);
sig = BufRd.ar(1,~src, phs) * env;
snd = GrainIn.ar(2, Dust.ar(100 * LFNoise0.ar(80).range(0.7,2.1)), 2, Mix.ar(sig), LFNoise0.ar(100).range(-1,1), -1, 512, 1);
snd = RLPF.ar(snd, LFTri.kr(1/32).exprange(800,15000), 0.8);
snd = Limiter.ar(snd, 0.8);
snd = LeakDC.ar(snd);
snd = snd * Lag3.kr(\amp.kr(0.7), 1);
snd = JPverb.ar(snd, 5, 0, 10) * 0.3 + snd;
snd * EnvGen.kr(Env.asr(attackTime: \attack.kr(0.7), sustainLevel: 1.0, releaseTime: \rel.kr(3)), gate: \gate.kr(1), doneAction: Done.none);
};
);