SPARKI THEREMIN LESSON

See Also: Arduino Boot Camp

Introduction

A theremin is a musical instrument that produces different sounds depending on the position of the player’s hand around the theremin.

We can use Sparki as a theremin! Sparki has a buzzer that can play various tones. We will use Sparki’s ultrasonic rangefinder and  Sparki’s three light sensors to capture the position of the player’s hand.

In this lesson, we will use the ultrasonic rangefinder to play a range of frequencies depending on the distance measured. We will use Sparki’s light sensor readings to decide which octave to play a particular note.

This lesson requires a lot of basic coding with applying concepts like variables, functions, and controls especially  if statements and for loops, so be sure to review that! 🙂

What you’ll Need

  • A Sparki!

How It Works

As we have learned in Making Sounds, Sparki’s buzzer can convert electrical signal to sound waves. Sound waves are vibrations in air pressure. The vibration frequency cycles per seconds (or hertz) of the sound wave determine how high or low the sound is perceived. This is also known as the pitch in musical terms. We will determine the pitch that the buzzer outputs based on the signals read from Sparki’s light sensors and ultrasonic rangefinder.

Although it varies from person to person, the human ear can hear around 20 Hertz to 20,000 Hertz.  An octave is the interval between one musical pitch to another with half or double its frequency. We will be playing notes at different octaves. 

The Coding Challenge

The theremin challenge is easier to understand if we specify the requirements of what we will actually do. To keep things simple, Sparki will only read and play a sound based on a particular sensor at one time. It will be very complicated if Sparki listens to all the sensors all at once! We will allot a few counts at a time for each sensor. In essence, Sparki listens to and plays music based on the ultrasonic rangefinder for a few counts, and then do the same based on the right light sensor. After that, it will read signals and play music based on the center light sensor, and finally based on left light sensor. Then the loop repeats! To tell us which sensor Sparki is listening to, Sparki will light up a particular color of its RGB LED.

To make it easier for the music player, we will limit the distance range to one centimeter to fifty centimeters only. If Sparki senses a longer distance, Sparki will keep quiet. The maximum distance is chosen arbitrarily, so feel free to change it as you see fit 🙂

Are you ready? Let’s get started!

