MIDI thru channel changer BPM issue

I'm trying to build a device that takes MIDI in on one channel and spits it out verbatim on a different channel.

I have a sequencer with one MIDI out controlling two synths. Both synths receive MIDI on channel 1 only, no way to change. In order to control the synths independently, I want to send MIDI on channel 1 for synth 1, and channel 16 for synth 2. Then have a 1-in 2-out MIDI thru, send one out to synth 1 receiving channel 1 data, and the other on channel 16 to the Arduino, change it into channel 1, and send it along to synth 2. Like this:

→ synth 1, channel 1
sequencer, channels 1 and 16 → MIDI split
→ Arduino, channel 16 → synth 2, channel 1

I have something working so far, attached below. It does exactly what I want it to do: takes MIDI in on channel n and sends it out on channel n. There's one encoder used for both input and output channel selection, depending on the encoder switch, and an OLED screen. It took a bit of hair pulling but seems to work fine... except there is just one problem left:

When I hit the play button on the sequencer controlling the device, the device plays back at 4 times the BPM! What's going on? Any suggestions for where to look? Any thoughts on my code so far? Thanks for your time!

#include <MIDI.h>
#include <Adafruit_SSD1306.h>
#include <splash.h>
#include <Encoder.h>
#include <Bounce2.h>

#define OLED_RESET -1

MIDI_CREATE_DEFAULT_INSTANCE();
Encoder theEnc(2,3);
Bounce debouncer = Bounce();
Adafruit_SSD1306 display(OLED_RESET);

int encSw = 4;
bool encSwState = false;
int midiInputChannel;
int midiOutputChannel;
long encOld1 = 0;
long encNew1;
long encOld2 = 0;
long encNew2;
int inputDs;
int outputDs;   

void setup() {

  midiInputChannel = 1;
  midiOutputChannel = 1;

  debouncer.attach(encSw, INPUT_PULLUP);
  debouncer.interval(35);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextColor(WHITE); 
  display.setTextSize(1);
  display.setCursor(11,1);
  display.print("INPUT");
  display.setCursor(16,11);
  display.setTextSize(3);
  display.print(midiInputChannel);
  display.print(" ");
  display.setTextSize(1);
  display.setCursor(81,1);
  display.print("OUTPUT");
  display.setTextSize(3);
  display.setCursor(85,11);
  display.print(midiOutputChannel);
  display.display();
  
  MIDI.begin(MIDI_CHANNEL_OMNI);
}

void loop() {

  MIDI.setInputChannel(midiInputChannel);
  
  debouncer.update();
  if (debouncer.fell()) {
    encSwState = !encSwState;
  }
  
  if (encSwState == false) {
    encNew1 = theEnc.read()/4;
    if (encNew1 > encOld1) {
      midiInputChannel++;
      if (midiInputChannel > 16) {
        midiInputChannel = 16;   
      } 
    }
    if (encNew1 < encOld1) {
      midiInputChannel--;
      if (midiInputChannel < 1) {
        midiInputChannel = 1;
      }
    }
    encOld1 = encNew1;
  } //input

  if (encSwState == true) {
    encNew2 = theEnc.read()/4;
    if (encNew2 > encOld2) {
      midiOutputChannel++;
      if (midiOutputChannel > 16) {
        midiOutputChannel = 16;    
      }  
    }
    if (encNew2 < encOld2) {
      midiOutputChannel--;
      if (midiOutputChannel < 1) {
        midiOutputChannel = 1;
      }
    }
    encOld2 = encNew2;  
  } //output

  if (inputDs != midiInputChannel || outputDs != midiOutputChannel) {
    updateDisplay();  
  }
  
  if (MIDI.read()) {
      MIDI.send(MIDI.getType(),
               MIDI.getData1(),
               MIDI.getData2(),
               midiOutputChannel);
  } 
} //loop

void updateDisplay() {
  display.clearDisplay();
  display.setTextColor(WHITE); 
  display.setTextSize(1);
  display.setCursor(11,1);
  display.print("INPUT");
  display.setCursor(16,11);
  display.setTextSize(3);
  display.print(midiInputChannel);
  display.print(" ");
  display.setTextSize(1);
  display.setCursor(81,1);
  display.print("OUTPUT");
  display.setTextSize(3);
  display.setCursor(85,11);
  display.print(midiOutputChannel);
  display.display();

  inputDs = midiInputChannel;
  outputDs = midiOutputChannel;

} //display

