Chosto Being able to do PDM for almost any signal is... wow ! I
Keep in mind this is also useful for other systems such as Arduino or any microcontroller applications where you need a lot of DAC output channels but don't need high fidelity.
Chosto I hope that my class D amp is really bandlimited on the input, because it is not said in any spec that I read.
Even if not it will just put the PDM onto the speaker and the speaker will filter it in the end.
Chosto Combining noise with the input signal (is this dithering ?) will decorrelate the quantization error
Yes, this is dithering. In a faster bit-rate system I probably would not have used this, but I think the use of dithering will be advantageous for your application.
Chosto Noise-shaping uses a feedback loop to re-inject the quantization error to the next sample. I "feel" that given the high bit rate, this will push noise power to high frequencies, but I can't really explain that.
Yes, you feel this correctly. In a true sigma-delta modulator the LPF is an integrator, which has extremely high gain at low frequencies. The average of the output pulse train will strongly suppress the error between the input and output at lower frequencies, so the noise tends to be the highest above the frequency where the integrator slope crosses 0dB.
The more true delta-sigma modulator might be worth trying...I just got going on one technique and stuck with it.
Chosto Now, the noise power is quite low in low frequencies, so we can choose any cut-off <= 1/2 the bit rate without having a peak at the cut-off frequency.
This is correct. To my ears all of the noise sounds like white noise when I listen to my demo code through speakers.
Chosto ryjobil Then look at Analysis2.ods and plots
I cannot find this file in the repository.
[EDIT] I got it added into the repo. Here's the image for faster access:

From this you can see signal peaks are >30 dB higher than the noise floor, and then the noise peak pushed up into the > 4 kHz band. If your voice coils are responsive enough around 4 kHz to make a vibration that can be felt you may need to suppress this with an RC filter at the Bela output, or push this peak higher (higher cut-off in the LPF) to take advantage of the speakers natural filtering...but then the noise floor in the low frequency range will come up.
Chosto Just a few questions :
What is the relation between lfo and pure_sine ? I am confused since in set_sine_oscillator_frequency, I can see the 2pif (angular frequency), but multiplied by Ts. Initially, f is 150Hz and Ts is 1/44.1kHz for pure_sine, but then f becomes sine_oscillator_tick(lfo)in set_sine_oscillator_frequency(pure_sine, sine_oscillator_tick(lfo));.
This part of the code is just a police siren sound. The LFO changes the frequency of the audio-frequency "pure sine" chirping up and down over the course of a few seconds. This is not part of the PDM implementation -- it's just a test signal to hear the effect in my speakers.
Also, I can see noise shaping, but just to be sure : if( (input - lpf1->y1) > 0.0)
is the place where the feedback loop takes place, right ?
Yes, this is where the feedback loop takes place. The noise pre-added to the input is high-pass filtered so it has the highest noise density in the high frequency range. This noise is what de-correlates the output rate. As addressed earlier, this is dithering.
input = x + hf_noise_gen(hpf1, hpf2);
Then looking at hf_noise_gen() you see it has a 4rth order IIR high-pass applied (two cascaded 2nd order filters):
// Noise shaped for highest density at high frequencies
return run_filter(run_filter( ((float) (rand()%65536))/65536.0, hpf1)/50.0, hpf2);
Also, is it an IIR filter because y1 is function of y1 ? Also, your always run the biquad filter twice, does this mean that the result corresponds to a higher order filter ?
Yes it is an IIR filter and you are correct it is a higher order filter because the function is run twice, but notice it is run on a second set of state variables (. It is a 2nd-order filter run on another 2nd-order filter for a total of 4-order filter.
Notice that if you simply ran the same filter twice this would not be the same thing.
Chosto ryjobil by setting filtering cut-off as a non-power-of-two ratio of the bit rate
Saved this for last. The filter cut-off corresponds roughly to the moving-averaging interval. So if you have a bit-rate of 44.1 kHz and use a moving-average with cut-off filter at (for example) 1 kHz, then the averaging interval becomes 1ms.
The number of samples averaged is 44.1 kHz * (1/1kHz) = 44.1 samples. log_2(44.1) = 5.46 Bits
Above is a simplification to communicate the idea. Remember this is an analog filter in the outside continuous-time world, so the filter will be a weighted moving average that will be the most strongly influenced by what happened in the past 1 ms, but stuff that happened beyond 1 ms will also be averaged into the output in progressively lower amounts. An integral of the impulse response would need to be computed in order to come up with an actual equivalent resolution.
In either case it is more apparent quantization error is better treated as high frequency noise that you can filter since fractional bits resolution aren't intuitive units of measure. The quantization error can be treated as a signal-noise ratio more intuitively than a fractional number of bits resolution. The lower the cut-off and the steeper the filter, the lower the noise.
Based on the premise that audio CODECS can produce 16-bit resolution @ 44.1 kHz from a 20-MHz bit rate I would surmise a 44.1 kHz bit rate could produce 16-bit signals <50 Hz with the correct PDM algorithm and analog filtering. That said you could use the digital outputs to drive a subwoofer with fairly good fidelity (but not using my noisy dithered approach -- the modulator would need to be a proper sigma-delta).