Distortion of an analytic signal - has this been tried?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

A_SN wrote: Thu Mar 21, 2024 9:59 am
G-Spot wrote: Thu Mar 21, 2024 9:56 am a(t) only depends on x(t).
But that's wrong, a(t) would be a function of sqrt(x(t)^2+y(t)^2).
G-Spot wrote:Since the imaginary component y has been made up with a Hilbert transform, it depends on x and doesn't contain any additional information, so your gain a actually depends on x only.
Maybe the misconception here is that although the whole amplitude envelope signal "a" depends on the whole signal "x", we cannot really say that instantaneous value "a(t)" depends only on "x(t)". Maybe it's a bit like making a conscious notational distinction between a mathematical function "f" which represents the function in its entirety vs its application to a particular value "f(x)" which only represents the evaluation of "f" at one single point? In a somewhat sloppy notation, these things ("f" and "f(x)") may get confused. The whole signal "a" depends on the whole signal "x" only globally, but locally "a(t)" cannot be computed from "x(t)" alone. The global information contained in x is gathered by the convolution integral with the Hilbert impulse response that is used to produce y. Every y(t) depends on the whole function x and not only on x(t).

By the way, I have my Hilbert filter (prototype) up and running (thanks A_SN for the formula - that really saved me some time) and I can now reproduce the effect on the sawtooth. Well, sort of - I have still some artifact - I see some small parasitic rippling/oscillation at the Nyquist freq - probably due to a non-ideal Hilbert filter, I guess. But generally, it seems to work. Now the experimentation fun can begin...
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

Music Engineer wrote: Fri Mar 22, 2024 11:47 am
A_SN wrote: Thu Mar 21, 2024 9:59 am
G-Spot wrote: Thu Mar 21, 2024 9:56 am a(t) only depends on x(t).
But that's wrong, a(t) would be a function of sqrt(x(t)^2+y(t)^2).
G-Spot wrote:Since the imaginary component y has been made up with a Hilbert transform, it depends on x and doesn't contain any additional information, so your gain a actually depends on x only.
Maybe the misconception here is that although the whole amplitude envelope signal "a" depends on the whole signal "x", we cannot really say that instantaneous value "a(t)" depends only on "x(t)". Maybe it's a bit like making a conscious notational distinction between a mathematical function "f" which represents the function in its entirety vs its application to a particular value "f(x)" which only represents the evaluation of "f" at one single point? In a somewhat sloppy notation, these things ("f" and "f(x)") may get confused. The whole signal "a" depends on the whole signal "x" only globally, but locally "a(t)" cannot be computed from "x(t)" alone. The global information contained in x is gathered by the convolution integral with the Hilbert impulse response that is used to produce y. Every y(t) depends on the whole function x and not only on x(t).

By the way, I have my Hilbert filter (prototype) up and running (thanks A_SN for the formula - that really saved me some time) and I can now reproduce the effect on the sawtooth. Well, sort of - I have still some artifact - I see some small parasitic rippling/oscillation at the Nyquist freq - probably due to a non-ideal Hilbert filter, I guess. But generally, it seems to work. Now the experimentation fun can begin...
Yes well said, x(t) alone isn't enough, what we get is ultimately based on convolution. If you use a raw sawtooth then you have aliasing before you do anything else which then gets phase shifted. To make my graph I did a Fourier series summation for both the sawtooth and its Hilbert transform so it was strictly bandlimited. But when you process real audio signals that won't be a problem.

Btw if anyone wants to experiment with my sawtooth graph I used my drawing calculator (it draws circles based on the formula you give it) and I used this "formula" with the knobs k0 at -0.5 and k1 at 0.6:

Code: Select all

d x = -6
loop_x:
  d y = 0
  d y_im = 0

  // Loop through frequencies and sum them to y
  d k = 0
  loop_freq:
    k = add k 1
    d freq = mul k 0.001
    expr d ts = k * k0
    expr y = y + sin(ts*pi*x)/k
    expr y_im = y_im + cos(ts*pi*x)/k
  i c_freq = cmp freq < 0.5
  if c_freq goto loop_freq

  // Magnitude distortion by erf()
  expr d y_mag = sqrt(y^2+y_im^2)
  expr d y_mc = erf(y_mag*k1)/k1
  expr d y_gain = y_mc / y_mag
  d y_re = mul y y_gain
  y_re = add y_re -4

  // Draw the dots
  d v = symb x y .004 0.01 0.08 0.01 1
  // d v = symb x y_im .02 0.1 0.01 0 1
  // d v = symb x y_mag .02 1 0.1 0 1
  d v = symb x y_re .004 0.4 0.04 0 1
