MIDI Foot Controller using Arduino

Started by niektb, June 19, 2021, 06:20:24 AM

Previous topic - Next topic

niektb

Hey all!

I thought I'd do some design documentation for the Locrius' MIDI Master: a tiny midi controller I'm working on as present for my brother! ;D
Hopefully it can be of use for y'all!

Initially the point is to control a Boss RC-500, but I wanted to make it easy to adapt so I can also control my own Line6 M5 with minimal changes in the code :)

First of all, the schematic and the PCB lay-out! I made it to fit 3 footswitches inside a 1590A. To save space, I opted for a 3.5mm jack as MIDI out (there are cables for this) Very easy as you can see. I used a cheap Arduino Nano clone I bought on eBay (for like $3.50) because it gives me an easy way of programming and debugging :).



I had it custom drilled and UV printed at Tayda Electronics (if you want a drill template / Vector Graphics, I can give it to you :)). Not without hiccups though lol, I didn't vectorize the fonts:  :o



Assembling the box, it all first barely so this means I engineered it perfectly  8) :P
I covered the inside of the lid with isolation tape, to prevent shorts with the USB connector :)



Onto the software! I want to detect 3 switches simultaneously without any delays and with proper software debouncing, so I'm inclined to use some form of an RTOS / Schedular. I turned towards Manny Peterson's HeliOS (https://github.com/MannyPeterson/HeliOS) which I used in the past and is quite easy to use ánd lightweight (in contrary to f.e. FreeRTOS).

I have a Github Repository here! https://github.com/niektb/Midi-Master-Software
Feedback on the code is welcome! Especially if you know how to remove the code duplication ;D

In the current state (19th of June) it can detect Press, Hold and Release from 3 different switches asynchronously and prints this over serial. It can also measure the duration of the press (not sure if im going to use it, but it might be possible to do some tap tempo or expression control?). Midi functionality isn't implemented yet! As a proof of concept:



Thanks for reading! I'll update the post when I implement more software functionality!  ;D

potul

Hi

Nice project, and a good present for your brother.

I haven't gone through your code, but my 2 cents is that using RTOS for simple midi messaging and button detection is an overkill. If you are familiar with it and ok working with RTOS, then move on, it will not hurt. But it's not really needed.
The timing needs for button detection and midi are not really very demanding.

Mat

ElectricDruid

+1 agree with Potul. RTOS sounds like a lot to add.

I've done this type of thing using a polling loop, and even then, I had a timer to trigger the loop only every 0.5 millisecond and slow it down a bit. MIDI bytes arrive once per millisecond (roughly) even at full tilt, and you probably don't want to scan the buttons even that fast, since they'll need debouncing, and faster scanning tends to make debouncing into a bigger problem.

Alternatively, I've also done MIDI using interrupts (this is a good idea for the basic parsing of Real Time messages, which are separate from the main MIDI messages and can occur at any point). Since MIDI bytes are slow this doesn't add a crushing overhead. MIDI was designed to work on 1980's processors like the 8051, remember!

The project looks good. I love the cute little enclosure! Very nice. And well done getting everything squashing in there so neatly too.

niektb

#3
Thanks for your kind compliments  ;D

You are both correct! A full-blown RTOS would normally be way too much for handling 3 buttons and MIDI :)
However, it's not thát complicated. HeliOS leans more towards scheduler territory and is non-preemptive which means that a lot of the RTOS headaches do not exist (of course, at the expensive of
the 'Real-Time' aspect) ;) Look at the code! It's really not that bad ;) (except for the code duplication i mentioned earlier). It makes me totally lazy cause i can write three independent loops and totally don't care about interaction, love it!  8) ;D
Also, I like to have some form of easy extensibility! Think more of it as a platform which I could easily use for other digitally controlled pedals I may or may not create at some point in the future (yep, pretty vague ;) )


#include <HeliOS_Arduino.h>

/* PIN DEFINITIONS */
const uint8_t pin_sw1  = 4;
const uint8_t pin_sw2  = 3;
const uint8_t pin_sw3  = 2;

/* DEBOUNCE PARAMETERS */
const uint8_t debounce_delay = 20;

// SWITCH 1
bool s1state;
bool last_s1state = HIGH;
bool last_button1 = HIGH;

unsigned long ldt1 = 0; // last debounce time
unsigned long ftt1; // first tap time
unsigned long frt1; // first release time
bool wfr1 = false;  // wait for release

// SWITCH 3
bool s2state;
bool last_s2state = HIGH;
bool last_button2 = HIGH;

unsigned long ldt2 = 0; // last debounce time
unsigned long ftt2; // first tap time
unsigned long frt2; // first release time
bool wfr2 = false;  // wait for release

// SWITCH 3
bool s3state;
bool last_s3state = HIGH;
bool last_button3 = HIGH;

unsigned long ldt3 = 0; // last debounce time
unsigned long ftt3; // first tap time
unsigned long frt3; // first release time
bool wfr3 = false;  // wait for release

/* TASKS */
void taskSW1(xTaskId id_)
{
  int button = digitalRead(pin_sw1);
  // reset debouncing timer if the switch changed, due to noise or pressing:
  if (button != last_button1)
    ldt1 = millis();

  // If the button state is stable for at least [debounce_delay], fire body of the statement
  if ((millis() - ldt1) > debounce_delay) {

    // Button and last_button represent the 'unstable' input that gets updated continuously.
    // These are used for debouncing.
    // s1state is the stable input that can be used for reading button presses.
    if (button != s1state)
      s1state = button;

    if (s1state == LOW && last_s1state == HIGH) // SWITCH PRESS
    {
      ftt1 = millis();
      wfr1 = true;

      // PERFORM PRESS ACTION
      xTaskNotify(xTaskGetId("TASKSERIAL"), 4, (char *)"PRE1" );
     
      last_s1state = s1state;
    }
    else if (wfr1 && ((millis() - ftt1) >= 1000)) // SWITCH HOLD
    {
      wfr1 = false;
      // PERFORM HOLD ACTION
      xTaskNotify(xTaskGetId("TASKSERIAL"), 4, (char *)"HOL1" );
    }
    else if (s1state == HIGH && last_s1state == LOW) // SWITCH RELEASE
    {
      if (wfr1)
      {
        frt1 = millis();

        // PERFORM RELEASE ACTION
        xTaskNotify(xTaskGetId("TASKSERIAL"), 4, (char *)"REL1" );

        wfr1 = false;
      }
      last_s1state = s1state;
    }
  }
  last_button1 = button;
}

void taskSW2(xTaskId id_)
{
  int button = digitalRead(pin_sw2);
  // reset debouncing timer if the switch changed, due to noise or pressing:
  if (button != last_button2)
    ldt2 = millis();

  // If the button state is stable for at least [debounce_delay], fire body of the statement
  if ((millis() - ldt2) > debounce_delay) {

    // Button and last_button represent the 'unstable' input that gets updated continuously.
    // These are used for debouncing.
    // s2state is the stable input that can be used for reading button presses.
    if (button != s2state)
      s2state = button;

    if (s2state == LOW && last_s2state == HIGH) // SWITCH PRESS
    {
      ftt2 = millis();
      wfr2 = true;

      // PERFORM PRESS ACTION
      xTaskNotify(xTaskGetId("TASKSERIAL"), 4, (char *)"PRE2");
     
      last_s2state = s2state;
    }
    else if (wfr2 && ((millis() - ftt2) >= 1000)) // SWITCH HOLD
    {
      wfr2 = false;
      // PERFORM HOLD ACTION
      xTaskNotify(xTaskGetId("TASKSERIAL"), 4, (char *)"HOL2");
    }
    else if (s2state == HIGH && last_s2state == LOW) // SWITCH RELEASE
    {
      if (wfr2)
      {
        frt2 = millis();

        // PERFORM RELEASE ACTION
        xTaskNotify(xTaskGetId("TASKSERIAL"), 4, (char *)"REL2");

        wfr2 = false;
      }
      last_s2state = s1state;
    }
  }
  last_button2 = button;
}

