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
- Notation
- MODULE
And TYPE Directives
- Parameter
Validation Directives
- Global
Directives
- Input
Directives
- Output
Directives
- File Module
Specific Directives
- The
IPCHANDLER Directive
- Macros
- That
can be used anywhere...
- That
can be used anywhere except GLOBALINIT and GLOBALDESTROY...
- GETPARAMETER(char*)
- ITERATEPARAMETERS
- That
can be used in INPUT handling directives...
- GETINTERNALPOS()
- SETINTERNALPOS(int)
- GETSAMPLELENGTH()
- SETSAMPLELENGTH(int)
- That
can only be used in the IPC handler...
- GETIPCMESSAGETYPE()
- GETIPCPARAMETER(char*)
- ITERATEIPCPARAMETERS
- IPCCONFIRM()
- IPCFAIL()
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.
|