surge icon indicating copy to clipboard operation
surge copied to clipboard

Portamento ST mode does not go all the way to zero

Open Sin-tel opened this issue 1 year ago • 19 comments

Bug Description: When setting portamento time to 0ms in ST mode, there is still noticable portamento on some oscillator and filter types. For self-oscillating filters it is much longer than 32 samples so I don't think it is related to internal block size. This also happens on 'modern' oscillator type, but not on wavetable or classic. I have attached two patches: self osc.fxp and modern.fxp that demonstrate the issue, as well as exported samples and screenshots highlighting the issue in Audacity. report.zip

Surge XT Version Version: Surge XT 1.3.nightly.2405dfd Build: 2023-09-27 @ 15:01:20 on 'fv-az407-912/pipeline' with 'MSVC-19.35.32217.1' using JUCE 7.0.5 System: Windows 64-bit VST3 on AMD Ryzen 5 3550H with Radeon Vega Mobile Gfx
Host: Ableton Live 11 @ 44.1 kHz / Process block size: 32 samples

Reproduction Steps: Steps to reproduce the behavior:

  1. Init saw patch
  2. Mute osc 1
  3. Unmute noise and add a tiny bit
  4. Set filter 1 to LP12dB and set resonance to 100
  5. Set F1 keytrack to maximum
  6. Set play mode to MONO ST or MONO ST+FP (see patch in attach)

Expected Behavior: Instantaneous change - or at least on the order of 32 samples.

Screenshots: self_osc

Computer Information:

  • OS: Windows 10
  • Host: Ableton Live
  • Version: Tried both 1.2.3 and latest nightly [NIGHTLY-2023-09-27-2405dfd]

Additional Information: Related: https://github.com/surge-synthesizer/surge/issues/6984 https://github.com/surge-synthesizer/surge/pull/6986

Sin-tel avatar Sep 27 '23 19:09 Sin-tel

Thanks for reporting! This is suspiciously similar to 6984, I wonder if something has regressed that. Investigation needed.

Andreya-Autumn avatar Sep 27 '23 19:09 Andreya-Autumn

Yeah thanks for the excellent report

I wonder where the lag is coming from. Something - at least in modern - must be lagged too aggressively. With this report it should be straight forward to at least diagnose it.

baconpaul avatar Sep 27 '23 23:09 baconpaul

Ahh I see exactly why this happens with modern. It is a remnant of the original version of the algorithm which is no longer needed. Let me see if I can at least clean up that branch.

baconpaul avatar Sep 30 '23 20:09 baconpaul

Screen Shot 2023-09-30 at 4 21 26 PM

Yeah OK here's the fix for modern.

Need to look at the filters separately. That may be smoothing of the key track.

baconpaul avatar Sep 30 '23 20:09 baconpaul

Initial investigation on the filter is: it is not the key track being smoothed. the filter gets the cutoff reset immediately.

The filter does have history (it is a filter after all) and I wonder if that's what's actually smoothing the self oscillation.

baconpaul avatar Sep 30 '23 20:09 baconpaul

OK the filter is getting reset immediately but the algorithm has enough history that there's a small lag. the coefficients reset instantly though.

So to understand the filter behavior gotta wade through this

{
    f->C[0] = _mm_add_ps(f->C[0], f->dC[0]); // F1
    f->C[1] = _mm_add_ps(f->C[1], f->dC[1]); // Q1

    __m128 L = _mm_add_ps(f->R[1], _mm_mul_ps(f->C[0], f->R[0]));
    __m128 H = _mm_sub_ps(_mm_sub_ps(in, L), _mm_mul_ps(f->C[1], f->R[0]));
    __m128 B = _mm_add_ps(f->R[0], _mm_mul_ps(f->C[0], H));

    __m128 L2 = _mm_add_ps(L, _mm_mul_ps(f->C[0], B));
    __m128 H2 = _mm_sub_ps(_mm_sub_ps(in, L2), _mm_mul_ps(f->C[1], B));
    __m128 B2 = _mm_add_ps(B, _mm_mul_ps(f->C[0], H2));

    f->R[0] = _mm_mul_ps(B2, f->R[2]);
    f->R[1] = _mm_mul_ps(L2, f->R[2]);

    f->C[2] = _mm_add_ps(f->C[2], f->dC[2]);
    const __m128 m01 = _mm_set1_ps(0.1f);
    const __m128 m1 = _mm_set1_ps(1.0f);
    f->R[2] = _mm_max_ps(m01, _mm_sub_ps(m1, _mm_mul_ps(f->C[2], _mm_mul_ps(B, B))));

    f->C[3] = _mm_add_ps(f->C[3], f->dC[3]); // Gain
    return _mm_mul_ps(L2, f->C[3]);
}

and turn it into math and understand how to make it have less hysteresis when self oscillating. It seems.

So either i'm wrong about that and there's a lag elsewhere (but I did a print-when-coefs chang and they are instant) or I'm right about that. In either case: not today!

baconpaul avatar Sep 30 '23 20:09 baconpaul

That's the parallel intrinsics stuff right? Looks intimidating... Anyway, nice about the modern fix!

Andreya-Autumn avatar Sep 30 '23 21:09 Andreya-Autumn

yeah its not that hard to read just you need to do it. then figure out the math. putting it here for when i stumble on it next.

My guess is the filter has enough history that it takes its time adjusting state. It is a low pass filter after all. But I can't demonstrate that. What I was able to show is that the inputs to configure the filter snap immediately, and there's no other code lagging in the way. So I think the lag is in the math on that.

