Monday, 23 February 2015

Sending MIDI messages from our MIDI keyboard

Originally we hadn't planned on creating a complete MIDI keyboard. The idea was to simply modify an existing (working) keyboard and add some lights to it. The Oregon keyboard we eventually went with just looked so cool, would couldn't pass it up - even if it meant buying it "sold as seen".

And although it was seen with all the lights working, ultimately it turned out that it was "sold as broken". So we've had to gut the thing completely and build our own controller for it, as well as add in our light-up keys.

This does have a few benefits (despite meaning loads more work) - firstly, we'll be able to use any MIDI soundbank as we like (the keyboard was first built in the mid-80s and some of the synthesized sounds were... erm, a bit sketchy to say the least!). It also means we've about a million un-used buttons and sliders all over the original casing, which we can re-use for any purpose we like.

Luckily, the original device used a relatively simple 8-bit multiplexing method of reading the entire keypad - or at least, if it didn't the hardware suggests that it did something very similar.

We can read each bank of eight keys by sending their common line low and detecting which of our input pins has gone low (indicating that a key has been pressed). Then we send the common line of the next back of keys low, and detect which of those keys have been pressed by monitoring the same set of 8 input pins. The reason we think this is how the keyboard originally worked is because every single pad on the breakout circuit board has been connected with a diode, which isolates each bank of eight keys and only allows the current to flow "from" the keypad connector "towards" the common rail - the ideal set-up if you're using pull-up resistors on your input pins, and shorting the keys to ground when they are pressed.

So now we can detect keypresses, we need to monitor (and remember) the state of each key as it is both pressed and released.

Voice MessageStatus ByteData byte1Date byte2
Note off8xKey numberNote Off velocity
Note on9xKey numberNote On velocity
Polyphonic Key PressureAxKey numberPressure amount
Control changeBxControl numberControl value
Program changeCxProgram number-
Channel pressureDxPressure value-
Pitch bendExMSBLSB

We're going to be focussing on note on and off signals (our hardware isn't really set up for velocity, so we'll just set this to "full whack" - or maybe make it an editable option). We may use control and program change messages for some of the re-purposed buttons, but for now let's concentrate on getting the keys working: there's no point having a MIDI keyboard that doesn't detect and respond to keypresses!

Our 49 keys mean storing the current state of each key across seven bytes. We're going to imagine these bytes not in groups of eight bits, but as one, big, massive 56 bit value. Simply put, each time a key is pressed, we set the corresponding bit in this big long number to a one, and when it is released, we un-set the corresponding bit to zero.

Each time we read the input port, we compare the value on the input pins to the corresponding byte in this big long number (since the keys are read in groups of eight also). If they are the same, then nothing has changed.

If they are different, we look to see whether a bit value has gone from zero to one (a key has been pressed) or a bit has gone from one to zero (a key has been released). We can then generate and send the appropriate MIDI signal.

When we start, all our values are zero (all the keys are released). Let's say we hit a C-major chord. The first byte is now 10001001 and the second byte is 00001000 (if we do this higher up the keyboard, it may be the third and fourth bytes, or the same pattern may be split over bytes two and three, but you get the idea).

To detect keypresses we do this: Take the previous pattern and XOR the current key pattern over it.
This gives us all the keys that have recently changed - note, this isn't necessarily the keys that have just been pressed, as we'll soon see.
So our original pattern of

00000000 00000000 XOR
10001001 00001000 =
10001001 00001000

While this does correspond to all the keys that we've just pressed, it's important to understand that this is a pattern of keys that have changed. The next time we scan the keypad, we get

10001001 00001000 XOR (previous pattern of keys pressed)
10001001 00001000 = (current pattern of keys pressed)
00000000 00000000 (nothing has changed).

Now let's release the keys and see what happens:
10001001 00001000 XOR (previous pattern of keys pressed)

00000000 00000000 = (current pattern of keys pressed)
10001001 00001000 (the keys that make up the C major chord have changed)

So we're able to detect which keys  have changed - now we need to work out whether the key has been pressed or released. We need to do this is two stages - first work out which keys have been released then work out which keys have been pressed:

Take the pattern of keys that have changed and bit-wise AND with the current (input) pattern.

Any change from nothing to C-major to nothing again isn't going to demonstrate things as nicely as changing from one chord to another, so here goes - we're going from C to Cm7 (hey, maybe this is some quirky jazz number or something?)

We're holding down a C major chord, which we represent (in binary) as 1000100100001000
Now let's move to C minor 7th, which is represented as 1001000100100000

First, get the keys that have changed by bit-wise XOR-ing the two values:

1000100100001000 XOR (before)
1001000100100000 = (after)

The changed pattern shows not only the keys that have been released, but also the keys that have been pressed. So let's get the keys that have been released first (assuming you lift your fingers off the keys before placing them in new locations).

Take the resultant "changes" pattern and bitwise AND it with the previous pattern

0001100000101000 AND (result)
1000100100001000 = (previous pattern)

And if we compare this back to the keyboard, we can see that we've identified

And it turns out that these are the two keys that we've lifted off. So far so good. Now let's work out which keys have just been pressed. To do this we bitwise AND the current (input) pattern with the resulting pattern of changes.

0001000000100000 AND (result)
1001000100100000 = (new/current pattern)

And if we compare this back to the keyboard, we've identified

and it just so happens that these are the two keys we've just pressed/added to the previous chord. The notes that didn't change (the C and the G) don't appear anywhere, because they didn't actually change.

So there we have it - we create a pattern to find out which keys have changed by XOR-ing the current pattern with the previous pattern.

To find out which keys have been released, we AND this result with the previous pattern. To find out which keys have been pressed, we AND this result with the current pattern.

Now we can detect keys being pressed and released, we need to send our MIDI messages to the synthesizer/midi sequencer. We'll add some clever stuff in later, to change which channel we're sending data on, so for now, we'll assume channel one (but it could be any channel from zero to 15).

Most ad-hoc MIDI messages are made up of just a simple three byte packet. The upper half of the first byte is 8 for a note off message (1000----). The lower half of the byte is channel number (we're using 1 for now ----0001). So our first byte, for any key release will start with 0x81 (or 10000001 in binary).

The second byte is the note value, in the range 21-108, where 0 is low A0 (27.5hz) and 108 is C8 (4186hz). Concert pitch A (the note most instruments use as a reference for tuning, at 440hz) is MIDI note number 69 (decimal, 0x45 in hex).

Note a note on MIDI values - all values (except for the first byte) must remain in the range 0-127. This allows MIDI devices to detect the beginning of a midi message - it should be the only byte in the entire packet that has the MSB set to one; all values contained within the message should always have a MSB un-set to zero.

The final byte in a MIDI message is the velocity (how hard you hit the note). We're going to just use a global variable for this - so you can set the velocity for every key, else just set it to maximum (0x7F - note that the leading MSB is cleared).

That's as simple as our MIDI messaging needs to be.

For key presses, we do exactly the same, except the first byte will begin 0x91 (the upper half of the first byte set to 0x09 indicates a note on, whereas 0x08 indicates note off).

We've a whole load of buttons still to mount into the enclosure when the time comes, which means we could have control change messages and system messages and all that kind of stuff floating around - but we're going to stick to simple note on and note off messages for now.