6.031
6.031 — Software Construction
Spring 2018

Project: ABC Karaoke

Team contract due
Mon Apr 30, 10pm
Warmup due
Fri May 4, 11am
Specs due
Wed May 9, 11am
Project deadline
Wed May 16, 11am
Reflection deadline
Wed May 16, 11am

Composition of a musical piece is often a trial-and-error process, in which the musician writes down a series of notes on paper and tests them out on a musical instrument. One way to do this on a computer is to type the notes into a text file using a special notation and feed them to a program that understands this notation. This way, you can transcribe your favorite pieces of music or compose your own pieces, and easily exchange them among your friends on the web.

abc is one of the languages designed for this purpose. It was originally intended for notating folk and traditional tunes of Western Europe, but it provides a sufficient set of constructs for transcribing a reasonably complex piece of music, such as a Beethoven symphony. Since its invention in 1980’s, abc has become one of the most popular notations for music, with around 50,000 abc files circulating around the web.

The abc notation also supports including lyrics with a tune, so we will go one step farther than just playing back music, and create a karaoke machine whose input is an abc file.

In this project, you will build an abc karaoke player. You will use ParserLib to parse abc files, the Java MIDI API to play back the music, and a web server to stream the lyrics to multiple people who want to sing along. You are required to handle only a subset of the abc language, which we will discuss in more detail in the specification below. This subset is sufficient to play a large number of interesting tunes that are available on the web, but you are welcome to implement the rest of the standard, as long as your overall design remains clean and simple.

In this project, you will practice working in a small group, using the software engineering techniques we have learned this semester: version control, specifications, unit tests, immutable data, abstract data types, and so on. You will also get more practice with grammars, parsers, and abstract syntax trees.

Design Freedom

On this project, you have complete design freedom to choose the packages, interfaces, classes, and method signatures you use in your code. Choose them wisely. You will be expected to use specs, tests, abstraction functions, rep invariants, safety arguments, checkRep and other assertions.

The specification in this handout constrains what your solution must do, but you will have many design questions that are not answered in this handout. You are free to come up with your own answers to these questions – just be reasonable, consistent, safe from bugs, easy to understand, and ready for change. Examples of questions that you won’t find answers to here:

  • Do we need to check the input for errors? What should we do if there are errors?
  • Should we handle nested repeats?
  • What if two voices contain a different number of total bars?

…and there are many, many more. You can always ask your TA mentor or on Piazza for advice, but there is unlikely to be a single hard-and-fast answer.

On this project, Didit will run only your own tests. As always, correctness is your responsibility, and you must rely on your own careful specification, testing, and implementation to achieve it.

Your team’s repository

To get started, (note: only available after the project announcement in class)

  1. One member of your team should ask Didit to create a remote projects/karaoke repository, which will create a single repository shared by all members of your team.
  2. Then all team members will be able to go to Didit to get a personalized URL for cloning the repo from Athena using Git. Your URL starts with your username, so you should be using your own Athena password to access the repo, not a teammate’s username and password.

Overview

During the project, class meetings will be partly or completely replaced by team work time. Your team is expected to meet and work together during these times.

Checking in

Your team will be assigned a TA mentor who will help you with your design and help you stay on track as you implement it. You are required to check in with your TA during every team work time class. At this check-in, each team member should be prepared to:

  • say what they have accomplished since the last check-in
  • say what they plan to accomplish by the next check-in
  • say what, if anything, is blocking their progress
  • show that their own working copy of the project is fully committed and up to date with the remote repo

Use the check-in to review how each person’s plans fit together and decide how to resolve blocking problems.

Working together

Other than reflections at the end of the project, all parts of the project should be committed to the repository you share. Each commit to the repository should have a useful commit message that describes what you changed.

Use the code review skills you’ve practiced on Caesar to review one another’s code during the project. Caesar won’t have your project code loaded into it, however, so you can use in-person discussion or email for these reviews.

You are also strongly encouraged to try pair programming, where two people collaborate on a single computer. Pair programming is a skill that requires practice. Be patient: expect that pairing will mean you write code more slowly, because it’s like code review in real time, but the results are more correct, more clear, and more changeable. You can find plenty of advice on the Internet for how to structure your pairing.

Note that “pair programming” normally means just one keyboard, with one person driving (typing) and the other person in the backseat (code reviewing). So you are editing only one working copy, and committing the changes just once. When multiple people contribute to a commit, mention them in the commit message. Your TA will be reviewing the Git log to see individual contributions.

