0
$\begingroup$

I encountered a problem when I tried to generate by a program an sinus audio signal. My parameters are

Sample Rate: 44100 Hz Data Bits: 16 bits Channels: 2

I need to create a WAV file that is generated on the fly in and play in real time directly to a audio out port. There are two more requirements. I need to be able to change Frequency of the sinus signal with the delay of no more than 50 ms. The last requirements are to make any periods of sound and silence with the sinus. For example 100 ms sound and 50 ms of silence or even continuous signal of sound.

I made everything and works but not for all frequencies.

I mean my signal is flawless. It's wave form displayed in audio editing software is perfect. Played through Media Player is perfect. But when I play it on the fly it is producing strange noise, strange "bump" noise at the end of every wave chunk.

The audio signal to be able to change it on the fly or with a maximum delay of 50ms I am creating wave chunks of 50 ms.

When I play it with 200Hz (sinus) and 50 ms buffer it sounds good. But when I change a freq to 201Hz (sinus) and 50 ms buffer it is producing a constant train of "BANG" sounds at the and of every chunk. "BUMP", "BUMP", "BUMP", "BUMP", ...

I think that I made a mistake in discrete domain. Some rounding of values at the end of every chunk. In addition I noticed that the problem is audible on the frequencies that are between every freq. that is not a multiple of number 20.

For example: 200Hz (good), 201, 202, ... 219 (bad), 220 Hz again good and so on.

I really do not understand what's wrong.

Mathematically the only thing about what I can think is some relation between the buffer size of 50ms, Sample Rate of the audio signal and the frequency of sinus.

the numbers:

44100 Hz => 44100 Samples/sec x 50ms (buffer) = 2205 Samples / buffer 200 Hz (sinus) => T = 1/f = 5ms period

50ms (buffer) / 5 ms (sinus period) = 10 sinus waves per buffer

if the freq. is increased: 201 Hz (sinus) => T = 1/f = 4,9751 ms period 50 ms (buffer) / 4,9751 ms = 10,05 sinus waves per buffer

The point is in the second example the last sinus does not fit entirely into a buffer.

I can say that I am filling the next buffer at the exact following point but something still happens. I think that somewhere around this point is a problem.

I need at least a mathematical solution in discrete domain. The best will be a computer code or even pseudo code is fine.

Can somebody be so kind to help me? It is very important to me. Thanks

UPDATE

I tried to use the buffer chunk size of 50 ms and the Sinus frequency of 201 Hz and then used Audacity program to record the audio in real time. Very strange things are happening. at 100 ms and 200 ms are spikes at 300 is not, at 400, 450, 500, 550, 600 ms are other spikes. 650 is good as it must be, 700 is too, 750 again not, 800 spike ... then at 1000 ms is a different spike. I rely do not understand why is this happening. Maybe some calculation... Some rounding problems.

I will put only a part of the code used. Complete code is very big. It consists of producer thread and consumer thread + the Wave out player internal thread. In between is a queue to not used to have constant flow between produce and consumer (player). So at least this is satisfied.

These are the critical parts of the code.

format.dwSamplesPerSec = 44100
format.wChannels = 2
format.wBitsPerSample = 16

int minAudioChunk = 50; // in milliseconds

data.dwChunkSize = (uint)(data.shortArray.Length * (format.wBitsPerSample / 8));

volume = (double)(Volume / 100.0f);

// Number of samples = sample rate * channels * bytes per sample
uint numSamples = (uint)(format.dwSamplesPerSec * format.wChannels * ((double)minAudioChunk / 1000));

// Initialize the 16-bit array
data.shortArray = new short[numSamples];

// Max amplitude for 16-bit audio
amplitude = (int)(MAX_AMPLITUDE_16BIT * volume);

// Create a double version of the frequency for easier math
double freq = (double)frequency;

// The "angle" used in the function, adjusted for the number of channels and sample rate.
// This value is like the period of the wave.
t = (Math.PI * 2 * freq) / (format.dwSamplesPerSec * format.wChannels);

value = amplitude * Math.Sin(t * i);

// Fill with a simple sine wave at max amplitude
for (int channel = 0; channel < format.wChannels; channel++)
{
    data.shortArray[bufferIndex + channel] = (short)value;
}

bufferIndex  goes from 0 ... (numSamples-1)
$\endgroup$

4 Answers 4

1
$\begingroup$

It seems to me that your phase accumulator gets reset at the beginning of each chunk/buffer. When the waveform exactly goes through an integer number of periods over the duration of a buffer, there is no discontinuity, but if a buffer contains a non-integer number of periods, then a discontinuity will appear at each buffer boundary. Solution: store your phase accumulator somewhere at the end of a buffer; and reuse its value when rendering the next buffer.

Best way to troubleshoot this kind of problem is to run your audio rendering code offline and dump the samples into a .wav file. Open the file in an audio editor or make a plot to help you (and us) visualize the defect.

