<<

module_asrc

The Asynchronous Sample Rate converter is capable of deleting or inserting samples in arbitrary places in the input stream. This operation can be used when an incoming signal has to be synchronised with a local clock. By nature this operation introduces harmonic distortion that can be minimised by setting the filter to a high upsampling rate and high order.

There are two interfaces to the ASRC module. The first interface is a simple interface that manages the deletion and insertion of samples at arbitrary places. After initialisation, a single function asrcFilter() is called that takes care of buffer management and interpolation.

The second interface is slightly more complex but enables continuous interpolation of a signal; but buffer management has to be taken care of by the caller: asrcContinuousBuffer() adds a sample to the buffer and asrcContinuousInterpolate() interpolates a sample given a fractional position. The fractional position is a number between 0 and 1 inclusive. If, for example, the stream is too fast, the caller will increase the fractional position gradually, and when it reaches 1 an extra sample should be added to the buffer, and the fractional position be reset to 0. Similarly, rather than decreasing the fractional position to below zero, it should wrap back to 1 and an extra interpolation should take place.

General API

Configuration defines

ASRC_ORDER

This sets the number of samples over which to smooth the signal. A higher value creates less audible artefacts, but increases latency and computational requirements linearly. Must be a power of 2 to simplify buffer management.

ASRC_UPSAMPLING

This sets the number of steps over which the lost/added sample is generated. The filter can only insert or delete a sample once during the upsampling period. The higher the value, the lower the noise floor. Higher values require more memory (the coefficient array is of size ASRC_ORDER * ASRC_UPSAMPLING). ASRC_UPSAMPLING should be a power of 2 in order to simplify the fractional sample location used by asrcContinuousInterpolate()

The default values for ASRC_ORDER and ASRC_UPSAMPLING are 8 and 128. For each combination a table of coefficients is required. Tables are defined as part of the module (in coeffs.xc) for the following combinations:

  • 4 and 256
  • 4 and 128
  • 8 and 128
  • 8 and 64
  • 16 and 64

To support other combinations, compute the coefficients for a low-pass FIR filter (using the makefir program in this repo) with the following parameters:

  • Corner frequency: -low 1
  • Sampling rate: -fs 2 * ASRC_UPSAMPLING
  • Number of taps: -n ASRC_UPSAMPLING * ASRC_ORDER + 1
  • Scale value: -one 16777216 * ASRC_UPSAMPLING

Delete the second half of the generated values, (the filter will be symmetrical) so that you are left with (ASRC_UPSAMPLING * ASRC_ORDER)/2 + 1 coefficients, and so that the last value of the array is 16777216. Add this array to an appropriate #elif in coeffs.xc

Types

asrcState

Structure that is used to store the state of the converter.

One structure should be declared for each channel that the converter is executed on. The internals of this structure are relevant only inside the converter and should not be relied upon by the caller.

Structure Members:

int wr
points one above the last value written ::
Current index in historic sample value,
int firStart

The first point of the FIR to use.

int state

Inserting, Deleting, or Neither.

int buffer

historic sample values

Functions

void asrcInit(struct asrcState &state)

Function that initialises the asynchronous sample rate converter.

This resets the state and should be called once for each declared.

Parameters:
  • state – buffer structure containing past sample values for interpolation

Simple API

Functions

int asrcFilter(int sample, int diff, struct asrcState &state)

Function that produces a new sample, possibly interpolating.

To be called on every sample. Set the parameter to -1 or +1 to indicate that a sample is to be deleted or inserted. Anytime that this function is called with a request to delete or insert a sample, at least ASRC_UPSAMPLING+1 calls should be made with set to 0 for the interpolation to complete.

When is -1, the return value of the function should be ignored, this accounts for the deleted sample. When is +1, the input sample to the function will be ignored, this accounts for the inserted sample.

Parameters:
  • sample – current sample value. Ignored if is +1.
  • diff – value to indicate that a sample shall be deleted or inserted from the stream. When -1, a value shall be deleted and the return value of this function should be ignored. When +1 a value shall be inserted into the stream, and the sample passed into the function will be ignored.
  • state – buffer structure containing past sample values for interpolation
Returns:

an interpolated sample value. To be ignored if is -1.

Example

A simple example reclocks an input stream to a given wordclock. The assumption are that both input stream and wordclock are stable, and almost the same frequency. A sample is added or deleted when the stream runs out of sync too far with the word clock

#define MAXDIFF 4

