Endlessly repeating...

A synced audio looper in SuperCollider

7 December 2010

Solving this problem has been bugging me on and off for ages now so I thought I'd share the solution I eventually came up with. First, the problem: how to do on-the-fly looping of audio in SuperCollider in sync with a sequencer? In this case the sequencer is going to be a very simple example in Processing but the method will work with a hardware drum machine as well.

The trick I settled on is to think like an old hardware loop pedal. In other words, set up the looper with more capacity that you think you're going to need and then do the recording into some part of that space. The record 'head' (to borrow some old tape terminology) is positioned using a phasor that, left alone, runs from the start of the recording area to the end. An OSC responder (or MIDI responder or whatever your sequencer speaks) is set up that causes the phasor to jump back to zero when it's triggered. If you send the trigger at the end of your sequenced loop you get synchronised looping. Overdubbing on to the loop is a simple matter of making sure that the contents of the buffer is read before the new material is added on each pass.

This is, in a way, a bit of a dumb approach and I'm kind of embarrassed about how long it took me. The obvious problem is that you need to make a buffer longer than you're ever going to need and "ever" is always a bit hard to define. Also, having huge under-utilised chunks of memory isn't exactly efficient but in, in practice, a 60 second loop only takes up ~2.5MB so it's not going to bring a modern laptop to it's knees even with a few instances going. Anyway, I hope this helps someone.

First the SuperCollider code:

// definition of the looper
SynthDef(\looper, {
    // need a buffer to listen to and an input for the loop trigger
    arg bufnum, t_reset;

    // variables for the existing signal in the loop, the new input,
    // the output signal and the recording head position
    var inputSig, outputSig, existingSig, recHead;

    // get the input signal
    inputSig = In.ar(0);

    // generate the recording (also playback) position
    recHead = Phasor.ar(t_reset, BufRateScale.kr(bufnum), 0, BufFrames.kr(0));

    // read the existing signal from the loop
    existingSig = BufRd.ar(1, bufnum, recHead);

    // put the existing signal plus the new signal into the loop
    BufWr.ar(inputSig + existingSig, bufnum, recHead);

    // play back signal we got from the loop before the writing operation
    Out.ar(0, existingSig);
}).add;

// a crappy instrument to test with
SynthDef(\ping, {
    arg freq;
    var sig;
    sig = SinOsc.ar(freq) * 0.5;
    sig = EnvGen.kr(Env.perc(0.05, 2), doneAction:2) * sig;
    Out.ar(0, sig);
}).add;

// create a big empty buffer (20 secs is enough for me)
b = Buffer.alloc(s, 20 * s.sampleRate, 1)

// listen for a an OSC message indicating that we've reached the loop point
p = OSCresponderNode(nil, '/newbar', {|t, r, msg| ~looper.set(\t_reset, 1)}).add;

// listen for OSC triggering the test instrument
o = OSCresponderNode(nil, '/newnote', {
    |t, r, msg| msg[1].postln; Synth.new(\ping, [\freq, msg[1].midicps])
}).add;

// create an instance of the looper
~looper = Synth.new(\looper, [\bufnum, b.bufnum])

Now the Processing:

// import the OSC libraries (see http://www.sojamo.de/libraries/oscP5/)
import oscP5.*;
import netP5.*;

// the OSC handler and target address
OscP5 oscP5;
NetAddress remote;

// where in the bar are we
int beatCount;

// lock to a scale so the test instrument sounds a bit nicer
int[] cMinor = {48, 50, 52, 53, 55, 57, 59, 60, 62, 64, 65, 67, 69};

void setup() {
  size(260, 200);
  frameRate(30);

  // draw a keyboard to test with
  for(int i = 1; i <= 12; i++) {
    line(20 * i, 0, 20 * i, 100);
  }

  // instantiate the OSC handler
  oscP5 = new OscP5(this, 10000);

  // Supercollider is assumed to be listening on 
  // localhost at the default sclang port
  remote = new NetAddress("127.0.0.1", 57120);
}

void draw() {
  // pulse in time with beat
  fill(255 / ((frameCount % 15) + 1));
  rect(10, 110, 40, 40);

  // one beat every 15 frames
  if(frameCount % 15 == 0) {
    beatCount++;
  }

  // at the end of the 16 beat bar, trigger the loop back to the beginning
  if(beatCount == 16) {
    OscMessage msg = new OscMessage("/newbar");

    oscP5.send(msg, remote);
    beatCount = 0;
  }

  // pulse in time with the bar
  fill(255 / (beatCount + 1));
  rect(60, 110, 40, 40);
}

// use the mouse to play the test instrument
void mousePressed() {
  int noteNum = int(mouseX / 20);
  OscMessage msg = new OscMessage("/newnote");
  msg.add(cMinor[noteNum]);
  oscP5.send(msg, remote);
}
-

Monorailmachine!

7 December 2010

After the backward lookinginess of the last three posts I thought a bit of new music might be nice. I picked up an Elektron Monomachine at the weekend and this is the result of the first proper play I've had with it:

First Monomachine jam

It's a very easy fit with the Machinedrum (unsurprisingly). I haven't quite figured out how to fit it in with the rest of my existing live setup. I'm very keen to keep using open-source software in there somewhere so I may have to get into using Supercollider more for effects processing and textures.

-

Where's the music III?

7 December 2010

The great link dump concludes (probably, I might put up some of stuff from collaborations at a later date) with the EP I put together in 2006:

  1. Resignation
  2. Backward Rough
  3. Smoky
  4. Sinewaves
  5. We Make My Doubts

