AWE Core OS 8.B.20 Documentation
AWE Core OS Integration Guide

Table of Contents

Introduction

This User Guide is designed to give an overview on the usage and functionality of the AWE Core OS™ product.

AWE Core OS is a hardware-independent, reconfigurable audio-processing engine that includes over 400 audio-processing modules. From mixers and filters to compressors and FFTs, these modules can be easily combined to create complex signal processing flows. The AWE Core OS engine operates by passing audio through a user-defined sequence of these modules, called a layout, as directed by configuration data at run-time.

AWE Core vs AWE Core OS

While the AWE Core™ product is meant to be a portable library that targets bare-metal applications, AWE Core OS targets applications using a rich OS, such as Linux or Android. AWE Core OS extends the functionality of the AWE Core by taking advantage of features common to these Operating Systems, such as File I/O, TCP/IP Sockets, and multi-threading. Using native components of AWE Core OS, users can easily load layouts from AWBs, record input and output audio streams, process and log TCP/IP tuning command packets, and run complex, multi-sublayout signal processing flows across multiple cores.

Terms and Definitions

Term Definition
API Application Programming Interface
AWB Audio Weaver configuration data binary file
AWD Audio Weaver configuration data design file
AWE Audio Weaver
AWE OS Instance The main AWE Core OS object
AWS Audio Weaver configuration data script file
Clock Divider Property of multi-rate layouts
Layout Audio Weaver signal processing flow
Pump Process audio using a loaded layout
Tuning Interface The interface by which AWE tuning commands/replies are transferred
Sub-Layout Independently processed section of a layout with a unique clock divider
RT Real-time

Additional Documentation

This document will describe how to use various components of AWE Core OS in an audio processing application. For related documentation, see:

API Documentation: AWECoreOS API Doc.

Brief view of the API: See 'AWECoreOS_cheatsheet.pdf' in this package.

Details on underlying concepts and operation: Theory Of Operation

Help with ALSA Devices: Alsa Devices

Tuning protocol details: Tuning Protocol Doc.

Overview of Integration Steps

Here are the basic steps to include AWE Core OS in an application.

  1. Include headers and link libraries in build projects
  2. Define/allocate required variables
  3. Configure and initialize the AWE Core OS instance
  4. Setup a tuning interface
  5. Setup realtime audio
  6. Setup a control interface (optional based on product intent)
  7. Verify proper scheduling/priority.

Include Headers and Link Libraries

Required Header Files

  • AWECoreOS.h
    • This is the main API header file. It includes all of the API functions and structures required for an integration. See the API doc for details. AWECoreOS.h API Doc.
  • StandardDefs.h
  • Errors.h
    • When doing error checking on API calls, negative return values correspond to an error. The error can be determined by using the definitions in Errors.h.
  • ModuleList.h
    • Every AWE Core OS deliverable contains a default ModuleList.h in the 'Include' folder. By default, this file includes a list of all the modules available in the AWE Core OS library. User created modules can be added to this list, or unneeded modules can be removed in order to reduce the size of the application.

Required Libraries

Update application build project to link the following libraries:

  • AWECoreOS
    • This is the main AWE Core OS library. The exact name and extension will vary from platform to platform, but will always have the basename "awecoreos".
    • The library is delivered in both static and shared formats (.a and .so for Linux systems) when possible.
  • System libraries
    • Unix OS's: pthread (POSIX Threads), m (Math), and rt (Realtime Extensions) libraries.

Helper Code / Optional

  • AWECoreUtils (AWECoreUtils.c/AWECoreUtils.h)
    • AWECoreUtils contains various helper code including SPI/UART helper functions, a Multi-instance reply table generator, and a CRC computer for preparing custom replies to AWE Server. AWECoreUtils.h API Doc
  • ProxyIDs.h
    • ProxyIDs.h contains a list of all the AWE Core OS tuning commands. ProxyIDs.h
  • Additional Module Pack Libraries
    • Additional module pack libraries can be linked in (VUI modules, 3rd party custom modules, etc).

Initializing the AWE OS Instance

Declaring the Required Variables

A number of variables must be defined before configuring/initializing the AWE Instance.

  • The AWEOSInstance object encapsulates a single AWE Core OS instance. Most API calls take a pointer to an AWEOSInstance as the first argument.
      AWEOSInstance * g_AWEOSInstance;
    
  • An AWEOSConfigParameters parameter must be defined to pass into the initialization functions.
      AWEOSConfigParameters configParams;
    
  • A list of module classes, usually defined in ModuleList.h by macro LISTOFCLASSOBJECTS. The size of the list must also be calculated as below.
      static const void* moduleDescriptorTable[] =
      {
          LISTOFCLASSOBJECTS
      };
      UINT32 moduleDescriptorTableSize = sizeof(moduleDescriptorTable) / sizeof(moduleDescriptorTable[0]);
    

Configuring the AWE OS Instance

AWE Core OS provides a function aweOS_getParamDefaults to fill a configuration parameter structure with default values. See AWEOSConfigParameters for details on the default values. It is recommended to always call aweOS_getParamDefaults in an application to get the default values, and then overwrite any of the defaults with user parameters as needed. The remainder of this section describes the individual configuration parameters in more detail.

  • Get the default parameters aweOS_getParamDefaults(configParams);