Challenge Outline

  1. (count #1-500) Sparki will light up the color BLUE to signal that it is reading the ultrasonic rangefinder. It will play a frequency between 50 Hz and 5000 Hz proportional to the distance readings.
  2. (count #601-1000) Sparki will light up the color RED to signal that it is reading the right light sensor. It will play different octaves of the mid-C note (261 Hz) based on the reading.
  3. (count #1001-1500) Sparki will light up the color YELLOW to signal that it is reading the center light sensor. It will play different octaves of the F note (370 Hz) based on the reading.
  4. (count # 1501-2000) Sparki will light up the color GREEN to signal that it is reading the left light sensor. It will play different octaves of the A note (440 Hz) based on the reading.
  5. Repeat steps 1 to 4 indefinitely!

Note that the count range (1-500 counts per sensor), frequency range for the ultrasonic sensor (50 Hz – 5000 Hz), LED colors (blue, red, yellow green), and notes (mid-C, F, A) were chosen arbitrarily. Feel free to use what you like!

Splitting the Challenge

If we look at the challenge, it might seem rather complicated, but don’t get overwhelmed! The trick is to split the challenge into manageable bite-size pieces. This is exactly what we’re going to do.

  1. Make an independent program for just the ultrasonic rangefinder (Step 1 in the Challenge Outline). Make Sparki play a frequency between 50 Hz and 5,000 Hz proportional to the distance readings!
  2. Calibrate the light sensors. The readings of the light sensors can be affected by many factors in the environment like the ambient light of the room or if Sparki is outside, the time of day and weather (sunny or cloudy). So we need to check what maximum or minimum values we will be able to read at each light sensor at a particular time.
  3. Make an independent program for just one light sensor.Try to make the right sensor work first (Step 2 of the Challenge Outline). If you’re successful, making the center and left sensor work will be a piece of cake!
  4. Put all the light sensors code together. Put the independent programs you’ve made for the right, center, and left sensor into one program. Use what you made in Step 3 of Splitting the challenge.
  5. Put them all together to complete the code challenge! (Finally, phew!)

Ultrasonic Rangefinder Theremin 

The key concept here is using the function map()   on line 17. We have a minimum and maximum value we’ve specified for the ultrasonic rangefinder. We also have specified the minimum and maximum frequency range to output. Instead of calculating manually to match the particular sensor values to the possible pitch multipliers, we can use the function map() to re-’map’ the sensor reading from the “ultrasonic rangefinder” range to the frequency range.


#include <Sparki.h>

void setup() {
}

void ultrasonicTheremin(){

int maxDist = 60;
int minFreq = 50;
int maxFreq = 10000;
int freq = 0;

sparki.RGB(RGB_BLUE);
int ping = sparki.ping();

if ( ping > 1 && ping <= maxDist){
freq = map(ping, 0, maxDist, minFreq, maxFreq);
sparki.beep(freq);
} else {
sparki.noBeep();
}
}

void loop(){
ultrasonicTheremin();
}
*********
Ultrasonic Theremin Code for Sparki:https://gist.github.com/mithi/eebba54ed2e4401a2be9
*********

Light Sensor Calibration

The readings of the light sensors can be affected by many factors in the environment like the ambient light of the room or if Sparki is outside, the time of day and weather (sunny or cloudy). So we need to check what maximum or minimum values we will be able to read at each light sensor at a particular time.

Let’s make a program to do just that! This program is only for the right light sensor. When you run this program, start by putting your finger at the top of the right light sensor so no light can get through. After this, gradually lift your finger up to about three centimeters directly above the light sensor.

#include <Sparki.h>

int sensorLow = 80000;
int sensorHigh = 0;
int sensorValue = 0;
int calibrationTime = 5000;

void setup(){

signal(3, 75);

int startTime = millis();
int endTime = startTime + calibrationTime;

while (millis() < endTime){

sensorValue = sparki.lightLeft();

if(sensorValue > sensorHigh){
sensorHigh = sensorValue;
}

if(sensorValue < sensorLow){
sensorLow = sensorValue;
}
}

signal(10, 30);
displayCalibratedValues(sensorLow, sensorHigh);
}

void loop(){
}

void signal(int n, int interval){
for (int i = 0; i < n; i++){
sparki.RGB(RGB_RED);
sparki.beep();
delay(interval);
sparki.noBeep();
sparki.RGB(RGB_OFF);
delay(interval);
}
}

void displayCalibratedValues(int sensorLow, int sensorHigh){

sparki.clearLCD();
sparki.println("Calibrated Values for Right Sensor:");
sparki.print("lowest value:");
sparki.println(sensorLow);
sparki.print("highest value:");
sparki.println(sensorHigh);
sparki.updateLCD();
delay(5000);
}
*********
Calibration Code for Light Sensor Theremin

https://gist.github.com/mithi/37d808e023290bdd5223
*********

For lines 1 to 6, we are just including Sparki’s library and declaring variables which we will use for the calibration process. The initial value of sensorLow is just some arbitrary high number. (Why?)

The millis() function is used to time the calibration phase which is exactly five seconds (or 5000 milliseconds). This function returns the number of milliseconds that has passed since the last time Sparki was turned on or reset.

The lines 17 to 25 inside the while loop reads the signal from the light sensor and checks whether or not this is the lowest or highest value it has seen so far. If so, it updates the appropriate variable accordingly.

We are also using two helper functions: signal() and displayCalibrationValues(). We use the signal() function to signal the start and end of the calibration phase. We use the displayCalibrationValues() to display the values on Sparki’s LCD. These are the values we have derived from the calibration phase. Can you explain in your own words what each helper function does?

Do this for each light sensor, so that we know what is the maximum and minimum reading of each.  You can change the calibration period if you like! 🙂

One Light Sensor Theremin

The values of sensorLow and sensorHigh are derived from the callibration phase. We have a maxPitchCount variable which specifies the number of octaves that Sparki will play of the particular note (why?). Like in the ultrasonic rangefinder theremin, we used the map() function again. Can you explain what is happening inside the play() function?

This program only uses the left light sensor. Modify this code to work for the center light sensor and right light sensor. Don’t forget to change the color of Sparki’s RGB LED! 🙂


#include <Sparki.h>

int sensorLow = 335;
int sensorHigh = 872;
int sensorValue;
int freq = 440;
int maxPitchCount = 20;

void setup(){}

void play(int sensorVal, int sensorLow, int sensorHigh, int freq){
pitchMultiplier = map(sensorVal, sensorLow, sensorHigh, 1, maxPitchCount);
sparki.beep(pitchMultiplier*freq);
delay(20);
}

void loop(){
sparki.RGB(RGB_LED);
sensorValue = sparki.lightLeft();
play(sensorValue, sensorLow, sensorHigh, freq);
}

*********
Single light sensor theremin for Sparki
https://gist.github.com/mithi/c9d667da72b4c3b913fe
*********

Three Light Sensor Theremins!

Firstly, we defined a couple of constant integers, namely: freqA, freqF, freqmidC, countMax, delayPerBeep, maxPitchCount, RIGHT, LEFT, and CENTER. Let’s go through them one by one.

As you might have guessed, freqA, freqF,and freqmidC are just for storing the base frequencies of the note we want each of the three light sensors to play. delayPerBeep is just the time before Sparki changes the tone it is playing. We have already used maxPitchCount and countMax for the single light sensor theremin. Do you remember?

The most interesting ones that we have defined are RIGHT, CENTER, and LEFT. What do you think they are for? Notice that if re-write the code such that without them, it will be a little bit more difficult to understand.

To make this program, we build upon the code we have made for the single light sensor theremin. We still have our play() function. The most important part of the code is line 40 to line 55 (that’s only 16 lines of code!). We put three if-statements inside a for-loop. Can you explain what this part of the code does?

We also used another helper function display(). What do you think this is for? Will the program work without it? Why or why not?

The last part is line 57 to line 61. Why do we have to include this part?

#include <Sparki.h>

const int RIGHT = 0;
const int CENTER = 1;
const int LEFT = 2;

const int freqA = 440;
const int freqF = 370;
const int freqmidC = 261;

const int countMax = 500;
const int delayPerBeep = 10;
const int maxPitchCount = 15;
int state = 0;

void setup(){
}

void play(int sensorValue, int sensorLow, int sensorHigh, int freq){
int pitchMultiplier = map(sensorValue, sensorLow, sensorHigh, 1, maxPitchCount);
sparki.beep(pitchMultiplierr*freq);
display(state, sensorValue);
delay(delayPerBeep);
}

void display(int state, int val){
sparki.clearLCD();
sparki.print("state: ");
sparki.println(state);
sparki.print("sensor value: ");
sparki.println(val);
sparki.print("counts left:");
sparki.println(countMax - count);
sparki.updateLCD();
}

void loop(){

for(int count = 0; count < countMax; count++){
if( state == RIGHT){
sparki.RGB(RGB_RED);
play(sparki.lightRight(), 30, 368, freqmidC);
}

if( state == CENTER){
sparki.RGB(RGB_YELLOW);
play(sparki.lightCenter(), 10, 239, freqF);
}

if( state == LEFT){
sparki.RGB(RGB_GREEN);
play(sparki.lightLeft(), 21, 349, freqA);
}
}

state++;

if (state >= 3){
state = RIGHT;
}
}

*********
Light Sensor Theremin (right, center, left) for Sparki
https://gist.github.com/mithi/f371ff088e1f1f373665
*********

Theremin Challenge Complete!

Wear your thinking cap! How can you put everything we have learned from the three light sensors theremin and the ultrasonic sensor theremin into a single program to complete the theremin challenge?

#include <Sparki.h>

const int ULTRASONIC = 0;
const int RIGHT = 1;
const int CENTER = 2;
const int LEFT = 3;

int freqA = 440;
int freqF = 370;
int freqmidC = 261;

int maxPitchCount = 15;
int state = 0;
int countMax = 500;
int delayPerBeep = 10;

void setup(){
}

void display(int state, int val){
sparki.clearLCD();
sparki.print("state: ");
sparki.println(state);
sparki.print("sensor value: ");
sparki.println(val);
sparki.print("counts left:");
sparki.println(countMax - count);
sparki.updateLCD();
}

void ultrasonicTheremin(){

int maxDist = 60;
int minFreq = 50;
int maxFreq = 10000;
int freq = 0;

int ping = sparki.ping();

if ( ping > 0 && ping <= maxDist){
freq = map(ping, 0, maxDist, minFreq, maxFreq);
sparki.beep(freq);
} else {
sparki.noBeep();
}

display(ULTRASONIC, ping);
}

void play(int sensorValue, int sensorLow, int sensorHigh, int freq){
int pitchMultiplier = map(sensorValue, sensorLow, sensorHigh, 1, maxPitchCount);
sparki.beep(pitchMultiplier*freq);
display(state, sensorValue);
delay(delayPerBeep);
}

void loop(){

for(count = 0; count < countMax; count++){

if (state == ULTRASONIC){
sparki.RGB(RGB_BLUE);
ultrasonicTheremin();
}

if( state == RIGHT){
sparki.RGB(RGB_RED);
play(sparki.lightRight(), 30, 368, freqmidC);
}

if( state == CENTER){
sparki.RGB(RGB_YELLOW);
play(sparki.lightCenter(), 10, 239, freqF);
}

if( state == LEFT){
sparki.RGB(RGB_GREEN);
play(sparki.lightLeft(), 21, 349, freqA);
}
}

state++;

if (state >= 4){
state = ULTRASONIC;
}
}

*********

Total Theremin Challenge for Sparki
https://gist.github.com/mithi/5b76108acb1bdc4ece75
*********

Other Things To Do

 In this challenge, notice that we heavily used if-statements. Can we use switch-statements instead? Sparki also has a lot of other interesting sensors. For example, it has five infrared reflectance sensors that are usually used for detecting edges and lines. Can you make a theremin using them?

References

Arduino Video Tutorial 04: Light Theremin | RS Components
https://www.youtube.com/watch?v=57S3dylfw3I
Arduino Theremin
http://makezine.com/projects/arduino-theremin/
Arduino Lesson 10. Making Sounds
https://learn.adafruit.com/adafruit-arduino-lesson-10-making-sounds?view=all

 

See Also: Arduino Boot Camp