MIDI Controller via Ultrasonic Sensor

Hi guys,

that’s my first post in this forum. I have read a lot of your posts in the past, but now was the time I couldn’t find a solution for this specific problem and wanted to ask if anybody of you could help me with this.

I know a few things about electronics as I completed a vocational education, but I am relatively new into Arduino programming, as this is just a hobby for me.
I think, it’s a really simple thing I try to do, and I probably should see the mistake by myself, but I tried now for most of the evening and google researches didn’t bring me answers where the mistake in my sketch is.

So, I am trying to make a 1 channel MIDI controller with an ultrasonic sensor as an input. The idea is, to control an audio program like ableton via the ultrasonic sensor quite similar to a theremin or something like that.

First I did a “dry run” with a poti and followed these steps right here:

I did just use the first potentiometer and could succesfully control ableton with it.

I set up the ultrasonic sensor in a second sketch according to this:

I could read out the distance via the serial monitor without any problems.

Then my attempt was to combine these two to one sketch, so I could control ableton via the ultrasonic sensor.

This resulted in the following code:

int val = 0; //Our initial pot values. We need one for the first value and a second to test if there has been a change made. This needs to be done for all 3 pots.
int lastVal = 0;
const int trigPin = 9;
const int echoPin = 10;
long duration;
int distance;
void setup()
{
pinMode (trigPin, OUTPUT);
pinMode (echoPin, INPUT);
Serial.begin(9600); // Set the speed of the midi port to the same as we will be using in the Hairless Midi software
}
int sensorPin = 0;
int sensorValue = 0;
void loop()
{
digitalWrite (trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite (trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = duration0.034/2;
if (distance < 31)
{
val = distance
4; // Multiply by 4 to get range of about 0-127 for midi
if (val != lastVal) // If the value does not = the last value the following command is made. This is because the pot has been turned. Otherwise the pot remains the same and no midi message is output.
{
MIDImessage(176,1,val);} // 176 = CC command (channel 1 control change), 1 = Which Control, val = value read from Potentionmeter 1 NOTE THIS SAYS VAL not VA1 (lowercase of course)
lastVal = val;
delay(10); //here we add a short delay to help prevent slight fluctuations, knocks on the pots etc. Adding this helped to prevent my pots from jumpin up or down a value when slightly touched or knocked.
}
else
{
delay(10);
return;
}
}
void MIDImessage(byte command, byte data1, byte data2) //pass values out through standard Midi Command
{
Serial.write(command);
}

I think there are just minimal differences to the code from instructables, it also compiles and upload succesfully, but my hairless midi doesn’t receive any data. I already double checked settings like baud rate and can’t get it.

I set the if clause to distance < 31, because I just wanted to send signals, if I go closer than 30cm to the sensor.

If you need more information on what where my thoughts on a specific part, please ask me.
And again, I am aware that this topic will almost certainly cause annoyance with some of you and I want to excuse in advance, but I really couldn’t get any step forward here. Please get me the chance to learn and don’t blame me.

I am open for any improvment suggestions :slight_smile:

Thanks and regards,
Sront

You're only sending the first of three MIDI bytes, what about data1 and 2?

Pieter

The original code was for 3 potis. I cropped the code for the last two lines and forgot to delete the defintion of data1 and data2 :confused:

But if I add them again:

void MIDImessage(byte command, byte data1, byte data2) //pass values out through standard Midi Command
{
   Serial.write(command);
   Serial.write(data1);
   Serial.write(data2);
}

the result still stays the same.

But I mentioned one thing…could it be that I have an error in reasoning here?

I wanted to delete the definition as mentioned above for “clean coding”:

void MIDImessage(byte command) //pass values out through standard Midi Command
{
   Serial.write(command);
}

and for compiling it says:
exit status 1
too many arguments to function ‘void MIDImessage(byte)’

  1. 31*4 = 124 < 127 = 100%
  2. Read the MIDI specification.
    The number of Serial.write commands have nothing to do with the number of potentiometers.

The format of a control change message is as follows: 0b 1011nnnn 0ccccccc 0vvvvvvv, where n is the channel, c is the controller number, and v is the value.
All CC messages are three bytes long, that’s why there are 3 Serial.write commands.

Using 176 as a decimal representation is bad practice, because it doesn’t show the underlying meaning. 0b10110000 or 0xB0 are more appropriate.

So a better function would be:

/* The format of the message to send via serial. We create a new data structure, that can store 3 bytes at once.  This will be easier to send as MIDI. */
typedef struct {
  unsigned int channel : 4;   // second nibble : midi channel (0-15) (channel and status are swapped, because Arduino is Little Endian)
  unsigned int status : 4;    // first  nibble : status message (NOTE_ON, NOTE_OFF or CC (control change) 
  uint8_t data1;              // second byte   : first value (0-127), controller number or note number
  uint8_t data2;              // third  byte   : second value (0-127), controller value or velocity
} 
t_midiMsg;          // We call this structure 't_midiMsg'

void MIDISend(uint8_t status, uint8_t channel, uint8_t data1, uint8_t data2) {
  t_midiMsg msg;
  msg.status = status & 0xF;
  msg.channel = (channel-1) & 0xF; // channels are 0-based
  msg.data1 = data1 & 0x7F;
  msg.data2 = data2 & 0x7F;
  Serial.write((uint8_t *)&msg,3);
}

And to send a control change (CC) message on channel 1, for controller 0x07 (“channel volume”, check MIDI specs for other codes), with ‘val’ a whole number from 0 to 127:

#define CC 0xB
#define CHANNEL_VOLUME 0x07
...
  MIDISend(CC, 1, CHANNEL_VOLUME, val);

Srontduino:
But I mentioned one thing…could it be that I have an error in reasoning here?

I wanted to delete the definition as mentioned above for “clean coding”:

void MIDImessage(byte command) //pass values out through standard Midi Command

{
  Serial.write(command);
}




and for compiling it says:


exit status 1
too many arguments to function ‘void MIDImessage(byte)’

You changed the function definition to have 1 parameter, but in your loop, you still call that function with 3 parameters:

void func(type x) { ... }
...
void loop() {
  func(x, y, z);
}

This won’t work, because the compiler doesn’t know any function ‘func’ that takes three arguments.

Start over, and write your own sketch that sends the value of a single potentiometer as a control change message. Once that’s working, and you understand every single line, add the code for the ultrasonic sensor back in.

Pieter

Thank you Pieter!!

I got it working today.

Your tips were really helpful, especially in understanding the basics.

I rewrote the whole code step-by-step, and I asked myself after every step: “wait, what is this doing exactly and how?” with the old MIDImessage function and then replaced it with the MIDISend function.

The result is as follows:

float val = 0; 
int lastVal = 0;


const int trigPin = 9;
const int echoPin = 10;

long duration;
int distance;


int sensorPin = 0;
int sensorValue = 0;


#define CC 0xB
#define CHANNEL_VOLUME 0x07


void setup()
{

pinMode (trigPin, OUTPUT);
pinMode (echoPin, INPUT);

   Serial.begin(9600);       // Set the speed of the midi port to the same as we will be using in the Hairless Midi software 
}




/* The format of the message to send via serial. We create a new data structure, that can store 3 bytes at once.  This will be easier to send as MIDI. */
typedef struct {
  unsigned int channel : 4;   // second nibble : midi channel (0-15) (channel and status are swapped, because Arduino is Little Endian)
  unsigned int status : 4;    // first  nibble : status message (NOTE_ON, NOTE_OFF or CC (control change)
  uint8_t data1;              // second byte   : first value (0-127), controller number or note number
  uint8_t data2;              // third  byte   : second value (0-127), controller value or velocity
}
t_midiMsg;          // We call this structure 't_midiMsg'



void MIDISend(uint8_t status, uint8_t channel, uint8_t data1, uint8_t data2) {
  t_midiMsg msg;
  msg.status = status & 0xF;
  msg.channel = (channel-1) & 0xF; // channels are 0-based
  msg.data1 = data1 & 0x7F;
  msg.data2 = data2 & 0x7F;
  Serial.write((uint8_t *)&msg,3);
}


void loop()
{

digitalWrite (trigPin, LOW);
delayMicroseconds(2);

digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite (trigPin, LOW);

duration = pulseIn(echoPin, HIGH);
distance = duration*0.034/2;


if (distance<31)
{
   val = (distance * 4.2)*-1;
   if (val != lastVal) // If the value does not = the last value the following command is made. This is because the pot has been turned. Otherwise the pot remains the same and no midi message is output.
   {
   MIDISend(CC, 1, CHANNEL_VOLUME, val); 
   lastVal = val;
   delay(20);
   }
}
   
}

To be honest, I obviously didn’t understand the MIDISend function completely.
It works, I can attach the soft knobs in Ableton to the ultrasonic sensor but when I paused the Live Set and I get in the near of the sensor, so it sends a CC command, the Live Set starts altough it is attached to a different knob.
It also starts the sequence again.
I think that’s something with the MIDI command I send and I have to in-depth study the MIDI interface in the next time. I know that, but on first hand I wanted to get it running.

Except for understanding MIDI the next step I want to try is to smooth the signal, because it jumps back and forth. Any recommendations on how to do that? I found out there are different ways, and I don’t know right know which one I should follow.

edit: oh and one thing I also don’t understand…I defined the val as a float because it’s a decimal. But “distance” also works with decimal places altough it’s an integer? How is that possible?

Thanks and regards,
Sront

Glad to hear that you got it working!

You can check out how I implemented smoothing in my MIDI Controller library, I used the runningaverage approach.
(It's in Analog.h and Analog.cpp)

Pieter

Hey,

I tested this code with my sensor and I'm getting the midi data in hairless and in my IAC driver.
When I test this in Ableton the MIDI comes in but I can't play an MIDI instrument.
My preferences are correct, my track is armed, monitor is on..

I see in hairless that the data is on controller 7 but it comes in ableton on channel 1 so I don't think that's the problem.
Tested this in Qlab and I can trigger a song so it's really in Ableton I guess.

Thanks, Wout