1

As a followup to my True Peak detection question, I'm trying to implement a detection method by following this documentation using the Catmull-Rom interpolation method.

What I've done so far can be seen in my Desmos worksheet (includes sample of y -data which creates the issue in question).

enter image description here

I've (probably) got stationary points solving to work correctly but, calculation used with the cases not having critical points and are solved through finding inflection point using equation $x = \frac{-b}{3a}$ are not giving proper results. I suppose the result could be at least improved by using suitable conditional clauses. As seen in the Desmos worksheet, resulting x values are used as an input for the interpolation function to get the peak level solved (yi = interpolation(y, x), where y is up-sampled data, interpolated using half-polyphase FIR low-pass filter).

Any suggestions to get this issue solved?

Edit: Here are some C++ code at Compiler Explorer.

catmullrom4() is the function name related to this issue:

float catmullrom4(float* y){   
   // Solve tp through stationary and inflection points
   float a, b, c, d, x, yi;

a = -0.5fy[0] + 1.5fy[1] - 1.5fy[2] + 0.5fy[3]; b = y[0] - 2.5fy[1] + 2.fy[2] - 0.5fy[3]; c = -0.5fy[0] + 0.5f*y[2]; d = y[1];

float s1 = (-b - std::sqrt((bb) - (3.0fac))) / (3.0fa); float s2 = (-b + std::sqrt((bb) - (3.0fac))) / (3.0fa); float s3 = -b/(3.0f*a);

float abs1 = std::abs(s1); float abs2 = std::abs(s2); float abs3 = std::abs(s3);

if(abs2 > abs1 ){ x = s1; } else if (abs1 > abs2){ x = s2; } else{ x = s3; if (abs3 > 3.0f){ x = 1.0f; } else if (abs3 < 2.0f){ x = 0.0f; } } return (x * (x * (a * x + b) + c) + d); }

EDIT: Updated the Desmos and Compiler Explorer links

