_slipmat @ Twitter

If RSS isn’t your cup of tea, you can alternatively follow _slipmat @ twitter. All new blog posts, along with git commits (in digest form) are set to auto-aggregate to there. I’ll also post links and information related to slipmat, computer music and audio synthesis from time to time. And you’ll be able to send tweets my way as well.

Stacking Time

By utilizing a Python list as a stack, it is possible to move the stylus to a specific time in the score, schedule events relative to the stylus, and then pop it back into its previous position. This is inspired by Processing functions pushMatrix() and popMatrix().

I’ll explain what I’m talking about through example. The following is an excerpt from the pushpop.py example in the Slipmat github repo:

# Measure 1
s.event(0, 0.25, Sine(1, cpspch(7.00)))
s.event(1, 0.25, Sine(1, cpspch(7.03)))
s.event(2, 0.25, Sine(1, cpspch(7.07)))
s.event(3, 0.25, Sine(1, cpspch(7.10)))

# Measure 2
s.time.append(4)
s.event(0, 0.25, Sine(1, cpspch(8.00)))
s.event(1, 0.25, Sine(1, cpspch(8.03)))
s.event(2, 0.25, Sine(1, cpspch(8.07)))
s.event(3, 0.25, Sine(1, cpspch(8.10)))
s.time.pop()

In measure 1, four Sine() events are created at times 0, 1, 2 and 3, specified in the first parameter of s.event().

In measure 2, before any calls to s.event() are made, the value 4 is append to a list object called “time,” part of class ScoreEvents. Until “time” is popped or appended, the value 4 is added to every start time in method event(). Measure 2 generates 4 events, created at times 4, 5, 6 and 7.

If these two measures were explicitly written, it would look like this:

s.event(0, 0.25, Sine(1, cpspch(7.00)))
s.event(1, 0.25, Sine(1, cpspch(7.03)))
s.event(2, 0.25, Sine(1, cpspch(7.07)))
s.event(3, 0.25, Sine(1, cpspch(7.10)))
s.event(4, 0.25, Sine(1, cpspch(8.00)))
s.event(5, 0.25, Sine(1, cpspch(8.03)))
s.event(6, 0.25, Sine(1, cpspch(8.07)))
s.event(7, 0.25, Sine(1, cpspch(8.10)))

The current position of the stylus is the combined sum of all values stored in list “time.” For example:

s.time.append(16)
s.time.append(30)
s.time.append(11)
s.event(7, 1, foo())
s.time.pop()
s.event(2, 1, bar())

The foo() event is scheduled at beat 64. That’s (16 + 30 + 11) for “time” plus an additional 7 for the first parameter of event(). After “time” is popped, bar() plays at beat 48; (16 + 30) for “time” plus 2 for the relative start of the event.

For the record, I think the s.event() code is a bit ugly, which I’m already addressing behind the scenes. If you’ve been keeping up with this blog, you might already have a good idea as to where I’m going with this.

Slipmat at Github

Slipmat code has a new home at github. I have few things to get in order before I push the code to the new repository. Things should be up sometime week.

Just to be absolutely clear, the blog is staying here at noisepages.

Lead-In Redux

I had three goals in mind in getting the first working Python Slipmat prototype, Lead-In, up and running:

  • Use pure Python, with no external modules.
  • Getting Python to behave like a real audio langauge.
  • Mold the syntax to feel like a real audio language.

I would say it’s a success on all three fronts, with plenty of room to grow.

However, there are some major issues. The biggest being that it’s slow; Amazingly slow. This was expected, as Python itself is an interpreted language, and not a DSP power house like C/C++. On my modern iMac, slipmat_lead-in.py took 1 minute and 19.681 seconds to render. I bet Csound would do a comparable job, in 1996.

So one of my first goals of the upcoming redesign is to make it just fast enough until a proper C/C++ engine can be written. I’m looking into NumPy/SciPy.

I’m also looking into Audiolab, “a python package to make noise with numpy arrays,” written David Cournapeau. This is, more or less, a Python wrapper module for Erik de Castro Lopo’s excellent libsndfile; I was already planning on using libsndfile, anyhow.

Score Events

No computer music system is complete without the ability to place notes into a score/timeline. Read and download today’s script here.

The mechanisms that schedule score events and print the results are still a bit wonky. So I’m going to omit the explanation for now, and instead just focus on how they’re used. The following is the __main__ from the script:

