CS21 Lab 11: Swatify (objects)

Due Wednesday, December 11, before midnight


Please read through the entire lab before starting!

In this lab, you’ll write classes to represent Song and Playlist objects that you’ll use to play .mp3 audio files.

For this lab, you’ll be playing audio. Please be respectful about the noise you may be generating in the lab. Whenever possible, please use headphones to test your audio, and if that’s not possible for some reason, please keep the volume low if others are working nearby.
Only test audio playback while physically present in the lab, while logged in to the machine you’d be testing on. You will NOT hear anything, and thus will be unable to test correctness, if you’re logged in remotely.

All of the standard style guidelines from previous labs still apply. Be sure to follow good top-down-design practices.

As you write programs, use good programming practices:

  • Use a comment at the top of the file to describe the purpose of the program (see example).

  • All programs should have a main() function (see example).

  • Use variable names that describe the contents of the variables.

  • Write your programs incrementally and test them as you go. This is really crucial to success: don’t write lots of code and then test it all at once! Write a little code, make sure it works, then add some more and test it again.

  • Don’t assume that if your program passes the sample tests we provide that it is completely correct. Come up with your own test cases and verify that the program is producing the right output on them.

  • Avoid writing any lines of code that exceed 80 columns.

    • Always work in a terminal window that is 80 characters wide (resize it to be this wide)

    • In vscode, at the bottom right in the window, there is an indication of both the line and the column of the cursor.

Function Comments

All functions should have a top-level comment! Please see our function example page if you are confused about writing function comments.

Goals

The goals for this lab assignment are:

  • Gain experience writing classes.

  • Gain experience reading and using classes.

1. Writing Classes