Optionally overwrite any configuration parameters with values as appropriate for application

  • Assign the “numThreads” to the maximum number of audio processing threads that the application will support. See the Multi-Rate section for more info.
     configParams.numThreads = 8; 
    
  • Assign the “sampleRate” member to the fundamental sample rate of the system.
     configParams.sampleRate = 16000.0f;
    
  • Assign the “fundamentalBlockSize” member to the fundamental block size of the system. This value determines the rate at which the aweOS_audioPumpAll function must be called. Every layout that is run on the system must have a block size that is a multiple of this fundamental block size.
     #define AWE_BLOCK_SIZE 256
     configParams.fundamentalBlockSize = AWE_BLOCK_SIZE;
    
  • Assign heap sizes. These values are used to allocate heap space in aweOS_init only if the heap pointers below are NULL.
     configParams.fastHeapASize = FAST_HEAP_A_SIZE;
     configParams.fastHeapBSize = FAST_HEAP_B_SIZE;
     configParams.slowHeapSize = SLOW_HEAP_SIZE;
    
  • Assign pointers to user allocated arrays if the application wants control of allocating heap space. If these are set, the arrays must be of the sizes specified by the size parameters above.
     configParams.pFastHeapA = g_FastHeapA;
     configParams.pFastHeapB = g_FastHeapB;
     configParams.pSlowHeap = g_SlowHeap;
    
  • Assign the “coreSpeed” and “profileSpeed” members to the CPU/profiling speed of the system. Only used for profiling displays in Audio Weaver tools.
     configParams.coreSpeed = 1.2e9f;
     configParams.profileSpeed = 10e6f;
    
  • Assign the string that will be displayed in the AWE Server dialog as the name of the instance. Max length is 8 characters.
     configParams.pName = “MyTarget”;
    
  • Assign the "userVersion" member to a UINT32 value. The value and meaning is entirely up to the application writer.
     // This example represents a date of April 6, 2020
     configParams.userVersion = (UINT32) 04062020;
    
    If not planning on using aweOS_tuningSocketOpen, allocate and assign packet buffer(s) to be used by user created tuning interface.
  • Assign the configuration parameter “packetBufferSize” to 264
     #define PACKET_BUFFER_SIZE  264
     ...
     configParams.packetBufferSize = PACKET_BUFFER_SIZE;
    
  • Assign the configuration parameter “pPacketBuffer” pointer to a UINT32 buffer of size packetBufferSize, only if not planning on using aweOS_tuningSocketOpen.
     UINT32 packetBuffer[PACKET_BUFFER_SIZE];
      ...
     configParams.pPacketBuffer = packetBuffer;
    
  • Assign the configuration parameter “pReplyBuffer” pointer to the reply buffer, only if not planning on using aweOS_tuningSocketOpen. Note that this pointer can be set to the same buffer as the send buffer if a single buffer for send/reply is desired.
     UINT32 packetReplyBuffer[PACKET_BUFFER_SIZE];
     ...
     configParams.pReplyBuffer = packetReplyBuffer;
    

Initializing the AWE OS Instance

Once the instance parameters are configured correctly, initialize the AWE OS Instance by calling aweOS_init. If aweOS_init is called before the configuration parameter structure is fully configured, the initialization will fail. The module table and table size must also be provided to the initialization function. Note that a pointer to the AWE OS Instance pointer (AKA Double Pointer) must be the first argument.

int ret = aweOS_init(&g_AWEOSInstance, &configParams, moduleDescriptorTable, moduleDescriptorTableSize);

Registering AWE OS Callbacks

After initialization of the AWE OS Instance, users can optionally register callbacks supported by the AWE Core OS. AWE Core OS supports callbacks for logging functionality (aweOS_registerLoggingCallback), and for Event module support (aweOS_registerEventCallbacks).

Logging

The AWE Core OS logging functionality provides log information to applications that have registered a callback function. Log level (Error, Warning, Info, Debug), as well as a log type (bitfield), are provided as part of the log messages. The user can configure which log levels and log types to filter out using the aweOS_registerLoggingCallback function. The register function can be called at any time after aweOS_init, and the registered log level and log types can be updated with subsequent calls.

To receive callbacks from AWE Core OS for logging messages, the application needs to define a function matching the signature of cbAweOSLogging_t. A simple example of a user function that prints a timestamp and the content of the logging payload is shown below. Note that the payload is always an ASCII message with no trailing newline.

    void usrLogging(AWEOSInstance* pAWEOS, INT32 level, UINT32 type, void* payload, INT32 payloadSizeInBytes)
    {
        // Simple logging function always writes payload to stdout
        struct timeval ts;
        char timeCh[25];

        // Get current time for log
        gettimeofday(&ts, NULL);
        snprintf(timeCh, 25, "%ld.%03ld: ", ts.tv_sec, ts.tv_usec / 1000);

        printf("%s%s\n", timeCh, (char *)payload);
    }

See Libtester.c for more details of an example use case.

Events

The AWE Core OS has an Event Module that can trigger callbacks to the system when events occur in the layout. The meaning of the event must be defined by the Event Module user and the application has to understand how to respond to different event types. Similarly to the logging feature, event callbacks must be registered with the AWEOSInstance for the Event Module to be able to trigger any events. The Event Module supports 3 callbacks:

  1. cbAweOSEventTrigger_t : Called by the Event Module when an event trigger is detected
  2. cbAweOSEventRegister_t : (optional) Called by the Event Module when it is constructed
  3. cbAweOSEventDeregister_t : (optional) Called by the Event Module when it is destroyed

These callbacks must be registered using aweOS_registerEventCallbacks. See Libtester.c for an example use case.

Setting up a Tuning Interface

Once the AWEOSInstance is initialized, the next step is to enable a tuning interface. One of the most important components of an AWE Core OS application, a tuning interface provides a communication channel between the AWE OS Instance and an application like AWE Server running on the host PC. This communication channel is essential as it provides key audio tuning and debugging capabilities. For example, tuning commands can instantiate on the target a layout generated in AWE Designer, or can set and query the values of module parameters in real-time.

