Go Down

Topic: MIDI lib, controlling volume on ch 1 with pot (Read 4149 times) previous topic - next topic

FranPhony

Good day forum users.

I am a music student from South Africa, hoping to get some pointers.

My ultimate goal is to build a class compliant MIDI Ondes Martenot with Arduino, I am currently using Arduino UNO but will eventually upgrade to something with higher resolution ADC for pitch bend. For the time being however, I will be taking baby steps because there are quite a few concepts involved that I am yet to fully comprehend.

For those who are unfamiliar with the Ondes Martenot, here are a few links:

https://en.wikipedia.org/wiki/Ondes_Martenot

https://www.youtube.com/watch?v=Yy9UBjrUjwo

I am not the first to want to make a MIDI version of the instrument, infact one Mitshubi Abe made such a device in 2012 and I will be basing the my hardware and my code on his version as it seems to work quite all right.

Watch his video demonstration here:

https://www.youtube.com/watch?v=BzYTdDpLfAU

And his code is available on his blog here:

http://gam.boo.jp/blog/archives/2012/09/midiondes_marte.html

Now, I tried my very best to decipher his code but I get errors all over the show. So I have decided to break it into little pieces, the first of which is note on/off on MIDI channel 1, controlled by a switch, and  Channel volume controlled by a sensor (potentiometer for prototyping). Just to clarify, when the button is pressed the message note on, middle C, velocity 127 is sent. The volume of that note is then controlled by the sensor which is linked to channel volume.

This is my current predicament. I have my arduino speaking to FL studio via hairless MIDI and I am getting a middle C at velocity 127 every minute or so. The switch does nothing, the pot does nothing.
Here is the sketch, clearly I have made some horrid mistakes. I urge you to point them out and help me correct them

Code: [Select]
#include <MIDI.h>

#define SWITCH 7

int volumeSensor = 0;
int volumeValue;
int MidiValue = 0;
int Old_midiValue;
int cc = 7;  // channel volume


int Notecounter = 0;
int val1 = 0;

int MIDI_ROOT_NOTE = 60;

MIDI_CREATE_DEFAULT_INSTANCE();

void SendMIDI (char cmd, char data1, char DATA2){
 
  Serial.write (byte(cmd));
  Serial.write (byte(data1));
  Serial.write (byte(DATA2));
}

void Control(){
 
  val1 = digitalRead(SWITCH);
 
  if (val1 = HIGH){
    Notecounter = Notecounter +1;
    if (Notecounter == 1)
    {
      SendMIDI(0x90, MIDI_ROOT_NOTE, 127);
    }
  }
  else{
    SendMIDI(0x80, MIDI_ROOT_NOTE, 127);
    Notecounter = 0;
   
  }
}
 
 
  void Volume(){
   
    volumeValue = analogRead(volumeSensor);
   
    MidiValue = volumeValue / 7.6;
   
    if (MidiValue > 127){
      MidiValue = 127;
    }
    if (MidiValue = Old_midiValue){
      SendMIDI (0xB0, cc, MidiValue);
    }
      Old_midiValue = MidiValue;
     
  }

void setup() {

  MIDI.begin (MIDI_CHANNEL_OFF);
 Serial.begin(115200);
}

void loop() {
 
  Control();
Volume();
  delay (1);

}




Grumpy_Mike

Lots of errors here. First always use == in an if statement you have single = in some of them.
Next you can not divide an integer with a floating point number and get a sensible answer just divide it by eight.

Finally the volume control uses two cc channels one for the most significant bits and another for the least significant bits.

Correct those things and post again if you are still having trouble.

FranPhony

Thanks for the response Grumpy Mike.

I have fixed the switch, it now sends the appropriate noteOn/ noteOff messages, although I can't help but notice some latency(for now I'll blame hairless). I also realised that I was using pull up configuration instead of pull down. I can now play middle C all day.