void taskSW3(xTaskId id_)
{
  int button = digitalRead(pin_sw3);
  // reset debouncing timer if the switch changed, due to noise or pressing:
  if (button != last_button3)
    ldt3 = millis();

  // If the button state is stable for at least [debounce_delay], fire body of the statement
  if ((millis() - ldt3) > debounce_delay) {

    // Button and last_button represent the 'unstable' input that gets updated continuously.
    // These are used for debouncing.
    // s3state is the stable input that can be used for reading button presses.
    if (button != s3state)
      s3state = button;

    if (s3state == LOW && last_s3state == HIGH) // SWITCH PRESS
    {
      ftt3 = millis();
      wfr3 = true;

      // PERFORM PRESS ACTION
      xTaskNotify(xTaskGetId("TASKSERIAL"), 4, (char *)"PRE3");
     
      last_s3state = s3state;
    }
    else if (wfr3 && ((millis() - ftt3) >= 1000)) // SWITCH HOLD
    {
      wfr3 = false;
      // PERFORM HOLD ACTION
      xTaskNotify(xTaskGetId("TASKSERIAL"), 4, (char *)"HOL3");
    }
    else if (s3state == HIGH && last_s3state == LOW) // SWITCH RELEASE
    {
      if (wfr3)
      {
        frt3 = millis();

        // PERFORM RELEASE ACTION
        xTaskNotify(xTaskGetId("TASKSERIAL"), 4, (char *)"REL3");

        wfr3 = false;
      }
      last_s3state = s1state;
    }
  }
  last_button3 = button;
}

void taskSerial(xTaskId id_)
{
  xTaskGetNotifResult res = xTaskGetNotif(id_);
  if (res)
    Serial.println(res->notifyValue);
  xMemFree(res);
  xTaskNotifyClear(id_);
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("[MIDI Master Debug Stream]"));

  // PIN SETUP
  pinMode(pin_sw1, INPUT);
  pinMode(pin_sw2, INPUT);
  pinMode(pin_sw3, INPUT);

  xTaskId id = 0;
  xHeliOSSetup();
 
  id = xTaskAdd("TASKSW1", &taskSW1);
  xTaskStart(id);

  id = xTaskAdd("TASKSW2", &taskSW2);
  xTaskStart(id);

  id = xTaskAdd("TASKSW3", &taskSW3);
  xTaskStart(id);

  id = xTaskAdd("TASKSERIAL", &taskSerial);
  xTaskWait(id);
}

void loop()
{
  xHeliOSLoop();
}

ElectricDruid

I agree that does make things impressively simple to write. Nice. :)

potul

HelIOs looks interesting... I might give it a try in a project someday.

Regarding code duplication, the only thing that comes to my mind is that you could use a function for all the button debouncing and action triggering, where you pass the button number as a parameter, and call it from each task.
You would need then to use arrays for each button related variable. Like ldt[1] instead of ldt1

Blackaddr

It sounds like you're already well into HellOs, but if somebody also has similar requirements and finds this forum thread, I suggest they look at the TeensyThreads that ships with Teensyduino.

It provides threading and basic scheduling on the Teensy without any OS, and it provides both an Arduino library style interface as well as the C++ std::thread syntax and also provides mutex support. I've used it quite a bit for separating overhead work (like low-speed comms and button/rotary encoder/potentiometer) processing onto secondary, threads with scheduled time limits to ensure my real-time audio doesn't take a hit.
Blackaddr Audio
Digital Modelling Enthusiast
www.blackaddr.com

potul

Quote from: Blackaddr on June 22, 2021, 07:55:03 AM
It sounds like you're already well into HellOs, but if somebody also has similar requirements and finds this forum thread, I suggest they look at the TeensyThreads that ships with Teensyduino.
Does this one work with typical Arduinos, or is it Teensy only?

Blackaddr

Quote from: potul on June 22, 2021, 12:45:18 PM
Quote from: Blackaddr on June 22, 2021, 07:55:03 AM
It sounds like you're already well into HellOs, but if somebody also has similar requirements and finds this forum thread, I suggest they look at the TeensyThreads that ships with Teensyduino.
Does this one work with typical Arduinos, or is it Teensy only?

