Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

//

Synthesize White Noise

25.11.2022 | 8 Minuten Lesezeit

Overview

We walk through the process of digitally synthesizing a White Noise sound. To begin we provide the audible outcome, general context and motivation. Then technical information and coding samples follow.

Contents

What Is White Noise?

Noise is a special kind of signal. As an audio signal it is neither just a high nor just a low sound but a mixture of a lot of frequencies. White noise contains all audible frequencies from lowest to highest. It is named in analogy with white light which is a mixture of all visible wavelengths of light. The picture of a prism breaking up white light into its colour components illustrates this well.

How Does It Sound Like?

What does white noise sound Like? You already know sounds similar to white noise. It's reminiscent of rain and ocean waves. Sounds of wind and rustling of leaves are also noisy in our sense and can be very enjoyable nonetheless. Here are three examples of noise-like sound.

Rain

Flowing Air

Flowing Water

Noise and Pure Waves

White Noise contains all frequencies at once. It corresponds to the beam of white light that enters the prism in our introductory image. The opposite extreme is a single selected frequency only. It corresponds to one of the coloured light components that leave the prism separately. These two sounds are nothing alike. Have a listen to a single frequency wave in contrast to white noise.

Sine Wave

White Noise

Their waveforms look very different too. To generate white noise, we will use a random stream of bits. For the sound of a pure frequency a sine wave would have to be encoded instead.

Sine Waveform

Random Waveform

Applications

White noise is used as a test signal by audio engineers. The signal to noise ratio is a key measurement in signal processing. Audiophiles care a lot about the signal to noise ratio. Some people swear by noise sounds as a sleeping aid or for relaxation. Others feel it helps with concentrated work and improves focus. It is even used for masking tinnitus. White noise is a fundamental building block of both analogue and digital synthesizers for musical and more general sound design. It can be shaped into snare and hi-hat drum sounds for example.

Randomness and Independence

We generate the white noise sound digitally from scratch. To do so we build a signal in which each data point bears no connection to its previous data points. Each point is independent from rest of data points. This contrasts the case of a pure sine wave in which you can predict the continuation of the periodic wave once you know its general form. A random signal with uniformly distributed amplitude features the characteristics we need.

Code Samples

The code samples are given in C++ as it is widely used in audio applications and digital signal processing. If you would like to see the code in a different language, we encourage you to do the transfer. The general ideas and concepts remain the same.

Audio Stream

The C++ standard library provides us with a number generator that produces a uniformly distributed sequence of values. The default_random_engine is to be found in <random>.

1random_chars_engine engine = 
2    independent_bits_engine<default_random_engine, CHAR_BIT, unsigned char>;

We wrap the default_random_engine in a independent_bits_engine to take control of the type of values that are generated. We would like to obtain byte sized values. A standard engine produces unsigned integer types so choose unsigned char. CHAR_BIT is the number of bits that is used to represent values of this type. It specifies that all bits of the generated values shall become random. If we had chosen a smaller number, the remaining bits would always be zero.

1vector<unsigned char> audioStream(bytesPerSecond*lengthOfAudioInSeconds);

The vector data is our container for the values we generate with the random_chars_engine. Let us assume that bytesPerSecond is the number of bytes that are used to encode one second of audible audio and that the size of unsigned char is one byte. To contain the data for an audio clip of a duration lengthOfAudioInSeconds we choose the length of the vector data to be bytesPerSecond*lengthOfAudioInSeconds.

1generate(begin(audioStream), end(audioStream), ref(engine));

We have prepared a random_chars_engine as a generator of random values and a suitable vector as a container to receive the generated values. The helper function generate now fills the container data from beginning to end with values from the random_chars_engine called engine.

1random_bytes_engine engine = 
2independent_bits_engine<default_random_engine, CHAR_BIT, unsigned char>;
3vector<unsigned char> audioStream(bytesPerSecond*lengthOfAudioInSeconds);
4generate(begin(audioStream), end(audioStream), ref(engine));

This results in a vector filled with random values of type 'unsigned char' such that their combined binary representation encodes an audio stream of our desired duration.

We will write this binary representation to a file eventually. This bitstream is to be played back by a media application as the sound it was meant to represent that is as white noise in our case.

WAV Header

