Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

This section describes the portion of the C run-time environment used by audio modules.  This includes the module processing and constructor functions, memory allocation, and how the modules interface to the Server running on the target.

Each audio module has an associated header file ModClassName.h containing a class definition, and a source file ModClassName.c containing the associated class structure and functions.  The header and source files are generated by MATLAB, but it is important to understand their contents and how all of the pieces fit together.

1.1.  Data types and Structure Definitions

Let's begin by looking at the class definition for the smoothly varying scaler found in ModScalerSmoothedExample.h:

typedef struct _awe_modScalerSmoothedExampleInstance

{

    ModuleInstanceDescriptor instance;

    FLOAT32            gain;                // Target gain

    FLOAT32            smoothingTime;       // Time constant of the smoothing process

    FLOAT32            currentGain;         // Instantaneous gain applied by the module.  This is also the starting gain of the module.

    FLOAT32            smoothingCoeff;      // Smoothing coefficient

   

} awe_modScalerSmoothedExampleInstance;

 

The first field, "instance", is the common header used by all audio modules and the type ModuleInstanceDescriptor is defined in

<AWE>\AWEModules\Source\Examples\Include\Framework.h. 

Framework.h contains the primary data types and definitions used by Audio Weaver.  Most of the definitions are used internally by the Server, but a few apply to audio modules.

1.1.1.    Module Instance Structure

Following the instance header are all of the module specific variables.  Only 8 types of module variables are currently supported:

int – 32-bit signed integer

uint – 32-bit unsigned integer

float – 32-bit floating-point value

fract32 – 32-bit fractional in 1.31 format

int * - pointer to an array of signed integers

uint * - pointer to an array of unsigned integers

float * - pointer to an array of floating-point values 

fract32 * - pointer to an array of fractional values

You'll note that only 32-bit data types are currently supported.  This is for simplicity, but may be expanded in the future.

All scalar module variables must precede arrays in the instance structure.  This is due to the manner in which audio modules are allocated and initialized by the Server.  The MATLAB module generation scripts automatically reorder variables (using the function awe_reorder_variables.m).  Detailed description of awe_reorder_variables.m is found in Section 5.1.9.

The module instance descriptor is also defined within Framework.h:

/** A module instance. The instance is variable length on the heap, the size

being determined by the constructor arguments. */

/*private*/ typedef struct _ModuleInstanceDescriptor

{

      /** The basic instance data. */

      ModInstanceDescriptor instanceDescriptor;             /* 0 */

 

      /** Pointer to owning layout instance (type is a forward reference). */

      struct _LayoutInstance *pOwner;                                   /* 12 */

 

      /** Array of input and output wires. There are nInwires + nOutWires + nScratchWires elements. */

      WireInstance **pWires;                                            /* 16 */

 

      /** Pump function for the module. May be empty, pProcessFunc, or, pBypassFunc. */

      void (*pProcessFunc)(void *pInstance);                      /* 20 */

 

      /** Control flags. */

      UINT32 packedFlags;                                                     /* 24 */

 

      /** Module specific profiling time. This is number of cycles times 256. */

      UINT32 profileTime;                                                     /* 28 */

}

ModuleInstanceDescriptor;           /* 32 bytes */

 

The module instance header is 8 words in length and contains the fields:

instanceDescriptor – 3 word data structure that allows the Server to keep track of the module once it has been allocated on the target.

pOwner – points to the overall layout, or collection of audio modules.

pWires – pointer to an array of wires used by the module.  Wires are ordered as inputs, outputs, and scratch wires.

pProcessFunc – pointer to the module's current run-time function.  This points to the processing function if the module is ACTIVE.

packedFlag – a packed integer containing the number of input, output, and scratch pins, as well as the current modules status.

profile_time – CPU profiling information.  This is measured on a block-by-block basis and smoothed by a first order filter.  Time is measured in units of clock ticks, where the resolution is platform dependent.  Some platforms, such as the SHARC and Blackfin, are cycle accurate.

Access to the module instance fields is via macros defined in Framework.h.  You should use these macros since it enables us to change the underlying instance structure without breaking existing modules.  Let S be a pointer to an audio module.  The following macros are provided:

ClassModule_GetWires(S) – returns a pointer to the array of wires associated with the module.  The wires are ordered as input wires, output wires, and then scratch wires.

ClassModule_GetNInWires(S) – returns the number of input wires.

ClassModule_GetNOutWires(S) – returns the number of output wires

ClassModule_GetNScratchWires(S) – returns the number of scratch wires

ClassModule_GetModuleState(S) – returns the module's run-time state.  ACTIVE=0, BYPASSED=1, MUTED=2, and INACTIVE=3.

1.1.2.    Pins and Wires

A wire in Audio Weaver contains more than just audio samples.  A wire is defined as:

typedef struct _WireInstance

{

      /** The basic instance data. */

      InstanceDescriptor instanceDescriptor;

 

      /** The wire buffer. */

      Sample *buffer;

 

      /** Wire sample rate. */

      float sampleRate;

 

      // Number of channels         10 bits - 0x3ff

      // Max block size             17 bits - 0x7fffC00

      // isComplex                        1 bit - 0x8000000

      // Sample size in bytes.      4 bits - 0xf0000000

      UINT32 wireInfo1;

 

      // Block size                       17 bits - 0x1ffff

      // Data Type                        6 bits - 0x7e0000

      UINT32 wireInfo2;

 

      // Rows                                   10 bits - 0x3ff

      // Columns                          10 bits - 0xffc00

      UINT32 wireInfo3;

 

      /** Bind chain pointer default NULL. */

      struct _WireInstance *m_pNextBind;

 

      /** What its bound to default NULL. */

      IOPinDescriptor *m_pBoundPin;

 

#ifndef USE_TEST_PADDING

#if defined(BUILD64) || defined(AWE_STORAGE_ALIGN4)

      UINT32 padding2;

#endif

      UINT32 padding;

#endif

}

WireInstance;

 

The data type WireInstance describes a wire.  To get a pointer to the data portion of the first wire, use the macro:

Sample *buffer = (pWires[0]->buffer);

*buffer points to the buffer of audio samples.  The data type Sample is a union of integer and floating point values:

typedef union _Sample

{

      INT32 iVal;

      UINT32 uiVal;

      float fVal;

}

Sample;

 

Since Sample is a union, you must cast it to one of the possible data types:

(float *) W->buffer;

(INT32 *) W->buffer;

(fract32 *) W->buffer;

(UINT32 *) W->buffer;

The fract32 data type is actually defined as int32.  Providing the fract32 data type is for convenience allowing the programmer to distinguish between integer and fractional values.

A wire can have an arbitrary number of interleaved channels and an arbitrary block size (number of samples per channel).  A wire with N channels and a block size B has a total of NB audio samples.  The samples are ordered as:

Channel 0, Sample 0

Channel 1, Sample 0

Channel 2, Sample 0

...

Channel N-1, Sample 0

Channel 0, Sample 1

Channel 1, Sample 1

Channel 2, Sample 1,

      ...

      ...

Channel N-1, Sample B-1

The items in the wire's pin descriptor are accessed via macros:

ClassWire_GetChannelCount(W) – returns the number of channels in the wire.

ClassWire_GetBlockSize(W) – returns the block size of the wire.

