Home > Uncategorized > Replace Apple MainStage with a custom Max/MSP implementation (Part 2)

Replace Apple MainStage with a custom Max/MSP implementation (Part 2)

July 26th, 2011

In part 1 of this article, I introduced some of the basic Max patchers I created such as generic MIDI input and output devices and then instrument-specific versions. After a few days of testing, I ran into some bizarre behavior where Scorecerer would not change pages via MIDI remote control, but only for some songs.

I finally tracked the problem down and it turned out that there was a ‘double-bug’, one in Scorecerer and one in my Max implementation. These conspired to cause the problem. What was happening was that when I pressed the button on my MPK61 to go to the next page in Scorecerer, Scorecerer didn’t respond at all but the (highly recommended) MIDI Monitor app I was running reported that two copies of the CC event were being sent to Scorecerer. I’ll get back to the reason two copies were being sent in a moment as that caused me to change the original design of the MIDI device patches.

The reason Scorecerer didn’t respond at all (as opposed to changing the page twice, which is what should have happened) is because I was (stupidly) using the length of the incoming packet to decide whether I was getting a CC event (3 bytes) or a ProgramChange event (2 bytes). However, in this particular case, the packet length was 6 (two CC events) and so was just being ignored. I fixed this by switching on the actual Status byte but I’m glad I found this issue as if it had been reproduced by customers but not by us, it would have been very difficult to track down.

Let’s get back to the reason Max was (sometimes) sending the event twice. Max has a very nice feature where, rather than having to use a single instance of a particular object, you can instantiate it multiple times and place it conveniently. For example, suppose you want an incoming note to be played on two synthesizers, with one of them playing the note transposed up by 7 semitones. You could use the following simple patch.

This will work absolutely fine but if you decide that you want to perform more sophisticated operations along the way, the view will get complicated due to too much entanglement. A better solution is to use the following patch.

In this version, even though each noteout object refers to different output devices (not shown here), both notein objects can refer to the same input device. As you will see when I show a patcher for a complete song further down, this separation can make it much easier to view and understand the patcher.

Now, if you recall from part 1, I had defined a generic MIDI In patcher and then created keyboard specific devices with it. In particular, those devices had some explicit outlets (representing notes, pitchbend and aftertouch) and used multiple send objects to make incoming CC events with different numbers available easily to multiple receivers. Therein lies the problem. Take a look at the following patcher, representing the input of my Yamaha AN1x.

As you can see, it has some outlets for notes, pitchbend, aftertouch and it also has a collection of send objects used to send out data streams for the knobs and pedals. Now, consider what happens if you create two or more of these patchers. There’s no impact on wired connections (if you don’t connect anything to the outlets, nothing happens) but anytime a CC event occurs, it will be sent out as many times as there are patcher instances. Some of my song patchers had the instrument device instantiated more than once (for the reasons described earlier above) and that’s what led to the problem.

After finally figuring out what happened, I changed the design slightly to address this problem. The first thing I did was get rid of the explicit outlets completely and replaced them with send objects. So the AN1x input patcher now looks as follows:

Now, every item of interest uses the send/receive mechanism. The second design change was to create a patcher that contained a single instance of all my input devices. This is called PhysicalInputDevices. Here it is.

As you can see, this patcher contains one object for each MIDI input device of interest. A  single instance of this patcher is created after which point all outputs of interest from all of these devices are immediately available wherever they are needed by just inserting a receive object into any patcher and using the desired argument name.

Songs

Now that the basic construction has been explained, lets take a look at an actual song patcher. Although I’ll include an entire song patcher here just so you can see the whole thing, I’ll then just focus on pieces of it at a time to explain how it works. A song patcher represents the same functionality as an individual MainStage patch and so there are multiple song patchers, each representing (typically) the required functionality for specific songs. Here is a patcher that I use with my band to perform the song All I Need is a Miracle (Mike and the Mechanics). Take a few moments to just look around the patcher.

