Tutorial: Understanding MPE zones

Learn the concept of zones and the conventions for zone layouts as defined in the MPE standard. Hook up your MPE synthesiser to an MPE-compatible device.

Level: Intermediate

Platforms: Windows, macOS, Linux

Classes: MPEZoneLayout, MPEZoneLayout::Zone, MPEMessages, MPESynthesiser

Getting started

Download the demo project for this tutorial here: PIP | ZIP. Unzip the project and open the first header file in the Projucer.

If you need help with this step, see Tutorial: Projucer Part 1: Getting started with the Projucer.

Note
It would be helpful to read Tutorial: Build a multi-polyphonic synthesiser first, as this is used as a reference point in a number of places.

The demo project

The demo project is similar to the MPEDemo project in the JUCE/examples directory and builds upon the simplified version in Tutorial: Build a multi-polyphonic synthesiser. In order to get the most out of this tutorial you will need an MPE compatible controller. MPE stands for MIDI Polyphonic Expression, a specification that allows multidimensional data to be communicated between audio products.

Some examples of such MPE compatible devices are ROLI's own Seaboard range (such as the Seaboard RISE) and the BLOCKS range (such as the Lightpad Block).

With a Lightpad Block connected to your computer the window of the demo application should look something like the following screenshot:

tutorial_mpe_zones_screenshot1.png
The demo application

You will need to enable one of the MIDI inputs (here you can see a Lightpad Block is shown as an option).

Any notes played on your MPE compatible device will be visualised in the lower portion of the window as explained in the Tutorial: Build a multi-polyphonic synthesiser tutorial.

MPE Specifications

In the introductory tutorial Tutorial: Build a multi-polyphonic synthesiser, we were able to implement an MPE-compatible synthesiser easily by bypassing the standard MPE configuration process using legacy mode. In this tutorial, let's configure our synthesiser by following the procedure described in the latest MPE standard.

For the synthesiser to be able to determine whether we are working in MPE mode or not pertains to the notion of MPE zones. If at least one zone is defined using an MPE Configuration Message (MCM), then we are in MPE mode. Otherwise, if no zones are defined, then MPE mode is off. So what are zones?

MPE Zones

The concept of zones is an MPE-specific term describing a group of contiguous MIDI channels comprising of one Master Channel and one or more Member Channels.

A Master Channel receives messages that apply to the entire zone whereas Member Channels receive messages that only apply individually.

In MPE, we can have at most two zones and they are defined as Lower and Upper zones.

  • Lower Zone: Channel 1 as the Master Channel and one or more Member Channels allocated increasing from channel 2.
  • Upper Zone: Channel 16 as the Master Channel and one or more Member Channels allocated descending from channel 15.

A Member Channel can only belong to one zone at a time and the most recent MCM takes precedent over previous ones.

As an example, we can set the Lower Zone to contain channels 2 to 10 and the Upper Zone to contain channels 11 to 15.

Alternatively, we can also restrict ourselves to a single zone, using either the Lower or Upper Zone. However, it is recommended to use the Lower Zone by default. In a single zone scenario, the remaining unused Master Channel can be used as a Member Channel for the other zone, resulting in a maximum of 15 Member Channels.

An MPE zone can be turned off by sending an MCM without any member channels to the zone's master channel and MPE mode is therefore turned off when all zones are empty.

In JUCE, the implementation of a zone is encapsulated in the MPEZoneLayout::Zone struct and different zone configurations can be defined using the MPEZoneLayout class.

Zones are a useful means to provide different timbral characteristics using only one MPE controller and facilitates the propagation of MIDI messages across groups of channels using Master Channels.

MIDI Modes

There are mainly two MIDI modes that are supported with MPE: MIDI Mode 3 and 4.

  • MIDI Mode 3 (Poly Mode): In Poly Mode, a single MIDI channel can hold multiple notes simultaneously however, channel messages will affect all active notes on that channel. When a new MIDI note is created, the controller tries to assign it to an empty channel when possible, otherwise assigns it to a channel with existing active notes.
  • MIDI Mode 4 (Mono Mode): In Mono Mode, a single MIDI channel can only hold a single note and when a new MIDI note is created, if the other channels are full, the controller will overwrite existing active notes.

