DIY Axe-Fx Foot Controller for $50 (Open Project)

Yes, the same old ring binder. I've been stomping it for almost 2 years (only at home) and it is still alive. But now I've found a band in Bangkok to play live, so I have to seriously reconsider its resilience :rolleyes:

I am going to rescue an old Behringer FCB1010 (I was not using it because it is too large for my bedroom). I will study if there is extra space to drill,to add extra foot-switches, and embed my arduino controller inside.
I saw somebody who sawed the pedal section off of an FCB1010... You could probably shorten it if you don't need the pedals.

Edit:

Quick Google search found one:

https://www.kemper-amps.com/forum/i...move-built-in-expression-pedals-change-EPROM/
 
Here is my first draft to read the preset name and the scenes names from the Axe-FX III, (according to the Axe-FX III Third Party Midi Specs https://forum.fractalaudio.com/threads/axe-fx-iii-third-party-midi-spec.140602/ )

I want to add one display per footswitch, to show at the pedalboard what is on each scene.

I don't have the Axe here and cannot test it until next week, so there may be many bugs. I know it is premature to share it, but just in case that someone wants to test and debug it right now :rolleyes:

Code:
/edit: I delete this useless aberration
}
 
Last edited:
Niiiiice, he's coding again :)

But having coded my own midi controller I strongly advise you not to use delay()! If e.g. you want to display the tuner (which is quite easy!), delay() causes trouble over trouble.

I'm in the process of finishing my project, will take at least another month, more realistically it's two months. I actually want to wait until then to release my code, but if you want you can have my beta version. Just PM me :)
 
Back to basis. I've spent several hours with this, but I still cannot remember how to handle sysex data. Must be the age... :oops:

I need help!

I have reduced the code to the minimum: just few lines to query a Scene name and print it at the Serial Monitor.

First I define the query for Scene #1 name

Code:
byte QuerySceneName1[8] = {0xF0,0x00,0x01,0x74,0x10,0x0E,0x01,0xF7};
// Returns F0 00 01 74 10 0E dd dd dd … cs F7; where dd dd dd … is 32 characters of name, cs = checksum.

Then I send the query
Code:
MIDI.sendSysEx(8,QuerySceneName1)

I print the length of the received sysex
Code:
            length = MIDI.getSysExArrayLength();
            Serial.print("SysExArrayLength = ");Serial.println (length);

And I print the raw HEX contents of the received sysex
Code:
            Serial.print(" Data: 0x");
            data_p = MIDI.getSysExArray();
            for (uint16_t idx = 0; idx < length; idx++)
            {
              Serial.print(data_p[idx], HEX);
              Serial.print(" 0x");
            }
            Serial.println();

But what I get on the Serial Monitor doesn't make sense to me:

Axe-FX III SysEx Requests debug start
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 0
Data: 0x
SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x1 0x7E 0xF7 0x
SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x1 0x7E 0xF7 0x
SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x1 0x7E 0xF7 0x
SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x1 0x7E 0xF7 0x
SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x1 0x7E 0xF7 0x
SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x1 0x7E 0xF7 0x
SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x1 0x7E 0xF7 0x
SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x1 0x7E 0xF7 0x
SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x0 0x7E 0xF7 0x
.....

Why does it start receiving data only after the 10th reiteration?
Why 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x0 0x7E 0xF7?


Here is the complete code:

Code:
///Axe-FX III Midi Controller Rev02
///Test SysEx to query Preset and Scene names
///M.Guerrero 26-Aug-2018
#include <MIDI.h>
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

char SceneName1[32];

uint16_t length;
const uint8_t * data_p;

byte QuerySceneName1[8] = {0xF0,0x00,0x01,0x74,0x10,0x0E,0x01,0xF7};
// Returns F0 00 01 74 10 0E dd dd dd … cs F7; where dd dd dd … is 32 characters of name, cs = checksum.

void setup() {
        Serial.begin(115200);
        MIDI.begin(0);
        MIDI.turnThruOff();
        Serial.println ("Axe-FX III SysEx Requests debug start");
}