Code: [Select]
void control () {

  val1 = digitalRead (SWITCH);

  if (val1 == HIGH) {
    noteCounter = noteCounter + 1;
    if (noteCounter == 1) {
      SendMIDI(0x90, MIDI_ROOT_NOTE, 127);
    }
  }
  else {
    SendMIDI(0x90, MIDI_ROOT_NOTE, 0);
    noteCounter = 0;
  }
}


I also tried using note on at 0 velocity, but I'm not sure whether this is a correct implementation of running status.

Sending MSB and LSB for channel volume to their respective cc channels, I admit, is beyond me at this point. Are there any forum entries, tutorials or video tutorials that you (forum users) could refer me to that explains the process?

Grumpy_Mike

Quote
Sending MSB and LSB for channel volume to their respective cc channels, I admit, is beyond me at this point.
CC message 07 is the most significant 7 bits of the volume,  message 39 is the least significant 7 bits. Together you have 14 bits to control the volume. So suppose you have a variable called vol that contains a 14 bit number, that is a number from 0 to 16383. You split it up and send it over two CC messages

vol07 = vol >> 7
the variable vol07 is sent as the value of CC message 7

vol39 = vol & 0x7f
the variable vol39 is sent as the value of CC message 39

Quote
I also tried using note on at 0 velocity, but I'm not sure whether this is a correct implementation of running status.
It is not.
Running status means that after the initial note on byte and note number and velocity number subsequent notes are just represented by the note number and velocity number, the note on byte is missed out.

FranPhony

Quote
So suppose you have a variable called vol that contains a 14 bit number, that is a number from 0 to 16383. You split it up and send it over two CC messages
Thanks Grumpy Mike, I believe I'm on the right track.


With this information, i did the following:

Code: [Select]
void volume () {
 sensorValue = analogRead (SensorPin);
 MidiValue = sensorValue * (16383 / 1023);
 
 if (MidiValue > 16383){
   MidiValue = 16383;
 }
 if (MidiValue < 0){
   MidiValue = 0;
 }
 
  vol07 = (byte)(MidiValue >> 7);
  vol39 = (byte)(MidiValue & 0x7F);
 
  SendMIDI(0xB0, cc7, vol07);
  SendMIDI(0xB0, cc39, vol39);
 
}


I say that I believe I'm on the right track because hairless is giving me rational feedback in the debug window.

Quote
-Serial In: Ch 1: Controller 7 value 0
-Serial In: Ch 1: Controller 39 value 0
None of my plugins are accepting it as channel volume, I can, however, assign it to parameters. Could this problem be related to the code? or the software?
Sure, I can assign it to some other volume control but it seems like a bit of a hack.


Grumpy_Mike

Code: [Select]
  SendMIDI(0xB0, cc7, vol07);
  SendMIDI(0xB0, cc39, vol39);

should this not be:-
Code: [Select]
  SendMIDI(0xB0, 7, vol07);
  SendMIDI(0xB0, 39, vol39);

Or are these constants defined anywhere else?

I am not keen on this line:-
Code: [Select]
MidiValue = sensorValue * (16383 / 1023);
Interger division will seldom give you the answer you need. This is a much better way of scaling the number
Code: [Select]
MidiValue = sensorValue << 4;
And you don't need the value checks it will be right.

Quote
None of my plugins are accepting it as channel volume
That could be down to your plug in, what values is it expecting for volume. The 07 & 39 is for the master volume control you would only expect to send that to the final mixer.

FranPhony

In the meantime I worked on the remainder of the code. I got pitch bend and reset 0 point to work.

Here's the code in its entirety
Code: [Select]

#include <MIDI.h>
# define SWITCH 7 // switch for noteOn
# define PITCHADJUST 8 // switch to adjust pitch
# define PITCHINPUT 1 // 10 turn pot for pitch bend

int val1 = 0;
int val2 = 0;
int DATA3 = 0;
int noteCounter = 0;
int MIDI_ROOT_NOTE = 60;

int PITCHADJUST_RANGE = 0;
int PITCH_RANGE = 24;

byte LowerBits;
byte UpperBits;

int SensorPin = 0; // Connect the pressure sensor to A0
int sensorValue;
int MidiValue = 0; // volume Value
int Old_midiValue;
int vol07 = 0;
int vol39 = 0;
int cc7 = 7;
int cc39 = 39;