For this lab, you’ll be doing your work in three files:

  1. song.py: This file will define a Song class that you’ll use to represent the information you know about one song (e.g., the artist, title, and path to the corresponding .mp3 file.

  2. playlist.py: This file will define a Playlist class that you’ll use to represent a collection of Song objects that should be played, in order. It will also provide methods for getting information about the collection as a whole (e.g., the total playtime duration of all the songs in the list).

  3. swatify.py: The main file that will contain your main function and supporting helper functions. The code in this file will create Playlist and Song objects to "put everything together" and play music according to the specifications below.

1.1. Testing

  • As always, you should test your program as you go.

  • Since you’re working with three files, it’s a good idea to put the tests for your class in the corresponding file.

  • Like we saw in lecture, this can be done by writing a main() method, and then calling it at the bottom using:

    if __name__ == "__main__":
        main()

    This way, your testing code will run when you execute python3 song.py on the command line, but it won’t run when you import from song in swatify.py

1.2. The Song Class

The song.py file should contain a class definition for a class named Song. The Song class should have at minimum the following methods:

  • Constructor (__init(…​)__)

    • The constructor should take the path to the corresponding .mp3 file for this song (a string, for example, "/home/kwebb/music/Johnny Cash-Hurt.mp3".

    • You should save the file path as-is in an instance variable. The system for playing music that we provide for you will need to refer to the file path later.

    • Based on the file path, you should also store two additional instance variables (strings): an artist and a title. You can determine these values by splitting the file path string. The provided strip_path() function will remove the directory names in the path and the .mp3 suffix from the file name for you. After that, you may assume that the artist and song title will always be separated by a dash: "-".

  • get_filename(…​)

    • This method should take no parameters (other than self), and it should return the stored full file path to the .mp3 file for this song that was passed in to the constructor.

  • get_artist(…​)

    • This method should take no parameters (other than self), and it should return a string containing just the artist of the song.

  • get_title(…​)

    • This method should take no parameters (other than self), and it should return a string containing just the title of the song.

  • get_duration(…​)

    • This method should take no parameters (other than self), and it should return a float that contains the length of the song, in seconds.

    • The provided lookup_song_duration() function will help you to determine the length of a song.

  • __str__(…​)

    • This method should take no parameters (other than self), and it should return a string with a short description of the song. For example, if the title is Hurt and the artist is Johnny Cash, this function should return a description like "Hurt by Johnny Cash".

    • This method should not print anything, just return a string.

  • __eq__(…​)

    • This method tests for equality between two Song objects. It should take one parameter (in addition to self): another Song object. It should return True if the two Song objects represent the same song (i.e., they have the same .mp3 file path) or False otherwise.

    • You will not call this method directly. Instead, python will automatically use it when two Song objects are compared using ==. For this lab, it’s needed so that you can use the in operator to check if a Song is in a list, which will be helpful when you write the Playlist class.

Before moving on to the Playlist class, add tests cases to a main function at the bottom of the song.py file as describing in the Testing section above. You should create at least one song using the constructor, then you should test each of the methods you’ve written. You can test __str__ by printing the object.

1.3. The Playlist Class

The playlist.py file should contain a class definition for a class named Playlist. The Playlist class should have at minimum the following methods:

  • Constructor (__init(…​)__)

    • The constructor should take no parameters (other than self), and it should initialize a list instance variable that will later store a list of Song objects.

  • add_song(…​)

    • This method should take one parameter (in addition to self): the Song object to add to the playlist. It does not return any value.

    • If the song is not already in the playlist, it should add the song to the playlist. Note that using the in operator to test whether a Song object is in a list will only work if the Song object’s __eq__() method is working properly.

    • If the song is already in the playlist, it should print a message to inform the user that the song is already in the playlist and not change the contents of the playlist.

  • get_song(…​)

    • This method should take one parameter (in addition to self): the index of the song to retrieve from the playlist (starting from 0, like a python list).

    • You can assume this method is provided a valid index.

    • It should return the Song object in the playlist at the requested position.

  • get_song_count(…​)

    • This method should take no parameters (other than self), and it should return the number of songs in the playlist (an integer).

  • get_duration(…​)

    • This method should take no parameters (other than self), and it should return a float containing the length of the whole playlist, in seconds.

  • __str__()

    • This method should take no parameters (other than self), and it should return a string with the contents of the playlist and the total length of the playlist in seconds.

    • If the playlist is empty (contains 0 songs), simply return the string "Playlist is empty".

    • If the playlist has one or more songs in it, the string this method should generate a string that contains each song on a separate line, followed by the duration in seconds.

    • This method should not print anything, just return a string.

Before moving on, add tests cases to a main function at the bottom of the playlist.py file as describing in the Testing section above. You should create a playlist using the constructor. You can then create multiple songs using the Song constructor and then add these songs to the playlist. You should test each of the methods you’ve written. You can test __str__ by printing the object.

Here’s an example of what it would look like to print a Playlist that has three Song objects added to it:

2. Provided Library

This lab provides a music library that gives you several helpful functions and a Player object to make this lab easier. To use the functions described below, you will import them from the music library. For example, to use the strip_path and lookup_song_duration functions, you would say:

from music import strip_path, lookup_song_duration

Here are the functions included in the music library:

  • strip_path(…​)

    • This function takes a path to a .mp3 file and 1) removes all of the leading directory names, and 2) removes the .mp3 suffix. For example, given the file path:

      /home/kwebb/music/Johnny Cash-Hurt.mp3

      It would produce the string: "Johnny Cash-Hurt"

  • lookup_song_duration(…​)

    • This function takes a file path string and returns the length of the song, in seconds, as a float.

  • read_song_directory(…​)

    • This function takes a path to a directory that contains .mp3 files (for example, "/home/kwebb/music"). It will return a list containing the paths of all the .mp3 files in that directory. For example, if you call read_song_directory on the path "/home/kwebb/music", it will generate a list that looks like:

      ['/home/kwebb/music/Aretha Franklin-Respect.mp3',
       '/home/kwebb/music/Cowboy Junkies-Sweet Jane.mp3',
       '/home/kwebb/music/David Bowie-Waterloo Sunset.mp3',
       ...,
       '/home/kwebb/music/The Animals-House of the Rising Sun.mp3',
       '/home/kwebb/music/The Blind Boys of Alabama-Way Down in the Hole.mp3']
  • Player() object: A Player will help you to manage playing music so that you don’t need to worry about the details of playing audio files. Your program should create a Player at the start of main and continue to use that one Player for the duration of the program. The constructor for Player takes no parameters, so you can create one early in main with a line like:

def main():
    player = Player() # create the music player
  • The player has the following methods defined for you:

  • .play(): Takes one parameter, a Playlist object. Returns no value. When called, it will begin playing the songs on the playlist, in order. For it to work, you’ll need most of the Song and Playlist methods described above to behave correctly.

  • .stop(): Takes no parameters and returns no value. Stops playback of the playlist if it’s playing. Does nothing if the playlist isn’t playing.

  • .next(): Takes no parameters and returns no value. Skips forward to the next song in the playlist if there is one. Stops the playlist if it’s currently playing the final song. Does nothing if the playlist isn’t playing.

  • .get_current_song(): returns a string containing information about the current song that is playing (or a string to indicate that nothing is playing if there isn’t one).

3. Putting it all together in swatify.py

The song.py file should contain a class definition for a class named Song, and playlist.py should contain a class definition for a class named Playlist. You will need to import these classes into your swatify.py file by typing:

from song import Song
from playlist import Playlist
Even though your files are called song.py and playlist.py, you import from song and playlist. Python adds the .py automatically when looking for the file.

You will also need to import some functions from the music library:

from music import Player, read_song_directory

Like prior labs, this lab will present the user with a menu-based interface to make selections. The menu should look like:

