-
Notifications
You must be signed in to change notification settings - Fork 3
Home
What is Gibberwocky for Max?
Brings together the marvelous magic of Max/MSP and the glorious live coding of Gibber. A quick way to augment a Max patcher with live coded sequencing & modulations. Live coding happens in a browser-based text editor, following similar idioms as in Gibber, a widely-used web-based live coding language. Modulations can be messages, UI knob tweaks, and even arbitrary signal graphs.
Open Max's Package Manager, find & install the gibberwocky
package.
Alternatively, download/checkout https://github.com/gibber-cc/gibberwocky.max into Documents/Max 8/Packages
(or My Documents/Max 8/Packages
on windows)
Make sure you have a recent version of Chrome (or Firefox or Safari (or maybe even Edge now)).
Add a [gibberwocky]
object to your Max patcher. Turn on audio in the patcher, if it isn't already on.
Note: you should only have 1
[gibberwocky]
object open at any time.
Send the [gibberwocky]
object a bang
to open the editor (alternatively, just open http://gibberwocky.cc/burble in your browser). In the editor's console, if it says "gibberwocky.max is ready to burble", you're all set!
Note: You might need to open port 8081 on your firewall.
Gibberwocky => Max:
-
message
=> messages/lists from [gibberwocky] 1st outlet -
param
=> messages to objects with scripting names -
device
=> messages to Live device objects (including sending midinotes and setting parameters) -
signal
=> audio outlets of [gibberwocky]
All of the above can also be sequenced with patterns and pattern generators.
Single lines: put the cursor on the line and press Ctrl-Enter.
Multiple lines: select the code, and press Ctrl-Enter.
You can stop all sequences in gibberwocky with the Ctrl+. keyboard shortcut (Ctrl + period).
Open the "schemas" tab and select the mom
(Max object model) to see what entry points have been detected. This list gets updated whenever you save the patcher with the [gibberwocky]
object in it. You can drag any item in the list into the editor to quickly create the corresponding code to modulate it.
message("hello")("world")
...will send the message hello world
out of the gibberwocky 1st outlet. So will:
message("hello.world")
message(“hello.world”) => “hello world”
This can get as long as you need:
synth1.a.b.c.d.e.f.g.h( 'i?' ) // sends 'synth1 a b c d e f g h i?'
A message path of this kind can also be stored as an callable object, for re-use:
hello = message("hello")
hello("world")
yo = message(“yo”)
yo(“hi”) => “yo hi”
yo(“lo”) => “yo lo”
yo.seq(“yo”, 1/4) => “yo yo” … “yo yo” … “yo yo” …
Messages can also be sequenced into repeating patterns, using .seq(values, timings)
; where timings are normally expressed as multiples/fractions of a whole note.
// sends "hello world" every whole note:
message("hello").seq("world", 1)
// sends "hello world" every quarter note:
message("hello").seq("world", 1/4)
If the arguments to seq(value, timing)
are arrays, they will be iterated over each time:
// alternates between sending "hello me" and "hello you" every quarter note
message("hello").seq(["me", "you"], 1/4)
rndf()
and rndi()
are used to generate a single random float or integer
// make sure you have the console tab in the gibberwocky sidebar
log( rndf() ) // outputs floats between 0-1
log( rndi() ) // outputs either 0 or 1
// although 0 and 1 are the default min/max values, we can pass
// arbitrary bounds:
log( rndf(-1,1) )
log( rndi(0,127) )
Arrays of random values: if we pass a third value, we can create multiple random numbers at once, returned as an array.
log( rndf( 0,1,4 ) ) // array of four integers, each either 0 or 1
log( rndi( 0,127,3 ) )
Random generators: Sometimes you don't want a random number immediately, but rather want to sequence calls to generate random numbers. The Capitalized versions of rndi and rndf do that job:
message("die").seq(Rndi(6), 1/4) // random die roll every beat
Forwards messages directly to Max objects with scripting names.
For example, can be handy to name attrui
or other UI objects and address them directly without drawing patch cords everywhere:
params["foo"](74) // sends a message `74` to the object with scripting name "foo"
params["foo"].seq(Rndi(127), 1/4) // on every quarter note, sends a random integer up to 127 to the object with scripting name "foo"
Live Devices are usually given scripting names by default, which makes it easy to address them. The MOM also grabs all the parameters within a device so you can modulate those directly.
// set the synth resonance:
devices['synth']['filter_resonance'](.95)
// alternate filter cutoff between 100Hz and 10kHz every beat:
devices['synth']['cutoff'].seq([100, 10000], 1/4)
Devices that are also instruments will also respond to note
and midinote
events, opening up some more musical parameterization and pattern transformation options.
devices['bass'].note( 'fb2' )
devices['synth'].note.seq( ['c4','e4','g4'], 1/8 )
In gibberwocky, the default scale employed is C minor, starting in the fourth octave. This means that if we pass 0 as a value to note(), C4 will also be played:
// same:
devices['synth'].note( 'c4' )
devices['synth'].note( 0 )
Passing integers to note()
thus selects relative values in the current scale:
devices['synth'].note.seq( [-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7], 1/16 )
To change the current scale (root and mode):
Scale.root( 'd4' )
Scale.mode( 'lydian' )
Scale.root( 'c4' )
Scale.mode( 'phrygian' )
We can also sequence changes to the root / mode:
Scale.root.seq( ['c4','d4','f4','g4'], 2 )
Scale.mode.seq( ['lydian', 'ionian', 'locrian'], 2 )
Stop the scale sequencing:
Scale.root[0].stop()
Scale.mode[0].stop()
Scale.root( 'c4' )
We can also define our own scales using chromatic scale indices. Scales can have arbtrary numbers of notes.
Scale.modes[ 'my mode' ] = [ 0,1,2,3,5,6,10 ]
Scale.mode( 'my mode' )
We can also change the octave centre of an instrument:
devices['synth'].octave(-2)
Using note names:
devices['synth'].chord( ['c4','eb4','gb4','a4'] )
Or chord names:
devices['synth'].chord( 'c4maj7' )
devices['synth'].chord( 'c#4sus7b9' )
Or using scale indices:
devices['synth'].chord( [0,2,4,5] )
// midi note number:
devices['synth'].midinote( 60 )
// midi note number, midi velocity, duration in ms:
devices['synth'].midinote( 60, 120, 1000 )
Note, velocity, and duration can also be sequenced independently:
devices['bass'].midinote.seq( 60, 1/2 )
devices['bass'].velocity.seq( [16, 64, 127], 1/2 )
devices['bass'].duration.seq( [10, 100,500], 1/2 )
// midi note numbers in an array:
devices['synth'].midichord( [60, 63, 67] )
Note how only one of these will actually run, even if you trigger both:
devices['synth'].midinote.seq( 72, 1/4 )
devices['synth'].midinote.seq( 48, 1/4 )
By default, gibberwocky will replace an existing sequence with a new one.
To set up multiple sequences, you can add a "track" ID number as a third argument to .seq():
devices['synth'].midinote.seq( 60, 1/2, 1 )
devices['synth'].midinote.seq( 72, 1/3, 2 )
devices['synth'].midinote.seq( 84, 1/7, 3 )
You can access the data of track 0 like this:
log( devices['synth'].midinote[0].values.toString() )
log( devices['synth'].midinote[0].timings.toString() )
When you launch a sequence on a "track" that has the same ID as another running sequence, the older sequence is stopped. If the sequences have different IDs they run concurrently. This makes it really easy to create polyrhythms.
Note: In the examples of sequencing we've seen so far, no ID has been given, which means gibberwocky is assuming a default ID of 0 for each sequence.
devices['synth'].midinote.seq( 48, 1 ) // assumes ID of 0
You can also stop all sequences on a specific object:
devices['synth'].stop()
We can also sequence calls to chord() and midichord(). To move between different chords, we need to pass an array of arrays:
devices['synth'].midichord.seq( [[60,64,68], [62,66,72]], 1/2 )
devices['synth'].chord.seq( [[0,2,4,5], [1,3,4,6]], 1 )
We've seen that the first outlet of gibberwocky is used for messaging. The remaining outlets are used for signals. You can determine the number of outlets using the @signals property; for example, [gibberwocky @signals 4] has four outputs for audio signals in addtion to its messaging output (for a total of 5).
You can send gen~
graphs from the browser to gibberwocky to be compiled and run within Max. Most gen~ operators are available in gibberwocky.
Here's a simple ramp from 0 to 1 at 1Hz -- hook up the 2nd outlet of [gibberwocky]
to a [scope~]
to see it in action:
signals[0]( phasor(1) )
In gen~, the cycle
function generates a sine oscillator (as opposed to sin
, which calculates the mathematical sin of a number). Let's start by passing a straight sine oscillator to our leftmost signal output.
osc = cycle(4)
signals[0]( osc )
We can access the properties of any gen~ object using bracket notation, starting from 0
. In the above example, the first parameter of our osc
object is the frequency (which we set to four) which means we can change it as follows:
osc[0]( 2 )
// sequence changes to frequency over time
osc[0].seq( [2,4,8], 1/2 )
Note that the cycle ugen generates a full bandwidth audio signal with a range of {-1,1}. Oftentimes we want to specify a center point (bias) for our sine oscillator, in addition to a specific amplitude and frequency. Here's an LFO with scale and bias applied (so we can control the low and high values of the curve in addition to the frequency).
osc = add( 1, mul( 0.5, cycle(2) ) // has 3 parameters: 1 (bias), 0.5 (scale), 2 (frequency)
In the above codeblock, osc[0]
controls the bias, osc[1]
controls the gain, and osc[2]
controls the frequency. That is, the number in the []
brackets refers to the position of the parameter in the original code of the graph.
Gibberwocky also provdes a few richer functions not present in gen, including an lfo()
equivalent to the above:
// frequency, bias, amplitude
mylfo = lfo( 2, 1, 0.5 )
signals[0]( mylfo )
Some more useful operators:
sine(period_in_beats, center_bias, amplitude) // sine wave (-1..1) with named properties: "period", "bias", "amp", "phase"
lfo(freq_in_hz, center_bias, amplitude) // sine wave (-1..1) with named properties: "period", "bias", "amp", "phase"
beats(period_in_beats) // ramp wave (0..1), not bandlimited
btof(period_in_beats) // convert period_in_beats to a frequency in Hz
Gibberwocky's seq()
can also handle functions in place of arrays, to generate patterns for us.
Any Javascript function can be used to generate values for seq()
, but Gibberwocky provides a few rich and useful examples oriented to specific tasks:
-
Pattern()
: an arbitrary sequential pattern that can return a sequence of values over and over again, which can also be modifed as it plays -
Arp()
: an arpeggiator designed to generate values fornote()
- Generators designed to create timing values for
seq()
:-
Euclid()
: timings based on Euclidean rhythms -
Hex()
: timings based on binary patterns -
Automata()
: timings based on 1D cellular automata
-
- Signal generators can also be used in sequences; the signals will be "sampled" at the timing points to generate new values
Gibberwocky also provides some objects to quickly spawn multiple sequences:
-
Steps()
: a set of generators optimized for drum machine patterns with strings -
HexSteps
: Multiple note sequences likeSteps()
, but using patterns likeHex()
An arbitrary sequential pattern that can return a sequence of values over and over again.
mypattern = Pattern( 60,62,64,65 )
log( mypattern() ) // 60
log( mypattern() ) // 62
log( mypattern() ) // 64
log( mypattern() ) // 65
log( mypattern() ) // back to 60...
Patterns can be used in place of arrays for sequencing:
devices['bass'].midinote.seq( mypattern, 1/8 )
We can access these patterns using the "track" ID, and thus apply transformations:
devices['bass'].midinote[0].values.reverse()
devices['bass'].midinote[0].values.transpose( 1 ) // add 1 to each value
devices['bass'].midinote[0].values.scale( 1.5 ) // scale each value by .5
devices['bass'].midinote[0].values.rotate( 1 ) // shift values to the right
devices['bass'].midinote[0].values.rotate( -1 ) // shift values to the left
devices['bass'].midinote[0].values.reset() // reset to initial values
Just like anything else, we can even sequence these transformations:
devices['bass'].midinote[0].values.rotate.seq( 1,1 )
devices['bass'].midinote[0].values.reverse.seq( 1, 2 )
devices['bass'].midinote[0].values.transpose.seq( 1, 2 )
devices['bass'].midinote[0].values.reset.seq( 1, 8 )
Designed to generate patterns of timings for seq()
You can also specify Euclidean rhythms using the Euclid() function, which returns a pattern:
devices['drums'].midinote.seq( 36, Euclid(5,8) )
Euclidean rhythms are specifcations of rhythm using a number of pulses allocated over a number of beats. The algorithm attempts to distribute the pulses as evenly as possible over all beats while maintaining a grid. Examples of these distributions are given below (where 'x' represents a pulse and '.' represents a rest):
1,4 : x...
2,3 : x.x
2,5 : x.x..
3,5 : x.x.x
3,8 : x..x..x.
5,8 : x.xx.xx.
4,9 : x.x.x.x..
5,9 : x.x.x.x.x
As in Gibber, by default the number of beats chosen also determines the time used by each beat; selecting '5,8' means 5 pulses spread across 8 1/8 notes. However, you can also specify a different temporal resolution for the resulting pattern: '5,8,1/16' means 5 pulses spread across 8 beats where each beat is a 1/16th note.
You can read a paper describing Euclidean rhythms here: http://archive.bridgesmathart.org/2005/bridges2005-47.pdf
The Hex function creates rhythmic patterns by turning hexadecimal numbers into binary patterns, based off an idea originally implemented by Steven Yi.
Hex objects accept strings of hexadecimal numbers (0-9, a-f). Each hexadecimal number is responsible for populating four 1/16th notes (by default) with pulses and rests. A value of 0 means no pulses are present.
devices['drums'].midinote.seq( 36, Hex('0') ) /* 0000 */
devices['drums'].midinote.seq( 36, Hex('1') ) /* 0001 */
devices['drums'].midinote.seq( 36, Hex('2') ) /* 0010 */
devices['drums'].midinote.seq( 36, Hex('3') ) /* 0011 */
devices['drums'].midinote.seq( 36, Hex('4') ) /* 0100 */
devices['drums'].midinote.seq( 36, Hex('5') ) /* 0101 */
devices['drums'].midinote.seq( 36, Hex('6') ) /* 0110 */
devices['drums'].midinote.seq( 36, Hex('7') ) /* 0111 */
devices['drums'].midinote.seq( 36, Hex('8') ) /* 1000 */
devices['drums'].midinote.seq( 36, Hex('9') ) /* 1001 */
devices['drums'].midinote.seq( 36, Hex('a') ) /* 1010 */
devices['drums'].midinote.seq( 36, Hex('b') ) /* 1011 */
devices['drums'].midinote.seq( 36, Hex('c') ) /* 1100 */
devices['drums'].midinote.seq( 36, Hex('d') ) /* 1101 */
devices['drums'].midinote.seq( 36, Hex('e') ) /* 1110 */
devices['drums'].midinote.seq( 36, Hex('f') ) /* 1111 */
Longer strings create longer patterns:
devices['drums'].midinote.seq( 36, Hex('92') ) /* 10010010 */
Normally the step size is 1/16th notes, however you can change this with a second argument:
// set step size to quarter notes:
devices['drums'].midinote.seq( 36, Hex('8', 1/4) ) /* 1000 */
Designed to work with note()
myarp = Arp( [0,2,4,5], 4, 'updown' ) // other modes include 'up' and 'down'
log(myarp())
// randomize arpeggiator
myarp.shuffle()
// transpose arpeggiator over time
myarp.transpose.seq( 1,1 )
// reset arpeggiator
myarp.reset()
Gibberwocky enables you to define continuous signals that are periodically sampled to create patterns, a common technique in the modular synthesis community that was popularized in live coding by the Impromptu and Extempore environments. For example, to use a sine oscillator to generate a repeating musical scale:
devices['bass'].note.seq( sine( 1, 0, 4 ) /* period (in beats), center, amplitude */, 1/8 )
As another example, in multi-samplers / drum machines different midi notes trigger different sounds. Using signals to select sounds can yield interesting patterns over time.
The below oscillator will range from 36–44. Note that we initialize the sine() with phase 0.75 to make sure that the kick drum triggers on beat 1.
devices['drums'].midinote.seq( sine( 1, 40, 4, .75 ), 1/16 )
A Lookup()
object can lookup a value in an array based on a signal.
For example, here's an example specifically sequencing a snare drum pattern:
devices['drums'].midinote.seq( 38, Lookup( beats(4), [ 1/32, 1/16, 1/8, 1/4 ] ))
Steps() creates a group of sequencer objects. The first argument to Steps is an object that maps MIDI note numbers to sequencer patterns. The second argument to Steps is the instrument to target.
Each sequencer is responsible for playing a single note. The velocity of each note is determined by a hexadecimal value (0-f), where f is the loudest note. A value of '.' means that no MIDI note message is sent
The lengths of the patterns can differ. By default, the amount of time for each step in a pattern equals 1 divided by the number of steps in the pattern. In the example below, most patterns have sixteen steps, so each step represents a sixteenth note. However, the first two patterns (60 and 62) only have four steps, so each is a quarter note.
Note that while the example below is designed to work with the Analogue Drums device found in the gibberwocky help file, that instrument is actually NOT velocity sensitive.
steps = Steps({
[36]: 'ffff',
[38]: '.a.a',
[41]: '........7.9.c..d',
[43]: '..6..78..b......',
[45]: '..c.f....f..f..3',
[42]: '.e.a.a...e.a.e.a',
[46]: '..............e.',
}, devices['drums'] )
The individual patterns can be accessed using the note numbers they are assigned to:
// rotate one pattern (assigned to midinote 71)
// in step sequencer every measure
steps[42].rotate.seq( 1,1 )
// reverse all steps each measure
steps.reverse.seq( 1, 2 )
Multiple note sequences like Steps()
, but using patterns like Hex()
:
h = HexSteps({
// kick
36:'82008224',
// snare
38:'0808',
// closed hat
42:'bbbf',
// "cowbell"
45:'ab5a'
}, devices.drums )
In fact, any function can be used inside of a sequence:
myvalues = function() {
// generate a random note number:
return 60 + 2*Math.ceil(Math.random() * 3);
}
devices['bass'].midinote.seq( mypattern, 1/8 )
So, if we wanted to sequence a random midinote to the 'bass' device in the gibberwocky help patcher, we could sequence a function as follows:
devices['synth'].midinote.seq( ()=> rndi(40,100), 1/8 )
// or
devices['bass'].note.seq( Rndi(0,7), 1/8 )
Arrays can also be turned into random picker functions:
// randomly play open or closed hi-hat every 1/16th note
devices['drums'].midinote.seq( [42,46].rnd(), 1/16 )
Resources
Docs: http://gibberwocky.cc
For a complete description of the gibberwocky API, see the gibberwocky reference. Otherwise the various tutorial are the best place to get started.
For questions, answers, and announcements try the mailing list or the gibber Slack channel for more informal discussion. If you aren't already a member of the Live Coding Slack group there's a site that generates automatic invites.
Source code: https://github.com/gibber-cc/gibberwocky.max
sine(period_in_beats, center_bias, amplitude) // sine wave (-1..1) with named properties: "period", "bias", "amp", "phase"
cycle(freq_in_hz) // sine wave (-1..1)
lfo(freq_in_hz, center_bias, amplitude) // sine wave (-1..1) with named properties: "period", "bias", "amp", "phase"
beats(period_in_beats) // ramp wave (0..1), not bandlimited
phasor(freq_in_hz) // ramp wave (0..1), not bandlimited
btof(period_in_beats) // convert period_in_beats to a frequency in Hz
add(a, b), sub(a, b), mul(a, b), div(a, b) // add, sub, multiply, divide etc. two signals