It's for Teensy 3.x & 4.x.
Blackaddr Audio
Digital Modelling Enthusiast
www.blackaddr.com

niektb

Quote from: Blackaddr on June 23, 2021, 06:49:24 AM
Quote from: potul on June 22, 2021, 12:45:18 PM
Quote from: Blackaddr on June 22, 2021, 07:55:03 AM
It sounds like you're already well into HellOs, but if somebody also has similar requirements and finds this forum thread, I suggest they look at the TeensyThreads that ships with Teensyduino.
Does this one work with typical Arduinos, or is it Teensy only?

It's for Teensy 3.x & 4.x.

Aaaah yes, I'm working with an Atmega328-based arduino which is much more limited in speed and memory, so I would need something really lightweight :)
I'm thinking of using a class to remove the code duplication, got that suggestion from a co-worker of mine. Not sure though, as introducing OOP would make it instantly less beginner-friendly :)

niektb


First version is working! Still have to tidy up the code (as I need to figure out some details on how to construct the classes properly :)) and implement additional features but the concept stands! (left and right are - and +, middle button goes to predefined favorite)

Sweetalk

Check out the Notes and Volts MIDI controller tutorial. The code is a really good starting point to make almost anything in a midi controller.

niektb

#12
Update! I finished my code during the holiday but had no internet to share. So after a delay here it is! Might not be the easiest code if you're not familiar with classes but overal I think it's pretty readable  :icon_mrgreen:

midi_master_main.ino

#include "switch.h"

/* SWITCH DEFINITIONS */
Switch switch1 = Switch(4U);
Switch switch2 = Switch(3U);
Switch switch3 = Switch(2U);

// MIDI PARAM
const uint8_t programChangeLine6M5 = 192;
//const uint8_t controlChangeLine6M5 = 176;
const uint8_t favPresetLine6M5 = 7;
uint8_t presetLine6M5 = favPresetLine6M5;

// Send MIDI message
void sendMidiProgramChange(int COMMAND, int DATA) {
    Serial.write(COMMAND);
    Serial.write(DATA);
}

/* TASKS */
void taskSW1(xTaskId id_)
{
    // Call the task function of switch1.
    switch1.TaskFunction(id_, (char *)"PRE1", (char *)"HOL1", (char *)"REL1");
}

void taskSW2(xTaskId id_)
{
    // Call the task function of switch2.
    switch2.TaskFunction(id_, (char *)"PRE2", (char *)"HOL2", (char *)"REL2");
}

void taskSW3(xTaskId id_)
{
    // Call the task function of switch3.
    switch3.TaskFunction(id_, (char *)"PRE3", (char *)"HOL3", (char *)"REL3");
}

void taskSerial(xTaskId id_)
{
    xTaskGetNotifResult res = xTaskGetNotif(id_);
    if (res)
        Serial.println(res->notifyValue);
    xMemFree(res);
    xTaskNotifyClear(id_);
}

void taskMan(xTaskId id_)
{
    xTaskGetNotifResult res = xTaskGetNotif(id_);
    if (res)
    {
        // Perform task if we got a notification
        if (strcmp(res->notifyValue, "PRE1") == 0)
        {
            // Press Switch 1
            if (presetLine6M5 > 0)
                presetLine6M5--;
            else
                presetLine6M5 = 23; 
           
            sendMidiProgramChange(programChangeLine6M5, presetLine6M5);
        }
        else if (strcmp(res->notifyValue, "PRE2") == 0)
        {
            // Press Switch 2
            presetLine6M5 = favPresetLine6M5;
         
            sendMidiProgramChange(programChangeLine6M5, presetLine6M5);
        }
        else if (strcmp(res->notifyValue, "PRE3") == 0)
        {
            // Press Switch 3
            if (presetLine6M5 < 23)
                presetLine6M5++;
            else
                presetLine6M5 = 0;
           
            sendMidiProgramChange(programChangeLine6M5, presetLine6M5);
        }
    //xTaskNotify(xTaskGetId("TASKSERIAL"), 4, res->notifyValue);
    }
    xMemFree(res);
    xTaskNotifyClear(id_);
}

