Other things on this site...

Evolutionary sound
Listen to Flat Four Internet Radio
Learn about
The Molecules of HIV
Make Oddmusic!
Make oddmusic!

isobar python pattern library

One of the nicest things about the SuperCollider language is the Patterns library, which is a very elegant way of doing generative music and other stuff where you need to generate event-patterns.

Dan Jones made a kind of copy of the Patterns library but for Python, called "isobar", and I've been meaning to try it out. So here are some initial notes from me trying it for the first time - there may be more blog articles to come, this is just first impressions.

OK so here's one difference straight away: in SuperCollider a Pattern is not a thing that generates values, it's a thing that generates Streams, which then generate values. In isobar, it's not like that: you create a pattern such as a PSeq (e.g. one to yield a sequence of values 6, 8, 7, 9, ...) and immediately you can call .next on it to return the values. Fine, cutting out the middle-man, but I'm not sure what we're meant to do if we want to generate multiple similar streams of data all coming from the same "cookie cutter".

For example in SuperCollider:

      p = Pseq([4, 5, 6, 7]);
      q = p.asStream;
      r = p.asStream;
      r.next;  // outputs 4
      r.next;  // outputs 5
      q.next;  // outputs 4
      q.next;  // outputs 5

and in isobar it looks like we'd have to do:

      q = PSeq([4, 5, 6, 7])
      r = PSeq([4, 5, 6, 7])
      r.next()  # outputs 4
      r.next()  # outputs 5
      q.next()  # outputs 4
      q.next()  # outputs 5

Note how I have to instantiate two "parent" patterns. (I could have cached the list in a variable, of course.) It looks pointless with such a simple example, who cares which of the two we do. But I wonder if this will inhibit the pattern-composition fun in isobar, that you can do in SuperCollider by putting patterns in patterns in patterns... who can say. Will dabble.

The other thing that was missing is Pbind, the bit of magic that constructs SuperCollider's "Event"s (similar to Python "dict"s).

As a quick test of whether I understood Dan's code I added a PDict class. It seems to work:

      from isobar import *
      p = PDict({'parp': PSeq([4,5,6,7]), 'prep': PSeq(['a','b'])})

      p.next()   # outputs {'prep': 'a', 'parp': 4}
      p.next()   # outputs {'prep': 'b', 'parp': 5}
      p.next()   # outputs {'prep': 'a', 'parp': 6}
      p.next()   # outputs {'prep': 'b', 'parp': 7}
      p.next()   # outputs {'prep': 'a', 'parp': 4}

This should make things go further - as in SuperCollider, you should be able to use this to construct sequences with various parameters (pitch, filter cutoff, duration) all changing together, according to whatever patterns you give them.

There's loads of stuff not done; for example in SuperCollider there's Pkey() which lets you cross the beams - you can use the current value of 'prep' to decide the value of 'parp' by looking up its current value in the dict, whereas here I'm not sure if that's even going to be possible.

Anyway my fork of Dan's code, specifically the branch with PDict added, is at:


Sunday 8th January 2012 | IT | Permalink
Name: felix
Email: felix art crucial-systems dort com
Date: Sunday 8th January 2012 19:53
I think the embedInStream part is essential for fun. that means patterns inside of patterns and also chaining patterns and exhausting them in succession, etc.

like today I wanted to do this in python:

def two():
yield 3

def one():
yield 1
yield 2
return two()

for x in one():
print x

[will probably get killed in the formatting]

you can't just call another function and continue to yield objects from that one. you get an error that you can't call return in a generator.

you can add to the end of def one go for x in two(): yield x but that's not the same. that's what embedInStream does

Name: daniel
Website: http://www.erase.net/
Email: dan-web art erase dort net
Date: Monday 19th March 2012 19:43
Sorry, somewhat slow to respond to this one!

I decided to take a slightly different approach than mediating through Streams. Rather than making a cookie-cutter before producing cookies, isobar takes the approach that you start by creating a cookie, which you can then clone if you like it.

Your example could be rewritten as:

q = PSeq([4, 5, 6, 7])
r = q.copy() # deep copy
r.next() # outputs 4
r.next() # outputs 5
q.next() # outputs 4
q.next() # outputs 5

It's possible that there are things that can be achieved with the Stream/Pattern separation that can't be done with this approach; if so, I'd be interested to hear about them.

Thanks for PDict, now checked into patterns/core :-)

isobar is still very much a work in progress, and has been mostly driven by my own needs - which, up until now, has been for interesting but non-interacting stochastic sequences. For that reason, these bits -- L-systems, Markov chains etc -- are fairly well developed, whereas a lot of other stuff that is core to SC's pattern lib has been somewhat neglected.

I'm starting another major project with it over the next month, so expect to see a lot more developments being pushed to git.

Add your comments:

I am a:
Everything is optional - and email addresses will be marmalised to protect you
Creative Commons License
Dan's blog articles may be re-used under the Creative Commons Attribution-Noncommercial-Share Alike 2.5 License. Click the link to see what that means...