void loop() {
        MIDI.read();

        MIDI.sendSysEx(8,QuerySceneName1);delay(50);

            length = MIDI.getSysExArrayLength();
            Serial.print("SysExArrayLength = ");Serial.println (length);

            Serial.print(" Data: 0x");
            data_p = MIDI.getSysExArray();
            for (uint16_t idx = 0; idx < length; idx++)
            {
              Serial.print(data_p[idx], HEX);
              Serial.print(" 0x");
            }
            Serial.println();
                  delay(1000);
}

I would appreciate any help. Then I will continue the work and I will post the results if I get a working version.

I have asked at the Arduino Forum, but they are reluctant to answer "silly" beginner questions.
 
I'm also a "silly" beginner ;) so I might be wrong here.

I use callbacks for receiving sysex data (tuner + tempo led). This only works, when MIDI.read() is called as often as possible (that's why in my code you see this in every other line in the void(loop) so the time gap between midi readings is as short as possible). Otherways the arduino "misses" sysex data. That's the first thing that came to mind when I saw your output.

In case I read your code correctly, you send the sysex command (request for the scene name), wait for 50ms (Why 50ms? Result of trial and error?) and then try to work with the received data. Then you wait for 1 second and do the same thing again.
I'm actually wondering how your code manages to actually "see" the incoming sysex data! The Axe receives the request and answers as fast as it can. Let's say it takes the Axe 20ms to respond. But you wait for 50ms which means (afaik) that the Axe's answer is long gone and cannot be received by your code. There might be some buffering in the Arduino's serial interface, but with your code the timing has to be very precise ... too precise imho.

With callbacks this would be different. You put MIDI.setHandleSystemExclusive(handleSysEx) in the setup function and put all the stuff for handling the sysex data in a separate handleSysEx() function. This function is called every time sysex data is being received by the MIDI.read() call. If this MIDI.read() is called every 0.x ms (absolutely possible if void(loop) contains MIDI.read() and nothing else!), there's a big chance to actually receive the incoming message.

If I had an Axe III I'd love to build something together with you, unfortunately I don't and won't in the near future. But I have my Axe II and could do something similar: Receiving and displaying preset names :) Maybe I'll write something next week, pretty busy, but coding is fun and like I said in the PM, I'm already forgetting things, so this would be a nice practice exercise!

For now I tried to rewrite your code. It compiles, but I don't know if it works.
Code:
///Axe-FX III Midi Controller Rev02
///Test SysEx to query Preset and Scene names
///M.Guerrero 26-Aug-2018
#include <MIDI.h>
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

char SceneName1[32];

uint16_t length;
const uint8_t * data_p;

byte QuerySceneName1[8] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x01, 0xF7};
// Returns F0 00 01 74 10 0E dd dd dd … cs F7; where dd dd dd … is 32 characters of name, cs = checksum.

unsigned long timing = 0;

void setup() {
  Serial.begin(115200);
  MIDI.begin(0);
  MIDI.turnThruOff();
  Serial.println ("Axe-FX III SysEx Requests debug start");
  MIDI.setHandleSystemExclusive(handleSysEx);
}

void loop() {
  MIDI.read(); //Now this is being called VERY often, since the loop hardly does anything else ;-)
  
  //This is supposed to be a substitute for delay(1000) which doesn't interrupt the loop.
  if (millis() - timing > 1000) {
    MIDI.sendSysEx(8, QuerySceneName1);
    timing = millis();
  }
}

void handleSysEx() {
  length = MIDI.getSysExArrayLength();
  Serial.print("SysExArrayLength = "); Serial.println (length);

  Serial.print(" Data: 0x");
  data_p = MIDI.getSysExArray();
  for (uint16_t idx = 0; idx < length; idx++)
  {
    Serial.print(data_p[idx], HEX);
    Serial.print(" 0x");
  }
  Serial.println();
}
 
OK. Now I am receiving constant data. But I am still not getting the sysex containing the 32 characters of the name. It is returning this:

SysExArrayLength = 10
Data: 0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x0 0x7F 0xF7


I note that I've made a mistake on the definition of QuerySceneName1.

According to the documentation, it should be:

Message format: F0 00 01 74 10 0E dd cs F7; where dd is desired scene. cs is the XOR checksum.
To return the current scene name dd = 7F.

Returns: F0 00 01 74 10 0E dd dd dd … cs F7; where dd dd dd … is 32 characters of name.

Like this:
Code:
byte QuerySceneName1[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x01, 0xcs, 0xF7};

What would be cs? The checksum of the previous string F0 00 01 74 10 0E 01?
 
Last edited:
Not working.

I've tried the checksum of F0 00 01 74 10 0E 01. cs=7C
and the checksum of F0 00 01 74 10 0E 01 F7. cs = 85

But I don't get the 32 characters of name. Only this reply:

0xF0 0x0 0x1 0x74 0x10 0x64 0xE 0x0 0x7F 0xF7
 
First stage completed!.:) I can partially read one scene name.

And I say partially because the first 3 positions are strange characters. I don't know why

Anyway,enough coding for today. I am going to play guitar.

The next stage will be to figure out how to read the names of the 8 scenes and the preset name, and how to refresh them when there is a change, either from the Axe-FX front panel or from the foot controller.

Here is the latest version of the code:
Code:
///Axe-FX III Midi Controller Rev02
///Test SysEx to query Preset and Scene names
// Debug version. So far this is only reading Scene1 name
///M.Guerrero 27-Aug-2018
#include <MIDI.h>
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

char PresetName[32];
char SceneName1[32];
char SceneName2[32];
char SceneName3[32];
char SceneName4[32];
char SceneName5[32];
char SceneName6[32];
char SceneName7[32];
char SceneName8[32];

uint16_t length;
const uint8_t * data_p;

byte QueryPresetName[10] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0D, 0x7F, 0x7F, 0x18, 0xF7};
// Returns: F0 00 01 74 10 0D dd dd dd … cs F7; where dd dd dd … is 32 characters of name.
byte QuerySceneName1[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x00, 0x1B, 0xF7};
byte QuerySceneName2[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x01, 0x1A, 0xF7};
byte QuerySceneName3[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x02, 0x19, 0xF7};
byte QuerySceneName4[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x03, 0x18, 0xF7};
byte QuerySceneName5[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x04, 0x1F, 0xF7};
byte QuerySceneName6[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x05, 0x1E, 0xF7};
byte QuerySceneName7[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x06, 0x1D, 0xF7};
byte QuerySceneName8[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x07, 0x1C, 0xF7};
// Returns: F0 00 01 74 10 0E dd dd dd … cs F7; where dd dd dd … is 32 characters of name

unsigned long timing = 0;

void setup() {
  Serial.begin(115200);
  MIDI.begin(0);
  MIDI.turnThruOff();
  Serial.println ("Axe-FX III SysEx Requests debug start");
  MIDI.setHandleSystemExclusive(HandleSysEx);
}

void loop() {
  MIDI.read(); //Now this is being called VERY often, since the loop hardly does anything else ;-)
 
  //This is supposed to be a substitute for delay(1000) which doesn't interrupt the loop.
  if (millis() - timing > 5000) {
    MIDI.sendSysEx(9, QuerySceneName1);
    Serial.println (">>>>>> QuerySceneName1 Request sent");
    timing = millis();
  }
}

// parse SceneName1 name from sysex data
void parseName(const byte * sysex, int l) {
    // reset char array to spaces
    for(byte i = 0x00; i < 0x1F; i++) {
        SceneName1[i] = 0x20;
    }
    // get sysex data into char array pname
    Serial.println("Scene 1 name: ");
    for(byte i = 0x00; i < 0x1F; i++) {
        //char p = sysex[i + 6];
        SceneName1[i] = sysex[i + 6];
        Serial.print (SceneName1[i]);
       }
        Serial.println("");
}

void HandleSysEx(byte *SysExArray, unsigned int size) {
  length = MIDI.getSysExArrayLength();
  const byte *sys = MIDI.getSysExArray();
  Serial.print("SysExArrayLength = "); Serial.println (length);
  parseName(sys,length);

  data_p = MIDI.getSysExArray();
  for (uint16_t idx = 0; idx < length; idx++)
  {
    Serial.print(data_p[idx], HEX);
       Serial.print(" ");
  }
  Serial.println();
}
 