$\endgroup$
9
  • $\begingroup$ In fact I used Audacity for that and the wave form is perfect without any kind of defect. $\endgroup$
    – Patrik
    Jan 10, 2013 at 22:06
  • $\begingroup$ Maybe you could post your code here? $\endgroup$ Jan 11, 2013 at 0:46
  • $\begingroup$ Make sure you dump to your wav file from the actual 50mS buffers, not some other code path. $\endgroup$
    – hotpaw2
    Jan 11, 2013 at 10:13
  • $\begingroup$ Thanks. I made a dump and is perfect. So the only good explanation can be that is something in a player. But the same player (based on Windows WaveOut API) works perfectly with saved files. There must be something with the player and generator at the same time. $\endgroup$
    – Patrik
    Jan 11, 2013 at 21:20
  • $\begingroup$ What is the value of your "i" variable? I suspect it gets reset to "0" at every buffer when you run your code in realtime, though it does not get reset to 0 when you render to an audio file and that's why you get perfect .wav dumps. The consequence is that the phase of your sine wave is reset to 0 at each buffer boundary. $\endgroup$ Jan 11, 2013 at 21:58
1
$\begingroup$

As pointed by the other people you most likely have a frame boundary error. Let me suggest a different approach outlined in the code below. This is based on creating a oscillator using complex multiplication. In order to prevent amplitude drift there is a "stabilization" function that needs to be called every once in a while. This would work as follows: create the structure, call init() once, call setFrequency (in radians) to set the frequency. Call nextSample() to get the next value and fill your buffer as needed. Call stabilize() once every buffer. This has the advantage that it's cheap, very accurate, requires almost no memory, and the frequency can be adjusted whenever desired without creating any discontinuity.

Caveat: I have neither compiled nor tested any of that.

typedef struct 
{
  float cr, ci; // frequency, complex phasor
  float xr, xr; // state
} osc_t;

Void oscInit(osc_t *p)
{
  p->cr = p->xr = 1.0F;
  p->ci = p->ci = 0.0F;
}

float oscNextSample(osc_t *p)
{
   float xr1,xi1;
   // complex multiply
   xr1 = p->xr*p->cr – p->xi*p->ci;
   xi1 = p->xr*p->ci + p->xi*p->cr;
   p->xr = xr1;
   p->xi = xi1;
   return(xr1);
}

void oscSetFrequency(osc_t *p, float freq)
{
  p->cr = cosf(2*M_PI*freq);
  p->ci = sinf(2*M_PI*freq);
}

void oscStabilize(osc_t *p)
{
  float mag;
  mag = p->xr*p->xr + p->xi*p->xi;
  mag = .5F*(3.0F-mag);
  p->xr *= mag;
  p->xi *= mag;
}
$\endgroup$
1
$\begingroup$

I looked at your code. There a couple of problems.

  • First, you don't preserve phase between buffers: Assuming that your index "i" runs from 0 to numSamples-1, your buffer always starts at 0. If the previous buffer didn't end on a 0 (which it won't for frequencies that are not a multiple of 200 Hz you get a discontinuity.
  • Frequency calculation looks wrong, you divide by the number of channels, which I don't think is right
  • The calculation of numSamples uses mixed fixed point and floating point with the final result being truncated instead of rounded. Depending on the hardware platform you may get tiny math errors and come up one sample short. Rounding would be much safer here.

To avoid the first problem, do something like this

global float gPhaseAccumulator = 0.0F; // bad style, but this is for illustration only

float dPhi; // phase increment
// set frequency
dPhi = 2.0F*M_PI*freq/(float)(format.dwSamplesPerSec);
// do the sine wave
for (i = 0; i < numSamples; i++)
{
   gPhaseAccumulator += dphi;
   value = amplitude*sinf(gPhaseAccumulator );
}
// unwrap the accumulator once per buffer
nWraps = (int)(gPhaseAccumulator/(2.0F *M_PI);
gPhaseAccumulator -= nWraps*2.0F*M_PI;

The use of the global variable will make sure that the phase is continuous between subsequent calls.

$\endgroup$
5
  • $\begingroup$ No. "i" is used only for Sinus generation. "bufferIndex" is used to fill the buffer. So "bufferIndex" is reset at the end of every buffer. "i" increments continuously. I am producing two channels so I need to divide by 2. Anyway I will try to use your oscilator and then will see. $\endgroup$
    – Patrik
    Jan 12, 2013 at 15:30
  • $\begingroup$ I confirm Hilmar's comment that you don't need the division by the number of channels. This might not cause a problem in your code if i gets incremented twice per frame - but this is still dodgy coding. $\endgroup$ Jan 12, 2013 at 16:17
  • $\begingroup$ If I omit the format.wChannels the sound does not resembles the same sound for the same frequency. Using other software I compared the sound and the sound looks different. $\endgroup$
    – Patrik
    Jan 12, 2013 at 16:47
  • $\begingroup$ Cnn you list your declaration and definition of "i" and where and how it's incremented? It needs to be global or locally static or (better) part of a persistent state structure. Otherwise this doesn't work. $\endgroup$
    – Hilmar
    Jan 12, 2013 at 17:15
  • $\begingroup$ This code runs in thread in a endless loop the "i" is part of that. It is declared outside of the loop. $\endgroup$
    – Patrik
    Jan 12, 2013 at 19:17
0
$\begingroup$

One other possibility is that you are sending the buffers out to the DAC with the wrong timing (a synchronization error), or perhaps not flushing the data cache at the appropriate time. Waves periodic in the buffer length will produce identical successive buffers and thus won't show borked buffer swaps.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.