baconpaul avatar Sep 30 '23 21:09 baconpaul

{
    //f->C[0] = _mm_add_ps(f->C[0], f->dC[0]); // F1
    //f->C[1] = _mm_add_ps(f->C[1], f->dC[1]); // Q1
   F1 = C[0]; Q1 = C[1];

    //__m128 L = _mm_add_ps(f->R[1], _mm_mul_ps(f->C[0], f->R[0]));
    L = R[1] + F1 * R[0];

    //__m128 H = _mm_sub_ps(_mm_sub_ps(in, L), _mm_mul_ps(f->C[1], f->R[0]));
    H = in - L - Q1 * R[0];

   // __m128 B = _mm_add_ps(f->R[0], _mm_mul_ps(f->C[0], H));
    B = F[0]+ F1 * H;

    //__m128 L2 = _mm_add_ps(L, _mm_mul_ps(f->C[0], B));
    L2 = L + F1 * B;

    // __m128 H2 = _mm_sub_ps(_mm_sub_ps(in, L2), _mm_mul_ps(f->C[1], B));
    H2 = in - L2 - Q1 * B;

    //__m128 B2 = _mm_add_ps(B, _mm_mul_ps(f->C[0], H2));
    B2 = B + F1 * H2;

    //f->R[0] = _mm_mul_ps(B2, f->R[2]);
    R[0] = B2 * R[2];
    //f->R[1] = _mm_mul_ps(L2, f->R[2]);
    R[1] = L2 * R[2];

    // f->C[2] = _mm_add_ps(f->C[2], f->dC[2]); // C2 exists
   // const __m128 m01 = _mm_set1_ps(0.1f);
   // const __m128 m1 = _mm_set1_ps(1.0f);
   // f->R[2] = _mm_max_ps(m01, _mm_sub_ps(m1, _mm_mul_ps(f->C[2], _mm_mul_ps(B, B))));
   R2 = max(0.1, 1 - C[2] * B * B);

    //f->C[3] = _mm_add_ps(f->C[3], f->dC[3]); // Gain
    //return _mm_mul_ps(L2, f->C[3]);

    return L2 * C[3]
}

baconpaul avatar Sep 30 '23 21:09 baconpaul

{
    F1 = C[0]; Q1 = C[1];
    L = R[1] + F1 * R[0];
    H = in - L - Q1 * R[0];
    B = F[0]+ F1 * H;
    L2 = L + F1 * B;
    H2 = in - L2 - Q1 * B;
    B2 = B + F1 * H2;
    R[0] = B2 * R[2];
    R[1] = L2 * R[2];
    R2 = max(0.1, 1 - C[2] * B * B);
    return L2 * C[3]; // C[3] is 'gain' in the comments
}

so when that is self oscillating what's the lag under coefficient change? The C[0] etc.. are all set in the coefficient maker.

Not obvious.

baconpaul avatar Sep 30 '23 22:09 baconpaul

well ≈512 samples, I guess judging by Sintels report. But indeed, it's not obvious why that should be so. Lemme try if it's different at blocksize 8 just for good measure.

Andreya-Autumn avatar Sep 30 '23 22:09 Andreya-Autumn

Hmmm... Screenshot 2023-10-01 at 00 12 16

That's with the patch from Sintels report. That looks and sounds pretty near instant to me. Doubt this has to do with block size. Wonder if it's a platform thing. Different platforms interpret those parallel instructions differently right? Would that matter?

Andreya-Autumn avatar Sep 30 '23 22:09 Andreya-Autumn

no. there's a slightly different coefficient restatement speed but that's block size nothing else. and the statements are just math so they work same on intel and arm.

that's with the filter patch?

the modern problem was a legit one though and is now fixed in nightly.

baconpaul avatar Sep 30 '23 22:09 baconpaul

Ok, that wouldn't be it then.

Yeah that's the filter patch. I tried with a bandpass too and it was also quick. It's late here and I'm not gonna compile a block size 32 Surge to test with, but other than that and windows IDK what would be different between Sintels setup and mine. Weird.

Andreya-Autumn avatar Sep 30 '23 22:09 Andreya-Autumn

I used print statements didn’t even scope it

I have an 8 16 and 32 build all set up in my ide so can peek tomorrow when I have my laptop open next

thanks so much for looking. I can’t believe I didn’t!

baconpaul avatar Sep 30 '23 22:09 baconpaul

No worries. Catch ya tomorrow.

Andreya-Autumn avatar Sep 30 '23 22:09 Andreya-Autumn

Can confirm on latest nightly modern osc is fixed! The filter thing seems unlikely to be related to any specific DSP algorithm, since it happens on any filter I tried (at least ones that can self-oscillate, which is most of them, but not notch and allpass). Weirdly, for the two comb filters, slewing seems to happen when ramping up but not down.

Sin-tel avatar Oct 01 '23 13:10 Sin-tel

OK I'll try and reproduce this week. That's useful information. It means something is slewing your key track. But I don't see in the code what that could be and it didn't happen when I tried it.

Glad Modern is fixed! Great find on that. Thanks.

baconpaul avatar Oct 01 '23 13:10 baconpaul

I have had to revert the modern change since it causes pops under extreme modulation, which it shouldn't do but did.

patches.zip

Demonstrating patches there for further investigation.

baconpaul avatar Oct 05 '23 11:10 baconpaul