( SynthDef(\wavesetPlayer, { |out=0, bufnum, rate=1, amp=0.3, pan=0| var sig = PlayBuf.ar(1, bufnum, rate * BufRateScale.kr(bufnum), doneAction: 2); sig = sig * amp; Out.ar(out, Pan2.ar(sig, pan)); }).add; ) ( ~processWavesets = { |w, minLength=300, weightLength=5, numClusters=10, groupSize=3| var lens, amps, validIndices; var lensVec, ampsVec, normLens, normAmps, features; var kmeans, results, centers, assignments; var reps; // Extract lengths and amplitudes lens = w.lengths; amps = w.amps; // Find indices of wavesets >= minLength validIndices = (0..(lens.size - 1)).select { |i| lens[i] >= minLength }; if (validIndices.isEmpty) { "No wavesets found above minLength!".postln; ^nil; }; // Gather features for valid wavesets lensVec = validIndices.collect { |i| lens[i] }; ampsVec = validIndices.collect { |i| amps[i] }; // Normalize features to 0..1 range normLens = lensVec.normalize(0, 1); normAmps = ampsVec.normalize(0, 1); // Weight length feature features = Array.new(validIndices.size); validIndices.size.do { |i| features[i] = [normLens[i] * weightLength, normAmps[i]]; }; // Define k-means clustering function kmeans = { |data, k, maxIter=100| var centers, assignments, changed; // Initialize centers randomly centers = data.chooseN(k); assignments = Array.new(data.size, -1); maxIter.do { changed = false; data.size.do { |i| var distances = centers.collect { |c| (c - data[i]).norm }; var minIndex = distances.indexOfMin; if (assignments[i] != minIndex) { assignments[i] = minIndex; changed = true; }; }; if (changed.not) { ^[centers, assignments] }; // Update centers centers = (0..(k - 1)).collect { |cid| var clusterPoints = data.indices.select { |i| assignments[i] == cid }.collect { |i| data[i] }; if (clusterPoints.isEmpty) { data.choose; } { clusterPoints.reduce({ |a, b| a + b }) / clusterPoints.size; }; }; }; [centers, assignments] }; // Run clustering results = kmeans.(features, numClusters); centers = results[0]; assignments = results[1]; // Find representative waveset per cluster (closest to centroid) reps = Dictionary.new; numClusters.do { |cid| var clusterIndices = validIndices.select({ |vi, idx| assignments[idx] == cid }); if (clusterIndices.notEmpty) { var clusterFeatures = clusterIndices.collect { |i| features[validIndices.indexOf(i)] }; var center = centers[cid]; var distances = clusterFeatures.collect { |f| (f - center).norm }; var minIdx = distances.indexOfMin; reps[cid] = clusterIndices[minIdx]; }; }; // Playback routine fork { var total = validIndices.size; var pos = 0; var localGroupSize = groupSize; while { pos < total } { var clusterID, repIndex; // Adjust groupSize if near end if (pos + localGroupSize > total) { localGroupSize = total - pos; }; clusterID = assignments[pos]; repIndex = reps[clusterID] ?? validIndices[pos]; // fallback to original // Play groupSize wavesets starting at representative var ev = w.eventFor(repIndex, localGroupSize, 1, 1); ev.put(\pan, rrand(-0.5, 0.5)); ev.play; ev.sustain.wait; pos = pos + localGroupSize; }; }; }; ) // Usage example with your Wavesets instance `w` // Adjust parameters as you like ~processWavesets.(w, minLength: 400, weightLength: 5, numClusters: 12, groupSize: 4);