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
3.9 KiB
Plaintext
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);
|
|
|