I did something I should have done awhile ago, and that’s invest into a SoundCloud account. I recently rendered all the Csound Blog examples and uploaded them. So now it’ll be easier for many of you to listen to clips without having to render them yourselves.
Monthly Archives: May 2010
Simple as Possible
Now that the Slipmat dev code is up at github, I can write smaller examples rather than cram everything into one large script. The example test_tone.py is written specifically to demonstrate the simplest possible script that generates audio. Here’s the code:
import slipmat s = slipmat.ScoreEvents() s.event(0, 4, slipmat.Sine()) slipmat.ScoreEventsToWave(s, "./test_tone.wav")
This produces a 4-second 440Hz sine tone, and writes it to a wave file.
Here’s the line-by-line run down. First, the slipmat module is imported, essentially turning Python into a synthesizer. Then a score object is created from class slipmat.ScoreEvents(); this is akin to a Csound score file, as it is responsible for scheduling events for unit generators and instruments. A slipmat.Sine() object is instantiated then scheduled to play for 4-seconds using the default frequency of 440Hz. Then slipmat.ScoreEventsToWave() parses the score and renders the audio, writing the results to the file “./test_tone.wav”.
This is how it looks today. As Slipmat changes and evolves, so will this example.
_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.
Audio: Lead-In
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.