if __name__ == "__main__":
    @Instr
    def RingTine(dur, amp, freq_1, freq_2):
        return Sine(amp, freq_1) * Sine(amp, freq_2) * RiseFall(dur, 0)

    s = ScoreEvents()
    s.event(0, 0.25, RingTine(0.25, 1, 440, 44))
    s.event(0.5, 0.125, RingTine(0.125, 0.333, 262, 44))
    s.event(0.75, 0.25, RingTine(0.25, 0.25, 262, 44))
    s.event(0.75, 0.25, RingTine(0.25, 0.5, 440, 44))
    for frame in PrintSamples(s): pass

The first part of __main__ defines an instrument called RingTine that generates a percussive ring-modulated timbre. At least in theory; Still no sound output.

The next step creates a ScoreEvents() object. This is where events are scheduled. This is followed by 4 lines of code that schedule events for @Instr RingTine with the ScoreEvents class method event(). The method takes three arguments:

ScoreEvents.event(start_time, duration, UnitGenerator(*args))

The very last line prints the samples generates by the score with the PrintSamples() iterator. The implementation is seriously lame at the moment, but is perfectly acceptable for a crude prototype.

Operators Made Easy

There was something that seriously annoyed me about yesterday’s instrument graph. The use of the Multiply() and the Sum() is bothersome; I’m used to expressing this functionality in a more concise manner using the * and + operators. Download today’s code here.

This doesn’t work for me:

return Multiply(Sum(a1, a2), RiseFall(dur, 0.5))

Thankfully, Python allows us to overload the operators, so we can express the same line as this:

return (a1 + a2) * RiseFall(dur, 0.5)

Less typing, easier to read. Let’s see it in the context of @Instr MyInstr:

@Instr
def MyInstr(dur=1.0, amp=1.0, freq=440, tune=12):
    a1 = Sine(amp * 0.5, freq)
    a2 = Sine(amp * 0.5, freq * 2 ** (tune / 12.0))
    return (a1 + a2) * RiseFall(dur, 0.5)

Here’s how I implemented it. I started by creating a generic iterator class called UnitGenerator:

class UnitGenerator:
    def __init__(self): pass
    def __iter__(self): pass                 
    def next(self): raise StopIteration    
    def __add__(self, i): return Add(self, i)
    def __mul__(self, i): return Mul(self, i)

The last two lines of the class redefine __add__() and __mul__(), which control the behaviors of + and *. These functions use the custom classes Add() and Mul(). These were originally called Sum() and Multiply(), though I renamed them to follow Python naming conventions. The last thing I had to do was alter some of the existing classes to derive from class UnitGenerator, so they automatically incorporate the overloaded operators.

class Instr(UnitGenerator):  ...
class IterReduce(UnitGenerator):  ...
class Mul(IterReduce):  ...
class Add(IterReduce):  ...
class RiseFall(UnitGenerator):  ...
class Sine(UnitGenerator):  ...

Classes Mul and Add are also of type UnitGenerator. They inherit for class IterReduce which inherents from UnitGenerator.

Instruments Simplified with Python Decorators

Python can be molded to look like an audio synthesis language with the help of decorators. In my last post, I showed an instrument graph built within a Python iterator class. However, Iterator classes are too clunky looking, and require more work than should be necessary for creating a simple graph. Read and download this blog’s example script here.

So how does it look in comparison?

@Instr
def MyInstr(dur=1.0, amp=1.0, freq=440, tune=12):
    a1 = Sine(amp * 0.5, freq)
    a2 = Sine(amp * 0.5, freq * 2 ** (tune / 12.0))
    return Multiply(Sum(a1, a2), RiseFall(dur, 0.5))

The @Instr decorator signals to both the human and the computer that the proceeding definition is an instrument class. Much easier to deal with than having to fiddle around with class methods __init__(), __iter__() and next(). There’s less code to write, and far less chance to break the internals of an iterator class. And it more closely resembles an audio synthesis language. Here’s a near equivalent version written in Csound:

instr MyInstr
    idur = p3
    iamp = p4
    ifreq = p5
    itune = p6
    
    a1 oscils iamp * 0.5, ifreq, 0
    a2 oscils iamp * 0.5, ifreq * 2 ^ (itune / 12), 0
    kenv linseg 0, idur * 0.5, 1, idur * 0.5, 0
    out kenv * (a1 + a2)
endin

