News:

SMF - Just Installed!

Main Menu

External Midi Controller

Started by Peetem, March 02, 2023, 06:40:45 AM

Previous topic - Next topic

Peetem

While we're busy debugging my Pi Stomp build, I had another question around midi controllers.

I'm thinking I will build an external midi controller board with 8 buttons.  I'll use the Teensy 4.1 and its usb port to send messages to the Pi Stomp via the existing USB port.  However, I have also added the Midi add-on kit for my expression pedal.

Should I use the TRS MIDI in port on the add-on board or just use the existing USB port?

Wanted to completely understand before I start hooking stuff up - creating more for me to debug!

Many thanks again - having fun building and debugging the Pi Stomp.  Good learning experience.

Peetem

In in addition to if I can use the USB port for MIDI, does the unit want to "see" CC or MIDI notes?

Randall (Admin)

Glad you got it working and enjoyed the experience.  Sound like a fun project you have planned.

Good question.  From the perspective of the MOD software, MIDI coming in is all the same.  For plugin parameter controls, MOD uses CC messages.  MIDI notes would be used to control generator type plugins.

Here are my thoughts.  Pluses (+) and minus (-)

USB MIDI:
+ Use the Teensy USB port plus the USB lib without need to add extra parts (TRS and resistors) or a pcb.
+ You can likely power the Teensy from the pi-Stomp (I did that with my earliest pi-Stomp prototypes before switching to an ADC and debounce chips for analog inputs.  Worked great.)
+ USB can be bidirectional if you wanted pi-Stomp to send data to the controller for LED's, etc.

TRS MIDI:
+ Using TRS, leaves USB free for connecting other modern MIDI gear (keyboard, etc.)
- MIDI via Opto isolator might incur a bit more latency than the USB route.  I don't know this to be true, but when I used the 6N138 instead of the quicker 6N137 that I sell in the MIDI+Exp kits, I could notice the latency.
- You'll need separate power for the Teensy.
- Would need two cables (midi in and out) to have bidirectional communication.

Please report back with what you build.  I've wanted to do something similar, just never spent the time.  The enclosure for such a pedal is always a sticking point for me due to the odd aspect ratio I'd want (long, narrow, short).

Peetem

#3
Hey -

Build the Arduino controller and it works great.  Only problem is I am using momentary switches, so when I let up whatever event I wanted to happen (e.g., turn on a synth) go back to off.....so at this point its just a coding issue.  Here's what I've got:

/* Simple Teensy 4.1 DIY USB-MIDI controller.

*/

#include <Bounce.h>

//The number of push buttons
const int NUM_OF_BUTTONS = 8;

// the MIDI channel number to send messages
const int MIDI_CHAN = 1;

// Create Bounce objects for each button and switch.
//button debounce time
const int DEBOUNCE_TIME = 5;

Bounce buttons[NUM_OF_BUTTONS + 1] =
{
  Bounce (0, DEBOUNCE_TIME),
  Bounce (1, DEBOUNCE_TIME),
  Bounce (2, DEBOUNCE_TIME),
  Bounce (3, DEBOUNCE_TIME),
  Bounce (4, DEBOUNCE_TIME),
  Bounce (5, DEBOUNCE_TIME),
  Bounce (6, DEBOUNCE_TIME),
  Bounce (7, DEBOUNCE_TIME),
  Bounce (8, DEBOUNCE_TIME)
};

const int MIDI_MODE_NOTES = 0;
const int MIDI_MODE_CCS = 1;

//Variable that stores the current MIDI mode of the device (what type of messages the push buttons send).
int midiMode = MIDI_MODE_NOTES;

//Arrays the store the exact note and CC messages each push button will send.
const int MIDI_NOTE_NUMS[NUM_OF_BUTTONS] = {40, 41, 42, 43, 36, 37, 38, 39};
const int MIDI_NOTE_VELS[NUM_OF_BUTTONS] = {110, 110, 110, 110, 110, 110, 110, 110};
const int MIDI_CC_NUMS[NUM_OF_BUTTONS] = {24, 25, 26, 27, 20, 21, 22, 23};
const int MIDI_CC_VALS[NUM_OF_BUTTONS] = {127, 127, 127, 127, 127, 127, 127, 127};

