arduino-arpa-laser-midi

How to Build a Laser Harp with Arduino

  • 7 min

If you grew up watching Jean-Michel Jarre’s concerts in the 80s, you surely remember that iconic moment when he played an instrument made of green light beams. Today we are going to build our own version: the laser harp.

This project is possibly one of the most visually striking you can make. Visually it has that “I touch the air and music plays” appeal, which looks fantastic in a demo.

But from an engineering point of view, it’s a fantastic excuse to master one of the most important concepts in analog electronics: The Variable Voltage Divider and Sensor Calibration.

Safety first: We are going to work with lasers. Even though we use low-power 5V modules (<5mW), we should NEVER look directly at the emitter or point it at the eyes of people or animals. Reflection on metallic surfaces can also be dangerous.

Detecting the absence of light

Unlike a button that closes a mechanical circuit, here we are detecting an optical interruption.

The system consists of two aligned parts:

  1. Emitter: A laser diode that sends a concentrated beam of light.
  2. Receiver: A Photoresistor (LDR) or a photodiode that constantly receives that beam.

While we are not touching the harp, the laser shines on the LDR. The LDR lowers its resistance drastically (there is a lot of light). When we put our hand in, we block the beam, the LDR is in “shadow” and its resistance increases.

Voltage divider

Arduino cannot measure resistance directly, only voltage. To convert the LDR’s resistance variation into a readable voltage (0-5V), we use a voltage divider.

  • Resting State (Light): R_LDR is low (e.g., 500Ω). Vout will be high because the LDR is connected to 5V.
  • Active State (Shadow): R_LDR is high (e.g., 50kΩ). Vout will be low because the fixed resistor to GND dominates.

This assumes we put the LDR in the “upper” part of the divider, as we do in the assembly. If we put it on the bottom, the logic is reversed. Both are valid as long as we account for it in the code.

Components

  • [1x|blue] Arduino Uno | The brain of the project
  • [3x|red] 5V Laser Modules | Light emitters {standard red dot}
  • [3x|orange] Photoresistors (LDR) | Light receivers {GL5528 or similar}
  • [3x|gray] 10kΩ Resistor | Pull-down voltage dividers
  • [1x|purple] Passive Piezoelectric Buzzer | Sound generation
  • [Various|gray] Rigid Structure | Wood, PVC or 3D printing {critical alignment}
  • Rigid structure (wood, PVC, or 3D printing) to keep the lasers aligned with the LDRs. Alignment is critical.

Connection diagram

We will connect the LDRs to the analog inputs.

Lasers

The lasers will be permanently on while the harp is powered. Mount them well aligned and without pointing at eyes.

[ { “from”: “Lasers VCC (all)”, “to”: “Arduino 5V”, “color”: “red”, “note”: “Always on” }, { “from”: “Lasers GND (all)”, “to”: “Arduino GND”, “color”: “black” } ]

LDR Sensors

Each string has an LDR and a 10kΩ resistor forming a pull-down divider.

[ { “from”: “LDR 1 + 10kΩ”, “to”: “Pin A0”, “color”: “orange”, “note”: “Pull-down: LDR to 5V, R to GND” }, { “from”: “LDR 2 + 10kΩ”, “to”: “Pin A1”, “color”: “orange”, “note”: “Pull-down: LDR to 5V, R to GND” }, { “from”: “LDR 3 + 10kΩ”, “to”: “Pin A2”, “color”: “orange”, “note”: “Pull-down: LDR to 5V, R to GND” }, { “from”: “10kΩ Resistors”, “to”: “Arduino GND”, “color”: “black” } ]

Buzzer

The buzzer or passive transducer generates the note of the active string.

[ { “from”: “Buzzer (+)”, “to”: “Pin 8”, “color”: “purple” }, { “from”: “Buzzer (-)”, “to”: “Arduino GND”, “color”: “black” } ]

We have configured a Pull-Down divider.

  • Lots of light (Laser hitting) = High Reading (~1023).
  • Little light (Hand blocking) = Low Reading (~200).

The Code

The programming challenge here is calibration and polyphony (or lack thereof). The Arduino Uno can only generate one tone at a time with the tone() function. If you try to play two strings at once, it will play one or the other, or do strange things.

To manage the notes, we will use a standard header file pitches.h containing the frequencies of musical notes, or define the frequencies manually for simplicity.

Definitions and Setup

// Frequencies of the notes (C4, E4, G4)
// You can add more notes to complete a scale
#define NOTE_C4  262
#define NOTE_E4  330
#define NOTE_G4  392

const int numStrings = 3;
const int ldrPins[] = {A0, A1, A2};
const int notes[] = {NOTE_C4, NOTE_E4, NOTE_G4};

const int buzzerPin = 8;

// --- CALIBRATION ---
// This value is CRITICAL. You must look at the Serial Monitor to adjust it.
// If the laser hits the LDR, you will read ~900. If you put your hand in, you will read ~300.
// The threshold should be in the middle.
const int THRESHOLD = 600; 

void setup() {
  Serial.begin(9600);
  pinMode(buzzerPin, OUTPUT);
}
Copied!

Main Loop

We will constantly read the sensors. If we detect that a reading falls below the threshold, we activate the sound.

We will implement a priority logic: if you interrupt several beams, the string with the highest index will sound, because each subsequent detection overwrites the previous one and the buzzer is monophonic.

void loop() {
  int activeString = -1; // -1 means no one is playing anything

  // Go through all the strings
  for (int i = 0; i < numStrings; i++) {
    int reading = analogRead(ldrPins[i]);
    
    // Debugging: Uncomment this to see the values and calibrate
    /*
    Serial.print("String "); Serial.print(i);
    Serial.print(": "); Serial.println(reading);
    */

    // Pull-Down logic with LDR to 5V and resistor to GND:
    // Light = R_ldr low = V_out HIGH.
    // Shadow = R_ldr high = V_out LOW.
    // Therefore, if reading < THRESHOLD -> We are playing it.
    
    if (reading < THRESHOLD) {
      activeString = i;
    }
  }

  // Sound Management
  if (activeString != -1) {
    // If there is an active string, play its note
    tone(buzzerPin, notes[activeString]);
  } else {
    // If not, silence
    noTone(buzzerPin);
  }
  
  delay(10); // Small pause for stability
}
Copied!

Troubleshooting common problems

  1. The harp sounds by itself: This means the laser is not pointing perfectly at the center of the LDR, or there is too much ambient light in the room confusing the sensor.
  • Solution: Use black tubes (heat shrink or painted straws) around the LDRs so they only “see” the laser light and not the ambient light.
  1. I have to put my hand very close for it to sound: Your threshold is poorly calibrated. Open the Serial Monitor, see what value it gives when you are not touching it and what value it gives when you are touching it, and set the THRESHOLD constant right in the middle.
  2. The sound is annoying: A passive piezo produces a very sharp timbre. You can reduce the level with a series resistor chosen for the transducer. An 8 Ω speaker should not be connected directly to the pin: it needs a transistor or amplifier stage.

MIDI Output via USB

Making noises with a buzzer is fun, but what if we use the harp to control a professional synthesizer on the computer?

If you use an Arduino Leonardo or Micro (with native USB), you can use a compatible USB MIDI library. Instead of calling tone(), you would send noteOn and noteOff messages to the computer.

void noteOn(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
  MidiUSB.flush();
}
Copied!

This way, your laser harp could sound like a grand piano, a violin, or… well, like Jean-Michel Jarre’s synthesizers, which is appropriate.