MIDI Controller, 3 Pots SAMD21 (Seeeduino Xiao)

Hello, I am building a 3 pot MIDI controller for expression in Cubase, I would like CC 11, CC1, CC21, for Expression, Dynamics, and Vibrato. I understand I can change these in my DAW later on.

I jumped into this thinking I could program a Seeeduino XIAO, thinking the SAMD21 was MIDI over USB capable, but I am having problems getting anything to load into the XIAO board.

There is an example I really like, that suits my project specifically, but was designed for a Teeensy 3.1, which I see has a very simple USB MIDI in Arduino: Tools>USB>MIDI...then with this sketch BOOM probably easily use in the DAW. My Seeeduino XIAO is a bit different: Tools>USB>Ardiuno OR TinyUSB (I thought this SAMD21 was USB MIDI capable!)

I have tried including USBMIDI in the sketch, and have considered the TinyUSB route but can find no examples as nicely as this one for my project and am not sure where to go, am considering starting over with a Teensy board but have 2 of these xiaos and would REALLY like to make it work, as I am a MIDI production student and don't have 300$ for this desired controller I'm trying to make!

Anyone able to help me? I would REALLY appreciate some guidance!

Here is my ideal example I mistakenly thought would be easy for me to use!

#include <Bounce.h>

///////////////////////////////////////////////////////////////////////////
// define how many pots are active up to number of available analog inputs
#define analogInputs 3
//////////////////////////////////////////////////////////////////////////

// define arrays for input values and lagged input values
int inputAnalog[analogInputs];
int iAlag[analogInputs];
// define array of cc values
int ccValue[analogInputs];
// include the ResponsiveAnalogRead library
#include <ResponsiveAnalogRead.h>

///////////////////////////////////////////////////////////////////////////
// define pins and cc codes
const int A_PINS = 3;
const int ANALOG_PINS[A_PINS] = {A0, A1, A2};
const int CCID[A_PINS] = {11, 1, 21};
///////////////////////////////////////////////////////////////////////////

// a data array and a lagged copy to tell when MIDI changes are required
byte data[A_PINS];
byte dataLag[A_PINS];

// ititialize the ReponsiveAnalogRead objects
ResponsiveAnalogRead analog[]{
///////////////////////////////////////////////////////////////////////////
{ANALOG_PINS[0],true},
{ANALOG_PINS[1],true},
{ANALOG_PINS[2],true},
///////////////////////////////////////////////////////////////////////////
};

// the setup routine runs once when you press reset:
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
}

void loop(){
// update the ResponsiveAnalogRead object every loop
for (int i=0;i<A_PINS;i++){
analog[i].update();
// if the repsonsive value has change, print out 'changed'
if(analog[i].hasChanged()) {
data[i] = analog[i].getValue()>>3;
if (data[i] != dataLag[i]){
dataLag[i] = data[i];
usbMIDI.sendControlChange(CCID[i], data[i], 1);
}
}
}
}

Please and Thank you!!

Ken

What error messages do You face?

A tip regarding posting code: Format the code. In the IDE world it's done by Ctrl + T. Mark the code and press Ctrl C.
Then click on the code tag symbol looking like </> and paste.

When I upload sketch as is, I get: 'usbMIDI' was not declared in this scope.
Also, the last line of the sketch is highlighted:

usbMIDI.sendControlChange(CCID[i], data[i], 1);

(Hope I pasted that right, and thanks for the tip)

usbMIDI is Teensy-only. You'll need a different library.

The Control Surface library I maintain provides a uniform MIDI API across different boards and backends (although I've never tested it on Seeeduino's Arduino Cores).
Control Surface: MIDI Tutorial

By default, it tries to use PluggableUSB through the Arduino MIDIUSB library on most boards, but you can use TinyUSB as well, see e.g. https://github.com/tttapa/Control-Surface/blob/main/src/MIDI_Interfaces/USBMIDI/USBMIDI_Adafruit_TinyUSB.hpp or Control Surface: Custom-USB-MIDI-Backend.ino.

Ok, wow, I loaded one of the potentiometer examples, and basically this is how I feel:
[IT WORKS - Back to the Future - YouTube]

lol it WORKS!! The MIDI over USB from your library works for the Seeeduino!

I thought I was going to have to start from scratch!
Thank you SO MUCH Pieter for this, and the beautiful library, WOW if I knew about this Control Surface, I might have had the confidence to put a bank button in my controller and an LCD or OLED display! Maybe next controller project... :slight_smile:

I found another thread where you helped a guy on here, to configure for 3 pots and cc values, think I can reference that for now, or just follow the wonderful tutorials in this library, so easy and logical to follow. How kind and generous to put forth the effort to help people on here with this marvelous programming knowledge you have! I really appreciate it!

Many thanks,
Ken

1 Like

This is a game changer for Seeeduino Users!! Thank you so much!!!
:smiling_face_with_tear:

