Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# A typical USB Audio application

This section provides a walk through of a typical USB Audio application. Where specific examples are required, code is used from the application for XK-AUDIO-316-MC (`app_usb_aud_xk_316_mc`).

Note

The applications in `sw_usb_audio` use the “Codeless Programming Model” as documented in `lib_xua`. Briefly, the `main()` function is used from `lib_xua` with build-time defines in the application configuring the framework provided by `lib_xua`. Various functions from `lib_xua` are then overridden to provide customisation. See `lib_xua` for full details.

Each application directory contains:

> 1. A `CMakeLists.txt`

> 2. A `src` directory

The `src` directory is arranged into two directories:

> 1. A `core` directory containing source items that must be made available to the USB Audio framework i.e. `lib_xua`.

> 2. An `extensions` directory that includes extensions to the framework such as external device configuration etc

The `core` folder for each application contains:

> 1. A `.xn` file to describe the hardware platform the application will run on

> 2. An optional header file to customise the framework provided by `lib_xua` named `xua_conf.h`

## `lib_xua` configuration

The `xua_conf.h` file contains all the build-time `#defines` required to tailor the framework provided by `lib_xua` to the particular application at hand. Typically these override default values in `xua_conf_default.h` in `lib_xua/api`.

Firstly in `app_usb_aud_xk_316_mc` the `xua_conf.h` file sets defines to determine overall capability. For this application most of the optional interfaces are disabled by default. This is because the applications provide a large number build configurations in the `CMakeLists.txt` enabling various interfaces. For a product with a fixed specification this almost certainly would not be the case and setting in this file may be the preferred option.

Note that `ifndef` is used to check that the option is not already defined in the `CMakeLists.txt`.

    /* Enable/Disable MIDI - Default is MIDI off */
    #ifndef MIDI
    #define MIDI               (0)
    #endif

    /* Enable/Disable S/PDIF output - Default is S/PDIF off */
    #ifndef XUA_SPDIF_TX_EN
    #define XUA_SPDIF_TX_EN    (0)
    #endif

    /* Enable/Disable S/PDIF input - Default is S/PDIF off */
    #ifndef XUA_SPDIF_RX_EN
    #define XUA_SPDIF_RX_EN    (0)
    #endif

    /* Enable/Disable ADAT output - Default is ADAT off */
    #ifndef XUA_ADAT_TX_EN
    #define XUA_ADAT_TX_EN     (0)
    #endif

    /* Enable/Disable ADAT input - Default is ADAT off */
    #ifndef XUA_ADAT_RX_EN
    #define XUA_ADAT_RX_EN     (0)
    #endif

    /* Enable/Disable Mixing core(s) - Default is on */
    #ifndef MIXER
    #define MIXER              (1)
    #endif

    /* Set the number of mixes to perform - Default is 0 i.e mixing disabled */
    #ifndef MAX_MIX_COUNT
    #define MAX_MIX_COUNT      (0)
    #endif

    /* Audio Class version - Default is 2.0 */
    #ifndef AUDIO_CLASS
    #define AUDIO_CLASS        (2)
    #endif

Next, the file defines properties of the audio channels including counts and arrangements. By default the application provides 8 analogue channels for input and output.

The total number of channels exposed to the USB host (set via `NUM_USB_CHAN_OUT` and `NUM_USB_CHAN_IN`) are calculated based on the audio interfaces enabled. Again, this is due to the multiple build configurations in the application `CMakeLists.txt` and likely to be hard-coded for a product.

    /* Number of I2S channels to DACs*/
    #ifndef I2S_CHANS_DAC
    #define I2S_CHANS_DAC      (8)
    #endif

    /* Number of I2S channels from ADCs */
    #ifndef I2S_CHANS_ADC
    #define I2S_CHANS_ADC      (8)
    #endif

    /* Number of USB streaming channels - by default calculate by counting audio interfaces */
    #ifndef NUM_USB_CHAN_IN
    #define NUM_USB_CHAN_IN    (I2S_CHANS_ADC + 2*XUA_SPDIF_RX_EN + 8*XUA_ADAT_RX_EN)  /* Device to Host */
    #endif

    #ifndef NUM_USB_CHAN_OUT
    #define NUM_USB_CHAN_OUT   (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN + 8*XUA_ADAT_TX_EN)  /* Host to Device */
    #endif

    /*** Defines relating to channel arrangement/indices ***/