float MIDIPITCHSCALE = 0.03785;

MIDI_CREATE_DEFAULT_INSTANCE();

void SendMIDI (char cmd, char data1, char DATA2) {

  Serial.write(byte (cmd));
  Serial.write(byte (data1));
  Serial.write(byte (DATA2));

}

void control () {

  val1 = digitalRead (SWITCH);

  if (val1 == HIGH) {
    noteCounter = noteCounter + 1;
    if (noteCounter == 1) {
      SendMIDI(0x90, MIDI_ROOT_NOTE, 127);
    }
  }
  else {
    SendMIDI(0x80, MIDI_ROOT_NOTE, 127);
    noteCounter = 0;
  }
}

void volume () {
 sensorValue = analogRead (SensorPin);
 MidiValue = sensorValue * (16383 / 1023);
 
 if (MidiValue > 16383){
   MidiValue = 16383;
 }
 if (MidiValue < 0){
   MidiValue = 0;
 }
 
  vol07 = (byte)(MidiValue >> 7);
  vol39 = (byte)(MidiValue & 0x7F);
 
  SendMIDI(0xB0, cc7, vol07);
  SendMIDI(0xB0, cc39, vol39);
 
}



void ProcessAnalogValue (byte) {

  float _x = Z(1);
  val2 = digitalRead(PITCHADJUST);
  if (val2 == HIGH) {
    DATA3 = analogRead(1);
    PITCHADJUST_RANGE = 608 - DATA3;
  }
  int _Converted = (int) ((_x - 297 + PITCHADJUST_RANGE) / MIDIPITCHSCALE);

  if (_Converted > 16383) {
    _Converted = 16383;
  }
  if (_Converted < 0 ) {
    _Converted = 0;
  }

  LowerBits = (byte)(_Converted & 0x7F);
  _Converted >>= 7;
  UpperBits = (byte)(_Converted & 0x7F);

  SendMIDI(0xE0, LowerBits, UpperBits);
}


float Z (byte PIN)
{
  int tmp;
  int data1 = 0;
  int DATA2 = 0;
  int summary = 0; // summary of input data
  int h;

  // Get average data from Analogpin1
  for (h = 0; h < 10; h ++) {
    data1 = analogRead (PITCHINPUT);
    summary = summary + (data1);
  }
  tmp = summary / 10;
  return (float) tmp;
}


void setup() {

  MIDI.begin(1);
  Serial.begin(115200);
  SendMIDI (0xB0, 0x06, PITCH_RANGE);
  pinMode (PITCHADJUST, INPUT);
  pinMode (SWITCH, INPUT);
}

void loop() {

  volume();
  control();
  ProcessAnalogValue(PITCHINPUT);
  delay (1);
}


Credit for the code goes to Mitshusi Abe, I only tinkered with it to get it working.

FranPhony

Apologies Mike. On the top of my code I declared and initialized the variables.
I will try out your suggestions.


FranPhony

Good day. I finally got some time to get back to my project. I last left off with all my components sending the correct messages, but of course a new problem arose. My volume is extremely noisy, I tried a smoothing cap on the wiper of my pot but it didn't help much. I think it is a code related problem, it should be noted that a constant stream of midi message are sent. Which I think is the root of the problem. Is there a way to tell arduino to only send messages when the pot is turned??

Here is the code for volume controll
Code: [Select]
void volume () {
 sensorValue = analogRead (SensorPin);
 MidiValue = sensorValue << 4;
 

  vol07 = (byte)(MidiValue >> 7);
  vol39 = (byte)(MidiValue & 0x7F);
 
  SendMIDI(0xB0, 7, vol07);
  SendMIDI(0xB0, 39, vol39);
 
}

Grumpy_Mike

Quote
. Is there a way to tell arduino to only send messages when the pot is turned??
Yes.
Just remember the last value of the pot you sent and only send it to midi if it is different by at least four.
The abs function helps.
Code: [Select]

if( abs(lastValue - currentValue) > 4) {
// send to MIDI
}

