Creating a software editor

Status
Not open for further replies.

Aussie_CrocHunter

Well-known member
Hi, I'm making a midi controller. What software would I use to create an app which would enable a user to easily change a midi CC assignment or midi channel being sent?
Like a configuration app. Is that possible with teensy? I'm using a teensy 2.0
 
There is not an easy way to do this. The basic idea is to use the USB serial port and send messages either as single letters or more structured commands from your program. The basic proof of concept is to do this from the Arduino serial terminal to your device, and then build software that can take over the serial messaging with a prettier interface once that Arduino end is working well. This splits the task into two separate parts. For the PC end anything that can produce serial messages can work, I have used processing https://processing.org/reference/libraries/serial/index.html but would suggest using whatever programing environment you are already comfortable with and finding serial libraries for it rather than learning a new environment just for the job.
 
The best way is to write a Web Midi API based web page that connects to your Teensy via Midi and sends config data as Sysex. A lot of Midi devices are configured like this now. The biggest advantages are that (1) there is no software to install by the end-user as it is browser based and hosted on your web site, and (2) will work across Windows, Mac, Linux etc.
 
Or add an ESP32 or ESP8266 to the project, that offers a webpage in WLAN or even on it's own AP to do all the config needed.
Is a bit like shooting cannons at bugs, but we need to add a co-processor to handle different USB types already, so it is just another chip.
 
btw: it would be super cool to have a USB type "network adapter" that could handle a local webserver... unfortunately I am not ale to code somethig like that.
 
Have a look at: https://ctrlr.org/
It has the ability to do what you want if you can program it right, the basic objects won’t be able to do it without some programming though. It allows you to attach lua scripts to different aspects of an object which can get pretty extensive. There aren’t many tutorials or documentation for it, but they have a lot of free examples that other people have made that you can look through and get ideas for. It’s exclusively MIDI so nothing special is needed to interface with it, you just select the MIDI port for your device.
 
The best way is to write a Web Midi API based web page that connects to your Teensy via Midi and sends config data as Sysex. A lot of Midi devices are configured like this now. The biggest advantages are that (1) there is no software to install by the end-user as it is browser based and hosted on your web site, and (2) will work across Windows, Mac, Linux etc.
This would be ideal. Reading a few bytes of sysex and storing it in eeprom is not difficult.

I did it years ago...
https://forum.pjrc.com/threads/24537-Footsy-Teensy-2-based-MIDI-foot-pedal-controller

You read from the eeprom during setup and the sysex code loads up the eeprom when it gets a valid message.

Warning... that code has getType() == 7 which needs updating before it will work with current Teensyduino.
 
I was adding comments to your code and thought I'd try to graft on SysEx preset config.

I don't recommend you even trying to compile this yet but I think it's only a few typos and brain-farts from working.

You would still need to make the editor page but you can also set with midi utility if you can figure out the hex to send.

The message structure is

0xF7 // start sysex
0xD7 // manufacturer's ID (public use)
0x65 // these four are key to avoid reading other sysex
0x67
0x72
0x48 // ACH0
bank0 index 0 CC //15 are bank/switch CC numbers
bank0 index 1 CC
...
bank 3 index 4 CC
Tap CC
0xF0 // end sysex

Code:
//************LIBRARIES USED**************
// include the Bounce library for 'de-bouncing' switches -- removing electrical chatter as contacts settle
#include <Bounce.h> 
// usbMIDI.h library is added automatically when code is compiled as a MIDI device

// ********************CONSTANT VALUES**********************************
// **<user configurable content>  // configurable parameters as constants

//********** MIDI DEFINITIONS
const int channel = 1; // MIDI channel
const int MODE_COUNT = 3; // number of rows of banks
//CC configuration matrix!! - not a const as it's configured by SysEx
int MIDI_CC_NUMS[MODE_COUNT][D_PINS] = { //rows are banks up to MODE_COUNT
    {60,61,62,63,64},
    {65,66,67,68,69},
    {70,71,72,73,74}
}; // these are the D1 values sent for each mode|pin pair
int TAP_CC = 15; // D1 value for TAP down
const int ON_Value = 127; // note-one velocity sent from buttons (should be 65 to  127)
const int OFF_Value = 0; // note-one velocity sent from buttons (should be 65 to  127)


//******************************** PHYSICAL DEFINITIONS ***************
const int BOUNCE_TIME = 30; // 5 ms is usually sufficient
const int modeThreshold = 800; // how long to hold before mode changes
const int flashOnTime = 150; // how long flashed LED is on
const int flashOffTime = 250; // how long flashed LED is off
const bool LED_ON = LOW; // LOW for active LOW wiring
const bool LED_OFF = HIGH; // HIGH for active LOW wiring
const int onClocks = 4; // number of clock messages with LED on for tempo pulse
const int waitflash = 2; // number of dark flash cycles after bank change before tempo pulse resumes

//** </user configurable content> // don't change below here without knowing how it works!

//********** PIN DEFINITIONS (edit only if rewired or home built with different connections)
const int D_PINS = 5; // number of Digital PINS in main group (excl. tap/bank)
const int DIGITAL_PINS[D_PINS] = {0,1,2,3,4}; // pins to switches 
const int RED_LED_PINS[D_PINS] = {6,19,8,10,17};
const int GREEN_LED_PINS[D_PINS] = {7,20,9,16,18};
const int MODE_TAP_PIN = 5;  // momentary switch to set tempo and select bank
const int MODE_TAP_LED_PIN = 11; // pin to LED for tempo/bank indicator

//******VARIABLES***********
// a data array to remember the current state of each switch
boolean state[MODE_COUNT][D_PINS]; // !!!!! state of the main switches; used to select D2 as 'off' or 'on' value
elapsedMillis modeTimer,flashTimer; // timers for mode select hold and for flash control 
int bank = 0 ; // index of selected bank to select CC number set
int flashcount= (-1*waitflash); // default is effective sequence stop of flash counter
int shiftUp; // keeps track of whether the mode change needs to be handled (true) or was (false)
int modeLED; // keeps track of whether LED is on without testing it..
int ClockCount; // for tempo tracking
boolean tempoFlashOn = true;// tempo flash defaults to on

//************INITIALIZE LIBRARY OBJECTS**************

