MIDI output in Laser Harp Project

Hi people!

newby alert!

I have built 2 projects, that I would like to mix together, and I really do not have the tools to do it myself...

I built an Electronic Marimba following the instructions of the drum kit by Spikenzielabs (DrumKitKit)

I also built Radioshack´s Laser Harp (http://www.radioshack.com/graphics/uc/rsk/Support/ProductManuals/Laser_Harp_Online_Instructions.pdf)

And I would like my Laser Harp, to trigger midi notes as I do it with my electronic marimba.

Can somebody help me out!

Thanks!!!!

I attach both sketches.-

sketch_marimba.ino (4.8 KB)

RS_DIY_LaserHarpJr.ino (1.22 KB)

I didn't give this a supper in depth look, but I did dig through the code. It looks fairly easy to accomplish.

What I would do is copy the MIDI_TX function from the marimba sketch to the laser harp sketch Then you need to make the laser harp sketch send the right messages. You don't want to repeatedly send the same message over and over as that will make the midi re-attack the note. Instead make a Boolean variable that keeps track of whether each note is played or not played. When a note is first played the variable will be false, you will set the variable to true and send the key pressed signal of

MIDI_TX(144,PadNote[pin],hitavg);

with hitavg replaced by a number you think sounds good as I don't think you can get a velocity from a laser.

Later, when the note is released you will make the variable false and send the command:

MIDI_TX(128,PadNote[pin],127);

In both of these commands the second parameter will depend on which note was played. You will then connect the the system with the processing sketch the same as you did for the marimba.

thanks so much for your reply!

I am trying to give it a try, but I am a little bit overwhelmed, since I dont fully understand either the marimba or laser harp projects completely, it makes it really difficult for me to follow your advice.

I am going to be here, trying to sort it out from the beggining...

:slight_smile:

OK, Lets start in by trying to understand the two source projects.

First the LaserHarp:

#include <Tone.h>

const boolean DEBUG = false;

const int CALIBRATION_PIN = A5;
const int SPEAKER_PIN = 8;
const int SENSOR_COUNT = 3;

This code includes another code file called Tone.h that contains the code to output the audio tone. It also declares a constant debugging flag to be off and it declares the three pins that it will use.

typedef struct 
{
  int pin;
  int note;
} sensor_type;

This code makes one datatype that contains both the pin and the note called sensor_type.

sensor_type sensor[SENSOR_COUNT];

This line declares an array of those sensor types

Tone notePlayer;

Declares a Tone object called notePlayer

void setup(void)
{
  if (DEBUG) {
    Serial.begin(9600);
  }
  sensor[0].pin = A0; // analog input 0, etc...
  sensor[0].note = NOTE_G3;
  sensor[1].pin = A1;
  sensor[1].note = NOTE_D4;
  sensor[2].pin = A2;
  sensor[2].note = NOTE_A4;
  
  notePlayer.begin(SPEAKER_PIN);
}

Now we are in the setup code that runs once on startup. If the debug flag is true (which it isn't) we will start a serial port. Following this, we initialize the first three elements of the sensor array by defining the pin and note of each element. Lastly we run the begin method of notePlayer to start the tone code for the given speaker pin.

The rest of the code is in the loop function and runs continuously.

int calibration = analogRead(CALIBRATION_PIN);
  if (DEBUG) {
    Serial.print("cal: ");
    Serial.print(calibration);
  }

We read the value from the calibration pin, and if debuging is set (which it isn't) we print the value to the Serial terminal.

int activeSensor = -1; 
for (int p = 0 ; p < SENSOR_COUNT ; p++) {

Here we find the core code. We set the integer variable activeSensor to be -1. Then we start a for loop on the variable p from 0 to one less than the number of sensors.

int sensor_value = analogRead(sensor[p].pin);

This line gets the reading from the sensor. Remember each time through the loop sensor[p] will refer to a different sensor and different note. It is short way to write all of the needed reads.

if (DEBUG) {
      Serial.print("\tsensor ");
      Serial.print(p);
      Serial.print(": ");
      Serial.print(sensor_value);
    }

Debugging code that prints what was read by the sensor.

if ( sensor_value < calibration ) {
      activeSensor = p;
      if (DEBUG) Serial.print("!"); // "!" indicates note being played
    }

We are still inside of the for loop. If the read in value is bigger than the calibration value then the note is being played. In this case the activeSensor variable is set to be equal to the current sensor number p, and if debugging is set we print this as well.

Now we leave the for loop. At this point we have checked all of the notes and activeSensor will be set to the last note in the series that is currently being played. If no note is being played it will stay at its initial value of -1.

if (activeSensor == -1) {
    notePlayer.stop();
  } else {
    notePlayer.play(sensor[activeSensor].note);
  }

I left off some debugging lines, but I think you get the idea by this point. This code here uses the value of active Sensor to set the note being played. If activeSensor is -1 it stops the player and halts any playing notes, otherwise it plays the given note. It finds this note by calling sensor[activeSensor].note

sensor is the array of notes and pins
sensor[activeSensor] is just the sensor_type object that is being played
sensor[activeSesnor].note is the note type being played

Hopefully this demystifies this code. I plan to shortly post back with a similar breakdown of the marimba sketch.

Amazing!!! I am on it already!

THANKS!

And the Marimba Code:

Note the excellent comments for what the variables do!

//*******************************************************************************************************************
// User settable variables			
//*******************************************************************************************************************

unsigned char PadNote[8] = {40,41,42,43,44,45,46,47};   // MIDI notes from 0 to 127 (Mid C = 60) // Original = {52,16,66,63,40,65}

int PadCutOff[8] = {20,20,20,20,20,20,20,20};           // Minimum Analog value to cause a drum hit

int MaxPlayTime[8] = {90,90,90,90,90,90,90,90};         // Cycles before a 2nd hit is allowed

#define  midichannel	0;                              // MIDI channel from 0 to 15 (+1 in "real world")

boolean VelocityFlag  = true;                           // Velocity ON (true) or OFF (false)




//*******************************************************************************************************************
// Internal Use Variables			
//*******************************************************************************************************************

boolean activePad[8] = {0,0,0,0,0,0,0,0};                   // Array of flags of pad currently playing
int PinPlayTime[8] = {0,0,0,0,0,0,0,0};                     // Counter since pad started to play

unsigned char status;

int pin = 0;
int hitavg = 0;

Most of these variables are self-explanatory. We will encounter them more specifically later.

void setup() 
{
  Serial.begin(57600);                                  // connect to the serial port 115200
}

All setup does here is start a fast serial terminal. Note the baudrate mismatch between the comment and the code??? Either should work, just make sure the rate here matches the rate in the serial monitor or the Processing sketch.

Now we are in the main loop.

void loop() 
{
  for(int pin=0; pin < 8; pin++)
  {

We start a for loop that sets pin equal to the numbers 0 through 7.

 hitavg = analogRead(pin);                                                        // read the input pin

This is where we do an analog read of the input pin. Curiously we read 8 pins even through the Arduino Uno only has 6 analog pins. If the code works like this on an Uno it is likely because reading a non-existant pin returns a value that doesn't mess up the later code, but I don't know for sure what happens in this case.

if((hitavg > PadCutOff[pin]))
    {
      if((activePad[pin] == false))
      {
        if(VelocityFlag == true)
        {
//          hitavg = 127 / ((1023 - PadCutOff[pin]) / (hitavg - PadCutOff[pin]));    // With full range (Too sensitive ?)
          hitavg = (hitavg / 8) -1 ;                                                 // Upper range
        }
        else
        {
          hitavg = 127;
        }

Now we use some code to make decisions based on the value we read in. If this value was greater than the cutoff set for that pin at the top of the file then we check to see if the pin is already active. If the pin is not already active we check to see if the Velocity flag is true. If it is, then the code sets hitavg to be one less than its previous value divided by 8. If variable velocity mode is not turned on, then hitavg is simply set to be 127, the maximum midi velocity.

MIDI_TX(144,PadNote[pin],hitavg); 
        PinPlayTime[pin] = 0;
        activePad[pin] = true;

At this point the command to play the key is sent. See the note at the bottom for what MIDI_TX does. We also reset the pin's play time and make the pin active so that it will not be re-triggered by the next loop.

else
      {
        PinPlayTime[pin] = PinPlayTime[pin] + 1;
      }

This code runs if the pin was already active. In this case it increments the pin play time so that the note will eventually stop playing.

else if((activePad[pin] == true))
    {
      PinPlayTime[pin] = PinPlayTime[pin] + 1;
      
      if(PinPlayTime[pin] > MaxPlayTime[pin])
      {
        activePad[pin] = false;
        MIDI_TX(128,PadNote[pin],127); 
      }
    }

This code runs only if the hitAvg was not above the threshold AND the given note is active (so it was previously triggered). In this case we still increment the pin play time, but we also check to see if this time is greater than the maximum set for this pin. If that is the case the pin is reset to inactive and the command to stop the note is sent.

//*******************************************************************************************************************
// Transmit MIDI Message
//*******************************************************************************************************************
void MIDI_TX(unsigned char MESSAGE, unsigned char PITCH, unsigned char VELOCITY)
{
  status = MESSAGE + midichannel;
  Serial.write(status);
  Serial.write(PITCH);
  Serial.write(VELOCITY);

The midi transmit commands are sent by this code. It is critical to note that these are not true midi commands, but are instead serial commands that are sent to a serial midi converter that then outputs the midi signal to another application on your computer.

This function takes in a message, pitch, and velocity. It adds the message character and the midichannel (Midi has 16 different channels so that various controllers can be daisy chained and separated on the receiving side) to form a new status parameter. It then sends the status along with the pitch and velocity over serial.

From the code I gather that a Message of 144 will play a note while a 128 will stop the note. The note parameter is the midi-number for the note (See picture below). The velocity corresponds to how fast the keyboard key is pressed on a keyboard and will range from 0 to 127. He uses a message of 144 to stop a note, but sending a velocity 0 message should also accomplish this in theory.

super.

note that:

Curiously we read 8 pins even through the Arduino Uno only has 6 analog pins. If the code works like this on an Uno it is likely because reading a non-existant pin returns a value that doesn't mess up the later code, but I don't know for sure what happens in this case.

I am using an arduino mega 2560 for this marimba project.

I will study this in detail in order to try to have a better understanding of the projects. I think I really need to start from the beginning because I never did it, and I am missing a lot of points.

So far, I still wouldn´t know to accomplish my goal. can you assist me with the code, and I will try to understand it from there?

Thanks so much for your support!

On a request for further clarification:

To start I would make a copy of the laser harp sketch as that is the framework that you hope to modify.

Then to start the modifications you need to replace each notePlayer method with an equivalent from the marimba sketch.

Here are the ones I saw on a quick scan through.

notePlayer.begin(SPEAKER_PIN); can simply be replaced by a Serial.begin(57600);

notePlayer.stop() could be replaced with the line that stops a note on the midi marimba: MIDI_TX(128,40,127);

notePlayer.play(...) could be replaced with a simple: MIDI_TX(144,40,127);

In the last example I made it play only a single note # 40 for testing, you should be able to add more notes once you get this working. I made the velocity the constant 127 because you won't naturally get a velocity from a laser harp.

Copy in the Transmit MIDI function from the bottom of the marimba sketch

Remove the #include <Tone.h>, and the Tone notePlayer

At this point you should have something fairly close to what you need. There may be a few leftover variables floating around and such, but it should get you close. At this point you may find that repeatedly sending the same note over and over again does not work well with the midi synth, but you should be able to hear something.

See if you can get to this point and post code for however far you got along with a description of what happens. From there I or someone else should be able to help further.

hi!

Here goes a list of the steps I recreated and the errors I received afterwards:


notePlayer.begin(SPEAKER_PIN) replaced by Serial.begin(57600)

notePlayer.stop() replaced by MIDI_TX(128,40,127)

notePlayer.play(sensor[activeSensor].note) replaced by MIDI_TX(144,40,127)

sensor[1].pin = A1;
sensor[1].note = NOTE_D4;
sensor[2].pin = A2;
sensor[2].note = NOTE_A4; removed since I am trying it with just one laser


Transmit MIDI function copied to the sketch

//*******************************************************************************************************************
// Transmit MIDI Message
//*******************************************************************************************************************
void MIDI_TX(unsigned char MESSAGE, unsigned char PITCH, unsigned char VELOCITY)
{
status = MESSAGE + midichannel;
Serial.write(status);
Serial.write(PITCH);
Serial.write(VELOCITY);
}


#include <Tone.h> removed

Tone notePlayer; removed




RS_DIY_LaserHarpMOD.ino: In function 'void setup()':
RS_DIY_LaserHarpMOD:23: error: 'NOTE_G3' was not declared in this scope
RS_DIY_LaserHarpMOD.ino: In function 'void loop()':
RS_DIY_LaserHarpMOD:54: error: 'notePlayer' was not declared in this scope
RS_DIY_LaserHarpMOD.ino: In function 'void MIDI_TX(unsigned char, unsigned char, unsigned char)':
RS_DIY_LaserHarpMOD:64: error: 'status' was not declared in this scope
RS_DIY_LaserHarpMOD:64: error: 'midichannel' was not declared in this scope

Hope we can sort it out!

RS_DIY_LaserHarpMOD.ino (1.52 KB)

OK, the first error message is due to the fact that NOTE_G3 was somthing that noteplayer defined. We don't use that note declaration anymore so you can just eliminate the line that causes the error. At this point it looks fairly stupid to use an entire defined type to keep track of the sensor pin, but let's just go with it.

So delete: sensor[0].note = NOTE_G3;

Now for the note player error, this is caused because you forgot to do what you listed in your post:


notePlayer.play(sensor[activeSensor].note) replaced by MIDI_TX(144,40,127)

Now it complains that status is undefined, so all we need to do is copy that declaration from the marimba code to your code:

unsigned char status;

And do something similar for the definition of midichannel

#define  midichannel	0;

Both of these lines should go before the setup function. At this point it should compile and be ready for actual testing.

Excellent!

It is actually working!

One thing that I need to correct is the following:

Now it is triggering the same note repeatedly as far as the light beam is interrupted. Instead of this, I only need it to trigger a single note when the light beam is interrupted, and in case I want to "pluck" this note again, I first need to allow the light to hit the sensor, and interrupt it again. I dont know how to give this instruction.

Then, to finish, I have to prepare the sketch to work with 16 lasers and 16 photoresistors, each of them triggering a different note. I think I will be able to do this by my own. if not I´ll shout again!

THANKS JROORDA! you are great! :slight_smile:

RS_DIY_LaserHarpMOD.ino (1.5 KB)

Yep, I thought that would be the problem.

This is a very normal type of operation on the Arduino. What you need to do is store a boolean (true or false) value that represents the last state of the key. Then you can see if there has been a change and take the appropriate action. When there hasn't been a change you do nothing.

So...

Before the setup function add a line like this one to declare the Boolean variable (assume true = playing, false = not playing)

boolean Key1LastState = false;

Then

if (activeSensor == -1) {
    MIDI_TX(128,40,127);
  } else {
    MIDI_TX(144,40,127);
  }

Turns into

  if (activeSensor == -1 && Key1LastState == false) {
    // In this case we have a new key pressed for the first time
    MIDI_TX(128,40,127); //Play the note
    Key1LastState = true; // Set the lastState variable
  } else if (activeSensor != -1 && Key1LastState == true) {
   // In this case we have a new key released for the first time
    MIDI_TX(144,40,127);  //Stop the note
    Key1LastState = false; // Set the lastState variable
  }

2 Notes about the above code:

  1. I am away from the Arduino IDE right now, so this is uncompiled (error prone code). If it won't compile look for a typing error first.

  2. I know there are simpler ways to write the logic checks in the if statements, but these are the most readable for newcomers.

Oh, and thanks. I am glad I could help. This thread has given me a chance to use some of what I just learned in a synthesizer class I took this past January.

NOW, it is just perfect.

I will be today preparing the sketch to work with the rest of the lasers. (14 more lasers)

I think it should be fairly simple, but lets see what problems I face in between

Helping is nice already, but when helping others helps oneself, is even better!

ok. here is my attempt of making this sketch work with 2 photoresistors, 2 lasers, and two different notes per sensor:

const int SENSOR_COUNT = 1;

is now

const int SENSOR_COUNT = 2;
 sensor[0].pin = A0;

is now

sensor[0].pin = A0;
sensor[1].pin = A1;

and afterwards I made a copy the following:

  if (activeSensor == -1 && Key1LastState == false) {
    // In this case we have a new key pressed for the first time
    MIDI_TX(128,40,127); //Play the note
    Key1LastState = true; // Set the lastState variable
  }

Mainly because I saw that in your notes you said that this code "plays the note", but then I realized that actually this is not the code that plays the note, but this one:

else if (activeSensor != 0 && Key1LastState == true) {
   // In this case we have a new key released for the first time
    MIDI_TX(144,48,127);  //Stop the note
    Key1LastState = false; // Set the lastState variable
  }

I realized this by replacing MIDI_TX(144,40,127) with MIDI_TX(144,48,127), and in effect I heard a change in the pitch.Or I am completely wrong...

SO, I decided to made a copy of this part of the code: (again)

else if (activeSensor != 0 && Key1LastState == true) {
   // In this case we have a new key released for the first time
    MIDI_TX(144,48,127);  //Stop the note
    Key1LastState = false; // Set the lastState variable
  }

but it wasnt that easy. how to proceed?

RS_DIY_LaserHarpMOD2pines.ino (2.09 KB)

I realized this by replacing MIDI_TX(144,40,127) with MIDI_TX(144,48,127), and in effect I heard a change in the pitch.Or I am completely wrong...

I have checked in the following website (http://www.midi.org/techspecs/midimessages.php) and indeed MIDI_TX(144,xx,xxx) is the message to play the note.

binary / hex / dec function 2nd byte 3rd byte
10010000= 90= 144 Chan 1 Note on Note Number (0-127) Note Velocity (0-127)

well I have been trying different things to get 2 sensors with different notes working, without the desire results.

I see where are the codes that I need to modify, but I still havent found the way. things like for example:

if (DEBUG) Serial.println();
  if (activeSensor == -1 && Key1LastState == false) {
    MIDI_TX(128,40,127); //NOTE OFF
    Key1LastState = true; // Set the lastState variable
  } else if (activeSensor == 0 && Key1LastState == false) {
    MIDI_TX(128,48,127); //NOTE OFF
    Key1LastState = true; // Set the lastState variable
  } else if (activeSensor != -1 && Key1LastState == true) {
    MIDI_TX(144,40,127);  //NOTE ON
    Key1LastState = false; // Set the lastState variable
  } else if (activeSensor != 0 && Key1LastState == false) {
    MIDI_TX(144,48,127);  //NOTE ON
    Key1LastState = true; // Set the lastState variable
  }

with different combinations of "true" and "false"... Sometimes the correct sensor triggers the right order, but repeats the note non stop, etc...

ey!

This is going a little bit out of control. too many things in this sketch

I started over with a simpler version of it...

unsigned char status;
#define  midichannel	0;

int sensorValue;


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

void loop() {
  sensorValue = analogRead(A0);

  
  if (sensorValue > 700){MIDI_TX(128,40,127);
  }
  else if (sensorValue < 700){MIDI_TX(144,40,127);
  }
}



//*******************************************************************************************************************
// Transmit MIDI Message
//*******************************************************************************************************************
void MIDI_TX(unsigned char MESSAGE, unsigned char PITCH, unsigned char VELOCITY)
{
  status = MESSAGE + midichannel;
  Serial.write(status);
  Serial.write(PITCH);
  Serial.write(VELOCITY);
}

the code works, but now I am having problems with serial midi converter, so I am contacting the people of spikenzielabs for this. lets see...

Aah simplicity!

Yes, you were right to be confused by the midi messages as I had them switched around in my mind, so my comments are backwards and some of the variables are thus named in confusing ways.

Your new code looks good, but you would need to do the same exercise of adding a boolean variable to prevent continually triggering the note. To add more notes you would add more sensor pins and more sets of if and else statements with the midi note number changed for each one.

There are of course nifty ways to do this with for loops and arrays, but that is what made the other code so confusing, and copy and paste is comparatively quite easy. The for loop might save a little program space if you are doing lots of notes, but you would need to write a LOT more code before that became an issue.

I FUCKING GOT IT

:slight_smile:

it is attached.

I still dont understand completely the bolean function, but what a great thing to exist!

I will be reading on it

thanks for everything!

leoharp_ino.ino (1.14 KB)

Awesome, I am glad to see it working for you.

A boolean is just another variable type like an integer or float. A boolean simply holds a true or a false value.

In the if statements the && is a logical AND.

thus if I had two boolean variables A and B and I only wanted "some code" to run when they were both true I might write somthing like this:

if(A && B){
//Some Code
}