Arduino MIDI Remapper - Part 3

Hammond XB5 Drawbars
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