Guitar Hero Checklist
Introduction
- What are the goals of this assignment?
To learn how to create user-defined data types (classes) in Kotlin and to learn about digital audio.
- Do I need to follow the prescribed API?
Yes, we will be testing the methods in the API directly. If your method has a different signature or does not behave as specified, you will lose a substantial number of points. You may not add public properties or functions to the API; however, you may add private properties or functions (which are only accessible in the class in which they are declared).
RingBuffer
- How should I represent a
RingBuffer?
There are many viable approaches, including one suggested in the assignment specification. Be sure that your representation allows you to implement each operation efficiently (because the
GuitarHeroclient will call the methods 44,100 times per second).
- Is the size of a
RingBufferequal to its capacity?
No. The size of a
RingBufferis the number of elements currently in it; the capacity of aRingBufferis its maximum possible size.
- Is the size of a
RingBufferequal to the number of its nonzeros elements?
No. Some of the elements in the buffer can be zero.
- I get the following error message on OS X. How can I fix it?
This application, or a library it uses, is using the deprecated Carbon Component Manager for hosting Audio Units. Support for this will be removed in a future release.
You can ignore it. It is a warning intended for the authors of the Mac OS X Java sound library.
GuitarString
- When generating random values between −0.5 and +0.5, should I include the two endpoints?
The assignment specification does not specify, so you are free to choose whichever convention you find most convenient. You may include the starting point but exclude the ending point of an interval. For example,
s.substring(i,j)returns the substring of the stringsbeginning at indexiand ending at indexj-1inclusive. Or you may include both endpoints of an interval. For example,s.substring(i..j)returns the substring of the stringsbeginning at indexiand ending at indexjinclusive.
- How do I round a
Doubleup to the nearestInt?
Use
Double.roundToInt()from kotlin.math.
GuitarHero
- Where do I enter keystrokes in GuitarHeroLite and GuitarHero?
Be sure that the standard drawing window has focus by clicking in it. Then, type the keystrokes.
- What happens if I call
StdAudio.play(x)wherexis greater than 1 or less than −1?
The value is clipped—it is replaced by the value +1.0 or −1.0, respectively. This is fine and can happen when you play chords.
- When I run
GuitarHeroLitefor the first time, I hear no sound. What am I doing wrong?
Make sure you have tested with the
main()provided forGuitarString. If that works, it is likely something’s wrong withpluck()since themain()provided forGuitarStringdoes not test that method. To diagnose the problem, print the values ofsample()and check that they become nonzero after you type the lowercase characters'a'and'c'.
- When I run
GuitarHeroLite, I hear static (either just one click, and then silence or continual static). What am I doing wrong?
It's likely that
pluck()is working, buttic()is not. The best test is to run themain()provided forGuitarString.
- How do I use
keyboard.indexOf(key)?
If
keyboardis aStringandkeyis a character, thenkeyboard.indexOf(key)returns the integer index of the first occurrence of the characterkeyin the stringkeyboard, or –1 if it does not occur. You can read about it in the Java String documentation page
- Can I hardwire the constants 44,100, 440.0, and 37 in my program?
As usual, we may deduct style points for using an unnamed constant, especially if you use it more than once. We recommend using the name
SAMPLING_RATEfor 44,100 andCONCERT_Afor 440.0 and the expressionkeyboard.lengthfor 37. You do not need to name all of the constants in the formula 2 (i − 24) / 12.
Testing
Be sure to thoroughly test each piece of your code as you write it. We offer some suggestions below, but these are only partial tests. Write code to test each constructor and each method of each class, especially in RingBuffer.
- Ring buffer. You can test your
RingBufferdata type on the followingmain(). It enqueues the numbers 1 throughn, and then repeatedly dequeues the first two, and enqueues their sum.
fun main(args: Array<String>) { val n = args[0].toInt() val buffer = RingBuffer(n) for (i in 1..n) buffer.enqueue(i.toDouble()) val t = buffer.dequeue() buffer.enqueue(t) println("Size after wrap-around is " + buffer.size) while (buffer.size >= 2) { val x = buffer.dequeue() val y = buffer.dequeue() buffer.enqueue(x + y) } println(buffer.peek()) }
$ kotlin RingBufferKt 10 Size after wrap-around is 10 55.0
$ kotlin RingBufferKt 100 Size after wrap-around is 100 5050.0
- Guitar string. You can test your
GuitarStringdata type with the followingmain():
fun main(args: Array<String>) { val samples = doubleArrayOf( 0.2, 0.4, 0.5, 0.3, -0.2, 0.4, 0.3, 0.0, -0.1, -0.3 ) val testString = GuitarString(samples) val m = 25 // 25 tics for (i in 0 until m) { val sample = testString.sample() println("%6d %8.4f".format(i, sample)) testString.tic() } }
$ kotlin GuitarStringKt 0 0.2000 1 0.4000 2 0.5000 3 0.3000 4 -0.2000 5 0.4000 6 0.3000 7 0.0000 8 -0.1000 9 -0.3000 10 0.2988 11 0.4482 12 0.3984 13 0.0498 14 0.0996 15 0.3486 16 0.1494 17 -0.0498 18 -0.1992 19 -0.0006 20 0.3720 21 0.4216 22 0.2232 23 0.0744 24 0.2232
Next, using your implementations of RingBuffer and GuitarString, compile and execute GuitarHeroLiteKt.
Guitar Hero. Type the following into your guitar to get the beginning of Led Zeppelin's Stairway to Heaven. Multiple notes in a column are dyads and chords.
w q q 8 u 7 y o p p i p z v b z p b n z p n d [ i d z p i p z p i u i iWhat is this familiar melody? (
S=space)nn//SS/ ..,,mmn //..,,m //..,,m nn//SS/ ..,,mmnHere is a round that you can practice with 3 friends:
x c v x x c v x v g n v g n nmngv x nmngv x x p x x p x x c v x x c v x v g n v g n nmngv x nmngv x x p x x p x x c v x x c v x v g n v g n nmngv x nmngv x x p x x p x x c v x x c v x v g n v g n nmngv x nmngv x x p x x p x
Possible Progress Steps (RingBuffer)
These are purely suggestions for how you might make progress. You do not have to follow these steps.
- Download the files for this project. They are zipped as guitar.zip. The zip file include the
readme.txtandGuitarHeroLite.kt. - Review the material on digital audio.
- Start with the
RingBuffer.kttemplate. You will need to fill in the variables, properties, constructors, and methods. As indicated in the assignment specification, we recommend defining the instance variables as follows:
private val rb DoubleArray<capacity> // items in the buffer private var first ... // index for the next dequeue or peek private var last ... // index for the next enqueue var size ... // number of items in the buffer private set- Develop
RingBufferin an incremental and iterative manner: implement one method at a time, together with a corresponding test inmain(). Test using some small examples, perhaps aRingBufferof capacity 4. You might also want to implement aprivatehelper method that prints aRingBufferobject, i.e., prints the current values forfirst,last, andsize. This can be very useful to help you debug! After you implement all the methods for
RingBuffer, test it using the test client. Do not proceed until you have thoroughly tested yourRingBufferdata type.
Possible Progress Steps (GuitarString)
These are purely suggestions for how you might make progress. You do not have to follow these steps.
- Start with the
GuitarString.kttemplate. You will need to fill in the variables, properties, constructors, and methods. - Implement the
GuitarString(DoubleArray)constructor. - Implement the
GuitarString(Int)constructor. - The primary constructor should have an init block and the secondary constructor should call the primary constructor.
- Implement the
lengthproperty. - Implement the
sample()method. Usepeek(). - Test: In
main, test theGuitarString(frequency)constructor:- Instantiate a few different
GuitarStringobjects. - Use
lengthproperty on each object. Is the value returned bylengthwhat you expect? - Invoke
sample()on each object. Is the value returned bysamplewhat you expect?
- Instantiate a few different
- Test: In
main, test theGuitarString(DoubleArray)constructor:- Instantiate a few different
GuitarStringobjects. - Use
lengthproperty on each object. Is the value returned bylengthwhat you expect? - Invoke
sample()on each object. Is the value returned bysamplewhat you expect?
- Instantiate a few different
- Implement
pluck(). Use a combination ofRingBuffermethods includingdequeue()andenqueue()to replace the buffer with values between −0.5 and 0.5. - Test: In
main:- Instantiate a
GuitarStringobject. - Invoke
lengthandsample()on this object. - Next invoke
pluck()on this object. - Next invoke
lengthandsample()again on each object. Are the values returned what you expect? - Try this with a few
GuitarStringobjects.
- Instantiate a
- Implement
tic(). Use a combination ofenqueue(),dequeue(), andpeek(). - Test: In
main:- Instantiate a
GuitarStringobject. - Invoke
lengthandsample()on this object. - Next invoke
tic()on this object. - Next invoke
length()andsample()again on each object. Are the values returned what you expect? - Try this with a few
GuitarStringobjects.
- Instantiate a
- After you implement the methods for
GuitarString, runGuitarStringKtusing the test client. - Now test using
GuitarHeroLiteKt. Which additional constructor and method does that test?
Possible Progress Steps (GuitarHero)
These are purely suggestions for how you might make progress. You do not have to follow these steps.
- To develop
GuitarHero.kt, useGuitarHeroLite.ktas a starting point. - Create an array of
GuitarStringobjects. Remember, to create an array of objects, you need to create the array, then you need to create each individual object. - Can you play individual notes? Do the notes have the correct frequencies? Can you play a chord?
Optional: Enrichment
Here are some concrete ideas for synthesizing other instruments. Some come from the paper of Karplus and Strong.
- Harp strings: Flipping the sign of the new value before enqueueing it in
tic()will change the sound from guitar-like to harp-like. You may want to play with the decay factors to improve the realism, and adjust the buffer sizes by a factor of two since the natural resonance frequency is cut in half by thetic()change. - Drums: Flipping the sign of a new value with probability 0.5 before enqueueing it in
tic()will produce a drum sound. A decay factor of 1.0 (no decay) will yield a better sound, and you will need to adjust the set of frequencies used. - Guitars play each note on one of six physical strings. To simulate this you can divide your
GuitarStringinstances into 6 groups, and when a string is plucked, zero out all other strings in that group. - Pianos come with a damper pedal which can be used to make the strings stationary. You can implement this by, on iterations where a certain key (such as Shift) is held down, changing the decay factor.
- While we have used equal temperament, the ear finds it more pleasing when musical intervals follow the small fractions in the just intonation system. For example, when a musician uses a brass instrument to play a perfect fifth harmonically, the ratio of frequencies is 3/2=1.5 rather than 2 7/12 ∼ 1.498. Write a program where each successive pair of notes has just intonation.
Here is some cool stuff that is closely related.
- ChucK. ChucK as specialized programming language for real-time synthesis, composition, and performance originated by Ge Wang and Perry Cook at Princeton University. Here's the Karplus–Strong algorithm in ChucK.
- Slide flute. Here's a description of a physically modeled slide flute by Perry Cook.