Skip to content

Play Microtonal and MPE

A MIDI note number is a semitone. To play between them, a track sends the nearest note plus a pitch bend. Every method here is in the MIDI reference.

.set_key() resolves scale degrees sent to the track and pulls absolute notes onto the scale:

let lead = MidiTrack("lead");
lead >> MidiOutput("synth");
lead.set_key(Key(C4, "major"));
lead << [^1 ^3 ^5 ^3];

It takes a Key, or a scale name or Scale plus a root:

lead.set_key("minor", A3);
lead.set_key(Scale(#[0, 2, 4, 5, 7, 9, 11]), C4);

Write in a Key covers degrees and keys in patterns.

Load a tuning as the key and turn on .set_tuning(). A note that falls between semitones is sent as the nearest MIDI note plus a pitch bend:

let just = MidiTrack("just");
just >> MidiOutput("synth");
just.set_tuning();
just.set_key(load_scala("just/5-limit"), C4);
just << [^1 ^3 ^5 ^3];

The bend range defaults to ±2 semitones, which every synth understands. If you raise it with .set_tuning(range), set the same range on the synth.

One channel has one pitch bend, so overlapping notes bend each other: the last one wins. For chords and legato lines, use MPE.

.set_mpe() spreads notes across member channels, each with its own pitch bend:

let pad = MidiTrack("pad");
pad >> MidiOutput("synth");
pad.set_mpe();
pad << [C4, E4, G4];

Put the synth in MPE mode. Resonon sends the MPE configuration message and each channel’s bend range before the first note, so the synth sets itself up.

If the synth takes a different bend range, or only some member channels, pass them — the range in semitones, then the lowest and highest member channel:

pad.set_mpe(24); // ±24 semitones
pad.set_mpe(48, 2, 9); // member channels 2 to 9