AMaMP
Audio Mixing and Manipulation Project
SourceForge.net Logo
 
 
 

io2c Documentation
This document describes the syntax for IO files, the input to the io2c build tool which turns IO files into C files that will compile on the current platform, either as a shared library or into the core itself.

CONTENTS

I/O Module Types
Each I/O module has a type that specifies its capabilities and expectations of the core. The types are listed below, and the first step in writing an IO file is deciding which of these types you require.

  • FileInput
    Used when the module is going to read data in from a file. FileInput modules are expected to implement the INPUTOPEN, INPUTREAD, INPUTSEEK, INPUT CLOSE, INPUTCHECK and GETLENGTH directives, as well as register file extension(s) that they handle.
  • FileOutput
    Used when the module is going to write data to a file. FileOutput modules are expected to implement the OUTPUT OPEN, OUTPUT WRITE and OUTPUT CLOSE directives, as well as register file extension(s) they can handle.
  • FileInputOutput
    Used when your module both reads and writes files. It is expected to implement everything described for both a FileInput and a FileOutput type module.
  • StreamInput
    Used when the module is going to read data from a real-time producer of data, e.g. from the soundcard. StreamInput modules are expected to implement the INPUTOPEN, INPUTREAD, INPUTPAUSE, INPUTRESUME and INPUTCLOSE directives.
  • StreamOutput
    Used when the module is going to write data to a real-time consumer of data, e.g. to the soundcard. StreamOutput methods are expected to implement the OUTPUTOPEN, OUTPUTWRITE, OUTPUTPAUSE, OUTPUTRESUME, OUTPUTCLOSE and OUTPUTKILL directives.
  • StreamInputOutput
    Used when your module exchanges data with real-time producers and consumers of data. It is expected to implement everything described for both a StreamInput and a StreamOutput type module.
  • GeneratorInput
    Used when writing an input module that is going to generate data on-demand, e.g. a software synthesizer. GeneratorInput modules are expected to implement the INPUTOPEN, INPUTREAD, INPUTSEEK and INPUTCLOSE directives.
  • DataOutput
    Used when writing output modules that use the mixdown data to generate some form of output that is neither real-time nor being written to a file. DataOutput modules are expected to implement the OUTPUTOPEN, OUTPUTWRITE and OUTPUTCLOSE directives.

Notation
All IO directives are in upper case (and are case sensitive). An IO
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 io2c before any processing of the file begins.

MODULE and TYPE Directives

The first directive in an IO file must be a MODULE directive, which must be placed before anything else (besides comments). The MODULE directive is followed by the name of the I/O module. By convention the name of the IO file will match the name given to the module.

The second directive in an IO file must be a TYPE directive, which is to be followed by one of the module types in the "I/O Module Types" section of this document.

An example of how these might look would be:-

MODULE WAVEPCM
TYPE FileInputOutput

Parameter Validation Directives
Two directives are provided for parameter validation; PARAMREQUIRE and PARAMACCEPT. These must be prefixed by either INPUT or OUTPUT, which describes whether they apply to inputs, outputs or both.

PARAMREQUIRE is to be followed by a comma-seperated list of paramter names that this module requires. You can follow this directive with NONE to state that no parameters are required.

PARAMACCEPT is to be followed by a comma-seperated list of parameter names that the module accepts, but does not require. You can follow this directive with ALL to state that any parameters passed are valid.

If you implementing a module that handles input, you must supply either INPUT PARAMREQUIRE or INPUT PARAMACCEPT or both. If you implementing a module that handles output, you must supply either OUTPUT PARAMREQUIRE or OUTPUT PARAMACCEPT or both.

An example of how these might look would be:-

INPUT PARAMREQUIRE NONE
OUTPUT PARAMACCEPT SamplingRate, BitDepth, Channels

Global Directives
The following optional directives, which are expected to be followed by C code, are valid in any module. The order in which they appear is not important.

PREAMBLE
Code placed after the optional PREAMBLE directive (which takes no
parameters) will be placed before any other functions or other code in the IO module. This is the place to put any #include style code 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 module.

GLOBALINIT
The code following this directive is called once when the I/O module is loaded by the core. If you need to set up some kind of global state that you will be able to access in all other calls made to the module, this is the place to do it. You should return (using the C return keyword) a void* (e.g. a pointer of some kind) to whatever structure is set up to store the global state.

