From The MPEG-4 Structured Audio Book by John Lazzaro and John Wawrzynek.

Part IV/2: Filter Core Opcodes

Sections

Core Opcodes:

allpass bandpass bandstop biquad chorus comb delay delay1 fir firt flange fracdelay hipass iir iirt lopass reverb

Other Elements:

oparray

Introduction

In this chapter, we describe the core opcodes for audio filters. These a-rate opcodes return a filtered version of the signal coded by the a-rate parameter in.

Most opcodes in this chapter are normative, and compute filters that sound identical on any compliant decoder. A few opcodes are non-normative, and are provided as convenient tools for algorithm prototyping.

We also introduce a SAOL language feature, the oparray, in conjunction with the fracdelay core opcode. Oparrays let several syntactically distinct calls to the same opcode share a single set of internal state variables.

 

FIR Filters

Two finite impulse response (FIR) filter core opcodes, fir and firt, are provided in SAOL. The right panel shows the header syntax for the opcodes.

Both opcodes return a filtered version of the signal provided by the a-rate parameter in. During the first call to these opcodes, the internal filter state variables are initialized to zero.

Both opcodes are normative, in the sense that the filter coefficients parameters define the transfer function of the filter, assuming one opcode call per a-pass.

The method for implementing this transfer function is non-normative.

The fir opcode specifies the FIR filter coefficients as the list b0, b1, b2 ... of scalar k-rate parameters.

The firt opcode specifies the FIR filter coefficients as the table parameter t, whose length is the order of the filter. If the optional k-rate parameter order is less than the table length, this parameter determines the filter order, as specified on the right panel.

FIR: Parameter Coefficients


aopcode fir(asig in, ksig b0
            [, ksig b1, 
             ksig b2 ...])



Implements transfer function

                -1       -2
H(z) = b0 + b1 z   + b2 z   ...

Exact implementation is decoder
dependent.

FIR: Wavetable Coefficients


aopcode firt(asig in, table t
            [, ksig order]) 


FIR coefficients are stored in
the table t. If order is not
supplied, let order be the
length of table t. The 
opcode implements the transfer
function:

                    -1         -2
H(z) = t[0] + t[2]*z   + t[2]*z   

                      -(order-1)
      ... t[order-1]*z

Exact implementation is decoder
dependent.


IIR Filters

The iir and iirt core opcodes implement Infinite Impulse Response (IIR) filters. See the right panel for header syntax for these opcodes.

Both opcodes return a filtered version of the signal provided by the a-rate parameter in. During the first call to these opcodes, the internal filter state variables are initialized to zero.

The iir opcode specifies the transfer function coefficients as a list of k-rate scalar parameters b0, b1, b2 ... for numerator coefficients, and an interleaved list of k-rate scalar parameters a1, a2 ... for denominator coefficients.

The iirt opcode specifies the transfer function coefficients as two wavetables, table b for numerator coefficients and table a for denominator coefficients.

These opcodes are normative in the sense that the coefficients supplied as parameters define the filter transfer function, assuming one opcode call per a-pass.

The actual method for implementing the transfer function is non-normative.

Normative IIR Implementation

For some IIR transfer functions, control over the filter implementation is crucial to assuring filter stability.

The core opcode biquad provides an exact implementation of a second-order filter structure, using the Transposed Direct Form II structure.

The right panel shows the header syntax and exact implementation of the biquad core opcode.

Normative implementations of higher-order filter structures may be created by composing several biquad opcode calls in a series or parallel manner.

IIR: Parameter Coefficients


aopcode iir(asig in, ksig b0
            [, ksig a1, ksig b1,  
               ksig a2, ksig b2,
               ...])



Implements transfer function

                -1       -2
        b0 + b1 z   + b2 z   ...
H(z) = --------------------------
                 -1       -2
         1 + a1 z   + a2 z   ...

Exact implementation of the 
transfer function as a filter
is decoder dependent.

IIR: Wavetable Coefficients


aopcode iirt(asig in, 
             table a, table b
             [, ksig order]) 


Implements transfer function:

                    -1       -2
       b[0] + b[1]*z + b[2]*z   ...
H(z) = --------------------------
                   -1       -2
         1 + a[1]*z + a[2]*z   ...


Up to coefficients b[order-1] and
a[order-1]. If optional parameter
order is not supplied, the 
length of tables a or b determine
the order of the numerator and
denominator respectively.

Exact implementation of the 
transfer function as a filter
is decoder dependent.


Transposed Direct Form II


aopcode biquad(asig in, ivar b0,
               ivar b1, ivar b2,  
               ivar a1, ivar a2)


At the first call, initializes
internal storage variables d1 and
d2 to zero. At each call, computes
these equations in order, and 
returns "ret".

ret = d2 + b0*in
d2  = d1 - a1*ret + b1*in
d1  =    - a2*ret + b2*in

Parametric Filters

The IIR and FIR core opcodes described in the previous sections may be used to create normative implementations of the classic filter shapes (lowpass, highpass, bandpass, and bandstop) with dynamic transfer functions. However, these opcodes require the programmer to specify the coefficient values for the filters.

The non-normative filter core opcodes lopass, hipass, bandpass and bandstop let the programmer specify filter frequency response at a higher level of abstraction.

These opcodes return a filtered version of the signal provided by the a-rate parameter in. The right panel shows the header syntax for these opcodes.

The lopass and hipass opcodes have a single k-rate control parameter cut that sets the 6 dB cutoff point for the filter.

The bandpass and bandstop opcodes have two k-rate control parameters, a cf parameter that sets the center frequency for the passband or stopband, and a bw parameter that sets the bandwidth of the passband or stopband, measured at the 6 dB cutoff points

The control parameter definitions assume the opcodes are called once per a-pass.

Note that since the opcodes are non-normative, specifications such as the cutoff slope, the passband ripple, and the stopband ripple are decoder-dependent.

Basic Filter Blocks


aopcode lopass(asig in, ksig cut)

aopcode hipass(asig in, ksig cut)


cut: -6 dB cutoff point of the filter,
specified in Hz.



aopcode bandpass(asig in, 
                 ksig cf, ksig bw)


aopcode bandstop(asig in, 
                 ksig cf, ksig bw)


cf: center frequency of the passband
or stopband, in Hz.

bw: bandwidth of the passband or 
stopband, measured as the distance
between the -6dB cutoff point below
and above the center frequency.

Integral Delays

The core opcodes delay1 and delay delay the signal parameter in for a fixed number of opcode calls. These opcodes are useful building blocks for filter design.

See the right panel for the header syntax and exact semantics for these opcodes.

The delay1 opcode delays a signal for one opcode call. If the opcode is called once per a-pass, it corresponds to a delay of a sample period. The first call to delay1 returns zero.

The delay opcode implements a delay line as a shift register structure. The parameter t (units of seconds) sets the time delay of the line, assuming the opcode is called once per a-pass.

The delay line state variables are created during the first call to the opcode, and are initialized to zero.

During each call to delay, the delay line is shifted forward one position. The new value of the parameter in is inserted into the front of the delay line, and the value that falls off the end of the delay line is returned by the opcode.

delay1


aopcode delay1(asig in)

        -------
        |  -1 |   
in -----| z   |----- y
        |     |
        -------

On first call, initialize 
delay line value to zero.

On each call, return y, 
and insert parameter in
into the unit delay.

delay


aopcode delay(asig in, ivar t)

     ------------------
     | floor(t*srate) |
in --|     delay      |--- y
     |     units      |
     ------------------

On first call, initialize
delay line shift register
values to zero.

On each call, shift in 
new value of in, and return
value of y that falls out
the end of the line.

Fractional Delays

The filter and delay opcodes presented in the previous sections share a common calling semantics.

  1. Each call provides the opcode with the next input sample, via the parameter in
  2. Each call returns the next output sample of the filter or delay algorithm.
  3. Internal state variables are created and initialized to zero during the first opcode call, and updated during each opcode call.

These semantics are incompatible with the function of the core opcode fracdelay (see the right panel for header syntax).

This opcode creates a delay line structure, and lets the programmer insert and sum values into arbitrary taps along the line. It also lets the programmer read out the delay line value at a fractional position along the line, by interpolating between delay line positions using the interpolation method specified by the global parameter interp.

