Translating Eli Fieldsteel’s SC tutorial code into Overtone, Part 1

As part of a project that has been torturing me with its lack of existence since 2013, I need to learn Supercollider and Overtone. This is what brought me to Clojure in the first place. I’ll be blogging about that project plenty, but enough of that for now.

So I’ve been translating Supercollider code from Eli Fieldsteel‘s very excellent YouTube Supercollider tutorial series into Clojure for use with Overtone. It’s the domain-specific knowledge that gets you. I can’t be productive in Overtone until I understand how Supercollider works. So I’ve got to start with Supercollider, but keep my chops up in Clojure. Hence, this exercise.

Tutorial 3 talks about composing Ugens together to define and control synths in Supercollider. Yesterday and today I translated the final pulseTest synth into Clojure. What I’ve got sounds almost like the example code run from the Supercollider IDE, but something’s off with the ampHz Ugen. It seems to be pulsing at about half the speed, and occasionally I get a very loud low-frequency boom when I kick off the synth. I’d appreciate anyone who can spot my error or help me track down the cause. Here’s what I have:

(defsynth pulsetest "A super awesome synth that almost works the same as it would in SC"
  [ampHz 4
   fund 40
   maxPartial 4
   width 0.5]

  (let [amp1 (* 0.75 (lf-pulse:kr ampHz 0   0.12))
        amp2 (* 0.75 (lf-pulse:kr ampHz 0.5 0.12))

        freq1 (round 
                ; http://doc.sccode.org/Classes/UGen.html#-exprange explains
                ; that it uses a LinExp ugen to do the work. Here, we do the
                ; same manually 
                (lin-exp:kr :in    (lf-noise0:kr 4) 
                            :dstlo fund 
                            :dsthi (* fund maxPartial)) fund)
        freq2 (round 
                ; see above
                (lin-exp:kr :in    (lf-noise0:kr 4) 
                            :dstlo fund 
                            :dsthi (* fund maxPartial)) fund)

        freq1 (* (+ 1 (lf-pulse:kr 8)) freq1)
        freq2 (* (+ 1 (lf-pulse:kr 6)) freq2)

        sig1  (* amp1 (pulse:ar freq1 width))
        sig2  (* amp1 (pulse:ar freq2 width))

        sig1  (free-verb:ar sig1 0.7 0.8 0.25)
        sig2  (free-verb:ar sig2 0.7 0.8 0.25)]
    (do
      (out 0 sig1)
      (out 1 sig2))
    )
  )

Here is the SC version:

(
SynthDef.new(\pulseTest, {
        arg ampHz=4, fund=40, maxPartial=4, width=0.5;
        var amp1, amp2, sig1, sig2, freq1, freq2;
        amp1 = LFPulse.kr(ampHz,0,0.12) * 0.75;
        amp2 = LFPulse.kr(ampHz,0.5,0.12) * 0.75;
        freq1 = LFNoise0.kr(4).exprange(fund, fund * maxPartial).round(fund);
        freq2 = LFNoise0.kr(4).exprange(fund, fund * maxPartial).round(fund);
        freq1 = freq1 * (LFPulse.kr(8)+1);
        freq2 = freq2 * (LFPulse.kr(6)+1);
        sig1 = Pulse.ar(freq1, width, amp1);
        sig2 = Pulse.ar(freq2, width, amp2);
        sig1 = FreeVerb.ar(sig1, 0.7, 0.8, 0.25);
        sig2 = FreeVerb.ar(sig2, 0.7, 0.8, 0.25);
        Out.ar(0, sig1);
        Out.ar(1, sig2);
}).add;
)

And that’s how far I am. I’m going to proceed to another tutorial for now. I’m also going to find a theme and plugin that makes this code look a little nicer.

You can track my progress at the git repo I have set up for this project.

The main problem of learning to code

I was about to write another post about my journey with Supercollider and Overtone, and I probably will right after this, but I had a thought about learning to code that I thought merited its own post.

Learning the syntax and semantics of a programming language, especially a modern one like Ruby, is indeed achievable within the context of a bootcamp. But the real challenge of programming isn’t learning how to talk to computers. It’s learning how computers talk to each other (roughly layers 3-6), how programs talk to each other (layer 7 when it comes to network, plus a bunch of other concepts and markup languages and, as you get more advanced, OS-specific API knowledge), and the general ecosystem in which your programs live (sysadmin/tech support background helps here).

There’s a blog post that pretty accurately captures all the extra stuff you pick up as a web developer, but it tries to sell you more bootcamps and classes. I can’t vouch for the content of those classes, but I can tell you from experience that this isn’t a problem you have to throw money at. Linux is free and will run on equipment you would have retired.

This “extraneous” knowledge amounts to several years of active computing experience. You just have to spend a couple years messing around with computers, trying to run Arch and get everything done, stopping to troubleshoot esoteric problems, etc. Somebody with an IP address in China logs in and deletes your home directory for lulz. You grieve the years’ loss of writing, recordings, and other personal data. You learn iptables, set up denyhosts, start using SSH keys, change the way you structure your network, and start backing up your data (ask me how I know!).

Or you can take some classes, I suppose. I chose the former route.

Either way, as far as I can tell, there’s no shortcut. Until you get this contextual knowledge, you’re gonna struggle. You know a language, but have nothing to talk about for lack of any frame of reference. It’s like learning the words in Spanish for “fruit”, “dress”, and “bathroom” but being from an alien planet where they don’t eat, wear clothing, or defecate.

