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

Thread: Creating a software editor

  1. #1

    Creating a software editor

    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

  2. #2
    Senior Member
    Join Date
    Apr 2013
    Posts
    1,909
    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/lib...ial/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.

  3. #3
    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.

  4. #4
    Have you seen https://processing.org/ ?
    This allows to make Apps for Windows, Android and Pi in an arduinoesque way, that can communicate with MIDI, COM and other channels.

  5. #5
    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.

  6. #6
    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.

  7. #7
    Senior Member vjmuzik's Avatar
    Join Date
    Apr 2017
    Location
    Florida
    Posts
    428
    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.

  8. #8
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,238
    Quote Originally Posted by thomasp View Post
    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...dal-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.

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

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

  11. #11
    Quote Originally Posted by oddson View Post
    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.

  12. #12
    Also, I take it this code is still not at testing stage?

  13. #13
    Senior Member vjmuzik's Avatar
    Join Date
    Apr 2017
    Location
    Florida
    Posts
    428
    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.

  14. #14
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,238
    Quote Originally Posted by Aussie_CrocHunter View Post
    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/#reques...lusive-support

    http://www.w3.org/TR/webmidi/#sendin...-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.

    Quote Originally Posted by Aussie_CrocHunter View Post
    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 by oddson; 11-25-2019 at 06:26 PM.

  15. #15
    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.

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

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

  18. #18
    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/58283...?highlight=Bmc

    im still finishing the documentation and getting examples together but should be released early 2020.

  19. #19
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,238
    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+8] == 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

Posting Permissions

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