Please select one of the following choices:
1. Show available song files
2. Print current playlist
3. Add song to playlist
4. Start playlist
5. Skip to next song
6. Stop playlist
0. Quit

Enter selection:

Depending on what the user selects, you should do one of the following things:

  1. Show available song files: Print a numbered list of available song files for the user to choose from. The provided read_song_directory() will do much of the work for you here. If you give it a path to a directory, it’ll give you a list of the .mp3 files in that directory. You can use the directory path "/home/kwebb/music" for example files, or you can make a directory of your own with .mp3 files in it.

  2. Print current playlist: Print information about the current playlist, including the contents of the list, the total duration of the playlist in seconds, and the currently playing song, if there is one. The .get_current_song() method of a Player will tell you what is playing (or that nothing is playing).

  3. Add song to playlist: Add a song to the playlist. Prompt the to enter a number for which song they’d like add, create a Song object, and add it to your playlist. You should validate that the user enters an integer and that it’s a valid choice (i.e., there’s a song file corresponding to that number).

  4. Start playlist: Start playing the playlist with the provided .play() method of a Player.

  5. Skip to next song: Tell the Player object to skip to the next song. If nothing is currently playing, this option does nothing. If the playlist is currently playing the final song, this option will stop playback.

  6. Stop playlist: Tell the Player object to stop playback. If nothing is currently playing, this option does nothing.

  1. Quit: Exit the program.

4. Requirements

The code you submit for labs is expected to follow good style practices, and to meet one of the course standards, you’ll need to demonstrate good style on six or more of the lab assignments across the semester. To meet the good style expectations, you should:

  • Write a comment that describes the program as a whole using a comment at the top of the file.

  • All programs should have a main() function.

  • Use descriptive variable names.

  • Write code with readable line length, avoiding writing any lines of code that exceed 80 columns.

  • Add comments to explain complex code segments (as applicable).

  • Write a comment for each function (except main) that describes its purpose, parameters and return value.

In addition, you’ll need to demonstrate good top-down design practices on two or more lab assignments across the semester. To meet the top-down design expectations, you should:

  • Have a main() function that serves as an outline for your program.

  • Your main() function should call other functions that implement subtasks.

  • The functions that are called by main() should be well-named and have good comments explaining their purpose, parameters (including their types), and return value (including its type).

Your program should meet the following requirements:

  1. Your implementation of the Song class should be in the file song.py and its methods should behave as described in section 1.1.

  2. Your implementation of the Playlist class should be in the file playlist.py and its methods should behave as described in section 1.2.

  3. Your song.py and playlist.py files should contain tests of the methods you’ve written in those files.

  4. Your swatify.py should contain a main function that presents the user with menu choices as described in section 3.

  5. Your implementation of swatify.py allows the user to list song files, add songs to a playlist, and print the playlist.

  6. Your implementation of swatify.py allows the user to play a playlist, skip to the next song, and stop playback.

5. Extra Challenges

Here are some optional extensions! These are not required, but they might be fun and rewarding.

If you implement extensions, please make sure the original menu operations remain the same. That is, you are welcome to add a menu item 7, but don’t change menu item 3 to be menu item 4.

5.1. Optional: Remove song from playlist

Provide the user with an option to remove a song from a playlist. You’ll need to prompt them to tell you which song, and then you’ll need to find it in the list to remove it.

5.2. Optional: Sort the playlist

Sort the playlist based on any criteria you like (artist name, song title, duration, etc.).

5.3. Optional: Print song information

Each .mp3 file in the example directory also has a corresponding text file with the same name + ".info" added to the end of it. If the user asks for information about a file, prompt them to enter a number for which song they’d like information about, open the corresponding .info file, and print the contents of that file to the terminal.

5.4. Optional: Add features based on your own interests!

Do whatever you’d like that we haven’t told you to do! Go crazy!

Answer the Questionnaire

After each lab, please complete the short Google Forms questionnaire. Please select the right lab number (Lab 11) from the dropdown menu on the first question.

Once you’re done with that, you should run handin21 again.

Submitting lab assignments

Remember to run handin21 to turn in your lab files! You may run handin21 as many times as you want. Each time it will turn in any new work. We recommend running handin21 after you complete each program or after you complete significant work on any one program.

Logging out

When you’re done working in the lab, you should log out of the computer you’re using.

First quit any applications you are running, including your vscode editor, the browser and the terminal. Then click on the logout icon (logout icon or other logout icon) and choose "log out".

If you plan to leave the lab for just a few minutes, you do not need to log out. It is, however, a good idea to lock your machine while you are gone. You can lock your screen by clicking on the lock xlock icon. PLEASE do not leave a session locked for a long period of time. Power may go out, someone might reboot the machine, etc. You don’t want to lose any work!