Fig.1 - Hammond XB5 Drawbars |
I finally got around to picking up this project again.
In Part 2 I got the Arduino receiving MIDI messages from the XB5 organ and retransmitting them to the B4000+ organ sound module. The next step was to work on the Arduino code to change the CC (control change) messages coming from the XB5, to control the functions in the B4000+.
The format of MIDI CC messages is shown below.
B0h byte1 byte2
B0 signifies a CC message on MIDI channel 1.
byte1 is the control number.
byte2 is the control value.
Both the XB5 and the B4000+ use CC messages for drawbar settings, but the implementation is completely different. So what was needed was a way to convert the CC messages. Looking at the MIDI implementation charts for both devices I could see that there was no simple relationship between the two, therefore some kind of lookup table would be required, i.e. in programming terms, one or more arrays. I decided to use some simple maths to generate a contiguous range of index values from the two bytes from the XB5. This would then be used as an index for two arrays, which would produce the two bytes needed for the B4000+.
Index = (XB5byte1 - 80) x 81 + XB5byte2
The table below shows the relationship between the XB5 and B4000+ bytes for all the available drawbar settings produced by the XB5. The numbers are all in decimal.
XB5 | B4000+ | Descr' | Index | ||||
Byte1 | Byte2 | Byte1 | Byte2 | Manual | D'bar | Level | |
80 | 0 | 27 | 0 | Upper | 16' | 0 | 0 |
80 | 1 | 27 | 16 | Upper | 16' | 1 | 1 |
80 | 2 | 27 | 32 | Upper | 16' | 2 | 2 |
80 | 3 | 27 | 48 | Upper | 16' | 3 | 3 |
80 | 4 | 27 | 64 | Upper | 16' | 4 | 4 |
80 | 5 | 27 | 80 | Upper | 16' | 5 | 5 |
80 | 6 | 27 | 96 | Upper | 16' | 6 | 6 |
80 | 7 | 27 | 112 | Upper | 16' | 7 | 7 |
80 | 8 | 27 | 127 | Upper | 16' | 8 | 8 |
80 | 9 | 28 | 0 | Upper | 5 1/3' | 0 | 9 |
80 | 10 | 28 | 16 | Upper | 5 1/3' | 1 | 10 |
80 | 11 | 28 | 32 | Upper | 5 1/3' | 2 | 11 |
80 | 12 | 28 | 48 | Upper | 5 1/3' | 3 | 12 |
80 | 13 | 28 | 64 | Upper | 5 1/3' | 4 | 13 |
80 | 14 | 28 | 80 | Upper | 5 1/3' | 5 | 14 |
80 | 15 | 28 | 96 | Upper | 5 1/3' | 6 | 15 |
80 | 16 | 28 | 112 | Upper | 5 1/3' | 7 | 16 |
80 | 17 | 28 | 127 | Upper | 5 1/3' | 8 | 17 |
80 | 18 | 29 | 0 | Upper | 8' | 0 | 18 |
80 | 19 | 29 | 16 | Upper | 8' | 1 | 19 |
80 | 20 | 29 | 32 | Upper | 8' | 2 | 20 |
80 | 21 | 29 | 48 | Upper | 8' | 3 | 21 |
80 | 22 | 29 | 64 | Upper | 8' | 4 | 22 |
80 | 23 | 29 | 80 | Upper | 8' | 5 | 23 |
80 | 24 | 29 | 96 | Upper | 8' | 6 | 24 |
80 | 25 | 29 | 112 | Upper | 8' | 7 | 25 |
80 | 26 | 29 | 127 | Upper | 8' | 8 | 26 |
80 | 27 | 30 | 0 | Upper | 4' | 0 | 27 |
80 | 28 | 30 | 16 | Upper | 4' | 1 | 28 |
80 | 29 | 30 | 32 | Upper | 4' | 2 | 29 |
80 | 30 | 30 | 48 | Upper | 4' | 3 | 30 |
80 | 31 | 30 | 64 | Upper | 4' | 4 | 31 |
80 | 32 | 30 | 80 | Upper | 4' | 5 | 32 |
80 | 33 | 30 | 96 | Upper | 4' | 6 | 33 |
80 | 34 | 30 | 112 | Upper | 4' | 7 | 34 |
80 | 35 | 30 | 127 | Upper | 4' | 8 | 35 |
80 | 36 | 31 | 0 | Upper | 2 2/3' | 0 | 36 |
80 | 37 | 31 | 16 | Upper | 2 2/3' | 1 | 37 |
80 | 38 | 31 | 32 | Upper | 2 2/3' | 2 | 38 |
80 | 39 | 31 | 48 | Upper | 2 2/3' | 3 | 39 |
80 | 40 | 31 | 64 | Upper | 2 2/3' | 4 | 40 |
80 | 41 | 31 | 80 | Upper | 2 2/3' | 5 | 41 |
80 | 42 | 31 | 96 | Upper | 2 2/3' | 6 | 42 |
80 | 43 | 31 | 112 | Upper | 2 2/3' | 7 | 43 |
80 | 44 | 31 | 127 | Upper | 2 2/3' | 8 | 44 |
80 | 45 | 33 | 0 | Upper | 2' | 0 | 45 |
80 | 46 | 33 | 16 | Upper | 2' | 1 | 46 |
80 | 47 | 33 | 32 | Upper | 2' | 2 | 47 |
80 | 48 | 33 | 48 | Upper | 2' | 3 | 48 |
80 | 49 | 33 | 64 | Upper | 2' | 4 | 49 |
80 | 50 | 33 | 80 | Upper | 2' | 5 | 50 |
80 | 51 | 33 | 96 | Upper | 2' | 6 | 51 |
80 | 52 | 33 | 112 | Upper | 2' | 7 | 52 |
80 | 53 | 33 | 127 | Upper | 2' | 8 | 53 |
80 | 54 | 34 | 0 | Upper | 1 3/5' | 0 | 54 |
80 | 55 | 34 | 16 | Upper | 1 3/5' | 1 | 55 |
80 | 56 | 34 | 32 | Upper | 1 3/5' | 2 | 56 |
80 | 57 | 34 | 48 | Upper | 1 3/5' | 3 | 57 |
80 | 58 | 34 | 64 | Upper | 1 3/5' | 4 | 58 |
80 | 59 | 34 | 80 | Upper | 1 3/5' | 5 | 59 |
80 | 60 | 34 | 96 | Upper | 1 3/5' | 6 | 60 |
80 | 61 | 34 | 112 | Upper | 1 3/5' | 7 | 61 |
80 | 62 | 34 | 127 | Upper | 1 3/5' | 8 | 62 |
80 | 63 | 35 | 0 | Upper | 1 1/3' | 0 | 63 |
80 | 64 | 35 | 16 | Upper | 1 1/3' | 1 | 64 |
80 | 65 | 35 | 32 | Upper | 1 1/3' | 2 | 65 |
80 | 66 | 35 | 48 | Upper | 1 1/3' | 3 | 66 |
80 | 67 | 35 | 64 | Upper | 1 1/3' | 4 | 67 |
80 | 68 | 35 | 80 | Upper | 1 1/3' | 5 | 68 |
80 | 69 | 35 | 96 | Upper | 1 1/3' | 6 | 69 |
80 | 70 | 35 | 112 | Upper | 1 1/3' | 7 | 70 |
80 | 71 | 35 | 127 | Upper | 1 1/3' | 8 | 71 |
80 | 72 | 36 | 0 | Upper | 1' | 0 | 72 |
80 | 73 | 36 | 16 | Upper | 1' | 1 | 73 |
80 | 74 | 36 | 32 | Upper | 1' | 2 | 74 |
80 | 75 | 36 | 48 | Upper | 1' | 3 | 75 |
80 | 76 | 36 | 64 | Upper | 1' | 4 | 76 |
80 | 77 | 36 | 80 | Upper | 1' | 5 | 77 |
80 | 78 | 36 | 96 | Upper | 1' | 6 | 78 |
80 | 79 | 36 | 112 | Upper | 1' | 7 | 79 |
80 | 80 | 36 | 127 | Upper | 1' | 8 | 80 |
81 | 0 | 18 | 0 | Lower | 16' | 0 | 81 |
81 | 1 | 18 | 16 | Lower | 16' | 1 | 82 |
81 | 2 | 18 | 32 | Lower | 16' | 2 | 83 |
81 | 3 | 18 | 48 | Lower | 16' | 3 | 84 |
81 | 4 | 18 | 64 | Lower | 16' | 4 | 85 |
81 | 5 | 18 | 80 | Lower | 16' | 5 | 86 |
81 | 6 | 18 | 96 | Lower | 16' | 6 | 87 |
81 | 7 | 18 | 112 | Lower | 16' | 7 | 88 |
81 | 8 | 18 | 127 | Lower | 16' | 8 | 89 |
81 | 9 | 19 | 0 | Lower | 5 1/3' | 0 | 90 |
81 | 10 | 19 | 16 | Lower | 5 1/3' | 1 | 91 |
81 | 11 | 19 | 32 | Lower | 5 1/3' | 2 | 92 |
81 | 12 | 19 | 48 | Lower | 5 1/3' | 3 | 93 |
81 | 13 | 19 | 64 | Lower | 5 1/3' | 4 | 94 |
81 | 14 | 19 | 80 | Lower | 5 1/3' | 5 | 95 |
81 | 15 | 19 | 96 | Lower | 5 1/3' | 6 | 96 |
81 | 16 | 19 | 112 | Lower | 5 1/3' | 7 | 97 |
81 | 17 | 19 | 127 | Lower | 5 1/3' | 8 | 98 |
81 | 18 | 20 | 0 | Lower | 8' | 0 | 99 |
81 | 19 | 20 | 16 | Lower | 8' | 1 | 100 |
81 | 20 | 20 | 32 | Lower | 8' | 2 | 101 |
81 | 21 | 20 | 48 | Lower | 8' | 3 | 102 |
81 | 22 | 20 | 64 | Lower | 8' | 4 | 103 |
81 | 23 | 20 | 80 | Lower | 8' | 5 | 104 |
81 | 24 | 20 | 96 | Lower | 8' | 6 | 105 |
81 | 25 | 20 | 112 | Lower | 8' | 7 | 106 |
81 | 26 | 20 | 127 | Lower | 8' | 8 | 107 |
81 | 27 | 21 | 0 | Lower | 4' | 0 | 108 |
81 | 28 | 21 | 16 | Lower | 4' | 1 | 109 |
81 | 29 | 21 | 32 | Lower | 4' | 2 | 110 |
81 | 30 | 21 | 48 | Lower | 4' | 3 | 111 |
81 | 31 | 21 | 64 | Lower | 4' | 4 | 112 |
81 | 32 | 21 | 80 | Lower | 4' | 5 | 113 |
81 | 33 | 21 | 96 | Lower | 4' | 6 | 114 |
81 | 34 | 21 | 112 | Lower | 4' | 7 | 115 |
81 | 35 | 21 | 127 | Lower | 4' | 8 | 116 |
81 | 36 | 22 | 0 | Lower | 2 2/3' | 0 | 117 |
81 | 37 | 22 | 16 | Lower | 2 2/3' | 1 | 118 |
81 | 38 | 22 | 32 | Lower | 2 2/3' | 2 | 119 |
81 | 39 | 22 | 48 | Lower | 2 2/3' | 3 | 120 |
81 | 40 | 22 | 64 | Lower | 2 2/3' | 4 | 121 |
81 | 41 | 22 | 80 | Lower | 2 2/3' | 5 | 122 |
81 | 42 | 22 | 96 | Lower | 2 2/3' | 6 | 123 |
81 | 43 | 22 | 112 | Lower | 2 2/3' | 7 | 124 |
81 | 44 | 22 | 127 | Lower | 2 2/3' | 8 | 125 |
81 | 45 | 23 | 0 | Lower | 2' | 0 | 126 |
81 | 46 | 23 | 16 | Lower | 2' | 1 | 127 |
81 | 47 | 23 | 32 | Lower | 2' | 2 | 128 |
81 | 48 | 23 | 48 | Lower | 2' | 3 | 129 |
81 | 49 | 23 | 64 | Lower | 2' | 4 | 130 |
81 | 50 | 23 | 80 | Lower | 2' | 5 | 131 |
81 | 51 | 23 | 96 | Lower | 2' | 6 | 132 |
81 | 52 | 23 | 112 | Lower | 2' | 7 | 133 |
81 | 53 | 23 | 127 | Lower | 2' | 8 | 134 |
81 | 54 | 24 | 0 | Lower | 1 3/5' | 0 | 135 |
81 | 55 | 24 | 16 | Lower | 1 3/5' | 1 | 136 |
81 | 56 | 24 | 32 | Lower | 1 3/5' | 2 | 137 |
81 | 57 | 24 | 48 | Lower | 1 3/5' | 3 | 138 |
81 | 58 | 24 | 64 | Lower | 1 3/5' | 4 | 139 |
81 | 59 | 24 | 80 | Lower | 1 3/5' | 5 | 140 |
81 | 60 | 24 | 96 | Lower | 1 3/5' | 6 | 141 |
81 | 61 | 24 | 112 | Lower | 1 3/5' | 7 | 142 |
81 | 62 | 24 | 127 | Lower | 1 3/5' | 8 | 143 |
81 | 63 | 25 | 0 | Lower | 1 1/3' | 0 | 144 |
81 | 64 | 25 | 16 | Lower | 1 1/3' | 1 | 145 |
81 | 65 | 25 | 32 | Lower | 1 1/3' | 2 | 146 |
81 | 66 | 25 | 48 | Lower | 1 1/3' | 3 | 147 |
81 | 67 | 25 | 64 | Lower | 1 1/3' | 4 | 148 |
81 | 68 | 25 | 80 | Lower | 1 1/3' | 5 | 149 |
81 | 69 | 25 | 96 | Lower | 1 1/3' | 6 | 150 |
81 | 70 | 25 | 112 | Lower | 1 1/3' | 7 | 151 |
81 | 71 | 25 | 127 | Lower | 1 1/3' | 8 | 152 |
81 | 72 | 26 | 0 | Lower | 1' | 0 | 153 |
81 | 73 | 26 | 16 | Lower | 1' | 1 | 154 |
81 | 74 | 26 | 32 | Lower | 1' | 2 | 155 |
81 | 75 | 26 | 48 | Lower | 1' | 3 | 156 |
81 | 76 | 26 | 64 | Lower | 1' | 4 | 157 |
81 | 77 | 26 | 80 | Lower | 1' | 5 | 158 |
81 | 78 | 26 | 96 | Lower | 1' | 6 | 159 |
81 | 79 | 26 | 112 | Lower | 1' | 7 | 160 |
81 | 80 | 26 | 127 | Lower | 1' | 8 | 161 |
82 | 0 | 12 | 0 | Pedal | 16' | 0 | 162 |
82 | 1 | 12 | 16 | Pedal | 16' | 1 | 163 |
82 | 2 | 12 | 32 | Pedal | 16' | 2 | 164 |
82 | 3 | 12 | 48 | Pedal | 16' | 3 | 165 |
82 | 4 | 12 | 64 | Pedal | 16' | 4 | 166 |
82 | 5 | 12 | 80 | Pedal | 16' | 5 | 167 |
82 | 6 | 12 | 96 | Pedal | 16' | 6 | 168 |
82 | 7 | 12 | 112 | Pedal | 16' | 7 | 169 |
82 | 8 | 12 | 127 | Pedal | 16' | 8 | 170 |
82 | 9 | 14 | 0 | Pedal | 8' | 0 | 171 |
82 | 10 | 14 | 16 | Pedal | 8' | 1 | 172 |
82 | 11 | 14 | 32 | Pedal | 8' | 2 | 173 |
82 | 12 | 14 | 48 | Pedal | 8' | 3 | 174 |
82 | 13 | 14 | 64 | Pedal | 8' | 4 | 175 |
82 | 14 | 14 | 80 | Pedal | 8' | 5 | 176 |
82 | 15 | 14 | 96 | Pedal | 8' | 6 | 177 |
82 | 16 | 14 | 112 | Pedal | 8' | 7 | 178 |
82 | 17 | 14 | 127 | Pedal | 8' | 8 | 179 |
While playing with the MIDI connected XB5 and B4000+, I discovered that changing the Leslie speed to fast on the XB5 caused all of the sound from the B4000+ to be muted. The B4000+ was also muted if the XB5 organ was powered-up after the B4000+, since the XB5 was sending the CC message for fast Leslie on power-up. The XB5 uses the control number 5C (92 in decimal) for fast Leslie, but this causes the B4000+ to mute all the audio. It took me a while to work this out since, according to the B4000+ MIDI implementation chart, this CC number is unused! The solution is to remap it to the appropriate B4000+ CC number.
I implemented the drawbar (and Leslie speed) remapping in the Arduino code and used MIDI-OX for testing and debugging, before confirming correct operation with the XB5 and B4000+. The resulting code is shown below.
// *** XB5remap3 ***
#include <MIDI.h>
#include <AltSoftSerial.h>
AltSoftSerial midiSerial;
MIDI_CREATE_INSTANCE(AltSoftSerial, midiSerial, MIDI);
// MIDI RX/TX pins are 8/9
int byte1[] = {27, 27, 27, 27, 27, 27, 27, 27, 27,
28, 28, 28, 28, 28, 28, 28, 28, 28,
29, 29, 29, 29, 29, 29, 29, 29, 29,
30, 30, 30, 30, 30, 30, 30, 30, 30,
31, 31, 31, 31, 31, 31, 31, 31, 31,
33, 33, 33, 33, 33, 33, 33, 33, 33,
34, 34, 34, 34, 34, 34, 34, 34, 34,
35, 35, 35, 35, 35, 35, 35, 35, 35,
36, 36, 36, 36, 36, 36, 36, 36, 36,
18, 18, 18, 18, 18, 18, 18, 18, 18,
19, 19, 19, 19, 19, 19, 19, 19, 19,
20, 20, 20, 20, 20, 20, 20, 20, 20,
21, 21, 21, 21, 21, 21, 21, 21, 21,
22, 22, 22, 22, 22, 22, 22, 22, 22,
23, 23, 23, 23, 23, 23, 23, 23, 23,
24, 24, 24, 24, 24, 24, 24, 24, 24,
25, 25, 25, 25, 25, 25, 25, 25, 25,
26, 26, 26, 26, 26, 26, 26, 26, 26,
12, 12, 12, 12, 12, 12, 12, 12, 12,
14, 14, 14, 14, 14, 14, 14, 14, 14}; // Array for byte1
int byte2[] = {0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127,
0, 16, 32, 48, 64, 80, 96, 112, 127}; // Array for byte2
int index = 0; // Index for arrays
void setup() {
byte inControlNumber;
Serial.begin(115200);
Serial.println("starting serial with debug monitor");
Serial.println("MIDI RX/TX pins are 8/9");
MIDI.begin(MIDI_CHANNEL_OMNI); // Launch MIDI, by default listening to all channels
MIDI.turnThruOff(); // we have to avoid channel based events being sent thru
// set callbacks for all different event types
MIDI.setHandleClock(handleMidiEventClock);
MIDI.setHandleTick(handleMidiEventClock);
MIDI.setHandleStop(handleMidiEventStop);
MIDI.setHandleStart(handleMidiEventStart);
MIDI.setHandleContinue(handleMidiEventContinue);
MIDI.setHandleNoteOn(handleMidiEventNoteOn);
MIDI.setHandleNoteOff(handleMidiEventNoteOff);
MIDI.setHandleAfterTouchPoly(handleMidiEventAfterTouchPoly);
MIDI.setHandleAfterTouchChannel(handleMidiEventAfterTouchChannel);
MIDI.setHandleProgramChange(handleMidiEventProgramChange);
MIDI.setHandleControlChange(handleMidiEventControlChange);
MIDI.setHandlePitchBend(handleMidiEventPitchBend);
MIDI.setHandleSystemExclusive(handleMidiEventSysEx);
MIDI.setHandleTimeCodeQuarterFrame(handleMidiEventTimeCodeQuarterFrame);
MIDI.setHandleSongPosition(handleMidiEventSongPosition);
MIDI.setHandleSongSelect(handleMidiEventSongSelect);
MIDI.setHandleTuneRequest(handleMidiEventTuneRequest);
MIDI.setHandleSystemReset(handleMidiEventSystemReset);
// Reset all bass pedal drawbars except for 16' and 8'.
int inChannel = 1;
int inControlValue = 0;
inControlNumber = 13;
MIDI.sendControlChange(inControlNumber, inControlValue, inChannel);
inControlNumber = 15;
MIDI.sendControlChange(inControlNumber, inControlValue, inChannel);
inControlNumber = 16;
MIDI.sendControlChange(inControlNumber, inControlValue, inChannel);
inControlNumber = 17;
MIDI.sendControlChange(inControlNumber, inControlValue, inChannel);
}
void loop() {
MIDI.read();
}
void handleMidiEventNoteOn(byte inChannel, byte inPitch, byte inVelocity) {
MIDI.sendNoteOn(inPitch, inVelocity, inChannel);
}
void handleMidiEventNoteOff(byte inChannel, byte inPitch, byte inVelocity) {
MIDI.sendNoteOff(inPitch, inVelocity, inChannel);
}
void handleMidiEventAfterTouchPoly(byte inChannel, byte inNote, byte inValue) {
MIDI.sendAfterTouch(inNote, inValue, inChannel);
}
void handleMidiEventControlChange(byte inChannel, byte inControlNumber, byte inControlValue)
{
if (inControlNumber!=5) //Use this to filter out a CC message
{
if (inControlNumber==92)
{
inControlNumber=4; //Change CC number for Leslie speed control
}
if ((inControlNumber==80)||(inControlNumber==81)||(inControlNumber==82)) //Check if it is drawbar setting
{
index = (inControlNumber-80)*81 + inControlValue;
inControlNumber = byte1[index];
inControlValue = byte2[index];
}
MIDI.sendControlChange(inControlNumber, inControlValue, inChannel);
}
}
void handleMidiEventProgramChange(byte inChannel, byte inProgramNumber) {
MIDI.sendProgramChange(inProgramNumber, inChannel);
}
void handleMidiEventAfterTouchChannel(byte inChannel, byte inPressure) {
MIDI.sendAfterTouch(inPressure, inChannel);
}
void handleMidiEventPitchBend(byte inChannel, int inPitchValue) {
//MIDI.sendPitchBend(inPitchValue, inChannel); Dont forward pitchbend messages
}
void handleMidiEventSysEx(byte* inData, unsigned inSize)
{
MIDI.sendSysEx(inSize, inData, true);
}
void handleMidiEventTimeCodeQuarterFrame(byte inData)
{
MIDI.sendTimeCodeQuarterFrame(inData);
}
void handleMidiEventSongPosition(unsigned inBeats)
{
MIDI.sendSongPosition(inBeats);
}
void handleMidiEventSongSelect(byte inSongNumber)
{
MIDI.sendSongSelect(inSongNumber);
}
void handleMidiEventTuneRequest()
{
MIDI.sendTuneRequest();
}
void handleMidiEventSystemReset() {
MIDI.sendSystemReset();
}
void handleMidiEventClock() {
MIDI.sendClock();
}
void handleMidiEventStart() {
MIDI.sendStart();
}
void handleMidiEventStop() {
MIDI.sendStop();
}
void handleMidiEventContinue() {
MIDI.sendContinue();
}
The next step will be to work on the code to map the NRPN messages coming from the XB5 organ to control further functions in the B4000+ sound module. My hope is that I will be able to use the push-buttons on the XB5 to control the classic ‘B3’ features such as vibrato, chorus, percussion (2nd & 3rd harmonic) etc.
Continued in Part 4.
References
“277-MIDI Programming 101”, HARMAN, https://help.harmanpro.com/Documents/2215/277-MIDI%20Programming%20101.pdf
Disclaimer: This is my personal blog. Views expressed in my posts are my own and not of my employer. The information provided comes with no warranty. I cannot be held responsible for the content of external websites. Any practical work you undertake is done at your own risk. Please make health and safety your number one priority.
Comments
Post a Comment