32

((Answer selected - see Edit 5 below.))

I need to write a simple pink-noise generator in C#. The problem is, I've never done any audio work before, so I don't know how to interact with the sound card, etc. I do know that I want to stay away from using DirectX, mostly because I don't want to download a massive SDK just for this tiny project.

So I have two problems:

  1. How do I generate Pink Noise?
  2. How do I stream it to the sound card?

Edit: I really want to make a pink noise generator... I'm aware there are other ways to solve the root problem. =)

Edit 2: Our firewall blocks streaming audio and video - otherwise I'd just go to www.simplynoise.com as suggested in the comments. :(

Edit 3: I've got the generation of white-noise down, as well as sending output to the sound card - now all I need to know is how to turn the white-noise into pink noise. Oh - and I don't want to loop a wav file because every application I've tried to use for looping ends up with a tiny little break in between loops, which is jarring enough to have prompted me in this direction in the first place...

Edit 4: ... I'm surprised so many people have jumped in to very explicitly not answer a question. I probably would have gotten a better response if I lied about why I need pink noise... This question is more about how to generate and stream data to the sound card than it is about what sort of headphones I should be using. To that end I've edited out the background details - you can read about it in the edits...

Edit 5: I've selected Paul's answer below because the link he provided gave me the formula to convert white noise (which is easily generated via the random number generator) into pink noise. In addition to this, I used Ianier Munoz's CodeProject entry "Programming Audio Effects in C#" to learn how to generate, modify, and output sound data to the sound card. Thank you guys for your help. =)

Erik Forbes
  • 35,357
  • 27
  • 98
  • 122
  • Otherwise you can go to SimplyNoise.com and concentrate on your work instead :) – Jonas Engström Mar 05 '09 at 22:20
  • I would, except our firewall blocks streaming audio and video. – Erik Forbes Mar 05 '09 at 22:21
  • Heard of noise cancelling head phones? – Andrew Harry Mar 05 '09 at 23:05
  • in the loop, you blend the two wave files, this is easily done in audacity. anyway... you sound a bit crazy. Don't shoot anyone – Andrew Harry Mar 05 '09 at 23:07
  • @NguyenMinhBinh I wish I could, but I don't have the project files anymore as it's been 11 years since I asked this question. Some of the answers to this question -- https://scicomp.stackexchange.com/questions/18987/algorithm-for-high-quality-1-f-noise -- look promising however. – Erik Forbes Dec 17 '20 at 20:35

5 Answers5

14

Maybe you can convert the C/C++ code here to C#:

http://www.firstpr.com.au/dsp/pink-noise/

The easiest way to get sound to the sound card is to generate a wav (spit out some hardcoded headers and then sample data). Then you can play the .wav file.

Paul
  • 1,483
  • 8
  • 8
  • 1
    I'm going to accept this as my answer, because it contains the formulas needed to convert white noise into pink noise - thanks for the reference. =) – Erik Forbes Mar 05 '09 at 23:57
7

Pink noise is just white noise put through a -3dB/octave LPF. You can generate white noise using rand() (or any function that generates uniformly random numbers).

Streaming stuff to the soundcard is reasonably trivial, as long as you have Google handy. If you choose to avoid DirectX, consider using PortAudio or ASIO for interfacing with the soundcard... although I think you're gonna have to use C++ or C.

Other than that, why waste CPU time generating it? Loop a damn WAV file!

Justas
  • 5,718
  • 2
  • 34
  • 36
switchmode
  • 173
  • 4
  • _Streaming stuff to the soundcard is reasonably trivial._ it really isn’t if you don’t have the experience, which I’d assume the OP does not – fdcpp Oct 14 '21 at 09:42
6

Here's a very simple way to create pink noise, which just sums lots of waves spaced logarithmically apart, together! It may be too slow for your purposes if you want the sound created in realtime, but further optimization is surely possible (e.g: a faster cosine function).

The functions outputs a double array with values from -1 to 1. This represents the lowest and highest points in the waveform respectively.

