Despite the fact that I knew the test was for remembering quantization boundaries after tempo changes when sooperlooper is JACK timebase master, I proceeded to write it as if I was testing sooperlooper’s performance with the loop stretching. Haha, sometimes brains just won’t get unstuck (well, mine at least).

This code will eventually not be one test, but I’m writing it all in one blob at first just to get my thoughts out and to experiment. I’ve had a lot of fun writing this, and just like I predicted it has really forced me to learn some of the the boring how-to-glue-it-together parts of supercollider instead of pasting code for weird noises in and then playing my guitar for an hour.

I still intend on a blog post describing the exact thing I’m trying to fix in sooperlooper, and how I intend to fix it. Incidentally, I found a couple other interesting little bugs to fix when there’s time. You can’t set the tap tempo over OSC. I found a place where it had been commented out (can’t remember where), and when I uncommented and recompiled, sooperlooper responded to the OSC tap-tempo message with a segfault. I sense somebody’s been down this road before… 😀

No matter. I used MIDI instead for now and put that on my list for later.

Here’s what I have so far:

// 1. Start an instance of the sooperlooper engine
// 2. create 8 loops
// 3. set playback sync, sync, and timestretch on all loops
// 4. make sooperlooper timebase master
// 5. set the tempo TODO
// load a soundfile into each loop AND/OR record a pattern into each loop
// -- this will be a simple bleep or bloop, something that can be detected
// -- or a breakbeat.
// TESTING LOOP STRETCH (no reason, it works perfectly. What's up with my brain?):
// send tap-tempo signal to change up the tempo a bunch.
// listen to test events and record whether they happened at the time expected. Try to do this
// automatically, signal detection.
// It'll probably work just fine.
// After some tempo changes, try to record or overdub. While doing so, query
// the loop states over OSC. See if they change when we took the action or if
// they got stuck or mistimed. (I remember, they used to get stuck). okay,
// how do I test when it records? OSC docs.

//################# SETUP ###############
~engine ="localhost", 9951);

"sooperlooper &".systemCmd();

SystemClock.sched(2.0, {{~engine.sendMsg("/loop_add", 1,1)});

// Global settings
~engine.sendMsg("/set", "sync_source", -1);
~engine.sendMsg("/set", "smart_eighths", 0);
~engine.sendMsg("/set", "jack_timebase_master", 1);
~engine.sendMsg("/add_midi_binding", "0 on 55 set tap_tempo -2 0 1 norm 0 127");

~engine_midi = MIDIOut.findPort("sooperlooper-sooperlooper", "sooperlooper-sooperlooper");

for (0,7, {arg i; ~engine.sendMsg(format("/sl/%/set", i), "sync", 1)});
for (0,7, {arg i; ~engine.sendMsg(format("/sl/%/set", i), "playback_sync", 1)});
for (0,7, {arg i; ~engine.sendMsg(format("/sl/%/set", i), "tempo_stretch", 1)});
for (0,7, {arg i; ~engine.sendMsg(format("/sl/%/set", i), "relative_sync", 1)});
for (0,7, {arg i; ~engine.sendMsg(format("/sl/%/set", i), "quantize", 2)});
for (0,7, {arg i; ~engine.sendMsg(format("/sl/%/set", i), "mute_quantized", 1)});
for (0,7, {arg i; ~engine.sendMsg(format("/sl/%/set", i), "overdub_quantized", 1)});


~player = Routine({

// No idea why 0.exit() works or whether it's the best convention, but this allows
// me to just run this code like a script and not have the sclang interpreter
// just hanging around

SystemClock.sched(5.0, {"pkill sooperlooper".systemCmd(); 0.exit(); nil;});