The AWE Core OS API includes a built-in TCP/IP Socket based tuning interface that can be started with aweOS_tuningSocketOpen. This function can be used for any target with an ethernet or WiFi connection, and lets the user specify a port number that can be used to connect to an AWE Server instance on the host PC. The tuning interface can be closed by the application using aweOS_tuningSocketClose, and all of the packets sent and received can be logged using aweOS_tuningLoggingEnable. If using this built-in tuning interface, then no more action needs to be taken by the application.

Most AWE Core OS applications will use the built-in aweOS_tuningSocketOpen function to open a TCP/IP socket that can be used to communicate with AWE Server.

    INT32 interfaceRet = aweOS_tuningSocketOpen(&g_AWEOSInstance, PORTNO, 1);
    if (interfaceRet == E_SUCCESS)
    {
        printf("Opened TCP tuning interface on port %d: Waiting for AWE Server Connection from PC... \n", PORTNO);
    }

See aweOS_tuningSocketClose and aweOS_tuningLoggingEnable for additional control functions for the built-in tuning interface.

If TCP/IP communication is not available on the target hardware, then the application will have to implement a custom tuning interface. Different platforms will support different transport protocols for the tuning interface. AWE Server supports USB, TCP/IP (sockets), RS232, and SPI communications.

Any implementation of a tuning interface will follow these general steps

  1. Setup/test the desired transport layer between the board and the PC. Make sure that packets of length 264 words can be passed back and forth (echo). The communication link can be verified independently of AWE Core OS using a simple test application.
  2. Allocate UINT32 arrays of size 264 for packetBuffer and, optionally, packetReplyBuffer. Assign these to the configuration parameters structure before calling aweOS_init.
  3. Once commands can be received from the PC, they will need to be processed by the AWE OS Instance using aweOS_tuningPacketProcess. Once a tuning command is received from AWE Server, copy it into packetBuffer and then call aweOS_tuningPacketProcess.
  4. The aweOS_tuningPacketProcess function will process the packet in the registered packetBuffer, and then generate a reply in the registered replyBuffer buffer. If the same buffer is used for both receive and reply, the reply message will overwrite the original command directly in packetBuffer.
  5. Finally, the reply is sent back to the AWE Server over the transport layer. See the following pseudocode representation of the complete transaction.
    readPacket(packetBuffer, 264)
    aweOS_tuningPacketProcess(&g_AWEOSInstance)
    writePacket(packetReplyBuffer, 264)
    
    The TX/RX of the packet buffers will differ between each tuning protocol. The rest of this document will explain the message structure and protocol of Audio Weaver tuning commands. It will also explain the details needed to implement each supported transport method's protocol.

For a complete list of Audio Weaver tuning commands, see the file Tuning Protocol Doc.

Real-time Audio

RT Audio Introduction

The next step is to integrate real-time audio. AWE Core OS aside, real-time audio can be a tricky topic. Before attempting to integrate real-time audio into an AWE Core OS system, please ensure that the integrator has a basic understanding of digital audio and real-time audio processing. Here are some helpful links.

http://www.rossbencina.com/code

Giulio Moro - Assessing the stability of audio code for real time low latency embedded processing

RealTime Audio Integration Steps

  1. Verifying Audio HW IO with a Passthrough Ensure that audio can be cleanly passed between input/output without the AWE Core OS involved in the signal path. A simple sine wave input into the system can often be the best audio source for this type of test, as any distortion or dropouts are more easily audible than with a more complex signal. This step is critical to ensure a high fidelity audio system, and can vary greatly from platform to platform. For instance, an embedded target might utilize the HW’s audio IO DMA, while Linux systems may use ALSA to drive audio. For Linux systems, see the RTAudio-alsa.c reference application for an example of using ALSA as the audio driver.
  2. Determining Audio HW Settings and Configuring the AWE Instance.
    1. Configure the sample rate and fundamental block size for the audio device. The block size of any layout loaded on the target must be a multiple of this fundamental block size. If the audio device's sampling rate is 44100.0, then the AWE OS Instance’s sampleRate should be set to 44100.0.
    2. Determine the sample format of the audio HW. See the Audio Sample Formatting section below for more information on audio sample formatting.
    3. Determine the Input/Output channel count of the audio device. AWE Core OS needs to be able to import/export data for each available HW channel. For each available hardware input channel, the application will need to call aweOS_audioImportSamples, and, likewise, aweOS_audioExportSamples will need to be called for each available hardware output channel. Using these channel-by-channel functions make it easy to aggregate multiple audio sources into a single, multi-channel input or output. Additionally, the Import and Export functions automatically handle what is referred to as 'channel matching'. Since AWE Core OS is designed to be flexible and Audio Weaver layouts are designed to be portable, the number of channels in a layout need not match the number of HW inputs and outputs in a system. If a layout has more I/O channels than the hardware channel count, then the Import and Export functions will automatically fill the input signal with additional zeroed channels, and will ignore any additional output channels. Similarly, if the layout loaded has fewer channels than the hardware channel count, then the Import and Export functions will ignore the additional HW input channels, and fill the additional HW output channels with zeros.
  3. The Realtime Audio Loop. Most OS based audio drivers allow the user to register a callback function when a full frame of input audio has been collected, or a full frame of output audio is needed. The AWE Core OS audio processing functions should be implemented in this callback as follows:
    1. Check to see that a valid layout is loaded and that audio has been started with the API calls aweOS_layoutIsValid and aweOS_audioIsStarted.
    2. Call aweOS_audioImportSamples for each input channel and pass in the audio data from the audio device.
    3. Call aweOS_audioPumpAll
    4. Call aweOS_audioExportSamples for each output channel and pass in the audio HW’s output data.

