Skip to content

Instantly share code, notes, and snippets.

@charlieroberts
Last active April 15, 2018 03:00
Show Gist options
  • Select an option

  • Save charlieroberts/4d466a1e73575797870cceffa482e81a to your computer and use it in GitHub Desktop.

Select an option

Save charlieroberts/4d466a1e73575797870cceffa482e81a to your computer and use it in GitHub Desktop.
Notes from a workshop on using gibberwocky for PLOrk

# PLOrk gibberwocky workshop 4/12/2018

Some important not-very-well documented aspects of gibberwocky

This section will mainly focus on writing custom patterns and pattern transformations, which should enable you, with a bit of time and programming knowledge to create any type of generative music system your heart desires in gibberwocky.

A sequence consists of a pattern for output and a pattern for scheduling (“values” and “timings”). Each pattern is just a function that returns a value.

When you pass arrays or special objects to any call to .seq, this objects are wrapped in functions. For example, consider the following sequence:

namespace('test').seq( [0,1,2,3], 1 )

The first array of numbers (0,1,2,3) is converted to a function that outputs each value, one at a time, progressing linearly and looping when it reaches the end of the arrray. The second argument to .seq is converted to a function that always outputs the same value, in this case 1.

The important insight is that we can easily write our own pattern generators to exhibit any behavior we choose. For example, the following codeblock is functionally identical to the single line of code above:

let   idx = 0
const values = [0,1,2,3]
const valuesFunc = ()=> values[ idx++ % values.length ]

namespace('test').seq( valuesFunc, ()=> 1 )

Note that we can also chain these types of functions using an array. For example, here’s an additional function that skips through the array two at a time; we’ll add that to our previous test sequence:

let   idx = 0
const values = [0,1,2,3]
const valuesFunc = ()=> values[ ++idx % values.length ]
const skipper = ()=> values[ ( idx += 2 ) % values.length ]

namespace('test').seq( [ valuesFunc, skipper ), ()=> 1/2 )

You can create your own custom pattern transformations.

If you’ve gone through the patterns and pattern transformations tutorial, you might know that there are a lot of simple serialist techniques that you’re able to apply to patterns, for example .reverse, .rotate, .transpose etc. In gibberwocky we can also use custom functions (or filters)to create these pattern transformations.

The function signature for a pattern filter is as follows:

filter( args, pattern )

Both the argsarray and the pattern object will automatically be passed to the function once it has been added as a filter to a pattern in a running sequence. While the pattern variable simply refers to the pattern that contains the filter, the args array contains three values. The first is the output of the pattern, which we can modify before returning it from our function. The second is a modifier for the phase of the pattern, which will normally be 1 if the pattern is advancing linearly through a sequence, but could also be zero if we want to repeat a value or a higher number if we want to skip values like we did earlier. The final value is the current index.

Let’s say we wanted to create a pattern filter that would repeat every value it outputs twice. We could do that as follows:

const repeatTwice = ( args, pattern ) => { args[1] = .5; return args }
namespace('test').seq( [0,1,2,3], 1/4 )
namespace('test')[0].values.filters.push( repeatTwice )

Note that we are modifying the args array that the filter is passed and then returning it. For example, if we wanted to scale a pattern by value, we could do so as follows:

const scale = ( args, pattern ) => { args[0] *= 2; return args }
const repeatTwice = ( args, pattern ) => { args[1] = .5; return args }
namespace('test').seq( [0,1,2,3], 1/4 )
namespace('test')[0].values.filters.push( repeatTwice, scale )

You can also write this a bit more tersely, which is useful for long chains:

namespace('test').seq( [0,1,2,3], 1/4 )
namespace('test')[0].values.filters.push( 
  ( args, pattern ) => { args[0] *= 2; return args },
  ( args, pattern ) => { args[1] = .5; return args }
)

Some ideas that I typically use in performances

  1. Rotate two Euclidean rhythms against each other. I do this all over the place, for melodies, bass lines, and beats.
devices['drums'].midinote.seq( 36, Euclid(5,8) )
devices['drums'].midinote.seq( 36, Euclid(5,16), 1  )
devices['drums'].midinote[1].timings.rotate.seq( 1,1 )
  1. Use two oscillators with differing frequencies / amplitudes to drive mono synths.
devices['bass'].note.seq( sine(1,0,3), Euclid(3,8) )  
devices['bass'].note.seq( sine(2,0,4), Euclid(4,9), 1 )

A video of using oscillators to drive sample-and-hold patterns: https://www.youtube.com/watch?v=dieGOIrp2L4&feature=youtu.be

  1. Make sure to remember to sequence velocity (assuming your synth is velocity enabled). You can either sequence velocity for all sequences targeting a particular synth, or for individual sequences:
// control all velocities on 'velocitysynth'. note that if you don't pass
// a scheduling pattern, gibberwocky will call this sequence every time
// a note is triggered. here we use a sine oscillator to gradually
// move velocities from 2-62.
devices['velocitysynth'].velocity.seq( sine( 2, 32, 30 ) )

// tell our note sequence number 2 to use a different sine osc for
// velocities; all other sequnces will use the previous one we assigned.
devices['velocitysynth'].note[2].velocity.seq( sine( 1, 15, 10 ) )
  1. Transitions are tricky! One function that should really be documented In the tutorials is the fade() function. It accepts three parameters: a duration, a starting value, and an ending value. I often find that about a fifth of my code is fading different instruments in and out, fading in and out FX etc.

Simple example of a fade:

devices['bass'].note.seq( sine(2,0,3), Euclid(5,16) )
devices['bass'].mod_wave( fade(16,0,127) )

NOTE: ACK! Fades are apparently broken for Max/MSP. I will fix this ASAP.

  1. I love HexSteps for defining beats. However, Steps is also great when you have a velocity-sensitive drum synth to work with. If you’re doing drumbeats definitely check both of those objects out; there’s tutorials for both included in gibberwocky.

  2. Sequences that have an odd number of values vs. an even number of timings (or vice-versa) wind up creating irregular patterns that eventually “loop”.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment