1
$\begingroup$

I am using the colorless allpass-filter proposed by Schroeder Manfred R to create a reverberator. It consists of about 8 colorless allpass-filters connected in series, each with a different delay length.

The gain ${g}$ of all the colorless allpass-filters is set to the desired reverberation time using the following equation:

${g = 10^{-3d_{apf} / t}}$

${d_{apf}}$ is the delay time in seconds for each all-pass filter, and ${t}$ is the reverberation time in seconds for the entire reverberator.

The problem is that when I set the reverberation time from a high value to a low value, the overall volume of the reverberator increases rapidly.

However, the volume does not diverge; it returns to normal when I finish setting the reverberation time.

The increase in volume is greater the faster I set the reverberation time, while the volume does not increase as much if I set the reverberation time slowly.

I can't be sure because I can't hear exactly what is going on, but on the other hand, when I set the reverberation time from a low value to a high value, the volume might be decreasing.

What do you think could be causing this problem? Also, I would like to have your suggestions on how to solve this problem.

Schroeder, Manfred R's colorless allpass-filter

colorless allpass-filter class (C++):

class ColorlessAPF
{
public:
  // constructor
  ColorlessAPF(float delay, float gain);

  // common method
  void apply(float* in_L, float* in_R = nullptr);

  // getter
  float getDelay() { return delay; }
  float getGain() { return gain; }

  // setter
  void setGain(float value) { gain = value; }
  void setSamplerate(double value);

private:
  // member
  double samplerate = 44'100.0;
  const float delay;
  float gain;

  using Buffer = std::unique_ptr<RingBuffer<float>>;
  Buffer buffer[2];
};

inline ColorlessAPF::ColorlessAPF(float delay, float gain) :
  delay(delay),
  gain(gain)
{
  setSamplerate(samplerate);
}

inline void ColorlessAPF::apply(float* in_L, float* in_R)
{
  const int num_loops = in_R ? 2 : 1;
  for (int i = 0; i < num_loops; i++) {
    float* in = i == 0 ? in_L : in_R;
    float from_buffer = buffer[i].get()->getFront();
    float to_buffer = *in + from_buffer * gain;
    buffer[i].get()->push(to_buffer);
    float output = *in * -gain + from_buffer * (1.0f - gain * gain);
    *in = output;
  }
}

inline void ColorlessAPF::setSamplerate(double value)
{
  samplerate = value;
  int buffer_size = value * delay / 1'000.0f;
  buffer[0].reset(new RingBuffer<float>(buffer_size));
  buffer[1].reset(new RingBuffer<float>(buffer_size));
}

/* and And how I set the reverberation time:
void setReverbTime(float value)
{
  for (auto&& item : apfs) {
    item.get()->setGain(-std::pow(10, (-3 * item.get()->getDelay() / 1'000) / value));
  }
}
*/
$\endgroup$
1
$\begingroup$

This is a really odd implementation of a Schroeder allpass. The Schroeder all pass can easily derived by warping a normal allpass and it's transfer function is simply

$$H(z) = \frac{g+z^{-M}}{1+gz^{-M}}$$

And the corresponding difference equation is

$$y[n] = g \cdot x[n] + x[n-M] - g \cdot y[n-M]$$

This difference equation can be implemented in any of the four "normal" topologies, i.e. Direct Form I, Transposed Form I, Direct Form II, Transposed Form II. Your implementation is neither one of those.

IF you want to update the filter dynamically, you need to watch the transfer function from the input to the state variables. while the transfer function from input to output is flat, the transfer function from the input to the state variables generally is NOT. This creates the volume changes when you update the filter.

The easiest solution here would probably to implement this all in Direct Form I. This way the only state variables are the input and the output and the transfer functions are guaranteed to have no gain. On the downside, this requires twice the amount of state memory since you need to delay the input and the output. However, since you are using 8 cascaded section, you can use the output delay for stage K as the input delay of stage K+1 so your memory consumption only increases with 9/8.

$\endgroup$
1
$\begingroup$

I can't say for sure without seeing the specifics of the all-pass filters, but it's between common and unavoidable for an IIR filter to exhibit a transient increase or decrease in output when you change its parameters.

The problem is that the states generally have meaning with respect to the parameters -- so when you change the parameters without adjusting the states to compensate, you're essentially re-initializing the filter with not so much random, as meaningless states.

Two possible fixes are to either restrict how fast the delay can be changed so the volume change isn't noticed, or dig deep into IIR filter design and re-tool the allpass filters so they don't respond to transients in the same way.

I don't know what processor you're using, but in these days of inexpensive memory, why not just use a ring buffer big enough to hold your longest delay, and set the actual delay time by where you pick off the signal?

$\endgroup$
1
  • $\begingroup$ I don't mess around with instances where changing filter parameters without generating transients matters. But if you do a web search that includes "IIR filter", "transient", and "changing parameter" you'll get enough information to start refining your search. $\endgroup$
    – TimWescott
    Jul 10 at 18:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.