Guitar Hero

Start: 11/20/2018
Due: 12/5/2018, by the beginning of class
Collaboration: individual
Important links: style guidelines, Checklist, guitar.zip, gradesheet

This assignment was developed by Andrew Appel, Jeff Bernstein, Maia Ginsburg, Ken Steiglitz, Ge Wang, and Kevin Wayne, at Princeton University. Original language was Java.


Goal

Write a program to simulate plucking a guitar string using the Karplus–Strong algorithm. This algorithm played a seminal role in the emergence of physically modeled sound synthesis (where a physical description of a musical instrument is used to synthesize sound electronically).

Background

We’ll first discuss the theory behind guitar synthesizer.

Digital audio

Before reading the rest of this assignment, please review the lecture note on digital audio.

Simulate the plucking of a guitar string

When a guitar string is plucked, the string vibrates and creates sound. The length of the string determines its fundamental frequency of vibration. We model a guitar string by sampling its displacement (a real number between –1/2 and +1/2) at n equally spaced points in time. The integer n equals the sampling rate (44,100 Hz) divided by the desired fundamental frequency, rounded up to the nearest integer.

Sampling from Karplus-Strong

Sampling from Karplus-Strong

Why it works?

The two primary components that make the Karplus–Strong algorithm work are the ring buffer feedback mechanism and the averaging operation.

From a mathematical physics viewpoint, the Karplus–Strong algorithm approximately solves the 1D wave equation, which describes the transverse motion of the string as a function of time.

Tasks

Starting from the skeleton files we’re providing, you are to write two Kotlin classes and a program to play guitar interactively. We’ll describe the specifications for the classes and program in detail below.

Files for this assignment

The file guitar.zip contains GuitarHeroLite.kt; API templates for RingBuffer and GuitarString; and this project's readme.txt template.

Ring buffer

Your first task is to create a data type to model the ring buffer. Write a class named RingBuffer that implements the following API:

class RingBuffer(val capacity: Int) {
    // PROPERTIES (All are read-only properties.):
    //   size // number of items currently in this ring buffer
    //   capacity // largest possible value of size
    //   isEmpty // is this ring buffer empty (size equals zero)?
    //   isFull // is this ring buffer full (size equals capacity)?
    fun enqueue(x: Double) // adds item x to the end of this ring buffer
    fun dequeue(): Double // deletes and returns the item at the front of this ring buffer
    fun peek(): Double // returns (but do not delete) the item at the front of this ring buffer
}

Guitar string

Next, create a data type to model a vibrating guitar string. Write a class named GuitarString that implements the following API:

class GuitarString (/* argument of your main constructor goes here */) {
    // You need to define the val property
    //      length
    // that is of type Int and it returns the number of samples
    // in the ring buffer.
    //
    // YOU NEED ONE PRIMARY CONSTRCUTOR AND ONE SECONDARY CONSTRUCTOR.
    //
    // One constructor takes a Double parameter named frequency and
    // creates a guitar string of the specified frequency,
    // using a sampling rate of 44,100.
    //
    // The other constructor takes a DoubleArray parameter and
    // creates a guitar string whose size is to be derived from,
    // and its initial values are given by the array parameter.
    //
    fun pluck() // plucks the guitar string (by replacing the buffer with white noise)
    fun tic() // advances the Karplus-Strong simulation one time step
    fun sample(): Double // returns the current sample
}

Constructors. There are two ways to create a GuitarString object.

Length. Return the number of samples n in the RingBuffer.

Pluck. Replace the n items in the ring buffer with n random values between −0.5 and +0.5.

Tic. Apply the Karplus–Strong update: delete the sample at the front of the ring buffer and add to the end of the ring buffer the average of the first two samples, multiplied by the energy decay factor.

Sample. Return the value of the item at the front of the ring buffer.

Interactive guitar player

This file GuitarHeroLite.kt is a sample GuitarString client that plays the guitar in real time, using the keyboard to input notes. When the user types the lowercase letter 'a' or 'c', the program plucks the corresponding string. Since the combined result of several sound waves is the superposition of the individual sound waves, it plays the sum of the two string samples.

// file name: GuitarHeroLite.kt
import kotlin.math.pow

fun main(args: Array<String>) {
    // Create two guitar strings, for concert A and C
    val CONCERT_A = 440.0
    val CONCERT_C = CONCERT_A * 2.0.pow(3.0 / 12.0)
    val stringA = GuitarString(CONCERT_A)
    val stringC = GuitarString(CONCERT_C)
    // the main input loop
    while (true) {
        // check if the user has typed a key, and, if so, process it
        if (StdDraw.hasNextKeyTyped()) {
            // the user types this character
            val key = StdDraw.nextKeyTyped()
            // pluck the corresponding string
            if (key == 'a') stringA.pluck()
            if (key == 'c') stringC.pluck()
        }
        // compute the superposition of the samples
        val sample = stringA.sample() + stringC.sample()
        // send the result to the sound card
        StdAudio.play(sample)
        // advance the simulation of each guitar string by one step
        stringA.tic()
        stringC.tic()
    }
}

Write a program GuitarHero.kt that is similar to GuitarHeroLite.kt, but supports a total of 37 notes on the chromatic scale from 110 Hz to 880 Hz. Use the following 37 keys to represent the keyboard, from lowest note to highest note:

val keyboard: String = “q2we4r5ty7u8i9op-[=zxdcfvgbnjmk,.;/’”

This keyboard arrangement imitates a piano keyboard: The "white keys" are on the qwerty and zxcv rows and the "black keys" on the 12345 and asdf rows of the keyboard.

The ith character of the string keyboard corresponds to a frequency of 440 × 2 (i − 24) / 12, so that the character 'q' is 110 Hz, 'i' is 220 Hz, 'v' is 440 Hz, and ' ' is 880 Hz.

Don't even think of including 37 individual GuitarString variables or a 37-way if statement! Instead, create and initialize an array of 37 GuitarString objects and use keyboard.indexOf(key) to figure out which key was typed.

If a keystroke does not correspond to one of the 37 possible notes, ignore it.

Extra credit

Write a program AutoGuitar.kt that will automatically play music using GuitarString objects. A few ground rules:

You may create chords, repetition, and phrase structure using loops, conditionals, arrays, and functions. Also, feel free to incorporate randomness. You may also create a new music instrument by modifying the Karplus–Strong algorithm; consider changing the excitation of the string (from white noise to something more structured) or changing the averaging formula (from the average of the first two samples to a more complicated rule) or anything else you might imagine. See the checklist for some concrete ideas.

Gradesheet

We will use this gradesheet when grading your lab.

Submission

Zip up your RingBuffer.kt, GuitarString.kt, GuitarHero.kt, and a completed readme.txt, and submit the zip file via Moodle. You may submit additional .kt and .txt files for extra credit, so long as you do not modify the files RingBuffer.kt, GuitarString.kt, or GuitarHero.kt to accommodate your extra credit work.