Custom Sequence List Generators

Python’s built-in range() list generator is full of possibilities when used in conjunction with Slipmat’s @ scheduler. And that’s just one generator out of, dare I say, a million possibilities.

Imagine being able to conveniently import generators from a massive user library of modules. This will be a reality for Slipmat for two reasons:

I built a list generator called range_plus(), which takes Python’s range() function a few steps farther. range() is limited to integers; range_plus() removes this restriction and allows for floats. range() supports a single increment step value; range_plus() additionally allows users to pass a list of increment step values that are chosen at random.

Here’s the working Python defintion for range_plus():

def range_plus(start, stop, random_steps=1):
    '''
    Returns a list of successive incremented values
    chosen randomly from a list.

    Input:
    start -- Starting value
    stop --  End point
    random_steps -- A list of incremental values chosen at random or a
        single numeric value
        
    Output:
    return -- A numeric list
    '''
    
    if stop < start:  # Avoid infinite loop from bad input
        return []

    if not isinstance(random_steps, list):
        random_steps = [random_steps]

    L = []

    while start < stop:
        L.append(start)
        start = start + random.choice(random_steps)

    return L

I made sure to write the docstrings, to make life easier for anyone who may import my code later. Here's a Python interpreter session to test out some numbers:

>>> from MyListGenerators import range_plus
>>> range_plus(0, 4, 0.5)
[0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5]
>>> range_plus(0, 4, [0.25, 0.25, 0.5, 0.75])
[0, 0.5, 1.0, 1.5, 1.75, 2.5, 3.0, 3.25, 3.75]

As applied to music, range_plus() would be a perfect fit for randomly generating rhythmic patterns, for things such as hi-hats.

@0  @range_plus(0, 4, [0.25, 0.25, 0.5, 0.75]) hat()
@4  @range_plus(0, 4, [0.25, 0.25, 0.5, 0.75]) hat()
@12 @range_plus(0, 4, [0.25, 0.25, 0.5, 0.75]) hat()
@16 @range_plus(0, 4, [0.25, 0.25, 0.5, 0.75]) hat()

Here's is one scenario for what those patterns would look like in trigger notation:

Bar 1:  x.x. x.xx ..x. xx.x
Bar 2:  xx.. x..x ..x. .xx.
Bar 3:  x..x ..x. xx.x .x..
Bar 4:  x.x. .xxx .xxx x..x

The 'x' is a 16th note trigger, the '.' is a 16th note rest, and spaces are used to distinguish between each quarter note.

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.

Nine Rules for Scheduling Events

How would Slipmat’s @ scheduler work when applying various Python methodologies?

To celebrate the release of my friend Sarah MacLean’s new book, Nine Rules to Break When Romancing a Rake, here are 9 rules for scheduling events. Each of the following examples produce the exact same result:

1. Individual Events

@0 foo()
@1 foo()
@2 foo()
@3 foo()

2. List

@[0, 1, 2, 3] foo()

3. Identifier

t = [0, 1, 2, 3]
@t foo()

4. List Comprehension

@[i for i in range(0, 4)] foo()

5. Loop

for i in range(0, 4):
    @i foo()    

6. Function

def bar():
    @0 foo()
    @1 foo()
    @2 foo()
    @3 foo()
    
bar()

7. Nested

foo()
1:
    foo()
    1:
        foo()
        1:
            foo()

8. Item Iteration

items = (0, 1, 2, 3)
@[i for i in items] foo()

9. Map

def bar(x): return x
@(map(bar, range(0, 4)) foo()

And a special message for Sarah, “Ninjas.”

Live Coding and Capturing a Perfomance

It’s the latest fad that’s sweeping computer music. And I would love for Slipmat to have this ability in its arsenal of tools. Without having to sacrifice non-realtime rendering for computationally expensive processes, of course.

The following conceptual live coding prototype shows what a simple session would look like if it was modeled on the Python interpreter:

$ slipmat --capture_performance my_session.txt
>>> from LiveCodeSeq import seq
>>> from MyBassLibrary import rad_rezzy
>>> from random import random
>>> p[0] = [int(random() * 12) for i in range(0, 16)]
>>> p[1] = [int(random() * 12) for i in range(0, 16)]
>>> p[0]
[5, 9, 11, 8, 7, 8, 5, 1, 10, 7, 4, 4, 6, 4, 4, 2]
>>> p[1]
[6, 6, 5, 3, 5, 7, 8, 4, 0, 0, 8, 7, 9, 7, 2, 4]
>>> r = rad_rezzy()
>>> s = seq(instr=r, pattern=p[0], base_pch=6.00, resolution=1/16, tempo=133)
>>> s.start()
>>> s.change_pattern(pattern=p[1], on_beat=0)
>>> @60 s.stop(onbeat=0)

I have a gut feeling that there are some changes that should be made. Though as a starting point, this isn’t a terrible one.

Being able to capture a live coding performance would be fantastic. Not sure how workable it would be, but perhaps such a feature would produce a file that could be played back later:

$ cat my_session.txt
@0             global.seed(7319991298)
@4.04977535403 from LiveCodeSeq import seq
@8.43528123231 from MyBassLibrary import rad_rezzy
@10.9562488312 from random import random
@15.6027957075 p[0] = [int(random() * 12) for i in range(0, 16)]
@20.7757632586 p[1] = [int(random() * 12) for i in range(0, 16)]
@26.2462371683 p[0]
@29.3961696828 p[1]
@34.0424988199 r = rad_rezzy()
@40.3211374075 s = seq(instr=r, pattern=p[0], base_pch=6.00, resolution=1/16, 
                   tempo=133)
@45.5491938514 s.start()
@47.8991166715 s.change_pattern(pattern=p[1], onbeat=0)
@52.6267958091 @60 s.stop(onbeat=0)

The @ schedules are the times in which return was originally pressed for each event. Looks like I’ll be spending some time with ChucK soon.

Early Java Slipmat Prototype

Slipmat has been a pet project of mine for a few years now. The first working prototype was realized in Java. The code for this is up at sourceforge.

Originally, the idea was for slipmat to be a java interface layer that sits on top of Csound. Hence the slipmat metaphor. At one point, it became much too unwieldily and I had abandoned it. Until later when I picked up Python, and decided to give it another go. Python made things much easier all the way around, but I eventually abandoned that too, for reasons I’ll go into later.

Here is an excerpt from one of the original Java prototype examples:

public ControllerExample() {
    /* Create empty SynthRack */
    SynthRack synthRack = new SynthRack(false);
    
    /* Create modules */
    LinearSlide linearSlider = new LinearSlide();
    SynthBass bass = new SynthBass();
    Output output = new Output();

    /* Add modules to the SynthRack */
    synthRack.addModule(linearSlider);
    synthRack.addModule(bass);
    synthRack.addModule(output);

    /* Patch the SynthBass to the Output */
    output.setInput(bass.getOutput());

    /* Create a sequence */
    linearSlider.slide(1.0, 0.05, 2.0, bass.getOscillator1Detune());
    linearSlider.slide(1.125, 0.05, 1.5, bass.getOscillator1Detune());
    bass.playNote(0.0, 8.0, 0.5, 100);
    bass.playNote(2.5, 6.0, 0.25, 200);

    /* Set duration and press play */
    synthRack.setDuration((double) SCORE_DURATION);
    synthRack.startCsound();
}

The full code can be seen here. Once I get things sorted out at sourceforge, I’ll post the Python package there as well.

Importing Modules and Reusing Code

I want to begin discussing the implications of yesterday’s Python-Csound mockup code (which I’ll refer to as slipmat for the time being), starting with with imports:

import Wavetable
from Gen import sine
from Pitch import cpspch

All of Csound’s 1400+ opcodes are available at all times. Great for convenience, perhaps not so great for organization. In contrast, the Python language starts out with only the basics, a clean slate. To extend functionality, users import modules. This is a cleaner approach than having it all hang out. There are some other advantages, too.

First, let’s look at a hypothetical import block. Let’s say you were to design a “computer network music” ensemble inspired by The Hub. Some communication modules you might include:

import Jack
import MIDI
import Network
import OSC

A computer network music ensemble sounds like it might be a complex piece of software. Complex enough where doing all your work in one file would be tedious. So you decide to start a new file, my_network.slip, where you store your own custom opcode/unit generator function definitions. In your main file, you write this to import:

import my_network

Not only can you use my_network for this project, but that code can be reused in any number of future projects. Code reusability is a beautiful thing. In fact, this would apply to any properly written slipmat document. For example, a composition would double as a library of synthesizers that you could plug into your own work:

import Trapped  # Trapped in Convert by Dr. Richard Boulanger
...
signal = Trapped.blue(22.13, 4, 0, 9.01, 600, 0.5, 20, 6, 0.66)

See trapped.csd.

What if Python DNA was Injected into Csound

Last year, I finally weened myself completely off of Perl and learned Python in its place. Colors have never been brighter. There is such an elegance to Python, and I would love to see this in a computer music language.

The following mockup code is what you would get if your combined the awesome powers of Csound with the beauty of Python. I’m taking some liberties. The example is ignorant of i, k and a-rate. Instead of an orchestra/score pair, everything is combined as one file. And I’m introducing a concept for scheduling events, @, which tells when to do something.

#!/usr/bin/env slipmat

import Wavetable
from Gen import sine
from Pitch import cpspch

def sine_arp(dur, amp, pitch, lfo_freq):
    this['dur'] = dur    
    pitch = cpspch(pitch)

    notes = [0, 3, 7, 8]
    arp = Wavetable.osc(1, lfo_freq, notes)
    freq = pitch + pitch * 2 ** (arp / 12.0)
    
    osc = Wavetable.osc(amp, pitch, sine(1000))
    
    output osc

def ping(amp=1.0, freq=262):
    this['dur'] = 0.5
    
    output Wavetable.osc(amp, freq, sine(32))
    
if __name__ == '__main__':
    def harmonic_pattern(freq=100):
        @0 ping(1.0, freq)
        @1 ping(0.8, freq * 2)
        @2 ping(0.6, freq * 3)
        @3 ping(0.4, freq * 4)
        @4 ping(0.2, freq * 5)
        
    sine_arp(4, 0.5, 8.00, 1 / 4)
    @4 sine_arp(4, 0.5, 7.07, 1 / 4)
    
    @8 harmonic_pattern()
    @13 harmonic_patten(cpspch(7.00))
    
    @18 ping()

I’ll comeback with a commented version of this in a few days. Though unlikely, it is my hope that some of you will take the time to try to figure out what is going on. My philosophy is that code should be human readable, and that syntax can help reinforce this.

CsMultitouch – Multitouch User Interfaces for Csound

This is a demonstration of the software i wrote for my MSc In Music Technology at DKIT under the supervision of Rory Walsh. The software is used to create custom multitouch user interfaces for controlling Csound Instruments. It allows users to define gui elements, such as sliders and buttons, in their csound file. The .csd file is parsed by CsMultitouch to retrieve this information. Said information is then used to create the multitouch interface using the PyMT framework.

I want this.

Csound Journal Issue 6

The 6th issue of the Csound Journal has been published at cSounds.com. Here is the list of articles pressent in this edition:

  • Aspects of Bandlimiting by James Hearon
  • Developing LADSPA Plugins with Csound by Victor Lazzarini and Rory Walsh
  • Using Python Inside Csound by AndrĂ©s Cabrera
  • Perl and Csound – Part 2 by Jacob Joaquin (that’s me)