x = add x 0.001
i c_x = cmp x < 6
if c_x goto loop_x
return 0
Developer of Photosounder (a spectral editor/synth), SplineEQ and Spiral

Post

A_SN wrote: Fri Mar 22, 2024 1:43 pm If you use a raw sawtooth then you have aliasing before you do anything else which then gets phase shifted.
This is what I currently get for a sawtooth and its Hilbert trafo via FIR (using a Blackman window):
HilbertSaw.png
It's a 441 Hz saw at a sample rate of 44100 Hz - so the aliasing should line up with the harmonics. Could that still explain the stairstep artifacts? My hunch is that the bandpass characteristic of the non-ideal Hilbert filter filters out the Nyquist frequency component that is present in the saw and should also be present in its Hilbert-trafo - but isn't due to its non-ideality. Or maybe it shouldn't - at the Nyquist freq one would sample the extrema of a cosine and the zeros of a sine...hmmm......dunno - something to investigate further. The wikipedia page talks about Hilbert filters with bandpass vs those with highpass characteristic - there are different types (1 to 4) - I think, I currently have type 3:

https://en.wikipedia.org/wiki/Hilbert_t ... _transform

Ah - I just looked it up in my Oppenheim/Schafer - Type 1 and 2 are filters with even symmetry. So for Hilbert filters, only type 3 and 4 are relevant (I wondered why wikipedia mentions only type 3 and 4 and doesn't say anything about 1 and 2 - that's the reason). Maybe I should try a highpass Hilbert filter, i.e. type 4, instead. This is actually, what I initially tried - but it didn't work. Your formula worked out nicely at first try but gave me the bandpass version. I guess, I need to dig deeper into Hilbert filter design if I really want a proper implementation. For some quick and dirty experiments, this one shall do. ...Edit: OK - done. I have the type 4 Hilbert filter up and running and that seems to indeed solve the problem of the stairsteps. It introduces the other problem of having to delay the direct path signal by half a sample. Soo...not yet sure which approach is better.
Btw if anyone wants to experiment with my sawtooth graph I used my drawing calculator
Aha! OK - done. I could reproduce your graph. So, k0 is the sawtooth freq and k1 is the "drive" parameter for the waveshaper. Interesting - you divide the factor out again after applying the drive. I did not do that so far. I need to try that...

By the way - my experimentation code is currently here - in the function hilbertDistortion (the line number 649 will likely get out of date someday):

https://github.com/RobinSchmidt/RS-MET/ ... s.cpp#L649

I've also already noted down some of my observations at the bottom of the function. The idea may indeed be worth to explore further. I already introduced another parameter to the algorithm: instead of juts scaling x and y by the ratio of the distorted and original magnitude, I raise that scaler to a power. 1 gives the original algorithm (giving some sort of compression effect - btw: without dividing by drive after applying the drive and shaper, we get upward compression), 0 behaves neutrally, -1 does expansion instead of compression, etc.
You do not have the required permissions to view the files attached to this post.
Last edited by Music Engineer on Sat Mar 23, 2024 6:26 am, edited 1 time in total.
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

Music Engineer wrote: Fri Mar 22, 2024 2:38 pm
A_SN wrote: Fri Mar 22, 2024 1:43 pm If you use a raw sawtooth then you have aliasing before you do anything else which then gets phase shifted.
This is what I currently get for a sawtooth and its Hilbert trafo via FIR (using a Blackman window):
HilbertSaw.png
As I noted above (with sketch of proof too) those spikes would be infinite for a theoretically perfect sawtooth and the only two reasons you see finite results are (1) finite kernel and (2) band-limited nature of sampled signals.

Post

mystran wrote: Fri Mar 22, 2024 11:26 pm As I noted above (with sketch of proof too) those spikes would be infinite for a theoretically perfect sawtooth ...
That's interesting. It implies that the instantaneous envelope defined as sqrt(x^2 + y^2) also becomes infinite at these instants. That's a weird feature for a signal that goes by the name "instantaneous envelope". For a saw, that "envelope" looks nothing like what one would intuitively draw in as envelope - i.e. the envelope with which the saw was synthesized. The terminology seems to make sense only for sinusoids.

Do you have any idea about the stairstep artifacts? Is this normal or should I be worried that my implementation is wrong?
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

