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.
scd/wavesets-patch.scd

131 lines
3.9 KiB
Plaintext

(
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);