Juha P
  • 917
  • 4
  • 12
  • 2
    That's an awful amount of work and complexity for a relatively simple problem. What's wrong with just up- sampling and poly-phase lowpass filtering? It's fast and easy to implement. How "precise" does your estimation need to be? – Hilmar Sep 17 '22 at 13:53
  • I'm actually using half-polyphase LPF (FIR) with the up-sampled data and now want to interpolate it with some cubic spline method ... this implementation is partly done because of 'dsp -guys' on another forum say that solving TP just using max(abs(Y)) is not the best way to do it. ;) Someone there suggested (maybe) this but mentioned taking roots etc. so ... maybe this is not the most complicate solution I'm after. I don't even know how precise it could be... . – Juha P Sep 17 '22 at 14:44
  • I agree with Hilmar: this is a very complicated way to do something that can be done much more simply and with the same or better accuracy. – Peter K. Sep 17 '22 at 15:19
  • 3
    Look, with all these type of problems there is a very shallow curve of "accuracy" vs "effort". You can spend 10 times the effort so squeeze another 0.02 dB of accuracy out of the thing. So the question is not what it CAN be, the question is what does it NEED to be for your specific application. A good peak limiter should have 0.5dB - 1dB of margin anyway, so if your True Peak estimator comes in within 0.5dB for 99.9% of all source material, you are good. Optimizing for a completely outlandish test case seems like a huge waste of time to me. – Hilmar Sep 17 '22 at 15:19
  • is this a signal (bandlimited, i presume) or is this some kinda discrete data that you're looking for a peak? – robert bristow-johnson Sep 17 '22 at 17:43
  • @robertbristow-johnson, audio data (from file or stream). In my Desmos sheet I gave y-data only for the problem in question. – Juha P Sep 17 '22 at 17:56
  • So it's an audio waveform, correct? And you're trying to detect peaks for what purpose? Compression, ducking or AGC? Are you also looking for peaks for negative values? Is this a sliding peak detection? – robert bristow-johnson Sep 17 '22 at 18:12
  • For dBTP metering purpose and also to find inter-sample peaks. Both, positive and negative values are handled. Here's an example with data that works correctly https://www.desmos.com/calculator/tymy74cnrc (when equation for s3 is used it goes wrong). – Juha P Sep 17 '22 at 18:25
  • I'm not really understanding that graphing calculator thing. Do something in matlab or python or C and I'll follow along. :-) – Peter K. Sep 17 '22 at 20:00
  • 1
    @PeterK., OK, added some C++ code to the post. I have Octave code as well if needed. – Juha P Sep 17 '22 at 20:22
  • 1
    why are you using the 4-point cubic? there is a little ambiguity about which 4 points. with the discrete peak and its two adjacent samples, you can model a nice quadratic with a clear location in time and peak height. i s'pose with the cubic, you can take the derivative (which is quadratic) and set it to zero. Then the discrete peak and its neighbor that is largest would be the two middle points of the 4 points. either way, you should upsample with a windowed sinc first. and if you do that, the quadratic peak location is as good as you need. – robert bristow-johnson Sep 18 '22 at 02:03
  • @robertbristow-johnson, I was suggested to go with 4-point cubic (Catmull-Rom) and it's derivative when showed my implementation which uses binary search w/ quintic hermite spline interpolation. I'll see if I manage to implement sinc -interpolation in C++. – Juha P Sep 18 '22 at 11:10
  • Now Juha, this 4-point cubic interpolation is fine for just a general, quick-and-reasonably-clean interpolation for either sample-rate conversion or for precision delay. it's just that when you are interpolating between two points to get the "true peak", which two points? the discrete peak must be one of them. which one? the point on the left or the point on the right? but with quadratic interpolation, using 3 points, it's clear. – robert bristow-johnson Sep 18 '22 at 14:07
  • @robertbristow-johnson, As it is off-line implementation in question (ATM), interpolation is done between all samples. I know this operation can be optimized and speed-up but, I do it then later. I'll check what I can do with quadratic interpolation (I guess these instructions can be used: http://www.matematicasvisuales.com/english/html/analysis/polynomial/quadratic.html , http://www.matematicasvisuales.com/english/html/analysis/derivative/quadratic.html ? ) – Juha P Sep 22 '22 at 20:00
  • But you still need to resolve the question of which 4 points you are using for the cubic interpolation. The maximum discrete point will be one of the two interior points of the 4. But which one? The interior point on the left or the interior point on the right? With quadratic, it's well decided because there is only one interior point of 3. – robert bristow-johnson Sep 23 '22 at 04:39
  • Desmos sheet may lead to wrong assumptions because of it shows linear curve between points. I do up-sampling using FIR or sinc interpolator before interpolating the points to find the peak. In cubic case, interpolation is done between p1 and p2 (p0..p1..p2..p3) so, max is not p1 or p2 but either one or somewhere inbetween them points. i.postimg.cc/L5xYjNFq/over1.png . In guadratic case, interpolation is done same way but inbetween p0 and p1 (p0..p1..p2) : i.postimg.cc/KzWTk0P0/quadr1.png – Juha P Sep 24 '22 at 04:31

3 Answers3

3

I think you are suffering needlessly here. This is a well known problem with an equally well known solution. Below is the code for up sampling by 4 with a 32 tap FIR filter. The error for the worst case sine wave is less than 0.1dB and for the outlandish double impulse it's 0.3dB.

This can be implemented efficiently as a polyphase filter with 8 taps each. The 0th phase doesn't need to be calculated since it's a pure delay. So you need a total of 24 multiplies per sample (no division or transcendental functions). You can further optimize using symmetry properties: the 1rst and 3rd phases are time reversals of each other and the 2nd phase is the time reversal of itself. So there are only 12 unique coefficients.

If that's not good enough, please explain why.

%% find true peak to upsampling

%% test with a worst case sine wave: true peak error is 1.414 nx = 256;

