Search code examples
c++randomrandom-walkxaudio2

Clicking sounds in brown/Brownian/random walk noise


I am trying to make brown noise in C++, and to play the sound of it. You can hear the brown noise, but I constantly hear clicking in the background and I don't know why.

Here is my code:

#include <xaudio2.h>
#include <iostream>
#include <random>

using namespace std;

#define PI2 6.28318530717958647692f
#define l 2205 //0.05 seconds

bool init();
bool loop();

random_device rd;
mt19937 gen(rd());
uniform_real_distribution<> dis(-.01, .01);

IXAudio2MasteringVoice* pMasterVoice;
IXAudio2* pXAudio2;
IXAudio2SourceVoice* pSourceVoice;
XAUDIO2_BUFFER buffer;
WAVEFORMATEX wfx;
XAUDIO2_VOICE_STATE state;
BYTE pDataBuffer[2*l];
BYTE bytw[2];

int pow16[2];
float w[l];
int frame, p;
float tt, ampl;

bool loop() {
    w[0] = w[l - 1] + dis(gen)*ampl;
    for (int t = 1; t < l; t++) {
        tt = (float)(t + frame*l); //total time

        w[t] = w[t - 1] + dis(gen)*ampl;
        if (w[t] > ampl) {
            cout << "upper edge ";
            w[t] = ampl - fmod(w[t], ampl);
        }
        if (w[t] < -ampl) {
            cout << "lower edge ";
            w[t] = -fmod(w[t], ampl) - ampl;
        }
        //w[t] = sin(PI2*tt/p)*ampl;
        //w[t] = (fmod(tt/p, 1) < .5 ? ampl : -ampl)*(.5f - 2.f*fmod(tt/p, .5f));

        int intw = (int)w[t];
        if (intw < 0) {
            intw += 65535;
        }

        bytw[0] = 0; bytw[1] = 0;
        for (int k = 1; k >= 0; k--) {
            //turn integer into a little endian byte array
            bytw[k] += (BYTE)(16*(intw/pow16[k]));
            intw -= bytw[k]*(pow16[k]/16);
            bytw[k] += (BYTE)(intw/(pow16[k]/16));
            intw -= (intw/(pow16[k]/16))*pow16[k]/16;
        }

        pDataBuffer[2*t] = bytw[0];
        pDataBuffer[2*t + 1] = bytw[1];
    }

    cout << endl << endl;

    if (frame > 1) {
        //wait until the current one is done playing
        while (pSourceVoice->GetState(&state), state.BuffersQueued > 1) {}
    }

    buffer.AudioBytes = 2*l; //number of bytes per buffer
    buffer.pAudioData = pDataBuffer;
    buffer.Flags = XAUDIO2_END_OF_STREAM;

    pSourceVoice->SubmitSourceBuffer(&buffer);

    if (frame == 1) {
        pSourceVoice->Start(0, 0);
    }
    frame++;

    return true;
}

bool init() {
    CoInitializeEx(nullptr, COINIT_MULTITHREADED);

    pXAudio2 = nullptr;
    XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);

    pMasterVoice = nullptr;
    pXAudio2->CreateMasteringVoice(&pMasterVoice);

    wfx = {0};
    wfx.wFormatTag = WAVE_FORMAT_PCM;
    wfx.nChannels = (WORD)1; //mono
    wfx.nSamplesPerSec = (DWORD)44100; //samplerate
    wfx.wBitsPerSample = (WORD)16; //16 bit (signed)
    wfx.nBlockAlign = (WORD)2; //2 bytes per sample
    wfx.nAvgBytesPerSec = (DWORD)88200; //samplerate*blockalign
    wfx.cbSize = (WORD)0;

    pSourceVoice = nullptr;
    pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx);

    tt = 0, p = 1000, ampl = 10000;

    pow16[0] = 16;
    pow16[1] = 4096;

    frame = 0;

    return true;
}

int main() {
    if (!init()) return 1;

    cout << "start";

    while (loop()) {}

    return 0;
}

The line before the for-loop in loop() is to make sure that the first element nicely attaches itself to the last element of the previous iteration.

To make sure that w doesn't go over ampl or under -ampl, I have added a couple lines that make them bounce back, and I make it output "upper edge" or "lower edge" respectively so that you know when this is happening. As you notice, the clicking also happens when the w is not near the edges.

As a test to make sure it isn't because of XAudio2 being implemented wrongly, you can comment the first line in loop() that defines the first element of w; make the for-loop (in the next line) start from 0; comment the lines that create the brown noise; and uncomment one of the two lines after that: the first line to hear a sine wave sound, the second line to hear a square wave sound (both with a frequency of about 44100/1000 = 44.1 Hz, which you can change around by changing how p is initialized in init()). You will (hopefully) hear a clean sine/square wave sound.

So what is going wrong?


