.. _sw_usb_audio:               https://www.xmos.com/develop/usb-multichannel-audio/#technical-documents
.. _lib_audio_dsp:              https://github.com/xmos/lib_audio_dsp
.. _lib_xua:                    https://github.com/xmos/lib_xua
.. _XTC:                        https://www.xmos.com/software-tools/
.. _CMake:                      https://cmake.org/download/
.. _Python:                     https://www.python.org/
.. _XK-AUDIO-316-MC-AB:         https://www.xmos.com/xk-audio-316-mc-ab
.. _XCommon CMake:              https://github.com/xmos/xcommon_cmake/releases/download/v1.3.0/xcommon_cmake.pdf
.. _Jupyter:                    https://jupyter.org/
.. _Python virtual environment: https://docs.python.org/3/library/venv.html#venv-def
.. _lib_xcore:                  https://www.xmos.com/documentation/XM-014363-PC-LATEST/html/tools-guide/tools-ref/libraries/lib-xcore-api/lib-xcore-api.html
.. _XTC getting started:        https://www.xmos.com/xtc-install-guide
.. _Graphviz:                   https://graphviz.org/download/

AN02015: Run-time DSP control in a USB Audio Application
========================================================

Introduction
------------

.. note::

  Some software components in this tool flow are prototypes and will be updated in Version 2 of the library. The underlying Digital Signal Processing (DSP) blocks are however fully functional. Future updates will enhance the features and flexibility of the design tool.

This application note is complementary to "AN02014: Integrating a Generated Audio DSP Pipeline into a USB
Audio Application". The goal is to show how to read and write the DSP configuration at run-time;
building dynamic features into the application. The application associated with this note makes use
of the buttons and LEDs on the XK-AUDIO-316-MC-AB_ to implement an active speaker application with volume control
and bass boost. The DSP pipeline also computes the RMS power of the signal and displays this
on the 4 LEDs to provide a simple VU meter.

Getting Started
---------------

Requirements
^^^^^^^^^^^^

Before running this application note ensure the following applications are installed on your system:

* XTC_ 15.3.1

* CMake_ >= 3.21.0

* Python_ 3.12

* Graphviz_, ensuring the `dot` executable available on your PATH.

The following hardware is required:

* XK-AUDIO-316-MC-AB_

* 2 Micro-USB cables

* An audio device with a 3.5 mm jack (e.g. speakers or headphones)

Running the example
^^^^^^^^^^^^^^^^^^^

First, connect the XK-AUDIO-316-MC-AB_ to your computer with both the "DEBUG" and "USB DEVICE"
Micro-USB ports as shown in :numref:`mcab-photo-label`.

.. _mcab-photo-label:

.. figure:: ../mcab-speakers.jpeg
   :width: 100%

   XK-AUDIO-316-MC-AB with both USB cables connected and a pair of speakers connect to OUT 1/2

Once connected follow these steps:

#. Open a terminal and activate the XTC_ enviroment (see `XTC getting started`_). Optionally, create a `Python virtual environment`_ and activate it.

#. Get the source code for this app note from `<https://www.xmos.com/application-notes/>`_

#. Navigate to the root directory of this app note and install the Python_ requirements:

   .. code-block:: bash

       pip install -Ur requirements.txt
       
       # for users in China, a pip mirror will give faster downloads
       pip install -Ur requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

#. Start the Jupyter_ notebook from the ``app_an02015`` directory. Jupyter_ Notebook is an interactive Python editor which was installed via the pip command in the previous step.

   .. code-block:: bash

       cd app_an02015
       jupyter notebook

#. If this does not automatically open a browser window, then copy the URL from the output of ``jupyter`` that
   starts with ``http://127.0.0.1`` and navigate to it in your web browser.

#. Open "dsp.ipynb" on the web interface by double-clicking on the file name.

#. Execute all the cells in the notebook by selecting "Run all cells" from the "Run" menu.

This final step will display a diagram that represents the provided simple DSP pipeline. It will then generate the xcore
source code, build the application, and run it on the connected device. A screenshot of the notebook after successful
completion is shown in :numref:`notebook-dia-label`. The device will appear on the connected computer as a stereo USB
audio device named “XMOS xCORE.ai MC (UAC2.0)”, supporting recording and playback.