Common media players as are not meant to play back a raw bitstream of encoded audio data. To handle it additional information about how to interpret the bitstream are in order. We package the necessary additional metadata together with the raw random bitstream as a WAV (Waveform Audio) file. As such the WAV file is suitable for play back via common media applications. Here we need to take care of the details to satisfy the WAV file format specification. Otherwise upon violation media players may reject the WAV file as invalid and rightfully so. The technical details follow.

We represent the necessary metadata as a struct in C++ code. It contains the information that make up the metadata.

1typedef struct WAV_HEADER {
2    uint8_t RIFF[4] = {'R', 'I', 'F', 'F'};
3    uint32_t ChunkSize;
4    uint8_t WAVE[4] = {'W', 'A', 'V', 'E'};
5
6    uint8_t fmt[4] = {'f', 'm', 't', ' '};
7    uint32_t Subchunk1Size = 16;
8    uint16_t AudioFormat = 1;
9    uint16_t NumOfChan = 1;
10    uint32_t SamplesPerSec = 44100;
11    uint32_t bytesPerSec = 44100 * 3;
12    uint16_t blockAlign = 2;
13    uint16_t bitsPerSample = 24;
14
15    uint8_t Subchunk2ID[4] = {'d', 'a', 't', 'a'};
16    uint32_t Subchunk2Size;
17} wav_header;

The WAV file format tells us what information is necessary as metadata and how many bytes each piece of information should occupy.

The WAV file header is structured into a main chunk RIFF that contains two subchunks 'fmt' and 'data'. Each chunk or subchunk has a name and a size. The names of C++'s fixed integer types uint8_t, uint16_t and uint32_t indicate their respective sizes in bits. These types are used to represent integers as well as characters. We use these types to guarantee that their assigned values occupy the correct number of bytes for the WAV file header to be valid.

A WAV file is a special kind of RIFF file. To be a valid RIFF file it has to start with the four bytes that make up the chunk name RIFF. Next the 'ChunkSize' of the RIFF-chunk is given. It has to contain the size of the remaining header plus the size of the audio bit stream that follows the header in the WAV file. We will fill in this value once we have these sizes at hand. Then the specific kind of the RIFF file is given which is 'WAVE' in case of a WAV file.

Next The first of two subchunks begins. The name of the fmt subchunk contains a space since it needs to occupy four bytes to be in accordance with the specification. The size of the fmt subchunk is given as 'Subchunk1Size'. The following six entries are concerned with the details of digital representation of audio data. There are different ways to digitally represent audio data, but we choose the canonical uncompressed format LPCM (Linear Pulse Code Modulation) which is what the value 1 of AudioFormat stands for. By setting 'NumOfChannels' to one we state that our bit stream will represent a mono audio signal. 'SamplesPerSecond' is the number of data points within a duration of one second that are described by our bit stream and 'bitsPerSample' is the number of bits used for the description of one such data point. The value of 'bytesPerSec' result from the values of 'NumOfChan', 'bitsPerSample' and 'SamplesPerSec'. 'blockAlign' is the number of bytes in a sample across all channels. Its value is derived from 'NumOfChan' and 'bitsPerSample'.

The second and last subchunk 'data' again has a name and a size. Its size is the size of the audio bit stream that follows the header in the WAV file.

To complete the WAV file header, we now set the remaining values that depend on the size of the audio stream and the size of the header itself.

1wav_header meta_data;
2meta_data.ChunkSize = audioStreamSize + sizeof(wav_header) - 8;
3meta_data.Subchunk2Size = audioStreamSize;

WAV File

We have prepared both the audio bitstream and the WAV file header. The two parts combined comprise the WAV file.

The header and audio stream can be written to the WAV file as follows.

1out.write(reinterpret_cast<char *>(&meta_data), sizeof(meta_data));
2out.write(reinterpret_cast<char *>(&audioStream[0]), audioStream.size());

Modifications

Modifications of bit depths and sample rate lead variations in sound. Feel free to go extremely low for a pronounced effect. A stereo white noise signal will also sound different. For this to work make sure to generate different white noise signals for the right and left channels as our random engine as it stands will produce exactly the same data again when used a second time which is not what you want for the stereo case.

Summary

We got to know the idea and concept of noise in general and more specifically white noise. Then we saw how its sound information can be represented as random data. We added metadata and packaged it together with the audio bitstream as a WAV file for play back.

Beitrag teilen

Gefällt mir

2

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.