Allpass Interpolation - Help

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Hey Folks! I'm getting some strange artifacts with my first attempt at a allpass interpolated DDL. I couldn't make sense of the "delayA" code in the STK-Toolkit, so I decided to just do it - empirically. I've only analyzed the code with a spectrum analyzer - seems that it's clicking somehow - most evident with high frequency material. Maybe I stuffed up the allpass, maybe I stuffed up the delay itself? This is exactly the 2nd delay I've ever coded - the first one was a linear interpolated DDL from a tutorial by xoxos.

Inanyway - here is my naive code:

Code: Select all

       


	for( int s = sampleFrames; s > 0; --s )
	{
		 float input1 = *in1 + 1.0e-18f;

          float fdbk = *in3;

         float lenf = *in2 * Samplerate;


float c_epsilon = 0.4f;

unsigned short int delayInt = lenf - c_epsilon;

if (delayInt > 65534) delayInt = 65534; // check at delay length calculation
         else if (delayInt < 1) delayInt = 1; 


float fractionalDelay = lenf - delayInt;
float APcoeff = (1.0f - fractionalDelay) / (1.0f + fractionalDelay);


r = p - delayInt;
r0 = r - 1;

if (r < 0) r += 65536;
if (r0 < 0) r0 += 65536;
                   
nout = buffer[r0] + APcoeff * buffer[r] - APcoeff * nout;//nout= unit-delay
buffer[p] = input1 + nout * fdbk;
p++;
if (p > 65535) p = 0;



		*out1 = nout;

		



// move to next sample in buffers
		in1++;
		in2++;
                in3++;
		out1++;


Any suggestions of what I did wrong? :help:


P.S - I'm a serious nOOb :ud: So a dumbed-down answer would be greatly appreciated!

Regards
Andrew

Post

Have you checked https://ccrma.stanford.edu/~jos/pasp/Fi ... ation.html ? One interesting note is the one at the bottom:
Note that, unlike linear interpolation, allpass interpolation is not suitable for ''random access'' interpolation in which interpolated values may be requested at any arbitrary time in isolation. This is because the allpass is recursive so that it must run for enough samples to reach steady state. However, when the impulse response is reasonably short, as it is for delays near one sample, it can in fact be used in ''random access mode'' by giving it enough samples with which to work.
~stratum~

Post

stratum wrote:Have you checked https://ccrma.stanford.edu/~jos/pasp/Fi ... ation.html ? One interesting note is the one at the bottom:
Yeah - I think this qualifies for implementation.

Looking at "DelayA" again - there is something different - that I didn't implement - can't remember Datorro's paper mentioning it...

Here:

Code: Select all

StkFloat outPointer = inPoint_ - delay + 1.0;     // outPoint chases inpoint
  delay_ = delay;

  while ( outPointer < 0 )
    outPointer += length;  // modulo maximum length

  outPoint_ = (long) outPointer;         // integer part
  if ( outPoint_ == length ) outPoint_ = 0;
  alpha_ = 1.0 + outPoint_ - outPointer; // fractional part

  if ( alpha_ < 0.5 ) {
    // The optimal range for alpha is about 0.5 - 1.5 in order to
    // achieve the flattest phase delay response.
    outPoint_ += 1;
    if ( outPoint_ >= length ) outPoint_ -= length;
    alpha_ += (StkFloat) 1.0;
  }

  coeff_ = (1.0 - alpha_) / (1.0 + alpha_);  // coefficient for allpass
}
Obviously I'm refering to the "alpha" part in the code. This code also confuses the :smack: out of me because the delay writes before it reads, and I very new to delays, and c++. So I can't just copy theirs explicitly. That's why I'm trying to do it - in a way I can atleast understand partially.

Post

Well, the schematic at JOS's site is easier to read than reading c++ code, and I cannot comment about the code above because it's hard to read. Maybe you should try a modular design and code the blocks commonly found in such schematics so you can make more complex "circuits" by attaching those components together.
For example, a z^-1 block is simply:

Code: Select all

		T ProcessSample(const T&sample)
		{
			T result = m_PreviousSample;
			m_PreviousSample=sample;
			return result;
		}
		T TapCurrent()
		{
			return m_PreviousSample;
		}
Put this to a class, and call it with a name that resembles z^-1, DelayOneSample, for example. Similarly you could have a z^-n block, and an all pass block, and so on. For example this is an all-pass filter block composed of a z^-n block wired like an all-pass:

Code: Select all

		T ProcessSample(const T&sample)
		{
			T b = (T)m_DelayN.TapCurrent();
			T a = (T)(sample-b*m_CoeffAm);		// sum1, what goes to delay
			m_DelayN.ProcessSample(a);
			T c = a*m_CoeffB+b;			// sum2, result
			return c;
		}
~stratum~

Post