A challenge with the Axe-FX III that we didn't have with the Axe-FX II:

When receiving sysex messages, we can identify them by the 6th byte of the array (at the Axe-FX II it was the 5th):

0C = current scene
0D = patch name
0E = scene name
0F = looper state

But on the scene names, how can we identify to which scene they are referred?

When all scene names are queried, how can we associate the returned messages with their correspondent scene, if they all have the same header? {F0 00 01 74 10 0E dd dd... cs F7}
 

Attachments

  • Axe-Fx III MIDI for 3rd Party Devices.pdf
    215.4 KB · Views: 6
I don't know if this is orthodox, but I've trusted that the 8 messages with the scene names will come one after another, and added an incremental counter (SceneCounter). With this I can read the preset name and the 8 scenes names :)

Here is the code:
Code:
///Axe-FX III Reader Rev04
///Test SysEx to query Preset and Scene names
// Debug version.
///M.Guerrero 28-Aug-2018
#include <MIDI.h>
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

char PresetName[32];
char SceneName1[32];
char SceneName2[32];
char SceneName3[32];
char SceneName4[32];
char SceneName5[32];
char SceneName6[32];
char SceneName7[32];
char SceneName8[32];
int SceneCounter = 0;

uint16_t length;
const uint8_t * data_p;

byte QueryPresetName[10] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0D, 0x7F, 0x7F, 0x18, 0xF7};
// Returns: F0 00 01 74 10 0D dd dd dd … cs F7; where dd dd dd … is 32 characters of name.
byte QuerySceneName1[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x00, 0x1B, 0xF7};
byte QuerySceneName2[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x01, 0x1A, 0xF7};
byte QuerySceneName3[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x02, 0x19, 0xF7};
byte QuerySceneName4[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x03, 0x18, 0xF7};
byte QuerySceneName5[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x04, 0x1F, 0xF7};
byte QuerySceneName6[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x05, 0x1E, 0xF7};
byte QuerySceneName7[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x06, 0x1D, 0xF7};
byte QuerySceneName8[9] = {0xF0, 0x00, 0x01, 0x74, 0x10, 0x0E, 0x07, 0x1C, 0xF7};
// Returns: F0 00 01 74 10 0E dd dd dd … cs F7; where dd dd dd … is 32 characters of name

unsigned long timing = 0;

void setup() {
  Serial.begin(115200);
  MIDI.begin(0);
  MIDI.turnThruOff();
  Serial.println ("Axe-FX III SysEx Requests debug start");
  MIDI.setHandleSystemExclusive(HandleSysEx);
}

void loop() {
  MIDI.read(); //Now this is being called VERY often, since the loop hardly does anything else ;-)
 
  //This is supposed to be a substitute for delay(1000) which doesn't interrupt the loop.
  if (millis() - timing > 10000) {
        SceneCounter = 1;
    MIDI.sendSysEx(10, QueryPresetName);
    MIDI.sendSysEx(9, QuerySceneName1);
    MIDI.sendSysEx(9, QuerySceneName2);
    MIDI.sendSysEx(9, QuerySceneName3);
    MIDI.sendSysEx(9, QuerySceneName4);
    MIDI.sendSysEx(9, QuerySceneName5);
    MIDI.sendSysEx(9, QuerySceneName6);
    MIDI.sendSysEx(9, QuerySceneName7);
    MIDI.sendSysEx(9, QuerySceneName8);
    Serial.println("----------------------");
  timing = millis();
  }
}

