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

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) = 0
        self.v = 0
    def __iter__(self):
        self.index = 0
        if <= self.rise and self.rise != 0:
            self.v = / float(self.rise)
            self.v = (self.fall - ( - self.rise)) / float(self.fall)
            += 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)
    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.

Rendering 1 Second of Audio Data



class Mixer:
    '''A simple mixer.'''
    def __init__(self, source1, source2):
        self.s1 = source1
        self.s2 = source2
    def __iter__(self):
        self.index = 0
        self.iter_1 = (i for i in self.s1)
        self.iter_2 = (i for i in self.s2)
        return self
    def next(self): 
        if self.index >= ksmps:
            raise StopIteration

        self.index += 1
        return +
class Run:
    '''Render frames over time.'''
    def __init__(self, dur=1.0):
        self.dur = dur  
    def __iter__(self):
        self.index = 0
        return self
    def next(self): 
        if self.index >= (self.dur * sr) / ksmps:
            raise StopIteration

        self.index += 1
        return self.index




if __name__ == "__main__":
    a1 = Sine(0.5, 440)
    amix = Mixer(a1, a2)
    for frame in Run(1.0):
        print frame, ':'
        for sample in amix:
            print 't', sample


4276 :


Python Iterator as a Sine Oscillator

I wrote a sine oscillator as a Python iterator class. Not the fastest digital oscillator in the world. Though for now, doing prototype work in pure Python will do just fine, even if it means slow render times. In the long run, Slipmat will require a powerhouse of an engine for real-time audio synthesis and DSP, probably written in C. All in good time.

Here’s the script:

#!/usr/bin/env python
import math
import itertools

class Sine:
    '''A sine wave oscillator.'''
    sr = 44100
    ksmps = 10    
    def __init__(self, amp=1.0, freq=440, phase=0.0):
        self.amp = amp
        self.freq = float(freq)
        self.phase = phase
    def __iter__(self):
        self.index = 0
        return self
    def next(self):
        if self.index >= self.ksmps:
            raise StopIteration

        self.index += 1
        v = math.sin(self.phase * 2 * math.pi)
        self.phase += self.freq /
        return v * self.amp

if __name__ == "__main__":
    a1 = Sine(1, 4410, 0.25)
    a2 = Sine(0.5, 8820)
    amix = (i + j for i, j in itertools.izip(a1, a2))
    for i in amix:
        print i

Currently, it produces the same sound as a tree falling in the woods with no one around to hear it; It doesn’t write to a DAC or sound file. At least we can still view the results:


Near the bottom of the code is a very simple graph, where two sine wave generators (a1 & a2) are patched into a mixer generator (amix), creating a very rudimentary additive synthesizer. The signals aren’t generated until the for-block at the very end. The script only prints one control-block’s worth of data, 10 samples, which coincides with the value of ksmps found in class Sine. The sample rate and control rate is built right into class Sine, and needs to be moved out.

Realm of the Practical

Brain storming is easy; I can make things up without being held accountable. However, I need to spend time in the realm of the practical. I won’t stop collecting ideas, but if Slipmat is going to be a reality, I need to know what the issues are. This means lots of research and lots of prototyping and lots of little scripts that test various facets of Python.

For example, creating a graph of Python 3 generators:

#!/usr/bin/env python3

import operator

ksmp = 8
foo = (i * 2 for i in range(ksmp))
foo = (i + 1 for i in foo)
bar = (11 for i in range(ksmp))
foo = map(operator.mul, foo, bar)


This is interesting to me because the names foo and bar do not receive an array/collect/list of numbers. Instead, they point to generator objects. These generators are not evaluated until print(*foo) is called.

By the way, if my understand or terminology is ever off, please correct me. I’m also here to learn.

x Trigger Notation



#!/usr/bin/env slipmat

from JakeLib.Generators import trig
from EasyKit import hat, snare, kick


@0    hat()
@0    kick()
@0.5  hat()
@1    hat()
@1    snare()
@1.5  hat()
@2    hat()
@2    kick()
@2.5  hat()
@3    hat()
@3    snare()
@3.5  hat()

The form is lost in translation.


def trig(seq, res=0.25):
    Creates a numeric sequence from a string and returns a list.

    of triggers and rests are determined by the argument res.

    res -- Resolution of note triggers and rests
    return -- A numeric list
    L = []  # The return list
    p = 0   # Position in sequence

    for c in seq:
        if c == 'x':
            L.append(p * res)
            p += 1            
        elif c == '.':
            p += 1

    return L


Import, Reuse, Remix


List Comprehension Grain Generator

This is a granular synthesizer:

@[random() * 8 for i in range(1000)] foo()

This generates 1000 events over the span of 8 beats, using list comprehension.

The function foo() is intentionally left blank. It may produce random single-cycle waveforms, bits of a sample, or glowing TRON-like particles for your live performance visuals. Whatever.

Auto-Generating Lists

In yesterday’s blog, Lists as Micro-Sequencers, we discussed writing lists instead of individual events. Today, we’ll generate lists instead of writing list values.

Python comes with the built-in range() function that generates and returns a list of integers, which can be used to schedule events. The following python interpreter session demos range() with three sets of args along with their respective outputs.

>>> range(4)
[0, 1, 2, 3]
>>> range(5, 9)
[5, 6, 7, 8]
>>> range(0, 16, 4)
[0, 4, 8, 12]

The range() function can be a huge saver of time and keystrokes. Especially if you’re composing goa trance:

@range(1024) goa_kick()

“The kicks are done, man.” Just to be absolutely clear, that generates 1024 goa_kick() events, one per beat.