Karplus–Strong string synthesis in JavaScript
TweetRecently I came across this awesome algorithm which can generate sound of a plucked string, kind of like when picking guitar strings. Idea of generating sound of a guitar string fascinated me and I wanted to understand and finally implement the algorithm to see it working.
Before I go ahead and start explaining, you can try it out by click the button below.
There are plenty of places on the web that give details of Karplus-Strong string synthesis algorithm. Basically, there are three main components that are required for this algorithm.
- Noise burst: A source of white noise for a brief time. It is used to inject energy into the string, which happens when you pluck the string.
- Delay Line: A delay line which would re-inject the noise again into the circuit after certain time delay. The length of the time after which it should re-inject noise depends on the fundamental frequency of the string we want to simulate.
- Filter: A low pass filter to dampen out energy out of the string.
The algorithm by itself generates a stream of audio samples. These audio samples must be converted into an audio signal which will be sent to the speakers to produce sound. This can be done using Web Audio API which provides interfaces that take audio samples and send them to computer’s hardware which can play them though the speakers. So, let us first get the setup ready which can send audio samples to the speaker.
The Setup
Here we create a global instance of AudioContext. It provides all the APIs require to interact with hardware. There must be only one instance of AudioContext, which is why I have a created a global object for it.
This creates a JavaScriptNode. JavaScriptNode can be used for sending an endless stream of audio samples to the hardware. AudioContext’s createJavaScriptNode takes three parameters. First parameter is the size of the buffer which will be used to send data to the hardware. Think of it like the buffer you would normally use for reading files from file system. Value of this should be a power of two like 256, 512, 1024, 2048, 4096 etc. Second and third parameters are the number of input and output channels respectively. One thing to note here is that jsNode variable must also be global otherwise chrome’s garbage collector will clean it up and jsNode will stop working once it is out of scope. This is probably a bug in chrome.
Next we define the callback that jsNode will call when it needs next buffersize number of samples. In this callback we iterate over the buffer array and fill it with new samples. The getNextSample() always returns the next sample in the sequence of samples that we want to send. All the magic now happens inside getNextSample() and we do not need to worry about buffersize, all that is taken care by this callback.
And finally we connect our JavaScript node to the hardware. With this we are done with code that can send arbitrary audio samples to the hardware.
1. Noise Burst
Noise source is nothing but a stream of samples with random values. Math.random() returns random number between 0 and 1. The above function returns a random number between -1 and 1. Now if you copy paste all of the above code in script tag of an html and load the page, you should hear some noise. In order to stop that noise you would have to close the tab since we are sending endless number of samples. For noise burst we should be able to send a fixed number of noise samples. Following version of getNextSample() does exactly that.
Now in some other function which can be called on click of a button we will set noise_samples to a finite value, so that after sometime it stops sending noise to audio device.
With this we have implemented first component, a noise burst, of karplus strong algorithm. It can inject a finite number of samples of white noise. Try this in your browser to see if it works.
2. Delay Line
Next we need to create a delay line. A delay line is used to re-introduce the samples back after a delay of time T. We can implement a delay line by storing the samples in an array. But how do we calculate number of samples the array should contain? In order to calculate number of samples we need to know the sampling rate of the audio card. Audio card plays a certain number of samples every second, called the Sample Rate. So to create a delay of time T we need T times he Sampling Rate of the Audio Card. sampleRate property in AudioContext tells us precisely that.
But wait, what is time T? The length of the delay line depends on the fundamental frequency of the string we are simulating, it is actually the length of the one complete cycle or time period of the wave with frequency same as the fundamental frequency. We know that time period of any wave is inverse of its frequency. So, let freq be the fundamental frequency of the wave so
Now let us plug this delay line into our getNextSample() function.
That’s it. We have created a delay line and plugged that into out sample generator. Currently, delay line does nothing but stores the samples when noise samples are being sent and then plays them later when there are no noise samples. Since there is no damping or losses this creates an endless stream of same noise samples repeating at a time interval of 1/freq. You can try this out, you should hear a clean note around 440Hz, and you may have to close the tab to shut it down.
3. Filter
Now lets add our third component which is a filter or to be accurate, a low pass filter. A discrete low pass filter can be implemented using the formula. y[i] = y[i-1] + alpha*(x[i] - y[i-1]) where, alpha is the gain factor, usually ranges between 0 to 1, y[i] is the output sample to be calculated y[i-1] is last calculated value x[i] is the current input value. Let us add this to our sample generator (getNextSample)
With this our implementation of Karplus-Strong algorithm is almost complete, there is one simple change we need to make to activate method.
If you now call activate method should hear the sound of a plucked string. You can play around with different frequencies and alpha values. Closer the alpha value to 1 longer the string would vibrate. Below is the complete source code listing.