void setup()
{
  // Configure the pins for input mode with pullup resistors.
  // The buttons/switch connect from each pin to ground.  When
  // the button is pressed/on, the pin reads low because the button
  // shorts it to ground.  When released/off, the pin reads high.

  for (int i = 0; i < NUM_OF_BUTTONS + 1; i++)
  {
    pinMode (i, INPUT_PULLUP);
  }

}

void loop()
{
  // Update all the buttons/switch.

  for (int i = 0; i < NUM_OF_BUTTONS + 1; i++)
  {
    buttons.update();
  }

  //==============================================================================
  // Check the status of each push button

  for (int i = 0; i < NUM_OF_BUTTONS; i++)
  {

    // Check each button for "falling" edge.

    if (buttons[i + 1].fallingEdge())
    {   
        usbMIDI.sendControlChange (MIDI_CC_NUMS, MIDI_CC_VALS, MIDI_CHAN);
      }

    // Check each button for "rising" edge

    else if (buttons[i + 1].risingEdge())
    {
        usbMIDI.sendControlChange (MIDI_CC_NUMS, 0, MIDI_CHAN);
    }

  } //for (int i = 0; i < NUM_OF_BUTTONS; i++)

  // MIDI Controllers should discard incoming MIDI messages.

  {
    // ignoring incoming messages, so don't do anything here.
  }
 
}

Randall (Admin)