Channel indices/offsets are set based on the audio interfaces enabled. Channels are indexed from 0. Setting `SPDIF_TX_INDEX` to 0 would cause the S/PDIF channels to duplicate analogue channels 0 and 1. Note, the offset for analogue channels is always 0.

    /* Channel index of S/PDIF Tx channels: separate channels after analogue channels (if they fit) */
    #ifndef SPDIF_TX_INDEX
        #if (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN) <= NUM_USB_CHAN_OUT
            #define SPDIF_TX_INDEX   (I2S_CHANS_DAC)
        #else
            #define SPDIF_TX_INDEX   (0)
        #endif
    #endif

    /* Channel index of S/PDIF Rx channels: separate channels after analogue channels */
    #ifndef SPDIF_RX_INDEX
    #define SPDIF_RX_INDEX     (I2S_CHANS_ADC)
    #endif

    /* Channel index of ADAT Tx channels: separate channels after S/PDIF channels (if they fit) */
    #ifndef ADAT_TX_INDEX
        #define ADAT_TX_INDEX    (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN)
    #endif

    /* Channel index of ADAT Rx channels: separate channels after S/PDIF channels */
    #ifndef ADAT_RX_INDEX
    #define ADAT_RX_INDEX      (I2S_CHANS_ADC + 2*XUA_SPDIF_RX_EN)
    #endif

The file then sets some frequency related defines for the audio master clocks and the maximum sample-rate for the device.

    /* Master clock defines (in Hz) */
    #ifndef MCLK_441
    #define MCLK_441           (512*44100)   /* 44.1, 88.2 etc */
    #endif

    #ifndef MCLK_48
    #define MCLK_48            (512*48000)   /* 48, 96 etc */
    #endif

    /* Minumum sample frequency device runs at */
    #ifndef MIN_FREQ
    #define MIN_FREQ           (44100)
    #endif

    /* Maximum sample frequency device runs at */
    #ifndef MAX_FREQ
    #define MAX_FREQ           (192000)
    #endif

Due to the multi-tile nature of the xcore architecture the framework needs to be informed as to which tile various interfaces should be placed on, for example USB, S/PDIF etc.

    #define XUD_TILE           (0)
    #define PLL_REF_TILE       (0)

    #define AUDIO_IO_TILE      (1)
    #define MIDI_TILE          (1)

The file also sets some defines for general USB IDs and strings. These are set for the XMOS reference design but vary per manufacturer:

    #define VENDOR_ID          (0x20B1) /* XMOS VID */
    #ifndef PID_AUDIO_2
    #define PID_AUDIO_2        (0x0016)
    #endif
    #ifndef PID_AUDIO_1
    #define PID_AUDIO_1        (0x0017)
    #endif

    #ifndef DFU_PID
    #if (AUDIO_CLASS == 1)
    #define DFU_PID             (0xD000 + PID_AUDIO_1)
    #else
    #define DFU_PID             (0xD000 + PID_AUDIO_2)
    #endif
    #endif

    #define PRODUCT_STR_A2     "XMOS xCORE.ai MC (UAC2.0)"
    #define PRODUCT_STR_A1     "XMOS xCORE.ai MC (UAC1.0)"

For a full description of all the defines that can be set in `xua_conf.h` see Configuration defines.

## User functions

In addition to the `xua_conf.h` file, the application needs to provide implementations of some overridable user functions in `lib_xua` to provide custom functionality.

For `app_usb_aud_xk_316_mc` the implementations can be found in `src/extensions/audiohw.xc` and `src/extensions/audiostream.xc`

The two functions it overrides in `audiohw.xc` are `AudioHwInit()` and `AudioHwConfig()`. These are run from `lib_xua` on startup and sample-rate change respectively. Note, the default implementations in `lib_xua` are empty. These functions have parameters for sample frequency, sample depth, etc.

