I just sold an sp1200. And it was so quick to make beats banging on the pads. Then you can change the time signature of a track at will and enter 64 th notes. For 64 th notes mixed with 16th notes or whatever, that has presented a huge challenge in supercollider cause it creates huge arrays if you do it brute force , and not some genius level of coding.
Here is the final one. Im just playing around with ideas for input control. If you use Super Clean. Its fun. Curious what other ideas people have. I’ll post this to sccode, Just for fun. This has buttons to add basic beats. And it works
// Initialize SuperClean if it doesn't exist
~clean = ~clean ?? { SuperClean(2, s) };
s.waitForBoot {
var window, sliders, buttons, patterns, pdefs;
var numTracks = 4;
var numSteps = 16;
var playing = false;
var currentStep = 0;
var stepIndicators;
// Initialize SuperClean if it doesn't exist
~clean = ~clean ?? { SuperClean(2, s) };
~clean.loadSoundFiles("/Users/ss/Documents/mmd");
s.sync;
"SuperClean initialized".postln;
// Initialize patterns and Pdefs
patterns = Array.fill(numTracks, { Array.fill(numSteps, 0) });
pdefs = numTracks.collect({ |i|
Pdef(("track" ++ i).asSymbol,
Pbind(*[
type: \cln,
snd: \mmd,
num: i % 4, // Ensure we only use existing samples (0 to 3)
dur: 0.15,
sustain: 1,
rel: 0.9,
amp: Pfunc {
var amp = patterns[i][currentStep];
amp
} * 1.5,
type: Pif(Pfunc { patterns[i][currentStep] == 0 }, \rest, \cln),
])
)
});
// Create window
window = Window("Drum Sequencer", Rect(100, 100, 581, 500)).front;
// Function to update pattern
~updatePattern = { |trackIndex|
patterns[trackIndex] = sliders[trackIndex].value.collect { |v|
if(v < 0.01) { 0 } { v } // Convert very small values to 0
};
};
// Create sliders
sliders = numTracks.collect({ |i|
MultiSliderView(window, Rect(10, 10 + (i * 100), 220, 60))
.size_(numSteps)
.value_(patterns[i])
.action_({ |sl|
~updatePattern.(i);
});
});
// Create step indicators
stepIndicators = numTracks.collect({ |i|
StaticText(window, Rect(10, 75 + (i * 100), 280, 20))
.string_("Current step: 0")
.align_(\center);
});
// Create erase buttons
buttons = numTracks.collect({ |i|
Button(window, Rect(10, 95 + (i * 100), 80, 20))
.states_([["Erase Track " ++ (i+1)]])
.action_({
patterns[i] = Array.fill(numSteps, 0);
sliders[i].value_(patterns[i]);
});
});
// Function to generate normalized Fibonacci sequence with variation
~generateNormalizedFibonacci = { |length|
var fib = [0, 1];
var maxVal;
(length - 2).do {
fib = fib.add(fib[fib.size - 1] + fib[fib.size - 2]);
};
maxVal = fib.maxItem;
fib = fib.normalize(0, 1);
fib = fib.collect { |val| (val * 0.8) + (0.2.rand) }; // Add variation
fib[0] = 0; // Ensure the first element stays 0
fib;
};
// Function to generate Euclidean rhythm with continuous values
~generateEuclideanRhythm = { |steps, pulses|
var pattern = Array.fill(steps, 0);
var bucket = 0;
steps.do { |i|
bucket = bucket + pulses;
if (bucket >= steps) {
bucket = bucket - steps;
pattern[i] = 1.0.rand; // Use random value between 0 and 1
};
};
pattern;
};
// Function to generate beat patterns
~generateBeatPattern = { |trackIndex, pattern|
var newPattern;
switch(pattern,
\house, { newPattern = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0].collect { |v| if(v == 1) { 0.7 + 0.3.rand } { 0 } } },
\alternating, { newPattern = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0].collect { |v| if(v == 1) { 0.7 + 0.3.rand } { 0 } } },
\hihat, { newPattern = { 0.7 + 0.3.rand }.dup(numSteps) },
\random, { newPattern = { 1.0.rand }.dup(numSteps) },
\fibonacci, { newPattern = ~generateNormalizedFibonacci.(numSteps) },
\euclidean, {
var pulses = (1..numSteps-1).choose;
newPattern = ~generateEuclideanRhythm.(numSteps, pulses);
}
);
patterns[trackIndex] = newPattern;
sliders[trackIndex].value_(newPattern);
};
// Create beat pattern buttons
[
["House", \house],
["Alter", \alternating],
["Hi-hat", \hihat],
["Random", \random],
["Fib", \fibonacci],
["Eucl", \euclidean]
].do({ |data, index|
numTracks.collect({ |trackIndex|
Button(window, Rect(300 + (index * 45), 10 + (trackIndex * 100), 40, 20))
.states_([[data[0]]])
.action_({
~generateBeatPattern.(trackIndex, data[1]);
});
});
});
// Function to print all patterns
~printAllPatterns = {
var code = "((\n";
numTracks.do({ |i|
code = code ++ " Pdef(\\track" ++ i ++ ",\n";
code = code ++ " Pbind(*[\n";
code = code ++ " type: \\cln,\n";
code = code ++ " snd: \\mmd,\n";
code = code ++ " num: " ++ (i % 4) ++ ",\n";
code = code ++ " dur: 0.15,\n";
code = code ++ " rel: 0.9,\n";
code = code ++ " sustain: 1,\n";
code = code ++ " amp: Pseq(" ++ patterns[i] ++ ", inf),\n";
code = code ++ " type: Pif(Pkey(\\amp) == 0, \\rest, \\cln),\n";
code = code ++ " ])\n";
code = code ++ " ).play(quant: 2.4);\n";
});
code = code ++ ").play;\n)";
code.postln;
};
// Add a "Print All Patterns" button
Button(window, Rect(10, 420, 150, 30))
.states_([["Print All Patterns"]])
.action_({ ~printAllPatterns.() });
// Add Play/Stop button
Button(window, Rect(170, 420, 80, 30))
.states_([["Play"], ["Stop"]])
.action_({ |btn|
if(btn.value == 1) {
playing = true;
currentStep = 0;
Routine({
while { playing } {
{
stepIndicators.do({ |indicator, i|
indicator.string_("Current step: " ++ currentStep);
});
}.defer;
pdefs.do(_.play(quant: 0.15));
0.15.wait;
currentStep = (currentStep + 1) % numSteps;
}
}).play(TempoClock.default);
"Started playing".postln;
} {
playing = false;
pdefs.do(_.stop);
"Stopped playing".postln;
};
});
// Initialize patterns
numTracks.do({ |i| ~updatePattern.(i) });
"Sequencer GUI created".postln;
};
)