void setup()
{
    Serial.begin(31250);
    //Serial.println(F("[MIDI Master Debug Stream]"));
   
    delay(3000); // Compensate for slow start Line6 M5
    sendMidiProgramChange(programChangeLine6M5, presetLine6M5);

    xHeliOSSetup();
   
    xTaskId id = xTaskAdd("TASKSW1", &taskSW1);
    xTaskStart(id);
   
    id = xTaskAdd("TASKSW2", &taskSW2);
    xTaskStart(id);
   
    id = xTaskAdd("TASKSW3", &taskSW3);
    xTaskStart(id);
   
    id = xTaskAdd("TASKMAN", &taskMan);
    xTaskWait(id);
}

void loop()
{
    xHeliOSLoop();
}


switch.h

#pragma once

#include <HeliOS_Arduino.h>

class Switch
{
public:
    // Constructor
    // @param pin  Pin number of the switch
    Switch(const uint8_t pin);
   
    // Destructor
    ~Switch();
   
    // Function to execute in the task
    // @param id_  ID of the calling task
    // @param pre  command to send to TaskMan on Switch press
    // @param hold command to send to TaskMan on Switch hold
    // @param rela command to send to TaskMan on Switch release
    void TaskFunction(xTaskId id_, const char pre[4], const char hold[4], const char rela[4]);

private:
    // Pin number of the switch
    const uint8_t PIN;
   
    // Stable input that can be used for reading button
    int switchState;
    // Previous stable input state
    int prevSwitchState = HIGH;
   
    // Last debounce time
    uint32_t lastDebounceTime;
    // First tap time
    uint32_t firstTapTime;
    // First release time
    uint32_t firstReleaseTime;
   
    // Wait for release
    bool WaitForRelease;
   
    // Raw previous button state used for debouncing
    int prevButtonState = HIGH;
};


switch.cpp

#include "switch.h"

Switch::Switch(const uint8_t pin) :
    PIN(pin),
    switchState(HIGH),
    prevSwitchState(HIGH),
    lastDebounceTime(0U),
    firstTapTime(0U),
    firstReleaseTime(0U),
    WaitForRelease(false),
    prevButtonState(HIGH)
{
    pinMode(this->PIN, INPUT);
}

Switch::~Switch()
{
}

void Switch::TaskFunction(xTaskId id_, const char pre[4], const char hold[4], const char rel[4])
{
    /* DEBOUNCE PARAMETERS */
    const uint8_t debounce_delay = 20;
   
    int buttonState = digitalRead(this->PIN);
   
    // reset debouncing timer if the switch changed, due to noise or pressing:
    if (buttonState != this->prevButtonState)
    {
      this->lastDebounceTime = millis();
    }
   
    // if the button state is stable for at least [debounce_delay], fire body of the statement
    if ((millis() - this->lastDebounceTime) > debounce_delay)
    {
        // buttonState and lastButtonState represent the 'unstable' input that gets updated continuously.
        // these are used for debouncing.
        // switchState is the stable input that can be used for reading button presses.
        if (buttonState != this->switchState)
        {
            this->switchState = buttonState;
        }
       
        if ((this->switchState == LOW) && (this->prevSwitchState == HIGH)) // SWITCH PRESS
        {
            this->firstTapTime = millis();
            this->WaitForRelease = true;
           
            // PERFORM PRESS ACTION
            xTaskNotify(xTaskGetId("TASKMAN"), 4, pre);
           
            this->prevSwitchState = this->switchState;
        }
        else if (this->WaitForRelease && ((millis() - this->firstTapTime) >= 1000)) // SWITCH HOLD
        {
            this->WaitForRelease = false;
           
            // PERFORM HOLD ACTION
            xTaskNotify(xTaskGetId("TASKMAN"), 4, hold);
        }
        else if (this->switchState == HIGH && this->prevSwitchState == LOW) // SWITCH RELEASE
        {
            if (this->WaitForRelease)
            {
              this->firstReleaseTime = millis();
           
              // PERFORM RELEASE ACTION
              xTaskNotify(xTaskGetId("TASKMAN"), 4, rel);
           
              this->WaitForRelease = false;
            }
            this->prevSwitchState = this->switchState;
        }
    }
    this->prevButtonState = buttonState;
}