Audio Sample Formatting

The data type of the input and output audio data is determined by the audio hardware. Typically, digital audio is represented as fixed point data in 16, 24, or 32 bit depth. The audio sample formats _SampleType supported by AWE Core OS's aweOS_audioImportSamples and aweOS_audioExportSamples functions are:

  • Sample16bit - Fixed point 16 bit audio data stored in 16 bit data type. Also known as Q15 format.
  • Sample24bit_low - Fixed point 24 bit audio stored in 32 bit data type. Right justified, so the 24 bits of data are stored in the lowest 24 bits of the 32 bit word.
  • Sample24bit_high - Fixed point 24 bit audio stored in 32 bit data type. Left justified, so the 24 bits of data are stored in the highest 24 bits of the 32 bit word. If both right justified and left justified formatting is available on the hardware, then choose left justified (Sample32bit_high) for a slight performance improvement.
  • Sample32bit - Fixed point 32 bit audio data. Also known as Q31 format. This data is passed directly to the signal processing chain without conversion.

Internally, all inputs and outputs to Audio Weaver layouts are of type Sample32bit, also referred to as fract32 within a layout. This is done to guarantee portability of any signal processing layout to any target system, regardless of the hardware's native audio data type. The aweOS_audioImportSamples and aweOS_audioExportSamples functions will convert to and from the Sample32bit data as needed based on the _SampleType argument. If the target's native audio sample formatting is not one of those listed above, then the program will have to convert the data to one of the supported types before using the import and export functions.

Since some common target systems natively support floating point audio, helper functions are provided to convert between float<-->fract32 in AWECoreUtils.h, which is provided in the AWE Core OS package. See the sample-by-sample conversion functions float_to_fract32 and fract32_to_float. See the API in doc AWECoreUtils.h for more info.

Multi-Rate Processing

The AWE Core OS library allows audio to be processed at multiple block rates. For example, consider a system with two processing paths: one that processes time-domain data in blocks of 32 samples and another that processes frequency-domain data in blocks of 64 samples but at 1/2 the block rate of the time-domain path. Each of these processing paths is called a sublayout, and each has a clock divider property equal to the inverse of the relative block rates. In this example, the sublayout with 32 samples has a clock divider of 1, and the sublayout with 64 samples has a clock divider of 2. Such a system is shown in the figure below. It uses BufferUp and BufferDown modules to connect the different block size domains. These modules effectively partition the layout into 2 sublayouts operating at different block rates. When this layout is pumped, the sublayout of 64 samples will only be processed half as often as the sublayout of 32 samples.

Multi-Rate Layout

Real-time constraints dictate that if the 64 sample sub-layout is executed in the same context as the 32 sample sub-layout, both will need to complete processing before the next block of 32 samples arrives or real-time will be broken. This imposes an unnecessarily strict constraint on the 64 sample sub-layout – its processing need only be completed before the next block of 64 samples is accumulated. Thus, its processing time can, in principle, be spread over 2 of the 32 sample block times without breaking real-time requirements. The internal scheduling of layout pumping in AWE Core OS handles this spreading of sublayout execution times to allow for effective multi-rate processing.

See the CPU Affinity for Multi-Rate Designs section for more info about threading and CPU affinity.

When profiling multi-rate layouts, AWE Designer's Manual Profiling tool may provide more accurate results than the real-time profiling tools. The Manual Profiling Tool uses a simulated audio callback to manually pump samples through each sublayout in a multi-rate design. The tool can be found in AWE Designer's top bar, "Tools->Manual Profile Layout".

Standalone Operation

At this point, the application should be able to load a layout from AWE Designer and run it on the target via the Tuning Interface. Once a layout in Designer has been tuned and finalized, the application can be updated to load the layout from an AWB file saved to the filesystem, or from a C array compiled directly into the application.

Loading AWB from an Array

From the viewpoint of AWE Core OS, the signal processing layout is described by a data array of binary Audio Weaver commands. This command array is generated by AWE Designer using the Tools/Generate Target Files menu item and enabling the [BaseName]_InitAWB checkbox. The layout is loaded into an AWE Core OS instance by making a call to aweOS_loadAWBFromArray with the command array, its size in 32-bit words, and the AWEOSInstance as arguments. If an error occurs during loading, then the offset of the offending AWB command is returned to the pErrorOffset argument.

INT32 err = aweOS_loadAWBFromArray (pAWE, pCommands,arraySize, &nErrOffset );
if (err)
{
    // report the error
    printf(“error code %d due to command at position %u\n”, err, nErrOffset);
    // handle the error
    ...
}

aweOS_loadAWBFromArray will load and process the entire array of binary commands included in the pCommands array. If an array of commands needs to be loaded on a remote instance, it can be loaded command by command with the awe_getNextAWBCmd() helper function in AWECoreUtils.h. Each command is parsed so that they can be routed to the remote instance.

Loading AWB from Filesystem

To generate an AWB file from a layout in Designer, use the Tools/Generate Target Files menu and enable the [BaseName].awb checkbox. This .awb file can be saved to the filesystem on the target and aweOS_loadAWBFile can be used to load and process the commands therein. Loading an AWB from a file rather than a C array has the advantage that an AWB file can be swapped out with an updated design without having to recompile the application.

AWE Core OS Scheduling and Priority