// initialize the bounce objects 
Bounce digital[] =   {
    Bounce(DIGITAL_PINS[0],BOUNCE_TIME), 
    Bounce(DIGITAL_PINS[1], BOUNCE_TIME),
    Bounce(DIGITAL_PINS[2], BOUNCE_TIME),
    Bounce(DIGITAL_PINS[3], BOUNCE_TIME),
    Bounce(DIGITAL_PINS[4], BOUNCE_TIME)
}; 
Bounce modeTap = Bounce(MODE_TAP_PIN, BOUNCE_TIME);

//************SETUP**************
void setup() {
  
  //'set a handle for returning MIDI messages'
  usbMIDI.setHandleControlChange(OnControlChange); // used for DAW feedback of settings
  usbMIDI.setHandleSongPosition(onSongPosition); // read expicit beat for pulse flash
  usbMIDI.setHandleClock(onClock); // pulse flash every 24 clock
  usbMIDI.setHandleStart(onStart); // reset clock
  usbMIDI.setHandleSystemExclusive(onSysEx); // used to set presets into EEPROM
  //'loop to configure input pins and internal pullup resisters for digital section'
  for (int i=0;i<D_PINS;i++){
    pinMode(DIGITAL_PINS[i], INPUT_PULLUP);
    pinMode(RED_LED_PINS[i], OUTPUT);
    pinMode(GREEN_LED_PINS[i], OUTPUT);
  }
  pinMode(MODE_TAP_PIN, INPUT_PULLUP);
  pinMode(MODE_TAP_LED_PIN, OUTPUT);
  digitalWrite(MODE_TAP_LED_PIN, LED_OFF);


  for (int i=0;i<D_PINS;i++){
    digitalWrite(GREEN_LED_PINS[i], LED_OFF);   // - GREEN OFF
    digitalWrite(RED_LED_PINS[i], LED_ON);   // - RED ON
  }
}

//************LOOP**************
void loop() {
  getMain(); // process the main switches
  getModeTap(); // process the mode/tap switch
  flasher(); // flasher called to handle flash queue events and end clock pulse suppression
  while (usbMIDI.read()) {
    // controllers must call .read() to keep the queue clear even if they are not responding to MIDI
    // but we are using handles for real time messages and CCs for setting LEDs
  }
}


//************DIGITAL SECTION**************
void getMain(){
  for (int i=0;i<D_PINS;i++){
    digital[i].update();
    if (digital[i].fallingEdge() || digital[i].risingEdge()) { // to use toggle hardware switch as a trigger
      if (state[bank][i]) { // if ON send OFF and vice versa
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], OFF_Value, channel);  
        digitalWrite(RED_LED_PINS[i], LED_ON);
        digitalWrite(GREEN_LED_PINS[i], LED_OFF);
      }else{
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], ON_Value, channel);  
        digitalWrite(RED_LED_PINS[i], LED_OFF);
        digitalWrite(GREEN_LED_PINS[i], LED_ON);
      }
      state[bank][i] = !state[bank][i] ; // toggle state so it match new settings
    }
  }
}

//************MODE/TAP SECTION**************
void getModeTap(){
  modeTap.update();
  if (modeTap.fallingEdge()) { // falling edge could be a tap or a bank-select but tap is sent either way!
    usbMIDI.sendControlChange(TAP_CC, ON_Value, channel);  // currently off is not sent but can be added to risingEdge
    modeTimer = 0; // reset timer to watch for mode change if held
    shiftUp = true; // mark as potentially indicating a bank select increment
    tempoFlashOn = false; // start suppressing the tempo pulse
    flashcount = 0; // is this error or magic?
    Serial.println("suspend tempo on tap");
  }    
  if (modeTap.risingEdge()){ 
    shiftUp = false; // ...mark that no shift up pending
    if (modeTimer<modeThreshold){
      tempoFlashOn = true; // no mode change so resume tempo pulse
    Serial.println("resume tempo no mode change");
    }
  }
  if (modeTimer>modeThreshold && shiftUp) { // shiftUp implies risingEdge has not been received so button held
    shiftUp = false; // so shift no longer pending
    bank = (bank+1)%MODE_COUNT; // increment bank and rollover at MODE_COUNT
    for (int i = 0; i < D_PINS ; i++){
      digitalWrite(GREEN_LED_PINS[i], LED_ON ? state[bank][i]):!state[bank][i]); // magic to allow active HIGH or LOW
      digitalWrite(RED_LED_PINS[i], LED_ON ? !state[bank][i]):state[bank][i]); 
    }
    // set counter of flashes 'owed' -- count them down after main part
    flashcount = bank + 1; // flash count is 1 plus bank index
    flashTimer = 0 ; // is this needed?
  }
}


//************BANK FLASH SECTION**************
void flasher(){
  if (flashcount>= -1*waitflash){
    if (flashcount> 0){
      if (flashTimer>(flashOnTime+flashOffTime)){ 
        flashcount-- ;// decrement flashcount
        flashTimer = 0;
        if (modeLED){
          modeLED = false;
          digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
        }
      }else{
        if (modeLED == false && flashTimer>flashOnTime){
          modeLED = true;
          digitalWrite(MODE_TAP_LED_PIN, LED_ON);
        }
      }
    }else{
      if (flashTimer>(flashOnTime+flashOffTime)){ 
        flashcount-- ;// decrement flashcount
        flashTimer = 0;
      }
    }
  }else{
    if (!tempoFlashOn){
      tempoFlashOn = true;
      Serial.println("resume tempo after bank select");
    }
  }
}


//************MIDI HANDLERS SECTION**************
void OnControlChange(byte rcvChannel, byte controller, byte value) {
  if (rcvChannel == channel){ // ...only if on correct channel (could support a different feedback channel!)
    for (int i = 0; i < D_PINS ; i++){ // cycle through CCs...
      if (MIDI_CC_NUMS[bank][i] == controller) { // ...and process if match
        if (value >= 64) {
          digitalWrite(GREEN_LED_PINS[i], LED_ON);
          digitalWrite(RED_LED_PINS[i], LED_OFF); //'receiving >=64 turns green on and red off'
          state[bank][i] = true; // store the current state
        }else{
          digitalWrite(GREEN_LED_PINS[i], LED_OFF);
          digitalWrite(RED_LED_PINS[i], LED_ON); //'receiving <64 turns red on and green off'
          state[bank][i] = false; // store the current state
        } // 'if not the controller for ‘i’ then skip this loop'    
      }
    }
  }
}

