Home‎ > ‎CS 11M‎ > ‎

Project 7: Chip Tunes

Objectives

  • Use strings and arrays
  • Understand how different frequencies are used to make music
  • Write functions to get user input
  • Make use of reference parameters

Academic Honesty

Read the Scholastic Honesty Policy and Assignment Integrity policies of the syllabus. Here are some clarifications for this particular assignment:

  • You are expected to work either by yourself or with one other student of this class following the rules of Pair Programming for Homework Assignments.
  • You may NOT give a copy of your code to your designated pair-programming partner if you did not develop the code together.
  • You must breadboard the circuits yourself or with a pair-programming partner.
  • You may NOT look at another student's code until you complete and submit this assignment, except for code you develop together with your pair-programming partner.
  • You may get help from people other than your pair-programming partner if you get stuck, but only if they show you like examples and not show or tell you the exact code to type.

Part 1: Chiptune Player

Setup

Add the speaker to your Arduino project:

Attach the Speaker

Background

Today, computers are capable of playing audio that was directly recorded from a real world source. This wasn't always the case. Classic gaming systems like the Atari 2600 and the original Nintendo Entertainment System contained microchips that worked like a keyboard synthesizer. Those chips could only play pre-defined notes just like a piano can only play the notes corresponding to its keys. Despite their limitations people created catchy chiptunes that went along with games.  Here's an example chiptune from Super Locomotive from  Wikipedia

In this project you'll implement a simplified version of an audio chip. 

Making Sense of Notes

Your project, named chiptune, will read a simplified music notation and play it. Each note is a specific frequency that you send to the speaker using Arduino's tone() function. The table below lists the notes your program should understand with their frequencies and the letter that will be used to play the tone:  

 Note  Code Letter  Action 
 Middle C  c  261 Hz
 C#   C  277 Hz
 D  d  294 Hz
 D#  D  311 Hz
 E  e  330 Hz
 F  f  349 Hz
 F#  F  370 Hz
 G  g  392 Hz
 G#  G  415 Hz
 A  a  440 Hz
 A#  A  466 Hz
 B  b  494 Hz
   -  Stretch last note
   (space)  Rest (no sound)

There are two special characters. They are used to "stretch" a note when a longer sound is needed and to make a musical "rest" when a gap in the sound is needed. Make a function in your program that translates letters into frequencies, ignoring for now the characters. The function should look something like this:  

/* getNote()
 *  char note: The letter that represents the note
  *  returns: The note's frequency  
 */

int getNote(char note) {
  // if-elseif-else goes here!
}
 
Once you have that working be sure you can play a scale: 

const int speakerPin = 3;

void setup() {
  string scale = "cdefgabagfedc";
  for (int i=0; i<scale.length(); i++) {
    tone(speakerPin, getNote(scale[i]));
    delay(200);
  }
  noTone(speakerPin);
}

Playing a Melody 

Now that you can play a scale it's time to play a melody. The first thing you must do is read the serial input for the melody. Here's how to read an entire melody from cin: 

    string melody; 
    getline(cin, melody);

The geline function reads all text until a newline. That will allow you to get the spaces, which are essential for the player. A melody will be input all at once like this: 

  agfgaaa ggg aaaa
 

The melody ends with the newline ('\n') character. When you read the newline character you should stop reading input and play the melody. You must take into consideration the special characters when you're playing your melody. Your program will have to keep track of time in order to make the melody sound right. Consider the difference between these two inputs:

input: aaa 

produces:  
  440Hz (190ms) then q
uiet (10ms)
  440Hz (190ms) then quiet (10ms)
  440Hz (190ms) then quiet (10ms)    

input: a-- 
produces:
  440Hz (200ms)
  440Hz (200ms)
  440Hz (200ms)

The first plays three distinct "A" notes. The second produces one long "A" note. A space in the input should just play 200ms of silence. When you're ready you can test your program using these melodies: 

  Mary Had a Little Lamb: 
