The MP4-SA Book: Part IV/2: Filter Core Opcodes
Description: Core opcodes specialized for sound filtering. These core opcodes are a part of the SAOL language of MPEG 4 Structured Audio.
Keywords: MP4-SA, core, opcodes, core opcodes, hipass, lopass, bandpass, bandstop, biquad, allpass, comb, fir, iir, firt, iirt, delay, delay1, fracdelay, reverb, chorus, flange

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

# Part IV/2: Filter Core Opcodes

## Sections Introduction. FIR Filters. Table and parameter coefficients. IIR Filters. Biquad and arbitrary order. Parametric Filters. Classic filter shapes. Integral Delays. Simple delay opcodes. Fractional Delays. Also introduces oparrays. `allpass` and `comb`. Recirculating filters. `reverb`, `chorus` and `flange`. Effects opcodes. | ## 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. Each call provides the opcode with the next input sample, via the parameter `in` Each call returns the next output sample of the filter or delay algorithm. 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.