When audio is played to the USB device from the host PC, the output can be heard by connecting a speaker or headphones to the "OUT 1/2"
jack on the XK-AUDIO-316-MC-AB_. Button 0 will toggle bass boost; button 1 increases the volume; button 2 decreases the
volume. The LEDs will show the signal power level, when no audio is playing all the LEDs will be off.

.. caution::
   To get all 4 LEDs to illuminate the signal will have to be very loud! Take special care when
   connecting headphones to the XK-AUDIO-316-MC-AB_.

.. _notebook-dia-label:

.. figure:: ../notebook-screenshot.png
   :width: 70%

   The notebook after running successfully

Application Overview
--------------------

The application accompanying this note is largely the same as that in AN02014. It is an sw_usb_audio_ application with
a DSP pipeline generated using the generation tools from lib_audio_dsp_. The DSP pipeline and |I2S| both run on tile 1.
On the XK-AUDIO-316-MC-AB_ the buttons and LEDs are connected to ports 4E and 4F respectively, both on
tile 0. The run-time control guide associated with lib_audio_dsp_ describes the DSP control interface that can be used
to modify the DSP configuration. The main constraint is that this must be done from the same tile as the DSP threads.
Accessing the ports for the buttons and LEDs must be done the tile that they are connected to, tile 0. Hence,
this application required an additional 2 threads compared to the base application from AN02014. These threads are show
in :numref:`thread-dia-label`.

.. _thread-dia-label:

.. figure:: ../threads.drawio.png
   :width: 100%

   System thread diagram

The DSP pipeline for this application is shown in :numref:`dsp-dia-label`. It contains the following stages, all of
which are stereo:

* Volume control (vol_ctl): Adjust the volume of the input signal.

* 8 band parametric EQ (peq): Configurable PEQ to adjust for the listeners preference or to account for the speaker
  response.

* Low pass filter (lpf), fixed gain (bass_boost) and limiter (bass_sw): This path takes the low frequencies and applies a boost to them.
  The fixed gain sets the magnitude of the boost. The output limiter reduces the level of the bass boost for large signals, avoiding overloading the loudspeaker at the output.
  This output limiter can be used to adjust the level of the bass boosted signal before it is summed with the bypass signal.
  Adjusting the limiter threshold can have the effect of enabling or disabling the bass boost effect.
  A low limiter threshold will mean the bass boost path is always held to a small signal level.

* Adder: combines bass boost signal with bypass signal to create a signal with boosted bass and unmodified higher
  frequencies.

* Limiter (lim): Compresses the output of the DSP pipeline to prevent clipping. The limiter computes an envelope
  of the signal which can be read out via its ``envelope`` parameter. This functionality will be used
  to determine which LEDs to light.

.. _dsp-dia-label:

.. figure:: ../dsp.drawio.png
   :width: 70%

   DSP graph diagram, stage labels for use in control shown in brackets. Dashed arrows indicate which stages will
   be accessed for the user interface

Creating the Control and GPIO Threads
-------------------------------------

In this application, the two thread entry functions (see :numref:`thread-dia-label`) are ``gpio_task``, defined in
"gpio_task.c", and ``dsp_control``, defined in "app_dsp.c". Both functions take a chanend as their only parameter which
is used to communicate between the two tiles. To spawn these threads the first step is to define the new channel in
user_main.h, and then to  place the gpio function on tile 0, passing it one end of the channel. The other end of the
channel is passed to  ``dsp_thread`` on tile 1. Below is an excerpt from "user_main.h":

.. literalinclude:: ../../app_an02015/src/extensions/user_main.h
   :language: c
   :start-after: // start of user main macros
   :end-before: // end of user main macros

The GPIO Thread
^^^^^^^^^^^^^^^

The GPIO thread, in "gpio_task.c" makes use of lib_xcore_ to implement some basic button logic. It also uses a hardware
timer to trigger a periodic query of the current signal level to update the LEDs.

The DSP Control Thread
^^^^^^^^^^^^^^^^^^^^^^

Overview of control features
****************************

The DSP control thread is spawned in parallel with ``adsp_auto_pipeline_main`` in the top level DSP function ``dsp_thread``.
``dsp_thread`` is defined in app_dsp.c and shown in the excerpt below. This structure ensures that the control thread and
the DSP thread will always be on the same tile.

.. literalinclude:: ../../app_an02015/src/extensions/app_dsp.c
   :language: c
   :start-after: // start dsp_thread
   :end-before: // end dsp_thread

