Recent Changes - Search:
ECG Home

GitHub

People

Publications

Calendar

Projects

Fall 2017

Older Courses

Spring 2017

Fall 2016

Spring 2016

Fall 2015

Spring 2015

Fall 2014

Spring 2014

Fall 2013

Spring 2013

Fall 2012

Spring 2012

Fall 2011

Spring 2011

Fall 2010

Spring 2010

Fall 2009

Spring 2009

Fall 2008

Spring 2008

Fall 2007

HOWTOs

edit SideBar

PS5b

Home Assignments Lecture Blog Resources Discussion Group

Guitar Hero: GuitarString implementation and SFML audio output (part B)

In Part B, we implement the Karplus-Strong guitar string simulation, and generate a stream of string samples for audio playback under keyboard control.

See http://www.cs.princeton.edu/courses/archive/spr15/cos126/assignments/guitar.html for the full assignment.

GuitarString Implementation

Write a class named GuitarString that performs the Karplus-Strong string simulation described in the Princeton assignment.

Our version of the class should implement the following API:

class GuitarString
---------------------------------------------------------------------------------------------------
          GuitarString(double frequency)       // create a guitar string of the given frequency
                                               //   using a sampling rate of 44,100
          GuitarString(vector<sf::Int16> init) // create a guitar string with
                                               //   size and initial values are given by the vector
     void pluck()                              // pluck the guitar string by replacing the buffer
                                               //   with random values, representing white noise
     void tic()                                // advance the simulation one time step
sf::Int16 sample()                             // return the current sample
      int time()                               // return number of times tic was called so far
---------------------------------------------------------------------------------------------------

Notes

  • In the GuitarString private member variables declarations, you must declare a pointer to a RingBuffer rather than declaring a RingBuffer object itself. Then in the GuitarString constructor you must use the new operator.
    • This is because you can't allow the ring buffer to be instantiated until the GuitarString constructor is called at run time (you don't know how big a ring buffer to make until given the frequency of the string).
    • See http://stackoverflow.com/questions/12927169/how-can-i-initialize-c-object-member-variables-in-the-constructor for an explanation.
    • Because the ring buffer contained in the guitar string class will be a pointer to a ring buffer, you'll need to use the dereference operator (*) to get at the ring buffer object itself.
    • Remember to explicitly delete the ring buffer object in the GuitarString's destructor.
  • In the GuitarString(double frequency) constructor, you must using the ceiling function when calculating the size of the ring buffer. See http://www.cplusplus.com/reference/cmath/ceil/ for details.
  • In the pluck method, you must fill the guitar string's ring buffer with random numbers over the int16_t range. int16_t is a short integer, which can hold values from -32768 to 32767.
Here is a snippet of code that can be used to generate a random number in this range: (int16_t)(rand() & 0xffff)
  • Also in pluck, the guitar string's ring buffer might already be full. So you should either empty it (by dequeuing values until it's empty), or by deleting it and making a new one which you'll then fill up.
Or, you could add a new method to your ring buffer, empty(), which would set the _first and _last index member variables to 0, and the _full boolean to false. (This would be the most efficient solution.)

Testing your GuitarString implementation

Before you proceed to generate sound, test that your GuitarString is implemented correctly!

Do this by compiling it against this test file: Attach:GStest.cpp.

Build instructions are at the top of the file.

SFML Audio Output

There are two parts of generating audio: (1) getting values out of the GuitarString object and into SFML audio playback object, and (2) playing the audio objects when key press events occur.

These are explained below, followed by demonstration code.

Getting samples out of GuitarString and into SFML Sound

In the sample code provided by Princeton, the stream of values from the GuitarString object is retrieved one value at a time and immediately given to an audio playback object.

This is an elegant solution and allows us to mix together signals from two (or more) GuitarStrings by averaging their values. (This is based on a similar superposition theorem that we used in the 2D physics simulation.)

For SFML, we have to have an existing sf::SoundBuffer that's created with a vector of sound samples. This SoundBuffer is created from a vector of sf::Int16s.

Then we create an sf::Sound object from the sf::SoundBuffer. The sf::Sound object can then be played.

So the whole sequence is:

Playing SFML Sounds when key presses occur

We'll use SFML to create an electronic keyboard:

  • When the “a” key is pressed, a sound corresponding to concert A (440 Hz) should be played.
  • When the “c” key is pressed, a C note should be played.

To handle the keypress events, we'll open an SFML window, and look for sf::Event::KeyPressed events.

When we get one, we'll see if its event.key.code is equal to sf::Keyboard::A or sf::Keyboard::C.

If so, we'll play the appropriate sound.

See the sample code below for how to do this.

GuitarHeroLite.cpp demo file

Here is runnable sample code that when given a correct implementation of GuitarString, will play a 440 Hz A string when the “a” key is pressed, and the corresponding C note when the “c” key is pressed.

In the first half of the code, two GuitarString objects are created (one for each frequency), and each is cranked to produce a stream of audio samples that are loaded into a sf::Int16 vector. Those vectors are made into sf::SoundBuffers, and those are made into playable sf::Sound objects.

In the second half of the code, an SFML window and event loop is set up to play the sounds when the “a” or “c” keys are pressed.

This file may be downloaded here: Attach:GuitarHeroLite.cpp.

/*
  Copyright 2015 Fred Martin, fredm@cs.uml.edu
  Mon Mar 30 08:58:49 2015

  based on Princeton's GuitarHeroLite.java
  www.cs.princeton.edu/courses/archive/fall13/cos126/assignments/guitar.html

  build with
  g++ -Wall -c GuitarHeroLite.cpp -lsfml-system \
     -lsfml-audio -lsfml-graphics -lsfml-window
  g++ -Wall GuitarHeroLite.o RingBuffer.o GuitarString.o \
   -o GuitarHeroLite -lsfml-system -lsfml-audio -lsfml-graphics -lsfml-window
*/