ClassWire_GetNumSamples(W) – returns the total number of samples in the wire.  This equals the number of channels times the block size.

ClassWire_GetSampleRate(W) – returns the sample rate of the data in the wire.  This is used by design functions, such as filter coefficient generation, that depend upon the sample rate of the data.  The sample rate is in Hz.

A pin descriptor structure can be shared by multiple wires as long as the number of channels, block size, and sample rate are the same.  Allocating and assigning pin descriptors is handled by MATLAB when a system is being built.

You'll note that the pin descriptor does not specify the type of data (float, int, or fract32) contained in the wire.  This allows a wire containing floating-point data to be reused for integer data as long as the number of channels, block size, and sample rate are identical.

1.1.3.    Examples

The absolute value module has a single input and output pin, and can handle an arbitrary number of channels, block size, and sample rate.  S is a pointer to the module instance.  The processing code is:

void ModuleAbs_Process(void *pInstance)

{

  ModuleAbsClass *S = (ModuleAbsClass *)pInstance;

  WireInstance **pWires = ClassModule_GetWires(S);

   

  Vec_Abs((float *)pWires[0]->buffer, 1,

        (float *)pWires[1]->buffer, 1,

        ClassWire_GetNumSamples(pWires[0]));

}

where

(float *) pWires[0]->buffer points to the buffer of input samples.

(float *) pWires[1]->buffer points to the buffer of output samples

ClassWire_GetNumSampels(pWires[0]) is the total number of samples in the input wire.

Vec_Abs() is an optimized vector function for computing the absolute value.

A second example is the processing function found ModScalerN.c.  This module has a single input pin with multiple channels.  Each channel is scaled by a separate gain. The processing function is:

void ModuleScalerN_Process(void *pInstance)

{

  ModuleScalerNClass *S = (ModuleScalerNClass *)pInstance;

  WireInstance **pWires = ClassModule_GetWires(S);

   

  UINT blockSize = ClassWire_GetBlockSize(pWires[0]);

  UINT numChannels = ClassWire_GetChannelCount(pWires[0]);

  UINT channel;

  float *dst;

  float *src;

   

  src = (float *) pWires[0]->buffer;

  dst = (float *) pWires[1]->buffer;

   

  for(channel=0;channel<numChannels;channel++)

    {

      Vec_Scale(src+channel, numChannels,

                dst+channel, numChannels,

                S->gain[channel], blockSize);

    }

}

This module has a multichannel input and output pin.  At the start of the processing function, the number of channels and block size are determined as well as pointers to the input and output buffers.  The module then loops over each channel scaling it by the appropriate scale factor from the gain[] array.  The arguments to the Vec_Scale function are:

src+channel – pointer to the buffer of input data.

numChannels – stride for the input data.

dst+channel – pointer to the buffer of the output data

numChannels – stride for the output data

S->gain[channel] – scale factor for a particular channel.

blockSize – number of samples to scale.

You can clearly see that the data is interleaved based on the offset to the start of the src and dst data as well as the strides. 

Both of the module examples use a set of "vector" functions to perform the actual computation.  It is possible to perform the computation within the module's processing function – many modules do this.  For some modules, however, we have chosen to use vector functions since smaller functions may be more easily optimized in assembly and a single function may be reused by multiple modules.  For example, Vec_Scale() is used by the scaler_module.m, scalern_module.m, and scaler_db_module.m.  Thus, by optimizing a single function vector for a target processor we obtain 3 optimized modules.

1.2.  Class Object and Constructor Functions

The module constructor function is called in response to a create_module command sent from the Server to the target.  A module's constructor function is called once per module instance and is responsible for allocating memory for the module and copying arguments from the create_module command into the instance structure. 

The operation of the constructor function hinges on information within the module's class structure.  All audio modules of the same class share a single module class structure.  Thus, if a target contains 100 different classes of audio modules, there will be 100 class structures defined.  The class structure is defined in Framework.h as:

/** Class descriptor for audio modules. */

typedef struct _ModClassModule

{

      /** The basic class data. */

      ModClassDescriptor modClassDescriptor;                      /* 0 */

 

      /** Pump function for the module. */

      void (*pProcessFunc)(void *pInstance);                      /* 8 */

 

      /** Bypass function for the module. */

      void (*pBypassFunc)(void *pInstance);                       /* 12 */

 

      /** Set function. */

      UINT32 (*pSet)(void *pInstance, UINT32 mask);         /* 16 */

 

      /** Get function. */

      UINT32 (*pGet)(void *pInstance, UINT32 mask);         /* 20 */

 

      /** Module version information */

      UINT32 nModuleVersion;                                            /* 24 */

 

      /** The number of construction module parameters. */

      UINT32 nPackedParameters;                                         /* 28 */

 

      /** Vector of 64 bits specifying the type of constructor

      arguments. Bit 0 starts at the first member past the

      instance object. Bit set means argument is float.

      Bits past the last argument are don't care, should be 0. */

      UINT32 bitVectorFloat[2];                                         /* 32 */

}

ModClassModule;         /* 40 bytes */

 

The first item of type ModClassDescriptor is defined as:

typedef struct _ModClassDescriptor

{

      /** All constructors look the same. To make debugging easy, we pass a generic

      array of words to the constructor, which needs to figure out what to do with

      them.

 

      On return, we have a pointer to an initialized instance ready for use.

      */

      struct _ModInstanceDescriptor *(*Constructor)(

                  INT32 * FW_RESTRICT retVal,

                  UINT32 nIO,

                  WireInstance **pWires,

                  size_t argCount,

                  const Sample * FW_RESTRICT args);         /* 0 */

 

      /** Unique ID of the class - set at compile time. */

      UINT32 classID;                                                   /* 4 */

}

ModClassDescriptor;           /* 8 bytes */

 

 

Let's look at each of these fields more closely.

classID – is a unique integer that identifies the module on the target.

When the target is asked to create a scaler_module.m, it is asked to create a module with a specific integer class ID.  In order to ensure that all classID's are unique, they are stored as offsets in the file classids.csv.  When asked to construct a module, the target searches the table of module class pointers looking for the specified classID.  If there is a match, then the Constructor function, also contained within ModClassDescriptor is called. 

Constructor() -  pointer to the module's constructor function.

The constructor function takes a specific set of arguments:

Constructor(int * FW_RESTRICT retVal,

          UINT nIO,

          WireInstance ** FW_RESTRICT pWires,

          size_t argCount,

          const Sample * FW_RESTRICT args);

Now returning to the other fields in ModClassModule.  The modClassDescriptor is followed by:

  UINT nPackedParameters;

This integer specifies the number of constructor arguments required by the module.  Two separate values are packed into this 32-bit integer and these are accessed via separate macros:

ClassMod_GetNPublicArgs(packed) – returns the number of public arguments that the module requires.  The number of public arguments equals the number of scalar variables that must be sent from the Server to the Constructor() function.

ClassMod_GetNPrivateArgs(packed) – returns the number of private arguments required by the module.  The private arguments are not sent by the Server but must be separately managed by the Constructor function.  Private arguments typically hold pointers to array variables.

The total size of a module's instance structure equals sizeof(ModClassModule) + # of public words + # of private words.

The remaining items in the module class structure are pointers to module functions.

pProcessFunc – pointer to the module's processing function.  Called when the module is in ACTIVE mode.