void HandleSysEx(byte *SysExArray, unsigned int size) {
  length = MIDI.getSysExArrayLength();
  const byte *sys = MIDI.getSysExArray();

  if(SysExArray[5]==0x0D) {       // 0DE = Identifier for Preset Name
  data_p = MIDI.getSysExArray();
  //  for (uint16_t idx = 0; idx < length; idx++)
 // {
 //   Serial.print(data_p[idx], HEX);
 //     Serial.print(" ");
 // }
 // Serial.println();
      Serial.print("Preset Name: ");                
      for(byte i = 0x00; i < 0x1F; i++) { // parse Preset Name name from sysex data
       PresetName[i] = data_p[i + 6];
        Serial.print (PresetName[i]);
       }
        Serial.println("");
  }

  if(SysExArray[5]==0x0E) {       // 0E = Identifier for Scene Name

  data_p = MIDI.getSysExArray();
 // for (uint16_t idx = 0; idx < length; idx++)
 // {
 //   Serial.print(data_p[idx], HEX);
 //     Serial.print(" ");
 // }
 // Serial.println();
      Serial.print("Scene ");Serial.print(SceneCounter);Serial.print(": ");        
      for(byte i = 0x00; i < 0x1F; i++) { // parse SceneName1 name from sysex data
       SceneName1[i] = data_p[i + 6];
        Serial.print (SceneName1[i]);
       }
        Serial.println("");
    SceneCounter = SceneCounter + 1;
  }
}

And the output:

Preset Name: Cameron Atomica & CCV100 #1
Scene 1: Atomica High - lower gain
Scene 2: Atomica High - Eruption-ish
Scene 3: Atomica Low w/detune
Scene 4: Atomica Low 2 w/Reverb
Scene 5: CCV-100 Ch1A - clean
Scene 6: CCV-100 Ch1A - grit clean
Scene 7: CCV-100 Ch1B breakup
Scene 8: CCV-100 Ch1B w/ pitch, delay
----------------------
 
Love this thread @Piing! I only came across it because it was in your signature, otherwise would not have seen it.

I'm busy with another Arduino project, but this is definitely my next one (and then I might be able to sell my MFC101 until the new FCs are released).

How is your file holding out with the gigs?
 
I have Liquid Foot midi controllers but to be honest, my eye sight is so bad, I can't read the little LCD's on the buttons! I'd be interested in having a larger enclosure, with very large displays for each button. This would serve another purpose - having each button further apart from one another.

On stage, I find it too easy to graze the button beside the one I really intended to step on, because the buttons are very close to each other. I literally have switched to narrower sneakers (converse) to minimize this issue. I know of other users who literally skip rows of buttons and use every other row, for the same reason. I need all my buttons, so I can't do that.

But, yeah, having a display above each button, that is like 3x5 index card, with huge letters, so I can actually read it, would be spectacular.

The other workaround in the short term would be to have a monitor or iPad, at my pedalboard, with a pictographical reproduction of my pedalboard, so I could read the buttons, and then step on the correct one at the pedalboard. But I'd need the display to chase my pedalboard for each song, and update for each song. And I don't know how to do that! I thought about making powerpoint slides that show the button configuration for each song, but again, I don't know how to link the presentation on an iPad, to my rack system, so it updates as we go through our setlist, and display the right buttons and the right names on the buttons.
 
Love this thread @Piing! I only came across it because it was in your signature, otherwise would not have seen it.

I'm busy with another Arduino project, but this is definitely my next one (and then I might be able to sell my MFC101 until the new FCs are released).

How is your file holding out with the gigs?

It is still alive. I am surprised of how resilient a 2-ring binder can be.
And I am also surprised of how lazy I can be to build a final version in a solid chassis, adding the individual OLED displays per switch :p

Recent pictures:



 
Been following this with interest. Love the DIY look, although I'm surprised it's surviving gigs!

Wondering if you have the code up on GitHub or anything like that? I'm starting to put together code for a similar project - arduino based controller for the AxeFx III. Tossing up between LCD vs OLED display at the moment. I'll throw my code on GitHub once I get a working prototype going.
 
I'm currently building a case for my controller. Built from ground up from pure steel plates etc. :) I'll soon provide technical drawings, CAD files, pictures, the code etc. Maybe I'll upload it to github or another provider. I hope I'll manage to finish it this month. It's quite a task I have to tell you, especially if you've never done anything close to this. Big learning experience!!!
 
Want a little teaser? :D That's what it looks like right now.
Controller-front.jpg

Controller-back.jpg

Sorry, I don't intend to hijack your thread @Piing ;)
 
Back
Top Bottom