My Stuff‎ > ‎

Lava lamp MIDI controller

How to make a lava lamp send radomly changing MIDI continuous controller messages that you can map within a synthesiser etc. You will need an Arduino compatible microcontroller board for this.. there are many cheap Arduino styley boards around you can even build up your own from scratch - but I used a Miduino PCB from Tom Scarff (http://tomscarff.110mb.com/miduino/miduino.htm)
 
Just to give you an idea of what its all about....this first video shows it mapped to synthesizer parameters in Reason, making some creepy ambient sounds
 
 
 
This second video shows it mapped to pattern changes in Reason, making some crazy random beats
 
 
 
The basis of the project is a standard lava lamp (a large one will be easier to work with) and 12 light dependent resistors ("photocells"). I don't even know what resistence mine are - they were cheap off ebay - the important thing is you can make pairs of the same resistance
 
Rather than trying to work directly with the resistance of the LDRs, I arranged them in 6 "voltage dividing" pairs as shown in the schematic below.
Each pair of resistors (e.g. R1 and R2) bridges from AGND (0V) to AREF (+5V) and it is the midpoint voltage that is measured by connecting it to the analog input pin (A0 ... A5) of the Arduino... so what we really measure is the difference in resistance between the LDRs in the pair. This is an important fact... it means that the 2 LDRs should be placed far apart on the lava lamp so they are getting light from different parts of the lamp... otherwise the measured voltage may not change at all.
 
I poked the LDR legs through holes I made in some velcro cable ties, and use the velcro ties (joined end to end) to hold the LDRs in place. I have 3 sets of 4 LDRs and I tried to arrange each set of 4 LDRs so they'd be spaced evenly along the velcro when attached round the lamp.
 
Using the designators above, I spread the LDR pairs between the 3 layers as follows: top row R1, R5, R7, R11. Mid row R3, R6, R9, R12. Bottom row R2, R4, R8, R10. I found that coiling the wiring around a pencil gives a suitable b-movie mad scientist look :o)
 

 

Now you will need to get your Arduino to send MIDI.... if you used a Miduino it has a labelled MIDI OUT port, however this is just connected to the serial TX pin of the Microcontroller and you can send MIDI from any Arduino clone... basically serial TX (atmega168 pin number 3) goes to pin 5 of the MIDI socket via a 220R resistor. MIDI socket pin 4 is connected to +5V again through a 220R resistor. Find a diagram to check the layout of the pin numbers on the 5-pin MIDI socket, they are kinda wierd.
 
Now here is the Arduino code
 

#define BUFLEN 50
// define a class to represent one of the controllers
class CController
{
public:
  int analogPin;
  int midiChannel;
  int midiController;
 
  int minReading;
  int maxReading;
  int currentValue; 
  int history[BUFLEN];
 
  CController()
  {
     minReading = -1;
     maxReading = -1;
     analogPin = 0;
     midiChannel = 0;
     midiController = 0;
     currentValue = -1;
  }
 
 void Monitor(int startup)
 {
     // read the raw analog value 0-1023
     int value = analogRead(analogPin);
    
     // add the value into the history buffer
     long tot = 0;
     for(int j=0; j<BUFLEN-1;++j)
     {
       history[j] = history[j+1];
       tot += history[j];
     }
     history[BUFLEN-1] = value;
     tot+=value;
    
     // if the history buffer is not yet
     // full then return
     if(startup)
       return;
      
     // get the average value from history
     value = (float)tot/BUFLEN;
    
     // remember highest and lowest values
     if((minReading == -1) || (minReading > value))
         minReading = value;
     if((maxReading == -1) || (maxReading < value))
         maxReading = value;
        
     // get the range of known readings
     int range = maxReading - minReading;    
     if(range < 1)
          range = 1;
        
     // scale the current value into the range
     int newValue = 127.0 * (((float)value - (float)minReading) / (float)range);
    
     // has the value changed?
     if(newValue != currentValue)
     {
              // light the LED
              digitalWrite(13,HIGH);
         
              // write out the CC message
              Serial.print(0xB0 | midiChannel, BYTE);
              Serial.print(midiController, BYTE);
              Serial.print(newValue, BYTE);
         
              // unlight the light
              digitalWrite(13,LOW);        
         
              // remember the value
              currentValue= newValue;
    }
 }
};
// array of controller objects
int startupCount = BUFLEN;
CController C[6];
void setup()
{
  // assign the input pins
  C[0].analogPin = 0;
  C[1].analogPin = 1;
  C[2].analogPin = 2;
  C[3].analogPin = 3;
  C[4].analogPin = 4;
  C[5].analogPin = 5;
  // assign the midi controllers
  C[0].midiController = 22;
  C[1].midiController = 23;
  C[2].midiController = 24;
  C[3].midiController = 25;
  C[4].midiController = 26;
  C[5].midiController = 27;
  pinMode(13, OUTPUT);    // Pin for visual feedback
  Serial.begin(31250);    // MIDI baudrate
  Serial.flush();
}
void loop()
{
  // pass a "starting up" flag while the history
  // buffers are initially being filled 
  int startupFlag = 0;
  if(startupCount > 0)
  {
    startupFlag = 1;
    --startupCount;
  }
 
  // run the monitor procedure for each controller in turn
   for(int i=0;i<6;++i)
     C[i].Monitor(startupFlag);
    
   delay(1);
}
 
This code establishes a range of ADC values that is being read from each pair of LDRs and scales the input accordingly so that the MIDI controller value uses the full 0-127 range (the real variation in input voltage is quite small so this upscaling is needed to get something interesting). Higher or lower values will increase the range accordingly.
 
One issue with this is that we're magnifying the usual ADC noise so that without some attempt at smoothing, the MIDI controller values would be jumping all over the place. I am smoothing the readings with a 50 value history buffer that we average with each new reading. This goes a long way to killing the ADC noise. There is a "startup flag" to prevent any averages being made before we fill the buffer.
 
Values are only sent out when they change and they are sampled at 1 ms intervals. The midi channel is hardcoded at 1.
 
The rest is up to you... this project makes no sound by itself.. you need to assign the 6 MIDI controller streams (CC#22 .. CC#27) to settings in a synthesiser etc.. experiment and see what happens!
 
I owe a lot to Tom Scarff's Miduino board and projects, which were a great introduction into this stuff.. If you don't already have an Arduino board and you're interested in MIDI stuff then the Miduino board is highly recommended (in particular it includes the additional components needed to support MIDI IN - it also has good idiot-proofing - polarity reversal protection diodes etc - which saved me blowing it up several times in the early days)
 
Comments