Sending MIDI CC to multiple Channels at once

Status
Not open for further replies.
Hi, so i would like to send a CC message to multiple MIDI Channels at once (volume control).
The purpose is to have one knob that set the volume level of channel 1 to 8. I'm using Teensy 3.6 as a usb midi controller directly into my instrument which is an OP-Z.


The solution i found kind of work but i'm pretty sure is not ideal and seems to introduce drift or lag (sorry for the lack of proper terms): the volume doesn't seems getting the same value from one channel to another (see below for more explanation).

My question is: is there a better way to do it than what i have done.

I use and tweak the following exemple provided in the Teensy Exemple library "USB MIDI AnalogControlChange Example"

The exemple was using this line of code to send CC (controlChange) to a specific channel:

MIDI.sendControlChange(controlNumber, controlValue, channel);

As i wanted to have multiple target channels for the third parameter i tried things like this:

MIDI.sendControlChange(controlNumber, controlValue, 1-2-3);

or

MIDI.sendControlChange(controlNumber, controlValue, (1,2,3));

or

MIDI.sendControlChange(controlNumber, controlValue, 1+2+3);

I guess it's naive but it didn't worked. I didn't find in the documentation if this parameter could accept more than one number.


What worked is to multiply analog read of the same knob and multiply the sent message. It's not elegant (i'm new to programing of course) but also i noticed i strange latency or drift if you will: the volume of the channel did went from 0 to full volume but the one on channel 2 didn't go to zero, so the purpose failed. The idea here is to have all the midi channels (1 to 8) but the 11 to be affected by the volume knob.

Below there's my code, hope you can help, in advance, thanks.

Code:
 /* USB MIDI AnalogControlChange Example
    
       You must select MIDI from the "Tools > USB Type" menu
       [url]http://www.pjrc.com/teensy/td_midi.html[/url]
    
       This example code is in the public domain.
    */
    
    #include <Bounce.h>
    
    // the MIDI channel number to send messages
    const int channel = 11;
  
    
    // the MIDI continuous controller for each analog input
    const int controllerA9 = 1; // param 1
    const int controllerA8 = 2; // param 2
    const int controllerA7 = 3; // cutoff
    const int controllerA6 = 4; // reso
    const int controllerA5 = 16; // volume (for channel 11)
    const int controllerA4 = 16; // volume (for other channels exept 11
    
    void setup() {
    
    }
    
    // store previously sent values, to detect changes
    int previousA9 = -1;
    int previousA8 = -1;
    int previousA7 = -1;
    int previousA6 = -1;
    int previousA5 = -1;
    int previousA4 = -1;
    
    elapsedMillis msec = 0;
    
    void loop() {
      // only check the analog inputs 50 times per second,
      // to prevent a flood of MIDI messages
      if (msec >= 20) {
        msec = 0;
        int n0 = analogRead(A9) / 8;
        int n1 = analogRead(A8) / 8;
        int n2 = analogRead(A7) / 8;
        int n3 = analogRead(A6) / 8;
        int n4 = analogRead(A5) / 8;
        int n5 = analogRead(A4) / 8;
        int n6 = analogRead(A4) / 8; //i duplicated this to be able to have a different channel target
        // only transmit MIDI messages if analog input changed
        if (n0 != previousA9) {
          usbMIDI.sendControlChange(controllerA9, n0, channel);
          previousA9 = n0;
        }
        if (n1 != previousA8) {
          usbMIDI.sendControlChange(controllerA8, n1, channel);
          previousA8 = n1;
        }
        if (n2 != previousA7) {
          usbMIDI.sendControlChange(controllerA7, n2, channel);
          previousA7 = n2;
        }
        if (n3 != previousA6) {
          usbMIDI.sendControlChange(controllerA6, n3, channel);
          previousA6 = n3;
        }
        if (n4 != previousA5) {
          usbMIDI.sendControlChange(controllerA5, n4, channel);
          previousA5 = n4;
        }
        
        if (n5 != previousA4) {
          usbMIDI.sendControlChange(controllerA4, n5, 1);
          previousA4 = n5;
        } // 1 here is the channel who's different from the int channel = 11
    
         if (n6 != previousA4) {
          usbMIDI.sendControlChange(controllerA4, n6, 2);
          previousA4 = n6;
        } // 2 here is the channel who's different from the int channel = 11
 
      }
    
      // MIDI Controllers should discard incoming MIDI messages.
      // [url]http://forum.pjrc.com/threads/24179-Teensy-3-Ableton-Analog-CC-causes-midi-crash[/url]
      while (usbMIDI.read()) {
        // ignore incoming messages
      }
    }
 
You are a bit light with symptoms of the problem. (From what I see the values should almost always be the same.)

From the code the two values are not necessarily the same because you are reading the pin twice instead of just using the result twice.

If you need this for lots of controls on lots of channels you might want to look at arrays and indexed loops to eliminate hundreds of lines of repetitive code but expect a bit of a learning curve.

Otherwise, just repeat the .send with different channels one after the other but with the same data value variable....
Code:
        if (n5 != previousA4) {
          usbMIDI.sendControlChange(controllerA4, n5, 1);
          usbMIDI.sendControlChange(controllerA4, n5, 2);
          previousA4 = n5;
        }

You might want to explain what you are trying to achieve specially.
 
Last edited:
Thank you for your time and answer.