The AWE Core OS library handles the scheduling and prioritization of all the internal processing threads. The user application must only set the schedule and priority of the audio loop function, most often implemented as part of the audio driver's callback function. Since each OS has different scheduling and prioritization models, the code to set the audio loop to a high priority level will be application specific. For a Linux system where the application can be executed with root permissions, the following code in the audio loop context sets the priority to a real-time level and the scheduling policy to SCHED_FIFO.

int max_priority = sched_get_priority_max(SCHED_FIFO);
struct sched_param audio_sched_param = {0};
pthread_t currentHandle = pthread_self();
int target_priority = max_priority - 2;
audio_sched_param.sched_priority = target_priority;
pthread_setschedparam(currentHandle, SCHED_FIFO, &audio_sched_param);

Setting up a Control Interface

Control Interface Overview

A control interface lets the system interact with modules in the running layout directly from the firmware. To access the layout from the API, use Designer to generate a control-interface header file for the layout. Then, use the following API calls define the functionality of the control interface:

Get/set a value of a module: aweOS_ctrlGetValue() and aweOS_ctrlSetValue()

INT32 aweOS_ctrlGetValue(const AWEOSInstance *pAWEOS, UINT32 handle, void *value, INT32 arrayOffset, UINT32 length);
INT32 aweOS_ctrlSetValue(const AWEOSInstance *pAWEOS, UINT32 handle, void *value, INT32 arrayOffset, UINT32 length);

Get/Set the status of a module (bypassed, active, inactive, muted): aweOS_ctrlSetStatus() and aweOS_ctrlGetStatus()

INT32 aweOS_ctrlSetStatus(const AWEOSInstance *pAWEOS, UINT32 handle, UINT32 *status);
INT32 aweOS_ctrlGetStatus(const AWEOSInstance *pAWEOS, UINT32 handle, UINT32 *status);

Check if a module exists, and if so return its ClassID: aweOS_ctrlGetModuleClass()

INT32 aweOS_ctrlGetModuleClass(const AWEOSInstance *pAWEOS, UINT32 handle, UINT32 *pClassID);

The following functions provide finer grained control over how module variables get set and are for advanced users: aweOS_ctrlSetValueMask() and aweOS_ctrlGetValueMask()

INT32 aweOS_ctrlSetValueMask(const AWEOSInstance *pAWEOS, UINT32 handle, void *value, INT32 arrayOffset, UINT32 length, UINT32 mask);
INT32 aweOS_ctrlGetValueMask(const AWEOSInstance *pAWEOS, UINT32 handle, void *value, INT32 arrayOffset, UINT32 length, UINT32 mask);

Control Interface Steps

To access a module and control it via the Control Interface,

  • Create the desired layout. In the build tab of a module, assign an object ID to the module's that will be accessed. The object ID must be a value between 30000-32767. Object IDs can also be assigned by right clicking on the module and clicking Assign under the ObjectID option.
  • Generate a ControlInterface.h header file from the layout with Tools->Generate Target Files. Make sure the [BaseName]_ControlInterface.h box is checked.
  • Include the generated [BaseName]_ControlInterface.h file in the AWE Core OS application. This file will contain all of the macros required to use the API's control functions for any module with an object ID assigned.
  • Before interacting with the module, check if the module exists in the loaded layout and is of the right class with aweOS_ctrlGetModuleClass. This prevents unsafe memory accesses in the case of a mismatch between the loaded layout and the ControlInterface.h.
  • Use one of the aweOS_ctrl functions to interact with the module.
    • The handle, length arguments will all be defined in the [BaseName]_ControlInterface.h file that was generated. See the API doc for details about the control functions arguments/return values.
  • Check the return value for errors and handle appropriately.

Generally, interacting with modules should follow this flow:

    // Does the current AWE model have a Scaler module with this control object ID?
    if (aweOS_ctrlGetModuleClass(g_AWEOSInstance, AWE_Scaler1_gain_HANDLE, &classId) == E_SUCCESS)
    {
        // Check that module assigned this object ID is of module class Scaler
        if (classID == AWE_Scaler1_classID)
        {
            // Query Scaler1 module's current gain value (value is a scaler)
            aweOS_ctrlGetValue(g_AWEOSInstance, AWE_Scaler1_gain_HANDLE, 0.5, 0, AWE_Scaler1_gain_SIZE);
        }
    }

See the LayoutControl.c app for a full example of controlling a module directly from the application.

Optimization

AWE Core OS supports multiple levels of memory optimization including custom heaps and reduced module sets. The audio processing threads created by AWE Core OS can also be optimized by fixing their cpu affinity at runtime.

Heap Sizing and Placement

AWE Core OS creates default heaps of size 5000000 words when using the aweOS_getParamDefaults API. If the configuration parameters heap members (heap pointers and heap sizes) are left untouched before aweOS_init, then these default heaps are allocated and assigned to the AWEOSInstance. In most development situations, these default heaps will be sufficient and the user can likely leave them untouched.

Although the default heaps are OK for development, the user may want to reduce the size of the heaps to save memory usage at production time. In some situations, they may also want complete control over the heap's memory location itself, rather than letting AWE Core OS allocate the heap internally.

Change Size of Internal Heaps

Overwrite any of the AWEOSConfigParameter's heap size members (fastHeapASize, fastHeapBSize, slowHeapSize) before calling aweOS_init to use a custom sized heap. When a heap size parameter is overwritten, AWE Core OS will internally allocate and assign heaps of the specified sizes. Note that the heap memory location itself is still internal to the AWE Core OS library.

Heap Memory Location