To provide this functionality, the fracdelay opcode includes a method parameter, that specifies the operation to be performed on the delay line. These operations include creating and initializing the delay line internal state, and shifting the contents of the delay line one cycle forward. The right panel describes each method in detail. Note that unlike previous filter opcodes, the fracdelay opcode does not automatically shift its delay line with each call.

Supporting this type of semantics requires SAOL language support, since each syntactically distinct opcode call has its own set of internal variables. For proper operation, multiple calls to fracdelay need to modify the same set of internal variables (i.e. the delay line). The SAOL atomic expression element oparray is designed for this application.

Oparray Declarations

The right panel shows a typical oparray declaration, as part of the stereo delay line example.

An oparray declaration creates the internal state variables for a fixed number of calls to an opcode. Each set of internal state variables is called a context.

An oparray declaration shares the syntax of a signal array declaration, substituting the name of an opcode for the name of the variable. The declaration includes a state specifier that sets the number of contexts created.

The state specifier may be an integer greater than zero. Alternatively, the state specifier may be keywords inchannels or outchannels, setting the number of contexts equal to the width of the input audio port or output audio port of the instrument, respectively.

An oparray declaration may not occur in the global block. Only one oparray may be declared per opcode type per instrument.

Oparray Calls

Oparray calls are opcode calls that use one of the contexts of an oparray declaration. Syntactically, oparray calls add an bracketed index element before the parameter list, that select the context for the call.

The right panel shows example oparray calls as part of the stereo delay line example.

Oparray calls have the rate of the referenced opcode. The rate of the index expression may not be faster than the rate of the opcode.

fracdelay


aopcode fracdelay(ksig method
		  [, asig p,
		     asig in])


Fracdelay implements multi-tap
delay lines. It is meant to be
used with the oparray construct,
so that several oparray fracdelay
calls may access the same set of
internal variables.

Fracdelay has different behaviors,
based on the value of the method
parameter, which may take on 
integral values between 1 and 5.


Initialize (method == 1):

This method creates internal
variables for a delay-line that
is p seconds long, and sets all
delay-line values to zero. The
number of locations in the delay
line is floor(p*s_rate). Returns 0.


Tap (method == 2):

This method returns the current
value in the delay line at 
position p seconds. If necessary,
interpolation is done between
delay-line taps, using the
interpolation method set by
the global parameter interp. 


Set (method == 3):

This method sets the delay-line
tap at position floor(p*s_rate)
to the value of parameter in.
Returns 0.


Sum (method == 4):

This method adds the value of
parameter in to the delay-line
tap at position floor(p*s_rate).
Returns new delay-line tap value.


Shift (method == 5):

This method shifts the delay line
forward one sample, shifting 0 into
the beginning of the delay line.
Returns the value shifted off 
the end.


Slib defines the constants
FRAC_INIT, FRAC_TAP, FRAC_SET,
FRAC_SUM, and FRAC_SHIFT to use
as the method parameter in 
fracdelay calls.


Oparray Example


// a stereo delay line

instr delayline(dtime)

{
  asig first, outval[2];
  oparray fracdelay[2];

  // on first pass, create
  // space for delay lines

  if (!first)
    {
      first = 1;
      fracdelay[0](1,dtime);
      fracdelay[1](1,dtime);
    }

  // do shift, collect output

  outval[0] = fracdelay[0](5);
  outval[1] = fracdelay[1](5);

  // insert into front of line

  fracdelay[0](3,0,input[0]);
  fracdelay[1](3,0,input[1]);

  // return result

  output(outval);

}

allpass and comb

The core opcodes allpass and comb are normative implementations of recirculating filter structures. These filters are useful building blocks for reverberation models, and for certain sound effects. These opcodes are also useful for creating waveguide structures for physical instrument models.

The right panel shows the header syntax and exact implementation for allpass and comb.

Both opcodes use a shift register delay line structure to hold filter state. The parameter t (units of seconds) sets the time delay of the line, assuming the opcode is called once per a-pass.

On the first call to allpass and comb, the delay line is created, and all values are initialized to zero.

On each call to these opcodes, the delay line is shifted one sample forward, and the return value and delay line insertion value is computed as described on the right panel.

Note that the gain parameter for allpass and comb is i-rate. To create a comb or allpass filter structure with a temporally modulated gain, use the delay opcode as a starting point for writing the filter structure directly in SAOL.