It make a lot of sense of course, i get the fondamental of what you describe but yes, i could see the learning curve here.
I'll try the "repetitive" solution for now but i'll dig also at the loop function as someone explained to me on Stackexange:

Code:
for (int c = 0; c < 16; c++)
    if (c != 10)
        usbMIDI.sendControlChange(controllerAx, nx, c);


What i'm trying to achieve is:

i'm using the 3.6 teensy to control a specific CC message of a specific channel on an hardware synth (the OP-Z from Teenage Engineering).
The idea is to use the OP-Z to play and tweak synth stuff but still having a dedicated hardware control to a specific channel. Fyi this specific channel is taking care of a specific track called "Tape Track" that let you apply live effects on all or specific tracks you chosen.

I've identified 5 or 6 CC messages of the Tape Track (channel 1) that i would like to have always my hand on, like "volume", "cutoff", "resonance", you name it.
So far the exemple "USB MIDI AnalogControlChange Example" worked well for me, i just added few more knobs.

Then i figured that it would be helpfull for live "jam" to have a dedicated knob that let me control all the others tracks/channel volume except the tape track/channel 11, like have a "solo" with this track for a while and then bring back all the other ones with this knob.

And here i am !

thanks again
 
Code:
        if (n5 != previousA4) {
          usbMIDI.sendControlChange(controllerA4, n5, 0);
          usbMIDI.sendControlChange(controllerA4, n5, 1);
          usbMIDI.sendControlChange(controllerA4, n5, 2);
          usbMIDI.sendControlChange(controllerA4, n5, 3);
          usbMIDI.sendControlChange(controllerA4, n5, 4);
          usbMIDI.sendControlChange(controllerA4, n5, 5);
          usbMIDI.sendControlChange(controllerA4, n5, 6);
          usbMIDI.sendControlChange(controllerA4, n5, 7);
          usbMIDI.sendControlChange(controllerA4, n5, 8);
          usbMIDI.sendControlChange(controllerA4, n5, 9);
          usbMIDI.sendControlChange(controllerA4, n5, 11);
          usbMIDI.sendControlChange(controllerA4, n5, 12);
          usbMIDI.sendControlChange(controllerA4, n5, 13);
          usbMIDI.sendControlChange(controllerA4, n5, 14);
          usbMIDI.sendControlChange(controllerA4, n5, 15);
          previousA4 = n5;
        }

equals
Code:
        if (n5 != previousA4) {
          for (int c = 0; c < 16; c++){
            if (c != 10){
              usbMIDI.sendControlChange(controllerA4, n5, c);
            }
          }
          previousA4 = n5;
        }

It says for (each) integer c (a new variable) starting at zero and stopping with 15 (for 16 steps), incremented by one; send the MIDI message but with the new value for c each time -- except on index = 10 (channel 11) where we skip to c=11 without a message.

The code in your post just divides by 8 to get the 7 bit MIDI value from the 10 bit pin reading but if the raw value is adjacent to a rounding boundary then even minimal noise can cause the code to pump out a stream of CC of the same type with the controller value alternating between two adjacent 7-bit values. The code relies on only running every 20 milliseconds to avoid spewing too much junk.

There are solutions that don't rely on delay and that stabilize the MIDI messages being sent in more effective ways if it turns out this doesn't perform as you require.
 
The code worked, thanks so far and really thanks for taking time explaining more deeply the loop function.
I'll try the loop function but i need to adapt it since i only want to have channel 1 to 8 (not 0) and to skip the channel 11 (not 10). In this OP-Z really, channel 1 is the first track while channel 0 is like another thing, i guess the main "menu" if you will (like the UI, more detail on the CC messages here: https://teenage.engineering/guides/op-z/midi )


Code:
 if (n5 != previousA4) {
          for (int c = 1; c < 8; c++){
            if (c != 11){
              usbMIDI.sendControlChange(controllerA4, n5, c);
            }
          }
          previousA4 = n5;
        }
 
c will never be 11 in this loop... :)
But the compiler is smart and does know this. Internally, it will ignore the
if (c != 11)
 
Actually it did introduce an issue because it didn't worked with the unnecessary (c !=11)
It works like that

Code:
  if (n5 != previousA4) {
          for (int c = 1; c < 8; c++){
              usbMIDI.sendControlChange(controllerA4, n5, c);
            }
          previousA4 = n5;
        }
 
For some reason I thought MIDI libraries are zero indexed for channel even though I must have known better as some point in the past. :confused:
 
Well I guess you’re still right about that it’s just that this specific hardware use some internal interpretations to “simplify” the user experience ?
But for me is indeed kind of confusing:

When you read midi from track 1 from the op-z you can clearly see that it’s coming from the midi channel 0. (See image below, one is from the midi configuration app of the OP-Z and the other one is from midi wrench for ipad that scan midi messages).
But with my prototype if I want to sent CC messages track 1 (or track 11 in my case) I must send it to channel 11. It’s why I guess the OP-Z is self interpreting and translate it internally to the right channel.

It doesn’t bother me right now but I could see some headache if at some time I want to also read midi from the OP-Z.


15ED8171-D087-4D6E-94FE-73EF30C39FBE.jpg
47BD7318-2358-4731-BB5F-C8C57CF6BE6F.jpg
 
Status
Not open for further replies.
Back
Top