AMaMP
Audio Mixing and Manipulation Project
SourceForge.net Logo
 
 
 

fx2c Documentation
If you haven't read the effects system overview, you should do before reading this document, otherwise it probably won't make a lot of sense. As described, the fx2c build tool takes an fx file and generates a C file for the effect. This document explains the format that an fx file should take and provides an example.

Notation
All FX directives are in upper case (and are case sensitive). An FX
directive and any parameters must be placed on one line with nothing (other than a comment) on it, and the directive must be at the start of the line. If you wish to put comments at any point in the module file you should use the C commenting convention of /* ... */. These will be stripped out by fx2c before any processing of the file begins.

FX File Directives

The first line of an FX file starts with a MODULE directive, which must be placed before anything else (besides comments). The MODULE directive is followed by the name of the effect module. By convention the name of the FX file will match the name given to the module.

Immediately beneath the MODULE directive are a number of directives that specify what parameters the module requires and accepts. Any other parameters found in an Effect chunk for this module (other than the module name) that are not listed in either of these sections will result in a parse error in the core. Any required parameters that are not specified in the an Effect chunk for this module will also result in a parse error.

The PARAMREQUIRE directive should be followed by a comma seperated list of required parameters. If none are required then this directive should be followed by the word NONE (in uppercase).

The PARAMACCEPT directive should be followed by a comma seperated list of parameters that are accepted, but not required. If you want to accept all parameters then this directive should be followed by the word ALL.

Directives after this are not expected to be in any particular order,
although putting PREAMBLE first and POSTAMBLE last would probably make sense. Unlike previous directives these ones are expected to be followed by C code, which continues until the next directive is encountered. Apart from PREAMBLE and POSTAMBLE, the code below a directive is the body of a function. For most of them you are expected to return something, and you do it just using the normal C "return" keyword. The list of parameters after the directive works on a positional basis; that is, you can call them whatever you like, but the first one will always be the global context (apart from the GLOBALINIT one) and so on. All fx2c does is wrap your code into a function body with an appropriate prototype, and the variable names you supply will be used.

PREAMBLE

Code placed after the optional PREAMBLE directive (which takes no
parameters) will be placed before any other functions or other code in the effect module. This is the place to put any #include style directives along with any function prototypes etc.

POSTAMBLE

Code placed after the optional POSTAMBLE directive (which takes no
prototypes) will be placed after any other code in the effect module. This is the best place to put any custom functions that you want to use in your effects module.

GOBALINIT
Code placed after this directive is executed as soon as the effect module is loaded (e.g. it only happens once). It is optional to provide this directive. It gives you the option of setting up global context that will be accessible from all individual applications of the effect and spanning all effect chunks. This code is expected to return a void pointer, which will get passed as the first parameter to all other chunks.

