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/fall13/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 aRingBuffer
rather than declaring aRingBuffer
object itself. Then in theGuitarString
constructor you must use thenew
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 theGuitarString
's destructor.
- This is because you can't allow the ring buffer to be instantiated until the
- 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 thesf::Int16
range.sf::Int16
is a short integer, which can hold values from -32768 to 32767.
(sf::Int16)(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.
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) GuitarString
s 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::Int16
s.
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::SoundBuffer
s, 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.
// GuitarHeroLite.cpp // Fred Martin, fredm@cs.uml.edu // Sun Mar 16 18:51:06 2014 // // based on Princeton's GuitarHeroLite.java // http://www.cs.princeton.edu/courses/archive/fall13/cos126/assignments/guitar.html #include <iostream> #include <string> #include <exception> #include <stdexcept> #define _USE_MATH_DEFINES #include <math.h> #include <limits.h> #include <vector> #include <SFML/Graphics.hpp> #include <SFML/System.hpp> #include <SFML/Audio.hpp> #include <SFML/Window.hpp> #include "RingBuffer.hpp" #include "GuitarString.hpp" using namespace std; #define CONCERT_A 440.0 #define SAMPLES_PER_SEC 44100 // this makes a vector of <sf::Int16> from the Karplus-Strong string simulation 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; 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
GuitarHero
that is similar to GuitarHeroLite
, but supports a total of 37 notes on the chromatic scale from 110Hz to 880Hz.”
Notice the statement
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 individualsf::Int16
vector holds the audio sample stream generated by oneGuitarString
. - a vector of 37
sf::SoundBuffer
s. EachSoundBuffer
object contains a vector of audio samples. - a vector of 37
sf::Sound
s. EachSound
object contains aSoundBuffer
. (It's theSound
object that can finally be played.)
You don't need a vector of GuitarString
s. Once you've pluck
ed it and tic
ed 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 associatedRingBuffer.hpp
- Your
GuitarString.cpp
and itsGuitarString.hpp
- Your
GuitarHero.cpp
file - A
Makefile
that builds an executable, or a clear discussion of the dependencies and how to build. - A filled-in copy of the
ps5b-readme.txt
Turn in via:
submit fredm ps5b files
Grading rubric
Feature | Value | Comment |
GuitarString implementation | 4 | |
GuitarString unit tests | 1 | evidence that your implementation passes the GStest.cpp tests |
GuitarHero player implementation | 4 | transforming the Lite version into the full 37-note player per assignment |
Makefile | 1 | Makefile or explicit build/link instructions included |
readme | 2 | discussion is expected -- at least mentioning something per section. |
Total | 12 |