void onClock() { // flash the tempo LED every 24 clocks
  if (tempoFlashOn){
    if (ClockCount<=onClocks){ // will flash on as counter starts up from zero
      digitalWrite(MODE_TAP_LED_PIN, LED_ON);
    }else{ // will turn off after the requireed number of clocks (onClocks)
      digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
    }
  }
  ClockCount = (ClockCount+1)%24; // increment and reset at 24
}

void onStart(){
  ClockCount = 0; // song restarted so clock counter reset
}


void onSongPosition(uint16_t semiQ){
  ClockCount= (semiQ*6)%24 ; // set clock to explicit position from song pointer
}

void onSysEx(byte *sysExBytes, unsigned int length)
  if (sysExBytes[0] == 0xF0  // sysEx start
  && sysExBytes[24] == 0xF7 // sysEx end
  && sysExBytes[1] == 0x7D // 7D is private use (non-commercial)
  && sysExBytes[2] == 0x41 // 4-byte 'key' - not really needed if via USB but why not!
  && sysExBytes[3] == 0x43
  && sysExBytes[4] == 0x48
  && sysExBytes[5] == 0x30){ // read and compare static bytes to ensure valid msg
    for (int i = 0; i < MODE_COUNT*D_PINS+1; i++) {
      EEPROM.write(i, sysExBytes[i+6]);
      mem[i] = sysExBytes[i+6];
      if (i<MODE_COUNT*D_PINS+1){
        MIDI_CC_NUMS[i%5][i] = sysExBytes[i+6];
      }else{
        TAP_CC = sysExBytes[i+6];
      }
    }  
    usbMIDI.sendSysEx(3, 0x7D,false);  // acknowledge msg - should be safe for any device even if listening for 7D
  }
}

Testing this without the box will be a bit tricky so once I'm close I may need your help to confirm it works.

… the sysEx stuff is at the very bottom (except where the handle is declared)
 
Code:
//************LIBRARIES USED**************
// include the Bounce library for 'de-bouncing' switches -- removing electrical chatter as contacts settle
#include <Bounce.h> 
#include <EEPROM.h>
// usbMIDI.h library is added automatically when code is compiled as a MIDI device

// ********************CONSTANT VALUES**********************************
// **<user configurable content>  // configurable parameters as constants

//********** MIDI DEFINITIONS
int channel = 1; // MIDI channel (variable assignable in sysEx EEPROM system)
const int MODE_COUNT = 3; // number of rows of banks
const int D_PINS = 5; // number of Digital PINS in main group (excl. tap/bank)
//CC configuration matrix!! - not a const as it's configured by SysEx
int MIDI_CC_NUMS[MODE_COUNT][D_PINS] = { //rows are banks up to MODE_COUNT
    {60,61,62,63,64},
    {65,66,67,68,69},
    {70,71,72,73,74}
}; // these are the D1 values sent for each mode|pin pair
int TAP_CC = 85; // D1 value for TAP down
const int ON_Value = 127; // note-one velocity sent from buttons (should be 65 to  127)
const int OFF_Value = 0; // note-one velocity sent from buttons (should be 65 to  127)


//******************************** PHYSICAL DEFINITIONS ***************
const int BOUNCE_TIME = 30; // 5 ms is usually sufficient
const int modeThreshold = 800; // how long to hold before mode changes
const int flashOnTime = 150; // how long flashed LED is on
const int flashOffTime = 250; // how long flashed LED is off
const bool LED_ON = HIGH; // LOW for active LOW wiring
const bool LED_OFF = LOW; // HIGH for active LOW wiring
const int onClocks = 4; // number of clock messages with LED on for tempo pulse
const int waitflash = 2; // number of dark flash cycles after bank change before tempo pulse resumes

//** </user configurable content> // don't change below here without knowing how it works!

//********** PIN DEFINITIONS (edit only if rewired or home built with different connections)
const int DIGITAL_PINS[D_PINS] = {0,1,5,3,4}; // pins to switches 
const int RED_LED_PINS[D_PINS] = {6,19,8,10,17};
const int GREEN_LED_PINS[D_PINS] = {7,20,9,16,18};
const int MODE_TAP_PIN = 1;  // momentary switch to set tempo and select bank
const int MODE_TAP_LED_PIN = 13; // pin to LED for tempo/bank indicator

//******VARIABLES***********
// a data array to remember the current state of each switch
boolean state[MODE_COUNT][D_PINS]; // !!!!! state of the main switches; used to select D2 as 'off' or 'on' value
elapsedMillis modeTimer,flashTimer; // timers for mode select hold and for flash control 
int bank = 0 ; // index of selected bank to select CC number set
int flashcount= (-1*waitflash); // default is effective sequence stop of flash counter
boolean shiftUp; // keeps track of whether the mode change needs to be handled (true) or was (false)
boolean modeLED; // keeps track of whether LED is on without testing it..
int ClockCount; // for tempo tracking
boolean tempoFlashOn = true;// tempo flash defaults to on

//************INITIALIZE LIBRARY OBJECTS**************

// initialize the bounce objects 
Bounce digital[] =   {
    Bounce(DIGITAL_PINS[0],BOUNCE_TIME), 
    Bounce(DIGITAL_PINS[1], BOUNCE_TIME),
    Bounce(DIGITAL_PINS[2], BOUNCE_TIME),
    Bounce(DIGITAL_PINS[3], BOUNCE_TIME),
    Bounce(DIGITAL_PINS[4], BOUNCE_TIME)
}; 
Bounce modeTap = Bounce(MODE_TAP_PIN, BOUNCE_TIME);

