Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 10 of 10

Thread: Sending MIDI CC to multiple Channels at once

  1. #1
    Junior Member
    Join Date
    Feb 2019
    Posts
    6

    Sending MIDI CC to multiple Channels at once

    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
           http://www.pjrc.com/teensy/td_midi.html
        
           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.
          // http://forum.pjrc.com/threads/24179-...ses-midi-crash
          while (usbMIDI.read()) {
            // ignore incoming messages
          }
        }

  2. #2
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,360
    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 by oddson; 03-29-2020 at 04:09 PM.

  3. #3
    Junior Member
    Join Date
    Feb 2019
    Posts
    6
    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

  4. #4
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,360
    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.

  5. #5
    Junior Member
    Join Date
    Feb 2019
    Posts
    6
    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;
            }

  6. #6
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,928
    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)

  7. #7
    Junior Member
    Join Date
    Feb 2019
    Posts
    6
    haha #facepalm

    thanks

  8. #8
    Junior Member
    Join Date
    Feb 2019
    Posts
    6
    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;
            }

  9. #9
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,360
    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.

  10. #10
    Junior Member
    Join Date
    Feb 2019
    Posts
    6
    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.


    Click image for larger version. 

Name:	15ED8171-D087-4D6E-94FE-73EF30C39FBE.jpg 
Views:	10 
Size:	79.4 KB 
ID:	19549
    Click image for larger version. 

Name:	47BD7318-2358-4731-BB5F-C8C57CF6BE6F.jpg 
Views:	11 
Size:	35.9 KB 
ID:	19550

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •