Setting momentary button array with latching functionality. Teensy 4.0 content

chris@home

New member
Hello all.

My first post here because almost any issue I ever have can usually be answered with a good search of this forum. This time I can't find exactly what I want, likely due to the search terms I'm using and my lack of knowledge in describing exactly what I want in a technical manner for the search engine.

I'm working on a MIDI controller that I would like to use with an organ I'm building using a Teensy 4.0. The ultimate goal is to have 12 latching buttons, 9 faders (organ drawbars) and 9 or so analog rotary knobs. I'm familiarizing myself with each chunk as I go just so I don't overwhelm myself with too much at once. So I'm learning to build each part in phases and learn from my mistakes as I go. I've figured out that baby steps helps me understand what I'm doing. I know I'll likely need to incorporate a MUX at some point, but so far I feel like I've learned a bunch on this journey.

In one of my searches, I did see a thread with code posted that was pretty close to what I needed... but didn't have the latching functionality. Here's a link to the original post MIDI footswitch for Guitar

I changed a few things around to get the latching functionality, but now I see that I've overlooked something in my inexperience. While it latches based on the last value of 0 or 127... the lookback is agnostic. If I have two buttons and the last value of one is 127 and the last value of the other is 0, when I press again the next value entered will be 0 regardless of the value of that specific button. So it's applying that last read value for any press even if it's not what I'm wanting.

Example:

DirectionMessageMIDI ChValueExpected ValueHex
outController Sustenuto Pedal (on/off): 0100B0 42 00
outController Soft Pedal (on/off): 1271127127B0 43 7F
outController Sustenuto Pedal (on/off): 010127B0 42 00

In this example you can see that the message outController Sustenuto Pedal (on/off) sent a value of 0 twice, but the expectation would have been a value of 127. However it looks like it was polling that last value of 127 set by the Controller Soft Pedal (on/off) and therefore sent a value of 0.

Honestly, I don't feel too bad about this because I'm amazed I made it this far.

I'm also seeing a similar issue with the LED's. My initial goal was to have a latching LED option assigned to each button so I would have a visual indicator of the state of each associated button. But it's merely turning on the LED associated with the button, then turning off when I press the next button. This one isn't an absolute, but I figured since I was being all fancy I might as well see if I could get that to do something neat as well.

Maybe I've just boy-coded too close to the sun and I need to lower my expectation and just use toggle switches instead of a momentary button?

Maybe I need to use something like control surface? I've only lightly dabbled with it so I'm not super comfortable with it. Also I feel like what I'm learning here can be ported to non-MIDI workflows as I get better so that's a consideration I've had too.

I would love some guidance and input on where I'm going wrong and if there's a better way to do this... let me know. I can take it.

Also, if I'm missing some detail please let me know and I'll add it.

Thanks!

Here's my code.

Code:
// select MIDI from the "Tools > USB Type" menu
// Create an option to use momentary switch with latching functionality.
// Optimally, this will allow multiple MIDI Control Change messages to be read as 0 or 127
// with button presses.

#include <Bounce.h>

// Set up the pins for buttons and LED's
const int channel = 1;
const int buttonPins[] = { 2, 3, 4, 5, 20, 21, 22, 23 };
const int ledPins[] = { 6, 7, 8, 9, 16, 17, 18, 19 };

// Configured buttons with 15 ms debounce time because my test buttons are so jittery. Final version
//should be around 5-10ms
Bounce buttons[8] = {
  Bounce(buttonPins[0], 15),
  Bounce(buttonPins[1], 15),
  Bounce(buttonPins[2], 15),
  Bounce(buttonPins[3], 15),
  Bounce(buttonPins[4], 15),
  Bounce(buttonPins[5], 15),
  Bounce(buttonPins[6], 15),
  Bounce(buttonPins[7], 15)
};

int lastButtonPressed = -1;  // Variable to track the last button pressed

void setup() {
  // Set button pins as input with internal pullup
  for (int i = 0; i < 8; i++) {
    pinMode(buttonPins[i], INPUT_PULLUP);
    pinMode(ledPins[i], OUTPUT);
    digitalWrite(ledPins[i], LOW);  // Turn off all LEDs initially
  }
}

// /Stores the value of Control Change state sent as high or low in the loop
uint8_t cc_value = 0;

// Stores the value of the last LED state as off or on in the loop
uint8_t led_value = LOW;

void loop() {
  // Update the state of the buttons
  for (int i = 0; i < 8; i++) {
    buttons[i].update();
  }

  // Check if any button was pressed and updates based on the last state
  for (int i = 0; i < 8; i++) {
    if (buttons[i].fallingEdge()) {
          if( cc_value == 127) cc_value = 0;
    else cc_value = 127;
      usbMIDI.sendControlChange(60 + i, cc_value, channel);  // Send Control Change message (60 + i is the MIDI CC)
      lastButtonPressed = i;                    // Update the last button pressed
    }
  }

  // Turn on only the LED of the last button pressed and turn off the others
  for (int i = 0; i < 8; i++) {
    if (i == lastButtonPressed) {
          if( led_value == LOW) led_value = HIGH; // This should be looking to the current state and
    else led_value = LOW;                         // responding appropriately like a latching switch, but doesn't.
      digitalWrite(ledPins[i], led_value);
    }
  }

  // Read and discard incoming MIDI messages
  while (usbMIDI.read()) {
    // read and discard incoming messages
  }
}
 
you need to hold all the cc_values at the moment you are just using one variable, something like:

Code:
// Store the value of Control Change for each button (0 or 127)
uint8_t cc_value[8] = {0, 0, 0, 0, 0, 0, 0, 0};

.......
// Toggle CC value between 0 and 127
if (cc_value[i] == 127) {
  cc_value[i] = 0;
  digitalWrite(ledPins[i], 0);
} else {
  cc_value[i] = 127;
  digitalWrite(ledPins[i], 1);
}
 
First off. Thanks so much for that info. I don't know why I didn't think of the need for storing the values. Seems sort of obvious in hindsight. Also, that suggestion is so much cleaner than what I was using before.

After the update I'm having success with the LED's, however now I'm only sending a single value of 116 on all CC messages. They're sending the correct message, but not updating the value (edited for clarity). I keep staring at the sketch thinking that will help me understand it, but enlightment keeps eluding me. I've tweaked a few things, but mostly get error messages with any change I make.

In the Output monitor here is what I'm seeing when compiling. I can successfully compile with no error messages, but it looks like we're not a happy family right now.

