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.

Lists as Micro-Sequencers

On Friday, I listed nine ways in which python methodologies could be used with the @ scheduler. How would they work in a real-world musical context? Today, I’m showcasing the List as a super convenient micro-sequencer.

When the @ scheduler is given a list of numbers, every value in the list is used to schedule an event; This saves keystrokes and increases legibility. Let’s see this applied to a simple four-beat rock groove with 8th note hats:

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

That plays hat() eight times, and snare() and kick() twice each. This beats having to type out 12 events.

Alternatively, an identifier can point to a predefined list, thus, a sequence can be reused multiple times. The following stores a complex hi-hat pattern in identifier p, and then plays it four times:

p = [0, 0.5, 1, 1.5, 2, 2.25, 2.5, 2.75, 3, 3.75]

@0  @p hat()
@4  @p hat()
@8  @p hat()
@12 @p hat()

Here’s the shorthand equivalent:

p = [0, 0.5, 1, 1.5, 2, 2.25, 2.5, 2.75, 3, 3.75]

@[0, 4, 8, 12] @p hat()

That’s 40 events in two lines of code, with improved legibility. If this was presented as 40 individual events, it would not be obvious that the same hat pattern is repeated four times.

Banks of Patterns

A list can be utilized as a bank of patterns, a list of lists. In the following example, an empty bank is created, filled with three patterns, and then used in a four measure sequence.

b = []
b.append([0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5])               # x.x. x.x. x.x. x.x.
b.append([0, 0.5, 1, 1.5, 2, 2.25, 2.5, 2.75, 3, 3.75])  # x.x. x.x. xxxx x..x
b.append([0, 0.5, 1, 1.5, 2, 2.5, 2.75, 3, 3.5, 3.75])   # x.x. x.x. x.xx x.xx

@0  @b[0] hat()
@4  @b[1] hat()
@8  @b[0] hat()
@12 @b[2] hat()

Bonus Round — Eight Ways to Notate 8th Note Hats

Some good, some bad, some ugly. All produce the same result.

1. @map(lambda x: x / 2.0, range(0, 8)) hat()
2. @[0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5] hat()
3. @[i / 2.0 for i in range(0, 8)] hat()
4. @[i / 2.0 for i in range(8)] hat()
5. @[0, 2] @[0, 1] @[0, 0.5] hat()
6. @[0, 1, 2, 3] @[0, 0.5] hat()
7. @range(0, 4) @[0, 0.5] hat()
8. @range(4) @[0, 0.5] hat()

Examples 2, 6 and 8 are my personal favorites.