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.