NEED - SIMPLE Guidance on sending a MIDI command..

For my son I am trying to build a Physical analog fader interface for QLC (sending midi commands)

I have got a 64ch multiplexer reading 64 inputs... That was straight forward.

But it's the impenetrable world of MIDI that I am struggling to grasp and the myriad of technical guides are not simple and the Arduino Project guides usually over complex, or they avoid telling you which library is needed, or any other simple information...

All I need is the bare bones to perform...

Read Analog pin A0
Write value of Analog A0 to a DMX Control Command for fader A1
Read Analog pin A1
Write value of Analog A1 to a DMX Control Command for fader A2

Ideally, just over the serial USB so that HairLess(I wonder why it got that name!!!) can receive the data.

After that it should all fit into place...

Thanks

Robin

You could take a look at my Arduino Control Surface library, or its predecessor, the Arduino MIDI Controller library.

Here's a "getting started" guide.
As you can see, it supports multiplexers out of the box. It also does things like filtering the analog inputs.

If you want to know more about how MIDI works, and just write code without any extra libraries, you can check out this MIDI guide

Pieter

to a DMX Control Command for fader A1

Well do you know what MIDI command will do that? Because it could be anything. Most probably it is the CC ( continuous controller ) message 0xBc, where c is the 4 bit channel number.
This is followed by two bytes with the most significant bit zero ( that is a number between 0 and 127 ), the first is the controller channel you want to send and the second is the value you want to send to it.

You would send this by using this function:-

sendCC(byte channel, byte number, byte value){
Serial.write( 0xB0 | channel);
Serial.write(number);  // controller number to use
Serial.write(value);  // value to set controller
}

You call this function with the MIDI channel you want to send it on, the controller number and the value you want to send.

As the analogRead returns a 10 but number you make it into a seven bit number by shifting it to the right three places, which is the same as divide by 8 but is done faster in the computer.

So:-

sendCC(channel, controller, analogRead(pin) >> 3);

This is ideal for including in a "for" loop.

I cracked the basics of it after posting last night after posting the message..

I read this

and it made more sense what the message format was...

I surmised that a message "191" would be midi channel 16 controller,
fader 1-64 would be my fader channels,
and value 0-127 for the actual value....

And as shown above I decided to BIN all the MIDI library stuff and go to basics with just a "serial.write" nothing fancy at all....

Serial.write(191);
Serial.write(fader);
Serial.write(value);

Monitoring in Hairless showed me just that :slight_smile:

+752.807 - Serial In: Ch 16: Controller 10 value 2

So in it's absolute simplest form, this loop sends some fixed values to a number of controllers. This is bare code to show the working with no attempt at code reduction.

void setup(){
 Serial.begin(9600);
}

void loop(){
 // send a byte with the controller value for Channel 16 (binary 1011,1111)( 1011 being continuous controller function , 
 // 1111 being midi channel 16) 
 Serial.write(191);

//Specify the controller channel 
 Serial.write(1); 

// Send value 50 out of 127 (any number between 0-127 will do)
 Serial.write(50); 
 
// Repeat to send data values to other channels, 
 Serial.write(191); // send a byte with the value
 Serial.write(2); // send a byte with the value
 Serial.write(10); // send a byte with the value
 
 Serial.write(191); // send a byte with the value
 Serial.write(3); // send a byte with the value
 Serial.write(127); // send a byte with the value
 
 Serial.write(191); // send a byte with the value
 Serial.write(4); // send a byte with the value
 Serial.write(89); // send a byte with the value
 
 Serial.write(191); // send a byte with the value
 Serial.write(5); // send a byte with the value
 Serial.write(87); // send a byte with the value
 
 Serial.write(191); // send a byte with the value
 Serial.write(6); // send a byte with the value
 Serial.write(10); // send a byte with the value
 Serial.write(191); // send a byte with the value
 Serial.write(7); // send a byte with the value
 Serial.write(90); // send a byte with the value
 Serial.write(191); // send a byte with the value
 Serial.write(8); // send a byte with the value
 Serial.write(20); // send a byte with the value
 Serial.write(191); // send a byte with the value
 Serial.write(9); // send a byte with the value
 Serial.write(98); // send a byte with the value

// We will wait for half a second
 delay(500);
// And then write out new different values to the same chanels 

 Serial.write(191); // send a byte with the value
 Serial.write(1); // send a byte with the value
 Serial.write(10); // send a byte with the value

 Serial.write(191); // send a byte with the value
 Serial.write(2); // send a byte with the value
 Serial.write(127); // send a byte with the value

 Serial.write(191); // send a byte with the value
 Serial.write(3); // send a byte with the value
 Serial.write(16); // send a byte with the value

 Serial.write(191); // send a byte with the value
 Serial.write(4); // send a byte with the value
 Serial.write(12); // send a byte with the value

 Serial.write(191); // send a byte with the value
 Serial.write(5); // send a byte with the value
 Serial.write(127); // send a byte with the value
 Serial.write(191); // send a byte with the value
 Serial.write(6); // send a byte with the value
 Serial.write(127); // send a byte with the value
 Serial.write(191); // send a byte with the value
 Serial.write(7); // send a byte with the value
 Serial.write(127); // send a byte with the value
 Serial.write(191); // send a byte with the value
 Serial.write(8); // send a byte with the value
 Serial.write(127); // send a byte with the value
 Serial.write(191); // send a byte with the value
 Serial.write(9); // send a byte with the value
 Serial.write(127); // send a byte with the value

//wait another half second
 delay(500);
//loop back to the top.
}