MPE is designed to work properly using MIDI Mode 3 (Poly Mode) but can still be used using MIDI Mode 4 (Mono Mode).

Note Level vs Zone Level Messages

Depending on whether a MIDI message is sent on a Master Channel or a Member Channel we will refer to them as Zone Level messages or Note Level messages, respectively. Some messages can be sent to both the Master Channel and Member Channels within a zone. When this happens, the receiving synthesiser has to combine both information in an appropriate manner.

Messages that must be sent as Zone Level are the following:

  • CC #1, #33: Control Change for Modulation. This CC can be sent at Note Level but will be ignored.
  • CC #7, #39: Control Change for Volume. This CC can be sent at Note Level but will be ignored.
  • CC #64: Control Change for a Damper Pedal. This CC can be sent at Note Level but will be ignored.
  • CC #120: Control Change to turn all sounds off. This CC can be sent at Note Level but will be ignored.
  • CC #127: Control Change to reset all controllers. This CC can only be sent at Zone Level.
  • Polyphonic Key Pressure: PKP is a future extension of the MPE specifications that are sent on the Master Channel.
  • MPE Configuration (MCM): MCMs are sent on the Master Channels to configure as described earlier.

Messages that can be sent as Zone Level and Note Level are the following:

  • Pitch Bend: Control Change for the first dimension also called Glide. Pitch Bend information must be combined if received at both message levels.
  • Channel Pressure: Control Change for the second dimension also called Press. Channel Pressure information must be combined if received at both message levels.
  • Timbre (CC #74): Control Change for the third dimension also called Slide. Timbre information must be combined if received at both message levels.
  • Pitch Bend Sensitivity: Control Change for changing pitch bend sentivity that should be sent to all Member Channels when used as Note Level.
  • Program Change/Bank Select: Program Changes are sent on the Master Channel unless in MIDI Mode 4 where they are sent on Member Channels.

Messages that are usually sent as Note Level are the following:

  • Note On/Off: Note On and Note Off messages should be sent on the appropriate Member Channel but are permitted on the Master Channel for backwards compatibility.
  • MIDI Mode (CC #126, #127): Control Changes for switching between MIDI Mode 3 and 4 are sent to the lowest Member Channel.

These message levels are important to remember as they influence the design decisions when defining zones within your synthesiser.

Configuring Zones

Without legacy mode as implemented in Tutorial: Build a multi-polyphonic synthesiser, our synth will not output any sound until we configure at least one zone.

In the MPESetupComponent class, we add three button callbacks using lambda functions that allow creation and deletion of zones. We can either create lower and upper zones or clear all the zones in our zone layout.

MPESetupComponent()
//...
{
addAndMakeVisible (isLowerZoneButton);
isLowerZoneButton.setToggleState (true, NotificationType::dontSendNotification);
//...
addAndMakeVisible (setZoneButton);
setZoneButton.onClick = [this] { setZoneButtonClicked(); };
addAndMakeVisible (clearAllZonesButton);
clearAllZonesButton.onClick = [this] { clearAllZonesButtonClicked(); };
//...
}

When the user decides to set a zone, the setZoneButtonClicked() function is called as defined here:

void setZoneButtonClicked()
{
auto isLowerZone = isLowerZoneButton.getToggleState();
auto numMemberChannels = memberChannels.getText().getIntValue();
auto perNotePb = notePitchbendRange.getText().getIntValue();
auto masterPb = masterPitchbendRange.getText().getIntValue();
if (isLowerZone)
zoneLayout.setLowerZone (numMemberChannels, perNotePb, masterPb);
else
zoneLayout.setUpperZone (numMemberChannels, perNotePb, masterPb);
//...
}

We first create new local variables to store the lower/upper zone choice, the number of member channels, the zone level pitch bend and the note level pitch bend. We then set the Zone in our MPEZoneLayout object by calling the corresponding setLowerZone() or setUpperZone() function.

To handle the callback when clearing the zones, we simply call the clearAllZones() function on the MPEZoneLayout object which removes all the zones as follows:

void clearAllZonesButtonClicked()
{
zoneLayout.clearAllZones();
//...
}

Configuring the Synth

Since the MPESetupComponent class acts as a broadcaster, we can register as a listener in the MainComponent class in order to receive callbacks when the zone layout has changed.

class MainComponent : public Component,
private MPESetupComponent::Listener
{
//...

We can then override the corresponding functions to configure the synthesiser accordingly.

In the zoneChanged() callback, we set the newly-created Zone to the MPEZoneLayout member variable [1]. We can then pass the MPEZoneLayout object to the MPESynthesiser by calling setZoneLayout() on it [2]:

void zoneChanged (bool isLowerZone, int numMemberChannels,
int perNotePitchbendRange, int masterPitchbendRange) override
{
auto* midiOutput = audioDeviceManager.getDefaultMidiOutput();
if (midiOutput != nullptr)
{
if (isLowerZone)
midiOutput->sendBlockOfMessagesNow (MPEMessages::setLowerZone (numMemberChannels, perNotePitchbendRange, masterPitchbendRange));
else
midiOutput->sendBlockOfMessagesNow (MPEMessages::setUpperZone (numMemberChannels, perNotePitchbendRange, masterPitchbendRange));
}
if (isLowerZone) // [1]
zoneLayout.setLowerZone (numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
else
zoneLayout.setUpperZone (numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
visualiserInstrument.setZoneLayout (zoneLayout);
synth.setZoneLayout (zoneLayout); // [2]
colourPicker.setZoneLayout (zoneLayout);
}

In the allZonesCleared() callback, we empty all zones in the MPEZoneLayout member variable [3]. We can then similarly pass the MPEZoneLayout object to the MPESynthesiser by calling setZoneLayout() on it [4]:

void allZonesCleared() override
{
auto* midiOutput = audioDeviceManager.getDefaultMidiOutput();
if (midiOutput != nullptr)
midiOutput->sendBlockOfMessagesNow (MPEMessages::clearAllZones());
zoneLayout.clearAllZones(); // [3]
visualiserInstrument.setZoneLayout (zoneLayout);
synth.setZoneLayout (zoneLayout); // [4]
colourPicker.setZoneLayout (zoneLayout);
}

When the number of MPESynthesiserVoice objects is modified, we receive the numberOfVoicesChanged() callback which allows us to remove or add voices by respectively using the reduceNumVoices() and addVoice() functions on the MPESynthesiser object:

void numberOfVoicesChanged (int numberOfVoices) override
{
if (numberOfVoices < synth.getNumVoices())
synth.reduceNumVoices (numberOfVoices);
else
while (synth.getNumVoices() < numberOfVoices)
synth.addVoice (new MPEDemoSynthVoice());
}

Assigning Pitch Bend

If we run the synthesiser, we are able to add lower and upper zones with a customisable number of member channels per zone.

tutorial_mpe_zones_screenshot2.png
Different zones in the demo application

Try assigning different pitch bend sensitivities to the lower and upper zones and notice the impact on the pitch bend of notes assigned to a midi channel of a different zone.

Excerise
Assign different sounds to lower and upper zones in the MPEDemoSynthVoice class and crossfade between a triangle and a sawtooth waveform in response to the timbre parameter.
Note
The source code for this exercise can be found in the MPEZonesTutorial_02.h file of the demo project.

Summary

In this tutorial, we have learnt how to manage MPE zones and notes. In particular, we have:

  • Learnt the conventions when setting up a Zone and an MPEZoneLayout.
  • Understood the two MIDI modes compatible with MPE.
  • Explored the different Note Level and Zone Level messages.
  • Assigned different pitch bend sensitivities to lower and upper zones.

See also