stratum wrote:Well, the schematic at JOS's site is easier to read than reading c++ code
stratum wrote: Put this to a class, and call it with a name that resembles z^-1, DelayOneSample, for example. Similarly you could have a z^-n block, and an all pass block, and so on. For example this is an all-pass filter block composed of a z^-n block wired like an all-pass:
Writing code in this "micro-class/function" style - is exactly why the "delayA" code is so unreadable. I'd rather write small code simply procedurally - makes it easier to read and spot bugs. (For me inanyway).

Thanks, stratum - for trying to help though. My code does actually work btw, think there is just a problem with the allpass coefficient and it possibly controlling the pointer update(?)

Post

there is just a problem with the allpass coefficient and it possibly controlling the pointer update(?)
If that value comes from a lookup table based oscillator try replacing with something more accurate. Once I had made a chorus effect with a lookup table and it had similar problems.
~stratum~

Post

stratum wrote:
there is just a problem with the allpass coefficient and it possibly controlling the pointer update(?)
If that value comes from a lookup table based oscillator try replacing with something more accurate. Once I had made a chorus effect with a lookup table and it had similar problems.
Nah, this is a simple delay.

The problem is definitely with the allpass coefficients - it should be varying around ~ +0.24 ~ -0.24 (If I understand correctly). But mine is varying around ~ +0.41 ~ -0.17. The spikes I'm seeing is probably the allpass ringing :x Simple rescaling obviously doesn't work either.

Post

this is a simple delay.

A simple delay can be made without an allpass filter. Why are you using it?
~stratum~

Post

stratum wrote:
this is a simple delay.

A simple delay can be made without an allpass filter. Why are you using it?
Sorry -> I was being vague, the code is for a simple delay - but the use will be for a chorus. Linear and other FIR interpolators(tried a 3rd order Hermite) - just isn't working for me. Thought I'd first try an Allpass Interpolator and then a Fractionally Addressed Delay. The allpass interpolator seems much much easier. And the link to the FAD code is broken inanyway...

Actually thought I could do it quickly in a day - I'm obviously a dumbass
:oops:

Probably not the best day to ask around here either, I need a life :P... Would probably need the help of someone who has actually done an allpass interpolator before or someone who intrisically understands the process. The dang coefficient update looks like saw in the time domain - that surely can't be right...

Post

I had implemented the chorus with a linear interpolator and the lfo needed to be something other than a lookup table, accuracy of that was important. Dattorro's paper or something other I had read implied that linear interpolated delay wasn't good enough (If I recall correctly it has a lowpass filtering side effect) so I have added an oversampling unit before the chorus and downsampling after to compensate for that. This is a working combination but probably overkill. Someday I'll try an allpass interpolator.

Anyway.. good luck
~stratum~

Post

stratum wrote: so I have added an oversampling unit before the chorus and downsampling after to compensate for that.
If I did anything esoteric like companding or distortion with the chorus, I'd definetly use your approach - would be cheaper that way.

Post

Ichad.c wrote:Probably not the best day to ask around here either, I need a life :P... Would probably need the help of someone who has actually done an allpass interpolator before or someone who intrisically understands the process. The dang coefficient update looks like saw in the time domain - that surely can't be right...
If I understand what you're referring to here, the allpass coefficient 'sawtooth flyback' happens at the same time as the integer value of the delay tap index changes.

I was never able to get an allpass-based modulated fractional delay to work stably, myself -- the trouble is, the filter has state (the delay memory) that is based on previous samples' coefficients, so changing the coefficients changes the "meaning" of the memory.

Polynomial interpolation, or oversampled linear interpolation, is the most straightforward way to go. For a chorus you can even just do simple linear interpolation - since you're mixing the unfiltered signal with the delayed signal, the lowpassing effect will be less noticeable.
Image
Don't do it my way.

Post

I guess one way to avoid that sawtooth jump would be to use a large number of those 1-sample all-pass interpolators instead of a M-sample delay + one 1-sample all-pass interpolator. For example, assume that we have 2*M number of 1-sample all-pass interpolators wired serially. At the point of the sawtooth jump where the integer part of the controller value changes by one and the fractional part jumps, one could just stop using the output of that all pass interpolator and switch to the output of the next/previous one to be able to avoid a random jump (as JOS says these are not suitable for random access). To get it working, when the controller voltage returns to a value that requires the same 1-sample allpass filter block to be used, it would find it at a sensible non-random value that didn't change for a while. To me it seems like this could work but the controller signal still needs to be a smooth one without jumps.
~stratum~

Post

Quick answer: Allpass interpolator is not designed for modulated delay.

But it is good for many other things, as subsample delay increases the accuracy of comb-filters and such.

Post

stratum wrote:I had implemented the chorus with a linear interpolator and the lfo needed to be something other than a lookup table, accuracy of that was important. Dattorro's paper or something other I had read implied that linear interpolated delay wasn't good enough (If I recall correctly it has a lowpass filtering side effect) so I have added an oversampling unit before the chorus and downsampling after to compensate for that. This is a working combination but probably overkill. Someday I'll try an allpass interpolator.

Anyway.. good luck
It's not overkill, actually quite necessary to get good resolution with the comb filtering. It's just a limit of discrete mathematics.

Post Reply

Return to “DSP and Plugin Development”