//************SETUP**************
void setup() {
  
  //'set a handle for returning MIDI messages'
  usbMIDI.setHandleControlChange(OnControlChange); // used for DAW feedback of settings
  usbMIDI.setHandleSongPosition(onSongPosition); // read expicit beat for pulse flash
  usbMIDI.setHandleClock(onClock); // pulse flash every 24 clock
  usbMIDI.setHandleStart(onStart); // reset clock
  usbMIDI.setHandleSystemExclusive(onSysEx); // used to set presets into EEPROM
  //'loop to configure input pins and internal pullup resisters for digital section'
  for (int i=0;i<D_PINS;i++){
    pinMode(DIGITAL_PINS[i], INPUT_PULLUP);
    pinMode(RED_LED_PINS[i], OUTPUT);
    pinMode(GREEN_LED_PINS[i], OUTPUT);
  }
  pinMode(MODE_TAP_PIN, INPUT_PULLUP);
  pinMode(MODE_TAP_LED_PIN, OUTPUT);
  digitalWrite(MODE_TAP_LED_PIN, LED_OFF);


  for (int i=0;i<D_PINS;i++){
    digitalWrite(GREEN_LED_PINS[i], LED_OFF);   // - GREEN OFF
    digitalWrite(RED_LED_PINS[i], LED_ON);   // - RED ON
  }
  if (EEPROM.read(MODE_COUNT*D_PINS+2)>0 && EEPROM.read(MODE_COUNT*D_PINS+2)<17){ // EEPROM for channel is non-zer0 or not a viable MIDI channel
    for (int i = 0; i <MODE_COUNT*D_PINS+2; i++) { // read EEPROM for sysEx data
      if (i<MODE_COUNT*D_PINS){
        MIDI_CC_NUMS[i/5][i] = EEPROM.read(i);
      }else if(i==MODE_COUNT*D_PINS) { // index after matrix 
        TAP_CC = EEPROM.read(i);
        Serial.println(TAP_CC);
      }else { // assume last byte 
        channel = EEPROM.read(i);
      }
      delay(5); // give time for EEPROM read??
    }
  }
  flashcount = 5;
} 

//************LOOP**************
void loop() {
  getMain(); // process the main switches
  getModeTap(); // process the mode/tap switch
  flasher(); // flasher called to handle flash queue events and end clock pulse suppression
  while (usbMIDI.read()) {
    // controllers must call .read() to keep the queue clear even if they are not responding to MIDI
    // but we are using handles for real time messages and CCs for setting LEDs
  }
}


//************DIGITAL SECTION**************
void getMain(){
  for (int i=0;i<D_PINS;i++){
    digital[i].update();
    if (digital[i].fallingEdge() || digital[i].risingEdge()) { // to use toggle hardware switch as a trigger
      if (state[bank][i]) { // if ON send OFF and vice versa
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], OFF_Value, channel);  
        digitalWrite(RED_LED_PINS[i], LED_ON);
        digitalWrite(GREEN_LED_PINS[i], LED_OFF);
      }else{
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], ON_Value, channel);  
        digitalWrite(RED_LED_PINS[i], LED_OFF);
        digitalWrite(GREEN_LED_PINS[i], LED_ON);
      }
      state[bank][i] = !state[bank][i] ; // toggle state so it match new settings
    }
  }
}

//************MODE/TAP SECTION**************
void getModeTap(){
  modeTap.update();
  if (modeTap.fallingEdge()) { // falling edge could be a tap or a bank-select but tap is sent either way!
    usbMIDI.sendControlChange(TAP_CC, ON_Value, channel);  // currently off is not sent but can be added to risingEdge
    modeTimer = 0; // reset timer to watch for mode change if held
    shiftUp = true; // mark as potentially indicating a bank select increment
    tempoFlashOn = false; // start suppressing the tempo pulse
    flashcount = 0; // is this error or magic?
    Serial.println("suspend tempo on tap");
  }    
  if (modeTap.risingEdge()){ 
    shiftUp = false; // ...mark that no shift up pending
    if (modeTimer<modeThreshold){
      tempoFlashOn = true; // no mode change so resume tempo pulse
    Serial.println("resume tempo no mode change");
    }
  }
  if (modeTimer>modeThreshold && shiftUp) { // shiftUp implies risingEdge has not been received so button held
    shiftUp = false; // so shift no longer pending
    bank = (bank+1)%MODE_COUNT; // increment bank and rollover at MODE_COUNT
    for (int i = 0; i < D_PINS ; i++){
      digitalWrite(GREEN_LED_PINS[i], LED_ON ? state[bank][i]:!state[bank][i]); // magic to allow active HIGH or LOW
      digitalWrite(RED_LED_PINS[i], LED_ON ? !state[bank][i]:state[bank][i]); 
    }
    // set counter of flashes 'owed' -- count them down after main part
    flashcount = bank + 1; // flash count is 1 plus bank index
    flashTimer = 0 ; // is this needed?
  }
}


//************BANK FLASH SECTION**************
void flasher(){
  if (flashcount>= -1*waitflash){
    if (flashcount> 0){
      if (flashTimer>(flashOnTime+flashOffTime)){ 
        flashcount-- ;// decrement flashcount
        flashTimer = 0;
        if (modeLED){
          modeLED = false;
          digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
        }
      }else{
        if (modeLED == false && flashTimer>flashOnTime){
          modeLED = true;
          digitalWrite(MODE_TAP_LED_PIN, LED_ON);
        }
      }
    }else{
      if (flashTimer>(flashOnTime+flashOffTime)){ 
        flashcount-- ;// decrement flashcount
        flashTimer = 0;
      }
    }
  }else{
    if (!tempoFlashOn){
      tempoFlashOn = true;
      Serial.println("resume tempo after bank select");
    }
  }
}


//************MIDI HANDLERS SECTION**************
void OnControlChange(byte rcvChannel, byte controller, byte value) {
  if (rcvChannel == channel){ // ...only if on correct channel (could support a different feedback channel!)
    for (int i = 0; i < D_PINS ; i++){ // cycle through CCs...
      if (MIDI_CC_NUMS[bank][i] == controller) { // ...and process if match
        if (value >= 64) {
          digitalWrite(GREEN_LED_PINS[i], LED_ON);
          digitalWrite(RED_LED_PINS[i], LED_OFF); //'receiving >=64 turns green on and red off'
          state[bank][i] = true; // store the current state
        }else{
          digitalWrite(GREEN_LED_PINS[i], LED_OFF);
          digitalWrite(RED_LED_PINS[i], LED_ON); //'receiving <64 turns red on and green off'
          state[bank][i] = false; // store the current state
        } // 'if not the controller for ‘i’ then skip this loop'    
      }
    }
  }
}

void onClock() { // flash the tempo LED every 24 clocks
  if (tempoFlashOn){
    if (ClockCount<=onClocks){ // will flash on as counter starts up from zero
      digitalWrite(MODE_TAP_LED_PIN, LED_ON);
    }else{ // will turn off after the requireed number of clocks (onClocks)
      digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
    }
  }
  ClockCount = (ClockCount+1)%24; // increment and reset at 24
}

void onStart(){
  ClockCount = 0; // song restarted so clock counter reset
}


void onSongPosition(uint16_t semiQ){
  ClockCount= (semiQ*6)%24 ; // set clock to explicit position from song pointer
}