CHUNKINIT global
Code placed after this directive is executed once per Effect chunk that uses the module. It is optional to provide this directive. This gives you the chance to set up chunk level context that will be passed to all applications of the effect from placements that relate to a particular chunk. This is a good place to parse any parameters and stash them away in a structure so you can use them once per effect application. You should return a void* pointer to this structure (or NULL if you don"t want one). This directive"s first parameter is the name of a void pointer that will contain whatever was returned from GLOBALINIT, or NULL if you didn"t have a GLOBALINIT.

INIT global, chunk
The INIT directive is to be followed by code that is executed to initialise the effect module. This is called once per effect application (e.g. before an effect is about to be applied to a placement). This can be used to set up any initial conditions from the parameters, allocate any buffers, etc. The code is expected to return a (void*), which will be passed to functions that apply the effect. It is expected that most effects modules will define their own struct, malloc it and set it up it in this INIT function and return a pointer to it, cast to a (void*). This directive"s first parameter is the name of a void pointer that will contain whatever was returned from GLOBALINIT, or NULL if you didn"t have a GLOBALINIT. This directive"s second parameter is the name of a void pointer that will contain whatever was returned from GLOBALCHUNK, or NULL if you didn"t have a GLOBALINIT.

APPLY global, chunk, current, buffer, bufferSampleLength
The APPLY directive is to be followed by code that is called to apply the effect to a buffer of data. The global parameter is a void pointer the same as that returned by GLOBALINIT. The chunk parameter is a void pointer the same as that returned by CHUNKINIT. The current parameter is a void pointer the same as that returned by INIT. The buffer parameter is an int* which points to the data that you are to process. You should do the processing destructively, e.g. to that buffer of data itself. bufferSampleLength is the number of samples, independent of the number of channels, of data you have.
For example, if you have 2 channels then *buffer will point to the first
sample for the first channel, *(buffer + 1) will point to the first sample for the second channel, etc - just as in a standard PCM WAVE file. Note that the number of channels and sampling rate are available through macros, described later in this document.

IPCHANDLER global, chunk
Called by the IPC message dispatch routine when an effect chunk handled by this module receives an IPC meesage. The module should check if it knows how to handle the message and handle it appropriately if so. If the message is syntactically invalid or the module is unable to handle it, then 0 should be returned. If it can be handled, 1 should be returned. If the handling of the message is completed successfully, then the IPCCONFIRM() macro should be called (like a function call); otherwise, the IPCFAIL() macro should be used. See the macro section for details on how to get details of the IPC message.

DESTROY global, chunk, current
The DESTROY directive is to be followed by code that is called once effect application to an individual placement has been completed. The global parameter is a void pointer the same as that returned by GLOBALINIT. The chunk parameter is a void pointer the same as that returned by CHUNKINIT. The current parameter is a void pointer the same as that returned by INIT. This is where you should free any memory related to what you returned from INIT.

CHUNKDESTROY global, chunk
Code placed after this optional directive is executed on a per-chunk basis, and usually just before the module is unloaded (note that all calls to CHUNKDESTROY will be completed before GLOBALDESTROY is called). The global parameter is a void pointer the same as that returned by GLOBALINIT. The chunk parameter is a void pointer the same as that returned by CHUNKINIT. This is where you should free any memory related to what you returned at CHUNKINIT.

GLOBALDESTROY global
Code placed after this optional directive is executed before the effect
module is unloaded. The global parameter is a void pointer the same as that returned by GLOBALINIT. This is the place to free any memory associated with the global pointer.

FX File Macros
There are certain bits of data that you may wish to fetch from your FX
module. Rather than requiring deep knowledge of AMaMP structures, some simple FX file "macros" are provided that fx2c replaces with appropriate C code. This means that changes to the core to effect module interface can be changed without having to modify the FX files.

The macros fall into two categories. Those that have their parameters in brackets can be placed anywhere in your C code, as if they were a normal statement. Those without parameters in brackets must go on a line of their own and code between them and their respective end directive should expect to be put in a block.

GETPARAMETER(char*)
Not valid in GLOBALINIT and GLOBALDESTROY. This macro expects a pointer to a NULL terminated string which names the parameter to fetch (of course, this can just be the name of the parameter in double quotes). Returns a poiner to the NULL terminated string containing the value of the parameter (which you should NOT free or modify) or NULL if the parameter does not exist.

ITERATEPARAMETERS name, value
....
ENDITERATEPARAMETERS

Not valid in GLOBALINIT and GLOBALDESTROY. This macro allows all parameters to be iterated over. The variable named as the first parameter will be a pointer to a NULL terminated string containing the name of the parameter. The variable named as the second parameter will be a pointer to a NULL terminated string containing the value of the parameter.

DEBUGMESSAGE("The message")

This macro expects a pointer to a NULL terminated string which contains text to use in a debug message. If the core was compiled to emit debug messages, a debug message will be generated. The message should not contain any newline characters.

THROWWARNING("The message")
This macro expects a pointer to a NULL terminated string which contains a warning message that you want to send, through the core, to the application invoking the AMaMP core. The message should not contain any newline characters.

THROWERROR("The message")
This macro expects a pointer to a NULL terminated string which contains an error message that you want to send, through the core, to the application invoking the AMaMP core. Note that throwing an ERROR will *terminate the entire core*. THROWERROR is only to be used in situations where you really can not go on, e.g. memory allocation fails. The message should not contain any newline characters.

GETCHANNELS()
Returns an integer containing the number of channels in the data buffer that is passed to the code beneath the APPLY directive. You must NOT assume there will always be 2 channels. There may be 1, there may be 5.

GETSAMPLINGRATE()
Returns an integer containing the number of samples per second in the data buffer that is passed to the code beneath the APPLY directive.

GETIPCMESSAGETYPE()
Only valid inside IPCHANDLER. Returns the type of the message as a NULL terminated string. At the time of writing, this will always be "effect".

GETIPCPARAMETER(char*)
Only valid inside IPCHANDLER. Takes the name of an IPC message parameter and returns the value of that parameter as a NULL terminated string, or NULL if that parameter doesn't exist.

ITERATEIPCPARAMETERS name, value
....
ENDITERATEPARAMETERS

Only valid inside IPCHANDLER. This macro allows all parameters in an IPC message to be iterated over. The variable named as the first parameter will be a pointer to a NULL terminated string containing the name of the parameter. The variable named as the second parameter will be a pointer to a NULL terminated string containing the value of the parameter.

IPCCONFIRM()
Only valid inside IPCHANDLER. Call this macro when an IPC message has been processed successfully to get the core to send a confirmation message to the requesting front end.

IPCFAIL()
Only valid inside IPCHANDLER. Call this macro when an IPC message was syntactically valid (e.g. 1 is going to be returned by IPCHANDLER rather than 0), but a problem occurred during processing. The core will send a failure message to the requesting front end.

Example
The following example simply emits debug messages for the various INIT and DESTROY calls and halves the volume on application. If you want more examples, simply look in the effects folder itself.

/* This is a dummy effects module to help with debugging
   the system. All it does is halve the volume. */

MODULE Dummy

PARAMREQUIRE NONE

GLOBALINIT
	DEBUGMESSAGE("Global Init Called");
	return NULL;

CHUNKINIT global
	DEBUGMESSAGE("Chunk Init Called");
	return NULL;

INIT global, chunk
	DEBUGMESSAGE("Init Called");
	return NULL;

APPLY global, chunk, current, buffer, bufferSampleLength
	/* This is a dummy module. We'll simply halve the volume. */
	int *curPos = buffer;
	while (curPos < buffer + (bufferSampleLength * GETCHANNELS()))
	{
		*curPos = (int) (0.5 * *curPos);
		curPos ++;
	}

DESTROY global, chunk, current
	DEBUGMESSAGE("Destroy Called");

CHUNKDESTROY global, chunk
	DEBUGMESSAGE("Chunk Destroy Called");

GLOBALDESTROY global
	DEBUGMESSAGE("Global Destroy Called");