pBypassFunc – pointer to the module's bypass function.  Called when the module is in BYPASSED mode.

pSet – pointer to the module's set function.  This function is called after the Server updates one of the module's variables.  This function is used to implement custom design functions on the target.

pGet – pointer to the module's get function.  This function is called prior to reading one of the module's instance variables by the Server.

pSetState – pointer to the module’s set module state function. This function is called prior to change the state of the module. Most of the module’s will have this field as NULL.

A module must provide processing and bypass functions; all other functions are optional.

Let's examine the class structure for the biquad_module.m.  This module implements a 5 multiply second order IIR filter.  The module's class structure defined in ModBiquad.c is:

const ModClassModule ClassModule_Biquad =

    {

#ifdef AWE_STATIC_CODE

      { CLASSID_ModuleBiquad, ModuleBiquad_Constructor },

#else

      { CLASSID_ModuleBiquad, NULL },

#endif

      ClassModule_PackArgCounts(5, 1),    // (Public words, private words)

      awe_modBiquad_Process,                    // Processing function

      IOMatchUpModule_Bypass,                   // Bypass function

#ifndef AWE_NO_SETGET_FUNCTIONS

      0,                                  // Set function

      0,                                  // Get function

#else

      0,                                  // Set function

      0,                                  // Get function

#endif

      0,                                  // Set State function

    };

The CLASSID_ModuleBiquad equals a base value plus an offset:

CLASS_ID_MODBASE + 13

The offset is defined in the file classids.csv:

Biquad,13

The module has a custom constructor function, ModuleBiquadConstructor(), and a total of 5 public instance variables and 1 private instance variable.  Looking at the module's instance structure, we see:

typedef struct _ModuleBiquad

{

  ModuleInstanceDescriptor instance;

  float           b0;

  float           b1;

  float           b2;

  float           a1;

  float           a2;

  float*          state;

} ModuleBiquadClass;

The 5 public variables are the filter coefficients: b0, b1, b2, a1, and a2.  The private variable is the pointer to the array of state variables.  The Biquad module operates on multichannel signals and allocates two state variables per channel.  The Biquad module utilizes the processing function awe_modBiquad_Process(), the bypass function IOMatchUpModule_Bypass(), and does not define a Get or Set function.

The module constructor function first allocates memory for the instance structure, then sets public variables using supplied values, and finally, allocates memory for any arrays.  Constructor functions all have a similar structure exemplified by the constructor for the Biquad module.  Starting with the function arguments:

ModInstanceDescriptor *ModuleBiquad_Constructor(int * FW_RESTRICT retVal,

                                    UINT nIO,

                                    WireInstance ** FW_RESTRICT pWires,

                                    size_t argCount,

                                    const Sample * FW_RESTRICT args)

{

  ModuleBiquadClass *S = (ModuleBiquadClass *)

    BaseClassModule_Constructor(&ClassModule_Biquad, retVal, nIO,

pWires, argCount, args);

 

  if (S == NULL)

    {

      return 0;

    }

 

  if ((S->state = (float *) awe_fwMalloc(ClassWire_GetChannelCount(pWires[0]) * 2 * sizeof(float),

                  AE_HEAP_FAST2SLOW, retVal)) == 0)

    {

      // Error code is in *retVal

      return 0;

    }

   

*retVal = E_SUCCESS;

return ((ModInstanceDescriptor *) S);

}

The function arguments are:

retVal – pointer to the function's error return value.  If successful, the function sets *retVal = E_SUCCESS.

nIO – a packed integer specifying the number of input, output, and scratch wires used by the module.

pWires – pointer to the array of wires used by the module.

argCount – total number of arguments supplied to the constructor.  Typically, the constructor arguments are computed by MATLAB and supplied to the create_module Server function.

args – pointer to the list of function arguments.  The function arguments can be either integer or floating-point values and are specified as a union.  The constructor function must know whether each argument is an integer or floating-point value.  arg[0].iVal is the first argument as an integer; arg[0].fVal is the first argument as a floating-point value; arg[0].uiVal is the first argument as an unsigned integer.

The first responsibility of the Constructor function is to call BaseClassModule_Constructor().  This function does several things:

Allocates memory for the module's instance structure.

Verifies that argCount equals the number of public instance variables specified in the module's class structure.

Copies the initial values of the public variables from args[] to the variables at the start of the instance structure.  (This method of initializing module variables requires that the public module variables be located contiguously at the start of the instance structure.)

Sets the pWires and packedFlags fields of the ModuleInstanceDescriptor.

Configures the module for ACTIVE mode.

The Constructor function next allocates memory for the state array;

if ((S->state = (float *) awe_fwMalloc(ClassWire_GetChannelCount(pWires[0]) * 2 * sizeof(float), AE_HEAP_FAST2SLOW, retVal)) == 0)

    {

      // Error code is in *retVal

      return 0;

    }

Note that the size of the array is computed based on the number of channels in the first input wire.  The function awe_fwMalloc() allocates memory from one of Audio Weaver's memory heaps.  The heap is specified by the second argument: AE_HEAP_FAST2SLOW and user can find all the macros defined in Framework.h.

Lastly, the Constructor function sets the return value and returns a pointer to the allocated and initialized object:

*retVal = E_SUCCESS;

return ((ModInstanceDescriptor *) S);

The instance pointer is cast to a common ModInstanceDescriptor return type.

The Biquad module was used as an example to illustrate the constructor function because it requires an array to be allocated.  If the Constructor function pointer in the module's class structure is set to NULL, then the generic module constructor is called.  The generic constructor simply calls BaseClassModule_Constructor() and is sufficient for modules that do not require additional memory allocation outside of the instance structure or other custom initialization.  The generic constructor is sufficient for a large number of modules and reduces the code size of the target executable.

1.3.  Memory Allocation using awe_fwMalloc()

Dynamic memory allocation is accomplished in Audio Weaver by the function awe_fwMalloc().  To avoid memory fragmentation, memory can only be allocated but never individually freed.  You can only free all memory completely using the destroy command and then start again.

The memory allocation function is declared as:

void *awe_fwMalloc(size_t size, UINT heapIndex, int *retVal);

All memory allocation in Audio Weaver occurs in units of 32-bit words.  The first argument to awe_fwMalloc() specifies the number of 32-bit words to allocate.

The second argument, heapIndex, specifies which memory heap should be used for allocation.  Audio Weaver provides 3 heaps and the heaps match the memory architecture of the SHARC processor.  There are two separate internal heaps – traditionally referred to as "DM" and "PM" on the SHARC – which are placed in different memory blocks; and a large heap in external memory.  Having separate internal memory heaps is required to obtain optimal performance from the SHARC.  For example, an FIR filter implemented on the SHARC processor requires that the state and coefficients be in separate memory blocks.  On the Blackfin with cache enabled, the heaps specifiers are irrelevant.

Audio Weaver refers to the heaps as "FAST", "FASTB", and "SLOW".  The heapIndex argument is set using #define's in Framework.h.  In the simplest case, you specify one of the following:

AWE_HEAP_FAST – fast internal memory.  Defined as 1.

AWE_HEAP_FASTB – a second internal memory heap located in a different block of memory than AWE_HEAP_FAST.  Defined as 2.

AWE_HEAP_SLOW – slower memory; usually external.  Defined as 3.