What we do in class with Constellation is a closer kind of collaboration than normal pair programming. Using Constellation during the project is possible but not encouraged, because it makes identical changes on two computers that require careful coordination to avoid merge conflicts. But if you really want to use Constellation, then before starting the collaboration, make sure both sides are in a clean state, with no uncommitted changes and fully in sync with the remote repo. After collaborating, both sides should commit the changes to their local repos, then push and pull until the commits are merged and both sides are again in a clean state. Note that you must be extremely disciplined to use Constellation and git together successfully. Never walk away from a Constellation collaboration without cleaning up and getting merged.

In general, working together will be easier if you:

  1. pull every time you sit down to start working
  2. commit and push frequently
  3. don’t break the build! (don’t commit code that doesn’t compile)

Tasks

  • Team contract. Before you begin, write and agree to a team contract.

  • Understand the problem. Read the project specification carefully.

  • Warmup. There is a warmup exercise that your group needs to do, explained below. It will help you learn about the MIDI music-playing interface and the abc file format.

  • Design. You will need to create abstract datatypes for various parts of your system, and a grammar for parsing music file input.

    Your software design is perhaps the most important part of the project: a good design will make it simpler to implement and debug your system. Remember to write clear specifications for classes and methods; write data type definitions for your expression data types; define abstraction functions and rep invariants, and write toString and checkRep; and document safety from rep exposure.

  • Test. You should write JUnit tests for the individual components of your system. Your test cases should be developed in a principled way, partitioning the spaces of inputs and outputs, and your testing strategy should be documented as we’ve been doing all along. Parts of your program may require manual testing, which you should document clearly as in problem set 4.

  • Implement. Write implementation code so that your tests pass. Iterate on the design and tests as needed to make the system work.

  • Reflection. Individually, you will write a brief commentary saying what you learned from this project experience, answering the reflection questions. Your reflection may not exceed 300 words, and should be submitted to the reflection form.

Dividing the work

Every team member must not only make a substantial contribution to the project, but every team member must make a concrete contribution to every major component of the project.

For example, you may not assign one team member who is solely responsible for the parser.

Contributions include writing specifications, writing testing strategy, writing tests, prototyping, writing internal docs, writing implementation code, fixing bugs, and giving code review feedback. Contributions you work on as a pair or a whole team are great, as long as everyone is involved. Tasks like running meetings, taking notes, and tracking bugs are important, but don’t count as contributions under this requirement.

You must also divide the work such that every team member makes several different kinds of contributions.

For example, you may not assign one team member who is solely responsible for writing tests.

Here is one way to break down the work for a 3-person group that satisfies the constraints above. Fill in the cells of this grid so that it is a Latin square, i.e. no person is mentioned more than once in each row or column:

Parsing & AST

Sound playback

Lyrics streaming

Specs

Tests

Code

Note that you may need to rebalance your work breakdown among group members as the design and implementation challenges become more clear, so be prepared to revisit this Latin square and change the rows or columns if necessary.

Automated testing

Didit will compile your project and run your tests every time you push.

The automated testing page describes how Didit finds your tests, what tests can and cannot do, and how to exclude tests from execution.

You are not required to run any of your tests on Didit, but your project must compile and have a green build on Didit in order to be graded. If it doesn’t build on Didit, your TA won’t be able to build it either.

Specification

The project specification describes how the karaoke player must work.

The specification is not meant to provide you with comprehensive information on abc notation, so you may find the following links helpful as you work to understand the notation:

Provided code

Main.java
placeholder for your main program
ABC.g
placeholder for your ParserLib grammar
SequencePlayer.java
an interface for playing notes at certain times
MidiSequencePlayer.java
a SequencePlayer that produces MIDI sound output
Pitch.java
represents a sound frequency
Instrument.java
represents a MIDI musical instrument
ScaleExample.java
example code that plays a musical scale
HostnameExample.java
example code that discovers your computer’s hostname(s) or IP address(es)
StreamingExample.java
example server code that streams lines of text to a web browser

It’s also fine to use any code provided in this semester’s class, including music code from the Little Languages classes. But be aware that the Music datatype developed in those classes was designed for a different problem than you are solving here, so it may not be directly useful.

We also provide some example abc files that you can use to test your abc player. These files are found in sample-abc/ in your Git repo.

scale.abc
A simple scale
little_night_music.abc
A Little Night Music by W. A. Mozart
paddy.abc
Paddy O’Rafferty, an Irish tune
invention.abc
Invention by J. S. Bach
prelude.abc
Prelude by J. S. Bach
fur_elise.abc
Fur Elise by L. v. Beethoven
abc_song.abc
Alphabet Song
waxies_dargle.abc
Waxie’s Dargle
sample1.abc
A very short piece with different notes and note lengths
sample2.abc
A very short piece with a chord
sample3.abc
A very short piece with voices