Solution

  • You have two issues in your code:

    1. You only have a single buffer therefore its near impossible to submit a new buffer for playing quickly enough after the buffer stops playing for there to not be a gap between buffers. You are also modifying the buffer data whilst it is being played which will corrupt the output. You should use multiple buffers. With enough buffers this would also allow you to add some short sleeps to your while loop which is checking BuffersQueued to reduce the CPU usage.
    2. You never set pDataBuffer[0] or pDataBuffer[1] so they will always be 0.

    This code works:

    #include <xaudio2.h>
    #include <iostream>
    #include <random>
    #include <array>
    #include <thread>
    
    using namespace std;
    
    #define PI2 6.28318530717958647692f
    #define l 2205 //0.05 seconds
    
    bool init();
    bool loop();
    
    random_device rd;
    mt19937 gen(rd());
    uniform_real_distribution<> dis(-.01, .01);
    
    IXAudio2MasteringVoice* pMasterVoice;
    IXAudio2* pXAudio2;
    IXAudio2SourceVoice* pSourceVoice;
    const size_t bufferCount = 64;
    std::array<XAUDIO2_BUFFER, bufferCount> buffers;
    WAVEFORMATEX wfx;
    XAUDIO2_VOICE_STATE state;
    std::array<std::array<BYTE,2 * l>, bufferCount> pDataBuffers;
    BYTE bytw[2];
    
    int pow16[2];
    float w[l];
    int frame, p;
    float tt, ampl;
    
    bool loop() {
      float prevW = w[l - 1];
      auto& pDataBuffer = pDataBuffers[frame & (bufferCount-1)];
      auto& buffer = buffers[frame & (bufferCount - 1)];
      for (int t = 0; t < l; t++) {
        tt = (float)(t + frame * l); //total time
    
        w[t] = prevW + dis(gen) * ampl;
        if (w[t] > ampl) {
          //cout << "upper edge ";
          w[t] = ampl - fmod(w[t], ampl);
        }
        if (w[t] < -ampl) {
          //cout << "lower edge ";
          w[t] = -fmod(w[t], ampl) - ampl;
        }
        //w[t] = sin(PI2*tt/p)*ampl;
        //w[t] = (fmod(tt/p, 1) < .5 ? ampl : -ampl)*(.5f - 2.f*fmod(tt/p, .5f));
        prevW = w[t];
    
        int intw = (int)w[t];
        if (intw < 0) {
          intw += 65535;
        }
    
        bytw[0] = 0; bytw[1] = 0;
        for (int k = 1; k >= 0; k--) {
          //turn integer into a little endian byte array
          bytw[k] += (BYTE)(16 * (intw / pow16[k]));
          intw -= bytw[k] * (pow16[k] / 16);
          bytw[k] += (BYTE)(intw / (pow16[k] / 16));
          intw -= (intw / (pow16[k] / 16)) * pow16[k] / 16;
        }
    
        pDataBuffer[2 * t] = bytw[0];
        pDataBuffer[2 * t + 1] = bytw[1];
      }
    
      //cout << endl << endl;
    
      if (frame > 1) {
        //wait until the current one is done playing
        while (pSourceVoice->GetState(&state), state.BuffersQueued > 1) { std::this_thread::sleep_for(std::chrono::milliseconds(1); }
      }
    
      buffer.AudioBytes = 2 * l; //number of bytes per buffer
      buffer.pAudioData = pDataBuffer.data();
      buffer.Flags = 0;
    
      pSourceVoice->SubmitSourceBuffer(&buffer);
    
      if (frame == 1) {
        pSourceVoice->Start(0, 0);
      }
      frame++;
    
      return true;
    }
    
    bool init() {
      CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    
      pXAudio2 = nullptr;
      XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);
    
      pMasterVoice = nullptr;
      pXAudio2->CreateMasteringVoice(&pMasterVoice);
    
      wfx = { 0 };
      wfx.wFormatTag = WAVE_FORMAT_PCM;
      wfx.nChannels = (WORD)1; //mono
      wfx.nSamplesPerSec = (DWORD)44100; //samplerate
      wfx.wBitsPerSample = (WORD)16; //16 bit (signed)
      wfx.nBlockAlign = (WORD)2; //2 bytes per sample
      wfx.nAvgBytesPerSec = (DWORD)88200; //samplerate*blockalign
      wfx.cbSize = (WORD)0;
    
      pSourceVoice = nullptr;
      pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx);
    
      tt = 0, p = 1000, ampl = 10000;
    
      pow16[0] = 16;
      pow16[1] = 4096;
    
      frame = 0;
    
      return true;
    }
    
    int main() {
      if (!init()) return 1;
    
      while (loop()) {}
    
      return 0;
    }
    

    I haven't tried to follow all of your logic but it seems over complicated and could definitely be simplified.

    The massive use of global variables is also not a great way to write a program. You should move variables inside the functions where possible, otherwise either pass them to the function as arguments or use a class to hold the state.