LilyPad ProtoSnap Plus Activity Guide

Pages
Contributors: MikeGrusin, Gella
Favorited Favorite 7

9: Theremin Project

Now that you've explored all the boards on the LilyPad ProtoSnap Plus, it's time to start combining some of those skills into more interactive projects. The first project we'll explore is creating a musical 'instrument' inspired by a theremin. A theremin is an instrument played without needing physical contact, typically utilizing antennas. In this 'theremin', you'll use readings from the light sensor as a controller for frequency of tones produced by the buzzer. We can further categorize this project an opto-theremin because it is using light as a controller. In addition to the sensor and buzzer, we'll display the light readings on the bar graph and add the button as an on/off trigger.

LilyPad Boards Used in This Activity

  • LilyPad USB Plus
  • LilyPad USB Plus's built-in LEDs
  • LilyPad Light Sensor
  • LilyPad Buzzer
  • LilyPad Button

Parts Used

New Concepts Introduced in This Activity

Using the Map() Function

The map() function is a handy function that translates one range of numbers into another range.

For example, let's say you wanted to use the light sensor (read by analogRead()) to control the brightness of a LED (written by analogWrite()), as we did back in activity 7. But the range of analogRead() (0 to 1023) doesn't match the range of analogWrite() (0 to 255).

This particular example is easy to solve in that you can simply divide the light sensor value by 4, as we did in activity 7. But it would take a bit more work to solve for arbitrary ranges that may not start at 0, as we'll be doing in this activity.

The map() function does this for you. The parameters it needs are:

result = map(value,fromLow,fromHigh,toLow,toHigh);

Where fromLow to fromHigh is the range of the original value, and toLow to toHigh is the range we want to translate that value into.

For example, here's how we'd set up map() for our earlier example:

LEDbrightness = map(lightSensorValue,0,1023,0,255);

It's worth mentioning that if value isn't within the original range, map() will still work; but it will return a result outside the target range. If your result must be within the target range, an additional function called constrain() can be used to ensure that:

result = constrain(value,low,high);

If value is already within the range of low to high, the result will be the same as value. But if value is outside that range, it will be set to low or high, whichever is closest. Our complete example would then look like:

LEDbrightness = map(lightSensorValue,0,1023,0,255);
LEDbrightness = constrain(LEDbrightness,0,255);

Example Code

To open the code, go to:

File > Examples > LilyPadProtoSnapPlus > LPP_09_Theremin

You can also copy and paste the following code into the Arduino IDE. Hit upload, and see what happens!

language:c
/*
LilyPad ProtoSnap Plus Activity 9: Theremin
SparkFun Electronics
https://www.sparkfun.com/products/14346

A Theremin is an electronic musical instrument that is played by
moving your hands over it. In this activity we'll create a Theremin
using the light sensor and the buzzer.

Follow the tutorial at: 
https://learn.sparkfun.com/tutorials/lilypad-protosnap-plus-activity-guide#9-theremin-project

This code is released under the MIT License (http://opensource.org/licenses/MIT)

******************************************************************************/

// Create variables for the pins we'll be using

int sensorPin = A2;
int buttonPin = A4;
int buzzer = A3;
int bargraphLED[6] = {15,16,17,18,19,20};

// Set the highest and lowest frequencies
// (Change these and see what happens)

int highestFrequency = 1047; // C6
int lowestFrequency = 523; // C5

void setup()
{
  int x;

  // Initialize the pins we'll be using

  pinMode(sensorPin, INPUT);
  pinMode(buzzer, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);

  for (x = 0; x <= 5; x++)
  {
    pinMode(bargraphLED[x],OUTPUT);
  }

  // Initialize the serial monitor

  Serial.begin(9600);
}

void loop()
{
  int sensorValue;
  int frequency;

  // Read the sensor value (will be 0 to 255):

  sensorValue = analogRead(sensorPin);

  // Print out the sensor reading:

  Serial.print("sensor value: ");
  Serial.println(sensorValue);

  // Display the sensor reading on the bar graph LEDs:

  barGraph(sensorValue);

  // Play a tone based on the light level:

  // The light sensor will return a value from 0 to 1023,
  // but we want to map this to a specific range of frequencies.
  // We'll use a built-in fuction called "map" that transforms one range
  // of values (0 to 1023) to another (lowestFrequency to highestFrequency):

  frequency = map(sensorValue,0,1023,lowestFrequency,highestFrequency);

  if (digitalRead(buttonPin) == LOW) // If the button is pressed:
  {
    tone(buzzer,frequency);
  }
  else
  {
    noTone(buzzer);
  }
}

void barGraph(int value)
{
  // Create a LED bargraph using value as an input.
  // Value should be in the range 0 to 1023.

  int x;

  // Step through the bargraph LEDs,
  // Turn them on or off depending on value.

  // Value will be in the range 0 to 1023.
  // There are 6 LEDs in the bargraph.
  // 1023 divided by 6 is 170, so 170 will be our threshold
  // between each LED (0,42,84, etc.)

  for (x=0; x <= 5; x++)
  {
    if (value > (x*170) )
    {
      digitalWrite(bargraphLED[x], HIGH);
    }
    else
    {
      digitalWrite(bargraphLED[x], LOW);
    }    
  }
}

What You Should See

To play the theremin, press and hold the button with one hand and cover the light sensor with the other. As the light sensor reads different light levels, the frequency on the buzzer will change and the bar graph LEDs will illuminate to show the different tones. Release the button to stop the sounds. You can also use a flashlight to shine more light on the sensor and get a large range of tones.

Understanding Your Program

Program Overview

  1. Create variables to store.
  2. Read the value from the light sensor and store in sensorValue.
  3. Print the light sensor reading to the serial monitor.
  4. Display the sensor reading on the bar graph using a custom barGraph() function.
  5. Map the values from the light sensor to the set range of frequencies (set in lowestFrequency and highestFrequency)
  6. Check to see if the button is pressed. a. If it is, send a tone() command to the buzzer using the frequency value. b. If it is not, turn the buzzer off using noTone() command.
  7. Repeat.

Code to Note

CodeDescription
map(value,inMin, inMax, outMin, outMax)
Mapping a Range of Values:
The map() function is a tool in Arduino used to take a value from one range and constrain it to another. This is especially helpful when you want to take readings from an analog input (which can range from 0-1023) and use them to display directly on an output (which ranges from 0-255). In previous examples, the code used division to make these two variables compatible. The map function is a little more sophisticated.

The map value has five parameters, listed below:

Value: sensorValue
The values/variable to map. In this code we are using the data we read from the light sensor stored in sensorValue.

inMin: 0
inMax: 1023
The minimum and maximum bounds of the value to be mapped. In this case, since sensorValue is from an analog sensor, these will be 0-1023.

outMin:lowestFrequency
outMax: highestFrequency
The minimum and maximum bounds you want to map the current value to. In this case, we've chosen a range of frequencies stored in the lowestFrequency and highestFrequency variables set at the beginning of the program.

Read more about the map() function on the Arduino Reference site.

Coding Challenges

  • Adjust lowestFrequency and highestFrequency to use different ranges for the tones the theremin creates.

  • Try adding additional sound controls by adding another if/else statement using the switch. Suggestions: Turning a light pattern on/off that coordinates with the sound or switching between different frequency ranges.