You can find many more abc examples online.

Warmup

Note: If you don’t understand musical notation, this Wikipedia page may be helpful.

  1. Transcribe each of the following small pieces of music into an abc file. Name your files piece1.abc, piece2.abc, and piece3.abc, respectively, and commit them under the directory sample-abc in your team’s Git repository. You may find the spec useful here.

piece1.abc: A simple, 4/4 meter piece with triplets.
As a starter, the header and the first two bars are already provided.
You should complete the rest of the piece by transcribing the last two bars.

X: 1
T:Piece No.1
M:4/4
L:1/4
Q:1/4=140
K:C
C C C3/4 D/4 E | E3/4 D/4 E3/4 F/4 G2 |

piece2.abc: A more complex piece, with chords, accidentals, and rests.
The piece below is only an excerpt, and the last measure is not a full measure.
Pad the piece with enough rests to complete the measure.
Use a default note length of 1/4, and tempo 200 default note lengths per minute.

piece3.abc: A piece with lyrics.
Make sure to align your lyrics with music correctly using the symbols described in the ABC subset.
Use a default note length of 1/8, and tempo 100 default note lengths per minute.

  1. Write JUnit tests that play these pieces using the sequencer, similar to the main method in examples/ScaleExample.java. Your test for piece3.abc should print synchronized lyrics to System.out. Put these JUnit tests in test/karaoke/sound/SequencePlayerTest. Didit cannot play music for you, so you’ll want to tell Didit to ignore this test file. See automated testing for how to tell Didit to ignore a test file.

    Hint: MidiSequencePlayer has a toString method that produces a string representation of all its events. If you are not confident in your listening skills, this might be useful if you want to compare sequences that sound the same.

  2. Write a data type definition and specifications of classes and methods for your immutable music data type. Commit them to your repository as skeleton Java class files.

Deliverables and grading

Mon Apr 30, 10pm
Your team contract must be committed to your group repository in a PDF file called team-contract.pdf in the top level of your repo.
Fri May 4, 11am
Warmup milestone meeting: you will have a meeting with your group’s TA mentor, at which you must demonstrate the deliverables described under warmup. You will demo the code and discuss your work at the meeting. Have a laptop with the program ready to run. Make sure to commit your warmup code to your group’s repository before this meeting.
Wed May 9, 11am
Specs milestone meeting: by this meeting, you should have committed (1) a ParserLib grammar for the abc file format, (2) specs for all your classes and methods, including a concurrency design (thread safety arguments for your networking and playback code) and (3) some progress on testing and implementation.
Wed May 16, 11am
Project deadline. Your specifications, tests, and implementation should be complete and committed to your group’s repository.
Wed May 16, 11am
Reflection deadline. Individually, you should write a brief reflection and submit it using the reflection form. Your reflection should be at most 300 words of plain text.

Grades will be determined according to the following breakdown:

  • Team contract: 5%
  • Design: 25%
  • Implementation: 40%
  • Testing: 25%
  • Reflection: 5%

The warmup and specifications deadlines are graded as binary checkoffs. Missing each of these intermediate deadlines will cost 5 points on the overall project grade. The associated milestone meetings also contribute to your design and implementation grades.

Check-ins with your TA mentor are also graded as binary checkoffs, either passed or missed. Missing a check-in costs 1 point on the overall project grade. You should check in with your mentor during class on:

  • Mon Apr 30
  • Wed May 2
  • Fri May 4 (warmup milestone meeting)
  • Mon May 7
  • Wed May 9 (specs milestone meeting)
  • Fri May 11
  • Mon May 14

Hints

Pay attention to the various kinds of whitespace handling described in the grammar. Windows and Mac/Unix have different line endings (\r\n vs. \n), but the abc grammar requires you to support both kinds interchangeably. Do not use System.lineSeparator(), because it would make your code handle only one kind of line ending. Make sure you have appropriate test cases to avoid any platform dependency. Also make sure you are tolerant of extra spaces and tabs where appropriate.

You can use a single ParserLib grammar, or break up the abc grammar into multiple ParserLib grammars. For example, you can parse the overall header and body structure with one grammar, then parse the details of the music itself with a separate grammar. You are required to use ParserLib.


Additional pages referenced above

Karaoke player specification

Automated testing

Team contract

Reflection

Running Java Programs with Command-Line Arguments