If a user wants complete control over a heap (size AND memory location), AWEOSConfigParameter's heap pointer members (pSlowHeap, pFastHeapA, pFastHeapB) can be overwritten with pointers to user allocated memory.

If the AWEOSConfigParameter's heap pointers are being overwritten, then the heap size members must also be overwritten to the size of the custom heap. Passing in a custom heap and no size will result in an error when initializing.

UINT32 customSlowHeap[MY_CUSTOM_SIZE];
configParams.pSlowHeap = customSlowHeap;
configParams.slowHeapSize = MY_CUSTOM_SIZE;

AWE Designer can generate the absolute smallest heap sizes needed for a specific layout via Tools->Generate Target Files->[BaseName]_HeapSize.h.

This can greatly reduce the memory footprint and aid in optimizing a system. Use these generated heap sizes to overwrite the heap size configuration parameters of AWE Core OS.

Module List Optimization

The ModuleList.h file that is delivered with AWE Core OS contains a complete set of Audio Weaver modules available in the library. This is great for designing layouts during development, but when the design is complete, the user may want to remove the unneeded modules to reduce the size of the application.

To trim the module list, simply remove the unused modules from the LISTOFCLASSOBJECTS in ModuleList.h. The module list is passed in as an argument to the aweOS_init function at build time, and only the module's in the list will be included in the application.

See the Declaring the Required Variables section for more implementation details regarding the module list.

AWE Designer can generate a new ModuleList.h file that contains only the module's used in a given layout. From Designer do Tools->Generate Target Files->[BaseName]_ModuleList.h.

Replace the default ModuleList.h with this trimmed version, and rebuild the application.

3rd party or user created modules can also be added to the application by adding the modules to ModuleList.h and linking the module libraries to the executable.

Threading and CPU Affinity for Multi-Rate Designs

Threading in AWE Core OS

AWE Core OS internally creates/manages a unique processing thread for each sublayout in a design, as well as a mandatory "workThread" and an optional "socketThread" per AWE Core OS instance. For example, the design shown in the Multi-Rate section, consisting of a single bufferup/down module (1 clockdivider + base layout), would create two pumpthreads when not running in Low Latency (LL) mode. If running in LL mode, a single pumpthread (awe_pumpthread0) is created only for the second sublayout, because the baselayout is pumping within the user audio thread. See the Low Latency Pumping section for more information.

It is important to note that these threads are independent of the user's audio callback thread (where aweOS_audioImportSamples / aweOS_audioExportSamples and aweOS_audioPumpAll are being invoked). The user's audio callback fills the aweOS import buffers with new samples, calls aweOS_audioPumpAll, and then exports the latest available data from the AWE OS output buffers. The aweOS_audioPumpAll function then coordinates the created pumpthreads to execute as needed. Normally, the aweOS_audioPumpAll function does not block until the individual pump threads are completed, and returns immediately once the pump threads have been signaled. See the Low Latency Pumping section for more information.

In design described above, the following threads would be seen in top/htop in a Linux system while NOT running in Low Latency Mode.

    ./<AWE Core OS Application Parent Process>
        <myAudioCallbackName>
        awe_pumpthread0
        awe_pumpthread1 (if LL mode, then this would not exist)
        awe_workThread (not an audio thread, but also high priority and time critical)
        awe_socketThread (not an audio thread, created upon @ref aweOS_tuningSocketOpen)

Note: if htop is not showing the names listed above, go to Setup (F2) -> Display Options and enable "Show custom thread names" as seen below. Enabling the Tree view display option is also helpful.

htop Thread Names

Retrieving AWE Core OS Internal Thread PIDs

AWE Core OS provides an API aweOS_getThreadPIDs() to retrieve internally created thread identifiers. The PIDs of all internal AWE Core OS pump threads, work thread, and tuning socket threads will be provided. See the API documentation of aweOS_getThreadPIDs() for more details, as well as the RTAudio-alsa.c example, which demonstrates setting CPU affinity within an AWE Core OS integration.

Priority Levels

Each operating system will have different methods to update the priority the process is given by the scheduler. The following sections describe Linux specific operations but the general theory can be applied to any OS.

Because the Linux kernel decides the scheduling or processes, the audio threads and the workThread need to be run at the highest possible priority so as not to be preempted by less critical processes. AWE Core OS internally sets the pumpthread's priority level based on the audiocallback's priority level (cascading downward). For example, if the audiocallback thread shown above was at priority level 99, then the pumpthread 0 would be set to 98, pumpthread1 to 97, and the workThread to 96.

This requires that the user places their audiocallback thread at a high enough priority to leave room for the number of supported threads (AWEOSConfigParameters.numThreads), as well as the workThread. To ensure that the priority is high enough and that the audio threads aren't unnecessarily interrupted, it is recommended to simply set the audiocallback's priority to the maximum that the system allows. The audiocallback should always be the highest priority, and the workthread will always be just under the lowest pumpthread's priority level.

CPU Affinity

The Linux kernel also has control over which CPU a thread executes on, and may choose to migrate the threads to different CPUs at any point. This can be problematic for a number of reasons, but is easily avoided by fixing CPU affinity for the entire process, or for individual threads. To fix all threads in an AWE Core OS application to a single CPU, simply run the executable with taskset and the desired physical CPU.

    taskset -c 0 ./RTAudio-alsa

The command above would run the RTAudio-alsa example app and all of its threads exclusively on CPU 1. This may be safer than letting the kernel migrate threads freely, but it also does not fully utilize the processing capabilities of multi-core hardware.