void onSysEx(byte *sysExBytes, unsigned int length){
  if (sysExBytes[0] == 0xF0  // sysEx start
  && sysExBytes[MODE_COUNT*D_PINS+9] == 0xF7 // sysEx end
  && sysExBytes[1] == 0x7D // 7D is private use (non-commercial)
  && sysExBytes[2] == 0x41 // 4-byte 'key' - not really needed if via USB but why not!
  && sysExBytes[3] == 0x43
  && sysExBytes[4] == 0x48
  && sysExBytes[5] == 0x30){ // read and compare static bytes to ensure valid msg
    for (int i = 0; i < MODE_COUNT*D_PINS+2; i++) {
      if (i<MODE_COUNT*D_PINS){
        MIDI_CC_NUMS[i/5][i] = sysExBytes[i+6];
  Serial.println(MIDI_CC_NUMS[i/5][i]);
      }else if(i==MODE_COUNT*D_PINS) {
        TAP_CC = sysExBytes[i+6];
  Serial.println(TAP_CC);
      }else { // assume last byte 
        channel = sysExBytes[i+6];
  Serial.println(channel);
      }
    }  
    byte data[] = { 0xF0, 0x7D, 0xF7 }; // ACK msg - should be safe for any device even if listening for 7D
    usbMIDI.sendSysEx(3,data,true);  // acknowledge msg - should be safe for any device even if listening for 7D
  }
}
Many boneheaded mistakes later... and still not tested properly.

pins etc. need to change back for you....

also there's a 17th parameter stored - channel. Also tested on reading EEPROM to see if it's non-zero and not more than 16.
 
Code:
//************LIBRARIES USED**************
// include the Bounce library for 'de-bouncing' switches -- removing electrical chatter as contacts settle
#include <Bounce.h> 
#include <EEPROM.h>
// usbMIDI.h library is added automatically when code is compiled as a MIDI device

// ********************CONSTANT VALUES**********************************
// **<user configurable content>  // configurable parameters as constants

//********** MIDI DEFINITIONS
int channel = 1; // MIDI channel (variable assignable in sysEx EEPROM system)
const int MODE_COUNT = 3; // number of rows of banks
const int D_PINS = 5; // number of Digital PINS in main group (excl. tap/bank)
//CC configuration matrix!! - not a const as it's configured by SysEx
int MIDI_CC_NUMS[MODE_COUNT][D_PINS] = { //rows are banks up to MODE_COUNT
    {60,61,62,63,64},
    {65,66,67,68,69},
    {70,71,72,73,74}
}; // these are the D1 values sent for each mode|pin pair
int TAP_CC = 85; // D1 value for TAP down
const int ON_Value = 127; // note-one velocity sent from buttons (should be 65 to  127)
const int OFF_Value = 0; // note-one velocity sent from buttons (should be 65 to  127)


//******************************** PHYSICAL DEFINITIONS ***************
const int BOUNCE_TIME = 30; // 5 ms is usually sufficient
const int modeThreshold = 800; // how long to hold before mode changes
const int flashOnTime = 150; // how long flashed LED is on
const int flashOffTime = 250; // how long flashed LED is off
const bool LED_ON = HIGH; // LOW for active LOW wiring
const bool LED_OFF = LOW; // HIGH for active LOW wiring
const int onClocks = 4; // number of clock messages with LED on for tempo pulse
const int waitflash = 2; // number of dark flash cycles after bank change before tempo pulse resumes

//** </user configurable content> // don't change below here without knowing how it works!

//********** PIN DEFINITIONS (edit only if rewired or home built with different connections)
const int DIGITAL_PINS[D_PINS] = {0,1,5,3,4}; // pins to switches 
const int RED_LED_PINS[D_PINS] = {6,19,8,10,17};
const int GREEN_LED_PINS[D_PINS] = {7,20,9,16,18};
const int MODE_TAP_PIN = 1;  // momentary switch to set tempo and select bank
const int MODE_TAP_LED_PIN = 13; // pin to LED for tempo/bank indicator

//******VARIABLES***********
// a data array to remember the current state of each switch
boolean state[MODE_COUNT][D_PINS]; // !!!!! state of the main switches; used to select D2 as 'off' or 'on' value
elapsedMillis modeTimer,flashTimer; // timers for mode select hold and for flash control 
int bank = 0 ; // index of selected bank to select CC number set
int flashcount= (-1*waitflash); // default is effective sequence stop of flash counter
boolean shiftUp; // keeps track of whether the mode change needs to be handled (true) or was (false)
boolean modeLED; // keeps track of whether LED is on without testing it..
int ClockCount; // for tempo tracking
boolean tempoFlashOn = true;// tempo flash defaults to on

//************INITIALIZE LIBRARY OBJECTS**************

// initialize the bounce objects 
Bounce digital[] =   {
    Bounce(DIGITAL_PINS[0],BOUNCE_TIME), 
    Bounce(DIGITAL_PINS[1], BOUNCE_TIME),
    Bounce(DIGITAL_PINS[2], BOUNCE_TIME),
    Bounce(DIGITAL_PINS[3], BOUNCE_TIME),
    Bounce(DIGITAL_PINS[4], BOUNCE_TIME)
}; 
Bounce modeTap = Bounce(MODE_TAP_PIN, BOUNCE_TIME);

//************SETUP**************
void setup() {
  
  //'set a handle for returning MIDI messages'
  usbMIDI.setHandleControlChange(OnControlChange); // used for DAW feedback of settings
  usbMIDI.setHandleSongPosition(onSongPosition); // read expicit beat for pulse flash
  usbMIDI.setHandleClock(onClock); // pulse flash every 24 clock
  usbMIDI.setHandleStart(onStart); // reset clock
  usbMIDI.setHandleSystemExclusive(onSysEx); // used to set presets into EEPROM
  //'loop to configure input pins and internal pullup resisters for digital section'
  for (int i=0;i<D_PINS;i++){
    pinMode(DIGITAL_PINS[i], INPUT_PULLUP);
    pinMode(RED_LED_PINS[i], OUTPUT);
    pinMode(GREEN_LED_PINS[i], OUTPUT);
  }
  pinMode(MODE_TAP_PIN, INPUT_PULLUP);
  pinMode(MODE_TAP_LED_PIN, OUTPUT);
  digitalWrite(MODE_TAP_LED_PIN, LED_OFF);


  for (int i=0;i<D_PINS;i++){
    digitalWrite(GREEN_LED_PINS[i], LED_OFF);   // - GREEN OFF
    digitalWrite(RED_LED_PINS[i], LED_ON);   // - RED ON
  }
  if (EEPROM.read(MODE_COUNT*D_PINS+2)>0 && EEPROM.read(MODE_COUNT*D_PINS+2)<17){ // EEPROM for channel is non-zer0 or not a viable MIDI channel
    for (int i = 0; i <MODE_COUNT*D_PINS+2; i++) { // read EEPROM for sysEx data
      if (i<MODE_COUNT*D_PINS){
        MIDI_CC_NUMS[i/5][i] = EEPROM.read(i);
      }else if(i==MODE_COUNT*D_PINS) { // index after matrix 
        TAP_CC = EEPROM.read(i);
        Serial.println(TAP_CC);
      }else { // assume last byte 
        channel = EEPROM.read(i);
      }
      delay(5); // give time for EEPROM read??
    }
  }
  flashcount = 5;
} 