The heapIndex argument can encode several different heaps one nibble (4-bits) at a time.  Set the low 4 bits of heapIndex to the heap you want to allocate memory from first; then set bits 4 to 7 to the next heap; and so forth.  For example, if you want to allocate memory from AWE_HEAP_FAST and then overflow into AWE_HEAP_SLOW, set the heapIndex argument to:

AWE_HEAP_FAST | (AWE_HEAP_SLOW << 4)

Several variations are already defined for you. 

#define AWE_HEAP_FAST2SLOW (AWE_HEAP_FAST | (AWE_HEAP_FASTB << 4) | (AWE_HEAP_SLOW << 8))

Allocate in the FAST heap first.  If this fails, then allocate in FASTB.  Finally, if this fails, then allocate SLOW external heap.

#define AWE_HEAP_FASTB2SLOW (AWE_HEAP_FASTB | (AWE_HEAP_SLOW << 4))

Allocate in FASTB.  If this fails, then allocate from the SLOW heap.

Most modules supplied with Audio Weaver take advantage of this overflow behavior to smoothly transition from internal to external memory once the heaps have filled up.

1.4.  Module Function Details

Each audio module has a set of 5 functions associated with it.  This section describes the arguments to each of the functions.

1.4.1.    Constructor Function

This function is usually generated by MATLAB but at times it needs to be hand written – especially if the module does more than just allocate memory.  Audio Weaver names the constructor function using the convention

awe_modCLASSNAMEConstructor()

where CLASSNAME is the module class.  The function allocates memory for the module from the heaps and then returns a pointer to the newly created object.  The constructor function is declared as:

ModInstanceDescriptor *awe_modCLASSNAMEConstructor(

int * FW_RESTRICT retVal,

UINT nIO,

WireInstance ** FW_RESTRICT pWires,

size_t argCount,

const Sample * FW_RESTRICT args)

The return type ModInstanceDescriptor is a common data structure which starts off all audio modules.  The function arguments are:

*retVal – integer return value.  The constructor should set this to E_SUCCESS (=0) upon successful completion.  The file Errors.h has a complete list of error codes to choose from.

nIO – packed integer specifying the number of input, output, scratch, and feedback wires.  Each number is packed into 8-bits as follows:

INPUT + (OUTPUT >> 8) + (SCRATCH >> 16) + (FEEDBACK >> 24)

Several macros are provided in Framework.h to extract the wire counts:

#define GetInputPinCount(x)   (x & 0xff)

#define GetOutputPinCount(x)  ((x >> 8) & 0xff)

#define GetScratchPinCount(x) ((x >> 16) & 0xff)

#define GetFeedbackPinCount(x)((x >> 24) & 0xff)

 

#define GetWireCount(x)       ( ((x >> 24) & 0xff) + ((x >> 16) & 0xff)

 + ((x >> 8) & 0xff) + (x & 0xff))

pWires – a pointer to an array of wire pointers.  The wires are ordered as input, output, and scratch. 

argCount – number of optional arguments supplied in the Sample array.

args – array of optional arguments.  This array is of type Sample which is union:

typedef union _Sample

{

      int iVal;

      unsigned int uiVal;

      float fVal;

}

Sample;

Each argument can thus be a floating-point, or signed or unsigned integer value.

The module constructor function should first call

ModInstanceDescriptor *BaseClassModule_Constructor(

            const ModClassModule * FW_RESTRICT pClass,

            int * FW_RESTRICT retVal,

            UINT nIO,

            WireInstance ** FW_RESTRICT pWires,

            size_t argCount,

            const Sample * FW_RESTRICT args);

The first argument, pClass, is a pointer to the class object for the module under construction.  The remaining arguments are identical to the Constructor arguments.  For example, in ModPeakHold.c, we have

awe_modPeakHoldInstance *S = (awe_modPeakHoldInstance *)

BaseClassModule_Constructor(&awe_modPeakHoldClass, retVal,

nIO, pWires, argCount, args);

The BaseClassModule_Constructor allocates memory for the module instance structure and wire pointer array used by the module.  The function also initializes the module instance variables by copying argCount words from the args array starting to the first instance variable in the structure.  The class structure awe_modPeakHoldClass contains the entry

ClassModule_PackArgCounts(5, 2),    // (Public words, private words)

Thus an instance of a peak hold module has a total of 7 variables with the first 5 being set at construction time and the last 2 used internally (or arrays)  argCount must then equal 5 and BaseClassModule_Constructor checks for this.  The remaining two variables in the instance structures are pointers to arrays which are separately initialized by the constructor function.

The final step performed by BaseClassModule_Constructor is to add the newly created module instance to the linked list of objects on the target and then return a pointer to the module instance.

After calling BaseClassModule_Constructor, the module constructor performs any other initialization that is needed.  This may include initializing indirect array variables, like in the ModPeakHold.c example.  When done, the constructor function sets the return error value in *retVal and returns a pointer to the created module instance.

The module constructor function is typically called by the framework in response to a message received from the PC over the tuning interface or from a message stored in a file system.  After a module constructor function is called, the framework checks if the module has an associated Set function.  If so, the Set function is called with a mask of 0xFFFFFFFF.  Thus there is no need to manually call the Set function within the Constructor.  The framework takes care of this.

1.4.2.    Processing Function

The processing function has the signature

void awe_modPeakHoldProcess(void *pInstance)

The function only gets a single argument which is a pointer to the module instance.  This is first cast to the appropriate module instance data type.  For example

awe_modPeakHoldInstance *S = (awe_modPeakHoldInstance *)pInstance;

Using macros defined in Framework.h, the processing function can obtain a pointer to the list of wires used by the module

WireInstance **pWires = ClassModule_GetWires(S);

The number of input wires

int numInWires = ClassModule_GetNInWires(S);

The number of output wires

int numOutWires = ClassModule_GetNOutWires(S);

The number of scratch wires

int numScratchWires = ClassModule_GetNScratchWires(S);

And the current run-time state of the module

int state = ClassModule_GetModuleState(S)

where

MODULE_ACTIVE = 0

MODULE_BYPASS = 1

MODULE_MUTE = 2

MODULE_INACTIVE = 3

Note, the processing function is only called when the state is active so checking the state is meaningless in the processing function.

The wire buffers are ordered as input, output, and scratch wires.  Each wire has certain properties that you can query using macros:

ClassWire_GetBlockSize(W)

ClassWire_GetChannelCount(W)

ClassWire_GetSampleRate(W)

ClassWire_GetNumSamples(W)

where the last macro returns the total number of samples in the wire and equals blockSize x numChannels.

Each wire contains a pointer to its data samples in the 🡪buffer field.  buffer is defined as the union Sample and must be cast to a pointer of the appropriate type.  For example,

(float *) (pWires[0]->buffer)

The processing function then performs the audio processing and writes the result into the output wire buffers.  Local state variables in the instance structure are updated, as needed.

Note that the processing function should never overwrite the data contained in the input wires; it may be used by other modules.

The processing function does not return an error code.  If you need to track error conditions, do this by adding an error code to the instance structure and then tracking it in the inspector.

1.4.3.    Set Function

This function implements a module's control functionality and typically converts high-level variables to low-level variables.  The PeakHold module has the function signature

UINT awe_modPeakHoldSet(void *pInstance, UINT mask)

