In this assignment the simple Karplus-Strong string model was implemented to create a polyphonic synthesis instrument which can be played in real-time by MIDI input. The RtAudio and RtMidi libraries were used for handling real-time audio output and midi message handling. The project's source files include the necessary files from these libraries as well as:
In order to build the project, download the zipped source folder. Makefiles are provided to compile the code for either Mac OS X or Linux by specifying the correct makefile to use. Typing 'make' at a command line in the source directory will output the correct syntax for specifying which of these makefiles to use.
After completing the basic Karplus-Strong instrument, I augmented it with some extra controls described at the end of my comments section. The following is the result of two layers of recording this augmented Karplus-Strong based beast being played by a few people.
The goal of constructing this program was to implement a simple physical model (the Karplus-Strong string model) and to create a real-time playable polyphonic instrument using a collection of these models and the MIDI interface provided by the RtMidi library.
I structured my program into a few classes, each of which represent a level of hierarchy in the overall execution of sound synthesis. At the lowest level is the Delay class, which implements the circular buffer at the heart of the Karplus-Strong algorithm. I went a bit beyond the project specs and implemented an all-pass filter section on the end of the integer length delay line to allow more accurate tuning of the strings through the use of fractional delays. At the next level of hierarchy is the KSString class which contains a Delay object and implements the feedback and low-pass one zero filter specified in the Karplus-Strong string model, providing interfaces to "pluck()" the string (load it with white noise) and acquire its sound output. At the top level is the KSInstrument, which instantiates a user specified collection of strings in the equal temperment scale and manages plucking strings and summing the output of active strings. The KSInstrument also enfoces the polyphonic voice limit.
A few design choices had to be made while putting together this instrument. A limit on the number of concurrent polyphonic voices was part of the design requirements (to prevent CPU overload), and thus an early decision had to be made regarding whether to instantiate a Karplus-Strong string object for every note that the instrument could play or to only instantiate an amount of strings corresponding to the maximum number of concurrent voices. This was essentially a memory vs. performance design trade-off (of little real concern due to the simplicity of the model being used here, but worth some thought as an exercise). After analyzing the amount of memory complete instantiation would require, I found it to be the best method because it requires little memory (less than 0.25MB) and simplifies the process of triggering and managing notes (since the frequency of each string never has to be adjusted). Additionaly, since strings require less and less memory allocation as their frequency increases due to requiring shorter delay lines, the cost of instantiating objects for all strings was only minorly more than the cost of instantiating a restricted amount of string objects that could accomodate any reasonable amount of polyphony (since all allocated delay lines would have had to be long enough to accomodate the lowest playable note as they may have had to output it at some point).
Another design problem was how to best implement polyphony. I found a simple and elegant solution was to create an over-arching KSInstrument class that manages the collection of individual Karplus-Strong strings using a list. This data structure allows active strings to be easily added, kept in the order which they were triggered, and inexpensively removed from the list when no longer being played. I chose to simply not output additional notes when the voice maximum is reached (since I would rather the user be aware they have reached the limit than hope that dropping old voices won't be noticeable), so keeping the active strings in the order they were triggered was not highly important but easy removal of strings from the middle of the list made it a well suited data structure for the task and allowed the other voice handling method to be easily used instead if desired.
After completing the assignment, I added several additional functions for fun including MIDI controllable string feedback gain, an overall output delay with a MIDI controlled feedback gain, slowly varying delay length, and a one zero low pass filter in its feedback loop, and a sample hold feature which samples a MIDI controllable length of input and repeats it while the user continues playing.