ThinkDSP
This notebook contains an implementation of an algorithm to generate pink noise.
Copyright 2015 Allen Downey
Generating pink noise
The Voss algorithm is described in this paper:
Voss, R. F., & Clarke, J. (1978). "1/f noise" in music: Music from 1/f noise". Journal of the Acoustical Society of America 63: 258–263. Bibcode:1978ASAJ...63..258V. doi:10.1121/1.381721.
And presented by Martin Gardner in this Scientific American article:
Gardner, M. (1978). "Mathematical Games—White and brown music, fractal curves and one-over-f fluctuations". Scientific American 238: 16–32.
McCartney suggested an improvement here:
http://www.firstpr.com.au/dsp/pink-noise/
And Trammell proposed a stochastic version here:
The fundamental idea of this algorithm is to add up several sequences of random numbers that get updated at different rates. The first source should get updated at every time step; the second source every other time step, the third source ever fourth step, and so on.
In the original algorithm, the updates are evenly spaced. In the stochastic version, they are randomly spaced.
My implementation starts with an array with one row per timestep and one column for each of the white noise sources. Initially, the first row and the first column are random and the rest of the array is Nan.
The next step is to choose the locations where the random sources change. If the number of rows is , the number of changes in the first column is , the number in the second column is on average, the number in the third column is on average, etc.
So the total number of changes in the matrix is on average; since of those are in the first column, the other are in the rest of the matrix.
To place the remaining changes, we generate random columns from a geometric distribution with . If we generate a value out of bounds, we set it to 0 (so the first column gets the extras).
Within each column, we choose a random row from a uniform distribution. Ideally we would choose without replacement, but it is faster and easier to choose with replacement, and I doubt it matters.
Now we can put random values at rach of the change points.
Next we want to do a zero-order hold to fill in the NaNs. NumPy doesn't do that, but Pandas does. So I'll create a DataFrame:
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 0.548786 | 0.512219 | 0.640662 | 0.155794 | 0.366652 |
1 | 0.583193 | NaN | NaN | NaN | NaN |
2 | 0.843573 | NaN | NaN | NaN | NaN |
3 | 0.184188 | NaN | NaN | 0.388648 | NaN |
4 | 0.262221 | NaN | NaN | NaN | NaN |
And then use fillna
along the columns.
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 0.548786 | 0.512219 | 0.640662 | 0.155794 | 0.366652 |
1 | 0.583193 | 0.512219 | 0.640662 | 0.155794 | 0.366652 |
2 | 0.843573 | 0.512219 | 0.640662 | 0.155794 | 0.366652 |
3 | 0.184188 | 0.512219 | 0.640662 | 0.388648 | 0.366652 |
4 | 0.262221 | 0.512219 | 0.640662 | 0.388648 | 0.366652 |
Finally we add up the rows.
If we put the results into a Wave, here's what it looks like:
Here's the whole process in a function:
To test it I'll generate 11025 values:
And make them into a Wave:
Here's what it looks like:
As expected, it is more random-walk-like than white noise, but more random looking than red noise.
Here's what it sounds like:
And here's the power spectrum:
The estimated slope is close to -1.
We can get a better sense of the average power spectrum by generating a longer sample:
And using Barlett's method to compute the average.
It's pretty close to a straight line, with some curvature at the highest frequencies.
And the slope is close to -1.