The quality parameter represents the number of waves produced to make the sound. I find 5000 waves (about 40 intervals per semitone) is just about the threshold where I can't detect any noticeable improvement with higher values, but to be on the safe side, you could (optionally) increase this to about 10,000 waves or higher. Also, according to Wikipedia, 20 hertz is around the lower limit of human perception in terms of what we can hear, but you can change this too if you want.

Note the sound gets quieter with a higher quality value due to technical reasons, so you may (optionally) want to adjust the volume via the volumeAdjust parameter.

public double[] createPinkNoise(double seconds, int quality=5000, double lowestFrequency=20, double highestFrequency = 20000, double volumeAdjust=1.0)
{
    long samples = (long)(44100 * seconds);
    double[] d = new double[samples];
    double[] offsets = new double[samples];
    double lowestWavelength = highestFrequency / lowestFrequency;
    Random r = new Random();
    for (int j = 0; j < quality; j++)
    {
        double wavelength = Math.Pow(lowestWavelength, (j * 1.0) / quality)  * 44100 / highestFrequency;
        double offset = r.NextDouble() * Math.PI*2;     // Important offset is needed, as otherwise all the waves will be almost in phase, and this will ruin the effect!
        for (long i = 0; i < samples; i++)
        {
            d[i] += Math.Cos(i * Math.PI * 2 / wavelength + offset) / quality * volumeAdjust;
        }
    }
    return d;
}
Dan W
  • 3,520
  • 7
  • 42
  • 69
  • Much of this I don't understand, how it works. Most of all, why is the offsets array created? But it does make VERY good sounds!!! – Confused Sep 27 '21 at 15:22
  • Thanks for the code. Calling it `highestFrequency`/ giving it a default value of 44100 can be misleading since 44100 is also the sampling frequency you use. Young children normally just hear up to 20 kHz (older people seldom above 10 kHz) so going up to 44100 seems a bit overkill. – Erik Thysell Oct 13 '21 at 11:19
  • 1
    @ErikThysell: I think that limit applies to single pure frequencies above 20k and not necessarily to mixed multiple simultaneous frequencies above 20k where phantom tones such as difference tones and virtual tones can come into play. If you have a study showing otherwise, I'd like to hear. Regardless, I've dropped it to 20kHz, because there was distortion effects apparent at or near 44.1 kHz anyway. Unfortunately, testing for this is difficult since most software, drivers and maybe hardware throws away sound info above 20kHz. FWIW, in a lab setting, some people can apparently hear up to 28k. – Dan W Oct 17 '21 at 12:54
2

If you're on Linux, you can use SOX (you may have it already, try the play command).

play -t sl - synth 3 pinknoise band -n 1200 200 tremolo .1 40 < /dev/zero

Eirik Birkeland
  • 588
  • 5
  • 11
2

Here's is an example of what the playback thread looks like. I'm using DirectSound to create a SecondaryBuffer where the samples are written. As you can see it's pretty straightforward:

    /// <summary>
    /// Thread in charge of feeding the playback buffer.
    /// </summary>
    private void playbackThreadFn()
    {
        // Begin playing the sound buffer.
        m_playbackBuffer.Play( 0, BufferPlayFlags.Looping );

        // Change playing state.
        IsPlaying = true;

        // Playback loop.
        while( IsPlaying )
        {
            // Suspend thread until the playback cursor steps into a trap...
            m_trapEvent.WaitOne();

            // ...read audio from the input stream... (In this case from your pink noise buffer)
            Input.Collect( m_target, m_target.Length );

            // ...calculate the next writing position...
            var writePosition = m_traps[ ((1 & m_pullCounter++) != 0) ? 0 : 1 ].Offset;

            // ...and copy audio to the device buffer.
            m_playbackBuffer.Write( writePosition, m_deviceBuffer, LockFlag.None );
        }

        // Stop playback.
        m_playbackBuffer.Stop();
    }

If you need more details on how it works I'll be glad to help.

Trap
  • 12,050
  • 15
  • 55
  • 67