For convenience, the whole thing's also available in a .zip file.

-

Where's the music II?

28 November 2010

The great link dump continues. Part two is a couple of older gig recordings:

-

Where's the music I?

15 November 2010

Time was, there was a whole load of music on this site. Until I get round to designing a way to present all that again (or just dump it all on Soundcloud) I thought I'd throw up the links as posts. Part one is the miscellanea:

-

Thoughts on OpenNight #4

20 April 2010

Rob Munro was kind enough to upload a recording of the set I played at The FleaPit last Thursday. Download it and have a listen if you weren't able to make it along.

I was pleased with how it went on the whole (and was offered another gig afterwards so it can't have sounded too bad). The main problem was that it took me a couple of minutes to settle into a groove which isn't ideal from the point of view of hooking people in who might be thinking about popping to the bar. I'm not entirely sure how to deal with this in the framework of improvising everything. The best idea I've had so far is to take a leaf out of the big book of chess and learn a bunch of opening gambits that I can quickly get going and then build on. Obviously this isn't really improvising any more but I think it'd be less constraining than turning up with an initial groove saved as a pattern.

On the technical side of things, I really want an arpeggiator function within the sequencer but I'm not sure how to implement this within the interface of the Launchpad just yet. Apart from that everything performed very well and I didn't run into any bugs despite the labyrinthine mess that the code as become.

-

OpenNight #4

29 March 2010

I'll be playing at The Fleapit, 45 Columbia Road, E2 on April 15th. Come along!

OpenNight 4 flyer

-

Reasons to love SuperCollider

28 January 2010

When I first started to get into the nuts and bolts of sound generation (rather than tweaking someone else's plugins), I did what a lot of other people do and downloaded Pure Data. It's a great environment to start playing around in, you connect an oscillator block to a filter block to an output block and you've got a little subtractive synth and it's clear how the signal flows through it. That's absolutely great for learning how things work and, to my mind, Pd is one of the gems of open-source audio software. Having said that, Pd, Max/MSP and every other draw lines between the building blocks audio systems have a problem - spaghetti. The building blocks lend themselves well to simple signal flows but when you get a hankering to play with 16 operator FM it's not quite as easy to see how everything fits together and it requires a hell of a lot of mousing around to put the thing together. Sure there are ways to organize subsections and that helps a lot (and is good practice generally) but I tend not to start a project with a clear view of where I'm going so once things have gone a bit Italian it's too late. Take all this with a pinch of salt of course - there are people out there who do wonderful, complex work with Pd, I'm just not one of them.

Anyway, the point of that ramble was to say that there's a need sometimes for a more abstract approach than blocks and wires. That's where SuperCollider comes in. Programmers have been representing complex problems as text for many years now and SuperCollider brings that power to audio (without the need to take a degree level class in DSP). To highlight the power of text I wanted to share this little example that I wrote after being inspired by this example of data based reverberation.

SynthDef(\dataverb, { |t_trig, freq, amp = 0.5 att = 0.1, dec = 0.8, rtime = 0.04|
    var steps = 24, d, a, w, f, sig, controlEnv;
    d = (1..steps - 1).collect({|n| rtime * n}).addFirst(0);
    a = (2..steps).collect({|n| amp / n}).addFirst(amp);
    w = [0.5, 0.6, 0.7, 0.4];
    f = [freq, freq * 0.999, freq * 1.001, freq / 2, freq * 1.5];
    sig = Mix.new(
        Pulse.ar(f, w, mul: EnvGen.kr(Env.perc(att, dec), DelayN.kr(t_trig, d, d), a))
    );
    controlEnv = EnvGen.kr(Env.new([0, 0], [d.last + att + dec]), doneAction: 2);
    Out.ar(0, sig!2);
})

Those 12 lines of code represent 24 channels of a delay, an envelope and an oscillator all subsequently mixed down. The magic comes from SuperCollider's ability to take an array as one of the creation arguments of a sound generating block and create an instance of the block for each element of the array. In the example above the line

d = (1..steps - 1).collect({|n| rtime * n}).addFirst(0);

creates an array of 24 gradually increasing delay times. When this is provided as an argument to the delay block (DelayN.kr(t_trig, d, d)) you get 24 delays. No fuss, no patch cables. Want more? Just change the value of the steps variable. Want to individually filter each step, it's only a couple more lines of code.

To get an idea of what it sounds like have a listen to this:

If you haven't looked into the power of the SuperCollider language yet, I hope this inspires you to do so.

-

Evidence

18 January 2010

I made a decision a while back to focus on playing live rather than recording. I've spent a lot of time hunched over Cakewalk Sonar tweaking tracks and I daresay I'll go back to that mode again eventually (or if I'm lucky find a compromise) but right now I want to play. The downside of this is that it can be a bit tricky to explain to people what the hell it is my music actually sounds like. Fortunately, the endlessly helpful Rob Munro was kind enough to record a couple of the recent OpenNights so for anyone who's curious (and can't make it to a gig which is a much better option) here's some music...

OpenNight #1

OpenNight #2

There's loads more live open-source musical loveliness at the OpenLab OpenNight blog.

-

Gig at The FleaPit - 11 February 2010

17 January 2010

I'll be playing at The FleaPit on Columbia Road, London, E2 on February 11th as part of one of OpenLab's OpenNIghts. Line-up and timing is TBC but turn up at around 7pm and you'll probably get to see Viktor Mastoridis, Rob Munro, Soviet Comonautics, Dave+Martin and me(!). Door tax is but a single beer.