System exclusive in Logic Audio: multi-byte messages

This tutorial comes with an environment that illustrates some of the things described here. You need Stuffit Expander to unpack it. Stuffit Expander is free, for Mac and PC, and can be downloaded here.

Download the environment "smbyte.lso".

This article supposes you're able to read hexadecimal numbers and have some basic knowledge w.r.t. sysex. You might first want to read my article dealing with hex notation, or the general sysex article. It's also assumed you know how a transformer set to "Sysex Mapper" works. If you don't, then please read the article on Sysex Mappers first.

  1. What's up?
  2. What's covered?
  3. A parameter ranging from -50 to 50
  4. A parameter ranging from -99 to 99
  5. Comparing both approaches
  6. Further, and beyond Logic's abilities

1. What's up?

As long as you need to send "fixed" sysex strings, like dump requests and such, life is easy: just make a Sysex Mapper, or a fader, that contains the string, and you're done. Even if you need a sysex message that contains a 'value byte' which needs to be changed dynamically (like a parameter change), you can often get away with simple solutions, like those discussed in the previous 2 tutorials: as long as the values you need to send are in the 0-127 range, you get by with using one single 7-bit byte, and all is fine. Problems arise as soon as you have to access values outside the 0-127 range. For example, a Delay Time parameter might accept values from 0-500, or a Detune parameter might have a range of -50 to 50. In these cases you'll most likely need to use so-called multi-byte values: more than one byte is needed to represent those ranges.

Since there are many possible ways a manufacturer might decide to represent large values or negative numbers, it's impossible to cover all possibilities here. Therefore we will limit ourselves to a relatively common format. If this format doesn't apply to your particular machine, you will probably still find enough ideas, hints and tactics here to adapt this tutorial to your specific needs.

top

2. What's covered?