/Arduino/Teensy_latching_MIDI_with_array/Teensy_latching_MIDI_with_array.ino: In function 'void loop()':
/Arduino/Teensy_latching_MIDI_with_array/Teensy_latching_MIDI_with_array.ino:59:41: warning: invalid conversion from 'uint8_t*' {aka 'unsigned char*'} to 'uint8_t' {aka 'unsigned char'} [-fpermissive]
59 | usbMIDI.sendControlChange(60 + i, cc_value, channel); // Send Control Change message (60 + i is the MIDI CC)
| ^~~~~~~~
| |
| uint8_t* {aka unsigned char*}
In file included from /Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/WProgram.h:58,
from /Library/Caches/arduino/sketches/994EAD4FC8A9B2F6E97F909E43FCBD95/pch/Arduino.h:6:
/Library/Arduino15/packages/teensy/hardware/avr/1.59.0/cores/teensy4/usb_midi.h:152:57: note: initializing argument 2 of 'void usb_midi_class::sendControlChange(uint8_t, uint8_t, uint8_t, uint8_t)'
152 | void sendControlChange(uint8_t control, uint8_t value, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
| ~~~~~~~~^~~~~
Memory Usage on Teensy 4.0:
FLASH: code:10424, data:3048, headers:9052 free for files:2009092
RAM1: variables:4576, code:8640, padding:24128 free for local variables:486944
RAM2: variables:6272 free for malloc/new:518016

Here's the updated loop:

Code:
// Store the value of Control Change for each button (0 or 127)
uint8_t cc_value[8] = {0, 0, 0, 0, 0, 0, 0, 0};

void loop() {
  // Update the state of the buttons
  for (int i = 0; i < 8; i++) {
    buttons[i].update();
  }
  // Check if any button was pressed and updates based on the last state
  for (int i = 0; i < 8; i++) {
    if (buttons[i].fallingEdge()) {
// Toggle CC value between 0 and 127
if (cc_value[i] == 127) {
  cc_value[i] = 0;
  digitalWrite(ledPins[i], 0);
} else {
  cc_value[i] = 127;
  digitalWrite(ledPins[i], 1);
}
      usbMIDI.sendControlChange(60 + i, cc_value, channel);  // Send Control Change message (60 + i is the MIDI CC)
      lastButtonPressed = i;                    // Update the last button pressed
    }
  }
  // Read and discard incoming MIDI messages
  while (usbMIDI.read()) {
    // read and discard incoming messages
  }
}
 
you need to reference to the element in the array:

Code:
from -
usbMIDI.sendControlChange(60 + i, cc_value, channel);
to -
usbMIDI.sendControlChange(60 + i, cc_value[i], channel);

Cheers, Paul
 
Oh my gravy. How did I miss that one? I think I was so engrossed thinking I made a mistake in the updates that I neglected that part.

Thank you again.

For posterity, here's the updated sketch and output table. In addition to the working bits we've already discussed, I updated it to allow users to call out specific CC messages for each button press. For my use case that is much better than the linear setup that I started with, but I didn't want to get too deep into the weeds until I worked through the on/off thing. I feel so fancy now.

DirectionMessageMIDI ChValueExpected ValueHex
OutController 1141127127B0 72 00
OutController 1151127127B0 73 00
OutController 114100B0 72 00
OutController 1171127127B0 75 7F
OutController 117100B0 75 7F
OutController 1141127127B0 72 00
OutController 115100B0 73 00
OutController 114100B0 72 00


Code:
// select MIDI from the "Tools > USB Type" menu
// Create an option to use momentary switch with latching functionality.

#include <Bounce.h>

// Set up the pins for buttons,  LED's, and the Control Change messages that correspond with each
const int channel = 1;
const int buttonPins[] = { 2, 3, 4, 5, 20, 21, 22, 23 };
const int ledPins[] = { 6, 7, 8, 9, 16, 17, 18, 19 };
//Change values as needed for different CC messages
const int cc_message[] = {110, 111, 112, 113, 114, 115, 116, 117};

// Configured buttons with 25 ms debounce time because my test buttons are so jittery. Final version
//should be around 5-10ms
Bounce buttons[8] = {
  Bounce(buttonPins[0], 25),
  Bounce(buttonPins[1], 25),
  Bounce(buttonPins[2], 25),
  Bounce(buttonPins[3], 25),
  Bounce(buttonPins[4], 25),
  Bounce(buttonPins[5], 25),
  Bounce(buttonPins[6], 25),
  Bounce(buttonPins[7], 25)
};

int lastButtonPressed = -1;  // Variable to track the last button pressed

void setup() {
  // Set button pins as input with internal pullup
  for (int i = 0; i < 8; i++) {
    pinMode(buttonPins[i], INPUT_PULLUP);
    pinMode(ledPins[i], OUTPUT);
    digitalWrite(ledPins[i], LOW);  // Turn off all LEDs initially
  }
}

// Store the value of Control Change for each button (0 or 127)
uint8_t cc_value[8] = {0, 0, 0, 0, 0, 0, 0, 0};

void loop() {
  // Update the state of the buttons
  for (int i = 0; i < 8; i++) {
    buttons[i].update();
  }

  // Check if any button was pressed and updates based on the last state
  for (int i = 0; i < 8; i++) {
    if (buttons[i].fallingEdge()) {
// Toggle CC value between 0 and 127
if (cc_value[i] == 127) {
  cc_value[i] = 0;
  digitalWrite(ledPins[i], 0);
} else {
  cc_value[i] = 127;
  digitalWrite(ledPins[i], 1);
}
      usbMIDI.sendControlChange(cc_message[i], cc_value[i], channel);  // Send MIDI message.
      lastButtonPressed = i;                    // Update the last button pressed
    }
  }

  // Read and discard incoming MIDI messages
  while (usbMIDI.read()) {
    // read and discard incoming messages
  }
}
 
Back
Top