Generally, audio data is dropped when a physical CPU's load approaches/exceeds 100%. Forcing all of the AWE Core OS threads to share a single CPU will cause audio dropouts on that CPU much sooner than if the pumpthreads were distributed to their own CPU's. When everything is forced to one CPU, the other physical CPUs are sitting idle and unutilized. This is highly inefficient, especially in a Multi-Rate design where multiple pumpthreads are running.

AWE Core OS Systems can be highly optimized by assigning strict CPU affinity to its internal threads. CPU affinity can be set from within an AWE Core OS integration (see aweOS_getThreadPIDs and usage in RTAudio-alsa.c ). Affinity can also be set after an application is running as described below...

The following shell script will search for a parent process containing the string "RTAudio", and all child processes containing the string "pumpthread". It sets the pumpthreads to their own dedicated CPUs.

This script assumes that 4 physical cores are available, and therefore assigns a max of 4 pumpthreads. It also assumes that the application name contains the string "RTAudio" (For example, the RTAudio-alsa sample app)

    #!/bin/bash
    parentPIDgrep=$(pgrep --list-name RTAudio)
    parentPIDarray=( $parentPIDgrep )
    parentPID=${parentPIDarray[0]}
    parentProcessName=${parentPIDarray[0]}
    echo "Found parent process $parentProcessName at pid $parentPID"

    processList="$(ps H -o 'tid comm' $parentPID)"
    oldIFS="$IFS"
    IFS='
    '
    IFS=${IFS:0:1} 
    processes=( $processList )
    IFS="$oldIFS"
    declare -i PUMPTHREAD_COUNTER
    PUMPTHREAD_COUNTER=0
    for process in "${!processes[@]}"
        do
            if [[ ${processes[process]} =~ "pumpthread" ]];
            then
                echo "found pumpthread ${processes[process]}"
                PUMPTHREAD_PID_ARRAY[$PUMPTHREAD_COUNTER]=${processes[process]}
                PUMPTHREAD_COUNTER=PUMPTHREAD_COUNTER+1
            fi
    done

    for pumpthread in "${!PUMPTHREAD_PID_ARRAY[@]}"
        do
            fullElement=${PUMPTHREAD_PID_ARRAY[pumpthread]}
            splitPID=( $fullElement )
            PID=${splitPID[0]}
            threadIdx=$pumpthread
            if [[ $threadIdx == "0" ]];
            then
                tasksetout=$(taskset -p 0x11 $PID)
            elif [[ $threadIdx == "1" ]];
            then
                tasksetout=$(taskset -p 0x12 $PID)
            elif [[ $threadIdx == "2" ]];
            then
                tasksetout=$(taskset -p 0x14 $PID)
            elif [[ $threadIdx == "3" ]];
            then
                tasksetout=$(taskset -p 0x18 $PID)
            fi
            declare -i COREINT
            COREINT=threadIdx
            COREINT=COREINT+1
            echo "set $PID cpu affinity to core $COREINT"
    done

Provided they have the required permissions, the user has total control of thread CPU affinity with taskset, and this script can be customized to suit a specific system.

CPU Affinity - Profiling

AWE Core OS will use the CPU affinity of each processing thread to improve the accuracy of the reported profiling for multi-threaded layouts. If the CPU affinity for all pump threads is locked to just one CPU, then AWE Core OS will be able to correct profiling calculations for each pump thread to avoid double counting cycles in the case of preemption of threads running on the same core. For accurate profiling with AWE Core OS, it is always recommended to set the CPU affinity to a single core for all threads using any of the methods described above.

Multi Instance

Multi Instance Introduction

Before setting up a multi instance tuning interface, it's recommended to first use and understand a single instance tuning interface. The HelloWorld.c example app is a good place to start.

AWE Core OS systems can contain multiple AWEOS Instances. Though each instance is unique and independent, AWE Core OS supports a single TCP tuning interface socket for up to 31 instances. This feature is demonstrated in the Multi-Instance example app as "single socket" mode. When AWE Server is connected to a multi-instance single socket, a dropdown will appear in the top left corner (the same is true in Designer). Use these dropdown menus to toggle between sending commands to individual AWEOS Instances.

AWE Core OS also supports opening a tuning socket for each instance on unique port numbers (demonstrated in the Multi-Instance example app as "twosocket" mode). While operating in this mode, only one instance can be controlled at a time from the PC. To control both instances, AWE Server needs to change its connection back and forth, or multiple PCs must be connected to the device.

For more implementation details of multiple sockets, please see the Multi-Instance.c example app.

For accurate profiling, link all the AWE Core OS instances into the framework with the API aweOS_setInstancesInfo. This will enable accurate profiling across all instances and pump threads provided that CPU affinities are set for all instances and threads. See the CPU Affinity - Profiling section for more details. For more details on how to use this API, see the Multi-Instance.c example app.

Multi Instance Tuning Interface Setup

  1. Declare an array of multiple AWEOSInstance 's. Also create a unique AWEOSConfigParameters for each instance, and populate them with defaults.
  2. Overwrite the "instanceID" member of each AWEOSConfigParameters struct in increments of multiples of 16. For example, a 3 instance system would be configured with instanceIDs 0, 16, and 32. AWEOSInstance 's have an "instanceID" parameter in the associated AWEOSConfigParameters configuration structure. Similarly, AWE Core tuning commands always contain a prefixed address called “instanceID”, which specifies the command's destination. For a single instance system, the AWEOSInstance 's "instanceID" is always 0, and all incoming packets are always addressed to 0.
  3. Initialize all instances by calling aweOS_init for each AWEOSInstance and its corresponding AWEOSConfigParameters (each of which now has a unique instanceID).
  4. After all instances are successfully initialized, open the tuning interface with the aweOS_tuningSocketOpen function. Pass it a pointer to the array of instance's, the desired port number for the socket, and the number of instances (elements in the instance array).
  5. Connect to AWE Server and verify that the dropdown menu appears, and toggle between instance's