//************LOOP**************
void loop() {
  getMain(); // process the main switches
  getModeTap(); // process the mode/tap switch
  flasher(); // flasher called to handle flash queue events and end clock pulse suppression
  while (usbMIDI.read()) {
    // controllers must call .read() to keep the queue clear even if they are not responding to MIDI
    // but we are using handles for real time messages and CCs for setting LEDs
  }
}


//************DIGITAL SECTION**************
void getMain(){
  for (int i=0;i<D_PINS;i++){
    digital[i].update();
    if (digital[i].fallingEdge() || digital[i].risingEdge()) { // to use toggle hardware switch as a trigger
      if (state[bank][i]) { // if ON send OFF and vice versa
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], OFF_Value, channel);  
        digitalWrite(RED_LED_PINS[i], LED_ON);
        digitalWrite(GREEN_LED_PINS[i], LED_OFF);
      }else{
        usbMIDI.sendControlChange(MIDI_CC_NUMS[bank][i], ON_Value, channel);  
        digitalWrite(RED_LED_PINS[i], LED_OFF);
        digitalWrite(GREEN_LED_PINS[i], LED_ON);
      }
      state[bank][i] = !state[bank][i] ; // toggle state so it match new settings
    }
  }
}

//************MODE/TAP SECTION**************
void getModeTap(){
  modeTap.update();
  if (modeTap.fallingEdge()) { // falling edge could be a tap or a bank-select but tap is sent either way!
    usbMIDI.sendControlChange(TAP_CC, ON_Value, channel);  // currently off is not sent but can be added to risingEdge
    modeTimer = 0; // reset timer to watch for mode change if held
    shiftUp = true; // mark as potentially indicating a bank select increment
    tempoFlashOn = false; // start suppressing the tempo pulse
    flashcount = 0; // is this error or magic?
    Serial.println("suspend tempo on tap");
  }    
  if (modeTap.risingEdge()){ 
    shiftUp = false; // ...mark that no shift up pending
    if (modeTimer<modeThreshold){
      tempoFlashOn = true; // no mode change so resume tempo pulse
    Serial.println("resume tempo no mode change");
    }
  }
  if (modeTimer>modeThreshold && shiftUp) { // shiftUp implies risingEdge has not been received so button held
    shiftUp = false; // so shift no longer pending
    bank = (bank+1)%MODE_COUNT; // increment bank and rollover at MODE_COUNT
    for (int i = 0; i < D_PINS ; i++){
      digitalWrite(GREEN_LED_PINS[i], LED_ON ? state[bank][i]:!state[bank][i]); // magic to allow active HIGH or LOW
      digitalWrite(RED_LED_PINS[i], LED_ON ? !state[bank][i]:state[bank][i]); 
    }
    // set counter of flashes 'owed' -- count them down after main part
    flashcount = bank + 1; // flash count is 1 plus bank index
    flashTimer = 0 ; // is this needed?
  }
}


//************BANK FLASH SECTION**************
void flasher(){
  if (flashcount>= -1*waitflash){
    if (flashcount> 0){
      if (flashTimer>(flashOnTime+flashOffTime)){ 
        flashcount-- ;// decrement flashcount
        flashTimer = 0;
        if (modeLED){
          modeLED = false;
          digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
        }
      }else{
        if (modeLED == false && flashTimer>flashOnTime){
          modeLED = true;
          digitalWrite(MODE_TAP_LED_PIN, LED_ON);
        }
      }
    }else{
      if (flashTimer>(flashOnTime+flashOffTime)){ 
        flashcount-- ;// decrement flashcount
        flashTimer = 0;
      }
    }
  }else{
    if (!tempoFlashOn){
      tempoFlashOn = true;
      Serial.println("resume tempo after bank select");
    }
  }
}


//************MIDI HANDLERS SECTION**************
void OnControlChange(byte rcvChannel, byte controller, byte value) {
  if (rcvChannel == channel){ // ...only if on correct channel (could support a different feedback channel!)
    for (int i = 0; i < D_PINS ; i++){ // cycle through CCs...
      if (MIDI_CC_NUMS[bank][i] == controller) { // ...and process if match
        if (value >= 64) {
          digitalWrite(GREEN_LED_PINS[i], LED_ON);
          digitalWrite(RED_LED_PINS[i], LED_OFF); //'receiving >=64 turns green on and red off'
          state[bank][i] = true; // store the current state
        }else{
          digitalWrite(GREEN_LED_PINS[i], LED_OFF);
          digitalWrite(RED_LED_PINS[i], LED_ON); //'receiving <64 turns red on and green off'
          state[bank][i] = false; // store the current state
        } // 'if not the controller for ‘i’ then skip this loop'    
      }
    }
  }
}

void onClock() { // flash the tempo LED every 24 clocks
  if (tempoFlashOn){
    if (ClockCount<=onClocks){ // will flash on as counter starts up from zero
      digitalWrite(MODE_TAP_LED_PIN, LED_ON);
    }else{ // will turn off after the requireed number of clocks (onClocks)
      digitalWrite(MODE_TAP_LED_PIN, LED_OFF);
    }
  }
  ClockCount = (ClockCount+1)%24; // increment and reset at 24
}

void onStart(){
  ClockCount = 0; // song restarted so clock counter reset
}


void onSongPosition(uint16_t semiQ){
  ClockCount= (semiQ*6)%24 ; // set clock to explicit position from song pointer
}

