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
GuitarHero
client will call the methods 44,100 times per second).
- Is the size of a
RingBuffer
equal to its capacity?
No. The size of a
RingBuffer
is the number of elements currently in it; the capacity of aRingBuffer
is its maximum possible size.
- Is the size of a
RingBuffer
equal 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 strings
beginning at indexi
and ending at indexj-1
inclusive. Or you may include both endpoints of an interval. For example,s.substring(i..j)
returns the substring of the strings
beginning at indexi
and ending at indexj
inclusive.
- How do I round a
Double
up 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)
wherex
is 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
GuitarHeroLite
for 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 forGuitarString
does 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
keyboard
is aString
andkey
is a character, thenkeyboard.indexOf(key)
returns the integer index of the first occurrence of the characterkey
in 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_RATE
for 44,100 andCONCERT_A
for 440.0 and the expressionkeyboard.length
for 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
RingBuffer
data 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
GuitarString
data 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 i
What is this familiar melody? (
S
=space)nn//SS/ ..,,mmn //..,,m //..,,m nn//SS/ ..,,mmn
Here 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.txt
andGuitarHeroLite.kt
. - Review the material on digital audio.
- Start with the
RingBuffer.kt
template. 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
RingBuffer
in an incremental and iterative manner: implement one method at a time, together with a corresponding test inmain()
. Test using some small examples, perhaps aRingBuffer
of capacity 4. You might also want to implement aprivate
helper method that prints aRingBuffer
object, 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 yourRingBuffer
data 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.kt
template. 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
length
property. - Implement the
sample()
method. Usepeek()
. - Test: In
main
, test theGuitarString(frequency)
constructor:- Instantiate a few different
GuitarString
objects. - Use
length
property on each object. Is the value returned bylength
what you expect? - Invoke
sample()
on each object. Is the value returned bysample
what you expect?
- Instantiate a few different
- Test: In
main
, test theGuitarString(DoubleArray)
constructor:- Instantiate a few different
GuitarString
objects. - Use
length
property on each object. Is the value returned bylength
what you expect? - Invoke
sample()
on each object. Is the value returned bysample
what you expect?
- Instantiate a few different
- Implement
pluck()
. Use a combination ofRingBuffer
methods includingdequeue()
andenqueue()
to replace the buffer with values between −0.5 and 0.5. - Test: In
main
:- Instantiate a
GuitarString
object. - Invoke
length
andsample()
on this object. - Next invoke
pluck()
on this object. - Next invoke
length
andsample()
again on each object. Are the values returned what you expect? - Try this with a few
GuitarString
objects.
- Instantiate a
- Implement
tic()
. Use a combination ofenqueue()
,dequeue()
, andpeek()
. - Test: In
main
:- Instantiate a
GuitarString
object. - Invoke
length
andsample()
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
GuitarString
objects.
- Instantiate a
- After you implement the methods for
GuitarString
, runGuitarStringKt
using 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.kt
as a starting point. - Create an array of
GuitarString
objects. 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
GuitarString
instances 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.