GLOBALDESTROY global
The code following this directive is called once when the I/O module is unloaded as the core terminates. This is where you clean up after any global state you established. In place of "global" above, put a variable name that you wish to hold the pointer that you returned in GLOBALINIT - it's just like a parameter to a function call.

Input Directives
These directives are only relevant if the module you are implementing supports input, and even then some of them (PAUSE and RESUME) are only needed for Stream modules.

INPUTOPEN global
This is called whenever the input source needs to be opened. The global parameter will be whatever GLOBALINIT returned. A void* (pointer of some kind) should be returned, which should point to a structure containing any required handle(s) or just to the handle itself (e.g. if you are using stdio.h, it is OK to return a FILE* here - you'll just need to re-cast it to a FILE* in code for later directives).

If you are writing a File module, the path can be retrieved using the GETPARAMETER macro, e.g. by doing GETPARAMETER("Path"), just as you would fetch any other parameter.

INPUTREAD global, chunk, buffer, samples
This is called to read data from an input source. The global parameter will be whatever GLOBALINIT returned. The chunk parameter will be whatever INPUTOPEN returned. The buffer is an int* (pointer to an int buffer) that is ready to recieve the data that is going to be read in. It is of size sizeof(int) * GETBASECHANNELS() * samples, meaning that the samples parameter is the number of samples to read for each channel.

When reading data into the buffer a few considerations need to be made. First is the order the samples go into the buffer. If the base number of channels is 2, then you should put data into the buffer LEFT SAMPLE, RIGHT SAMPLE, LEFT SAMPLE, RIGHT SAMPLE, etc. The buffer will be zeroed out before you start reading into it, and it is OK (and important) to leave channels beyond those of the file that is being read in blank.

Secondly, the base bit-depth must be considered. GETBITDEPTH() will return the bit-depth that the data is expected to be in the buffer at - that is, it will not always scale to sizeof(int). If you read data with a bit-depth of 8, and GETBITDEPTH() returns 16, then you will need to multiply all values in the buffer by 2^8 = 256. The bit-depth will always be one of 8, 16, 24 or 32.

Thirdly, the base sampling rate must be considered. The macro GETSAMPLINGRATE() will return the sampling rate that the core is working at. Data coming from an input source must be translated to this sampling rate if it is not the same.

If internal position data needs to be get and/or set (this is not generally important for Stream type inputs), use the GETINTERNALPOS() and SETINTERNALPOS(int) macros.

INPUTSEEK global, chunk, sampleOffset
This is called to seek to a certain position in an input source. sampleOffset is the position from the start of the input data, specified in samples, to seek to. Note that this value does not take into account the number of channels. This function will often use the SETINTERNALPOSITION(int) macro, passing it a new internal position calculated by the code.

INPUTPAUSE global, chunk
This is called when an input source needs to be paused. The global parameter will be whatever GLOBALINIT returned. The chunk parameter will be whatever INPUTOPEN returned. The code in this directive should handle the situation where the core has been paused for an indefinite amount of time and no more input will be required until it is resumed. Sometimes, closing the input source while paused will be the most sensible option.

INPUTRESUME global, chunk
This is called when an input source needs to be resumed. It will only ever be called when INPUTPAUSE has previously been called. The global parameter will be whatever GLOBALINIT returned. The chunk parameter will be whatever INPUTOPEN returned. The code in this chunk should handle resuming after a pause, so input can be read again.

INPUTCLOSE global, chunk
This is called when an input source needs to be closed. The global parameter will be whatever GLOBALINIT returned. The chunk parameter will be whatever INPUTOPEN returned. This is where any handles and structures created in INPUTOPEN should be destroyed.

Output Directives
These directives are only relevant if the module you are implementing supports output, and even then some of them (PAUSE, RESUME and KILL) are only needed for Stream modules.

OUTPUTOPEN global
This is called whenever the output needs to be opened. The global parameter will be whatever GLOBALINIT returned. A void* (pointer of some kind) should be returned, which should point to a structure containing any required handle(s) or just to the handle itself (e.g. if you are using stdio.h, it is OK to return a FILE* here - you'll just need to re-cast it to a FILE* in code for later directives).

If you are writing a File module, the path of the file to write can be retrieved using the GETPARAMETER macro, e.g. by doing GETPARAMETER("Path"), just as you would fetch any other parameter.

OUTPUTWRITE global, chunk, buffer, samples
This is called to request that a buffer of data of a certain length is written. The global parameter will be whatever GLOBALINIT returned. The chunk parameter will be whatever OUTPUTOPEN returned. The buffer is an int* (pointer to an int buffer) that contains the data to write. It is of size sizeof(int) * GETBASECHANNELS() * samples, meaning that the samples parameter is the number of samples to read for each channel.

The sampling rate of the data in the buffer is available through the GETSAMPLINGRATE() macro. The bit depth of the data in the buffer is available through the macro GETBITDEPTH(). The number of channels of data in the buffer is available through the macro GETCHANNELS().

Data in the buffer will be structured as "SAMPLE 1 CHANNEL 1" "SAMPLE 1 CHANNEL 2" "SAMPLE 2 CHANNEL 1" "SAMPLE 2 CHANNEL 2" etc - just as it is in a PCM wave file.

OUTPUTPAUSE global, chunk
This is called when an output needs to be paused. The global parameter will be whatever GLOBALINIT returned. The chunk parameter will be whatever OUTPUTOPEN returned. The code in this directive should handle the situation where the core has been paused for an indefinite amount of time and no more input will be required until it is resumed. Sometimes, closing the output while paused will be the most sensible option. This generally only applies to Stream type outputs.

OUTPUTRESUME global, chunk
This is called when an output needs to be resumed. It will only ever be called when OUTPUTPAUSE has previously been called. The global parameter will be whatever GLOBALINIT returned. The chunk parameter will be whatever OUTPUTOPEN returned. The code in this chunk should handle resuming after a pause, so more data can be written to the output.

OUTPUTCLOSE global, chunk
This is called when an output needs to be closed. The global parameter will be whatever GLOBALINIT returned. The chunk parameter will be whatever OUTPUTOPEN returned. This is where any handles and structures created in OUTPUTOPEN should be destroyed.

If you are writing a StreamOutput module and there is data in buffers that has not yet been dealt with (e.g. you've sent data to the soundcard) then you should wait while this data is finished with before exiting from this code.

OUTPUTKILL global, chunk
This is called when an output needs to be closed immediately. The global parameter will be whatever GLOBALINIT returned. The chunk parameter will be whatever OUTPUTOPEN returned. This is where any handles and structures created in OUTPUTOPEN should be destroyed.

If you are writing a StreamOutput module and there is data in buffers that has not yet been dealt with, you should not deal with it, but just drop it right away and close any output device.

File Module Specific Directives
These directives are only relevant if you are implementing a module of type FileInput, FileOutput or FileInputOutput.

INPUTEXTENSIONS
This directive should be followed by a comma seperated list of file extensions that your module can or might be able to read from. It is optional to supply this directive, and it should not be given if your module is of type FileOutput. No code is expected to follow this directive.

OUTPUTEXTENSIONS
This directive should be followed by a comma seperated list of file extensions that your module can write. It is optional to supply this directive, and it should not be given if your module is of type FileInput. No code is expected to follow this directive.

CHECKFILE global, path
Only to be supplied for modules that read from files, this directive should be followed by code that checks if the file accessible by the passed path is valid and able to be handled by the module. This checking should be based purely upon the content of the file itself and not upon the file extension; the code will be used not only to check if a file is valid, but also as a way of working out what module to use to handle a file when an extension check is fruitless. Return 1 if the file is valid and readable and 0 if it is not, using the usual C return statement.

GETLENGTH global, path
Only to be supplied for modules that read from files, this directive should be followed by code that determines the length of the file in samples. The number of samples must be in terms of the base sampling rate, available from the GETSAMPLINGRATE() macro, and should be the number of samples per channel of audio, not accross all channels. The number of samples should be returned in an int.

IPCHANDLER global, chunk
Called by the IPC message dispatch routine when an input or output 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.

Macros
A macro is a way of getting at a bit of data or setting up an iteration structure. Macros hide away the ugliness of the guts of the AMaMP core from your module.

GETBITDEPTH()
Returns an integer containing the bit depth of the data in any buffers passed to your I/O module, either for the module to read from or write to.

GETCHANNELS()
Returns an integer containing the number of channels that data in any buffers passed to your I/O module will have, either for the module to read from or write to.

GETSAMPLINGRATE()
Returns an integer containing the sampling rate (samples per second) of the data in any buffers passed to your I/O module, either for the module to read from or write to.