The first argument is a pointer to the module instance structure.  This needs to be cast to a pointer of the appropriate type:

awe_modPeakHoldInstance *S = (awe_modPeakHoldInstance *)pInstance; 

Once you have a pointer to the module instance, you have full access to the module wires using the macros defined in Framework.h.  This is useful, for example, when determining the sample rate or the block size of the module.

The second argument to the Set function is a bit mask that specifies which module variable(s) have changed.  The bit mask is set by the Server when module variables are updated while tuning.  The bit mask may also be set by control code running on the target.  Use of the bit mask is optional and is only really necessary when the module writer needs to distinguish between heavy duty changes requiring significant amounts of computation and lightweight changes.

A bit mask of 0xFFFFFFFF is used to force all variables in the Set function to update. This value of the bit mask is used in a call to the Set function after the module is constructed but before it is pumped for the first time. This ensures that all module variables are updated before they are used.

A special case of the bit mask is when it is equal to 0. When the destroy tuning command is processed by the AWECore, the Set function of every module is called with a mask of 0. This allows the module to do any necessary clean up operations. For example, if your module does file I/O you could close the file at this time. Or you might free memory that is dynamically allocated outside of the Audio Weaver heaps.

In the bit mask, bit N is set when the Nth module variable is set.  The bit counting includes the 8 word module instance header and thus the first tunable module variable is numbered 8.  The module header file contains a set of #define's indicating the bit value for each variable.  For example, ModPeakHold.h contains

#define MASK_PeakHold_Reset 0x00000100

#define MASK_PeakHold_attackTime 0x00000200

#define MASK_PeakHold_decayTime 0x00000400

#define MASK_PeakHold_decayCoef 0x00000800

#define MASK_PeakHold_attackCoef 0x00001000

#define MASK_PeakHold_peakHold 0x00002000

#define MASK_PeakHold_peakDecay 0x00004000

A few more things to keep in mind regarding masks.  When an array variable is set, the bit mask corresponding to the array is set as expected.  However, the Set function is not told which portion of the array has been changed.  Finally, since the mask is only 32 bits in length, it is possible to run out of unique bits if the instance structure is very long.  The high bit of the mask, bit 31, has special meaning.  When this bit is set, it indicates that at least one of the high order instance variables has changed.  However, you don't know precisely which one.

1.4.4.    Get Function

This function is rarely used in practice and translates low-level variables to higher-level variables.  This function has the same signature as the Set function

UINT awe_modClassNameGet(void *pInstance, UINT mask)

When called, the function should update instance variables and can use the mask argument to determine which variables should be updated.

1.4.5.    BypassFunction

This function implements a module's bypass functionality.  The function signature is identical to the processing function

 void awe_modClassNameBypass(void *pInstance)

There are several standard bypass functions defined in ModCommon.h and may be used by modules.

IOMatchModule_Bypass() – Works with modules that have the same number of input as output wires.  It copies the data from the Nth input wire to the Nth output wire.  It assumes (and does not check) that all input and output wires have the exact same number of samples.

IOAcrossModule_Bypass() – Works with modules that have a single input wire and single output wire.  The wires may have a different number of channels, though.  Let INCOUNT and OUTCOUNT represent the number of channels in the input and output wires, respectively.  The function copies the first min(INCOUNT, OUTCOUNT) channels across one at a time from input to output.  If OUTCOUNT > INCOUNT, then the remaining output channels are set to 0.

IOAllOnesModule_Bypass() – Sets all samples in the output wire to floating point 1.0.  This is used by some dynamics processors which compute time-varying gains.

IOMatchUpModule_Bypass() – Suitable for modules that have an arbitrary number of input and output wires.  The function starts with the first output wire and looks for the first input wire that matches it in terms of block size and number of channels.  If a match is found, the data from that input wire is copied to the output wire, and this input wire is no longer used for subsequent matches.  If not match is found for a particular output wire, then all samples are set to 0.0.  This process repeats for all output wires.

If a module's m-file does not specify a bypass function, then the code generator assigns IOMatchUpModule_Bypass() by default.

 

 

Generating Module Libraries

This section provides further details on module generation. We begin by discussing template substitution, the underlying process by which the generated .c and .h files are created.  The section also documents MATLAB commands that are related to module generation.  Many of the commands shown were used in previous examples and are fully documented here.

The main phases in creating an audio module library are:

  1. For each audio module in the library

a.     Add code markers

b.     Generate the module's .c and .h files using template substitution

  1. Generate the combined schema file describing all of the modules in the library

  1. Translate the schema information into a C data structure that can be compiled and included with the DLL.

  1. Compile the generated files and create an audio module dynamic link library (DLL).

Steps 1 and 3 are automated using the awe_generate_library.m command.  Many other MATLAB functions are internally called. Only a few other functions are documented here to help you better understand the code generation process.  Step 4 is done separately in VisualStudio and described in Section 6.

1.5.  awe_generate_library.m

awe_generate_library(MM, DIR, LIBNAME, USESDLLS, GENDOC)

This is the primary function for generating an audio module library.  It generates sources files for all of the modules in the library.  Arguments:

MM – cell array of audio module structures.

DIR – base directory in which the generated files should be placed.  Files are written here and in several other subdirectories.

LIBNAME – string specifying the name of the library.  For example, 'Advanced'.

USESDLLS – structure specifying other module libraries that the newly built library depends upon.  A library has a dependencies when it internally utilizes a module contained in another library.  By default, USESDLLS=[] and the library is independent of others.

GENDOC – integer indicating whether module documentation should be generated.  By default, GENDOC=0 and no documentation is generated.  If GENDOC=1, then a single document is created for the entire module library.  If GENDOC=2, then a separate document is created for each audio module.

1.5.1.    Creating the Cell Array of Modules

The argument, MM, is a cell array populated with modules to be placed in the final library.  For example, it might contain:

MM=cell(0,0);

MM{end+1}=downsampler_example_module('temp');

MM{end+1}=fader_example_fract32_module('temp');

MM{end+1}=fader_example_module('temp');

MM{end+1}=lah_limiter_example_module('temp');

MM{end+1}=peak_hold_example_fract32_module('temp');

MM{end+1}=peak_hold_example_module('temp');

MM{end+1}=scaler_example_module('temp');

MM{end+1}=scaler_smoothed_example_module('temp');

1.5.2.    Specifying Dependencies

USESDLLS is a cell array of structures that specifies dependencies.  Each structure contains the fields:

.MM – cell array of audio modules in the library.

.str – library name.

1.5.3.    Specifying the Output Directory

The argument DIR to awe_generate_library.m specifies the directory in which to generate the modules.  There are different ways to specify the directory including hard coding it.

Often it is useful to specify the directory relative to the location of the master library make script.  The following MATLAB code, contained in make_examples.m, shows how to determine the location of make_examples.m and then pull off the lowest level directory.  make_examples.m is located at:

<AWE>\AWEModules\Source\Examples\matlab\

The output directory is set to

<AWE>\AWEModules\Source\Examples\

by the code below.

MFILE=mfilename('fullpath');

[pathstr, name]=fileparts(MFILE);

 

% Remove the last directory level

ind=find(pathstr == filesep);

ind=max(ind);

 

DIR=pathstr(1:ind-1);

1.5.4.    Generated Files