And this is the bare bones of my actual full script.

It reads 8x8 Mux channels and sends them over serial to Hairless.

I had actually found the serial.write solution (credited) yesterday afternoon, but had forgotten to send myself it...

There's probably some unused bits and potentially a bit more tidying up to do..

The board I am using is fantastic, packing the 8x8 mux and an arduino together with no additional interconnection needed.

Cheers

TT

/* ====================AS-6408 Demo code====================================
// Credit inhaos + TwinTurbo 
 www.inhaos.com
 
 Hardware: AS-6408: 64 to 8 or 64 to 1 Analog Digital Multiplexer 
           http://www.inhaos.com/product_info.php?products_id=154

           UNO CORE: Arduino UNO CORE with ATMega328P
           http://www.inhaos.com/product_info.php?products_id=143
===========================================================================*/



//Port definitions
int ctr_A = 6 ;
int ctr_B = 8 ;
int ctr_C = 10 ;

int sig_Ctr_A = 5 ;
int sig_Ctr_B = 7 ;
int sig_Ctr_C = 9 ;



#define APP_RECV_BUFFER_SIZE      100

int App_RecvLen = 0;
bool App_RcvdFirstFlag = false;
bool App_RecvFinishFlag = false;
char App_AckCommand = 0;
char App_RecvBuffer[APP_RECV_BUFFER_SIZE];
int AdcDataArray[8];
int mux = 0;
int muxchan = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  //Initialize control ports
  pinMode(ctr_A, OUTPUT);
  pinMode(ctr_B, OUTPUT);
  pinMode(ctr_C, OUTPUT);
  pinMode(sig_Ctr_A, OUTPUT);
  pinMode(sig_Ctr_B, OUTPUT);
  pinMode(sig_Ctr_C, OUTPUT);

  digitalWrite(ctr_A, HIGH);
  digitalWrite(ctr_B, HIGH);
  digitalWrite(ctr_C, HIGH);
  digitalWrite(sig_Ctr_A, HIGH);
  digitalWrite(sig_Ctr_B, HIGH);
  digitalWrite(sig_Ctr_C, HIGH);

  //Adc ports initialize
}

void loop() {
  // Set the Mux
  // Set a varialble for that 
  // Read the mux/send the data over MIDI
  // Delay
  // Repeat for all MUXES.
  // Note, this can be put in a loop but it's shown here as WORKINGS

  //This sets the mux we are going to use out of the 8 available on the AS6408
  // call function SetChn(1)
  SetChn(1);
  // set a variable of mux for later use.
  mux=0;
  // read the mux lines using the function ReadAdcAll()
  ReadAdcAll();
  // Put in a little delay
  delay(200);
  
  // Repeat for the other 7 mux's
  mux=8;
  SetChn(2);
  ReadAdcAll();
  delay(200);
  mux=16;
  SetChn(3);
  ReadAdcAll();
  delay(200);
  mux=24;
  SetChn(4);
  ReadAdcAll();
  delay(200);
  mux=32;
  SetChn(5);
  ReadAdcAll();
  delay(200);
  mux=40;
  SetChn(6);
  ReadAdcAll();
  delay(200);
  mux=48;
  SetChn(7);
  ReadAdcAll();
  delay(200);
  mux=56;
  SetChn(0);
  ReadAdcAll();
  delay(200);
  }

