Hi Robert,

Thanks for the detailed reply -- this was exactly the kind of feedback I
was hoping the list would provide. I took your numbers seriously enough to
verify them against my actual filters, and the short version is: your rules
of thumb are impressively accurate.

> I found that L=512 (along with linear interpolation between neighbors)
gets you 120+ dB S/N ratio from the energy getting folded back into the
baseband. L=256 is okay. About 108 dB S/N.

Confirmed, almost exactly. I fractionally resampled a 19.5 kHz tone
(near-worst case) through my tables at 1 + 200 ppm, LS-fit the expected
tone, and measured the residual: L=256 gives 108.6 dB, L=512 gives 120.7
dB, slope +12.0 dB per doubling with the linear inter-phase blend, and +6.0
dB per doubling for drop-sample -- all four of your figures to within half
a dB. The check is now a committed notebook in the repo
(notebooks/asrc_rbj_analysis.ipynb) with the results assertion-pinned. One
nuance worth stating: the floor is frequency-dependent; the same L=256
table measures ~144 dB at 997 Hz, which is how my mid-band measurements
(126-135 dB through the full converter) coexist with the ~108 dB
near-Nyquist interpolation floor.

> Ooooh, that's a lotta taps. I think you can get away with T=32 taps.

Here I get a different answer, and I think it comes down to the stopband
budget rather than the phase count. T buys the FIR's own transition width
and stopband depth: at my band edges (flat to 20k, first image suppressed
from 28k, 120 dB) the Kaiser/harris estimate lands at T=47-48, and
measurement agrees -- pairing T=32 (whose achievable design at those edges
is ~96 dB) with L=512 still tops out at 84 dB at 19.5 kHz, because the FIR
stopband dominates before L matters. T=32 is exactly my "fast" preset,
offered for the 96 dB tier. With your Parks-McClellan designs you'd shave a
few taps (~44 for the same spec by the equiripple estimate), which is on my
list to try.

> But it also puts zeros right at the integer multiples of the sample rate
... You can have the script if you want it.

Yes please -- I'd like to see it. I measured the naive version of the idea
(convolving my Kaiser prototype with a one-sample rect): the benefit is
real (5.6 dB deeper worst image around fs, ~14 dB around 2fs) but so is the
sinc droop, -2.64 dB at 20 kHz, which my passband spec (+/-0.01 dB) can't
absorb. So doing it properly means folding the droop compensation into the
passband target of an optimal design, which I gather is exactly what your
MATLAB script does.

> That's sounds more expensive than swapping the order. I think creating
two output samples and linearly interpolating that pair of values for the
output would be quicker.

For mono it's essentially a wash (2T MACs either way, and your order saves
the scratch-row traffic). The reason I blend coefficients is multichannel:
every channel of a frame evaluates at the same mu, so the blended row is
computed once and shared -- (N+1)*T ops vs 2*N*T. That restructuring
measured -36% wall-clock for stereo and -52% for 8-channel when I made it,
and the high-channel-count path (one blended row broadcast across channel
lanes, frame-major) depends on the shared row existing.

> Not sure what you mean by "occupancy".

The FIFO backlog between the two clock domains, in frames -- it's the
loop's phase detector. The consumer-side observable is backlog + mu, which
makes it continuous across whole-sample slips (the +/-1-frame staircase
disappears into the fraction). Your SHArC time-stamping scheme is a more
informative observable and I can see it buying faster acquisition; my
occupancy servo gets there with gain scheduling instead (wide to acquire,
narrow to reject the block-quantization sawtooth). And on the 44.101 ->
44.100 point: the once-per-second slip does happen in my scheme, by design,
but it's bit-continuous -- the table carries L+1 rows so "mu wraps to 0 on
a shifted window" is the same filter, and a slip-continuity test bounds the
output's second difference against the analytic bound of a clean sine at
+500 ppm.

Your Q32.32 index and my accumulator are close cousins, by the way -- mine
is Q0.64 carrying only the deviation from unity (the integer "1" rides the
window advance), which near-unity operation makes possible; 64 fractional
bits is admittedly more than anyone needs.

Thanks again -- between this and the earlier notes I have two concrete
experiments queued: a PM/LS prototype with the k*fs zeros and droop
pre-compensation, and a mono benchmark of your two-dots-then-lerp ordering.

Cheers,
Tim

Reply via email to