Let DIR be the base directory for the audio module library and let 'Examples' be the name of the library.  awe_generate_library.m creates the following files:

<DIR>\Examples.h – Header file containing extern definitions for each module class object.  It also has macros defining the entire list of modules and all dependencies.

<DIR>\Examples.sch – Overall schema file for the library.

<DIR>\ExamplesSchema.cpp – Compiled schema file.

<DIR>\Doc – Location of generated documentation

<DIR>\Include – Location of generated module include files

<DIR>\matlab – Location of the module m-files.  This also contains the master library make script.

<DIR>\matlab\code – Suggested location for the inner code pieces.

<DIR>\matlab\process – Suggested location of the MATLAB versions of the processing functions.

<DIR>\matlab\test – Suggested location of MATLAB test scripts which validate the operation of the modules.

<DIR>\Source – Location of the generated module source files.

<DIR>\xml – Module XML file for AWE Designer, explained in section 3.12.

1.5.5.    SchemaBuilder.exe

You'll notice this executable within the Audio Weaver Bin directory.  This executable compiles the schema information.  That is, it takes a .sch file and compiles into a .cpp file.  The .cpp file is then included in the project for building the module DLL.  SchemaBuilder.exe is automatically called by awe_generate_library.m and there is no need to call it separately.

1.5.6.    AWE_INFO.buildControl

The global variable AWE_INFO contain the structure .buildControl which controls the library generation process.  AWE_INFO.buildControl contains many fields which are used internally.  We point out the user settable values.

AWE_INFO.buildControl.combineSourceFiles

This Boolean specifies whether each audio module should be placed into a separate source file.  By default, combineSourceFiles=0 and each module is written to a separate file.

AWE_INFO.buildControl.indentSourceFiles

This Boolean specifies whether the source code should be formatted after generation using the executable indent.exe.  Formatting the code regularizes identing, line breaks, etc.  By default, indentSourceFiles=0.  Formatting the source code significantly slows down the module generation process.

1.5.7.    Setting the Audio Module Search Path

When you are developing a custom audio module, you must add the base module directory to the Audio Weaver module search path.  It is not enough to merely modify your MATLAB path. The path can be updated in Designer using the ‘File->Set Module Path’ menu option. To add a directory to the Audio Weaver module search path programmatically, use the command

add_module_path(DIR)

The command adds the directory

<DIR>\matlab

to the MATLAB search path; this directory has to exist.  It then optionally adds

<DIR>\matlab\test

<DIR>\matlab\process

if these directories exist.  In addition, the function modifies the global variable

AWE_INFO.buildControl.modulePath

to include the new directory.  This global variable is used when searching for class IDs.

The newly added module directory is placed at the end of the module search path.  You can specify that the directory be added to either the beginning or end of the module path using the optional second argument

add_module_path(PATH, '-begin');

add_module_path(PATH, '-end');

 

 

1.5.8.    Specifying Class IDs

Every audio module in the library must have a unique class ID.  This 32-bit integer is specified in the file

<DIR>\classids.csv

This comma separated text file contains entries of the form

ClassName,ID

The file can also contain a single entry at the start of the form:

IDOFFSET=32768

The IDOFFSET is added to each ID listed in the file.  Using IDOFFSET allows you to quickly make wholesale changes to all of the modules in the library.

DSP Concepts reserves class IDs in the range 0 to 32767 for module libraries shipped with Audio Weaver.  You are free to use any IDs in the range 32768 to 63487 for your custom modules.

Audio Weaver provides a function for looking up classIDs across a range of audio module libraries.

ID=classid_lookup(CLASSNAME)

You pass the function a class name string and the classID is returned. The function works by using the audio module directory search path contained in

AWE_INFO.buildControl.modulePath

For each directory, the file classids.csv is opened and examined.  The function classid_lookup.m uses internal caching to speed up the search process.

1.5.9.    Reordering of Render Variables

As part of the build process, Audio Weaver reorders the variables in a module or subsystem to match the manner in which modules are instantiated by the Server.  The reordering is performed by the function

M=awe_reorder_variables(M)

where M is an @awe_module or @awe_subsystem object.  Variables are ordered according to the rules

  1. All non-hidden scalar variables

  1. All hidden scalar variables

  1. All array variables (pointers)

  1. Pointers to internal modules (subsystems only)

The ordering enforces that all scalar variables, which are initialized by the base module constructor function, are located at the start of the instance structure.  Refer to Section 4.4.1 for additional details.

1.5.10. Overwriting Existing Source Files

Audio Weaver is frequently used in conjunction with a source code control system.  The awe_generate_library.m function overwrites generated files only if there is an actual change to the file.  If nothing has changed, the file is untouched.

Each generated file is first written to a temporary file.  Then the temporary file is compared with the existing version.  If the files are identical, then the existing file is untouched and the temporary file is deleted.  If the temporary file differs from the existing file, then the existing file is overwritten.

1.5.11. Specifying Wiring Constraints

The .wireAllocation field of an audio module allows you to specify wiring constraints for the routing algorithm.  There are two possible values

'distinct' – None of the input wire buffers will be used as output wire buffers. That is, the outputs will be "distinct" from the inputs.

'across' – The routing algorithm will try and reuse wire buffers across the module, whenever possible.  That is, the wire buffer assigned to input pin N will be reused for output pin N.  The wires are the same "across" the module.

awe_module.m sets .wireAllocation to 'distinct' by default. This is the safest approach but consumes more memory.  'across' conserves memory and is often easier to debug.  When 'distinct' is specified, the output wires will always be different compared to the inputs.  When 'across' is specified, the routing algorithm will attempt to reuse the buffers, but this may not always be the case.

The choice between 'distinct' and 'across' depends upon the internal details of your processing algorithm.  Buffer pointer usage within the algorithm has to be studied to determine if 'across' wiring is possible.  Consider the scaler module processing function shown below.  This module has N inputs and N outputs with the Nth input being scaled and written to the Nth output.

void awe_modScalerProcess(void *pInstance)

{

  awe_modScalerInstance *S = (awe_modScalerInstance *)pInstance;

  WireInstance **pWires = ClassModule_GetWires(S);

  int numPins = ClassModule_GetNInWires(S);

  int pin;

       

  for(pin=0;pin<numPins;pin++)

    {

      awe_vecScale((float *)(pWires[pin]->buffer), 1,

               (float *)(pWires[numPins+pin]->buffer), 1,

               S->gain, ClassWire_GetNumSamples(pWires[pin]));

    }

}

It is safe to use 'across' wiring in this case. Now consider the Mixer module.  This module has 1 input pin with M channels and 1 output pin with N channels.  Each output channel is formed as a weighted sum of input channels with each output channel written in order.  In this case, you can no longer use 'across' wiring because each output channel depends upon each input channel.  If 'across' wiring were used, then writing the first output channel would overwrite the first input channel.

Note that 'across' is a suggestion to the routing algorithm.  In certain cases, 'across' wiring cannot be used and distinct buffers will be provided.  You should always write your audio module processing function anticipating that the buffer pointers may be distinct.  For example, the inner loop of the awe_modScalerExampleProcess function should not be written as:

  for(pin=0;pin<numPins;pin++)

    {

      awe_vecScale((float *)(pWires[pin]->buffer), 1,

               (float *)(pWires[pin]->buffer), 1,

               S->gain, ClassWire_GetNumSamples(pWires[pin]));

    }