Phew! OK — let’s look at this thing piecemeal and all will become clear.

The loadbang object is triggered automatically when a Max patcher is opened. The loadbang is connected to a send object with an argument called BigBang and a print object. The latter is just used for debugging and writes information to a console window. Various receive objects both within this song patcher as well as in some other auxilliary patches (such as the FrontPanelDisplay which provides a GUI similar to a MainStage layout showing what knobs and sliders are active) will be triggered as a consequence, allowing various initialization to occur as we will see shortly.

Sending program changes to external devices

Every song patcher contains a SystemProgramChange patcher to make it easy to send program changes to all devices in one go.

Here, we have six messages, each of which consists of a single number. Those numbers happen to be the MIDI Program Change values for my external devices for the Miracle song. The SystemProgramChange patcher encapsulates my external devices and exposes just the ProgramChange parameter of each of those external devices. Here’s what it looks like under the covers.

Note that because some synthesizers use 0 for their first patch and others use 1, I’ve enabled some of the devices to subtract 1 from the incoming value. That allows me to use the same values as displayed by the devices regardless of whether they start from 0 or from 1.

 

From input to output – our first sound

Lets now take a look at one of the playblocks (my term for a collection of objects that actually let you play one of your keyboards and get some sounds out of it).

This is the closest to a combination of a MainStage external MIDI channel strip coupled with the MIDI input section inspector. This is where you can define a routing from a keyboard (MIDI input device) to a synth (MIDI output device). Here we are using the AN1x to control channel 4 of the Receptor which has received a program change (see above) so that it produces a “pad” sound. On the right, we have connected Knob1 to the Volume inlet of the Receptor channel. Next to it, we have connected the pitchbend wheel of the AN1x to the pitchbend inlet of the Receptor (MIDI channel 4). Unlike in MainStage where some connections happen automatically unless you explicitly block them (great for simple setups but not so good for anything complex), in this Max environment, if you don’t make the connection, you don’t get any result.

The chunk on the left is a little more interesting. You can see that we are receiving incoming notes from the AN1x, but they are not going directly to the Receptor. Instead, they are going through another custom object called KeyboardLayer. Let’s look at that object.

All the objects here are built-in Max objects. Please refer to the Max documentation for deep details of what’s going on here. The left parameter takes a list of two values representing a note (the note number and the velocity value). The right parameter takes a list that represents the lowest allowed key, the highest allowed key and a transpose amount). The unpack object breaks up an incoming list into separate values, and the optional arguments define the type of those values. The default is two integers. So the note number and velocity are extracted from the incoming note. We don’t care about the velocity value and so it simply gets passed on to the pack object where (along with an eventually arriving note number) it will be combined back into a note and sent out.

The split object determines what range of values are allowed through its left outlet. The default (as defined by the arguments) is to allow all MIDI note numbers through. However, if you send it min and max values to the two right outlets, then it will only allow notes through that are within that min and max range. Any note number that gets through is added to a transpose value if provided.

Referring back to that playblock, it should now be clear that all notes on the AN1x are sent to the Receptor but transposed up by 12 semitones. The BigBang message, triggered by the loadbang when the song is loaded, is used to initialize the desired range and transpose for the specific KeyboardLayer object.

Splits and layers

If you look at the playblocks for the Polymoog and Piano in the song patcher above, you can see that separate sections of the Akai keyboard are being used for each of those sounds, i.e, MIDI notes 36 to 53 inclusive are sent to a piano sound (transposed up one octave) and MIDI notes 84 to 96 inclusive are sent to the Polymoog sound. By adjusting the ranges, you can configure any desired splits and/or layers that you need.

In Part 3, we will introduce a simple mechanism to implement velocity mapping and also talk about the consolidated front panel through which one can see quickly what knobs and sliders are associated with what parameters for the various devices.

Categories: Uncategorized Tags:
Comments are closed.