pink/brown noise generator

Hello -

I wrote a function to generate a pink (P = 1 / f) or brown (P = 1/ f ^ 0.5) noise wave (see below). The function is slow because I am using a loop to create many individual sin waves (columns in a single wave) that later get summed together into a single wave (using a matrixOP). I find myself stuck using the loop because I can't think of a different way to give each component sin wave it's own (random) phase and power.

I am seeking advice on how I could possibly use Matrix op or some other way to eliminate the loop from the function.

Note: If you are testing this function, you will find that the BuildWave wave gets big! and can lead to insufficient memory errors. One way to avoid this is to keep the Duration variable small (ex. 200 ms). I am planning to tackle this problem later by having the build wave sample rate to be twice the frequency of the LowPassCutOff. For now though, I am creating the wave a a sample interval that my hardware "likes".

Threadsafe Function WB_PinkAndBrownNoise(Amplitude, Duration, LowPassCutOff, HighPassCutOff, FrequencyIncrement, PinkOrBrown)
        variable Amplitude, Duration, LowPassCutOff, HighPassCutOff, frequencyIncrement, PinkOrBrown
        variable phase = (abs(enoise(2)) * Pi)
        variable NumberOfBuildWaves = floor((LowPassCutOff - HighPassCutOff) / FrequencyIncrement)
        make /free /n = (Duration / 0.005, NumberOfBuildWaves) BuildWave
        SetScale /P x 0,0.005,"ms", BuildWave
        variable Frequency = HighPassCutOff
        variable i = 0
        variable localAmplitude
        do
            phase = ((abs(enoise(2))) * Pi) // random phase generator
            if(PinkOrBrown == 0)
                localAmplitude = 1 / Frequency
            else
                localAmplitude = 1 / (Frequency ^ .5)
            endif
           
            MultiThread BuildWave[][i] = localAmplitude * sin(2 * Pi * (Frequency*1000) * (5 / 1000000000) * p + phase) //
            Frequency += FrequencyIncrement
            i += 1
        while (i < NumberOfBuildWaves)
       
        MatrixOp /o /NTHR = 0   SegmentWave = sumRows(BuildWave)
       
        SetScale /P x 0, 0.005,"ms", SegmentWave
        SegmentWave /= NumberOfBuildWaves
       
        Wavestats /q SegmentWave
        variable scalefactor = Amplitude/(V_max - V_min)
        SegmentWave *= ScaleFactor
End
In all cases where speed is important you want to minimize the number of operations in the loop. In this case I'd start by factoring out

2 * Pi * (1000) * (5 / 1000000000)

as this does not change during the implied loop.

The second point I'd make is that there is no reason for you to generate the phase and test for pink/brown inside the loop. Do it once outside and store the phase and frequency in a single waves (together with the argument for the sine) so that you can replace the multithread statement with MatrixOP. Note that enoise() is thread-safe so you should be able to fill that wave with a multithread instruction.

HTH,

A.G.
WaveMetrics, Inc.
AG,

Thank you for your help! Adam also sent me a modified function code. Adam eliminated the loop entirely and the function got significantly slower!? I've found that moving the phase or frequency calculation out of the loop also slows down the function. Simplifying the "(2 * Pi * (Frequency*1000) * (5 / 1000000000) * p + phase)" line did seem to increase the speed significantly with each variable I removed, but a second round of testing didn't realize as large improvements. I was also surprised that the "IF" statement doesn't slow down the function.