x1 = sin(2pi(0:nx-1)'/4+pi/4); % x = 0x; % x(100:101) = 1; y1=truePeak(x1,4,32); fprintf('Sine Wave true peak error = %6.2fdB\n',20log10(max(y1)));

%% test with a double impulse x2 = zeros(nx,1); gain = 1/(2sinc(0.5)); x2(nx/2) = gain; x2(nx/2+1) = gain; y2=truePeak(x2,4,32); fprintf('Double impulse true peak error = %6.2fdB\n',20log10(max(y2)));

%% true peak finder (not optimized) function ymax = truePeak(x,nUp,nFir) % desugn the lowpass filter as a windowed sinc t = (-nFir/2:nFir/2)'/nUp; h = sinc(t).*hamming(nFir+1); nx = length(x); y = zeros(nx,nUp); for i=1:nUp y(:,i) = conv(x,h(i:nUp:end),'same'); end ymax = max(abs(y),[],2); end

Hilmar
  • 44,604
  • 1
  • 32
  • 63
  • 1
    Besides suffering needlessly, if this is operating on raw audio and tracking peaks, I think there needs to be some "forgetting factor", where we only consider peaks in a reasonably narrow window of time. It may be they need a sliding maximum filter. But, if the audio has been decently upsampled by a factor of 2 or 4, simple quadratic peak interpolation is good enough. – robert bristow-johnson Sep 18 '22 at 00:01
  • Your method is good enough of course (there's a typo in sin() input). I have to check if I manage to bring that sinc interpolation into C++. – Juha P Sep 18 '22 at 10:55
  • Good catch, thanks. Fixed. – Hilmar Sep 18 '22 at 11:41
  • @robertbristow-johnson: As written the algorithm produces one output sample per input sample (not the peak over the whole segment). You could use this directly as the sidechain input to a limiter. – Hilmar Sep 18 '22 at 11:45
  • @JuhaP: I'm not sure what platform you are working on, but if you have access to any DSP library at all, I'm sure it includes an FIR filter, which is really the only thing you need here. – Hilmar Sep 18 '22 at 11:46
  • @Hilmar, do you mean like using the sinc.*hamming -coefficients held by variable h in your example code? (in Octave: filter(h, 1, X) or conv() as you do)? – Juha P Sep 18 '22 at 15:53
  • I mean what the code says. The lowpass filter is designed as a windowed sinc function and you implement it as a polyphase FIR. Since we are up sampling by 4 we need to implement 4 phases. The first one is trivial though, since it's a pure delay. – Hilmar Sep 18 '22 at 16:27
  • I'll accept this answer as best one even it is not answer to my actual question. I think I'm closer to find the solution and therefore updated Desmos and Compiler Explorer links. – Juha P Sep 19 '22 at 07:06
2

If the goal is to identify inter-sample overs such that you can normalize a signal and still not have voltages out of a D/A converter exceeding some maximum, you are essentially modelling the D/A conversion process, and without that knowledge, I think that there can be no absolute limit.

The D/A could be filtered by a close approximation to a sinc - or it could not.

Knut Inge
  • 3,384
  • 1
  • 8
  • 13
  • Some seemingly relevant sources on intersample overs: https://benchmarkmedia.com/blogs/application_notes/intersample-overs-in-cd-recordings https://hydrogenaud.io/index.php/topic,98753.0.html – Knut Inge Sep 18 '22 at 14:57
  • KnutInge, yes, you're right. I've seen couple test suites comparing ISP/TruePeak meters (plug-in and stand alone format) and those showed there are lots of variation in results. – Juha P Sep 19 '22 at 07:10
1

OK, followed @robertbristow-johnson's suggestion in comments and implemented TP detection based on quadratic functions.

Here's the C++ code at Compiler Explorer site with 4x up-sampled data from test case in Hilmar̈́s answer. Given C++ implementation needs some modifications to work with different up-sampling ratios.

Here's also Desmos sheet demonstrating the main functionality of the implementation.

Juha P
  • 917
  • 4
  • 12
  • Hi, I looked at the code but did not go through it thoroughly. Some stuff I do not recognize. Anyway, as long as your discrete peak (which I think you call y[1]) is a larger value than either of its adjacent neighbors (y[0] and y[2]), then you are guaranteed that the "true" peak location will be between y[0] and y[2] and closer to y[1] than to either of y[0]or y[2]. It's mathematically guaranteed, you need not test for that being out of range. What you need to test for is that the peak height, y[1], is larger than both y[0] and y[2]. That's the only test you need. – robert bristow-johnson Oct 01 '22 at 19:46