How to program own Wifi "Mute" Stomp Box to remote control a Behringer X32 Rack?

679 Views Asked by At

I´m totally new to coding, this is even my first post here. Im tryng this because nobody sells what I want/need ;-).

I achived already quite a bit, but at this moment I´m getting lost with a lot of things (I read a lot about coding in general and in special with Arduino the last 8 dayas)... but let me explain first what my intention on this project is:

I want to build a "Stomp Box" to mute a Behringer X32 Rack (wireless) Channels/Mutegroups/Buses, just Mute On/Off.. nothing else.

This Box should have 4-6 "stompers" (buttons), each of this buttons should have a different Mute function.

Also the current state of the Channel/Mutegroup/Bus should be indicated by LED´s green if unmuted or red if muted.
Therfore the box needs to evaulate the current state of the designated Channel/Mutegroup/Bus, because it could change also from other remote devices.
And then switch to the opposite state when pressing/stomping on designated button.

I´d like to have code where I can easily change the action of a button, Like:

button1 = /ch/01/mix/on ,i 1

button2 = /config/mute/1 ,i 1

button3 = /dca/1/on ,i 1

so in case I need a differnt Channel/Mutegroup/Bus for another event simply edit and recode my ESP32 Node Kit

So here is my code I already have:

#include "WiFi.h"
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <SPI.h>    
#include <OSCMessage.h> //https://github.com/CNMAT/OSC

#define WIFI_NETWORK "xxxxxxxxxx"    //SSID of you Wifi
#define WIFI_PASSWORD "xxxxxxxxxxx"  //Your Wifi Password
#define WIFI_TIMEOUT_MS 20000        // 20 second WiFi connection timeout
#define WIFI_RECOVER_TIME_MS 30000   // Wait 30 seconds after a failed connection attempt

int muteOn = 0;// 0=Mute
int muteOff = 1;// 1=Unmute
int input;
WiFiUDP Udp;

const IPAddress outIp (192, 168, 10, 129);  //Mixers IP
const unsigned int outPort = 10023;         //X32 Port

//variables for blinking an LED with Millis
const int led = 2; // ESP32 Pin to which onboard LED is connected
unsigned long previousMillis = 0;  // will store last time LED was updated
const long interval = 300;  // interval at which to blink (milliseconds)
int ledState = LOW;  // ledState used to set the LED

void connectToWiFi(){
  Serial.print("Zu WLAN verbinden...");
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_NETWORK, WIFI_PASSWORD);

  unsigned long startAttemptTime = millis();
  
  while(WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < WIFI_TIMEOUT_MS){
   Serial.println(".");
   delay(100);
   }
  if(WiFi.status() != WL_CONNECTED){
    Serial.println("Nicht Verbunden!");
    //optional take action
  }else{
    Serial.print("WLAN Verbunden mit ");
    Serial.println(WIFI_NETWORK);
    Serial.println(WiFi.localIP( ));
  }
}

void setup() {
    Serial.begin(115200);
    connectToWiFi();
    Udp.begin(8888);
    pinMode(led, OUTPUT);
    
  // Port defaults to 3232
  // ArduinoOTA.setPort(3232);

  // Hostname defaults to esp3232-[MAC]
  // ArduinoOTA.setHostname("myesp32");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}


void loop(){
  ArduinoOTA.handle();
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
  // save the last time you blinked the LED
  previousMillis = currentMillis;
  // if the LED is off turn it on and vice-versa:
  ledState = not(ledState);
  // set the LED with the ledState of the variable:
  digitalWrite(led,  ledState);
  }
  
  input=Serial.read();  
        if (input=='0'){
        // welcher status hat der kanal?
        // wenn Kanal gemutet dann unmute und umgekehrt
            Serial.println("Mute!");
            delay(100);
            sendMute();  //send Mute to Mixer
            Serial.println("...");
        }
        if (input=='1'){
            Serial.println("UnMute!");
            delay(100);
            sendUnMute();
            Serial.println("...");
        }
}       

void sendMute() {
    //the message wants an OSC address as first argument
    OSCMessage msg("/ch/01/mix/on");
    msg.add(muteOn);
              
    Udp.beginPacket(outIp, outPort);
    msg.send(Udp); // send the bytes to the SLIP stream
    Udp.endPacket(); // mark the end of the OSC Packet
    msg.empty(); // free space occupied by message

    delay(20);
}