boolean SetChn(int chn)
// SetChn sets the 3bit Multiplexer selection on the 6408 out of the 8 available Muxes.
// using the value passed by SetChn in the void loop. 
{
  switch (chn)
  {
    // if the value passed from the void loop is zero we set the 3 pins DigitalWrite High, and then stop 
    // further processing with a break.
    case 0:
      digitalWrite ( ctr_A , HIGH ) ;
      digitalWrite ( ctr_B , HIGH ) ;all the A0-A7 
      digitalWrite ( ctr_A , HIGH ) ;
      break;
   // Do the same for the other values passed, altering the 3 bit binary state of the pins to select the muxes...
   //
    case 1:
      digitalWrite ( ctr_A , HIGH ) ;
      digitalWrite ( ctr_B , HIGH ) ;
      digitalWrite ( ctr_C , LOW ) ;
      break;
    case 2:
      digitalWrite ( ctr_A , HIGH ) ;
      digitalWrite ( ctr_B , LOW ) ;
      digitalWrite ( ctr_C , HIGH ) ;
      break;
    case 3:
      digitalWrite ( ctr_A , HIGH ) ;
      digitalWrite ( ctr_B , LOW ) ;
      digitalWrite ( ctr_C , LOW ) ;
      break;
    case 4:
      digitalWrite ( ctr_A , LOW ) ;
      digitalWrite ( ctr_B , HIGH ) ;
      digitalWrite ( ctr_C , HIGH ) ;
      break;
    case 5:
      digitalWrite ( ctr_A , LOW ) ;
      digitalWrite ( ctr_B , HIGH ) ;
      digitalWrite ( ctr_C , LOW ) ;
      break;
    case 6:
      digitalWrite ( ctr_A , LOW ) ;
      digitalWrite ( ctr_B , LOW ) ;
      digitalWrite ( ctr_C , HIGH ) ;
      break;
    case 7:
      digitalWrite ( ctr_A , LOW ) ;
      digitalWrite ( ctr_B , LOW ) ;
      digitalWrite ( ctr_C , LOW ) ;
      break;
    default:
      return false;
      break;
  }
  return true;
}

void ReadAdcAll()
// Reads through the 8 mux channels on the selected mux and and initiates the message send.
{ 
  
  // Read the Mux to an array
  // Variable for the mux channel to read 0-7
  int i;
  // variable for the value read from the mux 0-1023 
  int y;
  // loop through values 0+1+1... until i = less than 8
  for(i = 0; i < 8; i++)
  {
    // fill and array the value i and the value read from Analoug A0
    AdcDataArray[i] = analogRead(A0 + i);
  }
;
  // we could actualy do away with this and the array above all together...
  // read the array and call the Serial Write.
  for(i = 0; i < 8; i++)
  {
    // Analog 1023 to MIDI 127 conversion
    // The output of the analogue read is from 0-1023, Midi understands values 0-127 . So divide the analogue value by 8
    y = AdcDataArray[i]/8;
   // the value mux was set int the main void loop,
   // we add this to i to get the correct channel in the range 1-64
   muxchan = mux + i;
   // We then send 3 values to the MIDImessage function
   // 191 we know is the CC value for the continus controller.
   // muxchan we have set above to specifiy the channel we are working on
   // y is the value determined from the mux read and division above.
    MIDImessage(191, muxchan, y); //pass values out through standard Midi Command
     }
  }



void MIDImessage(byte command, byte data1, byte data2) //pass values out through standard Midi Command
// Credit https://tttapa.github.io/Arduino/MIDI/Chap04-MIDI-Controller.html
{
   //write the values provided from ReadAdcAll above..
   Serial.write(command);
   Serial.write(data1);
   Serial.write(data2);
}

Original Post corrected..

That code is very long winded. There is a rule in programming which says whenever you find yourself writing the same thing over and over then you are not doing it right.

That long case statement can be written in just three lines using the switch variable “chn” and some bit read functions. The long sequence of writing fixed numbers to the serial port can be done in four lines. A for loop and an array of numbers you want to send.

But look at the code in post #3, did you put those smiles in? Well no you didn’t but the forum software mistook bits of your code. This is why we insist on you posting code inside code tags, which you would have known if you had bothered to read the the how to use this forum sticky post.

Grumpy_Mike:
That code is very long winded. There is a rule in programming which says whenever you find yourself writing the same thing over and over then you are not doing it right.

That long case statement can be written in just three lines using the switch variable “chn” and some bit read functions. The long sequence of writing fixed numbers to the serial port can be done in four lines. A for loop and an array of numbers you want to send.

I did say, "There's probably some unused bits and potentially a bit more tidying up to do." I have been coding for 37 years but mainly laterly in shell scripts. Managed to avoid "C" entirely outside the Arduino..