In the case of `app_usb_aud_xk_316_mc` these functions configure the external DACs and ADCs via an I²C bus and configure the xcore secondary PLL to generate the required master clock frequencies.

Due to the complexity of the hardware on the XK-AUDIO-316-MC the source code is not included here.

The application also overrides `UserAudioStreamState()`. This function is called from `lib_xua` when the audio stream to the device is started or stopped.

The application uses this function to enable/disable the on board LEDs based on whether an audio stream is active (input or output).

    // Copyright 2022-2025 XMOS LIMITED.
    // This Software is subject to the terms of the XMOS Public Licence: Version 1.
    #include <platform.h>

    on tile[0]: out port p_leds = XS1_PORT_4F;

    void UserAudioStreamState(int inputActive, int outputActive)
    {
        if(inputActive || outputActive)
        {
            /* Turn all LEDs on */
            p_leds <: 0xF;
        } else
        {
            /* Turn all LEDs off */
            p_leds <: 0x0;
        }
    }

Note

A media player application may choose to keep an audio stream open and active and simply send zero data when paused.

## The main program

The `main()` function is the entry point to an application. In the XMOS USB Audio Reference Design software it is shared by all applications and is therefore part of the framework.

This section is largely informational as most developers should not need to modify the `main()` function. `main()` is located in `main.xc` in `lib_xua`, this file contains:

> - A declaration of all the ports used in the framework. These clearly vary depending on the hardware platform the application is running on.

> - A `main()` function which declares some channels and then has a `par` statement which runs the required cores in parallel.

Full documentation can be found in lib_xua.

The first items in the `par` include running tasks for the Endpoint 0 implementaton and buffering tasks for audio and endpoint buffering:

                {
                    unsigned x;
                    thread_speed();

                    /* Attach mclk count port to mclk clock-block (for feedback) */
                    //set_port_clock(p_for_mclk_count, clk_audio_mclk);
    #if(AUDIO_IO_TILE != XUD_TILE)
                    set_clock_src(clk_audio_mclk_usb, p_mclk_in_usb);
                    set_port_clock(p_for_mclk_count, clk_audio_mclk_usb);
                    start_clock(clk_audio_mclk_usb);
    #else
                    /* AUDIO_IO_TILE == XUD_TILE */
                    /* Clock port from same clock-block as I2S */
                    /* TODO remove asm() */
                    asm("ldw %0, dp[clk_audio_mclk]":"=r"(x));
                    asm("setclk res[%0], %1"::"r"(p_for_mclk_count), "r"(x));
                    /* This clock block is started in audiohub in case we first need to connect other logic
                       e.g. digital Tx to it before starting */
    #endif
                    /* Endpoint & audio buffering cores - buffers all EP's other than 0 */
                    XUA_Buffer(
    #if (NUM_USB_CHAN_OUT > 0)
                               c_xud_out[ENDPOINT_NUMBER_OUT_AUDIO],       /* Audio Out*/
    #endif
    #if (NUM_USB_CHAN_IN > 0)
                               c_xud_in[ENDPOINT_NUMBER_IN_AUDIO],         /* Audio In */
    #endif
    #if (NUM_USB_CHAN_OUT > 0) && ((NUM_USB_CHAN_IN == 0) || defined(UAC_FORCE_FEEDBACK_EP))
                               c_xud_in[ENDPOINT_NUMBER_IN_FEEDBACK],      /* Audio FB */
    #endif
    #ifdef MIDI
                               c_xud_out[ENDPOINT_NUMBER_OUT_MIDI],        /* MIDI Out */ // 2
                               c_xud_in[ENDPOINT_NUMBER_IN_MIDI],          /* MIDI In */  // 4
                               c_midi,
    #endif
    #if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
                               /* Audio Interrupt - only used for interrupts on external clock change */
                               c_xud_in[ENDPOINT_NUMBER_IN_INTERRUPT],
                               c_clk_int,
    #endif
                               c_sof, c_aud_ctl, p_for_mclk_count
    #if (XUA_HID_ENABLED)
                               , c_xud_in[ENDPOINT_NUMBER_IN_HID]
    #endif
                               , c_mix_out
    #if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC)
                               , c_audio_rate_change
        #if (!XUA_USE_SW_PLL)
                               , i_pll_ref
        #else
                               , c_sw_pll
        #endif
    #endif
                        );
                    //:
                }
    #endif

                /* Endpoint 0 Core */
                {
                    thread_speed();
                    XUA_Endpoint0( c_xud_out[0], c_xud_in[0], c_aud_ctl, c_mix_ctl, c_clk_ctl, dfuInterface VENDOR_REQUESTS_PARAMS_);
                }