Would you mind posting your code for a quick check on my Xiao?

You could probably use this as a starting point: Control Surface: Multiple-Control-Change-Potentiometers.ino

Yes! Thank you! It worked :slight_smile:
You're amazing thank you so much!!

I’ve been testing this and all it’s potential with a Seeduino Xiao for a week straight. You can probably consider this as tested for this board.
What a fantastic library!

Anybody would know where to find a way to rename the USB device name when it shows up in your device list? Right now it’s defaulting to “Seeeduino Xiao”

I wondered the same thing. I found this:
Xiao USB Name / Device ID - XIAO - Seeed Forum, but I haven't had a chance to try messing with it in the bootloader yet.

Been having a lot of fun with this library.

Pieter, I've used the example "Note-LED.ino" as a starting point to get an LED to turn on when any midi note between 36-80 is triggered, rather than just one note (like C(4) on the example).

It works but only one note at a time. For example:
-I press D(4) it will turn on and then turn off the LED when released.
-I press Bb(5) it will turn on and then turn off the LED when released.

The problem arises when I press D(4) and then press Bb(5). While I'm holding both notes the LED is on, but as soon as I release one of the 2 notes the LED will turn off, even if my NOTE_ON message is still going on the other note I'm still pressing.

Any hints on where I can look to solve this?

I've tried an if-else function that will keep the "threshold" value > 0 if 'MIDIMessageType::NOTE_ON', which will then switch the state between 'HIGH : LOW' in the 'handleUpdate' function:
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
void handleUpdate(typename Matcher::Result match) override {
PinStatus_t state = match.value >= threshold ? HIGH : LOW;
AH::ExtIO::digitalWrite(ledPin, state);
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

But that didn't work either.

Let me know if you have an idea, this has been the best tool for me to learn coding. I went from beginner to intermediate studying this library!

You could use something like this:

#include <Control_Surface.h>

class NoteRangeLED
    : public MatchingMIDIInputElement<MIDIMessageType::NOTE_ON,
                                      TwoByteRangeMIDIMatcher> {
  public:
    /// @param  ledPin
    ///         Output pin with the LED connected.
    /// @param  address
    ///         The first address to listen to.
    /// @param  len
    ///         The length of the address range to listen to.
    NoteRangeLED(pin_t ledPin, MIDIAddress address, uint8_t len)
        : MatchingMIDIInputElement {{address, len}}, ledPin {ledPin} {}

  private:
    void handleUpdate(TwoByteRangeMIDIMatcher::Result match) override {
        bool newState = match.value >= threshold;
        bool oldState = states.get(match.index);
        if (newState != oldState) {
            states.set(match.index, newState);
            newState ? ++count : --count;
            if (count == 0)
                ExtIO::digitalWrite(ledPin, LOW);
            else if (count == 1 && newState)
                ExtIO::digitalWrite(ledPin, HIGH);
        }
    }

  public:
    void begin() override {
        ExtIO::pinMode(ledPin, OUTPUT);
        ExtIO::digitalWrite(ledPin, LOW);
    }

    void reset() override {
        ExtIO::digitalWrite(ledPin, LOW);
    }

    uint8_t getThreshold() const { return threshold; }
    void setThreshold(uint8_t threshold) { this->threshold = threshold; }

  private:
    pin_t ledPin;
    BitArray<128> states;
    uint8_t threshold = 0x01;
    uint8_t count = 0;
};

USBMIDI_Interface midi;
NoteRangeLED leds {LED_BUILTIN, MIDI_Notes::C(2), 44};

void setup() {
  Control_Surface.begin();
}

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

For a bit more details, see Control Surface: Custom-Note-LED-Input-Element-Callback.ino.

If you assume that Note On and Note Off events always come in pairs, you can use just the counter, without the states array, but this isn't very robust.

Ah yes that makes sense, counter AND state array. I will try to first decipher this and apply it.
It does make more sense to use the array of states in combination with the counter, I will surely try that route!

Thank you again Pieter

This is very clear now. If I understand correctly, the BitArray has 128 index positions, and each one represents a midi note number. Once a midi note is played it will update it's state to 1 (true) in the respective position - minus "first address to listen to" that we setup (If I start listening to addresses on note 12, and I play note 60, my index will be 48.) Once you release, the state of that same note goes to 0 (false).

The counter counts the amount of notes played under certain conditions and ++count or --count to keep track of true state notes.

But when you play multiple notes; as you release notes one by one, is there a function to update the new max index position that is still in state 1?

example: If I play C3, E3, F3, A3:
-Releasing A3 should return the index position of F3,
-Releasing F3 should return the index position of E3,
....so on...

An application would be for example mapping out a PWM to an LED that would dim from "off-->full brightness" between C2 and C4, but there's no way to track the release of notes and update the new max index position value to dim the LED accordingly.
Right now if I release A3, F3, E3 the LED stays on the same brightness as A3.

Thank you :slight_smile:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.