void sendUnMute() {
    //the message wants an OSC address as first argument
    OSCMessage msg("/ch/01/mix/on");
    msg.add(muteOff);
              
    Udp.beginPacket(outIp, outPort);
    msg.send(Udp); // send the bytes to the SLIP stream
    Udp.endPacket(); // mark the end of the OSC Packet
    msg.empty(); // free space occupied by message

    delay(20);
}  

So I testet this via serial Monitor, when I input "0" and click send, the mixer mutes channel 1 and on input "1" channel 1 becomes unmuted, so far so good... (OSCMessage msg("/ch/01/mix/on"); ... section.

What bothers me here in special is, I had to hardcode the command "/ch/01/mix/on", because I am not able to declare a variable? for this string? I am already so confused that I don´t know if I even have the terms right :-(

BTW: There are a lot solutions out there how to do it with MIDI, but MIDI is not wireles and I think for my project overkill. I also did some some research on github.com/CNMAT/OSC but I don´t get it... (crying)...
I found also a post here, but this didn´t helped either... :-(

Any advice on that how I can reach my goal?--

Any help is much apprceiated... even in German (my native language... )

PS: Yes I´m a begginner and I admit it. But at least I managed how to connect and flash this thing even via OTA in the last 8 days, so please be easy on me.

2

There are 2 best solutions below

0
On

Not wanting to hardcode your commands is a good instinct.

The Arduino language is C++, which is (mostly) a superset of C. C and C++ use a preprocessor which lets you define constants and test for their presence.

For instance, you could write:

#define CHAN01_MIX_ON_COMMAND "/ch/01/mix/on"

and then use CHAN01_MIX_ON_COMMAND anywhere you want to use that constant, like so:

void sendMute() {
    //the message wants an OSC address as first argument
    OSCMessage msg(CHAN01_MIX_ON_COMMAND);

Then if you ever need to change the string "/ch/01/mix/on" you can just change it in one location and not worry about finding every instance of it in your code.

Writing the names in #define statements is a convention people usually follow in order to make it more clear that they're constants.

You have to write the #define line before you use the constant you defined, so putting it at the start of the file (after any #include lines and before your first function) is a good practice. Or if you have several you might put them all in their own file called something like commands.h (the .h means header file)and then include that at the start of any file that needs it like so:

#include "commands.h"

This #include statement would insert the contents of the file commands.h into the file that the statement is in.

When you have several #define statements, keeping them all together in one place (whether it's at the top of the file or in their own file) is also a good practice so that you have one central place to find them and update them if you need to.

Some people will assign the string constant to a variable like so:

char *channel01_mix_on_cmd = "/ch/01/mix/on";

Here char means "a character" - like one letter or number or symbol. The * means pointer to, which lets you use an array of characters. Simple strings in C and C++ are just arrays of characters (or a pointer to the first character), with a special hidden character at the end set to numeric value 0 (not the character '0'). C++ also has a string datatype called std::string and Arduino programs have String but those are both overkill here. They all let you work with strings; String is much easier to use than char * but both have strengths and weaknesses.

Like the #define, you'd also place that outside a function near the start of the file. It defines a global variable that would be available to any function that references it.

You'd also use the variable anywhere they want the string. It's the same idea as using #define, just done slightly differently. For instance:

void sendMute() {
    //the message wants an OSC address as first argument
    OSCMessage msg(channel01_mix_on_cmd);

Using a variable here is an attempt to save storage by not having multiple copies of the string. It's not necessary; C/C++ compilers have for a very long time detected this and stored only one copy of the string. It might save space if your code is split into multiple files.

Saving space on CPUs like the ESP32 and ESP8266 is important because they have so little memory. #define is fine here because the compiler does it automatically for you.

0
On

You can create the command string with sprintf. so for example:

#define CHANNELON "on"
#define CHANNELOFF "off"
  
int channel;
int mute;
char messageString[100]; 
      
// some code that calculates the channel number and the mute state:
channel = 1;
mute = 1;
      
// then check the mute state and create the command string:
if (mute)
{
  // to turn off a channel:
  sprintf(messageString,"/ch/%02d/mix/%s",channel,CHANNELOFF);
}
else
{
  // to turn on a channel:
  sprintf(messageString,"/ch/%02d/mix/%s",channel,CHANNELON);
}

// send the command:
OSCMessage msg(messageString);

the %02d will substitute an integer with a zero in front, if it's smaller than 10 and that is always 2 characters long. so if channel is 1, the result would be 01