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.

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.

Rendering 1 Second of Audio Data

Yesterday’s python script rendered 1 frame of a rudimentary additive synthesizer. Today’s script renders 1 second. You can download the complete example at textsnip or at snipt.

To render multiple frames of audio, I added two classes: Mixer and Run.

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 self.iter_1.next() + self.iter_2.next()
    
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

Mixer is an iterator class that takes two iterator objects as its input. The values yielded by the inputs are summed together. Yesterday’s method was only good for one frame; A Mixer object does not have this restriction.

The Run iterator class is designed to loop through the iterator/audio graph over a user-defined duration of time, creating multiple frames of audio data.

The follow snippet of code resembles yesterday’s example as it creates a graph of two sine waves (a1 & a2) being fed into a mixer (amix.) The Run object is given a duration of 1 second, which produces 4410 frames (sr / ksmps) and 44100 samples (10 samples per frame.)

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

Still no output to an audio file. What it does output is a printed list of frames and samples. Here’s frame 4276:

4276 :
    0.128148365438
    0.138541849949
    0.14709747835
    0.153592896452
    0.157826911985
    0.15962183375
    0.158825589584
    0.155313602303
    0.148990404968
    0.139790979215

BTW, I’m trying a new service, textsnip.com, for storing and sharing my scripts online. If you have a better recommendation, let me know. Update: textsnip seems to add a couple of gremlins to the autodocs, so I’m trying out snipt.org as well.

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 / self.sr
        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:

1.0
1.28454525252
0.602909620521
-0.602909620521
-1.28454525252
-1.0
-0.333488736227
-0.0151243682287
0.0151243682287
0.333488736227

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.

Beginning Csound @ NYC Resistor

Beginning Csound

Beginning Csound
July 28, 2008 @ NYC Resistor
1 Session, 3 hours, with personalized post-session project with instructor via email.
Cost $75

Csound is the most powerful computer music language in the world, with a direct lineage to Max Mathews’ original Music-N languages. The focus of this class will be a synthesis of three topics: The Csound language, synthesizer theory, and composing weird alien music.

Together, we will demystify the assembly-like syntax of the Csound language. We will cover the fundamentals of synthesizer theory, including: oscillators, filters, envelopes, amplifiers and modulation. Finally, we’ll tie it all together by composing sounds in the vein of classic Sci-Fi movies.

Taught by Jacob Joaquin (that’s me.) Click here to enroll.