void onSysEx(byte *sysExBytes, unsigned int length){
  if (sysExBytes[0] == 0xF0  // sysEx start
  && sysExBytes[MODE_COUNT*D_PINS+9] == 0xF7 // sysEx end
  && sysExBytes[1] == 0x7D // 7D is private use (non-commercial)
  && sysExBytes[2] == 0x41 // 4-byte 'key' - not really needed if via USB but why not!
  && sysExBytes[3] == 0x43
  && sysExBytes[4] == 0x48
  && sysExBytes[5] == 0x30){ // read and compare static bytes to ensure valid msg
    for (int i = 0; i < MODE_COUNT*D_PINS+2; i++) {
      if (i<MODE_COUNT*D_PINS){
        MIDI_CC_NUMS[i/5][i] = sysExBytes[i+6];
  Serial.println(MIDI_CC_NUMS[i/5][i]);
      }else if(i==MODE_COUNT*D_PINS) {
        TAP_CC = sysExBytes[i+6];
  Serial.println(TAP_CC);
      }else { // assume last byte 
        channel = sysExBytes[i+6];
  Serial.println(channel);
      }
    }  
    byte data[] = { 0xF0, 0x7D, 0xF7 }; // ACK msg - should be safe for any device even if listening for 7D
    usbMIDI.sendSysEx(3,data,true);  // acknowledge msg - should be safe for any device even if listening for 7D
  }
}
Many boneheaded mistakes later... and still not tested properly.

pins etc. need to change back for you....

also there's a 17th parameter stored - channel. Also tested on reading EEPROM to see if it's non-zero and not more than 16.

Well, I must say I haven’t really had anything to do with Sysex. That’s a side of midi that I’ve really only just learned about. Very cool. Thanks oddson for adding it to my existing code. Do you think it’s simple enough to implement? i have a friends who’s very well versed in web apps and this seems like a very simple interface and backend to build. It would be a great feature to have.

One more complicated question: would it be possible to even edit whether it sends midi notes or ccs? Or even change the entire behaviour to make the ccs act as momentary? At a the moment my plan was simply to have 4 or 5 sketches which are different ‘profiles’ so people can choose the functions they want, load that profile, and then change the note or cc numbers only. It’s really simple to do, but there’s always the fact that people get very uncomfortable when they’re messing around with lines of code.
 
Making every control mappable is a thing you can do and it’s a really useful feature to have, a company called Open Labs used to make custom controllers like this with software to map every control. They also had presets you could make that were saved in the software so you could make an unlimited number of banks because the software handled it all in real-time. They also had a small display that you could program to go along with the banks and presets so you could name stuff for different DAWs and things.
 
Well, I must say I haven’t really had anything to do with Sysex. ... Do you think it’s simple enough to implement? i have a friends who’s very well versed in web apps and this seems like a very simple interface and backend to build. It would be a great feature to have.
It should be dead simple as a JavaScript goes. You just need to assemble the editable bytes from object.value properties into a sequence and send it on the press of a button or other object that can trigger the JavaScript.

http://www.w3.org/TR/webmidi/#requesting-access-to-the-midi-system-with-system-exclusive-support

http://www.w3.org/TR/webmidi/#sending-midi-messages-to-an-output-device

The second section shows for a note-on but for us the section building the message would would assemble a longer sequence but still load it with
output.send( [ 0xF0, 0x7D, … ,0xF7 ] );


One more complicated question: would it be possible to even edit whether it sends midi notes or ccs?
Turns out this is not all that complicated. The setting is just one more byte of data to store but the logic in the sketch isn't greatly impacted by the change.

You just need a conditional test for the state variable indicating button behaviour and send CC for one setting and note-on/off for the other.

Code:
        if (sendNotes){
          usbMIDI.sendNoteOn(MIDI_D1_VALUES[bank][i], OFF_Value, channel);  
        }else{
          usbMIDI.sendControlChange(MIDI_D1_VALUES[bank][i], OFF_Value, channel);  
        }
I've changed the array name to reflect the more generic D1 (data byte 1).

Or even change the entire behaviour to make the ccs act as momentary?
This is one thing you cannot do! Because you've used actual toggles since they send only one 'edge' per cycle there is no 'up' message to treat as the end to a momentary selection.