It also runs a task for the USB interfacing thread (`XUD_Main()`):

                    XUD_Main(c_xud_out, ENDPOINT_COUNT_OUT, c_xud_in, ENDPOINT_COUNT_IN,

The specification of the channel arrays connecting to this driver are described in the documentation for lib_xud.

The channels connected to `XUD_Main()` are passed to the `XUA_Buffer()` function which implements audio buffering and also buffering for other Endpoints.

                    XUA_Buffer(
    #if (NUM_USB_CHAN_OUT > 0)
                               c_xud_out[ENDPOINT_NUMBER_OUT_AUDIO],       /* Audio Out*/
    #endif
    #if (NUM_USB_CHAN_IN > 0)
                               c_xud_in[ENDPOINT_NUMBER_IN_AUDIO],         /* Audio In */
    #endif
    #if (NUM_USB_CHAN_OUT > 0) && ((NUM_USB_CHAN_IN == 0) || defined(UAC_FORCE_FEEDBACK_EP))
                               c_xud_in[ENDPOINT_NUMBER_IN_FEEDBACK],      /* Audio FB */
    #endif
    #ifdef MIDI
                               c_xud_out[ENDPOINT_NUMBER_OUT_MIDI],        /* MIDI Out */ // 2
                               c_xud_in[ENDPOINT_NUMBER_IN_MIDI],          /* MIDI In */  // 4
                               c_midi,
    #endif
    #if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
                               /* Audio Interrupt - only used for interrupts on external clock change */
                               c_xud_in[ENDPOINT_NUMBER_IN_INTERRUPT],
                               c_clk_int,
    #endif
                               c_sof, c_aud_ctl, p_for_mclk_count
    #if (XUA_HID_ENABLED)
                               , c_xud_in[ENDPOINT_NUMBER_IN_HID]
    #endif
                               , c_mix_out
    #if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC)
                               , c_audio_rate_change
        #if (!XUA_USE_SW_PLL)
                               , i_pll_ref
        #else
                               , c_sw_pll
        #endif
    #endif
                        );

A channel connects this buffering task to the audio driver which controls the I²S output. It also forwards and receives audio samples from other interfaces e.g. S/PDIF, ADAT, as required:

                usb_audio_io(
    #if (NUM_USB_CHAN_OUT > 0) || (NUM_USB_CHAN_IN > 0) || XUA_HID_ENABLED || defined(MIDI)
                    /* Connect audio system to XUA_Buffer(); */
                    c_mix_out
    #else
                    /* Connect to XUA_Endpoint0() */
                    c_aud_ctl
    #endif
    #if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE)
                    , c_spdif_tx
    #endif
    #if (MIXER)
                    , c_mix_ctl
    #endif
                    , c_spdif_rx, c_adat_rx, c_clk_ctl, c_clk_int
    #if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1)
                    , dfuInterface
    #endif
    #if (XUA_NUM_PDM_MICS > 0)
                    , c_pdm_pcm
    #endif
    #if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
                    , i_pll_ref
    #endif
    #if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC)
                    , c_audio_rate_change
    #endif
    #if ((XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) && XUA_USE_SW_PLL)
                    , p_for_mclk_count_audio
                    , c_sw_pll
    #endif
                );
            }

Finally, other tasks are created for various interfaces, for example, if MIDI is enabled a core is required to drive the MIDI input and output.

            on tile[MIDI_TILE]:
            {
                thread_speed();
                usb_midi(p_midi_rx, p_midi_tx, clk_midi, c_midi, 0);
            }