Low noise dithering

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

Hi guys,

I'm working on a simple dithering algorithm, but the background noise is too loud for me. I wish to improve the algorithm so that the background noise is below -100dB.

Code: Select all

const float noise = ((rand() + rand() + rand() + rand() + rand() + rand() - 0.5f * 6.0f) / 2.0f) * 0.0001f;
int step1 = static_cast<int>((noise + input + 1.0f) * 32767.5f); //float to 16bit int
step1 += quantizationError;
if(step1 < 0) step1 = 0;
else if (step1 > 65535) step1 = 65535;
const int step2 = step1 >> 4; //shift from 16 to 12 bits
quantizationError = step1 - (step2 << 4);
const float step3 = ((float)step2) / 2047.5f; //back to float
return (step3 - 1.0f) - noise;
How can I improve that?

Thanks,
Luca

Post

Your noise is

Code: Select all

((rand() + rand() + rand() + rand() + rand() + rand() - 0.5f * 6.0f) / 2.0f) * 0.0001f
Presuming rand() is a number between 0 and 1, then six of them sum up to anywhere between 0 and 6 with an average of 3.
You then subtract 0.5 * 6 = 3 (your theoretical average, lets hope the compiler optimizes and replaces it with just 3) and divide it by 2, giving an average of zero, minimum -1.5 and maximum 1.5. Finally this is divided again by 10000.

Then this:

Code: Select all

(noise + input + 1.0f) * 32767.5f
Usually signals are between -1 and +1 so I understand the +1 and * 32767.5 for converting it into the integer value.

You say the noise is loud. A peak value of the noise = (1.5 / 10000) * 32767 = 4.9
That is loud indeed! I think the purpose of dithering noise is to subtle change the least significant bit only, and thus be like ten times less loud.

Personally I'd make the noise in the range of the target value where "one" means one least significant bit. Then you get something like this:

Code: Select all

float noise = (((rand() + rand() + rand() + rand() + rand() + rand()) - 3.0f) / 6.0f; // yielding value -0.5 ... +0.5
int step1 = static_cast<int>((input + 1.0f) * 32767.5f + noise); //float to 16bit int
Then if you want to dither other bit depths, the algorithm doesn't need to change much.

You can avoid one addition by alternating adding & subtracting

Code: Select all

float noise = (rand() - rand() + rand() - rand() + rand() - rand()) / 6.0f; // yielding value -0.5 ... +0.5
I have not looked too deeply at the rest, but it looks like you're returning the signal truncated to 12 bits plus some noise. If your target is a noise level of -100dB then that can't ever be right (but lets hope I'm wrong)
We are the KVR collective. Resistance is futile. You will be assimilated. Image
My MusicCalc is served over https!!

Post

right! I have to place the noise on the lsb. That worked. Thanks a lot!

However, even using this gaussian noise, I still have some ringing on the high end. I'll try with the triangular-PDF

Post

First of all, you should really not use rand() for this as it's typically audibly poor quality and linear congruential generators additionally perform rather poorly when combining multiple sequential samples such that the combined distribution might no be quite what you expect (ie. the result will probably keep getting worse and worse as you keep adding more rand() values together).

Second, gaussian noise needs much higher level than TPDF to actually work as a dither. Either use TPDF by adding together two random numbers from a uniform distribution that's one quantization step wide, or take just one such (unit quantization step) random number and differentiate the noise (ie. apply the two-tap FIR [+1,-1]). The latter approach also results in TPDF of the desired magnitude, but rather than giving you a flat spectrum, it will give you a "blue" spectrum with 6dB/octave increasing slope towards higher frequencies, which might or might be preferable. Personally I like the "blue" approach, but YMMV.

Post

Thanks mystran. Since that's the first time I'm dealing with this technique, if I recall correctly a quantization step should be 2^(w-1), where w is the bits number, correct? So my random number should be from 0 to (2^(w-1))-1

Thanks,
Luca

EDIT: I've been totally wrong. I found an old link you posted with an interesting book about dithered quantization. A step size, as far as I understand, should be equal to 1 lsb.. so the TPDF should be 2 lsb. I'll go back to study!

Post

Audiority wrote: Fri Jan 21, 2022 9:24 am EDIT: I've been totally wrong. I found an old link you posted with an interesting book about dithered quantization. A step size, as far as I understand, should be equal to 1 lsb.. so the TPDF should be 2 lsb. I'll go back to study!
Right and you get 2 lsb TPDF by adding (or subtracting) two values from 1lsb RPDF.

Post

Yep, it works. Thanks! The noise floor is now around -90dB and it's quite acceptable.

Post

Audiority wrote: Fri Jan 21, 2022 3:20 pm Yep, it works. Thanks! The noise floor is now around -90dB and it's quite acceptable.
I assume this is in time-domain (which is around where you should see it for 16-bit). Since it's broad spectrum though, it'll be lower in spectral domain (around -120dB or so for flat spectrum) which is kinda the point: you actuall gain more dynamic range with dither. :)

Post Reply

Return to “DSP and Plugin Development”