Cool!  Are you sure it's not bouncing?  I've tried many varieties of software switch debounce.  None of them seem to cure it reliably or as well as hardware debounce.  Logiswitch IC's like pi-Stomp uses, is the easiest to implement, but I've had great results using RC debounce (Two 10k resistors and a 0.1uF capacitor as shown here: http://www.ganssle.com/debouncing-pt2.htm )

OR, do you really need to be sending a message for both rising and falling edges?  If you are using it for toggle type plugin parameters, then you should want it only sending a MIDI CC for one or the other.  If your switch is normally open which shorts a pull-up to ground, then I'd recommend sending only on the falling edge. 

If you want to mimic what pi-Stomp does to allow for short and long press events, here's that code:
https://github.com/TreeFallSound/pi-stomp/blob/master/pistomp/gpioswitch.py

Note, that it registers an event detect only for GPIO.FALLING.  Each time a falling event is detected, a timestamp is added to an event queue.  Then, on each polling interval, that queue is checked and compared to the current time to determine if it's been held longer than the long_press_threshold.

Peetem

Quote from: Randall (Admin) on March 03, 2023, 05:02:14 PM
Cool!  Are you sure it's not bouncing?  I've tried many varieties of software switch debounce.  None of them seem to cure it reliably or as well as hardware debounce.  Logiswitch IC's like pi-Stomp uses, is the easiest to implement, but I've had great results using RC debounce (Two 10k resistors and a 0.1uF capacitor as shown here: http://www.ganssle.com/debouncing-pt2.htm )

OR, do you really need to be sending a message for both rising and falling edges?  If you are using it for toggle type plugin parameters, then you should want it only sending a MIDI CC for one or the other.  If your switch is normally open which shorts a pull-up to ground, then I'd recommend sending only on the falling edge. 

If you want to mimic what pi-Stomp does to allow for short and long press events, here's that code:
https://github.com/TreeFallSound/pi-stomp/blob/master/pistomp/gpioswitch.py

Note, that it registers an event detect only for GPIO.FALLING.  Each time a falling event is detected, a timestamp is added to an event queue.  Then, on each polling interval, that queue is checked and compared to the current time to determine if it's been held longer than the long_press_threshold.

Thanks for the code and ideas around debounce.  I hadn’t considered my problem could be that, but you are probably on to something.  Toggle settings shouldn’t be doing that.  I’ll try coding the solution first as it requires so little effort (I’m lazy like that), so I’ll comment out the lines around rising edges and see what I get.

I play guitar in a cover band, and we do a lot of songs with guitar and synth - but we don’t have a synth player, I just run my guitar running through EHX Synth pedals depending on the song (Metro, Cars, etc.). 

My “real” pedal board now has the newly-built Arduino midi controller that I’ll be using to trigger a sequencer on my Pi Stomp “pedal board” .  I’ve coded several sequencers running a analog synth on the Stomp board.  Each midi switch on my “real” pedal board triggers a different sequencer on the Pi Stomp pedal board.  Of course for other Pi pedalboards, those switches will be used for other things….

Anyway, many thanks for the suggestions. I’ll let you know how they work out.

Peetem

#6
Quote from: Peetem on March 03, 2023, 10:45:16 PM
Quote from: Randall (Admin) on March 03, 2023, 05:02:14 PM
Cool!  Are you sure it's not bouncing?  I've tried many varieties of software switch debounce.  None of them seem to cure it reliably or as well as hardware debounce.  Logiswitch IC's like pi-Stomp uses, is the easiest to implement, but I've had great results using RC debounce (Two 10k resistors and a 0.1uF capacitor as shown here: http://www.ganssle.com/debouncing-pt2.htm )

OR, do you really need to be sending a message for both rising and falling edges?  If you are using it for toggle type plugin parameters, then you should want it only sending a MIDI CC for one or the other.  If your switch is normally open which shorts a pull-up to ground, then I'd recommend sending only on the falling edge. 

If you want to mimic what pi-Stomp does to allow for short and long press events, here's that code:
https://github.com/TreeFallSound/pi-stomp/blob/master/pistomp/gpioswitch.py

Note, that it registers an event detect only for GPIO.FALLING.  Each time a falling event is detected, a timestamp is added to an event queue.  Then, on each polling interval, that queue is checked and compared to the current time to determine if it's been held longer than the long_press_threshold.

Thanks for the code and ideas around debounce.  I hadn’t considered my problem could be that, but you are probably on to something.  Toggle settings shouldn’t be doing that.  I’ll try coding the solution first as it requires so little effort (I’m lazy like that), so I’ll comment out the lines around rising edges and see what I get.

I play guitar in a cover band, and we do a lot of songs with guitar and synth - but we don’t have a synth player, I just run my guitar running through EHX Synth pedals depending on the song (Metro, Cars, etc.). 

My “real” pedal board now has the newly-built Arduino midi controller that I’ll be using to trigger a sequencer on my Pi Stomp “pedal board” .  I’ve coded several sequencers running a analog synth on the Stomp board.  Each midi switch on my “real” pedal board triggers a different sequencer on the Pi Stomp pedal board.  Of course for other Pi pedalboards, those switches will be used for other things….

Anyway, many thanks for the suggestions. I’ll let you know how they work out.

So I CC'd the "rising edge" code and I can now latch "on" for the sequencers.  However, when I press again they don't go back to "off"....so more coding!

Edited - if I can't get the code to work, I'm going to try the debounce hardware option you described.  I did try 15ms rather than 5ms in addition to CC'ing the code you suggeted.  No dice!

Peetem

I simplified the code - using this, the button latches the "on" of each sequencer, but does not turn it off once pressed again:

/* Teensy 4.1 DIY USB-MIDI controller.
*/

#include <Bounce.h>

//The number of push buttons
const int NUM_OF_BUTTONS = 8;

// the MIDI channel number to send messages
const int MIDI_CHAN = 1;

// Create Bounce objects for each button
// Pin 0 is for future explansion if I need to send MIDI note values to something rather than CC's

//button debounce time
const int DEBOUNCE_TIME = 15;

Bounce buttons[NUM_OF_BUTTONS + 1] =
{
  Bounce (0, DEBOUNCE_TIME),
  Bounce (1, DEBOUNCE_TIME),
  Bounce (2, DEBOUNCE_TIME),
  Bounce (3, DEBOUNCE_TIME),
  Bounce (4, DEBOUNCE_TIME),
  Bounce (5, DEBOUNCE_TIME),
  Bounce (6, DEBOUNCE_TIME),
  Bounce (7, DEBOUNCE_TIME),
  Bounce (8, DEBOUNCE_TIME)
};

const int MIDI_MODE_NOTES = 0;
const int MIDI_MODE_CCS = 1;

//Variable that stores the current MIDI mode of the device (what type of messages the push buttons send).
int midiMode = MIDI_MODE_NOTES;

//Arrays the store the exact note and CC messages each push button will send.
const int MIDI_NOTE_NUMS[NUM_OF_BUTTONS] = {40, 41, 42, 43, 36, 37, 38, 39};
const int MIDI_NOTE_VELS[NUM_OF_BUTTONS] = {110, 110, 110, 110, 110, 110, 110, 110};
const int MIDI_CC_NUMS[NUM_OF_BUTTONS] = {24, 25, 26, 27, 20, 21, 22, 23};
const int MIDI_CC_VALS[NUM_OF_BUTTONS] = {127, 127, 127, 127, 127, 127, 127, 127};

//The setup function. Called once when the Teensy is turned on or restarted

void setup()
{
  // Configure the pins for input mode with pullup resistors.
  // The buttons/switch connect from each pin to ground.
  // Button shots to ground

  for (int i = 0; i < NUM_OF_BUTTONS + 1; i++)
  {
    pinMode (i, INPUT_PULLUP);
  }

}

//The loop function. Called over-and-over once the setup function has been called.

void loop()
{
  // Update all the buttons/switch.
  for (int i = 0; i < NUM_OF_BUTTONS + 1; i++)
  {
    buttons.update();
  }
  // Check the status of each push button

  for (int i = 0; i < NUM_OF_BUTTONS; i++)
  {

    // Check each button for "falling" edge.
    // Falling = high (not pressed - voltage from pullup resistor) to low (pressed - button connects pin to ground)

    if (buttons[i + 1].fallingEdge())
    {
      if (midiMode == MIDI_MODE_NOTES)
        usbMIDI.sendNoteOn (MIDI_NOTE_NUMS, MIDI_NOTE_VELS, MIDI_CHAN);
      else
        usbMIDI.sendControlChange (MIDI_CC_NUMS, MIDI_CC_VALS, MIDI_CHAN);
    }

  } //for (int i = 0; i < NUM_OF_BUTTONS; i++)

  //else if (buttons[0].risingEdge())
  {
    midiMode = MIDI_MODE_CCS;
  }
  // MIDI Controllers should discard incoming MIDI messages.
  while (usbMIDI.read())
  {
    // ignoring incoming messages, so don't do anything here.
  }
 
}

Randall (Admin)

Yes, if you simply got rid of the rising edge MIDI message, then you'll always just be sending the falling edge 127 "on" message but never an "off" message of 0.  You need a corresponding send of 0, every other time.

This is because the MIDI spec never included the concept of toggling.  Ideally, it would be nice to just send a message to say, "hey, toggle yourself!".  That requires that the thing being controlled, not the controller keeps track of the state.  But they took the other approach that the controller keeps track of the state.

So, you (being the controller here), need to keep track of the toggle state, then send the corresponding 127 or 0 based on that state.  And you need to keep track of a state per button.

The easiest way, IMO, would probably be to define a bool toggle state array, similar to the buttons array that you already have.

bool toggle_state[NUM_OF_BUTTONS] = { false, false, false, false, false, false, false, false }

BTW, did you notice you defined 9 buttons, even though NUM_OF_BUTTONS is 8?

Everytime you send a message, the state for that button should toggle:
  toggle_state[i + 1] = !toggle_state[i + 1];

And the message you send should be based on that toggle_state.  If it's true, send 127.  If it's false, send 0 (instead of that MIDI_CC_VALS that you have been sending)


After you code that, and get both 127 and 0 sending, you may still discover you have a bounce problem: You press the switch and you get a 127 AND a 0.  Or maybe even 127, then 0 then 127 per push.  But best to get your message code working then see.



Peetem

And this was the part I was dreading.....coding.

Not something I'm particularly good with.  :(

Randall (Admin)

I did start to write code for you but stopped not wanting to steal your thunder.  Plus you'll understand it more for future improvements if you do it yourself.

It's not much code, 5 lines maybe, but if you need help, lemme know.

Peetem

#11
Quote from: Randall (Admin) on March 04, 2023, 07:25:52 PM
I did start to write code for you but stopped not wanting to steal your thunder.  Plus you'll understand it more for future improvements if you do it yourself.

It's not much code, 5 lines maybe, but if you need help, lemme know.

No, I really appreciate teaching me to fish.  I'm learning a great deal about the Arduino while doing everything else as well.

Will post the latest code soon....

Peetem

Latest....doesn't work, but at least you can see the direction....

/* Teensy 4.1 DIY USB-MIDI controller.
*/

#include <Bounce.h>

//The number of push buttons
const int NUM_OF_BUTTONS = 8;

// Create Bounce objects for each button

//button debounce time
const int DEBOUNCE_TIME = 25;

Bounce buttons[NUM_OF_BUTTONS] =
{
  Bounce (1, DEBOUNCE_TIME),
  Bounce (2, DEBOUNCE_TIME),
  Bounce (3, DEBOUNCE_TIME),
  Bounce (4, DEBOUNCE_TIME),
  Bounce (5, DEBOUNCE_TIME),
  Bounce (6, DEBOUNCE_TIME),
  Bounce (7, DEBOUNCE_TIME),
  Bounce (8, DEBOUNCE_TIME)
};

//Array to the store the CC message value of each button

const int MIDI_CC_NUMS[NUM_OF_BUTTONS] = {24, 25, 26, 27, 20, 21, 22, 23};

bool running = false;

bool toggle_state[NUM_OF_BUTTONS] = {false, false, false, false, false, false, false, false};

//The setup function. Called once when the Teensy is turned on or restarted

void setup()

{
  // Configure the pins for input mode with pullup resistors.
  // The buttons/switch connect from each pin to ground. Button shorts to ground.
 
  for (int i = 0; i < NUM_OF_BUTTONS; i++)
  {
    pinMode (i, INPUT_PULLUP);
  }

}

//The loop function. Called over-and-over once the setup function has been called.

void loop()

{
  // Update all the buttons/switch.
  for (int i = 0; i < NUM_OF_BUTTONS+1; i++)
  {
    buttons.update();
  }
  // Check the status of each push button
  {
    for (int i = 0; i < NUM_OF_BUTTONS; i++)
  {
    if (buttons.fallingEdge())
     { 
       toggle_state =  !toggle_state;
     }
    {
      if (toggle_state)
        usbMIDI.sendControlChange (MIDI_CC_NUMS, 0, 1);
      else
        usbMIDI.sendControlChange (MIDI_CC_NUMS, 127, 1);
    }   
   }
   }

  // MIDI Controllers should discard incoming MIDI messages.
  while (usbMIDI.read())
  {
    // ignoring incoming messages, so don't do anything here.
  }
 
}

Randall (Admin)

Funny that you used the teach to fish analogy.  I was thinking that myself.

Yup.  You totally have the right idea there for one button.  It might almost work but you now have mixed types:  'buttons' and 'toggle_state' are still initialized as arrays, but you are referencing them in your loop as scalers.  Not quite sure what that will do, but I'm pretty sure it won't work, might even error out.

You need a toggle_state per button (ie. an array) otherwise you'll just have one state for all buttons which defeats the case for having multiple buttons.

Add back the indexing, and it might work:


  if (buttons[i].fallingEdge())
  {
     toggle_state[i] =  !toggle_state[i];
  }
  if (toggle_state[i])
    ...

Peetem

#14
Quote from: Randall (Admin) on March 05, 2023, 01:09:22 AM
Funny that you used the teach to fish analogy.  I was thinking that myself.

Yup.  You totally have the right idea there for one button.  It might almost work but you now have mixed types:  'buttons' and 'toggle_state' are still initialized as arrays, but you are referencing them in your loop as scalers.  Not quite sure what that will do, but I'm pretty sure it won't work, might even error out.

You need a toggle_state per button (ie. an array) otherwise you'll just have one state for all buttons which defeats the case for having multiple buttons.

Add back the indexing, and it might work:


  if (buttons[i].fallingEdge())
  {
     toggle_state[i] =  !toggle_state[i];
  }
  if (toggle_state[i])
    ...


So, this is weird.  My code has toggle_state as an array toggle_state, but when i post the code "[ i ] here it doesn't transfer that over....just "toggle_state"....in fact, even typing it here it just shows up "toggle_state".

I wonder if I'm not sending the right MIDI info ? (assume toggle_state is an array):

if (toggle_state)
        usbMIDI.sendControlChange (MIDI_CC_NUMS, 0, 1);
      else
        usbMIDI.sendControlChange (MIDI_CC_NUMS, 127, 1);