I became interested in this after messing around with using xoshiro to generate white noise.
Pink noise has a 1/f falloff. It is described as a more natural sounding noise, similar to a waterfall.
DSP generation of Pink (1/f) Noise is the most thourough information I could find.
There are 2 main ways to generate pink noise.
I am focussing on the Voss-McCartney algorithm because it can use the same xoshiro code I already created for generating white noise. Also, the algorithm itself seems kind of neat and interesting.
Pink noise can be made by adding together the output of several noise generators. Each generator's output is updated at half the rate of the previous.
xxxxxxxxxxxxxxxxx x x x x x x x x x x x x x x x x x x x
void PinkNoise(float* pfBuffer, uint32_t uNumSamples, uint32_t uNumOctaves)
{
const uint32_t kuMaxKey = ((uint32_t)1 << uNumOctaves) - 1); // uNumOctaves bits set
uint32_t uKey = 0;
RandomGenerator noise;
float fNoiseVals[uNumOctaves];
for(uint32_t uOctave = 0; uOctave < uNumOctaves; uOctave++)
{
fNoiseVals[uOctave] = noise.Generate() / uNumOctaves;
}
for(uint32_t uSampleIdx = 0; uSampleIdx < uNumSamples; uSampleIdx++)
{
float fSum = 0.0f;
uint32_t uPrevKey = uKey;
uKey++;
if(uKey > kuMaxKey)
{
uPrevKey = 0;
uKey = 1;
}
const uint32_t kuDiff = uPrevKey ^ uKey; // Bits that have changed
for(uint32_t uOctave = 0; uOctave < uNumOctaves; uOctave++)
{
if(kuDiff & (1 << uOctave))
{
noiseVals[i] = noise.Generate() / uNumOctaves;
}
fSum += noiseVals[uOctave];
}
pfBuffer[uSampleIdx] = fSum;
}
}
It is worth taking the time to make sense of the bit manipulation, and how that is generating the update pattern for the noise. It helps to plot out the binary numbers. The numbers 0 to 8 in binary representation are
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
| 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 |
| 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
The XOR operation gives the update rates we are aiming for.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
| 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
const uint32_t kuMaxKey = ((uint32_t)1 << uNumOctaves) - 1);
This line is setting the lower kuNumOctaves bits
of kuMaxKey. The code is
from Stack
Overflow. kuMaxKey is used to loop the updates of the noise
generators when the lowest octave band is updated.
There wrapping of uKey resets the key to 1, and updates the
previous key to 0. This is to ensure that the update of the noise generators
at each octave is correct. If key was reset to 0, some of the higher octaves
would miss an update due to the 0 XOR 8.