niektb

It's been a while but I finally got the new box. Looks beautifulll  ;D ::)



I'm also working on a branch that supports the Boss RC-500 looper (which is the pedal the design originally was meant for, as that's the pedal my brother has)

niektb

Just a small update, the author of the RTOS I used, has listened to my feedback and implemented a good deal of changes which means I can greatly simplify my code... Update incoming!

potul


Sweetalk

Why don't you use the arduino MIDI library?, is pretty easy.

niektb

Quote from: Sweetalk on March 27, 2022, 05:18:44 AM
Why don't you use the arduino MIDI library?, is pretty easy.
Hmm right now it's one function with 2 statements inside, can it get much easier?  ;D
plus using a library would increase the memory usage :)

niektb

This is the code that you would end up with! That is, i'm still thinking about something to minimize the definition of the footswitches (that is, put the default values somewhere else/define it only once) but this how it would roughly look :)
Unfortunately I don't have a device around here to test it in practice so it might contain some mistakes  :icon_eek:


#include <HeliOS.h>

/* PIN DEFINITIONS */
const uint8_t pin_sw1  = 4;
const uint8_t pin_sw2  = 3;
const uint8_t pin_sw3  = 2;

/* DEBOUNCE PARAMETERS */
const uint8_t debounce_delay = 20;

// SWITCH
// Define struct to pass to task
typedef struct footsw {
    int port;
    bool sstate;
    bool last_sstate;
    bool last_button;

    unsigned long ldt; // last debounce time
    unsigned long ftt; // first tap time
    unsigned long frt; // first release time
    bool wfr;  // wait for release
    char sw_press[4];
    char sw_hold[4];
    char sw_release[4];
} footsw;

// MIDI PARAM
const uint8_t fav_preset = 7; // preset 8
uint8_t current_preset = fav_preset;
const int PC = 192;
//const int CC = 176;

// Send MIDI message
void MIDIPC(int COMMAND, int DATA) {
  Serial.write(COMMAND);
  Serial.write(DATA);
}

/* TASKS */
void taskSW_main(xTask task_, xTaskParm parm_)
{
  footsw sw = DEREF_TASKPARM(footsw, parm_);

  int button = digitalRead(sw.port);
  // reset debouncing timer if the switch changed, due to noise or pressing:
  if (button != sw.last_button)
    sw.ldt = millis();

  // If the button state is stable for at least [debounce_delay], fire body of the statement
  if ((millis() - sw.ldt) > debounce_delay) {

    // Button and last_button represent the 'unstable' input that gets updated continuously.
    // These are used for debouncing.
    // sstate is the stable input that can be used for reading button presses.
    if (button != sw.sstate)
      sw.sstate = button;

    if (sw.sstate == LOW && sw.last_sstate == HIGH) // SWITCH PRESS
    {
      sw.ftt = millis();
      sw.wfr = true;

      // PERFORM PRESS ACTION
      xTaskNotifyGive(xTaskGetHandleByName("TASKMAN"), 4, sw.sw_press);
     
      sw.last_sstate = sw.sstate;
    }
    else if (sw.wfr && ((millis() - sw.ftt) >= 1000)) // SWITCH HOLD
    {
      sw.wfr = false;
      // PERFORM HOLD ACTION
      xTaskNotifyGive(xTaskGetHandleByName("TASKMAN"), 4, sw.sw_hold);
    }
    else if (sw.sstate == HIGH && sw.last_sstate == LOW) // SWITCH RELEASE
    {
      if (sw.wfr)
      {
        sw.frt = millis();

        // PERFORM RELEASE ACTION
        xTaskNotifyGive(xTaskGetHandleByName("TASKMAN"), 4, sw.sw_release);

        sw.wfr = false;
      }
      sw.last_sstate = sw.sstate;
    }
  }
  sw.last_button = button;

  DEREF_TASKPARM(footsw, parm_) = sw;
  return;
}

