Reconstructing the Ableton velocity compand curve in Max for Live

Posted on January 3, 2021

The Velocity effect in Ableton Live allows us to apply some transformations to the velocity of incoming MIDI notes.

In this brief blog post we will look at how we might recreate this velocity curve; specifically, the Comp part of the curve. The goal won’t necessarily be to recreate the curve exactly, but rather to have something that functions similarly.

Companding

Comp stands for companding, which is a combination of two words compression and expanding. It is a standard algorithm in signal processing. There are many algorithms for doing this, but for our purposes a suitable choice is μ-law companding.

Compression

Compression happens when we concentrate the range of values near the two extremes (very loud or very soft), rather than near the middle. In the Velocity effect, this is presented by a positive value of the Comp parameter, but we will focus on the standard formulas first before relating it back to the Comp parameter.

The formula for μ-law compression is pretty straight-forward. The intuition is that to compress a signal x, we take the logarithm in base μ (logμ(x)); to expand it again, we compute μx. The most important difference between this and the actual formulas is that they add some twiddling to avoid artefacts near zero.

The standard formula for μ-law compression is

or in gnuplot code:

sgn(x) * log(1 + mu * abs(x)) / log(1 + mu)

(Note that log can be logarithm in any base; we’re effectively computing a logarithm in base 1 + mu here.)

The parameter μ in the formula serves a similar function to the Comp knob: small values of μ compress the curve barely at all; large values compress it a lot. The following graphs shows the effect of this formula for different values of μ:

The range of this graph is from -1 to +1 on both axis; see side note on the original use of this formula in signal processing below. However, if we squint a bit and think of this graph as representing a mapping from velocity values between 0 and 127 on both axis (the MIDI velocity range), observe the effect the compression has: most values will either up really loud or really soft, with little nuance in the middle.

Although larger values of μ do compress more, we have to increase μ quite quickly; in this graph we increased it by an order of magnitude at each step. We will map this back to a parameter in the range 0 to 1 (like the Velocity effect does) below.

Side note: use in signal processing

If you think of the compression graph above in terms of what it does to a wave form, then small changes in the input signal near zero have a large effect on the output signal, but small changes the input signal near one only have a small effect on the output signal.

If we then represent this signal as, say, 8 bits samples, we are effectively using more bits to distinguish values near zero and fewer bits to distinguish values near one. This is useful, because the human ear is more sensitive to small changes in soft sounds than it is in small changes in loud sounds.

In this case, the translation is not done as an effect, but as an encoding technique. Compression is applied before the signal is encoded, and at the decoder side, expansion is applied before the signal is played back.

Expansion

If compression concentrates velocity values near the extremes, expansion concretates velocity near the middle (neither very loud nor very soft). It’s what happens when we dial to the Comp knob in the Velocity effect to negative values.

The standard formula for μ-law expansion is

or in gnuplot code:

sgn(x) * (1.0/mu) * (((1.0 + mu) ** abs(x)) - 1.0)

Here is a graph of what this function looks like for various values of μ:

Velocity curves

In order to translate the above formulas/graphs into something more similar to Ableton’s velocity curves, we need to do two things: we need to work in a range of [0:127] instead of [-1:+1], and we need to translate from a parameter in the range [0:1] (the value of the Comp knob) to the mu parameter.

To map a value in the range [0:127] to [-1:+1], we can just translate

in(x)  = -1 + (x / 127) * 2
out(y) = (y + 1) / 2 * 127

(Notice that out is the inverse of in: out(in(x)) = x). To define mu in terms of the Comp parameter (let’s call it f), we need to map a value between [0:1] to, say, [0:100]. One way we could do that is as follows:

mu(f) = 10.0 ** (2 * f)

Compression

This means we end up with the following gnuplot code for compression

out(sgn(in(x)) * log(1 + mu(f) * abs(in(x))) / log(1 + mu(f)))

with corresponding graph

Expansion

For expansion, we end up with the gnuplot code

out(sgn(in(x)) * (1.0/mu(f)) * (((1.0 + mu(f)) ** abs(in(x))) - 1.0))

and graph

Putting it all together

To turn all this into a Max for Live patcher, we can just implement this math in JavaScript (or in Max for Live itself, if you prefer), and set up an itable that can serve as a lookup table for velocity translations, and is updated by the JavaScript code every time the Comp parameter is changed.

The device itself can just consist of a live.dial to serve as a knob to change the Comp parameter, a js object to run the JavaScript code, and the itable itself:

As for the JavaScript code itself, you can download velocity.js; just place it in the same directory as your device, and you should be good to go.