Control-Surface icon indicating copy to clipboard operation
Control-Surface copied to clipboard

Provide a generic example for ButtonMatrix callback in the main sketch

Open emalihin opened this issue 3 years ago • 1 comments

Being very new to C++ development I was looking for a clean way to execute arbitrary code in the main sketch - triggered by the NoteButtonMatrix. My first use case was controlling WS2812B LEDs, which are driven by another library.

I haven't found an example in this repo/issues to do this. Existing LED examples are great but in all cases are driven by the Incoming MIDI messages. In my case WS2812B LEDs need to be driven individually with the push of a button in NoteButtonMatrix, independent of MIDI messages.

Initially I thought including LED library into Control Surface was a good idea, but then I found a much cleaner way - to register a function from the main sketch as a callback in Control Surface.

Sketch:

// Main sketch
#include <Control_Surface.h>
#include <WS2812Serial.h>

// MIDI
USBMIDI_Interface usbmidi;
HardwareSerialMIDI_Interface serialmidi = {Serial1, MIDI_BAUD};
BidirectionalMIDI_PipeFactory<2> pipes;

// MATRIX
const AddressMatrix<4, 4> addresses = {{
  {60,61,62,63},
  {64,65,66,67},
  {68,69,70,71},
  {72,73,74,75}
}};

NoteButtonMatrix<4, 4> buttonmatrix = {
  {19,18,17,16}, // row pins
  {6,7,8,9},    // column pins
  addresses,    // address matrix
  CHANNEL_1,    // channel and cable number
};

// LED
const int numled = 16;
const int pin = 14;

byte drawingMemory[numled*3];         //  3 bytes per LED
DMAMEM byte displayMemory[numled*12]; // 12 bytes per LED

WS2812Serial leds(numled, displayMemory, drawingMemory, pin, WS2812_GRB);

#define RED    0xFF0000
#define GREEN  0x00FF00

int ledArray[4][4] = {
  {0,1,2,3},
  {4,5,6,7},
  {8,9,10,11},
  {12,13,14,15}
};

void setActiveButtonLed(int row, int col, uint32_t color) {
  leds.setPixel(ledArray[row][col], color);
  leds.show();
}

void setup() {
  // MIDI
  Control_Surface | pipes | usbmidi;
  Control_Surface | pipes | serialmidi;
  Control_Surface.begin();

  Serial.begin(115200);

  // LED
  leds.setBrightness(20);
  leds.begin();

  for (int led = 0; led < numled; led++){ // Startup animation
    leds.setPixel(led, GREEN);
    delay(100);
    leds.show();
  }

  Control_Surface.set_color_callback(setActiveButtonLed);
}

void loop() {
  Control_Surface.loop();
}

Of interest here is Control_Surface.set_color_callback(setActiveButtonLed);

Here's the way I went about it in the library code:

  • In Control Surface/src/Control_Surface/Control_Surface_Class.hpp I added the following function pointer and a public function
  using m_cb = void (*)(int, int, uint32_t); // function pointer

  public:
    m_cb set_active_color_callback;

    void set_color_callback(m_cb act)
    {
        set_active_color_callback = act;
    }
  • In Control Surface/src/MIDI_Outputs/Abstract/MIDIButtonMatrix.hpp I extended the private onButtonChanged function as such to call Control_Surface.set_active_color_callback. I feel there should be a cleaner way to execute set_active_color_callback?
  private:
    void onButtonChanged(uint8_t row, uint8_t col, bool state) final override {

        #define RED    0xFF0000
        #define GREEN  0x00FF00

        int8_t address = addresses[row][col];
        if (state == LOW) {
            sender.sendOn({address, baseChannelCN});

            Control_Surface.set_active_color_callback(row, col, RED); //invoke the callback
        }
        else {
            sender.sendOff({address, baseChannelCN});
            
            Control_Surface.set_active_color_callback(row, col, GREEN); //invoke the callback
        }
    }

This works as expected - a push of the button in the matrix triggers setActiveButtonLed() and passes row, col, color to it, which in turn selects the correct LED from ledArray and turns it RED/GREEN.

The reason I added the callback pointer/function to the Control_Surface class is to be able to access it from the main sketch. But I am not sure if calling Control_Surface.set_active_color_callback() from the library is the best way to do it?

Would like some feedback/improvement advice and maybe we could add something like this to examples?

Additionally I am curious about portability of such modification through library updates. Will a future update wipe my changes? What's the best way to persist them (other than maintaining a fork of this library)?

Many thanks for this great library and the top level of support you provide here!

emalihin avatar Apr 16 '21 10:04 emalihin

It's not clear to me what the advantage is of adding the callback to the Control_Surface_ class. At first sight, the colors and events are all tied to the button matrix, and have nothing to do with Control_Surface_ itself. This approach doesn't generalize.
Why not add the callback to the button matrix?

If you want to be able to easily update the library later, you shouldn't change any of the source files. Everything you describe can be done without altering the library, by defining your own MIDI elements, as shown in this example: https://tttapa.github.io/Control-Surface-doc/Doxygen/d4/d3b/Custom-MIDI-Output-Element_8ino-example.html
You can use the MIDIButtonMatrix as a starting point and add your specific behavior and LED logic to your own custom version of that class.

tttapa avatar May 13 '21 15:05 tttapa