allpass

aopcode allpass(asig in, 
                ivar t, 
                ivar gain)

    ------------------
    | floor(t*srate) |
x-- |     delay      |--y
    |     units      |
    ------------------

On first call, initialize
delay line shift register
to zero.

On each call, return

out = y - gain*in

and insert

x = out*gain + in

into delay line.


comb

aopcode comb(asig in, 
             ivar t, 
             ivar gain)

    ------------------
    | floor(t*srate) |
x-- |     delay      |--y
    |     units      |
    ------------------

On first call, initialize
delay line shift register
to zero.

On each call, return y,
and insert

x = in + gain*y

into delay line.

reverb, chorus and flange

The allpass and comb core opcodes are useful as normative building blocks for modeling room reverbation.

The allpass and comb opcodes are also useful for simulating the sound effect created when several detuned versions of the same sustained sound are played simultaneously (called chorusing) and for simulating the flanging effect created by mixing a sound with a delayed version of itself.

An alternative normative approach to reverb and effects processing is to use the the fracdelay and delay opcodes as building blocks for recirculating filter designs.

The non-normative opcodes flange, chorus, and reverb provide convenient access to delay-based effects at a high level of abstraction for prototyping purposes. The right panel shows the header syntax for these opcodes.

These opcodes return the effected version of the signal provided by the a-rate parameter in. The opcode return value should be added to in by the programmer to create the complete effect. These opcodes are designed to be called once per a-pass.

Like all core opcodes, flange, chorus, and reverb return a scalar signal, producing a monophonic version of the effect.

chorus and flange

Chorus and flange effects are delay lines with smoothly varying delays. Chorus effects occur in the 20-40 millisecond range, and flange effects occur in the 1-10 millisecond range.

The flange and chorus opcode do not let the user specify the absolute delay used in the effect, and as a result these opcodes are non-normative. However, the rate and depth parameters specify the delay variation for the flange and chorus opcodes.

The k-rate depth parameter sets the excursion from the mean time delay, as a percentage from 0 to 100 percent. The k-rate rate parameter sets the frequency, in Hertz, of the low-frequency oscillator (LFO) that modulates the delay time about the mean.

For example, a flanger with a 2 ms mean delay, a 50 percent depth, and a sinusoidal LFO with a 5 Hz rate has a time delay that modulates between 1 ms and 3 ms sinusoidally, 5 times a second.

Note that the LFO waveform shape for the flange and chorus opcodes is non-normative.

reverb

The parameters for the reverb core opcode correspond to sonic characteristics of the reverberation sound. The reverberation algorithm is not specified by the standard.

If the reverb opcode is called with a single i-rate control parameter f0, it is taken as the RT60 of the full-bandwidth reverberation signal. An RT60 value is the time it takes for an impulse input signal to fall in amplitude by 60dB.

Alternatively, the reverb opcode may be called with an arbitrary number of pairs of i-rate control parameters (f0, r0), (f1, r1) .... In this case, each r value sets the RT60 time, in seconds, at a particular signal frequency f, in Hz.

Next section: Part IV/3: Signal Processing Core Opcodes

chorus and flange


aopcode chorus(asig in,
               ksig rate, 
	       ksig depth)


aopcode flange(asig in,
               ksig rate, 
               ksig depth)



rate: the rate of modulation of the
time delay, in Hz.

depth: the depth of the modulation,
in percent. For example, a flanger
effect typically uses a 15 ms delay.
A depth of 50 percent implies the
delay time varies from 7.5ms to 
22.5ms, at a rate determined by 
the rate parameter. note the waveform
is non-normative, as is the absolute
delay.

note that depth is specified as a
number between 0 and 100, not as a
number between 0.0 and 1.0.

reverb


aopcode reverb(asig in, ivar f0
               [, ivar r0, 
                  ivar f1, 
	          ivar r0 ...])



with no optional parameters:

f0: sets the decay time, in 
seconds, for an input impulse
to fall in amplitude 60dB.
known as the RT60.


with optional parameters:

fk, rk: rk is the RT60 of the
reverb (in seconds) at the 
frequency fk (in Hertz). 

last parameter must be an r,
not an f.


Copyright 1999 John Lazzaro and John Wawrzynek.