The DSP control thread entry function ``dsp_control``, defined in app_dsp.c, is shown below. This function implements a
simple channel based server protocol that interfaces with the GPIO thread on the other tile. This means that the thread
is idle until it receives a communication from the GPIO thread, then it reads from the channel to determine
what work is required. In this case the GPIO thread chooses from one of 4 operations:

#. ``BASS_BOOST_SW``: Toggle the bass boost. The GPIO thread will request this when the bass boost button is pressed. When
   this request is received it triggers the DSP control thread to update the threshold in the ``bass_sw`` limiter.

#. ``VOLUME_UP``: Increase the volume. This will be called when the volume up button is pressed. On receiving this
   request the DSP control thread will read the current target volume from the DSP volume control stage. Then the new
   volume is calculated and written to the DSP stage.

#. ``VOLUME_DOWN``: Decrease the volume. This will be called when the volume down button is pressed. The logic for
   volume down is the same as volume up.

#. ``GET_VU_LEVELS``: The GPIO thread uses a hardware timer to call this periodically. On reception the DSP control
   thread will read the energy level from the ``lim`` limiter and convert this into a port value for the LEDs. This
   LED port value is sent back over the channel to the GPIO thread.


.. literalinclude:: ../../app_an02015/src/extensions/app_dsp.c
   :language: c
   :start-after: // start dsp_control
   :end-before: // end dsp_control


The control interfaces used to read and write to the DSP stages are discussed next.

Bass Boost
**********

The bass boost functionality is implemented by adjusting the threshold of a limiter which compresses the bass boosted signal. In
this pipeline the bass boost limiter has been given the label "bass_sw" and can be accessed via the identifier
``bass_sw_stage_index``. The limiter is defined in the Jupyter_ notebook as an instance of ``LimiterPeak``. The
control commands for a ``LimiterPeak`` are documented in the lib_audio_dsp_ component guide, they include
``CMD_LIMITER_PEAK_THRESHOLD``.

The excerpt below shows the threshold being toggled between ``BASS_BOOST_ON`` and ``BASS_BOOST_OFF``. These values
are examples and can be updated to the preferences of the DSP designer.

The function ``adsp_write_module_config`` is used to upate the parameter. Details of how this interface works can be found
in the lib_audio_dsp documentation. In brief, an ``adsp_stage_control_cmd_t`` must be filled in and then
``adsp_write_module_config`` must be called until it returns ``ADSP_CONTROL_SUCCESS``.

.. literalinclude:: ../../app_an02015/src/extensions/app_dsp.c
   :language: c
   :start-after: // start bass boost
   :end-before: // end bass boost

Volume Control
**************

The volume control is implemented via the ``VolumeControl`` stage which has a parameter
``CMD_VOLUME_CONTROL_TARGET_GAIN``. The implementation is more complex than bass boost and takes the following
steps:

#. Read the current target gain from the volume control stage using its label ``vol_ctl_stage_index`` and the function
   ``adsp_read_module_config``.

#. Apply a precomputed gain to this value for volume up or volume down. In this case the values of ``VOLUME_UP_INC``
   and ``VOLUME_DOWN_INC`` are +3 dB and -3 dB respectively, converted to linear gain values in fixed point.

#. Write the new value back to the volume control stage with ``adsp_write_module_config``.

.. literalinclude:: ../../app_an02015/src/extensions/app_dsp.c
   :language: c
   :start-after: // start volume
   :end-before: // end volume

VU Levels
*********

The objective of the VU meter (volume unit meter) is to light more LEDs when the signal has more energy. The desired
scale of the VU meter is decibels. To determine the level of the signal, the ``CMD_LIMITER_PEAK_ENVELOPE`` parameter is
read from the stage with the label ``lim``. This value is a signal envelope level in fixed point format. The thresholds for
each LED are stored in LED0_TH, LED1_TH, LED2_TH, and LED3_TH which have been computed from the values -40 dB, -30 dB,
-20 dB and -10 dB respectively.

.. literalinclude:: ../../app_an02015/src/extensions/app_dsp.c
   :language: c
   :start-after: // start vu
   :end-before: // end vu

References
----------

* sw_usb_audio_
* lib_audio_dsp_
* lib_xua_
* XTC_
* XK-AUDIO-316-MC-AB_
* `XCommon CMake`_
* Jupyter_
* lib_xcore_

Support
-------

For all support issues please visit http://www.xmos.com/support
