Guitar Hero

Start: 11/19/2019
Due: 12/06/2019, 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 using their stdlib library.


Goal

Write a Kotlin program using the Processing and Minim libraries 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 the 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.

Simulating 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

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 complete two Kotlin classes to obtain a program that plays guitar interactively. We’ll describe the specifications for the classes in detail below.

Files for this assignment

The file guitar.zip is a zipped gradle project containing these files

    src/main/kotlin/GuitarHero.kt
    src/main/kotlin/GuitarString.kt
    readme.txt
    build.gradle.kts

The file src/main/kotlin/GuitarString.kt is a skeleton code for you to fill out.

The file src/main/kotlin/GuitarHero.kt actually contains code for GuitarHeroLite.kt to be described in the next subsection. You are supposed to evolve it into GuitarHero.kt.

The file readme.txt as usual contains this project's template for you to fill out and submit along with your Kotlin code.

The file build.gradle.kts is your gradle build file. Don’t modify it unless you know what you’re doing.

Guitar string

The first thing you should do is add code to this GuitarString class:

    /* file: GuitarString.kt */
    @file:Suppress("DEPRECATION")

    import kotlin.math.roundToInt
    import ddf.minim.*

    const val DECAY_FACTOR = 0.996F // energy decay factor

    class GuitarString(pitch: Float) : AudioSignal {
        // DECLARE THE DATA STRUCTURE FOR RING BUFFER HERE

        init {
            // CODE FOR THE PRIMARY CONSTRUCTOR GOES HERE
        }

        // The following two member functions: pluck() & generate()
        // are the heart of the Karplus-Strong algorithm.

        // Fills the buffer with random noise.
        fun pluck() {
            // YOUR CODE GOES HERE
        }

        // Fills the [signal] array with samples from buffer while updating
        // the buffer according to KS algorithm.
        override fun generate(signal: FloatArray) {
            // YOUR CODE GOES HERE
        }

        // AudioSignal requires both mono and stereo generate functions.
        override fun generate(left: FloatArray, right: FloatArray) {
            // YOUR CODE GOES HERE
        }
    }

The class should have 2 properties, a ring buffer and a pointer to its front.

The constructor should create the buffer whose size is determined by the pitch parameter.

The pluck() function should replace the items in the ring buffer with random values between −0.5 and +0.5.

Note that the GuitarString class implements the AudioSignal interface of the Minim library. This interface requires that the class implement code for two generate methods

    fun generate(signal: FloatArray)
    fun generate(left: FloatArray, right: FloatArray)

The generate(signal: FloatArray) function applies the Karplus-Strong algorithm described in the previous section. It works on the ring buffer, copying its content into the signal channel (which is a simple float array), while generating new stuff to fill the buffer and moving the front pointer appropriately, until the signal channel is full.

The code for the generate(left: FloatArray, right: FloatArray) function is simple. It calls the generate(left) to fill the left signal channel, and then copies the whole left signal channel into the right channel.

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 letter 'a', 'A', 'c', or 'C', the program plucks the corresponding string.

    /* file: GuitarHeroLite.kt */
    import kotlin.math.pow
    import processing.core.*
    import ddf.minim.*

    const val CONCERT_A = 440.0F
    lateinit var out: AudioOutput
    lateinit var stringA: GuitarString
    lateinit var stringC: GuitarString

    fun main(args: Array<String>) {
        PApplet.main("GuitarHeroLite")
    }

    class GuitarHeroLite : PApplet() {
        override fun settings() {
            size(512, 200)
        }

        override fun setup() {
            background(0)
            val minim = Minim(this)

            /*
             * Gets a line out from Minim, default bufferSize is 1024,
             * default sample rate is 44100, bit depth is 16
             */
            out = minim.getLineOut(Minim.STEREO)

            // Creates 2 guitar strings, one for A and the other for C.
            stringA = GuitarString(CONCERT_A)
            stringC = GuitarString(CONCERT_A * 2F.pow(3F / 12F))

            // Sets default size & alignment of text.
            textSize(32F)
            textAlign(CENTER)
        }

        override fun draw() {}

        override fun keyPressed() {
            background(0)
            text(key, width / 2F, height / 2F)
            if (key == 'a' || key == 'A')
                stringA.pluck()
            else if (key == 'c' || key == 'C')
                stringC.pluck()
        }
    }

When you believe you have completed the GuitarString class, you should immediately try running your program (through gradle). Try hitting keys on the keyboard, especially the a and c keys. If your program gives out the A guitar-plucked note in response to the a and A keys, and the C guitar-plucked note in response to the c and C keys, you can proceed to the next step.

Your next step is to evolve GuitarHeroLite.kt to become GuitarHero.kt. Your GuitarHero.kt should differ from GuitarHeroLite.kt in two respects.

  1. GuitarHero.kt 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.
  2. The draw() method of GuitarHero.kt is not empty. It should contain code to animate the sounds the guitar strings are making. To do this, draw() should display the waveforms of the signals from both the left and right channels of the out AudioOutput variable.

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 all your Kotlin source code, and the completed readme.txt file, and submit the zip file via Moodle. You may submit additional .kt and .txt files for extra credit too.