When I hit the play button on the sequencer controlling the device, the device plays back at 4 times the BPM!

As the sequencer is the input to your system I can’t see how your system could possibly effect the speed the sequencer sends out messages. When you say it quadruples the BPM is it sending notes to the sequencer four times for the sequencer’s one note output?

It would be good to have a list of two or three notes from the sequencer along with what is produced on each MIDI output.

I must say I find your code hard to follow and it is nothing like what I would expect to do if I had to tackle the problem.

Let me clarify the set up. I have a Beat Step Pro, a controller that has three 64-step sequencers but only one MIDI out. With a MIDI thru/splitter, I can send that one MIDI out signal to three different devices. If they each receive MIDI on a different channel, then I can control different sequences with the same transport and controller.
I have two devices that both unfortunately only receive MIDI on channel 1, so there's no way to separate the sequences from the Beat Step Pro. But if I send data for device 1 on channel 1 and data for device 2 on channel 2, then intercept the channel 2 data with the Arduino (after the MIDI splitter), I can change it back to channel 1 and device 2 will receive only the MIDI that was on channel 2.

When you say it quadruples the BPM is it sending notes to the sequencer four times for the sequencer's one note output?

No. Everything on the master sequencer works fine. It's the slave sequencer that's goofed. One of the devices I'm sending MIDI to is a Monotribe. It's a mono synth, great for bass lines. It also has 3 ultra basic drum sounds and an 8 step sequencer (which can sequence the mono synth as well as the drum sounds). When I press play on the Beat Step Pro, it sends a MIDI system message to start, right? The Monotribe's sequencer starts, but it's 4 times the rate it should be! The same thing happens to any other device with a sequencer that goes through the code. The note on/off and velocity info is right, the channel info is right, the master sequencer tempo is right, but the slave device's sequence plays back at x4.

I must say I find your code hard to follow...

I'm not a programmer, so maybe that's why it's hard to follow and clunky. Here's a breakdown:

The encoder button gives either true or false, if it's true, the encoder adjusts the output channel, if it's false, it adjusts the input channel. That gets displayed on a screen which is updated whenever the value changes (so it doesn't update every iteration of the loop). The encoder is limited to 1-16. That value is also used in the "set input channel" and "send" MIDI library functions (for input and output channels). Then read the MIDI and send it off to the channel of choosing. Or did i misunderstand what you meant?

...and it is nothing like what I would expect to do if I had to tackle the problem.

I would love to hear how you would start to tackle something like this!

Thanks it's getting clearer now but is your setup like this:-

Or is the Monotribe Synth 2 on this diagram?

What Arduino are you using?

I would love to hear how you would start to tackle something like this!

Well I would set things up like the diagram with all the data going to synth 1, because synth 1 only responds to channel 1 that will be fine for that one.
The I would set up the Arduino MIDI system to only listen to channel 2, use the call back functions like in the examples for the MIDI library. The handle MIDI functions like note on, note off, controller change and so forth will only be called when there is an event on channel 2. In those call back functions you simply send out the parameters you receive to channel 1. Thus converting that stream to channel 1.
This is what I would do for the note on messages:-

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1,     midi6pin); // I am using a Arduino Micro here

void HandleNoteOn(byte channel, byte pitch, byte velocity){

        midi6pin.sendNoteOn(pitch, velocity, 1); // send out as channel 1 
        }
}

It could be that your code is sending some messages multiple times and one of those could be a beat increment message

Or is the Monotribe Synth 2 on this diagram?

Yes, the Monotribe is Synth 2. Otherwise your diagram is correct.

What Arduino are you using?

Nano. I have an Uno, Mega and Teensy 4 at my disposal, too.

Well I would set things up like the diagram with all the data going to synth 1, because synth 1 only responds to channel 1 that will be fine for that one.

So far, so good, that's how I have it, too.

The I would set up the Arduino MIDI system to only listen to channel 2

How would you go about doing that? How would you make it a variable that I can change?

I had a bit of a time wrapping my brain around how to get the input channel to be a variable that I can adjust with an encoder. I was stuck on "MIDI.begin" which is in setup; no dice. Yes, I could "hard code" the input channel, but if I'm going through the trouble of making this thing, I might as well try to do it right. My solution was to use this in the loop:
MIDI.setInputChannel(midiInputChannel)
where midiInputChannel is the variable that I can change with the encoder. It works this way, which I'm grateful for since I couldn't think of any other way to do it.