agfgaaa ggg aaa agfgaaaaggagf 
  Yankee Doodle: ffgafag ffgaf-e- ffgaAagfecdef-f-

Your code should read characters from the serial port with the cin function and place them into an array. The array should be big enough to hold 128 characters. If a melody is bigger than 128 characters you must print an error message and refuse to play the melody.    

Part 2: Memory Banks (Extra Credit)

In this part you will expand on what you've already done by making your sketch remember four tunes.  In your setup function read in four different melodies. The output should look like this: 

Enter tune 1: 
agfgaaa ggg aaa agfgaaaaggagf
Enter tune 2: 
ffgafag ffgaf-e- ffgaAagfecdef-f- 
Enter tune 3: 
e-e-f-g-g-f-e-d-c-c-d-e-e--dd-- e-e-f-g-g-f-e-d-c-c-d-e-d--cc-- d-d-e-c-d-efe-c-d-efe-d-c-d-c- e-e-f-g-g-f-e-d-c-c-d-e-d--cc--
Enter tune 4:
c-------g-------c-------g-------c-------g-------c-------g---c---g-------g-------g-------g-------c-------g-------c-------g---c---

Your sketch will use buttons and LEDs to select and play the remembered tunes. Here's a function that reads the buttons and returns what tune to play: 

void setLEDNumber(int n) {
  for (int i = 0; i < 4; i++) {
    digitalWrite(LEDs[i], bitRead(n, i));
  }
}

int doSelect(bool &play) {
  static int melody = 0;
  static int lastButton[] = {HIGH, HIGH};
  int nextButton[2];
  nextButton[0] = digitalRead(Buttons[0]);
  nextButton[1] = digitalRead(Buttons[1]);
  if (nextButton[0] == LOW && lastButton[0] == HIGH) {
    melody++;
  }
  if (nextButton[1] == LOW && lastButton[1] == HIGH) {
    play = true;
  }
  melody %= 4;
  lastButton[0] = nextButton[0];
  lastButton[1] = nextButton[1];
  setLEDNumber(1 << melody);
  return melody;   
}

To use the function you must declare the following: 

const int LEDs[] = {2, 4, 7, 8};
const int Buttons[] = {12, 13}; 

And be sure to add this code to setup():

for (auto l : LEDs) {
    pinMode(l, OUTPUT);
}
for (auto l : Buttons) {
    pinMode(l, INPUT_PULLUP);
}

Now you can use my doSelect() function to tell you what tune to play and when it's time to play a tune. Here's some test code that shows you how it's used:

void loop() {
  bool play = false;
  int melody = doSelect(play);

  if (play) {
    cout << "Play melody " << melody << " now!" << endl;
  } 
}

My code will show the user what melody is selected using the LEDs and let the user change the selection with the RIGHT button and select to play the melody with the left button. Save the sketch for the second part in a file called chiptunes-multi.ino

Style Requirements

Remember to follow all the style rules from previous assignments, as well as the new rules we recently covered, including:

  1. Function naming conventions
  2. Indentation in functions and placement of curly braces
  3. Function comment blocks 
  4. Use named constants instead of magic numbers
  5. Proper use of spaces around operators

Grading Criteria

The instructor will evaluate your assignment using the following criteria. Thus you should check your assignment against these criteria to maximize your score.

Each criteria represents a specific achievement of your assignment and has a scoring guide. The scoring guide explains the possible scores you can receive. Some scoring guides have a list of indicators. These indicators are a sign of meeting, or a symptom of not meeting, the specific criterion. Note that a single indicator may not always be reliable or appropriate in a given context. However, as a group, they show the condition of meeting the criterion.

How to Submit

Upload all project files to Blackboard in the project folder that matches the name of this project. Include the following items for grading:

  1. Class exercises
  2. chiptunes.ino
  3. chiptunes-multi.ino

You must submit all the files needed to complete your assignment. Your assignment must work as submitted. Remember to test and double check your files before submitting them. If you make a mistake, you can resubmit up to the deadline, but must resubmit all your assignment files.

Comments