What we'll consider in this tutorial is a system that uses 2 bytes to represent any number. Numbers greater than 127 are simply formed by using the 2nd byte in the obvious way: after (hex) "00 7F" comes "01 00". Negative numbers are made by counting backwards from 0. So -1 would be represented as (hex) "7F 7F", -2 is then "7F 7E", et cetera. See the article on binary and hex notation for details regarding negative number representation. Furthermore we'll suppose the sysex format requires a MSB-LSB ordering: most significant byte first, least significant byte last. For hexadecimal notation, we'll adopt Logic's usage of a preceding "$" to indicate hex-numerals (that's easier than telling what we mean each time).

We'll build 2 different sysex patches. The first is supposed to send parameter values between -50 and 50. Although we do need negative numbers (and thus need 2 bytes), the range of the parameter is still less than 127: 101 values suffice to cover anything from -50 to 50. In the second example we'll build a patch that produces a range of -99 to 99: here the range clearly is larger than 127, and a different approach is needed.

At the end of the tutorial we'll look into the problems that arise when controlling parameters whose range includes negative numbers while at the same time exceeding 256 values, like a parameter with a -200 to +200 range. Here we find Logic's limits.

top

3. A parameter ranging from -50 to 50

Let's suppose our intended sysex messages looks like this:

$F0 VAL VAL $F7

This is of course an impossible message in real-life, since the first byte is normally used to indicate the manufacturer. This way however, we'll be able to concentrate on what's important and leave the rest out. Moreover, such a message, where the relevant bytes are at the very beginning, allows us to monitor what's happening with a plain Monitor object: since the Monitor object only show the first 2 data bytes, any other format would make monitoring impossible. OK, so remembering that we're using a MSB-LSB order, and considering the fact that -50 is written as $7F $4E (128 - 50 = 78, and 78 = $4E) and +50 as $00 $32, our sysex message should range from

$F0 $7F $4E $F7

to

$F0 $00 $32 $F7

Since the range of this parameter covers only 101 possible values, we start out by creating a text fader and set its range to 0 - 100. We use a text fader, so that instead of displaying "0" to "100" we can have it display "-50" to "50" instead. In a text editor, type -50, -49, ... 0, 1, ... 50, each on its own line. Select all, copy, and switch back to Logic. Double-click the text fader, and in the window's top right corner click on the little arrow: a menu drops down in which you pick "paste all names". Now we have a fader that neatly runs from -50 to 50... or so it seems...

Since we want to be able to automate this fader later on, we'll take that into account immediately. Let's suppose our entire environment will use controllers 100 - 119 for automation, on 16 possible channels (the "standard" way of automating sysex, as described in the previous tutorial, enabling us to automate 20 (CCs) * 16 (channels) = 320 faders with one single automation track). We therefore set the fader's In and Out definitions to controller 100, and its channel to 1.

The MSB. Now we have to do some thinking. We want the fader's 0 - 49 range to map to parameter values -50 to -1. So in the 0 - 49 range, we need to produce a MSB with value $7F. In the 50 - 100 range (parameter 0 to +50) we need the MSB to be $00. To achieve that we use a transformer that maps 0-49 to 127 ($7F) and 50-100 to 0. Create a new transformer and call it "MSB". Cable the fader to this transformer. In the transformer's window, set Conditions to:
Status = Control,
Cha: all,
-1- Inside 100-119 and
-2- Inside 0-100.
The settings for "-1-" will allow us to re-use this transformer for other fader later on (since we'd agreed on using CC's 100-119 for automation), while at the same time disallowing any "illegal" controllers that might accidentally turn up at the transformer's input from affecting the output.

We also have to set the "Operations":
Status: Thru,
Cha: Fix 2,
-1-: Fix 1,
-2-: Use Map.

Fixing the channel to 2 means that our output will change the Sysex Mapper's message but will not cause any output from the Sysex Mapper. This is desired behaviour, since we've also got to set the LSB, and that will cause the required sysex output. If we'd set the channel to 1 here, any fader change would cause two sysex messages to be output: one with the MSB changed, and one with both MSB and LSB changed. Fixing the controller number to 1 means that this message will affect the first data byte in the sysex message -- which also is indeed what we want.

We now have to set the map, since we specified "Use Map". We wanted 0-49 to give a MSB of 127 ($7F) and values 50-100 to give a MSB of 0. Adjust the map so that the first 50 values are mapped to 127 and the other values are mapped to 0: in the bottom left of the transformer window, set the first number box to 0 and the second to 127, then set the first to 1 and the second to 127 again. Repeat until you've reach 49. If you've used a default transformer, the rest will already be set to 0. This is a tedious job, but you only need to do it once, fortunately. After that, you can re-use this transformer for all parameters with a -50/+50 range.

There's one more thing to consider: we now only get one message as output: either 127 or 0. However, we'd like to also retain the original fader message, since we need that to get our LSB working. We fix things by setting the transformer mode to "Copy matching events & Apply operation (rev. order)". The "rev. order" means that first our newly created MSB will be output, and then a copy of the original fader message will follow. This is indeed what we want: the MSB message will set the MSB in the sysex message without causing any output (since the channel is set to 2). Then the LSB message will follow, which will set the LSB in the sysex message and finally cause the proper string to be output.

The LSB. For the LSB we need the following behaviour: fader range 0 to 49 gives LSB 78 to 127 ($4E to $7F, see above), and fader range 50 to 100 gives LSB 0 to 50. We'll once more use a transformer Map to achieve this. Create a new transformer, and connect the MSB transformer to it. We'll name the new transformer "map 0-100".

Open the transformer's window and set Conditions as we did in the MS transformer:
Status = Control,
Cha: all,
-1- Inside 100-119 and
-2- Inside 0-100.

Set the Operations part to:
Status Thru,
Cha: Fix 1
-1-: Fix 2
-2-: Use Map.

The channel=1 settings assures that the Sysex Mapper will actually output its message when this controller is received. Setting the controller number to 2 means that the 2nd data byte will be affected, as should be.

Note. If your system uses a LSB-MSB ordering instead of MSB-LSB, then you can easily adapt the two transformers. Simply set the Operations controller numbers to match the positions of the respective value bytes. I.e. reversing byte order in this particular example would mean: have the MSB transformer output controller 2, and have the "map 0-100" transformer output controller 1. That's all.

Now we once more face the tedious job of setting the correct Map. The mapping we use is: map 0 to 78, 1 to 79, ... , 49 to 127 (i.e. a linear ramp), and 50 to 0, 51 to 1, 52 to 2, ... , 100 to 50 (another linear ramp).

Last things... We're almost done. All we have to do is produce the actual sysex message. Create a new transformer and set it's mode to "Sysex Mapper". Set the Conditions to
Status = Control,
Cha: All
-1-: Inside 1-2
-2-: All
Set the sysex length to 4, and you're done.

Finally cable the "map 0-100" transformer to the Sysex Mapper, and connect a Monitor object to the Sysex Mapper's output. Drag the fader, watch the Monitor object -- and be amazed...

If you need to use a patch like this, you can conveniently wrap it in a macro. This saves screen real-estate and provides a kind of abstraction layer which can be convenient later on. The environment coming with this tutorial shows both the finished patch and the macro version.

Note. When building the MSB transformer, we set its mode to "Copy matching events & Apply operation (rev. order)". Instead we could have left it as it was, and might have cabled the fader directly to the "map 0-100" transformer instead (thus having both transformers cabled in parallel instead of serially). In that case however, we couldn't have made the patch into a macro, since we'd have needed two inputs instead of one -- which a macro doesn't allow. We could have solved this be inserting an extra transformer in front of both parallel transformers and use that as the macro input, but that would have cost us an extra transformer. Things are much cleaner the way we actually did it.

Creating a patch like this sounds complicated when described in plain language, and it's actually quite a lot of work to write it all down. In the end however, the entire patch contains just 3 transformers and setting it up isn't that hard at all. The hardest part if figuring out how everything fits together -- but now that has already been done for you. Of course, creating the proper maps also is a tedious job, but fortunately you don't have to do it all over again each time. Often synth parameters will only have a limited amount of ranges, like -50/+50, -99/+99, -12/+12 and 0-200. You have to go through all the map-making hassle once for each of those possibilities, after which you can simple "copy & paste" parts and re-use them.

top

4. A parameter ranging from -99 to 99

As in the previous section, our intended sysex messages looks like this:

$F0 VAL VAL $F7

In this case, our parameter has a range that exceeds the 127 possible values that are normally available. So the trick we used in the previous example, where we mapped "fader 0 to 100" to "sysex -50 to +50" won't work here. With some trickery we can however do something that's very similar. First we have to create a fader whose range is large enough. A normal fader won't cut it, so we have to use a 14Bit fader instead. Create a new text fader, and select it. In the parameter box, set its "Filter" to "14Bit". We now have a maximum range that covers 14 bits instead of the normal 7. So our fader could have a range from 0 to 2^14 - 1, or 16383: surely enough for most purposes.

It would be nice if the fader now simply sent out values from 0 to 16383, but it doesn't. Since the MIDI protocol doesn't allow values over 127 for data bytes and since faders (normally) send out MIDI data, Logic has to find a way around that. What happens if you set a fader to 14Bit, is that whenever you change the value, two messages are sent out: one is the controller to which the fader is set, and the other is the controller 32 up in the hierarchy. So a "controller 7" 14-bit fader will send out controller 7 and controller 39 simultaneously. The first controller functions as the MSB and the last controller as the LSB.

Try this experiment: set the range of your new text fader to be 0 - 500, and connect a Monitor object to it. Drag the fader all the way up, from 0 to 500 (ignoring that the fader's display goes haywire). At first you see a bunch of CC7's with value=0, together with CC39's whose value runs up from 0 to 127. Then, if you move the fader 1 position up, to 128, the CC7 event will suddenly have value=1, while the CC39 has value=0. Going up again, you see the CC39's running up to 127 again, while the CC7's retain their value of 1. Then when you get to 256, the CC7 changes to value=2, while the CC39 again drops down to 0. Et cetera. If you have trouble understanding what happens here, read the article that deals with MSB/LSB.

Since the -99 / 99 range covers 199 possible values, we could be tempted to set the fader's range to 0-198 and hope to find a way to convert those double-CC values the fader sends out to something we can use. This may well be possible, but there's an easier way. In the experiment above we saw that the lower CC (=MSB) changed from 0 to 1 as soon as we reached a fader value of 128. What we're actually going to need is a fader whose MSB changes from $7F (127) to 0 when we reach 0 (going up from -99). The numbers don't match, but at least we can make the events match so that half the work is already done for us.

So we're going to match 'fader value=128' to 'sysex value=0'. All the rest follows from that... Set the text fader's range to "29 - 227", since 128-99=29, and 128+99=227. As before, in a text editor type "-99, -98, ... -1, 0, 1, 2, ... , 99", each number on it's own line. Select all, copy and go back to Logic. Double click the text fader, click the little triangle in the top right corner and pick "paste all names". Now at least the fader looks right.

There's one more detail to tackle. In the previous section we decided to use CC100 - CC119 for automation purposes. If we did so here, we would run into trouble. Since a 14Bit fader uses CC-x and CC-(x+32), using CC100 will not work: there are only 127 controllers, so controller 100+32 = 132 doesn't exist. Therefore we have to use lower numbered controllers, so that there's at least a "headroom" of 32 CC numbers. For that reason we'll now decide to use CCs 80-94 for automation (94+32 = 126, and thus we avoid the last controller, 127, which is "System Reset". When working neatly, there's no chance this CC127 could end up at out synth, but you never know. Better safe than sorry). So change the text fader's In and Out definition to controller 80. We're done with the fader.

Now make 2 new transformers, call them MSB and LSB, and connect fader and transformers serially in the order "fader > MSB > LSB". Open the MSB's window. This one will take care of the sysex MSB. So, as before, we need a controller on channel 2 (alter the Sysex Mapper's message without sending it), and we need a Map to change the MSB values from 0-1 to $7F-0. Set the transformer to match this:

Conditions:
Status = Control
Cha: All
-1-: Inside 80-94
-2-: Inside 0-1

Operations:
Status: Thru
Cha: Fix 2
-1-: Fix 1 (since the sysex MSB is in the 1st data byte)
-2-: Use Map

The Map is simple: let 0 be mapped to 127, and 1 to 0. That's all. Now CC80=0 will output a value of 127, and CC80=1 will output 0, thus giving us exactly the MSBs we need.

The LSB transformer is even simpler: set the Conditions to "Status=Control, and -1-: Inside 112-126". Leave the rest of the Conditions unchanged. The settings for "-1" guarantee that only the LSB-controller (CC112 here) will be affected and the MSB-controller (CC1, and not CC80 anymore !) can pass through unaffected. Set the Operations to "Cha: Fix 1, and -1-: Fix 2". Channel=1 will make sure that the Sysex Mapper will send out its message, and setting the controller number to 2 will change the 2nd byte (which indeed is the LSB in the sysex string). Note that we don't need the Operations "-2-" setting: the value of the incoming CC112 message is already exactly what we need. So no hassle with Map's here...

To conclude the exercise, create a third transformer, cable it at the end of the chain we already had, and set it to be a Sysex Mapper. Set Sysex Length to 4, and connect a Monitor object to the end of the chain. That's all there is to it.

top

5. Comparing both approaches

In order to make a fader with a range from -50 to 50, we had to do quite a lot of work, including drawing rather complex Maps. Making the last fader, ranging from -99 to 99, was quite a bit easier. It's obvious that the last approach could also have been used in the first case: make a 14Bit fader and set it's range from 128-50 to 128+50 (i.e. from 78 to 178). Then follow the same strategy. However, there are several reasons why you still might want to opt for the more complex solution. First of all, using 14Bit faders limits the range of available controllers: as we saw, you can't use CC96 and up with 14Bit faders. Second, and maybe most important: 14Bit faders are messy to automate. If you let Logic record, and move a 14Bit fader, you'll be recording pairs of CC data -- the LSB and the MSB. Editing those afterwards is a nightmare, and hyperdrawing data for a 14Bit fader is next to impossible (since you'd have to draw 2 separate controllers). Suppose your automation ramp goes up from -10 to 10, and you want to change it to a -9/11 ramp (i.e. everything 1 up). With a 14Bit fader... well... figure this one out yourself, as an exercise (hint: LSB wraps to 0 after reaching 127, and MSB changes from 127 to 0 when the fader reaches 0). Using a simple standard (7 bit) fader, this sort of things is easy as pie: open event list, select all, drag one up, close window, done. Point taken? OK.

top

6. Further, and beyond Logic's abilities

You might ever need a fader that has a range from 0 to e.g. 500 (like Delay Time). The strategy is exactly as before. The only thing to be aware of is that Logic's text fader just can't handle this. Somewhere around 256, the text fader stops functioning properly. In this case you therefore have to use a regular fader. The environment that comes with this tutorial shows a 0-500 fader at work. After the foregoing, you should be able to figure out how it works.

When trying to make a -250/+250 fader you run into serious trouble, even though the range of the fader is similar to that of a 0-500 fader. The problem is that, in order to have the fader display the proper numbers, you have to use a text fader. But... text faders can't cover such a range. Deadlock... And there's no way out of it. It simply can't be done in any decent way. Of course it's entirely possible to use a 14Bit fader and use the strategy described in the section on the -99/+99 fader -- no problem with that. Only this time you'll be forced to use a non-text fader, and thus the display of the values will be all wrong. If you can live with that: fine. If you can't, then you simply won't be able to create faders which 1) require negative numbers and 2) exceed the 256-values (or so) limit.

Well, that's it for today. Enough stuff to chew on for I while, I reckon...

top

(c) H.J. Veenstra 2001.