Assignment: Music Box

Encoding songs as data

The Problem

In lab you used classes to create a piano program. An app class that represents the program itself, and a simple piano class that represents the ability to play notes.

In this assignment, you'll be adding another class, recorder, which allows you to:

  • record what you're playing!
  • play it back
  • save it to disk for later
  • read a song from disk to play back

Start with your working Part III code from the lab.

You will submit through blackboard:

  • One code file
  • A text file with a brief (couple paragraphs) write-up explaining how you approached the problem, obstacles you hit, and citing any resources (books, online, people) that you used while working on this assignment.

Turning in code that runs counts for 50 points. The write-up counts for 25 points. Readable code (variable and function names, reasonable whitespace, docstrings) counts for 25 points.

May be recorded for quality purposes (150 pts)

Being able to play music is cool. But there's something much more striking about having a machine play back what you just did. You're going to do that be creating a recorder class.

In object-oriented terms, think of the recorder class as a separate box that connects to the piano. Every time you hit a key, if a recorder is connected, then it somehow stores that key that you hit. Then, at a later time, it somehow plays backs all those keys.

We'll start by stubbing out the interface for the recorder. That is, the methods that we think the class will need. Start with this:

"""
recorder class with stub methods. Note that "pass" must be used for empty function bodies, will be removed when you put real code in.
"""

class recorder:
  def __init__(self):
    """
    Default constructor, going to need some way to store notes.
    """
    self.notes = []

  def record_key(self, key_index):
    """
    Method to record that a particular key (by index) was struck.
    """
    pass

  def playback(self, piano):
    """
    Method to play back the stored notes using the given piano.
    """
    pass

Now, you can connect up this stubbed recorder to your app to see how they can work together. Follow these steps:

  1. Add a data member for a recorder to the app constructor and set it to the value None (None is used to indicate the absence of an object)
  2. Add a "Record" button to the interface that calls a begin_recording method in the app class
  3. Have the begin_recording method print to the console so you can verify that your button works.
  4. Have the begin_recording method create a new recorder object and assign it to the data member from step (1)
  5. Update your key_down method so that if there is a recorder object (if the data member is not set to None), then it calls the record_key method on the recorder whenever you hit a key.
  6. Update the record_key method to print to the console. Now if you play notes you shouldn't see anything, but if you press the "Record" button and then play notes, you should see the printing from record_key.

That's the kind of process by which you incrementally create and test code. The design in this case is already mostly set by the lab work you did and the recorder class I gave you, so it's just a matter of making things work together. Here are some next steps that aren't quite as detailed for you.

  1. Update record_key so that it stores the notes being played, instead of just printing.
  2. Add a "Playback" button, just like you did "Record".
  3. Update the playback method on the recorder so that it prints the stored notes to the console.

Time, time, time... (150 pts)

Many programs deal with time and timing. In this case, we need to know not just what notes were played, but when.

Like every programming environment, Python has a way to ask what time it is. The relevant function is:

import time
now = time.time()

That time comes back as the number of seconds since midnight on Jan 1, 1970, an arbitrary Epoch chosen for Unix systems to track time. What's important is that by calling time.time() multiple times, it allows you to calculate relative time, such as the number of seconds since you started recording.

  1. Update the recorder constructor to store the time that it was created - that is, the time the recording starts.
  2. Update the record_key method to store not just the notes played, but also the time since the start of the song that they were played. How do you store both? You could use parallel arrays, another class, or a list-of-lists setup.
  3. Update your playback method, as necessary, to be able to print out the stored notes and times so that you can see if record_key is working or not.

And finally, update the playback method to actually playback the song. Remember that you have the key indeces and times stored inside the recorder, and we passed in the piano object to use to play the notes. Think about how you would play a song back given a list of notes and times, a stopwatch and a piano.

Posterity (100 pts)

Add buttons to your interface that allow the user to write the current recorded song to a file, and read that file in for playback. You can just have a single, hardcoded filename, you don't have to support multiple files.

Test Case:

  1. Start recording
  2. Play an awesome song
  3. Press the save button
  4. Quit the program
  5. Restart the program
  6. Press the load button
  7. Press the playback button and hear my awesome song!