Project 1 - Note Player
Introduction
The objective of this assignment is to play music entered by the user as a list of notes. Your user will type a song (a list of notes), and then your Note Player will play that song, ready to accept a new song. The user can repeatedly enter song after song, with your Note Player playing each one in turn. The user exits the program by entering the command “quit" instead of a song.
We will guide you through the project using a series of checkpoints. Each checkpoint will add a little more functionality, until your project is complete.
I don’t know anything about music!
Perfect. This project doesn’t require any knowledge of music, and this document tells you all you need to know. What we do expect you to know are String methods (indexOf and substring), Scanner, and later on, if/else and while loops.
Code Organization
Your code will start executing in your main method, as usual. Beyond that, it’s up to you how to organize your code. We’d recommend factoring your code: breaking up tasks into small methods and avoiding duplicating code by using helper methods. This will be easier for you to manage than stuffing all your code into main.
Comments
· The topics we have been learning in class are designed to allow you to complete this assignment. Remember to review your notes and the textbook.
o Scanner, indexOf, substring, etc.
· You are not allowed to use any Java features that we have not yet covered in our class. For example, you may NOT use arrays (or anything that involves arrays), you may not define any new classes and objects (use only the NotePlayer class we give you in your starter project), you may not use exception handling, or anything else not yet covered in our class.
· Use the unit tests! There is no penalty for running tests and failing a lot. We only grade the test results for the version that you choose to turn in for each checkpoint. See the Student GitHub Instructions document for details on how to do this.
· You can assume we will never give you input that would exceed the range of MIDI Note numbers (e.g., we will never give you Cb-5, as that would be -1, which would be outside the range).
Getting Started
Import the NotePlayer Starter Project
These are the same instructions as those for importing your Classwork project, except the project name is different:
1. Find the github URL for your starter project. The easiest way to do this is the following:
a. Go to https://buildci318.com/
b. Click on the “Projects” tab along the top
c. You should see a tile for NotePlayer, with a “Copy Project Link” button. Press it!
d. The URL for your starter project is now in your clipboard, ready to paste into Eclipse
2. IF the above doesn’t work for some reason, you can grab the URL from github instead. Do this ONLY if the above doesn’t work for you.
a. In Chrome, go to https://github.com/BHS-APCS
b. You should see the list of your projects.
c. Click on the NotePlayer result (should look like NotePlayer_<LastName><FirstName>), and then copy the URL to your clipboard. It will be something like: https://github.com/BHS-APCS/NotePlayer_<LastName><FirstName>.
3. Start Eclipse.
4. Go to “File” -> “Import”, and select “Git” -> “Projects from Git.” NOT with “smart import”
5. Click “Clone URI” and click next.
6. Fill out the form.
a. In the URI box, paste the URL you copied to your clipboard (often, Eclipse will paste this for you automatically)
b. In the User box, enter your GitHub username.
c. In the Password box, enter your GitHub password.
d. Check “Store in Secure Store,” so you don’t have to keep typing your password.
Here is an example:
Type this line in as you see it here, but use the link you found in step 1 or 2
Leave these, they get populated automatically
Type in your github username and password, and ensure the ‘store in secure store’ is checked
7. Click next. Make sure “master” is the only branch checked. (Uncheck any other branches that may be checked.) Click next/next/next/finish.
Working from Home and School
You should always use this pattern, or else you will cause Eclipse errors making it impossible to work on your project:
1. Whenever you sit down to work—either at home or school—always PULL first. (See Student GitHub Instructions document in your OneNote.)
a. This refreshes your computer with the latest version stored on github.
b. Note: The plug-in initiates this for you automatically when you launch Eclipse. If you left Eclipse open when you last took a break (not recommended!) you’ll need to pull manually.
2. Whenever you are ready to take a break, always COMMIT AND PUSH. (See Student GitHub Instructions document)
a. This uploads the changes you just made up to github.
b. Note: The plug-in initiates this for you automatically when you close Eclipse. Don’t just put your computer to sleep—always close Eclipse first so it forces the Commit + Push.
c. You can & should Commit + Push even more often than when you take a break. Great way to back up your changes, and the only way to force the tests to rerun when you make fixes.
3. If you forget either of the above steps, you will encounter a merge conflict, and pushing / pulling will start failing. See the Student GitHub Instructions doc for how to deal with this situation.
Checkpoint 1: Parse and print first note symbol
Example input: A_600 B_450 C_1
The goal of this checkpoint is to read a line of input from the user, find the first note symbol, and break it up into its letter and duration, which you will print to the screen. Here is an example of what the line of input from the user might look like:
A_600 B_450 C_1
First, you’ll notice a capital letter (the note letter, such as A), an underscore (_), and then an integer (the duration, like 600):
A_600
The sequence of characters “A_600” is called a note symbol. After this note symbol will be a single space, and then another note symbol and space, and then another, etc. The line might contain 2 note symbols, or 10, or 100. All you can be sure of is that, for checkpoint 1, there will be at least 2 note symbols (separated by a space).
This checkpoint is a small step toward your final project and can be done in about six lines or so. Read carefully!
· All Checkpoint1 Tests must pass
· You will create a Scanner and read one line of input (this is the “note list”)
· You will find the very first note symbol in the note list (the letter, underscore, and duration integer), break it up into the note letter and the duration, and then print them using the following format shown below
· All other note symbols on the line may be ignored.
· For now, you can assume there will be at least two note symbols, so you can rely on a space appearing after the first note symbol
When printing the first note symbol, you must use the following format exactly:
note letter: <letter>, duration: <duration>
For example, an input of A_600 B_450 C_1 should cause you to print:
note letter: A, duration: 600
There must be a single space after each colon (:) and comma (,). Do not print the angle brackets <>. Also, ensure that you print the above message on its own line. For example, if you wish to print a prompt when your program begins (such as “Enter note symbols:”), that’s ok, but be sure that the prompt is printed with a println so that Java moves to the next line when it’s done. This way when you print the note letter and duration, it will start on its own line.
Hint:
· Study the Java substring and indexOf String methods
Checkpoint 2: Play entire note list, ignore accidentals
The goal of this checkpoint is to start playing some music! You will find every note on the line and play them all. Read carefully!
For this checkpoint we’re adding a concept called an “accidental” that may appear after the note letter. You may ignore the accidental for now, but don’t let it interfere with your code that parses the note symbol. You will need to interpret the note letter to come up with the correct tone to play.
Note Symbol Part 1: Note Letter (Checkpoint 2)
The note letter is any capital letter from A through G. Your job is to convert this letter into a “MIDI Note Number” that the sound card uses to produce a sound. The following table shows this relationship.
Note Letter
|
MIDI Note Number in octave 0
|
C
|
60
|
D
|
62
|
E
|
64
|
F
|
65
|
G
|
67
|
A
|
69
|
B
|
71
|
You’ll notice that table begins with C as the lowest MIDI Note Number instead of A. You’ll also notice the numbers sometimes go up by 1, and sometimes by 2. Yeah, music is weird. Just go with it.
For now, you may ignore the words “in octave 0” in the table above.
Note Symbol Part 2: Note Accidental (Checkpoint 2)
The note accidental is sometimes present, but not always. It can either be a sharp “#” (hashtag symbol) or a flat “b” (lower-case letter b). For checkpoint 2, you may ignore the accidental. But make sure your code doesn’t get confused by the presence of an accidental when trying to find the note letter and duration number.
Note: Each note is followed by either no accidental, or one accidental, but never both accidentals.
Note Duration
The note duration is the length of time (in milliseconds) to play the note. It always appears after the note symbol’s underscore (_).
This part is easy. You’ll just take the note duration number and pass it directly to the playNote method as its second parameter (see Output section below).
Output
When your Note Player has determined the MIDI Note Number and the Note Duration for a note in the song, it passes those two numbers to the playNote method, which is already included in your starter project:
public static void playNote(int noteNumber, int durationMs)
playNote will print its parameters to the console window (to help you with your testing), and then send the note to your sound card so you can hear it through your speakers or headphones. You must not modify the playNote method in any way. It is there for you to call, not for you to change. (Note: There is another method (setInstrument) just underneath playNote. For now, leave setInstrument alone. You will be calling it later on in the project.)
Your Note Player will scan the note list in order, from left to right:
· Looking at each note symbol in the list,
· Breaking the note symbol into the components described above to determine the MIDI Note Number and Note Duration for the note,
· Calling the playNote method for that note, and then
· Continuing with the next note symbol in the list.
Checkpoint 2 Requirements Summary
Allowable Input: Note list with one or more note symbols, SINGLE line, NO playback adjustment, SOMETIMES accidentals, NO octaves.
Example: A_600 Bb_450 C#_1
· All Checkpoint1 and Checkpoint2 Tests must pass.
· Note! Remove your print statements from checkpoint 1, and your checkpoint 1 tests will continue to pass, so long as you call playNote correctly.
· Your code now needs to find every note symbol in the note list, and call the playNote method on each one
· This means you will now need to convert each note letter into its corresponding MIDI note number (using the table above)
· Some note symbols will contain accidentals (# and b) and some won’t. This will make parsing a little harder, but you may ignore the accidental when calling playNote. For example, if you see a note symbol like C#_100, you can actually play that note as if it were just C_100.
· A note list might contain only one note symbol, or possibly many
Hints:
· You will probably need a statement with several IF / ELSE IF clauses to convert the note letter into its MIDI note number. Remember to use the equals method, not ==, when comparing Strings
· You will find Integer.parseInt useful for the duration. Look it up!
Checkpoint 3: Accidentals and octaves
For this checkpoint we’re adding a new concept called an “octave”, and you will now need to interpret the accidentals you ignored in checkpoint 2.
Each note symbol now consists of up to four parts. An example of a complete note symbol looks like this:
You will convert the portion before the underscore (_) into a special number, using this plan:
Details are in the following sections.
Note Symbol Part 1: Note Letter (Checkpoint 3)
Do as you did in checkpoint 2. But be aware that the table from Note Symbol Part 1: Note Letter (Checkpoint 2) assumes octave 0, so if the user specifies a different octave, you will need to adjust the number. (See Note Symbol Part 3: Note Octave below.)
Note Symbol Part 2: Accidental (Checkpoint 3)
Think of the accidental as a slight adjustment to the MIDI Note Number, where you add or subtract 1 from the note number you already calculated:
Note Accidental
|
What it does
|
# (pronounced “sharp”)
|
+ 1
|
b (pronounced “flat”)
|
- 1
|
(no accidental specified)
|
Nothing (use the note number as is)
|
Note Symbol Part 3: Note Octave
The note octave is a number which appears after the note letter (or its accidental when present). This tells your Note Player how low or high the note should be played. An octave is sometimes present, but not always. If no octave number is present, use octave 0.
This table describes how an octave changes the MIDI Note Number. Always start with the Note Letter table we gave you above, then apply the accidental as described above, and then use the octave to change the value as follows
Octave Number
|
What it does
|
0
|
Nothing (use the note number from above)
|
1
|
Add 12 to the note number (12 * 1)
|
-1
|
Subtract 12 from the note number (12 * -1)
|
2
|
Add 24 to the note number (12 * 2)
|
-2
|
Subtract 24 from the note number (12 * -2)
|
3
|
Add 36 to the note number (12 * 3)
|
-3
|
Subtract 36 from the note number (12 * -3)
|
4
|
Add 48 to the note number (12 * 4)
|
-4
|
Subtract 48 from the note number (12 * -4)
|
5
|
Add 60 to the note number (12 * 5)
|
-5
|
Subtract 60 from the note number (12 * -5)
|
Here’s the pattern: Multiply the octave number by 12, and then add that product to the MIDI Note Number you’ve calculated in the sections above. For example:
D#-2 = 63 + (12 * -2) = 39
D#-1 = 63 + (12 * -1) = 51
D#0 = 63
D#1 = 63 + (12 * 1) = 75
D#2 = 63 + (12 * 2) = 87
Checkpoint 3 Requirements Summary
Allowable Input: Note list with one or more note symbols, SINGLE line, NO playback adjustment, SOMETIMES accidentals, SOMETIMES octaves.
Example: Ab_600 B3_450 C#-2_150
· All Checkpoint1, Checkpoint2, and Checkpoint3 Tests must pass.
· Accidentals will be present on some note symbols, and they must be played properly
· Octaves will be present on some note symbols, and they must be played properly
Hints:
· Each note symbol will be a little harder to parse now. It may or may not contain a sharp (#), flat (b), or octave number. All possible combinations need to be handled properly. Reread the Input section above! Possible examples for each note symbol (this is not exhaustive!):
o D_10
o D#_1
o Db_150
o D2_11
o D#-3_2000
o Db3_4
· Don’t have six (or more!) IF / ELSE IF clauses to check each possible combination. Instead, check for each component separately:
o What is the index of “_”?
§ This tells you where the note letter/accidental/octave portion ends
o What is the index of “#” and “b”?
§ This tells you if an accidental appears after the note letter. This helps you figure out where the octave might start (while the index of “_” above tells you where the octave would end).
Remember, some of those indices will be -1 (meaning that String is not present).
Final Submission: Multiple lines, instruments, playback adjustment
For your final submission, you will add the ability for the user to set the instrument to play, list the available instruments, and enter multiple lines (that is, multiple instrument commands and note lists). After each line is entered, you will process that line (either executing the instrument command or playing the note list entered on that line) and be ready to accept the next line. You will continue in this manner until the user types quit, at which point your program will return from main.
You will also add the ability to accept and process a playback adjustment that might appear at the beginning of each note list.
set instrument
You will allow your user to switch the instrument to use by typing the “set instrument” command in the console window. Any line your user types that begins with set instrument will then contain a space and an integer between 0 and 127 (inclusive) that indicates which instrument to use. For example:
set instrument 26
would set the jazz guitar to be the currently used instrument. And
set instrument 125
would set a helicopter sound to be the currently used instrument. You will process this command by passing the instrument number specified by your user to the setInstrument method, which has been present (unused) in your starter project all this time:
public static void setInstrument(int instrumentNumber)
list instruments
Since your user probably has not memorized your instrument list, you will allow your user to type
list instruments
from the console to cause you to print the following text exactly. You may copy and paste this into your project (adding any appropriate Java punctuation and / or escape sequences) to ensure you have it exactly right. (The tests will require you have this perfectly correct!)
0: Piano 1 1: Piano 2 2: Piano 3 3: Honky-tonk 4: E.Piano 1
5: E.Piano 2 6: Harpsichord 7: Clav. 8: Celesta 9: Glockenspiel
10: Music Box 11: Vibraphone 12: Marimba 13: Xylophone 14: Tubular-bell
15: Santur 16: Organ 1 17: Organ 2 18: Organ 3 19: Church Org.1
20: Reed Organ 21: Accordion Fr 22: Harmonica 23: Bandoneon 24: Nylon-str.Gt
25: Steel-str.Gt 26: Jazz Gt. 27: Clean Gt. 28: Muted Gt. 29: Overdrive Gt
30: DistortionGt 31: Gt.Harmonics 32: Acoustic Bs. 33: Fingered Bs. 34: Picked Bs.
35: Fretless Bs. 36: Slap Bass 1 37: Slap Bass 2 38: Synth Bass 1 39: Synth Bass 2
40: Violin 41: Viola 42: Cello 43: Contrabass 44: Tremolo Str
45: PizzicatoStr 46: Harp 47: Timpani 48: Strings 49: Slow Strings
50: Syn.Strings1 51: Syn.Strings2 52: Choir Aahs 53: Voice Oohs 54: SynVox
55: OrchestraHit 56: Trumpet 57: Trombone 58: Tuba 59: MutedTrumpet
60: French Horns 61: Brass 1 62: Synth Brass1 63: Synth Brass2 64: Soprano Sax
65: Alto Sax 66: Tenor Sax 67: Baritone Sax 68: Oboe 69: English Horn
70: Bassoon 71: Clarinet 72: Piccolo 73: Flute 74: Recorder
75: Pan Flute 76: Bottle Blow 77: Shakuhachi 78: Whistle 79: Ocarina
80: Square Wave 81: Saw Wave 82: Syn.Calliope 83: Chiffer Lead 84: Charang
85: Solo Vox 86: 5th Saw Wave 87: Bass & Lead 88: Fantasia 89: Warm Pad
90: Polysynth 91: Space Voice 92: Bowed Glass 93: Metal Pad 94: Halo Pad
95: Sweep Pad 96: Ice Rain 97: Soundtrack 98: Crystal 99: Atmosphere
100: Brightness 101: Goblin 102: Echo Drops 103: Star Theme 104: Sitar
105: Banjo 106: Shamisen 107: Koto 108: Kalimba 109: Bagpipe
110: Fiddle 111: Shanai 112: Tinkle Bell 113: Agogo 114: Steel Drums
115: Woodblock 116: Taiko 117: Melo. Tom 1 118: Synth Drum 119: Reverse Cym.
120: Gt.FretNoise 121: Breath Noise 122: Seashore 123: Bird 124: Telephone 1
125: Helicopter 126: Applause 127: Gun Shot
Note: there are no tab characters above—only text, spaces, and newlines (\n).
Playback Adjustment
Each song is entered as a single line containing:
· an optional “playback adjustment” followed by a comma (“,”), and then
· a list of note symbols with a space (“ “) between them
The following is an example, with the playback adjustment in italics and the list of note symbols shown in bold:
4_2.3,Ab3_600 B-2_450 Db5_150 G#_5 E_300 D#-1_10
The playback adjustment contains two parts:
1. A transpose adjustment integer, and
2. A tempo adjustment double
For example, here is a playback adjustment with the transpose (4) in bold, and the tempo (2.3) in italics:
4_2.3
The “transpose” adjustment is a number that is added to every note present in that song’s note list. This can be used to raise or lower the pitch of the entire song. For example, if you calculated that the MIDI Note Numbers for a song are 12, 14, 16, and the transpose adjustment is 4, then you’d add 4 to each MIDI Note Number, before passing it to the playNote method. In this case, you’d pass 16 as the MIDI Note Number in your first call to playNote, 18 as the MIDI Note Number in your second call to playNote, and 20 as the MIDI Note Number in your third call to playNote. The transpose adjustment can be any integer (negative, zero, or positive).
The “tempo” adjustment is a number that is multiplied with every note duration present in the song’s note list. For example, if the note durations in the note list were 100, 200, 149, and the tempo adjustment is 2.3, then you’d multiply 2.3 with each note duration, before passing it to the playNote method. In this case, then, you’d pass 230 as the Note Duration in your first call to playNote, 460 as the Note Duration in your second call to playNote, and 342 as the Note Duration in your third call to playNote. Notice how the product is cast back into an integer (truncating everything after the decimal point) before passing it to playNote, as 149 * 2.3 is actually 342.7, but your Note Player will pass 342.
For example, you can speed up the song by using a tempo adjustment of 0.5 (to play in half the time), or slow down the song by using a tempo adjustment of 2 (to double the length). The tempo adjustment can be any positive double (less than 1 would speed up the song, greater than 1 would slow it down).
The entire playback adjustment is sometimes present, but not always. You are guaranteed to see either both parts or neither (i.e., you will never see a transpose without a tempo, or a tempo without a transpose).
A playback adjustment applies only to the line (note list) that it appears on. It affects every note played from that list, but has no effect on any future lines entered by the user.
Command order
Your user may issue commands in any order she likes. She may choose to play some songs, then list instruments and then set instrument 12 and then play more songs. Your user might even choose to just do list instruments over and over again and then quit. Or do set instrument 4 without first doing list instruments. The key point is that you may make no assumptions about which commands your user will type or in what order those commands will be typed.
Final Submission Requirements Summary
· All Checkpoint1, Checkpoint2, Checkpoint3, and Final Tests must pass.
· You will now accept multiple lines from the user (until the user types “quit”)
· You will accept and process set instrument and list instruments commands
· The beginning of each note list MAY have a playback adjustment (transpose and tempo). If so, it must be applied to each note on that line.
· Note that either both the transpose and tempo adjustments will be present on a line, or neither will be present on a line. You will never have one without the other.
Hints:
· You will find Double.parseDouble useful for the tempo adjustment. Look it up!
· When a tempo adjustment is present, you will need to cast the resulting duration to an int before calling playNote.