// This example is untested... Ought to be...
void reclockExample(port inLRclk, chanend inSlave, chanend outSlave) {
    struct asrcState asrcState;
    int sample, v;
    int diff = 0;
    int lr = 0;
    asrcInit(asrcState);
    while(1) {
        select {
        case inLRclk when pinsneq(lr) :> lr:
            if (lr) {
                if (diff > MAXDIFF) {                   // Difference too large - add a sample in other stream.
                    v = asrcFilter(0, 1, asrcState);
                    outSlave <: v;
                } else {
                    diff++;
                }
            }
            break;
        case inSlave :> sample:
            if (diff < -MAXDIFF) {                     // Difference too large - remove a sample in this stream.
                (void) asrcFilter(sample, -1, asrcState);
            } else {
                v = asrcFilter(sample, 0, asrcState);
                outSlave <: v;
                diff--;
            }
            break;
        }
    }
}

A more complex example has two input streams, and it will delete a sample on either stream when it runs ahead too far.

#define MAXDIST 4

// This example is untested... Ought to be...
void twoStreamExample(chanend inMaster, chanend inSlave, chanend outMaster, chanend outSlave) {
    struct asrcState asrcState;
    int sample, v;
    int diff = 0;
    asrcInit(asrcState);
    while(1) {
        select {
        case inMaster :> sample:
            outMaster <: v;
            if (diff > MAXDIST) {                   // Difference too large - add a sample in other stream.
                v = asrcFilter(0, 1, asrcState);
                outSlave <: v;
            } else {
                diff++;
            }
            break;
        case inSlave :> sample:
            if (diff < -MAXDIST) {                  // Difference too large - remove a sample in this stream.
                (void) asrcFilter(sample, -1, asrcState);
            } else {
                v = asrcFilter(sample, 0, asrcState);
                outSlave <: v;
                diff--;
            }
            break;
        }
    }
}

Performance

The filtering function performs a low pass filter when inserting or deleting, which requires computation linear in ASRC_ORDER. As an indication, when ASRC_ORDER = 4, the worst case execution path is a double call to the filter function (to delete a sample), this takes 170 thread cycles or 3.4 us at 50 MIPS. This worst case is guaranteed to happen only once per deleted sample, and typical performance when filtering is 110 thread cycles or 2.2 us at 50 MIPS. Hence, if this function is called just prior to delivering an audio sample in a 48 kHz stream, then a single thread at 50 MIPS can filter around 6 streams at 48 kHz, or 3 streams at 96 kHz. If used in a system with a small buffer, 9 streams can be processed at 48 kHz.

Distortion

Below we show the frequency analysis of a 1kHz sinewave that has been sped up using the Asynchronous Sample Rate converter with upsampling rates of between 64 and 250, and filters of orders 4, 8, and 16. This experiment used a 48 kHz sample rate at 24 bits. Note that order 4 will be sufficient for many applications.

_images/100ppm-256-s.png

conversion to slightly faster clock, 256 x upsampling

_images/100ppm-128-s.png

conversion to slightly faster clock, 128 x upsampling

_images/100ppm-64-s.png

conversion to slightly faster clock, 64 x upsampling

Continuous API

Functions

void asrcContinuousBuffer(int sample, struct asrcState &state)

UNTESTED Continuous interface to ASRC: add a sample to the buffer.

This must be called once for every input sample. Occasionally an extra or missing call to asrcContinuousInterpolate() compensates for the asynchronous nature

Parameters:
  • sample – current sample value.
  • state – buffer structure containing past sample values for interpolation
int asrcContinuousInterpolate(int frac, struct asrcState &state)

UNTESTED Continous interface to ASRC: called once for each sample to be produced on the output stream.

The value is computed given the previous ASRC_ORDER samples in the buffer that have been added with asrcContinuousBuffer(). An interpolated value is returned, notionally between ASRC_ORDER+1 and ASRC_ORDER samples in the past. A fractional value of 0.0 indicates sample ASRC_ORDER+1 in the past, 1.0 indicates ASRC_ORDER samples in the past, 0.5 a value exactly inbetween, etc.

Parameters:
  • frac – fractional sample - a number between 0 and 1 inclusive, where 1 is represented by ASRC_UPSAMPLING.
  • state – buffer structure containing past sample values for interpolation
Returns:

the interpolated sample value.

Example

Performance

The interpolate function always performs a low pass filter, which requires computation linear in ASRC_ORDER. The Buffer function is to be called zero, twice, or once during this period; depending on whether the fraction went above one, below zero, or stayed between 0 and 1. As an indication, when ASRC_ORDER = 4, the worst case execution path is ...

Distortion

Below we show the frequency analysis of a 1kHz sinewave that has been sped up using the Asynchronous Sample Rate converter using the continuous interface. Upsampling rates are between 64 and 250, and filters of orders 4, 8, and 16. This experiment used a 48 kHz sample rate at 24 bits.

_images/100ppm-256-c.png

conversion to slightly faster clock, 256 x upsampling

_images/100ppm-128-c.png

conversion to slightly faster clock, 128 x upsampling

_images/100ppm-64-c.png

conversion to slightly faster clock, 64 x upsampling