Here is what I have settled on for now:
Threadsafe Function WB_PinkAndBrownNoise3(Amplitude, Duration, LowPassCutOff, HighPassCutOff, FrequencyIncrement, PinkOrBrown)// Pink = 0, Brown = 1
        variable Amplitude, Duration, LowPassCutOff, HighPassCutOff, frequencyIncrement, PinkOrBrown
        Variable startTime = StopMsTimer(-2)
        variable phase
        variable NumberOfBuildWaves = floor((LowPassCutOff - HighPassCutOff) / FrequencyIncrement)
        make /free /n = (Duration / 0.005, NumberOfBuildWaves) BuildWave
        SetScale /P x 0,0.005,"ms", BuildWave
        variable Frequency = HighPassCutOff
        variable i = 0
        variable localAmplitude
        //make /free /n = (NumberOfBuildWaves) PhaseWave
        //Multithread PhaseWave[] = ((abs(enoise(2))) * Pi)
        do
            phase = ((abs(enoise(2))) * Pi) // random phase generator
            if(PinkOrBrown == 0)
                localAmplitude = 1 / Frequency
            else
                localAmplitude = 1 / (Frequency ^ .5)
            endif
           
            MultiThread BuildWave[][i] = localAmplitude * sin( Pi * Frequency * 1e-05 * p + phase) //  factoring out Pi * 1e-05 actually makes it a tiny bit slower
            //MultiThread BuildWave[][i] = localAmplitude * sin(2 * Pi * (Frequency*1000) * (5 / 1000000000) * p + phase) // Multithread of sin funciton is the BOMB!!
            Frequency += FrequencyIncrement
            i += 1
        while (i < NumberOfBuildWaves)
       
        MatrixOp /o /NTHR = 0   SegmentWave = sumRows(BuildWave)
       
        SetScale /P x 0, 0.005,"ms", SegmentWave
        SegmentWave /= NumberOfBuildWaves
       
        Wavestats /q SegmentWave
        variable scalefactor = Amplitude / (V_max - V_min)
        SegmentWave *= ScaleFactor
        printf "WB_PinkAndBrownNoise3() took %g ms.\r", (StopMsTimer(-2) - startTime)/ 1000

End
Is there any reason you are not doing this sum as an (inverse) FFT? I'm not sure the commented-out version of the switch statement does exactly what you want for the frequency cutoffs and increment, but presuming those were only there to limit NumberofBuildWaves and noting that having zeros in the FFT input doesn't speed it up, you can just get rid of them. The code as is is equivalent to using zero LowPassCutOff and FrequencyIncrement, and infinite HighPassCutoff. 10 minutes worth of data sampled at 200 kHz is about as much as fits in memory on my laptop, and takes about 40 seconds to generate. 200 ms worth is generated in less than 10 ms. How fast does it need to be?

I took the liberty of using SI units, which allows Igor to be smarter about what units are displayed in axes.

 Function WB_PinkAndBrownNoise4(Amplitude, Duration, LowPassCutOff, HighPassCutOff, FrequencyIncrement, PinkOrBrown)// Pink = 0, Brown = 1
    variable Amplitude, Duration, LowPassCutOff, HighPassCutOff, frequencyIncrement, PinkOrBrown
   
    Variable startTime = StopMsTimer(-2)

    Variable samplerate = 1e3/0.005 // in Hz
    Variable samples = Duration * samplerate * 1e-3 // if duration is in ms
    samples = 2 * ceil(samples/2) // even number of points for FFT
    Make/O/C/N=(samples/2+1) magphase
    SetScale/P x 0,samplerate/samples,"Hz" magphase

    switch(PinkOrBrown)
        case 0:     // pink noise
            magphase[1,] = cmplx(1/x, enoise(Pi))
            break
        case 1:     // brown noise
            magphase[1,] = cmplx(1/sqrt(x), enoise(Pi))
            break
        default:    // brown noise
            magphase[1,] = cmplx(1/sqrt(x), enoise(Pi))
    endswitch
//  Variable lowsample = min(max(1,pnt2x(magphase, LowPassCutoff*1e3)),samples/2) // if LowPassCutOff is in kHz
//  Variable highsample = min(max(1,pnt2x(magphase, HighPassCutoff*1e3)),samples/2) // if HighPassCutOff is in kHz
//  Variable skipsample = min(1,pnt2x(magphase, frequencyIncrement*1e3)) // if frequencyIncrement is in kHz
// 
//  switch(PinkOrBrown)
//      case 0:     // pink noise
//          magphase[lowsample,highsample;skipsample] = cmplx(1/x, enoise(Pi))
//          break
//      case 1:     // brown noise
//          magphase[lowsample,highsample;skipsample] = cmplx(1/sqrt(x), enoise(Pi))
//          break
//      default:    // brown noise
//          magphase[lowsample,highsample;skipsample] = cmplx(1/sqrt(x), enoise(Pi))
//  endswitch
    MatrixOp/O SegmentWave = IFFT(p2Rect(magphase),3)
    MatrixOp/FREE ScaleFactor = Amplitude/(maxVal(SegmentWave)-minVal(SegmentWave)))
    MatrixOp/O SegmentWave = SegmentWave * ScaleFactor[0] // ScaleFactor is a 1x1 matrix

    SetScale /P x 0, 1/samplerate,"s", SegmentWave

    printf "WB_PinkAndBrownNoise4() took %g ms.\r", (StopMsTimer(-2) - startTime)/ 1000
End