Grumpy_Mike:
But look at the code in post #3, did you put those smiles in? Well no you didn’t but the forum software mistook bits of your code. This is why we insist on you posting code inside code tags, which you would have known if you had bothered to read the the how to use this forum sticky post.

There are better ways of saying things to people rather than alienating them. I Could, have sorted myself out and then not bothered to post back.

There are better ways of saying things to people rather than alienating them

If you consider that asking you to read the rules of this forum is alienating consider yourself alienated.
Alternatively I can give you a full refund on what you payed for this advice.

When one has spent hours (literally) as a newbie trying to piece something together by yourself , you may not notice the item that says "PLEASE READ" (not please not must) or even if you do, you may not note the relevance to what your asking or how it may be formatted.

Saying "I can't be bothered" is high handed and not particularly friendly way of pointing out that my posted code should have been formatted differently.

It could have been worded "BTW, you code is not showing correctly as it's not in code tags, check out the Sticky at the top of the forum if you have not done so already"

And BTW, I have technical post counts in the 10'k helping people on forums for other subject matters, so I am not a forum Newbie. Hence I tried to complete my question with my findings and answers (SO far)

If you find my reply offensive then please feel free to click on the “report to moderator” link below the post.

I find your post crass but it will not take any further action in this thread, including correcting the errors you have made in what you consider you need to do to make a workable system.

twinturbo:
When one has spent hours (literally) as a newbie trying to piece something together by yourself , you may not notice the item that says "PLEASE READ" (not please not must) or even if you do, you may not note the relevance to what your asking or how it may be formatted.

The sticky post in question doesn't just say "please read" it says "How to use this forum - please read". I would think that someone with all your experience of using different forums would be keen to look for information about how to use this particular forum, knowing that they all have their own ways of working. It's the first thing I did when I arrived not so long ago.

If you're going to take offence because someone points you to this information (while at the same time giving you plenty of useful advice about your problems) then it is you who is likely to alienate the people here who might try to help you.

Good luck with your problems.

Steve

Well sorry if I have caused offence, i was worn out when I posted the first post, I had not seen the sticky [ not every forum has or needs them]

I had noticed a section of code had not posted correctly in one of the latter post and tried to correct it but was then far too tired to think about it.

Parts of the code where I have not used any loops etc are due to it being a test and to simplify for anyone else who is just starting out. Showing the "working out" before the final result may be helpful to someone else.

Re-Write of "ReadAdcAll", there was no point in the array being written and then being read.

void ReadAdcAll()
// Reads through the 8 mux channels on the selected mux and and initiates the send
{
  int i;
  int Mv;
  int Av;
  for(i = 0; i < 8; i++)
  {
    Av =  analogRead(A0 + i);
    Mv = Av/8;
    muxchan = mux + i;
    MIDImessage(191, muxchan, Mv); //pass values out through standard Midi Command
   }
}

The hardware..

The notes on the website say signal should not exceed VCC or it will cause errors.

I had to take a line from the PSU to a adjustable voltage reg to trim it right down from 12V to under 3.3V to get a correct reading from the POTS and to avoid the UNO just spitting out garbage.

The test rig ( I have an old Celco Navigator desk to re-work for the final fader setup), the faders here are logarithmic which is not of great use.

Previously I have had little success with hairless midi, but this time round it worked well. The input from the Arduion is selected on the serial port menu. Debug messages are very helpful. Out put can be set to MIDI Out ( Midi Through ) to pipe the messages into QLC, if QLC is running there is another option for MIDI out but it's not needed.

Debug messages showing the one connected fader at it's lowest point ZERO.

In QLC on the INPUT/OUTPUT tab you will find Midi Input, Midi Through is the one we have mapped out of Hairless. RtMidi is the other one you would find in Hairless as Qlc0. Either works.

You will need to configure the channel received by clicking the wrench and screwdriver icon.

You know when MIDI is being received as the Universe shows a joystick ( depending on the receive rate it may flash )

The next step is to create an input profile for the new midi input

This can be done by manually assigning new channels, or by capturing the MIDI data.

The capture sets everything as button, it doesn't really matter. Also Capture assigns different QLC channels to those that are added manually.

The final stage for testing is to go to Virtual Console, create some faders and assign channels to them.
Create the fade from the menu button, then right click on the new fader to get "Widget Properties"

After this you should be able to change from design to "Operate" mode by clicklink the play icon in the top right.

And at this stage you should have real control of the virtual faders.

TT