The routing algorithm may decide to assign a distinct output buffer; always pass in the given output buffer pointer.

The most common situation when 'across' wiring cannot be applied is when a module sits between the input and output of a system.  For routing reasons, the wires attached to the input and output of a subsystem have to be distinct.  Placing a module with .wireAllocation='across' between them will still lead to distinct buffer allocation.

1.5.12. AudioWeaverModule Tag

The function awe_help.m displays a list of all Audio Weaver modules on the current module search path.  In order for a module m-file to be picked up, you need to add the tag AudioWeaverModule somewhere within the m-file.  This tag allows you to differentiate between actual module m-files and other MATLAB files contained in the same directories.  The tag can be placed anywhere within the file, usually in a comment as shown below:

% AudioWeaverModule [This tag makes it appear under awe_help]

1.6.  Code Markers and Template Substitution

Template substitution is an automated method of replacing strings within a file to generate an output file.  The process starts with a template file and Audio Weaver provides separate templates for the generated .c and .h files:

<AWE>\matlab\module_generation\templates\awe_module_template.h

<AWE>\matlab\module_generation\templates\awe_module_template.c

A template file contains normal text interspersed with entries of the form

$IDENTIFIER$

where IDENTIFIER is a string.  The template substitution function is given a list of identifiers together with their replacements.  The substitutions are performed and a new output file is created.  If an identifier within a file is not defined, then it is deleted and does not appear in the output.

1.6.1.    Adding Code Markers

A "code marker" is an identifier together with a replacement string.  Each module and subsystem has its own list of code markers.  The MATLAB function

awe_addcodemarker(M, IDENTIFER, SUBSTITUTION)

creates a new code marker.  The function arguments are:

M - @awe_module or @awe_subsystem object

IDENTIFIER – string indicating a specific identifier in a template file.

SUBSTITUTION – string containing the replacement value for the identifier. 

SUBSTITUTION can be either a single string or a cell array of strings for multi-line replacements.  If you call awe_addcodemarker.m and an IDENTIFIER of the specified name already exists, then the new SUBSTITUTION string is appended as another line.  For example, the following entry located near the top of awe_module_template.c file is used to specify include files:

$srcFileInclude$

If you call

awe_addcodemarker(M, 'srcFileInclude', '#include "file1.h"');

awe_addcodemarker(M, 'srcFileInclude', '#include "file2.h"');

Then the generated file will include both lines:

#include "file1.h"

#include "file2.h"

An optional 4th argument selects between appending and overwriting duplicate code markers.

awe_addcodemarker(M, IDENTIFER, SUBSTITUTION, APPEND)

By default, APPEND=1 and duplicate code markers are appended.  If you set APPEND=0, then an existing code marker of the same name is overwritten.  For example,

awe_addcodemarker(M, 'srcFileInclude', '#include "file1.h"');

awe_addcodemarker(M, 'srcFileInclude', '#include "file2.h"', 0);

Since $srcFileInclude$ is already defined, the second call to awe_addcodemarker.m will overwrite the code marker and the generated file will only contain

#include "file2.h"

The call awe_addcodemarker.m adds the code marker information into the field .codeMarker of the module.  You could, for example, examine the code markers defined for the scaler_module.m as follows:

>> M=scaler_module('fred');

>> M.codeMarker{1}

 

ans =

 

    name: 'processFunction'

    text: 'Insert:code\InnerScaler_Process.c'

 

>> M.codeMarker{2}

 

ans =

 

    name: 'discussion'

    text: {1x9 cell}

1.6.2.    Inserting Files

In many cases, the SUBSTITUTION string contains many lines and it is unwieldy to represent in MATLAB.  Instead, it is easier to copy the SUBSTITUTION text from another file.  If the SUBSTITUTION string has the form "Insert:filename.txt" then the text will be copied from filename.txt.  filename.txt is referenced relative to the location of the module's m-file.  You can specify subdirectories as well.  For example:

awe_addcodemarker(M, 'processFunction', ...

'Insert:InnerPeakHold_Process.c');

inserts the file InnerPeakHold_Process.c located in the subdirectory "Inner".

1.6.3.    awe_lookupcodemarker.m

The MATLAB function

STR=awe_lookupcodemarker(M, IDENTIFIER)

returns the contents of a code marker.  If IDENTIFIER is not defined, then the function returns an empty string.  The returned value STR is either a string, for simple replacements, or a cell array in the case of multi-line replacements.

1.6.4.    awe_deletecodemarker.m

M=awe_deletecodemarker(M, IDENTIFIER)

Deletes the code marker named IDENTIFIER.

M=awe_deletecodemarker(M)

Deletes all code markers associated with a module or subsystem.

1.6.5.    Template Substitution Preprocessor

The template files also contain their own preprocessor directives.  These directives are similar to those used by the C preprocessor but are handled by the template substitution function.  The directives are identified by ## and only a few variations are supported.

##if 1

… This code will appear in the output file …

##endif

 

##if 0

… This code will NOT appear in the output file …

##endif

When the argument to ##if is 1, then the text between the ##if and ##endif will appear in the generated file.  Otherwise, the text will not appear.  You can also use an identifier with an ##if statement as in

##if $myVariable$

Optional text

##endif

If you set

awe_addcodemarker(M, 'myVariable', '1')

then the optional text will appear in the generated file.  Note that $myVariable$ is being set to the string '1' not to the numeric value 1.  The template preprocessor directive also supports an else clause

##if $combineSourceFiles$

#include "$combinedIncludeName$"

##else

#include "$baseHFileName$"

##endif

1.6.6.    Frequently Defined Code Markers

This section lists out code markers that are frequently defined when developing custom audio modules.

1.6.6.1.             $bypassFunction$

C code which specifies the inner portion of the bypass function.  When this is defined, the bypass function is written into generated C file.

1.6.6.2.             $bypassFunctionName$

Allows you to specify a bypass function by name.  This is typically used in conjunction with one of the predefined bypass functions listed in Section 4.4.5.  For example,

awe_addcodemarker(M, 'bypassFunctionName', 'IOAcrossModule_Bypass');

The following logic defines the bypass function. 

  1. If $bypassFunction$ is defined, then the code is placed into the generated .c file and $bypassFunctionName$ is set to awe_modClassNameBypass,

  1. If $bypassFunctionName$ is defined, then this function is used for bypass behavior.  No additional code is placed into the generated .c file.

  1. Otherwise, the default bypass function IOMatchUpModule_Bypass() is used.  No additional code is placed into the generated .c file.

1.6.6.3.             $constructorFunction$

Specifies the inner portion of a custom constructor function.  The function must follow the calling convention outlined in Section 4.4.1.

Simple audio modules without indirect arrays – No need to define the constructor function.  The BaseClassModule_Constructor() usually does everything needed.

Audio modules with direct arrays – Set the .arrayHeap and .arraySizeConstructor fields of the array variables in MATLAB.  Audio Weaver will then auto generate a suitable constructor function.

Complex modules requiring further initialization – Write a custom $constructorFunction$ or use the $postConstructorFunction$ shown below.

Modules created out of subsystems – Audio Weaver automatically generates code for these systems.  If you need further initialization, use the $postConstructorFunction $ shown below.

