

(I learned it existed by reading faust's lib)

void hilbert(std::size_t size, float* input, float* output_r, float* output_i) {
assert(size % 4 == 0);
for (std::size_t i = 0; i < size; i += 4) {
// Modulation by exp(-j*pi/2*n) : sequence 1, -j, -1, j
output_r[i + 0] = input[i + 0]; // *1
output_i[i + 0] = 0.0f;
output_r[i + 1] = 0.0f; // *(-j)
output_i[i + 1] = -input[i + 1];
output_r[i + 2] = -input[i + 2]; // *(-1)
output_i[i + 2] = 0.0f;
output_r[i + 3] = 0.0f; // *j
output_i[i + 3] = input[i + 3];
// lowpass at à sr/4
float* c[] = {output_r + i, output_i + i};
lowpass.process(4, c);
// demodulation by exp(j*pi/2*n) : sequence 1, j, -1, -j
// i+0 : (*1) (nothing to do)
// i+1 : (*j) -> (-b + j a)
float tmp_r1 = output_r[i + 1];
float tmp_i1 = output_i[i + 1];
output_r[i + 1] = -tmp_i1;
output_i[i + 1] = tmp_r1;
// i+2 : (*-1) -> (-a - j b)
output_r[i + 2] = -output_r[i + 2];
output_i[i + 2] = -output_i[i + 2];
// i+3 : (*-j) -> (b - j a)
float tmp_r3 = output_r[i + 3];
float tmp_i3 = output_i[i + 3];
output_r[i + 3] = tmp_i3;
output_i[i + 3] = -tmp_r3;
}
}





Mostly Qwen3.6-35B-A3B with Hermes agent
I lost a lot of time trusting it too much, but things should be different with a frontier LLM
I am open for questions and for work!
On top : - fft gives information about the average behavior for a particular fft bin - a $n$ sec fft discriminate between 1/n hz bins - the leakage profile give clue about the signal underneath (the 3.5 bin has more enegy than the 2.5) - 2 consecutive fft gives information about the phase of phase of a signal evolve - 2 second of signal allows to pinpoint up to 0.01hz, without having to sample 100sec
we take the signal and fold it, look how the phase for each line compare to a reference signal diving the slope by 2π gives the frequency in hz
We just need what happens arround a reference frequency. We can multiply our signal by a complex oscillator at that frequency. its like "moving the 0hz" note that our signal is complex, it doesn't have aliasing arround 0hz the resulting signal's frequency is the algebraic difference between the original signal and the oscillator (it can be negative) Now we can decimate
The main pipeline is mixing with oscillator, decimate, then STFT an alternate path allows to analyse the enveloppe of the signal rather that the signal itself
The "standard" STFT reset the bin's "oscillator" for every fft. we compensate this for every bin to get phase coherence.
So far the signal is sampled with the soundcard's sample rate, the local oscillator is generated with the same soundcard's clock
First garph : we compare the paste of the soundcards clock and the system's one, and take their difference and perform linear regression. (here on 2 hours) Second graph, same linear regression over 300sec windows third graph : chrony's metrics (system hardware clock raw drift (red) , uncertainty (I believe its IC99) orange, doing down as the monitoring time increase and uncompensated drift (very close to 0, in blue)
mecanical watch movement, 1sec signal, 5 impulsion, but very little energy at 5hz instead of analysing the signal, we look at the enveloppe of the signal now we can see the fundamental at 5hz and its harmonics. technically the rate is 2.5hz (5 full cycle from the balance wheel every 2 seconds), but because both to and fro cycle are so similar we have very little energy at 2.5hz
in blue : a chirp real (both positive and negative frequency) in green : modulation by -SR/4 in red : low pass, (we get rid of the high frequency (positive and negative)) in purple : demodulation by SR/4, we put the positive frequency back on their place
multiplication SR/4 is trivial because the only coefficients are 1, -1, j, -j much faster than FIR and FFT based implementations for realtime
zoom arround 32.768khz time goes from bottom to top you see the 0.33hz is the strongest, and it has a phase drift with 20 sec period : 0.05hz
phase on the same 3 bin with the most energy : 0, 0.33, 0.66 hz
real signal -> positive and negative frequency identical. We could have 64hz of positive frequencies and more harmonics with a oscillator at 32Hz) see the phase shift for all harmonics
The soundcard send data to the DPS module that output decimated stream A timestamp is stored at the callback, attached to the soundcard's "clock" (in sample)
FrequencyCounterAsync get the decimated sample and store them in the STFT SoundCardDrift store the couple (system's time, soundcard's time) when needed : linear regression to know the current rate of the soundcard, analyse of the STFT (bin with most energy + linear regression for phase drift)
very weak signal need long fft. the signal is coherent (hopefully) its energy is integrated into a single bin, the noise is spread between more and more smaller bins
About the reference clock I dont want to re-implement chrony Maybe I can make a reference clock for chrony to use?