tl;dr

what: a max for live patch that lets you write javascript functions in Ableton

where: download it here

why: so you can do cool things like a seeded random selector knob

how: max for live javascript eval function

I was exploring a concept I had deemed functional reactive compositions when I discovered that all I really wanted was to write a script here and there in my Ableton sets.

The idea behind functional reactive compositions was inspired by current trends in the javascript community—namely, that there should be a single source of truth for your data, and that an application is reactive to this data. This is the idea behind spreadsheets. You input an equation in one cell that automatically updates any time the cells mentioned in its equation update. Thus, a reactive musical composition is a set of performers who react to a score (kind of like a band reading charts). This neatly divides up music production into data-composition and system-composition. You’re either writing the data that’s fed into the system or you’re designing the performers of that data.

After playing around in Ableton for a few days, I developed a set that contained a single MIDI clip and a host of tracks all reacting to the automation and midi notes in that one clip. This seems like a lot of work, but the payoff is that all of the instruments I built and sounds I designed using this approach were completely reusable in other projects.

There were several moments while working on this set that I wanted a simple map, filter, reduce, or switch function. I built a few gangly max for live devices to achieve these, but really wished I could just evaluate a little bit of code. On a hopeful whim, I opened up the max javascript environment and typed eval(post('hello world!')) and lo and behold! The message appeared in the console. A few days later and I had a max for live patch with eight inputs and eight outputs, evaluating a javascript function that spit out seeded random numbers to select arbitrary samples in a drum rack.

This is one of the most successful balances of art and code I’ve found to date and I’m excited about sharing it with the world. You can download the patch here. If you discover any bugs in the patch, please post an issue on the repo (or if you’re a max wizard coder type, submit a PR).

Walkthrough

To get everyone up and running quickly with this patch, I’ve written up a walkthrough and a few common patterns I’ve found myself using repeatedly. All of the helper functions built into the script can be found in the README in the github repo.

To get started, when you first load the device, you’ll be greeted with this boilerplate code:

"

if (isNote) {}

else {

out(1, val1);

}

"

The quotes are required to eval the script, so your code must always be wrapped in them. isNote is a boolean signifying that the script was triggered by an incoming midi note. This is vestigial from when I had hoped to do midi processing before determining it was too slow and unreliable. However, it may still be useful to react to note events for non-time-critical transformations, so I left it in. The else clause will fire whenever an input value changes. What are the input values? You may be wondering. Onward to the examples!

You have access to variables named var1 , var2 , var3 , etc… to var8 . these correspond to the eight dials on the left, and are floating point values between zero and one. To output a value, use the function out(port, value) . The value of port corresponds to the eight map buttons on the right. So to output val1 to the first map button, your code would look like:

"

if (isNote) {}

else {

out(1, val1);

}

"

Which is exactly what we have in the boilerplate. If you don’t mind the code being evaluated on midi note events, you can also simply write:

"

out(1, val1)

"

For brevity, we’ll assume that the following code in this article is in the else clause of the boilerplate unless it contains the surrounding parentheses.

One of the most useful helper functions is the scale function, which has the signature: scale(val, inLow, inHigh, outLow, outHigh) . An example of using the scale function could be something like this, which maps the input value to a sine function:

var freq = 100

val1 = scale(Math.sin(val1 * freq), -1, 1, 0, 1)

out(1, val1)

All output values need to be between 0 and 1, since this is what Ableton parameters expect. Since you frequently end up scaling input values, there is an additional helper function called valscale that has the signature: valscale(val, outLow, outHigh) . Expanding on the previous snippet, we could control the rate of the sine with the val2 knob by change the code to look like this:

var freq = valscale(val2, 2, 1000)

val1 = scale(Math.sin(val1 * freq), -1, 1, 0, 1)

out(1, val1)

You can output to as many of the map buttons as you like, as many times as you like. To output the inverse phase of val1 to output 2, we’d change the code to look like this:

var freq = valscale(val2, 2, 1000)

val1 = scaletoval(Math.sin(val1 * freq), -1, 1)

out(1, val1)

out(2, 1 - val1)

This example also shows a handy function called scaletoval , which takes a value and a range and returns a scaled value between 0 and 1.

You can output as many times as you like. To illustrate this, I’ll use the helper function setTimeout to output a random value 500 milliseconds later if val1 is 1:

out(1, val1)

if (val1 === 1) {

setTimeout(function(){

out(1, Math.random())

}, 500)

}

Be careful with this function! It can lead to very confusing bugs and behavior.

The last technique I’ll show here is how to do initialization in your script. Initializing values that change over time can lead to difficult bugs, so be wary. An empty object called state is provided for saving values across evaluations. You can use the state object to store things like the previous evaluated values in order to smooth random noise. The pattern to initialize values looks like this:

"

if (!state.init) {

// do init stuff here

state.init = true

} if (isNote) {}

else {

out(1, val1);

}

"

If you want to change something in your init function after the first time it is called, then you need to add state.init = false to the top of your script, wiggle a value, then erase state.init = false . This will force the init clause to be run again. An example of using the init technique to output a random value every 500 ms would look like this:

"

if (!state.init) {

var pingOutTwo = function() {

setTimeout(function() {

out(2, Math.random())

pingOutTwo()

}, 500)

}

pingOutTwo()

state.init = true

}

if (isNote) {}

else {

out(1, val1);

}

"

Again, this is difficult to debug, so be careful! Next, I’d like to share some common usages I’ve found myself writing over and over again:

Examples

Control a value using a macro while still being able to change the original value

// right click val1 and assign it to a macro

// map out1 to the parameter you want to control out(1, (val1 + val2) % 1.1)

Now when you turn the macro, it will change the parameter, but you can also change the parameter by twisting val2. The first time I used this was in a drum rack with multilayered samples. I had a randomize macro knob that would randomly select samples, but could also choose individual samples if a random combo was just a little off but almost right.

Turn a knob into an on/off switch

out(1, (val1 < 0.5 ? 0 : 1))

Turn a continuous value into a discreet value

var numOfSteps = 4

val1 = valtosteps(val1, numOfSteps)

out(1, val1)

Seeded Random Values

"

/** val1 is seed, val2 is iteration */

if (isNote) {}

else if (val2 === 0) {

[].range(8).map(function(i) {

out(i+1, 0)

})

}

else {

var numOfSeeds = 25

var seedint = int((val1 * numOfSeeds) + (val2 * 127))

var seed = (seedint * 9301 + 49297) % 233280

[].range(8).map(function(i) {

seed = (seed * 9301 + 49297) % 233280

out(i+1, seed / 233280)

})

}

"

Caveats and pitfalls

javascript is forced into max for live’s low-priority thread. This makes it unusable for midi effects.

sometimes the map buttons unmap and get buggy. Still not sure why. If there are any max masters reading this, let me know where I’m going wrong.

scripts require parenthesis around the text in order to be evaluated. Not too big a deal, but it’s ugly. If it does cause some issues, let me know!

Conclusion

If you find this patch and these concepts useful, consider emailing Cycling 74 or Ableton with a feature request. The ability to write scripts for realtime midi and audio processing in a little Ableton device would open up a new world of possibilities! Again, here’s a link to the patch. I’m excited to see what people come up with!