1.6.6.4.             $postConstructorFunction$

This code marker follows immediately after the $constructorFunction$ code marker in awe_module_template.c.  This marker allows you to add your own code in cases where Audio Weaver generates the memory allocation and instantiation code but further initialization is needed.  It is particularly useful for subsystems.

1.6.6.5.             $discussion$

Defines the discussion section of the help documentation.  This has been grandfathered for backwards compatibility.  When writing new modules, set the M.docInfo.discussion field of the audio module instead.

1.6.6.6.             $getFunction$

Specifies the inner portion of a module's Get function.  Follows the calling convention outlined in Section 4.4.4.

1.6.6.7.             $setFunction$

Specifies the inner portion of a module's Set function.  Follows the calling convention outlined in Section 4.4.3.

1.6.6.8.             $hFileDefine$

Located near the top of the template file awe_module_template.h.  Allows you to add additional preprocessor directives (or anything else) at the start of the header file.  Example,

awe_addcodemarker(M, 'hFileDefine', '#define TRUE 1');

1.6.6.9.             $hFileInclude$

Located near the top of the template file awe_module_template.h.  Allows you to include additional header files.

awe_addcodemarker(M, 'hFileInclude', '#define <stdlib.h>');

1.6.6.10.          $processFunction$

Specifies the inner code for the module's processing function.  This must always be defined for modules.  For subsystems, the auto-generated should be used.  Refer to Section 4.4.2 for a discussion of how to define this function.

1.6.6.11.          $preProcessFunction$

This code marker is located immediately before the $processFunction$ marker.  It is typically used with subsystems when you want to insert your code custom code immediately prior to the auto-generated $processFunction$.

1.6.6.12.          $postProcessFunction$

Similar to $preProcessFunction$ but the code here is inserted immediately after the $processFunction$.  It is typically used with subsystems when you want to insert your custom code immediately after the auto-generated $processFunction$.

1.6.6.13.          $srcFileDefine$

Located near the top of the template file awe_module_template.c.  Allows you to add additional preprocessor directives (or anything else) at the start of the source file. 

1.6.6.14.          $srcFileInclude$

Located near the top of the template file awe_module_template.c.  Allows you to specify additional includes files. 

1.6.6.15.          $hFileTemplate$ and $srcFileTemplate$

Audio Weaver uses the default template files specified at the start of Section 5.2.  You can override the templates used by a particular module by setting these code markers.  There are separate markers for the header file, $hFileTemplate$, and the source file, $srcFileTemplate$.  It is handy to override these values if you want to change copyright information in the generated files.

1.7.  Fine Tuning Code Generation

This section contains further instructions for fine tuning code generation.  Many of the items apply to modules generated using compiled subsystems.

1.7.1.    Automatically Generated Array Initializers

In many cases, an audio module requires a custom constructor function solely for the purpose of allocating indirect arrays.  If the size of the allocated memory can be defined using existing module variables, then Audio Weaver can automatically generate a constructor function.

The generated array constructor code is of the form:

if ((S->ARRAYVAR = (TYPE *) awe_fwMalloc(SIZE, HEAP, retVal)) == 0)

{

    // Error code is in *retVal

    return 0;

}

where

ARRAYVAR – is the name of the variable

TYPE – is the type of the module (either float, fract32, or int)

SIZE – is a user specified string

HEAP – is a user specified string.

ARRAYVAR and TYPE are already known to MATLAB.  SIZE and HEAP must be separately specified as C code to embed into the generated code.  The string SIZE is specified in MATLAB by setting field .arraySizeConstructor field of the array variable.  Similarly, HEAP is specified by setting the .arrayHeap field of the array variable.  For an example, see the Peak Hold module in Section 7.2

1.7.2.    Specifying Module Constructor Arguments in Subsystems

This advanced technique is frequently used when compiling subsystems to generate new module classes.  Audio Weaver automatically generates a Constructor function for the compiled subsystem, and this constructor in turn calls the constructor functions of the internal modules.   The difficulty arises because the arguments to the internal constructor functions are hard coded in the generated file based on the values at code generation time.  This is not always the desired behavior.

To get around this problem, Audio Weaver allows you to specify a piece of C code which overrides the default numeric initializer for an internal module variable.  The C code is written to the .constructorCode field of the variable.  For an example, refer to the look ahead limiter example of Section 0. 

1.7.3.    Avoiding Wire Allocation Problems with Subsystems

This issue also applies to the case of compiling subsystems to form new module classes.  If your subsystem has .flattenOnBuild=1 (which means that it is not compiled), then you can ignore this section.  This problem arises because Audio Weaver modules can operate on an arbitrary number of channels.  In certain circumstances, the routing algorithm incorrectly allocates wire buffers during code generation such that audio processing crashes at run-time.  In these cases, you need to be careful how you specify default channel counts and block sizes within modules.  The look ahead limiter example of Section 7.4 demonstrates the problem and presents a solution.

1.8.  unique_classes.m

C=unique_classes(SYS)

where

SYS - @awe_subsystem object.

Returns a list of all the unique module and subsystem classes found within subsystem SYS.  The function recursively searches through the system and identifies individual modules and subsystems.  The return result C is a cell array of strings. If called without any output arguments, the function displays the class list to the MATLAB output window.  The class name of the input SYS is not included in the C.  This function is useful for dependency checking.

[C, M]=unique_classes(SYS)

An optional second output receives a cell array of the actual modules/subsystems listed in C.

SYS can also be a cell array of subsystems.  In this case, the function returns the overall set of unique classes needed to create all of the systems in SYS.

[C, M]=unique_classes(SYS CLIST)

An optional Boolean argument CLIST specifies that the list of unique module classes should be displayed in a manner appropriate for inclusion in the TargetInfo.h file.

For example, to determine all of the unique module classes needed for the agc_example, type:

>> SYS=agc_example;

>> unique_classes(SYS)

Unique classes within the subsystem of class: test

AGC

AGCCore

AGCMultiplier

Meter

ScalerDB

If you want to build a target with only the modules required for the agc_example, you would set CLIST=1.  The following C code is then printed to the output window:

extern const ModClassModule awe_modAGCClass;

extern const ModClassModule awe_modAGCCoreClass;

extern const ModClassModule awe_modAGCMultiplierClass;

extern const ModClassModule awe_modMeterClass;

extern const ModClassModule awe_modScalerDBClass;

 

 

#define LISTOFCLASSOBJECTS \

&awe_modAGCClass, \

&awe_modAGCCoreClass, \

&awe_modAGCMultiplierClass, \

&awe_modMeterClass, \

&awe_modScalerDBClass

This text can be pasted directly into TargetInfo.h.

1.9.  awe_generate_module.m

M=awe_generate_module(M, DIRECTORY, WRITEFILES)

Internal function used by awe_generate_library.m.  In some cases, such as when generating documentation, you may want to call this function directly.  Arguments:

M - @awe_module object.

DIRECTORY – directory in which the generated files should be written to.  By default, this is set to MATLAB's current working directory (returned by the pwd command).

WRITEFILES – an optional Boolean argument which specifies whether the generated files should actually be written.  If you set WRITEFILES=0, then the module M will be updated with code markers, but no output files will be written.  Use WRITEFILES=0 when generating documentation as described in the next Section.

  • No labels