Custom Tuning Interface and Multi-Instance

If a custom tuning interface is desired (RS232, USB, etc.), then the application creator is responsible for the routing/processing of AWE Core OS tuning commands between multiple AWEOSInstances. This is described in theTuning Protocol Doc section.

Latency

Overview of Latency in AWE Core OS

This section discusses only the latency caused by the AWE Core OS framework. The signal processing flow, the firmware, and the hardware can all introduce additional latency to the overall system. In this discussion, a "block" of latency refers to the layout block size and the systems sample rate, unless otherwise specified. For example, with a layout block size of 256 and a sample rate of 48 kHz, the latency through the AWE Core system will be in multiples of blocks of (256 / 48000) = 5.33 ms. AWE Core OS can introduce up to 2 block’s of latency, but can also be configured to achieve 0 blocks of latency under certain conditions.

Conditions that Impact Latency

To achieve 0 blocks of AWEOS-induced latency, the following conditions must be met:

  1. Equivalent Block Sizes: The loaded layout’s block size must be equivalent to the AWE Core OS instance’s fundamental block size.
  2. Order of audio API calls: The order of AWE Core OS audio processing API calls in the applications audio callback should always be import -> pump -> export, as in the following:

    aweOS_audioImportSamples —> aweOS_audioPumpAll —> aweOS_audioExportSamples

If neither of the above are true, then the total latency will be 2 layout block sizes.

Equivalent Block Sizes – Double Buffering

AWE Core OS has an internal double buffering scheme on the input and output pins to handle situations where a layout’s block size is a multiple of the AWEOSInstance’s fundamental block size. For example, a system with a 64 BS layout running on a 32 fundamental BS AWEOSInstance must complete two, 32-sample audio callbacks before actually pumping audio through the layout (to have accumulated the 64 samples required by the layout). Double buffering is used in this situation in order to store the next frames of data in one buffer, while the processing occurs on the data in the other buffer. Two blocks of latency are introduced by this double buffering of the input and output pins.

However, in a situation where a layout BS is the same as a fundamental BS, the double buffering scheme is not required and two blocks of latency can be avoided. AWE Core OS has an internal mechanism to check if the layout and fundamental block sizes are equivalent at layout runtime, and will automatically bypass the double buffering to eliminate the introduced latency. Less memory is also consumed under this condition as only a single buffer can be used at the input and output pins.

Order of API Calls – Import Pump Export

Fundamentally, 0 blocks of latency can only be achieved through AWE Core OS if all of the audio processing occurs in a single callback. Based on this, the application's audio processing code must implement the following order of API calls to achieve the lowest possible latency:

aweOS_audioImportSamples
aweOS_audioPumpAll
aweOS_audioExportSamples

Different orders of API calls will still operate correctly from a processing standpoint, but may not achieve the lowest possible latency.

Low Latency Pumping

When the equivalent block size condition is met, AWE Core OS will handle pumping of the layout slightly differently than in the general, non-matching block size case. In order to guarantee minimal latency, AWE Core OS will pump the first sublayout directly in the aweOS_audioPumpAll function, rather than signaling a background thread to pump the layout in a separate context. This 'in-line' pumping of the first layout causes the aweOS_audioPumpAll function to block until the first sublayout is completed, which guarantees that the data returned by the subsequent call to aweOS_audioExportSamples contains the processed data, input by the previous aweOS_audioImportSamples call. Other non Low Latency paths in the layout (clockdivided sublayouts) will still be executed in their respective lower priority threads.

Troubleshooting and Common Pitfalls

Double Pointers

Although most of the AWE Core OS API functions take the AWEOSInstance argument as a single pointer (pass by reference), a few of the functions require a double pointer (pass by reference to reference). The functions that require double pointer arguments are aweOS_init and aweOS_tuningSocketOpen. For example aweOS_audioIsStarted would be called like...

AWEOSInstance *g_AWEOSInstance;
{
    UINT32 isStarted = aweOS_audioIsStarted(g_AWEOSInstance);
}

while aweOS_init would be called like...

AWEOSInstance *g_AWEOSInstance; 
{
     aweOS_init(&g_AWEOSInstance, &configParams, moduleDescriptorTable, moduleDescriptorTableSize); 
}

Notice that the instance itself is declared as a pointer, so for the functions that require a double pointer, we pass a reference to that pointer.

Specifying Custom Heaps without Specifying Heap Size

If the AWEOSConfigParameter's heap pointers are being overwritten, then the heap size members must also be overwritten to the size of the custom heap. If the user wants to use specific memory regions for heaps, then the application must allocate the memory for the heaps, set the heap pointers to this memory, and set the heapSize members of the AWEOSConfigParameter's to the appropriate sizes. Passing in a custom heap and a size of zero will result in an error when initializing, but passing mismatched heap pointers and sizes will likely lead to memory corruption and system crash.

Enabling Real Time Audio with the RTAudio-alsa Example

For information about how to use the RTAudio-alsa sample app with a specific audio device, please see

ALSA Devices

Audio Recording

In order to assist in debugging audio paths and layout input/outputs, there is an option to enable recording of the audio that is processed by the loaded layout using the function aweOS_audioRecordingEnable. This will produce .wav files for both the input and output audio of the layout and can be useful for detecting issues with audio drivers and debugging unexpected results. Also refer to aweOS_audioRecordingDisable and aweOS_audioRecordingRegisterNotificationCallback for more details.