I think the main problem with a lot of these training courses, and the culture of learning to code, is that they gloss over this part of the journey. People get frustrated, conclude that they’re just not smart enough for the craft, and give up.

I think that’s the wrong conclusion. You just have to realize that on your way to true competence, you’re gonna have to take on some pretty massive side quests as part of the grind. You probably have what it takes. This is just a very long game.

And when you’ve mastered the “basics”, there’s a whole other level to get at. For a professional software developer, it should be a given that they know their network, they know how OS’s work, they know how various flat-file and database storage systems work, they know a couple markup languages and a couple Layer 7 protocols, and a ton of other miscellaneous stuff I’m forgetting. Taking all that stuff as a given, here’s my frontier.

Keep learning. We practice a deep craft.

quick check-in

I’m still working on music coding stuff, but my day job took a lot of my attention in March.

But then a few months later I quit! My last day was July 15th. Now I’m a freelancer. That feels awesome.

I’m probably going to rewrite this blog in something other than WordPress just to be clever and flex my dev chops.

But not yet. I’ve got work to drum up, and I’d rather work on music stuff. If it ain’t broke…

Later!

Quick note on SL quantization bug

I tried just calling calculate_tempo_frames() from the jack timebase callback in jack_audio_driver in lieu of setting transport info, but I don’t think that’s the right approach. I need to be fixing the calculation for giving JACK the right transport info. The Engine::calculate_tempo_frames function doesn’t yield BBT info. So I need to dig in and figure out how that information is calculated in the engine, and glue that to the transport callback. It think it’s in Engine::set_tempo, and I already see some possibilities.

The bug occurs when quantization is set to 8th as opposed to cycle, this reminds me. Note to self, verify that and finish writing out the conditions the trouble appears under in the first place.

Progress on testing sooperlooper with supercollider

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:


// SETUP:
// 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
//
// RECORDING:
//
// 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.
//
// TESTING QUANTIZATION
//
// 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 ###############
MIDIClient.init;
~engine = NetAddr.new("localhost", 9951);

"sooperlooper &".systemCmd();

SystemClock.sched(2.0, {

8.do({~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");
MIDIOut.connect(0,~engine_midi);

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

nil;
});

~player = Routine({
MIDIOut(0).noteOn(0,55);
});

// 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;});

A nice realization

I’m trying to fix an esoteric problem with Sooperlooper keeping track of quantization over tempo changes when it’s also JACK transport master. It performs pretty well with time-stretching the loops, but it totally loses track of where the quantization boundaries are if you change the tempo. With loops, those milliseconds really add up after a few repetitions until things sound mega borked, so I rely heavily on the looper’s ability to not start recording until the next boundary even if I tap the button just a tad early like some kind of human or something.

(An aside, I’m almost positive this unmodified example code in jack_audio_driver.cpp is the problem. I will describe my hypothesis about exactly how the malfunction functions in another blog post — this one is reserved for describing my testing plan).

So, I spent all afternoon yesterday tediously trying to manually reproduce the issue, just to get it fresh in my mind again. I totally forgot about the quantization part of the problem and neglected to test it, and I was spinning my wheels running two instances of the engine (one with my changes and the other without) and manually connecting the GUI to either one, clicking buttons to set the settings. Having forgotten to test the quantization, I was confounded at the parity between the two versions’ performance as I tapped on the ‘t’ key at varying intervals. Yep, I forgot what bug I was even trying to fix. Lawl. What a mess.

I do actually do this for a living, so why the hell wouldn’t I have written some sort of test for it already?

“Eh, something something it’s a loopstation how would I test live audio and all that, it seems like more trouble than it’s worth.”

Okay. Fair enough. I am a green dev. So I’m trying to fix this minor malfunction in Sooperlooper, and I don’t know how to test it and I’m still learning C++ and reading the codebase and I have a day job and I’m not getting very far on this and I still wanna build my magical rig, so lately I’m also playing a lot with SuperCollider.

Hey! Supercollider is designed for audio and has its own internal timing mechanisms. It also talks OSC and MIDI. I can control Sooperlooper, I can send it audio, I can analyze the audio it returns! I have seen (and even partially understood!) code examples which do all of these things. And I need a beginner project.

This is perfect. I will write an automated test for this part of Sooperlooper in SuperCollider. I’ll bet you I can write the whole thing that way, even starting the engines. I can dispense with the GUI entirely. I can configure all the loops and settings, I can tell Sooperlooper to load files over OSC. Aw man, this is fucking RAD.

The timing is great. I’ve just recently started to understand busses and groups and how to route audio in SC, but I’m still looking for the exercise that will force me to understand how to wrangle the language toward a complex end goal. This is it.

Due to this realization, I’m very happy.

trying to build sooperlooper

Just some notes to myself for later.

Sooperlooper builds if I do like the patch file in the AUR package for sooperlooper does, and change all the includes for sigc++/object.h to sigc++/trackable.h

I’m going to want to research why that is, and whether that change breaks the build on other platforms. For now, I’m gonna get it building so I can start playing with the bugs I actually intended to tackle.

I also have to use these options to ./configure:

CPPFLAGS=-std=c++11 ./configure \
--prefix=/usr \
--with-wxconfig-path=/usr/bin/wx-config-2.8