#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <SFML/Audio.hpp>
#include <SFML/Window.hpp>

#include <math.h>
#include <limits.h>

#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
#include <vector>

#include "RingBuffer.hpp"
#include "GuitarString.hpp"

#define CONCERT_A 220.0
#define SAMPLES_PER_SEC 44100

vector<sf::Int16> makeSamplesFromString(GuitarString gs) {
  std::vector<sf::Int16> samples;

  gs.pluck();
  int duration = 8;  // seconds
  int i;
  for (i= 0; i < SAMPLES_PER_SEC * duration; i++) {
    gs.tic();
    samples.push_back(gs.sample());
  }

  return samples;
}

int main() {
  sf::RenderWindow window(sf::VideoMode(300, 200), "SFML Guitar Hero Lite");
  sf::Event event;
  double freq;
  vector<sf::Int16> samples;

  // we're reusing the freq and samples vars, but
  // there are separate copies of GuitarString, SoundBuffer, and Sound
  //   for each note
  //
  // GuitarString is based on freq
  // samples are generated from GuitarString
  // SoundBuffer is loaded from samples
  // Sound is set to SoundBuffer

  freq = CONCERT_A;
  GuitarString gs1 = GuitarString(freq);
  sf::Sound sound1;
  sf::SoundBuffer buf1;
  samples = makeSamplesFromString(gs1);
  if (!buf1.loadFromSamples(&samples[0], samples.size(), 2, SAMPLES_PER_SEC))
    throw std::runtime_error("sf::SoundBuffer: failed to load from samples.");
  sound1.setBuffer(buf1);

  freq = CONCERT_A * pow(2, 3.0/12.0);
  GuitarString gs2 = GuitarString(freq);
  sf::Sound sound2;
  sf::SoundBuffer buf2;
  samples = makeSamplesFromString(gs2);
  if (!buf2.loadFromSamples(&samples[0], samples.size(), 2, SAMPLES_PER_SEC))
    throw std::runtime_error("sf::SoundBuffer: failed to load from samples.");
  sound2.setBuffer(buf2);

  while (window.isOpen()) {
    while (window.pollEvent(event)) {
      switch (event.type) {
      case sf::Event::Closed:
        window.close();
        break;

      case sf::Event::KeyPressed:
        switch (event.key.code) {
        case sf::Keyboard::A:
          sound1.play();
          break;
        case sf::Keyboard::C:
          sound2.play();
          break;
        default:
          break;
        }

      default:
        break;
      }

      window.clear();
      window.display();
    }
  }
  return 0;
}

Your assignment

Once you have your GuitarString class implemented, extend GuitarHeroLite starter code per the Princeton assignment.

Follow the instructions that begin with the statement

“Write a program GuitarHero that is similar to GuitarHeroLite, but supports a total of 37 notes on the chromatic scale from 110Hz to 880Hz.”

Notice the statement

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

For our implementation, we actually need three parallel arrays (please use vectors):

  • a vector of 37 sf::Int16 vectors. Each individual sf::Int16 vector holds the audio sample stream generated by one GuitarString.
  • a vector of 37 sf::SoundBuffers. Each SoundBuffer object contains a vector of audio samples.
  • a vector of 37 sf::Sounds. Each Sound object contains a SoundBuffer. (It's the Sound object that can finally be played.)

You don't need a vector of GuitarStrings. Once you've plucked it and ticed it a bunch of times to get the sound samples out of it—and stored into the Int16 vector—you can throw it away and make a new one for the next frequency.

To turn in

You should turn in the following:

  • Your RingBuffer.cpp and associated RingBuffer.hpp
  • Your GuitarString.cpp and its GuitarString.hpp
  • Your GuitarHero.cpp file
  • A Makefile that builds an executable named GuitarHero.
  • A filled-in copy of the ps5b-readme.txt

Put all of these into a subdirectory named ps5b, tar up and gzip to:

Extra credit

For extra credit, make a version of the program that makes a different sound. Modify the algorithm to get a sound that resembles drum, chirp, piano, or anything other than the guitar.

This sound doesn't have to simulate a specific instrument. Here's a couple of ideas:

  1. Make your algorithm vary the number of samples on the queue as the sound is being synthesized, producing a frequency chirp. For example, for each 100 times that tic() is called, remove 100 samples from the queue, but only re-insert 99 samples. This will produce an up-frequency chirp (make sure to stop removing samples when the queue is almost empty, so that peek() and dequeue() don't throw exceptions for empty queue.)
  2. Change the low-pass filter so it leaves some of the noise in the buffer for longer, resulting in a "noisier" sound - this will sound more like a percussion instrument. One way to do this is to mix 90% of the last sample and 10% of the second-last sample (guitar sound uses 50%/50% mix.)

Your Makefile should build both the GuitarHero binary and your extra-credit binary.

Grading rubric

FeatureValueComment
GuitarString implementation4 
GuitarString unit tests1evidence that your implementation passes the GStest.cpp tests
GuitarHero player implementation4transforming the Lite version into the full 37-note player per assignment
Makefile1Makefile or explicit build/link instructions included
readme2discussion is expected -- at least mentioning something per section.
Total12 
Extra credit2Make a version of the program that makes a different sound. Modify the algorithm to get drum, chirp, piano, or anything other than the guitar
Edit - History - Print - Recent Changes - Search
Page last modified on May 17, 2015, at 11:03 AM