What’s going on exactly? Well, to be honest, decorators have been eluding me for the past two weeks. It wasn’t until last night as I was halfway through watching Eddie Izzard’s Dress to Kill that it suddenly clicked. So any explanation I give will be insufficient. I’ll just say that @Instr refers to a generic graph iterator class designed by me that creates a new iterator class that assimilates def MyInstr. If you want to know more, you can read Bruce Eckel’s Introduction to Python Decorators.

Question: Cake or death?

Python Iterator Class as Instrument Graph

I built an iterator class from the graph in yesterday’s example. Read and download the full script here. Let’s take a look at the class, then discuss the implications:

class MyInstr:
    '''My Instrument'''

    def __init__(self, dur, amp=1.0, freq=440, tune=12):
        a1 = Sine(amp * 0.5, freq)
        a2 = Sine(amp * 0.5, freq * 2 ** (tune / 12.0))
        self.out = Multiply(Sum(a1, a2), RiseFall(dur, 0.5))

    def __iter__(self):
        self.index = 0
        self._iter = (i for i in self.out)
        return self
        
    def next(self): 
        if self.index >= ksmps: raise StopIteration 
        self.index += 1
        return self._iter.next()

What good is a graph defined as an iterator class? The class essentially works like a Csound instrument. Multiple instances with varied input can be created, like Csound score i-events. It is also, more or less, a new unit generator that can be patched into a new graph. Most importantly, they can be easily shared with others in the Slipmat community. Import. Reuse. Remix.

There is a downside. If you’re not familiar with Python code or Python iterator classes, then you probably can’t make heads or tails out of this. One of the fundamental principles of the Slipmat philosophy is that the code needs to be user-friendly. Iterator classes as graphs, well, that’s a big technical hurdle, especially for n00bs. I don’t like hurdles; they restrict the creative flow, and can be a show stopper.

Fortunately, there is a solution, which I’ll post later today. If you happen to follow my twitter feed, then you probably have figured out where I’m going.

Here’s the the new __main__:

if __name__ == "__main__":
    t = 0.002
    my_instr = MyInstr(t, 1, 440, 7)
    
    for frame in Run(t):
        print '%d:' % frame
        for i in my_instr:
            print 't%.8f' % i

This produces identical results to yesterday’s script. The only difference is that the code has been refactored to increase modularity and possibly clarity.

Control-Rate Envelope Generator

I wanted to generate a control-rate signal with my Python-Slipmat prototype. To my surprise, doing so was fairly straight forward; Python iterator classes have a control-rate mechanism built right in.

Read and download the full script at snipt.

I designed a simple envelope that generates a rise-fall shape. By default, the rise and fall times are identical, though users can specify a peak value relative to the duration. Here’s the code:

class RiseFall:
    '''A rise-fall envelope generator.'''
    
    def __init__(self, dur, peak=0.5):
        self.frames = int(dur * sr / float(ksmps))
        self.rise = int(peak * self.frames)
        self.fall = int(self.frames - self.rise)
        self.inc = 0
        self.v = 0
        
    def __iter__(self):
        self.index = 0
        
        if self.inc <= self.rise and self.rise != 0:
            self.v = self.inc / float(self.rise)
        else:
            self.v = (self.fall - (self.inc - self.rise)) / float(self.fall)
            
        self.inc += 1
        return self
    
    def next(self):
        if self.index >= ksmps:
            raise StopIteration

        self.index += 1          
        return self.v

To make sense of this, I’m going to compare this Python iterator class to Csound. More or less, Csound works at three rates: init (i), control (k) and audio (a). All three of these are represented in class RiseFall. The i-rate of RiseFall is __init__(), the k-rate is __iter__() and the a-rate is next().

What makes RiseFall a k-rate unit generator is that the code that calculates the current value of the envelope resides in __iter__(), which gets executed at the beginning of each new frame of audio. If you look at class Sine, you’ll see that the code responsible for generating the sine wave is in the class method next().

And here is RiseFall added to our graph:

if __name__ == "__main__":
    t = 0.002
    a1 = Sine(0.5, 440)
    a2 = Sine(0.5, 440 * 2 ** (7 / 12.0))
    amix = Sum(a1, a2)
    aenv = RiseFall(t, 0.5)
    aout = Multiply(amix, aenv)

    for frame in Run(t):
        print '%d:' % frame
        for i in aout:
            print 't%.8f' % i

I also refactored class Mixer; It is now known as Sum, which can now sum two or more signals. Additionally, I added the class Multiply, which takes after Sum.