Contents

Navigation:

Light Theremin code

/* Light Theremin
   A. J. Misquitta (2013)
   based on code by Amanda Ghassaei
   
   Use the sine wave generated by an 8bit DAC as the foundation
   for a light theremin. 
   
   The sketch first performs a callibration step with beeps to help
   the user perform the necessary actions. With the callibration, the
   sketch determines the light and dark levels registered by the 
   phototransistor. This makes the range of tones generated by the
   light theremin fairly stable (though in low light the *number* of
   tones available will be limited). 
   Thereafter the pitch can be controlled by varying the light incident
   on the phototransistor - presumably using your hand as a mask.
   
   There is no software volume control possible. Instead, it is expected
   that the theremin will contain a hardware volume control.
*/

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
*/

byte sine[] = {
  127, 134, 142, 150, 158, 166, 173, 181, 188, 195,
  201, 207, 213, 219, 224, 229, 234, 238, 241, 245,
  247, 250, 251, 252, 253, 254, 253, 252, 251, 250,
  247, 245, 241, 238, 234, 229, 224, 219, 213, 207,
  201, 195, 188, 181, 173, 166, 158, 150, 142, 134,
  127, 119, 111, 103,  95,  87,  80,  72,  65,  58,
   52,  46,  40,  34,  29,  24,  19,  15,  12,   8,
    6,   3,   2,   1,   0,   0,   0,   1,   2,   3,
    6,   8,  12,  15,  19,  24,  29,  34,  40,  46,
   52,  58,  65,  72,  80,  87,  95, 103, 111, 119,
 };

// Control parameters: These can be altered.
int num_waves = 50; // number of sine waves between phototransistor readings
int max_delay = 30; // maximum delay (the larger the deeper the pitch can go)
int min_delay = 4;  // minimum delay (the smaller the higher the pitch can go)

//Sketch variables and parameters. Do not alter these.
int samples = 100; // number of samples in sine[]
int bright = 0; // bright reading
int dark = 0; // dark reading
int range_of_delays = (max_delay - min_delay);
int range_of_readings = 0;

void setup(){
  //set digital pins 0-7 as outputs
  for (int i=0;i<8;i++){
    pinMode(i,OUTPUT);
  }
  
  // The pitch phototransistor will be read at analogue pin A2:
  pinMode(A2,INPUT);
  
  //Start the serial monitor:
  Serial.begin(115200);
  
  /*
    Bright calibration.
    Remove your hands from the phototransistors. 
  
    The calibration uses the average of 10 readings 
    spread over around 5 secs.
    A beep will sound when the calibration is over.
  */
  Serial.println("Calibration: BRIGHT : No hands!!!");
  beep(12,500);
  delay(2000);
  bright = 0;
  for (int i = 1; i<10; i++){
    bright = bright + analogRead(A2);
    delay(500);
  }
  bright = bright/10;
  Serial.print("Bright level = ");
  Serial.println(bright);
  
  /*
    Dark calibration. 
    Place your hand in the lowest position comfortably possible
    over the phototransistor.
    
    As with the bright calibration, we use the average of 10 readings
    performed over 5 secs. 
    A beep will sound when this is done.
  */
  Serial.println("Calibration: DARK : Cover it!!!");
  beep(12,500);
  delay(2000);
  dark = 0;
  for (int i = 1; i<10; i++){
    dark = dark + analogRead(A2);
    delay(500);
  }
  dark = dark/10;
  Serial.print("Dark level = ");
  Serial.println(dark);
  
  Serial.println("Calibration complete.");
  delay(2000);
  beep(8,500);
  
  //This is the range of expected readings:
  range_of_readings = bright - dark;
}

void loop(){
  //Read in the phototransistor output:
  int photores = analogRead(A2);
  /* Calculate the pitch-delay.
     A simple linear interpolation is used to map the range of phototransistor
     readings to the range of expected values of the pitch-delay.
     The callibration step doesn't ensure we won't see a negative
     pitch-delay. This could happen if the light levels changed after the
     callibration was performed. So we use the abs() function.
  
     This calculation needs to be make faster. Divisions are expensive, so
     we should find a way to get rid of the division.
  */
  int pitch_delay = (range_of_delays*(photores-dark))/range_of_readings;
  pitch_delay = abs(pitch_delay) + min_delay;
  /*
    Another way of performing the mapping is to use the map()
    function. This is faster than the above and should be attempted.
    It will not check for negative values so some kind of offset 
    may be needed.
    
    The map() function maps the value of the first entry (variable photores)
    from the range [light,dark] to the range [max_delay,min_delay]
    With the exception of the abs() step, this is exactly what the above
    statements perform. But, this function is much better optimized so 
    is substatntially faster than the above.  
  */
  //int pitch_delay = map(photores,light,dark,max_delay,min_delay);
  
  // Uncomment the following lines if you wish to see the readings on the 
  // Serial monitor:
  /*
  Serial.print(photores);
  Serial.print("  ");
  Serial.println(pitch_delay);
  delay(500);
  */

  //Generate a specified number of sine waves. 
  for (int n=0; n<num_waves; n++){
    for (int t=0; t<samples; t++){
      PORTD = sine[t];
      delayMicroseconds(pitch_delay);
    }
  }
  
}

void beep(int pitch, int length){
  /* This is a beep function. It is similar to the tone()
     function in the Arduino library, but this one uses the
     DAC to generate the beep. 
     
     It accepts two arguments: the pitch delay (use values between
     6 and 12) and pitch length (use values around 500).
  */
  for (int n = 0; n < length; n++){
    for (int t = 0; t < samples; t++){
      PORTD = sine[t];
      delayMicroseconds(pitch);
    }
  }
}

AJMPublic/teaching/arduino-pi/projects/arduino/dac-audio/code-theremin (last edited 2021-04-14 13:14:55 by apw109)