void taskSerial_main(xTask task_, xTaskParm parm_)
{
  xTaskNotification res = xTaskNotifyTake(task_);
  if (res)
    Serial.println(res->notificationValue);
  xMemFree(res);
}

void taskMan_main(xTask task_, xTaskParm parm_)
{
  xTaskNotification res = xTaskNotifyTake(task_);
  if (res)
  {
    // Perform task if we got a notification
    if (strcmp(res->notificationValue, "PRE1") == 0)
    {
      // Press Switch 1
      if (current_preset > 0)
        current_preset--;
      else
        current_preset = 23; 
     
      MIDIPC(PC, current_preset);
    }
    else if (strcmp(res->notificationValue, "PRE2") == 0)
    {
      // Press Switch 2
        current_preset = fav_preset;
     
      MIDIPC(PC, current_preset);
    }
    else if (strcmp(res->notificationValue, "PRE3") == 0)
    {
      // Press Switch 3
      if (current_preset < 23)
        current_preset++;
      else
        current_preset = 0;
       
      MIDIPC(PC, current_preset);
    }
    //xTaskNotifyGive(xTaskGetHandleByName("TASKSERIAL")), 4, res->notificationValue);
  }
  xMemFree(res);
}

void setup()
{
  Serial.begin(31250);
  //Serial.println(F("[MIDI Master Debug Stream]"));

  // PIN SETUP
  pinMode(pin_sw1, INPUT);
  pinMode(pin_sw2, INPUT);
  pinMode(pin_sw3, INPUT);

  delay(3000); // Compensate for slow start Line6 M5
  MIDIPC(PC, current_preset);

    // initialize struct for switch 1
    footsw SW1 = {
        pin_sw1,    //port
        HIGH,       //sstate
        HIGH,       //last_sstate
        HIGH,       //last_button
        0,          //ldt
        0,          //fft
        0,          //frt
        false,      //wfr
        (char *)"PRE1", //press
        (char *)"HOL1", //hold
        (char *)"REL1"  //release
    };
    xTask taskSW1 = xTaskCreate("TASKSW1", taskSW_main, &SW1);

    // initialize struct for switch 2
    footsw SW2 = {
        pin_sw2,    //port
        HIGH,       //sstate
        HIGH,       //last_sstate
        HIGH,       //last_button
        0,          //ldt
        0,          //fft
        0,          //frt
        false,      //wfr
        (char *)"PRE2", //press
        (char *)"HOL2", //hold
        (char *)"REL2"  //release
    };
    xTask taskSW2 = xTaskCreate("TASKSW2", taskSW_main, &SW2);

    // initialize struct for switch 1
    footsw SW3 = {
        pin_sw3,    //port
        HIGH,       //sstate
        HIGH,       //last_sstate
        HIGH,       //last_button
        0,          //ldt
        0,          //fft
        0,          //frt
        false,      //wfr
        (char *)"PRE3", //press
        (char *)"HOL3", //hold
        (char *)"REL3"  //release
    };
    xTask taskSW3 = xTaskCreate("TASKSW1", taskSW_main, &SW3);

    //xTask taskSerial = xTaskCreate("TASKSERIAL", taskSerial_main, NULL);
   
    xTask taskMan = xTaskCreate("TASKMAN", taskSW_main, NULL);

    // Check if all tasks are created correctly
    if (taskSW1 && taskSW2 && taskSW3 && taskMan /*&& taskSerial*/) {
        xTaskResume(taskSW1);
        xTaskResume(taskSW2);
        xTaskResume(taskSW3);
        xTaskWait(taskMan);
        //xTaskWait(taskSerial);

        xTaskStartScheduler();

        xTaskDelete(taskSW1);
        xTaskDelete(taskSW2);
        xTaskDelete(taskSW3);
        xTaskDelete(taskMan);
        //xTaskDelete(taskSerial);
    }
    xSystemHalt();
}

void loop()
{
  // Should remain empty
}

pruttelherrie

Quote from: niektb on March 28, 2022, 04:10:17 AM
using a library would increase the memory usage :)

That does not really follow. Especially not when you're using an RTOS for convenience?

That said, if one is only sending Program Changes a couple of Serial.write() is all you need...