At a the moment my plan was simply to have 4 or 5 sketches which are different ‘profiles’ so people can choose the functions they want, load that profile, and then change the note or cc numbers only. It’s really simple to do, but there’s always the fact that people get very uncomfortable when they’re messing around with lines of code.
The above strategy could be used with a case or series of ]else if(){ statements to select behaviour based on what value the 'sendNotes' takes (you'd likely rename the state variable something like sendMode or sendType).

Pretty much anything you can leave accessible to users as define statements or variable declarations can be accessed from EEPROM instead.

And conditional tests and state variables can let you alter behaviour in specific sections of the code without rewriting the logic that scans the buttons or tracks the tap or sets the bank value.

The trickiest stuff is where you have behaviour across time instead of at a point in time as you need to leave the code scanning for other changes.

I've developed versions of my Footsy controller that did stuff like send a ramp of ascending/descending CC values at on/off events instead of instant changes to avoid artifacts on activating effects or to do automated cross-fades. Once you open up stuff like this you can have any number of control parameters and things can start to get complicated.

Also, I take it this code is still not at testing stage?
I have not tested further as I'm going to need to build a rig but it does compile and did seem to work.

This is the one untested bit of code:
Code:
      digitalWrite(GREEN_LED_PINS[i], LED_ON ? state[bank][i]:!state[bank][i]); 
      digitalWrite(RED_LED_PINS[i], LED_ON ? !state[bank][i]:state[bank][i]);

It can be switch back to the older simpler version
Code:
      digitalWrite(GREEN_LED_PINS[i], !state[bank][i]); // magic to allow active HIGH or LOW
      digitalWrite(RED_LED_PINS[i], state[bank][i]);
but it will not work with active high wired LEDs but that's not how it's wired anyway... it was more so I could build mine active HIGH.
 
Last edited:
Wow so much information! Thanks for your time on this! Don't forget you'll be sent one of my pedals for helping out ;) so don't go building one just yet!
Looks like I'm investigating JavaScript web apps now :) this will evidently be an ongoing process of developing the sketch to update functions.
 
I could build you a Flowstone based editor .exe file that will work on any Windows machine like I made for my Footsy controller.... it's really easy especially as I can use mine as a template so I think I do this and share it here (next weekend?).

A web page with a web forum can generate the string of bytes but only Chrome and Opera send directly via MIDI. For other browsers you could assemble the string of hex code to paste into a MIDI utility to program the box.

The web-page thing will give non-Windows users a chance to edit directly and a Flowstone tool might be welcomed by Windows users that don't use Chrome. But the box is ready for any version that can send that small sysex message.

F0 7D 41 43 48 30 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 0F F7

This string sent from a utility like Pocket MIDI would configure to your defaults (if the code didn't already) --an extra value at the end could be 00->CC values send and 01->Note-on/off sent

Note that these are hex coded so 3C -> 0x3C=60 means the first switch sends D2 = 60 (middle C if note-on or CC number 60 otherwise).



Can you let me know if the code above works like the prior version you said was functioning? The two lines I noted are practically the only significant change but sometimes I cock things up with minor alterations that seem totally safe and I'd like to know if there are any of these in there.
 
I was playing with HTML today... didn't have a MIDI capable browser so I focused on building the string to paste into a MIDI utility... just leaving the WIP code here for further development.


When I'm at home with a few minutes I'll see about actually making it talk MIDI directly... that's new for me.


The data validation is just a start... my JavaScript is pretty rusty and there's a lot of changes since I coded HTML last.

HTML:
<!DOCTYPE html>
<html><style>
.topRow{
  background-color: skyBlue;

}
body { 
  background-color: ivory;
  color: black;
  font-family: "Arial Black",Helvetica,sans-serif;
}
table  {
    border: 1px solid black;
  border-color: black;
margin: 20px;
} 
td{
  v-align=center;
text-align:center;
}
input.MIDIbyte{
  color: blue;
text-align: center;
}
h1{
  font-size: 1.2em; /* 30px/16=1.875em */
}
</style>






<body> 

<h1>A Demo Web to MIDI sysEx generator</h1>
<p id="demo">loading</p>
<button type="button" onclick="makeString()">Try it</button>





<form>


<table style="width: 500px">
	<tr class="topRow">
		<th width="20%">Switch</th>
		<th width="16%">1</th>
		<th width="16%">2</th>
		<th width="16%">3</th>
		<th width="16%">4</th>
		<th width="16%">5</th>
	</tr>
	<tr>
		<th>Bank 1</th>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B0S0"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B0S1"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B0S2"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B0S3"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B0S4"></td>
	</tr>
	<tr>
		<th>Bank 2</th>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B1S0"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B1S1"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B1S2"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B1S3"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B1S4"></td>
	</tr>
	<tr>
		<th>Bank 3</th>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B2S0"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B2S1"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B2S2"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B2S3"></td>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="B2S4"></td>
	</tr>	
</table>
<table>
	<tr>
		<th>Tap CC</th>
		<td><input class="MIDIbyte" max="127" min="0" maxlength="3" size="3" type="number" id="tap"></td>
	</tr>
    <tr>
    	<th>MIDI Channel</th>
        <td><input class="MIDInibble" max="16" min="1" maxlength="1" size="1" type="number" id="channel"  value="1" ></td>
    </tr>
</table>


<input type="text" id='sysExString' class='longString' size="80">
</form>
<script type="text/javascript">

if (navigator.requestMIDIAccess) {
var midi = null;  // global MIDIAccess object
document.getElementById("demo").innerHTML = "Send SysEx command to congigure your controller.";
}else{
document.getElementById("demo").innerHTML = "Generate a string of HEX for MIDI utilities that can send space delimited, two-character HEX values";
}
function onMIDISuccess( midiAccess ) {
  console.log( "MIDI ready!" );
  midi = midiAccess;  // store in the global (in real usage, would probably keep in an object instance)
}

function onMIDIFailure(msg) {
  console.log( "Failed to get MIDI access - " + msg );
}



function makeString(){
	elementList = ["B0S0" , "B0S1" , "B0S2" , "B0S3" , "B0S4" , "B1S0" , "B1S1" , "B1S2" , "B1S3" , "B1S4" , "B2S0" , "B2S1" , "B2S2" , "B2S3" , "B2S4" , "tap" , "channel"]
	var myString = "F0 7D 41 43 48 30 "
	var i;
	for (var i = 0; i < (elementList.length); i++) { 
		var ccNum =document.getElementById(elementList[i]).value
		if (document.getElementById(elementList[i]).value.length == 0) {
			if (i == 0) {
				ccNum = 11
			}else{
				ccNum = Math.min(parseInt(document.getElementById(elementList[i-1]).value)+1,127)
			}
		}
			document.getElementById(elementList[i]).value = Math.min(ccNum,127)
		if (ccNum <16) {
			myString +=  "0" + parseInt(ccNum).toString(16).toUpperCase()  + " "
		}else{
			myString +=  parseInt(ccNum).toString(16).toUpperCase()  + " "
		}
	} 
	myString += " F7"
	document.getElementById("sysExString").value = myString
}




</script>
</body>
</html>
 
I wrote a full library that does this with its own midi editor that is scalable and the editor is web based with also a desktop app running on electron. All done with sysex for communication, the library handles everything from buttons, leds, pots, encoders, relays plus up to 7 midi io ports

I announced it here https://forum.pjrc.com/threads/5828...-with-companion-Desktop-Editor!?highlight=Bmc

im still finishing the documentation and getting examples together but should be released early 2020.
 
Other than getting the index for the terminating character wrong the code from post 11 worked and I got the ACK back from a Teensy 2.0.

Code:
void onSysEx(byte *sysExBytes, unsigned int length){
  if (sysExBytes[0] == 0xF0  // sysEx start
  && sysExBytes[MODE_COUNT*D_PINS+[COLOR="#FF0000"][B]8[/B][/COLOR]] == 0xF7 // sysEx end
  ....

It will take a bit of time to check the CC values are changing properly as I have to do it grounding pins with a wire and reading a MIDI monitor.

This Web MIDI stuff is interesting now that I'm at an enabled browser but I haven't figured it out completely yet... I'll post the corrected HTML code when I do
 
Hi, sorry for taking soooooo long to reply! I've been selling the midistomp for about a month now - going very well! People are very happy and are blown away by the price and the functions, so thank you oddson for your marvelous help. I've sold about 30 so far and had 5 people accept a pedal for review/promotion. Three of whom have midi software-based businesses! I'm still very keen to get a web editor going, because that's going to be much more user friendly.
If you pm me your address I can send you the current physical build to work with :)
 
Status
Not open for further replies.
Back
Top