MIDI Theremin Controller Project Advice

I should start by saying that I'm new to the Arduino. I'm familiar with a little programming in C/C++, and I can grasp some basic MIDI protocol, but other than that I'm really a musician. I apologize in advance if I don't know what I'm getting myself into. I've highlighted my main questions in bold so it's easier to read this wall of text.

Anyway, I'd like to make a theremin controller, though there's two main differences from most of the theremin projects I've seen online.

First off, I'm not looking to generate any kind of sound with the Arduino itself. I'd just like to send MIDI pitchbend data to a software synth, most likely NI's Massive. I'd like to send the information out of the Arduino's built in USB port, but after Googling some MIDI-based projects, they all seem to require separate MIDI ports attached to the board. So I guess my first questions are:

Can I send MIDI data over the Arduino's USB connection, or do I need a MIDI port?
Wouldn't I need to get a software synth to recognize the Arduino as a MIDI controller? If so, how?
And I've seen a lot of discussion debating Serial.write() vs. Serial.print(). Which am I supposed to use?

Secondly, I'm using a distance sensor (an HC-SR04 Ultrasonic sensor) rather than an antenna of some sort. I've already found a program which simply prints out the distance of an object from the sensor, so I have that working. The values can jump and act a little sporadic at times, but that's only if I move my hand really fast in front of the sensor.

Is an ultrasonic distance sensor fine for a theremin project, or is there some obvious problem with this method of input that I don't know about?

Another problem is scaling the values from the sensor to proper MIDI protocol. The output values need to be scaled down to a proper range for pitchbend data: which, from what I understand of most MIDI stuff, generally operates on a scale from 0 to 127. If that's true for pitchbend, that's not going to be enough for a smooth transition for a large range (hoping for at least a 2 to 3 octave range).

This is a bit confusing because I have an M-Audio Axiom Pro controller with a pitchbend wheel, and the transitions across large ranges are still incredibly smooth. Either the controller is sending out a much larger range of values, or the synth itself performs some kind of pitch smoothing between the standard 128 range it's given. So with that in mind, some final questions:

Is there any insight on how Massive (or software synths in general) handle pitchbend data?
Should I use a range of 128, or could I use a much larger range?

That's all the questions I can come up with for now.

Can I send MIDI data over the Arduino's USB connection,

Yes but you will need a helper application like Hairless to convert the serial data into the MIDI bus inside your computer.
http://projectgus.github.io/hairless-midiserial/

Wouldn't I need to get a software synth to recognize the Arduino as a MIDI controller?

No Hairless will do it for you.

And I've seen a lot of discussion debating Serial.write() vs. Serial.print(). Which am I supposed to use?

Serial.write() - you can not use Serial.print() now that the BYTE option has be deprecated.

Is an ultrasonic distance sensor fine for a theremin project,

Seems good to me.

Should I use a range of 128, or could I use a much larger range?

No Pitchbend data is a 14 bit and the units of bend are set by controller messages 101 and 100

Awesome, thanks for the insight. Unfortunately my laptop just died on me and is in for repair, but I'll definitely check out Hairless and try out your other suggestions when I get it back in 3-5 business days.

Also, thanks for clearing up the pitchbend issue. Good to know I've got a much larger resolution for that.

Whenever I get my laptop back and make progress (or inevitably stumble into some other issue) I'll make sure to update this thread.

Okay, so I've got the laptop back. XP

Before I start trying to get the distance sensor working, I'm just trying to interface the Arduino itself with a software synth first.

Downloaded Hairless and I've got it connecting the Arduino to Massive (soft synth). I've found this Instructables page about sending pitchbend data from an Arduino, copied the code and ran it. The code is a little lengthy, so I'll just link to the Instructables page instead of copying it here. (BTW there's two versions of the code that use different pitchbend ranges. I'm using the full range, which is the second block of code on the page.)

However, I'm getting an error message from Hairless:

Warning: got a status byte when we were expecting 1 more data bytes, sending possibly incomplete MIDI message 0xc0 (sometimes 0x80)

It looks like the errors are happening almost every time the MIDImessage() function is called. I know roughly what the error means, but I'm not sure how or where to fix it.

According to the page, it looks like pitchbend data is contained in 7 bits. It'd make sense why Hairless might complain about this, since 8 bits seems more standard for any given MIDI message. When I run it, the program does whack Massive out of tune, so I know that something is working, but besides that it doesn't sound at all like the example video on the Instructables page. Any ideas?

According to the page, it looks like pitchbend data is contained in 7 bits.

No it has 14 bits. After the pitch bend message then there are two bytes sent both with the most significant bit at zero.

