brown/Brownian/random 步行噪音中的点击声

Clicking sounds in brown/Brownian/random walk noise

我正在尝试用 C++ 制作棕色噪音,并播放它的声音。你可以听到棕色噪音,但我经常听到背景中的咔嗒声,我不知道为什么。

这是我的代码:

#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;
}

loop() 中 for 循环之前的一行是确保第一个元素很好地附加到上一次迭代的最后一个元素。

为了确保 w 不会超过 ampl 或低于 -ampl,我添加了几行让它们反弹,我让它输出“上边缘”或“下边缘”,以便您知道何时发生这种情况。如您所见,当 w 不靠近边缘时也会发生点击。

作为确保不是因为 XAudio2 被错误实现的测试,您可以注释 loop() 中定义 w 第一个元素的第一行;使 for 循环(在下一行)从 0 开始;注释产生棕色噪声的线条;然后取消注释两行中的一行:第一行听到正弦波声音,第二行听到方波声音(两者的频率都约为 44100/1000 = 44.1 Hz,您可以通过更改来改变它p 如何在 init() 中初始化)。您会(希望)听到干净的 sine/square 波浪声。

所以怎么了?

您的代码中有两个问题:

  1. 您只有一个缓冲区,因此在缓冲区停止播放后几乎不可能提交新的缓冲区以足够快地播放,因为缓冲区之间没有间隙。您还在播放缓冲区数据时修改缓冲区数据,这会破坏输出。您应该使用多个缓冲区。如果有足够的缓冲区,这还可以让您向正在检查 BuffersQueued 以减少 CPU 使用率的 while 循环添加一些短暂的睡眠。
  2. 您从未设置 pDataBuffer[0]pDataBuffer[1],因此它们将始终是 0

此代码有效:

#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;
}

我没有尝试遵循您的所有逻辑,但它似乎过于复杂并且绝对可以简化。

大量使用全局变量也不是编写程序的好方法。您应该尽可能在函数内部移动变量,否则要么将它们作为参数传递给函数,要么使用 class 来保存状态。