use the call back functions

I'm new to these, I'll read up more!

will only be called when there is an event on channel 2

That only includes note-on messages, because it's the HandleNoteOn call back, correct? So for note-off, clock, start, stop and CC messages, I'll have a separate call back for each type of message? I'm green, sorry if it's a dumb question! I'm looking at this page:
http://arduinomidilib.sourceforge.net/a00001.html
I see this list of functions, that's where I saw setInputChannel. It took a while to figure out how to call it, like this: "MIDI.setInputChannel". I'm not sure how to interpret the data on that list of functions. If I create a function like your "void HandleNoteOn" do I need to call it in the loop? Or it's outside of the loop and called automatically because of MIDI_CREATE_INSTANCE? Would I just do the same for note-off, clock, transport and CC messages?

It could be that your code is sending some messages multiple times and one of those could be a beat increment message

The meat and potatoes of my code is this:

if (MIDI.read()) {
      MIDI.send(MIDI.getType(),
               MIDI.getData1(),
               MIDI.getData2(),
               midiOutputChannel);

Shouldn't that cover all types of MIDI messages?

Thanks for your time and help!

That only includes note-on messages, because it's the HandleNoteOn call back, correct? So for note-off, clock, start, stop and CC messages, I'll have a separate call back for each type of message?

Yes.

f I create a function like your "void HandleNoteOn" do I need to call it in the loop?

No, you need to initialise it in the setup function. Given I was using the Arduino Micro as initialised in my previous code I used.

// Connect the HandleXXX function to the library, 
    midi6pin.setHandleNoteOn(HandleNoteOn);  // Put in the name of the handling function
    midi6pin.setHandleNoteOff(HandleNoteOff);  // Put in the name of the handling function
    midi6pin.setHandleControlChange(HandleControl); // control change
    midi6pin.setHandlePitchBend(HandleBend); // Pitch bend   
    midi6pin.setHandleProgramChange(HandleProgram); // change of voice

Note the name of the function could be anything, it is the function that does the job. So if I had a function called fish_chips that handled what happened when a note on message was received it would be like:-

midi6pin.setHandleNoteOn(fish_chips);

These functions must be declared or written before the setup function in the code.

I noticed the documentation you pointed to was for version 3.2 of the library. The latest version is 5.0.2, make sure you update it in the Library Manager.

I've been messing with callbacks now, trying to get the concept into my skull. I'm having a hard time understanding the library, though. I'm looking through (the updated) library, and I see this:

inline void setHandleClock(ClockCallback fptr) { mClockCallback = fptr; }

this:

inline void sendClock()         { sendRealTime(Clock); };

I'm not sure how to use these. I've tried this (I removed the rest of the code for clarity):

void handleClock() {
  MIDI.sendClock()         
}

void setup() {
MIDI.setHandleClock(handleClock);
}

and I get this error: 'class midi::MidiInterface' has no member named 'sendClock'.

I'm not sure where to go next.

One other thing to mention is that the stop and start messages are getting through just fine with my previous code, despite no callbacks dealing with them. So as it is, everything is fine except MIDI clock.

and I get this error: 'class midi::MidiInterface' has no member named 'sendClock'

.

Have a read of this:-
https://forum.arduino.cc/index.php?topic=200599.0

I looked over that before, I guess I don't understand how the information in that thread is relevant. I understand how MIDI clock works in that it's a byte (0xF8) sent every 1/24th of a beat, depending on master device BPM. Was there something in particular I missed you thought would help?

I don't need to generate a MIDI clock. I need to pass a MIDI clock. I want the MIDI clock data that comes from the Beat Step Pro sequencer to control the tempo of the Monotribe. The way I'm doing it now, without callbacks, works fine other than the tempo is 3x too fast. I say 3 times, because if I slow the Beat Step Pro down to 40BPM, the Monotribe plays back at 120BPM. I made a mistake earlier thinking it was 4x. The stop/start goes through, and apparently so does the clock, just at 1/72 of a beat instead of 1/24. It is something happening in the Arduino because it doesn't happen when it's plugged directly into the Beat Step Pro, and every other device plays back 3x the BPM, just like the Monotribe.

There's something in the MIDI library about MIDI "softthru", this:

 // MIDI Soft Thru

public:
    inline Thru::Mode getFilterMode() const;
    inline bool getThruState() const;

    inline void turnThruOn(Thru::Mode inThruFilterMode = Thru::Full);
    inline void turnThruOff();
    inline void setThruFilterMode(Thru::Mode inThruFilterMode);

private:
    void thruFilter(byte inChannel);

It seems there might be something useful lurking in there? MIDI thru full, then only read one input channel after that?

This works perfectly as a software MIDI thru, including the MIDI clock and start/stop. But how do I go about parsing the input so it only reads the one channel selected and only writes to the one channel selected?

boolean byteReady; 
unsigned char midiByte;

void setup() {
    
    Serial.begin(31250);
    byteReady = false;
    midiByte = 0;  
}

void loop() {
   if (byteReady) {
        byteReady = false;
        Serial.write(midiByte);
    }
}


void serialEvent() {
  if (Serial.available()) {
    midiByte = (unsigned char)Serial.read();
    byteReady = true;
  }
}

That just makes a copy of every byte coming in on the input and puts it on the output. You have to pares the input stream into one two or three byte messages and only pass on the ones you want to act as the thru, in your case the command messages Which include your clock. What the various MIDI libraries do is mainly this parsing job and then they do stuff with the results like firing off the call back functions.

This is not as hard as it sounds as decoding the messages is just a matter of looking at the first byte and seeing how many bytes it is going to contain and then getting the next one or two bytes or doing something with the single byte call.

You don’t have to classify then all, just what your sequencer puts out.

Do you know the structure of the first byte of a message and how it contains the channel and action information along with how many bytes are in a message?

Just thinking about this and it is even simpler that that.
You just need to look at the most significant nibble of each byte, if it is 0x8 to 0xE and the least significant nibble is not zero make it zero and pass it on. If it is zero then ignore the next two bytes.
Anything else just pass on.

See the List of MIDI messages here Summary of MIDI 1.0 Messages

You just need to look at the most significant nibble of each byte, if it is 0x8 to 0xE...

So if the status nibble is 0x8 it's note off, 0x9 note on, etc. Right? Because messages have to start with a binary 1, commands have to start at 0x8, otherwise, if they were less than 8, they would have a binary 0 as the first bit, right? Slowly wrapping my noodle around this.

...and the least significant nibble [LSN] is not zero make it zero and pass it on. If it is zero then ignore the next two bytes.

Because the LSN of the status byte is the channel information, zero following 0x8-0xE would mean a voice message for channel 1, right? So if it isn't from channel one, turning the LSN into 0x0 makes whatever voice message it is become a channel 1 voice message, right? If the LSN is already 0x0, then it's a message for channel 1, which is to be filtered out. Ignoring the next two bytes, in a note-on message for instance, would mean no pitch or velocity information getting through, which would mean no action happening; which is essentially the same as filtering it out altogether, right? Sorry to be so slow.

So I read MIDI in, one byte at a time. If the byte starts with 0x8-0xE, it's a channel voice message. If it's not from channel 1, pass the message but change the channel. If it is from channel 1, don't pass the message.

Anything else just pass on

Because anything else would be a system message, right? As in clock and transport messages.

That all seems to make good sense to me. I don't know anything about parsing serial data or changing it, I have a lot of homework to do. Any suggestions on a good place to start learning how to do something like what you suggested?
I have your book by the way, I'm looking through MIDI manipulation and the "basic double tracking" sketch, seems very relevant, I'll report back once I digest it more.

Thank you for your time and for holding my hand through this!

So if the status nibble is 0x8 it's note off, 0x9 note on, etc. Right?

Yes that’s it.

So if it isn't from channel one, turning the LSN into 0x0 makes whatever voice message it is become a channel 1 voice message, right?

Yes.

Ignoring the next two bytes, in a note-on message for instance, would mean no pitch or velocity information getting through, which would mean no action happening; which is essentially the same as filtering it out altogether, right?

Close but you must also remove the note on message, that is not pass it on. Have a variable called bytesToMiss when you detect a message for channel 1 set this to three. Then when it comes to sending the byte out only do so if the bytesToMiss variable is not zero, but don’t forget to decrement the variable instead of writing it out.

That all seems to make good sense to me. I don't know anything about parsing serial data or changing it,

You do now this is all you need to know as you are doing it on the fly a byte at a time.

Here's what I have so far. It works, mostly. It's based on your "DTrack" sketch, from your book. When the Beat Step Pro isn't playing, it's "note controller" pads plays the Monotribe perfectly. It sends out on 16 and the Monotribe receives on 1, no issues. I can play whatever melody and there's no latency in the way. It also works in that it sends MIDI clock without sending the transport commands. I want it like that so I can start and stop the Monotribe's sequencer locally, with its own control, so that way it's independent of the other devices the Beat Step Pro is sending data to, but still in sync to the tempo. So for both of those functions, it's working great. But if the Beat Step is playing, the note controller pads behave like they have bad bouncing. The Monotribe becomes glitchy and sluiggish and there are lots of hanging notes. If I press play on the Monotribe (after pressing start on the Beat Step, so it's sending MIDI clock), it plays back fine, until I start hitting the note pads, then it becomes glitchy as well. Any idea what I've messed up? Do I seem like I'm on the right path with something like this?

 boolean noteDown = LOW;
  byte inputChannel = 15;  // MIDI channel 16                 
  byte outputChannel = 0;  // MIDI channel 1


void setup() { 
  Serial.begin(31250);   
}

void loop () {
  checkIn(); 
 }
  
void checkIn(){
  static byte note = 60;
  static byte state = 0; 
  if (Serial.available() > 0) {
   byte incomingByte = Serial.read();
   switch (state){
      case 0:
         if (incomingByte == ( 0x90 | inputChannel)){ 
            noteDown = HIGH;
            state=1; }
   
         if (incomingByte == (0x80 | inputChannel)){   
            noteDown = LOW;
            state=1; }

         if (incomingByte == 0xF8) {
           state = 3; }
        break; // case 0
         
       case 1:
       // get the note to play or stop
          if(incomingByte < 128) {
             note = incomingByte;
             state = 2;
          }else{
            state = 0; }
       break; // case 1     
         
       case 2:
       // get the velocity
          if(incomingByte < 128) {
             doNote(note, incomingByte, noteDown); }
          state = 0;  
       break; // case 2
       
       case 3: //for midi clock message
          doClock();    
          state = 0;          
       break; // case 3 
         
     } // switch state
  } // if serial avail
} //check in

void doNote(byte note, byte velocity, int down){
  // if velocity = 0 on a 'Note ON' command, treat it as a note off
  if ((down == HIGH) && (velocity == 0)){
      down = LOW; 
  }
  // send out this note message 
  if(down == LOW) {
    noteSend(0x80,note,velocity); // note off
  }else{  
    noteSend(0x90,note,velocity); // note on
  }
} // doNote

 void noteSend(byte cmd, byte data1, byte data2) {
  cmd = (cmd | (outputChannel & 0xf));
  Serial.write(cmd);
  Serial.write(data1);
  Serial.write(data2);
}

void doClock() {
  Serial.write(0xF8); // MIDI clock
}

I've also tried this with callbacks. It seems to work fine but with the same issue: tempo is crazy fast. Are serial.read or midi.read reading the incoming bytes too fast and sending out more than one callback per byte?

#include <MIDI.h>
using namespace midi;
MIDI_CREATE_DEFAULT_INSTANCE();

void handleNoteOn(byte pitch, byte velocity, byte channel) {
  MIDI.sendNoteOn(pitch, velocity, channel); 
}

void handleNoteOff(byte pitch, byte velocity, byte channel) {
  MIDI.sendNoteOff(pitch, velocity, channel);
}

void handleCC(byte ctrlNum, byte ctrlVal, byte channel) {
  MIDI.sendControlChange(ctrlNum, ctrlVal, channel);
}

void handleClock() {
  MIDI.sendRealTime(Clock);
}

void handleStart() {
  MIDI.sendRealTime(Start);
}

void handleStop() {
  MIDI.sendRealTime(Stop);
}

void handleCont() {
  MIDI.sendRealTime(Continue);
}

void setup() {
MIDI.begin(MIDI_CHANNEL_OMNI);
  
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.setHandleControlChange(handleCC);
  MIDI.setHandleClock(handleClock);
  MIDI.setHandleStart(handleStart);
  MIDI.setHandleStop(handleStop);
  MIDI.setHandleContinue(handleCont);
}

void loop() {
  MIDI.read();
}

Are serial.read or midi.read reading the incoming bytes too fast and sending out more than one callback per byte?

No, that is not possible but it is possible that the MIDI thru is sending stuff as well as your call back functions. I think it is enabled by default so try turning it off.

Do I seem like I'm on the right path with something like this?

Well it is not what I was thinking I described in reply #11.

I think we are in different time zones. I am in the UK so lying insomniacly in bed here. I will give you an outline of what I was thinking when I can get on my laptop later in the day.

Well I have written this.
The only problem is that if a SysEx message is received it somehow screws up and stops converting the messages. A reset of the Arduino brings it right. I have not been able to find out why so far. Anyway the rest of it seems to work although I have not checked all the system messages.

If your Beat Master doesn't generate any SysEx messages you should be alright. Try this:-

/* MIDI Filter
 *  This will block any messages for channel 1
 *  and convert messages for any other channel
 *  into channel 1 messages and pass them on.
 *  By Mike Cook June 2020
 */

char inputByte;

void setup() {
  Serial.begin(31250); // MIDI speed
}

void loop() {
  while(Serial.available() > 0) {
    inputByte = Serial.read(); // get a byte from input
    analyseByte(inputByte);
  }
}

void analyseByte(char in){
  byte spill, passOn;
  // first look at messages to block
  if((in & 0x0F) == 0 && (in & 0xF0) != 0xF0){ // message for channel 1 ( 0 )
    spill = 2; // bytes following to block
    if((in & 0xF0) == 0xC0 || (in & 0xF0) == 0xD0 ) spill = 1; // Program change and After pressure only have one extra byte
    spillBytes(spill); // do not pass them on
    return; // we are done here so don't bother with any further tests
  }
  // now look at messages to convert and send on
  if((in & 0x0F) != 0 && (in & 0xF0) != 0xF0){ // message for any other channel but 1
       passOn = 2; // bytes following to block
    if((in & 0xF0) == 0xC0 || (in & 0xF0) == 0xD0 ) passOn = 1; // Program change and After pressure only have one extra byte
    sendBytes(in, passOn); //  pass them on
    return; // we are done here so don't bother with any further tests 
  }
  // we only get here if we have a system message so pass it on
  if( in == 0xF0 ){ // are we dealing with a system exclusive message
    passSysEx();
  }
  else {
  if( in == 0xF1 || in == 0xF3) sendBytes(in, 1);
    else if(in == 0xF2) sendBytes(in, 2);
      else Serial.write(in); // pass on the system message
  }
}

void spillBytes(char count){ // do not pass on the next "count" bytes
  while(count !=0 ){
     while(Serial.available() == 0) { } // wait until a byte arrives
     Serial.read(); // we don't want this byte so don't store it anywhere
     count--; // decrement the count
  } 
}

void sendBytes(char mess, char count){
  char onwards;
  if((mess & 0xF0) != 0xF0) { // if it is not a system message
      mess = mess & 0xF0; // convert to a channel 1 message
  }
  Serial.write(mess); // send first byte of the message
    while(count !=0 ){
       while(Serial.available() == 0) { } // wait until a byte arrives
       onwards = Serial.read(); // we want this byte
       Serial.write(onwards);  // so send it to the output
       count--; // decrement the count
  } 
}

void passSysEx(){ // pass on all the bytes of a System exclusive message
  char mess = 0xF0; // start of sysex message
  Serial.write(mess);
  while(mess != 0xF7) { // pass on all bytes until the end of the sysex message
     while(Serial.available() == 0) { } // wait until a byte arrives
     mess = Serial.read(); // we want this byte
     Serial.write(mess);  // so send it to the output
  }
}

Amazing, thank you!!! I hope I can buy you a beer or tea or something some day, cheers!

It works very well. It's solid, no glitches. I don't imagine Beat Step puts out sysex, and so far there hasn't been any problems.

I have only glanced over it, I need to dig in and do some homework to understand it (thank you for the //notes). But I'm curious, is it feasible to:

A: Select input and output channels via encoder or something along those lines? It's important for my setup that only one channel of MIDI is getting to the Monotribe. It can be any channel 11-16, but should only be one of those channels.
I was thinking that the first if statement in the analyseByte function could block all but one channel (a variable selected by the user "input channel"), instead of only blocking channel 1. Then the second if can convert whatever gets through the first if statement, and convert it to an "output channel" selected by the user. Does that seem like the way to go about doing this?

B: Hit a button to turn transport messages on or off, so start/stop/continue get blocked, and only clock goes through. Then I can start or stop the transport locally on the Monotribe (instead of on the Beat Step), and it'll still be in tempo/sync. This would make it easier to deal with a sequencer controlling 3 other sequencers. Set a flag in the loop and if it's true, filter out transport controls from the final if statement?

Thanks again for your help, efforts, and time!