since 8 bits seems more standard for any given MIDI message.

The message is the whole two or normally three bytes. The first is the message type and always has the most significant bit set, it is then followed by data which always has the least significant bit as zero.
So hairless is complaining that there is not enough data bytes being sent for the type of message it has received.

Thanks again for clearing that up. Still learning some of this stuff.

Also, I figured out the problem. I needed to install the MIDI library and call MIDI.begin(), and I also had to conform the baud rate to 115200, the same as Hairless.

Anyway, after an hour of tweaking and combining different sources of code, I've got it working!

Well, almost.

The data is really sporadic, so the pitch of the synth goes all over the place. I'm looking up some tutorials on smoothing data, so hopefully I can get this thing to actually be playable. I think I might have to mount the sensor differently too; right now it's connected directly to the ports of the Arduino, but I think it's pointing downwards a bit and sometimes senses the breadboard in front of it. Figures.

I also still have to set the values to a proper range, so that my hand doesn't have to go back six feet just to reach the bottom octaves. But it's definitely getting there. :smiley:

Before you add the sensors how about just reading a pot. In that way you will have known good quality solid data to test your software with.

Actually I've already implemented the smoothing bit. The pitch is definitely a bit more stable, but it still goes out of whack every few seconds, and tends to jump around if my hand is moving too fast. Heck, sometimes the pitch jumps around for a few seconds even if my hand is still.

I'm considering at some point just looking into a more traditional setup involving an antenna of some sort. I feel the distance sensor may not be the best way of approaching this project, as it's just too sporadic to use for something as precise as pitch.

Regardless, I'll try implementing the smoothing code with a simple potentiometer first and see how reliable the data is.

Again check with a print if your nut is unstable.

You might want to look at the Sharp IR distance sensors before going to RF.

I'm using ir sensors to input my data, I'm getting some very interesting sounds out of my device, I can play notes, pitch bend, but for the life of me I can't get the mod wheel working.

I have ir sensors connected to arduino, which is connected to my midi controller though the midi in cable, my controller then plays notes via a softsyth programme on my laptop.

Here is a link to a short video of my third night of work on the project, as you can see it's working but my goal was to have eight sensors driving cc values, but so far I can only get pitch wheel and note on working.

My question is can someone show me simple code to drive say 'main volume'? or Mod wheel, I've looked at a bunch fo charts showing the different possible values but none of them work?

I've looked at a bunch fo charts showing the different possible values but none of them work?

Yes they all work if you do the right thing with them. As you are not showing us what you have done with them then it is hard to say.
Have you read the how to use this forum thread?

managed to get my volume and cc controls working. My problems were sending the calls to midi needed to byte my vars before sending. Now i have prox sensors working but my problem is dealing with the changing maximum and minimum values. I have a function that takes the prox sensor and converts it to a value between 0-127 but I need to define the max and min possible inputs which over time change. Unlike a pot switch where the min and max values never change using a prox sensors min and max values change depending on ambient light. If anyone knows a way to deal with this please let me know.

I also wrote a simple function to correct the incoming notes to be in any given scale. You take the note coming from the sensor and divide it by 12, then take the remainder and compare it to an array that defines that scale, so for major scale you create an array
majorScale[] = {1,3,6,8,10}; if the incoming note is found within the array I then tell it to bump up the note to the next value. Worked great and resulted in my device sounding less chaotic .

Here is my code for my prox synth

#include <SoftwareSerial.h>

byte note = 0; // The MIDI note value to be played
////billy finger vars
int IRpin = A0;
int IRpin2 = A1;
int IRpin3 = A2;
int IRpin4 = A3;
int IRemitter = 5;
//int IRemitter2 = 9;

int ambientIR;
int obstacleIR;
int value[10];
int distance;

int fingers[] = {IRpin,IRpin2,IRpin3,IRpin4};
int lastNotes[] = {0,0,0,0};
int noteMax[] = {0,0,0,0};
int noteMin[] = {1000,1000,1000,1000};
int scaleMajor[] ={2,4,7,9,11};
int minorScale[] = {2,5,8};
///end of billy vars

int pitchbend = 224;
int modwheel = 176;
int lsb = 0;
int msb = 0;

//software serial
SoftwareSerial midiSerial(2, 3); // digital pins that we'll use for soft serial RX & TX

void setup() {
Serial.begin(9600);
midiSerial.begin(31250);
pinMode(IRemitter,OUTPUT);
digitalWrite(IRemitter,LOW);
}

