Writing 8-bit PCM sine wave wav file produces overtones
Asked Answered
K

1

6

I wrote a program in c++ to generate a .wav file for an 800Hz sine wave (1 channel, 8-bit, 16000Hz sampling, 32000 samples so 2 seconds long), but when I play it or examine its spectrogram in Audacity, it has overtones.

I think the problem is with the algorithm converting the sine wave to PCM; I'm not sure where to put 'zero' displacement, at 127, or 127.5, or 128 etc.

char data[32000];
for (int j = 0; j < 32000; ++j)
{
    data[j] = (char) (round(127 + 60 * (sin(2.0 * 3.14159265358979323846264338327950 * j / 20.0))));
}

and the file produced is this: output.wav

If necessary, here's the cpp file: wavwriter.cpp

Thanks!

EDIT 2: I have changed the char to an uint8_t

uint8_t data[32000];
for (int j = 0; j < 32000; ++j)
{ 
    data[j] = round(127 + 60 * (sin(2.0 * 3.14159265358979323846264338327950 * j / 20.0)));


}
outfile.write((char *)&data[0], sizeof data);


outfile.close();
return true;

to avoid undefined behaviour. The same problem still applies.

Kansas answered 23/4, 2016 at 18:8 Comment(15)
Is char signed or unsigned on your platform?Adze
What do you mean? I'm using Windows and c++.Kansas
How much do you think (char)(127 + 60) is?Southwestward
You can't write unsigned char to ofstream. When I said 127, I meant 0x79 and 128 meant 0x80. To answer Drop, it would be -69 for a char and 187 for an unsigned char, but both would be written in the same way in hexadecimal as 0xBB.Kansas
@user1320881 I meant the case when sin is 1 or close to thisSouthwestward
@RobNo signed overflow is UB, not -69Southwestward
So then it would be round(127+60 * 1) which gives round(127+60) which should be 187. This should be converted to BBKansas
It should be converted to pink elephants that poo with colorful butterflies or to any other unpredictable result, as (char)(187) triggers undefined behavior. In practice, on Windows x86 and x64 it might be 0xBB, but you should not rely on that. Anyway, changing char to uint8_t everywhere along the serialization code and testing again should tell whether signedness is a problem or is it something else.Southwestward
But I somehow have to convert that to char, or I can't write it to ofstream; according to this #5041420 I can just use static_cast.Kansas
Nothing has changed in your new code, UB just moved one line down. See Writing unsigned chars to a binary file using write() for the recipe you need. To simplify: make array to be uint8_t and remove static_cast. Apply reinterpret_cast to data.Southwestward
Just throwing out a random idea, is it possible that you get this because 8 bits are not enough for a good approximation? Do you get less overtones with 16 bits?Tophet
That's a good idea, I'll find out.Kansas
" I can't write it to ofstream" -- yes you can, in binary mode.Fineable
The waveform is perfect for 8-bit PCM -- you won't be able to eliminate those smaller, higher frequency peaks, because they come from quantization noise. Of course, moving to 16 bit samples would improve this.Hammy
Thanks, this makes sense. Just to confirm though, where should I have centred the sine wave? 127, 127.5 or 128?Kansas
S
3

You've added rounding noise and deterministic quantization noise. Your sinewave repeats in an exact integer number of samples; thus the rounding error (or difference between the float value and the UInt_8 value of your sinewave) repeats exactly periodically, which produces audible harmonics.

You can reduce this noise by using dithering and noise filtered rounding when converting your floating point sinewave into UInt_8 values.

Dithering (or adding fractional random values to every sample before rounding) removes the deterministic noise. The resulting noise will be whiter instead of made out of overtones.

Noise filtering doesn't just throw away the fractional portion from rounding, but integrates this fractional remainder with an IIR filter to move the quantization noise to where it might be less audible and less likely to create a DC offset.

Styliform answered 23/4, 2016 at 22:20 Comment(1)
Thanks, this is helpfulKansas

© 2022 - 2024 — McMap. All rights reserved.