Music Engineer wrote: Sun Mar 24, 2024 8:57 am
mystran wrote: Fri Mar 22, 2024 11:26 pm As I noted above (with sketch of proof too) those spikes would be infinite for a theoretically perfect sawtooth ...
That's interesting. It implies that the instantaneous envelope defined as sqrt(x^2 + y^2) also becomes infinite at these instants. That's a weird feature for a signal that goes by the name "instantaneous envelope". For a saw, that "envelope" looks nothing like what one would intuitively draw in as envelope - i.e. the envelope with which the saw was synthesized. The terminology seems to make sense only for sinusoids.

Do you have any idea about the stairstep artifacts? Is this normal or should I be worried that my implementation is wrong?
"Envelopes" being the magnitude of a sound's analytic signal isn't necessarily the smooth synthesis envelope we picture, the "envelope" of a wide bandwidth signal is gonna have a wide bandwidth too. It only matches what we picture when dealing with very narrow band signals because then the magnitude of the analytic signal is as if we took the analytic signal and completely untwisted it flat. In a way this "envelope" shows you the reality of sawtooth waves which is that they're a pretty spiky thing that also twists (unlike pure tones that are flat but spin regularly), and the more harmonics you have the more the spikes are high and sharp. And our analytic distortion caps those spikes.

The "artifacts" you get are probably the ripples from the processing the bandlimited sawtooth, maybe aliasing has an effect too. Honestly you should do like me, take the continuous pill, synthesise sawtooths sine by sine (I could have used that opportunity to apply a Gaussian window on their intensities to make a ripple-free graph btw) and their Hilbert transform in the same way, then things get clearer. I'm so continuous-pilled I don't even want to process sounds as samples anymore, I'd rather turn them into polynomial chunks and never deal with an accursed sample again.

Anyway since you did the work with sampled signals, have you tried the concept on real sounds?
Developer of Photosounder (a spectral editor/synth), SplineEQ and Spiral

Post

I'm getting some hunch what is going on with these "artifacts". I think, the phase response of a linear phase FIR is forced to be a multiple of 180° at the Nyquist freq, right? And for even lengths, such a multiple occurs bang on the linear (plus offset) phase response. For an odd length, it is off the line. Here is a phase response of a length 11 Hilbert filter:

Hilbert10PhaseResp.png

and here a zoomed in view at the Nyquist freq:

Hilbert10PhaseRespZoom.png

So - my preliminary conclusion might be that the Nyquist freq component is phase-shifted with respect to the other signal components by 90° and that creates the apparent Nyquist ripples. Does that sound plausible? Edit - wait - no: the Nyquist freq component has magnitude zero anyway, so the phase shift should not matter. It's more likely the zero magnitude that creates the artifact, i.e. what I initially thought. Whatever the case may be - here is length 10 - it looks much nicer:

Hilbert10PhaseResp.png

...but it creates potential alignment issues due to its half-integer delay. I could use a 2-sample MA to get the delay back to a full integer at the expense of some smoothing - which may actually be a good thing in the context of envelope detection. But these things are all fine implementation details. The general algo works. These short lengths are just for demonstration purposes. In practice, the length would more likely to be in the hundreds.
You do not have the required permissions to view the files attached to this post.
Last edited by Music Engineer on Mon Mar 25, 2024 8:28 am, edited 8 times in total.
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

A_SN wrote: Sun Mar 24, 2024 11:45 am Anyway since you did the work with sampled signals, have you tried the concept on real sounds?
Not yet. So far, I didn't even render wavefiles to listen to. I'm just working with artificial mathematical signals and staring at plots. ...that's how music is being made, right? :hihi:

Edit: Now I did. It sounds actually very nice. Adds warmth to the signal. A nice and pleasant distortion that doesn't sound as harsh as regular waveshaping.

Edit 2 - Here are some first impressions of how it sounds:

https://www.youtube.com/watch?v=2jzaG8AKuZ0
Last edited by Music Engineer on Sun Mar 24, 2024 8:16 pm, edited 2 times in total.
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

Oh - and here are the corresponding magnitude responses. Length 11:

Hilbert11MagResp.png

It clearly shows the expected bandpass characteristic. And here is the length 10 version:

Hilbert10MagResp.png

featuring the expected highpass response. So I guess, I'm on the right track with my implementation. Or at least, not totally wrong.
You do not have the required permissions to view the files attached to this post.
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post Reply

Return to “DSP and Plugin Development”