void loop() {
for(int x=0;x<4;x++){//ren though the sensors
int tempVal = 0;
distance = readIR(6,x);
if(distance > noteMax[x]){noteMax[x] = distance;}
if(distance < noteMin[x]){noteMin[x] = distance;}

if((distance > noteMin[x]+1) && (distance < noteMax[x]-1)){
tempVal = ((distance * 127) / (noteMax[x]));
if(x==0){
int temptVal = (((distance - noteMin[x]) * 127) / (noteMax[x] - noteMin[x]));
int adjustVal = tempVal - (tempVal%12);
for(int z=0;z<5;z++){
if (adjustVal = scaleMajor[z]){
tempVal++;
}
}
if(lastNotes[x] != tempVal){
byte tempVal2 = 45;
noteOn(0x90, tempVal, tempVal2);
lastNotes[x] = tempVal;
}
}
if(x==1){
ccSend(19,tempVal);
}
if(x==2){
ccSend(20,tempVal);
}
if(x==3){
int tempVal3 = 127-tempVal;
ccSend(32,tempVal3);// volume!!!!
}

}

Serial.print(x);
Serial.print("(");
Serial.print(noteMax[x]);
Serial.print(":");
Serial.print(tempVal);
Serial.print(":");
Serial.print(noteMin[x]);
Serial.print(") ");
}
Serial.println();
}

void noteOn(byte cmd, byte data1, byte data2) {
midiSerial.write(cmd);
midiSerial.write(data1);
midiSerial.write(data2);
}

void ccSend(byte CCnumber, byte CCdata){
byte CCchannel = 0xB0;
midiSerial.write(CCchannel);
midiSerial.write(CCnumber);
midiSerial.write(CCdata);
}

int readIR(int times,int fingerNum){
for(int x=0;x<times;x++){
digitalWrite(IRemitter,LOW);
delay(1);
ambientIR = analogRead(fingers[fingerNum]);
digitalWrite(IRemitter,HIGH);
delay(1);
obstacleIR = analogRead(fingers[fingerNum]);
value[x] = ambientIR-obstacleIR;
}
distance = 0;
for(int x=0;x<times;x++){
distance +=value[x];
}
return(distance/times);
}

I have the volume working but for the life of me still can't get mod to work?

Read that code. See all the black squares? Did you put them there?
This is why we ask people to use code tags when posting code.
Please read the how to use this forum sticky post.

I copy pasted the code from my project to the form, I have a visual disability that makes it difficult to read.
I'm just trying to reach out to others doing similar creative things with arduino and midi, If anyone has some code examples to show how to send controls to the mod wheel would be much appreciated.

you're remarks are kind of like looking at a person in a wheelchair going up a ramp and commenting 'hey you know it would be faster to just use the stairs'.

No they are not.

They are like seeing a person in a wheelchair going up a ramp and commenting 'hey did you not read that big notice that says there is a lift ( elevator for the U.S. inclined ) '
It is intended to help both you and others.

The code you posted is unreadable, the forum mangled it, that is why we have code tags and that is why we explain them in the post I directed you to!

Thanks for all your help mike much appreciated.

If anyone has some code showing how to control midi mod wheel with arduino would be much appreciated. Once I have the unit fully working I'll post my code so others may benefit from what I have learned.

code showing how to control midi mod wheel with arduino

It is just like the other messages:-

void sendPB(int pb){ // send pitch bend message
  Serial.write( (byte)0xE0 | channel);
  Serial.write( pb & (byte)0x7f);
  Serial.write( (pb>>7) & (byte)0x7f);
}

Thanks for the sample code I will try it tonight. Here is where I am so far. Dealing with max and min values of the ir sensors has been the biggest challenge, it's most likely from touching the sensors and causing an abnormal spike in the voltage from the wires moving in the bread board, I'm sure once I solder it to a circuit board the sensors should be a little more predictable. (I've got all the code to handle max and min but every now and then I touch the sensor and makes the min or max way above what is the norm)

Thanks again mike, I was able to implement your code. Thanks for that.

I'm still having issues with max and min values coming in from the ir sensors,

As the values come in I then compare them to a max and min value if the incoming value is greater then the max I rewrite the max value to the new one, and then the same for the min value.

I then use the distance between the two to map it to 0-127 on my cc midi value.

But every now and again a greatly abnormal value comes in and redefines the max or min leaving me with the ability to turn the cc value say between 40-90% and never going back to zero, unless I get an abnormal value again?

I'm trying to implement a second level of logic to count the number of times a value comes in, if I get the same value too often I then consider the value as a realistic max or min and rewrite the max or min again.
Am I going about it the right way or does anyone have better way to self correct an ir input?