FranPhony

I tried this but it did not work, I tried it with numbers greater than 4 and I also tried it in different parts of the sketch but it is still noisy as can be.

Code: [Select]
void volume () {
 sensorValue = analogRead (SensorPin);
 MidiValue = sensorValue << 4;
 

  vol07 = (byte)(MidiValue >> 7);
  vol39 = (byte)(MidiValue & 0x7F);
 
  if (abs (Old_midiValue - MidiValue) > 4){
 
  SendMIDI(0xB0, 7, vol07);
  SendMIDI(0xB0, 39, vol39);
  }
}




 I'm getting continuous streams of information for note off and pitchbend also, consistent and correct as they may be, I don't think it is necessary to send note off constantly.

Code: [Select]
void control () {

  val1 = digitalRead (SWITCH);

  if (val1 == HIGH) {
    noteCounter = noteCounter + 1;
    if (noteCounter == 1) {
      SendMIDI(0x90, MIDI_ROOT_NOTE, 127);
    }
  }
  else {
    SendMIDI(0x80, MIDI_ROOT_NOTE, 127);
    noteCounter = 0;
  }
}


How do I remedy?

I am looking in to making a basic synth in pure data for this instrument and sending cc7 and cc39 to control one parameter seems to confuse the program. Would it not be easier to just send cc7 ? I also found a few vst's that accepts cc7 as volume, in some cases master volume .

FranPhony

#11
Sep 23, 2015, 10:32 am Last Edit: Sep 23, 2015, 10:34 am by FranPhony
This seems to have worked though..

Code: [Select]
void volume () {
 sensorValue = analogRead (SensorPin);
 MidiValue = sensorValue / 8;
 

  if (abs (Old_midiValue - MidiValue) > 4){
  
  SendMIDI(0xB0, 7, MidiValue);
  
  }
}


I'm still not sure about the note off and pitchbend though. I should also note that somethimes when opening FL studio it tells me it is experiencing an MIDI overflow and then nothing works!!

Grumpy_Mike

Quote
I'm getting continuous streams of information for note off and pitchbend also
You are not doing this correctly. Yes that code will send a constant stream of not off messages, that is what you have programmed it to do. Every time you read a digital input and it is not high ( I assume that means not pressed although that indicates you have not wired up your switch in the best way ) then a note off is being sent. This means it is continuously being sent. There are many ways round this perhaps the simplest way is to have a boolean variable called say noteOn. When you send a note on message you set this to true, when you send a note off message you set it to false. Then you have an if statement that sends a note off message only if your digital input is low AND your noteOn variable is true. You need to use a compound if statement where there are two conditions.

Code: [Select]
  if(noteOn == true &&  val1 == LOW){
    SendMIDI(0x80, MIDI_ROOT_NOTE, 127);
    noteCounter = 0;
    noteOn = false;
  }

 
Also I can't see where you are updating the value of "Old_midiValue", it needs to be updated every time you take a reading. Otherwise it will always send stuff.

It is much better to to this on the raw value read from the analogue input rather than the cut down value you send to the MIDI.


FranPhony

It works!! Thanks Grumpy Mike.

Code: [Select]
void control () {

  val1 = digitalRead (SWITCH);

  if (val1 == HIGH) {
    noteCounter = noteCounter + 1;
    if (noteCounter == 1) {
      SendMIDI(0x90, MIDI_ROOT_NOTE, 127);
      noteOn = true;
    }
  }
 if (noteOn == true && val1 == LOW)
 { 
    SendMIDI(0x80, MIDI_ROOT_NOTE, 127);
    noteCounter = 0;
    noteOn = false;
  }
}




Code: [Select]
void volume () {
 sensorValue = analogRead (SensorPin);
 
 MidiValue = sensorValue / 8;

  if (abs (Old_midiValue - sensorValue) > 4){
 
  SendMIDI(0xB0, 7, MidiValue);
  Old_midiValue = sensorValue;
 
  }
}


Pitch bend is still sending continuously but I think I can figure it out.

Grumpy_Mike


Go Up