Synthesizing music
Please be warned again that this is not my area of expertise. Don’t take anything I say too seriously. At best anything I say it just a reasonable approximation that happens to work for me.
I’m assuming you’re trying to generate musical tones, so I’ll talk about frequencies as if you already know what frequency you want to produce. I will also be using coffee-script and the Firefox Audio Data API.
The max value of a sample is 1.0 and the min value is -1.0 so all samples will be between these two values (these are the “peaks” of the volume).
Frequency / Period
If you consider a sound sample to be a sequence of a repeating shape, then count how many times per second does the shape repeat? This is called the “period” (e.g. the period of the sample/signal). The frequency then will be 1/p (where p is the period).
If your sample rate is SRATE, then that’s how many samples are there in a second. So, to get how many samples are required to generate one period p, it’s srate/freq.
Technically this is the “fundamental frequency” but I’m not too deep on the math of signal processing.
Sine waves
Sine waves produce a pure “peeeeeeep” sound. Generally speaking a sine wave is a good start when you’re hacking around, but I don’t know if you can get far enough with it. The formula for generating a sound wave doesn’t require calculating the period or anything like that; just the frequency.
The formula for it is simple mathematically: given a point in a long sound sample (think of point as index):
sine = (freq, point) ->
k = 2 * Math.PI * freq / SRATE
Math.sin(k * point)
This is not very efficient as you keep calculating k over and over, so:
sine = (freq) ->
k = 2 * Math.PI * freq / SRATE
(point) -> Math.sin(k * point)
Now sine(440) returns a function that takes a point and returns a sample value.
Sine waves are not very interesting like I already said. I thought we’d build more complex sounds on top of them, but it turns out they’re not needed at all when synthesizing string sounds using the karplus-strong algorithm. Perhaps sine waves are useful when generating sounds in another way.
There are a few extra things you should take note of, I already mentioned them here:
Quick notes on generating sine waves
Basically you’ll need to dampen the signal using e^(-5x) (you can change 5 to 4 or something else — feel free to experiment with it). To get x you have to know when do you want the signal to end.
In code would look like this:
dampen = (point, lastpoint) ->
x = point / lastpoint
Math.pow(Math.E, x * -5)
When you write the output, you multiply the sample from sine by the damp factor from dampen.
And the pink noise process is dead simple: you multiply the signal by 100/freq
s = sinefn(point)
d = dampen(point, lastpoint)
p = 100/freq
output[point] = s * d * p
Wavetables
Another way to generate sound is to use a pre-populated list. For some reason this list is called a wavetable. Please don’t confuse this table with hash-tables or dictionaries: it’s just an array of values.
For a sound of frequency freq, the size of the wavetable is SRATE/freq. Now this actually doesn’t produce exactly the desired frequency, but a close enough frequency that the ear can’t differentiate really.
See, if you want to generate a wavetable for the frequency 440, here’s what would happen:
coffee> SRATE = 96000
96000
coffee> freq = 440
440
coffee> SRATE/freq
218.1818181818182
coffee> Math.floor SRATE/freq
218
coffee> SRATE / 218
440.3669724770642
The period length would be 218, but that’s the period associated with frequency 440.4. This difference is like I said small enough that the human ear doesn’t notice a thing.
So if you pre-populate an array of this size, you can generate the samples on the fly:
length = Math.round SRATE/freq
wavetable = new Float32Array(length)
# fill table somehow
(point) ->
wavetable[point % length]
Since the process of pre-populating itself can be time-consuming, we can do it lazily:
(point) ->
if point < length
wavetable[point] = wavefn(point)
else
wavetable[point % length]
This leads us to the Karplus-Strong method of synthesizing guitar strings, which I already wrote about here:
Very simple implementation of Karplus-Strong algorithm in coffeescript
Note that it doesn’t make use of sine waves at all. Instead, it starts with a noise sample and causes it to decay in a very simple-minded way. It’s very surprising (to me at least) that it manages to generate a sound like that.
OK, this sums up my experience so far. Unfortunately I haven’t come across the ultimate function that can generate any kind of musical instrument :) but that ok, since it wasn’t my goal to begin with.