# Low noise dithering

DSP, Plug-in and Host development discussion. Audiority
KVRian
Topic Starter
1148 posts since 15 Nov, 2005 from Italy
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 BertKoor
KVRAF
13636 posts since 8 Mar, 2005 from Utrecht, Holland

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.

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. My MusicCalc is served over https!! Audiority
KVRian
Topic Starter
1148 posts since 15 Nov, 2005 from Italy
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

mystran
KVRAF
6922 posts since 12 Feb, 2006 from Helsinki, Finland
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.
Preferred pronouns would be "it/it" because according to this country, I'm a piece of human trash. Audiority
KVRian
Topic Starter
1148 posts since 15 Nov, 2005 from Italy
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!

mystran
KVRAF
6922 posts since 12 Feb, 2006 from Helsinki, Finland
Audiority wrote: Fri Jan 21, 2022 1: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.
Preferred pronouns would be "it/it" because according to this country, I'm a piece of human trash. Audiority
KVRian
Topic Starter
1148 posts since 15 Nov, 2005 from Italy
Yep, it works. Thanks! The noise floor is now around -90dB and it's quite acceptable.

mystran
KVRAF
6922 posts since 12 Feb, 2006 from Helsinki, Finland
Audiority wrote: Fri Jan 21, 2022 7:20 am 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. Preferred pronouns would be "it/it" because according to this country, I'm a piece of human trash.