gzdoom/dumb/docs/dumbfull.txt
Randy Heit 01f59fa85f - Added an alternate module replay engine that uses foo_dumb's replayer, a
heavily customized version of DUMB (Dynamic Universal Music Bibliotheque).
  It has been slightly modified by me:
  * Added support for Ogg Vorbis-compressed samples in XM files ala FMOD.
  * Removed excessive mallocs from the replay core.
  * Rerolled the loops in resample.c. Unrolling them made the object file
    ~250k large while providing little benefit. Even at ~100k, I think it's
    still larger than it ought to be, but I'll live with it for now.
  Other than that, it's essentially the same thing you'd hear in foobar2000,
  minus some subsong detection features. Release builds of the library look
  like they might even be slightly faster than FMOD, which is a plus.
- Fixed: Timidity::font_add() did not release the file reader it created.
- Fixed: The SF2 loader did not free the sample headers in its destructor.


SVN r995 (trunk)
2008-05-29 23:33:07 +00:00

1717 lines
71 KiB
Text

/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* dumb.txt - DUMB library reference. / / \ \
* | < / \_
* See readme.txt for general information on | \/ /\ /
* DUMB and how to set it up. \_ / > /
* | \ / /
* If you are new to DUMB, see howto.txt. | ' /
* \__/
*/
***********************************
*** Include Files and Libraries ***
***********************************
dumb.h
Include this if you only want the core DUMB library functions. You will
be able to load music files and render them into memory buffers at your
own pace. The core library is completely portable, and as such does not
access hardware; you must relay the sound data to the sound card yourself.
A stdio file input module is available, but you must actively register it
if you wish to use it (see dumb_register_stdfiles()); if you do not
register it, it will not be linked into your executable. You must register
it in order to load stand-alone music files.
Optimised: libdumb.a (-ldumb)
Debugging: libdumbd.a (-ldumbd)
aldumb.h
Include this if you wish to use DUMB with Allegro. This will provide you
with functions to play DUHs back through Allegro's audio streams and embed
music files in Allegro datafiles. A file input module using Allegro's
packfiles is provided; you have a choice between this and the stdio
module. You will be able to load datafiles containing music files no
matter which file input module you register, or even if you register no
file input module. However, you must register a file input module in order
to load stand-alone files.
Optimised: -laldmb -lalleg -ldumb
Debugging: -laldmd -lalld -ldumbd
libaldmb.a or libaldmd.a must be linked in first, so the symbols can be
resolved when linking in the other two libraries.
***********************************
*** Library Clean-up Management ***
***********************************
int dumb_atexit(void (*proc)(void));
Registers a function to be called at the end of your program. You can
register multiple functions to be called, and the one you register last
will be called first. If you try to register the same function twice, the
second attempt will have no effect.
See fnptr.txt for help with function pointers.
You must call dumb_exit() before exiting your program for this to work
properly. The library itself registers functions with dumb_atexit(), so it
is important to call dumb_exit() even if you do not use dumb_atexit()
yourself.
This function will return zero on success. It will return zero when
trying to install the same function twice. If it fails through lack of
memory, it will return nonzero. Generally you can ignore the return code;
in the worst case some memory will not be freed at the end. If it is
crucial that your function be called (e.g. to shut down some hardware or
save critical data), then you should call your function manually at the
end of the program instead of registering it here - or use the stdlib
function atexit(), guaranteed under ANSI C to succeed for at least 32
functions.
void dumb_exit(void);
You should call this before exiting your program if you have used any part
of DUMB in the program. Some parts of DUMB will allocate memory, and this
function will free it all up.
More specifically, this function will call any functions that have been
registered with dumb_atexit(). If a part of DUMB needs shutting down, the
shutdown procedure will have been registered in this way.
dumb_exit() will, of course, also call any functions you registered with
dumb_atexit() yourself.
After a call to dumb_exit(), the list of functions is erased. If you are
not ready to exit your program, you can start using DUMB anew as if your
program had just started. (Note that not everything will be reset in
practice - dumb_resampling_quality will retain whatever you set it to, for
example, though you should not assume it will.)
If you only need to call dumb_exit() once at the end of the program, you
can use the following to register dumb_exit() with stdlib.h atexit():
#include <stdlib.h>
atexit(&dumb_exit);
Then dumb_exit() will be called for you when your program exits. This is
the recommended method, since it will ensure clean-up even if your program
aborts. You should only call dumb_exit() manually if you need to shut DUMB
down prematurely, or if atexit() is unavailable for one reason or another.
*****************************
*** Sequential File Input ***
*****************************
DUMB provides a strictly sequential file input system which uses the
DUMBFILE struct. "Strictly sequential" means you cannot seek backwards.
However, the system will keep track of how many bytes you have read,
enabling you to seek forwards. DUMBFILEs provide a convenient error
detection system, so you do not have to check the return value from every
function call in the way you do with the ANSI C functions.
Note that DUMBFILEs cannot be used for output, nor can they be used
portably for text files.
If an error occurs when reading data from a DUMBFILE, the DUMBFILE will
become inoperative. All subsequent activities on the DUMBFILE will return
error codes without attempting to read from the file. The position in the
file will also be forgotten. You can find out if this has happened at any
stage with the dumbfile_error() function. You are still required to close
the DUMBFILE, and the return value from dumbfile_close() will tell you if
an error has occurred.
This system allows you to input large chunks of your file, neither
checking every return value nor wasting time accessing a file that has
already experienced an error. However, before you allocate an amount of
memory or read in a quantity of data depending on previous input from the
file, you should always check that such input was valid. In particular you
should passing zero or negative numbers to malloc() or dumbfile_getnc().
DUMBFILEs can be hooked. In other words, you can specify your own
functions to do the work of reading from a file. While DUMB contains two
modules for this purpose, it does not set them up for you automatically.
In most cases you must register one of these modules yourself, or provide
your own module. See register_dumbfile_system(), dumb_register_stdfiles()
and dumb_register_packfiles().
void register_dumbfile_system(DUMBFILE_SYSTEM *dfs);
Use this function to register a set of functions for use by the DUMBFILEs
(a DUMBFILE system). The DUMBFILE_SYSTEM struct contains the following
fields:
void *(*open)(const char *filename);
int (*skip)(void *f, long n);
int (*getc)(void *f);
long (*getnc)(char *ptr, long n, void *f);
void (*close)(void *f);
See fnptr.txt for help with function pointers such as these.
Your 'open' function should open the file specified and return a pointer
to a struct representing the open file. This pointer will be passed to
your other functions as 'f'. Your 'close' function should close the file
and free all memory pointed to by 'f'. Note that the 'close' operation
should never be able to fail; if you are calling a function with a return
value, you can generally ignore it.
Your 'getc' function should read one byte from the file and return its
value in the range 0 to 255. If an error occurs, you should return -1. Do
not worry about remembering that an error has occurred; DUMB will do that
for you.
'skip' is for skipping parts of the file, and should skip n bytes,
returning 0 on success or any other number on failure. 'getnc' should read
n bytes from the file, store them at 'ptr', and return the number of bytes
read (n on success, fewer on failure). However, these two functions are
optional, and you should only provide them if the operations can be done
more efficiently than with repeated calls to your 'getc' function. If this
is not the case, specify NULL for 'skip', 'getnc' or both, and DUMB will
use your 'getc' function to do the work.
Once you have written all your functions, you need to create a
DUMBFILE_SYSTEM struct to hold them, and pass its pointer to
register_dumbfile_system().
The DUMBFILE_SYSTEM struct must be permanent. In other words, it must be
either global or static, and you should not modify it later. DUMB will not
make its own copy.
You will most likely create your own struct to represent the open file,
but do not be tempted to specify that struct in the function prototypes
and pacify the compiler warnings by casting your function pointers. There
exist computer systems where a (void *) pointer and a (MY_STRUCT *)
pointer are represented differently in memory, and a cast of such a
pointer causes a tangible conversion to take place. If you cast the
function pointers, the computer cannot know when such a conversion is
necessary. Instead, use the following structure:
int myskip(void *f, long n)
{
FILE *file = f;
/* Do some stuff with 'file' */
return something;
}
If you need examples, have a look at the two existing DUMBFILE systems in
dumb/src/core/stdfile.c and dumb/src/allegro/packfile.c.
DUMBFILE *dumbfile_open(const char *filename);
Open the specified file for input. You must pass the DUMBFILE pointer
whenever you wish to operate on this file. When you have finished with the
file, you must pass it to dumbfile_close().
Before you use this function, make sure you have registered a DUMBFILE
system. See register_dumbfile_system(), dumb_register_stdfiles() and
dumb_register_packfiles().
DUMBFILE *dumbfile_open_ex(void *file, DUMBFILE_SYSTEM *dfs);
This function is provided for more specialised use. You should create a
DUMBFILE_SYSTEM specially for the purpose. Its 'open' field is irrelevant;
for neatness, set it to NULL, unless you are using this DUMBFILE_SYSTEM
with register_dumbfile_system() as well.
When you have called this function, the DUMBFILE struct it returned can be
used as normal. The specified DUMBFILE_SYSTEM will be used for all input,
with 'file' passed to your 'skip', 'getc' and 'getnc' functions as 'f'.
This can be used, for example, to read from an already open file.
Note that the position will always be initialised to 0 for this DUMBFILE.
This means for example that offsets in the file do not need adjusting when
embedding data in a larger file.
There are two ways to use this function. If you want 'file' to persist
after using a DUMBFILE returned by this function, you should make sure the
'close' field in the DUMBFILE is set to NULL. When the DUMBFILE is closed,
'file' will be left alone, and you can and should deal with it yourself
when the DUMBFILE has been closed.
Alternatively, you can provide a 'close' function to get rid of 'file' for
you when the DUMBFILE is closed. If you do this, you should not otherwise
use 'file' after a call to this function.
If dumbfile_open_ex() has to return NULL, owing to lack of memory, then
your 'close' function will be called if provided. In other words, if you
have provided a 'close' function, then you no longer need to worry about
'file' whether this function succeeds or not.
See dumb/src/helpers/stdfile.c and dumb/src/allegro/packfile.c for
examples of how to use this function. Neither provides a 'close' function,
so I hope my explanation here will suffice. If not, please feel free to
contact me so I can make the explanation clearer and help you do what you
want to do. Contact details are at the end of this file.
long dumbfile_pos(DUMBFILE *f);
Returns the number of bytes read from the DUMBFILE (or skipped) since it
was opened, or -1 if an error has occurred while reading.
int dumbfile_skip(DUMBFILE *f, long n);
Skips n bytes of the specified DUMBFILE. Returns zero on success.
int dumbfile_getc(DUMBFILE *f);
Reads one byte from the DUMBFILE and returns it in unsigned format (from 0
to 255). If an error occurs, or occurred before, this function returns -1.
int dumbfile_igetw(DUMBFILE *f);
Reads two bytes from the DUMBFILE and combines them into a word ranging
from 0 to 65535. The first byte read is the least significant byte, as
with Intel processors. This function returns -1 on error.
int dumbfile_mgetw(DUMBFILE *f);
Reads two bytes from the DUMBFILE and combines them into a word ranging
from 0 to 65535. The first byte read is the most significant byte, as
with the Apple Macintosh. This function returns -1 on error.
long dumbfile_igetl(DUMBFILE *f);
Reads four bytes from the DUMBFILE and combines them into a long integer
ranging from -2147483648 to 2147483647. The first byte read is the least
significant byte, as with Intel processors. This function returns -1 on
error, but -1 is also a valid return value. After a call to this function,
you can use dumbfile_error() to find out if an error occurred.
long dumbfile_mgetl(DUMBFILE *f);
Reads four bytes from the DUMBFILE and combines them into a long integer
ranging from -2147483648 to 2147483647. The first byte read is the most
significant byte, as with the Apple Macintosh. This function returns -1 on
error, but -1 is also a valid return value. After a call to this function,
you can use dumbfile_error() to find out if an error occurred.
unsigned long dumbfile_cgetul(DUMBFILE *f);
Reads an unsigned (nonnegative) integer from the DUMBFILE. The integer is
stored in a condensed format where smaller numbers use less space:
0 to 127 1 byte
128 to 16383 2 bytes
16384 to 2097151 3 bytes
2097152 to 268435455 4 bytes
268435456 to 4294967295 5 bytes
This format is the same as that used for the times between notes in MIDI
files.
If an error occurs, this function returns (unsigned long)(-1), but that
may be a valid return value. After a call to this function, you can use
dumbfile_error() to find out if an error occurred.
signed long dumbfile_cgetsl(DUMBFILE *f);
Reads a signed integer from the DUMBFILE. The integer is stored in a
condensed format where numbers closer to zero use less space:
-64 to 63 1 byte
-8192 to 8191 2 bytes
-1048576 to 1048575 3 bytes
-134217728 to 134217727 4 bytes
-2147483648 to 2147483647 5 bytes
If an error occurs, this function returns -1, but -1 is also a valid
return value. After a call to this function, you can use dumbfile_error()
to find out if an error occurred.
long dumbfile_getnc(char *ptr, long n, DUMBFILE *f);
Reads n bytes from the DUMBFILE and stores them at 'ptr'. Note that the
pointer is to a series of chars. You may also use this function to read in
a series of signed chars or unsigned chars (which are both officially
distinct types from char), but do not use this to read ints, structs or
any other data type from the file. Integers must be read one at a time
using dumbfile_igetl(), dumbfile_cgetul(), etc. To load a struct in, you
must read each field separately using an appropriate function for each
one. For complicated datatypes, you can simplify this process by writing a
function for each struct.
int dumbfile_error(DUMBFILE *f);
This function returns -1 if an error has occurred with the specified
DUMBFILE, or 0 if all is well.
int dumbfile_close(DUMBFILE *f);
This function closes the DUMBFILE, after which the pointer will be
invalid. dumbfile_close() returns the value that dumbfile_error() would
have returned, which is -1 if an error occurred while reading or 0
otherwise. Regardless of the return value, the file will always be closed
properly.
*******************************
*** stdio File Input Module ***
*******************************
void dumb_register_stdfiles(void);
This function registers the stdio file input module for use by DUMBFILEs.
FILE structs and their corresponding functions, as defined by the ANSI C
header stdio.h, will be used internally for all DUMBFILE input (unless
opened with dumbfile_open_ex()).
This must be called before dumbfile_open() is used, or else an alternative
system must be registered (see register_dumbfile_system() and
dumb_register_packfiles()).
DUMBFILE *dumbfile_open_stdfile(FILE *p);
If you have a stdio FILE struct representing an open file, you can call
this if you wish to read from it using a DUMBFILE. This is useful when you
need to pass a DUMBFILE struct to a library function, to read an embedded
music file for example. When you close the DUMBFILE, you can continue
using the FILE struct to read what follows the embedded data.
********************************
*** Signal Type Registration ***
********************************
NOTE: SINCE THIS IS NOT TO BE THE INTRODUCTION TO DUMB, SHOULD THE
FOLLOWING INFORMATION BE MOVED TO ANOTHER FILE?
If you are lazy, then don't bother to read this section. Simply follow
these steps:
1. Call all the dumb_register_sigtype_*() functions. You must do this
after dumb_init(), but before you try to call load_duh(). If you try
to load any DUH files beforehand, the library will fail to load
them.
2. Run your program, and make sure the DUH file was successfully loaded
and played.
3. Comment out the first dumb_register_sigtype_*() function call.
3.1. If the DUH file is still loaded and played, remove this function
call.
3.2. If the DUH file was not loaded and played, uncomment and keep this
function call.
4. Repeat Step 3 for the other function calls.
Alternatively, the musician might have told you which of the functions you
need to call.
If you are the epitome of laziness, stop after Step 1. However, if you do
this, your executable may contain unnecessary code.
If, on the other hand, you are interested in how all this works, then read
on.
A DUH file comprises a number of 'signals'. A signal may be thought of as
a 'black box'; commands go in, sound comes out. A signal may in turn call
upon other signals to generate sound, and then do something with this
sound to generate its own output.
For example, here are the contents of a simple DUH file:
Signal #0 SEQU
Signal #1 SAMP
Signal #2 SAMP
Signal #3 SAMP
'SEQU' and 'SAMP' are the signal types. 'SAMP' designates a sample, so
Signals #1, #2 and #3 are simple recordings. For instance, Signal #1 could
be a bass drum, Signal #2 a snare drum and Signal #3 a high hat. When
invoked, these signals simply output their respective percussion sounds.
'SEQU' designates a sequence. Signal #0 will therefore generate its sound
by taking the output waveforms from the other signals and mixing them in
at appropriate times. For example:
0.00 Signal #1
0.50 Signal #2
1.00 Signal #1
1.25 Signal #3
1.50 Signal #2 Signal #3
1.75 Signal #3
2.00 Signal #1
2.25 Signal #3
2.50 Signal #2 Signal #3
2.75 Signal #3
3.00 Signal #1
3.50 Signal #2 Signal #3
The numbers down the left are times. Those with a good sense of rhythm
will be able to see that this represents a rather lame, highly cheesy pop
beat. Experienced gurus will also realise how painfully slow the beat is,
given that the times are in seconds. But enough of that.
A DUH is played by taking the output from Signal #0 and sending it through
to the sound card via an Allegro audio stream. If you do not know what an
audio stream is, suffice it to say that you must call Allegro's
install_sound() function, and an audio stream uses one voice (hence one
voice per DUH file). If you still don't know what I'm on about, read up on
Allegro's digital sound system and then come back here. Alternatively,
read porting.txt for details on how to use DUMB without Allegro.
In reality, DUH files are likely to be much more complicated than the
above example. Sequences may call upon other 'sub-sequences' to generate
their sound, so the above beat might be used repeatedly by a
'super-sequence' to generate a more complex (but still lame) piece of
music.
The DUH player library is split into two main parts - the core and the
basic signal types. The core is very simple. It does not know what 'SAMP'
or 'SEQU' mean. In fact it is not familiar with any signal type. It only
contains generic code for dealing with signals and generating output. By
itself, it cannot load or play any DUH file.
In addition to the core, the player library comprises a few basic signal
types, including 'SAMP' and 'SEQU'. In order to use a signal type, you
must 'register' this signal type by calling its corresponding
dumb_register_sigtype_*() function. These functions are listed below,
along with descriptions of the signals.
If you do not register a signal type, the code for that signal type will
not be linked into your executable file. That means DUMB will not become
bloated with age; new features will not add to the size of your executable
unless you use them. Dynamically linked libraries will still increase in
size.
If you try to load a DUH file that uses a signal type you haven't
registered, the library will not crash. It will simply fail to load the
DUH file. In fact, an unrecognised or corrupt signal will prevent the
library from reading the rest of the file - but remember, DUH files are
always (intended to be) generated from other formats, so you will never
lose any data this way.
If you are unsure which signal types a DUH uses, get in contact with the
author of the DUH file, or register them all and remove them one by one to
find out which are required (as described in the steps for lazy people at
the start of this section).
The great advantage of DUMB over other music systems is that it enables
you to create your own signals. Filters, synthesisers and compression
algorithms can all be set up this way. Programming and audio experience
are useful, but the editor provides a way to create your own signals even
if you don't know C. More information is available in the on-line help in
the editor.
If you are interested in the gory details of how a signal works, or if you
wish to brave creating your own without the help of the editor, then see
the section entitled Signal Design.
void dumb_register_sigtype_sample(void);
Registers the sample signal type ('SAMP'). This signal deals with samples,
or recordings, in the DUH file. All samples are monaural; a combining
signal can be used to combine two of these into a stereo sample. The DUMB
library will cater for more than two channels to a certain extent, but it
is currently limited by Allegro's audio streams.
void dumb_register_sigtype_combining(void);
Registers the combining signal type ('COMB'). This signal takes a set of
monaural signals and combines them into one signal with multiple channels.
Stereo samples can be set up this way.
void dumb_register_sigtype_stereopan(void);
Registers the stereo pan signal type ('SPAN'). This signal takes a
monaural sample and sources it at the stereo position you specify,
provided the DUH is played in stereo (or, more precisely, provided stereo
output is requested of the signal).
void dumb_register_sigtype_sequence(void);
Registers the sequence signal type ('SEQU'). This signal sequences the
sound from other signals. It is what turns a random collection of
recordings of single notes into a complete piece of music.
**********************
*** DUH Management ***
**********************
DUH *load_duh(const char *filename);
Loads a .duh file. Before you call this, make sure you have registered all
the necessary signal types (see 'Signal Registration'). The DUH is
returned to you; you will need to pass it to various functions. When you
have finished with it, call unload_duh() to remove it from memory.
There is no special need to check that this function succeeds. All other
functions can safely be called with a null pointer, which means your music
will simply not play if it could not be loaded.
DUH *read_duh(DUMBFILE *f);
Reads a DUH from an already open file, and leaves the file open so you can
read subsequent data from the file if you wish. Otherwise this function is
identical to load_duh().
void unload_duh(DUH *duh);
Removes a DUH from memory. You must call this for all DUHs you load,
making sure they're not playing at the time.
long duh_get_length(DUH *duh);
Returns the length of a DUH; 65536 represents one second. This value is
simply lifted from the DUH struct, so it may not truly correspond to the
time for which the DUH will generate sound. However, if the musician is
any good - or if the code that calculated this value is written properly -
you can assume it represents the point at which the DUH first loops, or
else it allows time for any final flourish to be appreciated. It is used
by the Winamp plug-in to decide when to stop.
*******************************
*** Impulse Tracker Support ***
*******************************
int dumb_it_max_to_mix;
Specifies the maximum number of samples DUMB will mix at any one time.
The default number is 64. Regardless of this value, all samples will
continue to be processed up to an internal maximum of 128 (slightly
simplified), and cut samples will sound again as soon as the congestion
clears. Samples are prioritised by final volume, after all factors
affecting the volume of a sample have been considered.
If you play two or more IT files at once, this value represents the
maximum number of samples for each one. You will have to reduce it further
if your computer cannot keep up.
DUH *dumb_load_it(const char *filename);
Loads the specified Impulse Tracker file, encapsulating it in a DUH
struct. No signal types need be registered for this to work. The length
will be set to the point at which the music first loops (see
duh_get_length()).
Once the file is loaded, it can be treated exactly the same as any other
DUH in memory.
DUH *dumb_read_it(DUMBFILE *f);
Reads an Impulse Tracker file from an already open DUMBFILE. This leaves
the DUMBFILE open, but the DUMBFILE may not be positioned at the end of
the IT data. If you are embedding an IT in another file, you are advised
to store the size of the IT file and make up for it at the end using
dumbfile_pos().
Otherwise, this function is identical to dumb_load_it().
*******************************
*** DUH Rendering Functions ***
*******************************
Use these functions to generate samples from a DUH. First you call
duh_start_renderer() with the DUH, the number of channels you want and the
position at which you want to start. Then you use duh_render() to generate
the samples. You can call duh_render() as many times as you like, and it
will generate as many or as few samples as you require. When you have
finished, call duh_end_renderer().
DUH_RENDERER *duh_start_renderer(DUH *duh, int n_channels, long pos);
Starts a DUH_RENDERER off. This is the struct you can use to get samples
from a DUH. This function does not generate any samples; you must pass the
struct to duh_render() for that. When you have finished with it, you must
pass it to duh_end_renderer(). You can use as many DUH_RENDERER structs as
you like at the same time.
Currently, n_channels can only be 1 or 2, for monaural and stereo sound
respectively. The debugging library will cause your program to abort if
you pass anything else. Future versions will be enhanced to support more
channels as soon as someone needs them.
When specifying the position, 0 represents the start of the DUH, and 65536
represents one second. Unlike most other music systems, DUMB will always
make sure every note is there right from the start (provided any custom
signal types are properly designed). In other words, you can start a DUH
at a point halfway through a long note, and you will still hear the long
note.
long duh_render(
DUH_RENDERER *dr,
int bits, int unsign,
float volume, float delta,
long size, void *sptr
);
Generates some samples. Pass the DUH_RENDERER as returned by
duh_start_renderer(). Pass the number of bits, which should be 8 or 16. If
unsign is nonzero, the samples will be unsigned (centred on 0x80 or 0x8000
for 8 bits and 16 bits respectively). If unsign is zero, the samples will
be signed.
Allegro's audio streams always take unsigned samples. 8-bit .wav files
always take unsigned samples. 16-bit .wav files always take signed
samples.
The volume is a float. 1.0f is the pseudo-maximum. If you pass 1.0f, any
properly designed DUH will play nice and loud, but will not clip. You can
pass a greater volume if you like, but be prepared for clipping to occur.
Of course you can pass smaller values to play the DUH more quietly, and
this will also resolve clipping issues in badly designed DUHs.
Use delta to control the speed of the output signal. If you pass 1.0f, the
resultant signal will be suitable for a 65536-Hz sampling rate (which
isn't a commonly used rate). The most common sampling rates are 11025 Hz,
22050 Hz, 44100 Hz and 48000 Hz. You can work out the required delta value
as follows:
delta = 65536.0f / sampling_rate;
If you then increase this value, the DUH will speed up and increase in
pitch. If you decrease it, the DUH will slow down and decrease in pitch.
This function will attempt to render 'size' samples. In most cases it will
succeed. However, if the end of the DUH is reached, it may render fewer.
The number of samples rendered will be returned. Therefore, if the return
value is less than the value of 'size' passed, you know the DUH has
finished. It is safe to continue calling duh_render() if you wish, and it
will continually return 0. However, if you wish to do this, you will
probably have to fill the rest of the buffer with silence, which is 0 for
signed, 0x80 for 8-bit unsigned or 0x8000 for 16-bit unsigned.
The samples will be placed at sptr. Use an array of chars for 8 bits or an
array of shorts for 16 bits. Stereo samples will be interleaved, left
first. Your array should contain at least (size * n_channels) elements of
the appropriate bit resolution.
From an aesthetic standpoint if nothing else, it is wise to use the C
qualifiers 'signed' or 'unsigned' depending on whether the samples are
signed or unsigned. This is also convenient if you wish to process the
samples further yourself.
long duh_renderer_get_position(DUH_RENDERER *dr);
Tells you what position a DUH_RENDERER is up to, or -1 if it is invalid
(perhaps owing to lack of memory). As usual, 65536 is one second. Note
that this is a whole number, whereas a fractional part is stored
internally; the sample will not be continuous if you terminate the
DUH_RENDERER and then reinitiate it with the same position.
void duh_end_renderer(DUH_RENDERER *dr);
Terminates a DUH_RENDERER. Be sure to call this when you've finished with
one.
***********************************
*** Signal Design Helper Values ***
***********************************
DUMB_SEMITONE_BASE
When a 'delta' value is required, you can use DUMB_SEMITONE_BASE to
control the pitch. Use pow(DUMB_SEMITONE_BASE, n) to transpose up by n
semitones. To transpose down, use negative n.
DUMB_QUARTERTONE_BASE
When a 'delta' value is required, you can use DUMB_QUARTERTONE_BASE to
control the pitch. Use pow(DUMB_QUARTERTONE_BASE, n) to transpose up by n
quartertones. To transpose down, use negative n.
DUMB_PITCH_BASE
When a 'delta' value is required, you can use DUMB_PITCH_BASE to control
the pitch accurately. Use pow(DUMB_PITCH_BASE, n) to transpose up by n
units, where 256 units represent one semitone. This scale is used for the
'pitch' in a sequence (SEQU).
************************************
*** Signal Design Function Types ***
************************************
In order to design your own signal type, you will have to write six
functions to be linked into your program. They are described below.
See fnptr.txt for help with function pointer types such as these.
typedef void *(*DUH_LOAD_SIGNAL)(DUH *duh, DUMBFILE *file);
Write a function conforming to this type which loads or creates all the
data required by your signal. You may use this, for instance, to load a
sample. You will be reading directly from the DUH file, so make sure you
read exactly the quantity of data stored in the file for this signal. You
should allocate memory (multiple blocks if you like), and return a pointer
referencing all these data. Your data will be stored, and provided
whenever this signal is handled. Be careful about using global data, since
your load_signal function will be called more than once for different
signals of the same type (e.g. for signals which are both samples but are
different samples).
On failure you should return NULL. Please take the possibility of failed
memory allocation seriously, especially when loading large quantities of
data. You should make sure all allocated memory is freed before you return
with failure. See the standard signal types for examples of how to do this
elegantly using your unload_signal function. On failure, you need not
worry about reading the right quantity of data from the file.
Do not close the file under any circumstances.
Often you will write this function before you have written any code to
write a DUH file using this signal. If this is the case, don't worry; you
can write this function free-style, and then the function itself will
serve as a file format reference when you come to create the file.
Note that a null return value will always be interpreted as failure. If
this type of signal does not need to load any data, you should not provide
a load_signal function at all. The absence of this function will convey
the intended message to the library.
typedef void *(*DUH_START_SAMPLES)(
DUH *duh,
void *signal,
int n_channels,
long pos
);
Write a function conforming to this type. Every time your signal is
required to produce some output, this will be called first. The 'signal'
parameter is the same one you returned from your load_signal function. The
'n_channels' parameter specifies how many channels you will need to render
sound in. In general you should support one or two, although sometimes
only one is necessary, depending on how your signal is used. If you can
write generic code to support more channels, then do so. Store the number
of channels for use later, unless you're only permitting one value.
If your signal does not support more than two channels, you are
responsible for making sure it is never invoked with more than two;
likewise if your signal only supports one, then make sure it is never
invoked with more than one. In general you only need to make sure the DUH
file is never rendered with too many channels (see duh_start_renderer()
and al_start_duh()). It may be prudent to use Allegro's ASSERT() macro to
catch bugs of this kind.
The 'pos' parameter specifies where to start, 65536 being one second into
the signal. Even if you do not intend to start in the middle of the signal
yourself, you should support this as it will be used when a DUH is not
played from the beginning.
This function should make the necessary preparations in order to render a
string of samples. All your preparations should be stored in allocated
memory, to which you return a pointer; this allows several instances of
the same signal to play at the same time. You will not be given the 'duh'
and 'signal' parameters again, so you must store them if you need them in
the render_samples or free_samples functions.
Once again, on the rare occasions on which you do not need such data (e.g.
a white noise signal), you should not provide a start_samples function. A
null return code is interpreted as failure, and your music will be lacking
notes (which is better than crashing on undetected memory allocation
failure).
typedef void (*DUH_SET_PARAMETER)(
void *sampinfo,
unsigned char id, long value
);
Some signals can take parameters. For example, a stereo pan signal allows
you to specify the position at which to source the sample, and you might
want to choose the cut-off frequency for a low-pass filter. Such
parameters are set on an instance-by-instance basis, not globally.
Therefore, if you have any parameters, you should place default values for
them in the data you initialise in start_samples. Then you should write a
function conforming to this type, and have it change the parameters when
the correct IDs are passed (see below).
Each of your parameters should be identified by a single byte, which will
be passed in the 'id' parameter. When one of your parameters is correctly
identified by id, you should change the parameter's value in the data you
returned from your start_samples function. These data are available via
the 'sampinfo' parameter. If you do not recognise the ID passed, you may
wish to log the situation using Allegro's TRACE() macro (which will
compile away to nothing unless you define DEBUGMODE), since it signifies a
fault in the DUH file.
Take some care in deciding what should be a parameter and what should be
arranged through the use of separate signals (of the same type). For
example, if you are doing a low-pass filter, you will need a source signal
on which to apply the filter; this signal's index should be loaded by the
load_signal function, so you will create separate filter signals for the
separate source signals. However, the cut-off frequency for the filter is
more suitably stored in a parameter. Here are some guidelines:
Continuous values, or discrete quantities, can be stored in parameters.
Examples: filter cut-off, echo time, stereo pan position.
Discrete indices or references should be loaded by load_signal.
Examples: references to other signals.
Another way of looking at it is as follows:
If a value could be changed halfway through playing the signal, then
consider storing it in a parameter.
If a value needs to be known by the start_samples function and cannot
change later, then consider loading it in the load_signal function.
If your signal has no parameters, do not provide a set_parameters
function. Attempts to change parameters for this signal will not cause a
crash, but will cause the operation to be logged using Allegro's TRACE()
macro if the debugging library is used to play the DUH.
typedef long (*DUH_RENDER_SAMPLES)(
void *sampinfo,
float volume, float delta,
long size, sample_t **samples
);
Write a function conforming to this type. It should render a series of
samples into the array of buffers provided (see below). You are passed
'sampinfo' as returned by start_samples. The 'samples' parameter points to
an array of buffers, one for each channel. You can get a pointer to the
buffer for channel #n with:
sample_t *buffer = samples[n];
As you can see, samples are of type sample_t, which is typedeffed as a
signed int (32 bits). Your waveform should be centred on 0 and should peak
at no more than +/- 32768, or +/- 0x8000, given a volume of 1.0f. If your
waveform goes higher, it will be clipped later on (you do not need to test
for this yourself). Please do not read any special meaning into the volume
parameter; it is simply a factor to be multiplied into each sample. If you
wish to adjust the tone, use a parameter (see DUH_SET_PARAMETER).
In this function, you should render 'size' samples into the buffer for
each channel. Return the number of samples actually rendered, which will
be fewer than 'size' if you run out of samples (e.g. if you reach the end
of a non-looping sample). Never stop short unless the signal ends, and do
not overrun under any circumstances. Update the 'sampinfo' data so that
the samples generated by the next call to render_samples will continue
seamlessly from the samples generated this time.
The delta parameter governs the speed at which your signal will play back.
Higher values of delta should cause the speed and pitch to increase, as
with duh_render(). In general, if delta is 1.0f then your waveform should
be suitable for playback at a sampling rate of 65536 Hz. If you wish to
bend this rule, then be careful - usually it is wiser to adjust your
thinking in other parts.
For example, consider a sample recorded at 44100 Hz, stored in a sample
signal. If a delta of 1.0f is passed to this signal's render function, the
output will be exactly the same as the original sample - a signal at
44100 Hz. So it breaks the rule.
Ah, but it doesn't. Instead, it is wiser to consider the sample stored in
the DUH file as being sampled at 65536 Hz. Once we consider this, it is
clear that the output is suitable for playback at 65536 Hz, so we do not
break the rule. Now it is a simple matter of adjusting all the pitches in
the sequence to compensate for this. See duhtech.txt for details on how to
do this when you are writing the DUH file.
Note that the position that was passed to the start_samples function is
65536 for one second into the signal as played with delta 1.0f. So, with a
sample signal, this position is interpreted as the index of the sample on
which to start. If delta is 2.0, only half a second of the resultant
output will have been skipped.
Once again, please do not read any special meaning into the delta
parameter. The effect of doubling delta should be virtually the same as
the effect of not doubling delta but resampling the output. If you wish to
adjust the tone for different pitches, use a parameter.
This function is compulsory. Silent signals only increase processor and
memory usage, and we are not Microsoft-Intel evangelists.
typedef void (*DUH_END_SAMPLES)(void *sampinfo);
Write a function conforming to this type. It will be called when a signal
stops playing. The parameter points to the data you returned from the
start_samples function, and here you should simply free the memory up.
This function should be provided, always if, and only if, start_samples is
present. The debugging library will check you get this right.
typedef void (*DUH_UNLOAD_SIGNAL)(void *signal);
Write a function conforming to this type. It will be called when the DUH
is removed from memory. It should free all memory allocated by your
load_signal function. The parameter is the same pointer that you returned
from load_signal.
If you only ever use this signal type with make_duh(), and not with
dumb_register_sigtype(), then the following does not apply.
If load_signal is present, unload_signal should also be present; if you
did not provide a load_signal function, then you should not provide an
unload_signal function either. The debugging library will check you get
this right.
**********************************
*** Signal Design Registration ***
**********************************
void dumb_register_sigtype(DUH_SIGTYPE_DESC *desc);
When you have written all the functions that represent your signal, you
will need to register them with the library before it will be able to load
your DUH file. Use this function. The DUH_SIGTYPE_DESC struct contains the
following fields:
long type;
DUH_LOAD_SIGNAL load_signal;
DUH_START_SAMPLES start_samples;
DUH_SET_PARAMETER set_parameter;
DUH_RENDER_SAMPLES render_samples;
DUH_END_SAMPLES end_samples;
DUH_UNLOAD_SIGNAL unload_signal;
You need to create a DUH_SIGTYPE_DESC struct and pass its pointer to
dumb_register_sigtype(). The struct must be in permanent memory. In other
words, it must be either global or static, and you should not modify it
later. DUMB will not make its own copy.
'type' should be a four-character string encoded with DUMB_ID(), for
example DUMB_ID('M','E','O','W'). By convention it should be upper case,
and padded with spaces if you do not use all four characters. However, you
do not have to stick to this.
If you are not providing a function, specify NULL for the corresponding
function pointer.
**********************************
*** Signal Rendering Functions ***
**********************************
When you are designing your own signals, you will often want to retrieve
samples from another signal in the DUH. This signal's index ('sig') should
be loaded by your load_samples function. You can use the following
functions to obtain the samples.
DUH_SIGNAL_SAMPINFO *duh_signal_start_samples(
DUH *duh, int sig, int n_channels, long pos
);
Specifies where you want to start rendering. This function returns a
DUH_SIGNAL_SAMPINFO struct, which you need to pass to the other functions.
You can use as many DUH_SIGNAL_SAMPINFOs at once as you like.
Pass the DUH and the index of the signal whose samples you want to obtain
('sig'). Specify how many channels you want, and where you want to start
rendering (65536 represents one second).
There is no special need to check that this function succeeds. The other
functions are safe to call with null pointers. However, checking the
return value can make your code more efficient.
Be sure to call duh_signal_end_samples() when you've finished.
void duh_signal_set_parameter(
DUH_SIGNAL_SAMPINFO *signal_sampinfo,
unsigned char id, long value
);
Sets a parameter for the signal whose samples are being rendered by
signal_sampinfo.
Calls the set_parameter function for the instance started by
duh_signal_start_samples of the signal whose details you passed to that
function. Exactly what this does depends on the signal in question.
THIS IS NOT VERY HELPFUL. REFER TO A FILE?
long duh_signal_render_samples(
DUH_SIGNAL_SAMPINFO *signal_sampinfo,
float volume, float delta,
long size, sample_t **samples
);
Renders 'size' samples of the signal for which signal_sampinfo was set up.
See duh_render() and DUH_RENDER_SAMPLES for details on the 'volume' and
'delta' parameters. This function will return the number of samples
generated, which will be fewer than 'size' if the signal ends.
Sometimes you can pass the array of sample buffers which was passed to
your function, and process the data in place. Other times you will have to
set up the array of sample buffer pointers yourself, making sure each
buffer can hold 'size' samples. Below is some code to do that. Note that
we prefix some variable names with sub-, so they don't clash with the
parameters to the function that would typically contain this code.
sample_t **subsamples;
int n;
long subsize;
subsamples = malloc(n_channels * sizeof(*subsamples));
if (!subsamples)
return 0;
subsamples[0] = malloc(size * n_channels * sizeof(*subsamples[0]));
if (!subsamples[0]) {
free(subsamples);
return 0;
}
for (n = 1; n < n_channels; n++)
subsamples[n] = subsamples[n-1] + size;
subsize = signal_render_samples(
subsampinfo,
volume, delta,
size, subsamples
);
/* Process the samples here. */
free(subsamples[0]);
free(subsamples);
return subsize;
void duh_signal_end_samples(DUH_SIGNAL_SAMPINFO *signal_sampinfo);
Call this when you have finished with a DUH_SIGNAL_SAMPINFO struct. It
will free all memory used by the struct.
**************************
*** Resampling Helpers ***
**************************
The DUH player library provides a versatile resampling system. The sample
signal type uses it, and it is available for use in any of your signals.
Be warned that the resampler may overrun the memory you specify by up to
DUMB_EXTRA_SAMPLES samples, so you must allocate these samples and set
them to appropriate values when you load your signal. Generally, if you
are going to loop, set them to reflect the samples at the loop start
point; if you are not going to loop, set them to zero.
DUMB_EXTRA_SAMPLES is defined as follows:
#define DUMB_EXTRA_SAMPLES 3
extern int resampling_quality;
Allows you to control the quality of all resampling that takes place. This
may be set to any value from 0 to 4. Higher values will sound better, but
lower values will use up less processor time.
| --___
0 - Aliasing |__--- __
| ___--
| __
1 - Linear resampling | / \ /\
|/ \/ \__
2 - Linear resampling / linear average
3 - Quadratic / linear average
4 - Cubic / linear average
Level 0 has very noticeable unwanted overtones. It will occasionally
produce satisfactory results for noisy signals, but usually you will
want to pay for the extra processor time (which isn't much) and go
for Level 1.
Levels 1 and 2 are already pretty good. When resampling down a few
octaves, however, you will begin to notice unwanted high frequencies.
These can be eliminated by switching to Levels 3 or 4.
When Level 2 or higher are selected, a linear average function is used for
speeding the wave up. Instead of skipping samples, all samples in the
interval will be averaged. The interval is also smoothed at the edges.
This will be especially beneficial when increasing the pitch by several
octaves.
Levels 3 and 4 are both smooth curves to the eye. They both give
extremely good performance, but you may sometimes notice the difference
when reducing the pitch of a sample by several octaves, where Level 3
may exhibit unwanted high frequencies.
NOTE: WHAT IS THE DEFAULT? (2 at the moment, but might change. Config?)
long dumb_resample(
sample_t *src, long *_src_pos, int *_src_subpos,
long src_start, long src_end,
sample_t *dst, long dst_size,
float delta, int *_dir,
DUMB_RESAMPLE_PICKUP pickup, void *pickup_data
);
This is the resampling function. It takes an array of source samples and
fills as much of the destination array as it can for you. Its operation is
quite complicated, so pay attention.
All the parameters prefixed with an underline (_) are pointers to the
fields they describe. This means the function can modify the variables you
pass, but you have to prefix the variable with an ampersand (&) when
passing it. If you get warnings about ints being converted to pointers
without casts, you have most likely forgotten an ampersand somewhere.
'src' points to the source sample data. It should point to the beginning
of the sample, even if you are starting your resampling from somewhere in
the middle. Do not break this rule unless you know what you're doing.
'_src_pos' and '_src_subpos' together represent the current position in
the sample. When you first call the function, they represent the starting
position. Once the function has done its work, they will represent the
point at which the resampling stopped. '_src_pos' is measured in samples.
'_src_subpos' represents how far between samples we are, and ranges from
0 to 65535 - so if _src_subpos is 65535, we are very nearly on to the next
sample.
Once _src_pos and _src_subpos have been modified by this function, you can
pass them to the function again and the resampling will continue
seamlessly. This is important, as you will hardly ever get to render all
your samples in one go. Typically you will make one call to this function
from within your render_samples function.
This function does not need to know the size of the source buffer as such.
Instead, it knows src_start and src_end, which are start and end points.
The end point actually points to the first sample not to use, following
the usual start-inclusive end-exclusive convention. In general, the
resampling will stop when src_pos tries to pass one of these.
WARNING: dumb_resample() may read up to DUMB_EXTRA_SAMPLES samples beyond
src_end. Make sure the memory belongs to you.
The _dir parameter should be either 1 or -1, and tells this function
whether to go forwards or backwards respectively through the source sample
buffer. If _dir is 0, nothing will happen - resampling has stopped
permanently. Any other values of _dir will have unpredictable results, and
the debugging library will abort if you try to use them.
If _dir is 1, then _src_pos will only be tested against src_end.
If _dir is -1, then _src_pos will only be tested against src_start.
That means you can set src_start and src_end to your loop points, and
start playing from the beginning of the sample. The resampling will
proceed unimpeded while _src_pos is outside the loop section, provided it
is advancing towards the loop section. The sample signal makes extensive
use of this capability.
The output waveform will be rendered into the buffer pointed to by dst.
Up to dst_size samples will be generated. Fewer will be generated if the
resampling stops permanently. The number generated will be returned. You
can find out if resampling ended by testing the value of your direction
variable (pointed to by _dir); if it is 0, then resampling stopped
permanently.
If you pass NULL for dst, no samples will be generated. However, _src_pos,
_src_subpos and _dir will be updated as normal. The pick-up function will
be called as necessary; you should pass the usual 'src' parameter so it
can be passed to your pick-up function. (See below for information on
pick-up functions.) The function returns the number of samples that would
have been generated, had dst pointed somewhere. The operation of
do_resample() when dst is NULL is exactly the same as that when dst points
somewhere. However, do_resample() is much faster in this case; you should
use it when volume is 0, or when you are culling quieter samples to gain
execution speed.
If delta is 1.0f, the destination is the same speed as the source. Greater
values cause the sample to speed up, and lesser values cause the sample to
slow down. delta should always be positive.
We have covered most of the parameters. There are only two left - pickup
and pickup_data. The simplest usage is to set these both to NULL. Then, if
resampling stops, it stops permanently. Use this if your sample will not
loop.
Alternatively, you may wish to write a pick-up function. Your pick-up
function will take control whenever _src_pos tries to pass the start or
end points. You can use it for looping, or for a multitude of other tasks.
The typedef is as follows:
typedef int (*DUMB_RESAMPLE_PICKUP)(
sample_t *src, long *_src_pos, int *_src_subpos,
long *_src_start, long *_src_end,
int dir,
void *data
);
You are passed the source buffer, in case you want to fill it with new
samples. You have the _src_pos, _src_subpos, _src_start and _src_end
pointers, should you need to change the values to which they point. You
also have dir, but note that it is not a pointer. Instead, you should
return the new direction, or 0 to stop the resampling permanently.
The data parameter is a copy of pickup_data as passed to dumb_resample().
This is typically used to give you access to your 'sampinfo' parameter,
passed to your render_samples function.
BEWARE: you must refer to and change src_pos, src_subpos, src_start and
src_end through the parameters passed. Do not mistakenly use their
equivalents in your struct instead. The equivalents in your struct
will eventually be updated, but they will not be accurate during
the pick-up function.
On entry to this function, src_pos and src_subpos will have overrun by a
small amount. When changing them, you should preserve this overrun. The
following examples will do this for you:
If you are executing a simple loop, subtract (or add) the difference
between the loop points from (or to) src_pos. Do not simply set src_pos
to the other loop point.
*_src_pos -= *_src_end - *_src_start;
If you are executing a ping-pong loop, you need to reflect the pointer
off the loop boundary. To reflect off the loop end point:
*_src_pos = (*_src_end << 1) - 1 - *_src_pos;
*_src_subpos ^= 65535;
dir = -1;
To reflect off the loop start point:
*_src_pos = (*_src_start << 1) - 1 - *_src_pos;
*_src_subpos ^= 65535;
dir = 1;
In each case, don't forget to return the new direction correctly!
An ideal example of a pick-up function can be found in src/sample.c.
Of course there is more that can be done with a pick-up function. Enjoy
experimenting!
************************
*** DUH Construction ***
************************
DUH *make_duh(
long length,
int n_signals,
DUH_SIGTYPE_DESC *desc[],
void *signal[]
);
Constructs a DUH from its component parts. Use this function if you are
writing a function to load a music file format other than .duh. Indeed,
this function is used internally to load IT files.
Before you call this function, you must do some preparation. Your DUH will
contain a fixed number of signals; pass this number as n_signals. Each
signal will have a DUH_SIGTYPE_DESC struct, and you pass an array of
pointers to these. The DUH_SIGTYPE_DESC struct contains the following
fields:
long type;
DUH_LOAD_SIGNAL load_signal;
DUH_START_SAMPLES start_samples;
DUH_SET_PARAMETER set_parameter;
DUH_RENDER_SAMPLES render_samples;
DUH_END_SAMPLES end_samples;
DUH_UNLOAD_SIGNAL unload_signal;
The structs must be in permanent memory, i.e. either global or static, and
not modified later; however, the array of pointers can be destroyed as
soon as the function returns.
The values of 'type' and 'load_signal' are irrelevant; set them to 0 and
NULL respectively, unless you are using the DUH_SIGTYPE_DESC struct
elsewhere.
Because 'load_signal' is never used, you must provide an array of pointers
to the data that 'load_signal' would otherwise have returned. This will
be passed to the other functions as 'signal'. Once again, the array of
pointers can be destroyed as soon as the function returns. As for the
pointers themselves:
If an 'unload_signal' function is provided, then that will be used to
deallocate the pointer when you finally destroy the DUH. If the function
is not provided, then you are responsible for deallocating any memory
referenced by that pointer yourself. This applies individually to each
signal.
If this function fails and returns NULL, then any 'unload_signal'
functions you provided will have been called. You do not have any extra
work to do if it fails.
********************************
*** Allegro Packfile Support ***
********************************
void dumb_register_packfiles(void);
This function registers the Allegro PACKFILE input module for use by
DUMBFILEs. PACKFILE structs and their corresponding functions, as defined
by Allegro's header file allegro.h, will be used internally for all
DUMBFILE input (unless opened with dumbfile_open_ex()).
This must be called before dumbfile_open() is used, or else an alternative
system must be registered (see register_dumbfile_system() and
dumb_register_stdfiles()).
DUMBFILE *dumbfile_open_packfile(PACKFILE *p);
If you have an Allegro PACKFILE struct representing an open file, you can
call this if you wish to read from it using a DUMBFILE. This is useful
when you need to pass a DUMBFILE struct to a library function, to read an
embedded music file for example. When you close the DUMBFILE, you can
continue using the PACKFILE struct to read what follows the embedded data.
***********************************************
*** Allegro Datafile Registration Functions ***
***********************************************
void register_dat_duh(void);
If you wish to put a DUH file in an Allegro datafile, you must use "DUH "
for the type. The grabber will have a box for the type when you insert a
new object. The grabber will treat the DUH file as binary data, which
means the datafile will contain an exact copy of the DUH file on disk.
TODO: make it possible to choose the type...
You must then call register_dat_duh() in your program before you load the
datafile. Once you've done this, you'll be able to access the DUH using
the usual datafile[n].dat notation. You do not need to call unload_duh()
on this DUH; unload_datafile() will do that for you.
If you need to check the type of the object for whatever reason, you can
use DAT_DUH, defined as follows:
#define DAT_DUH DAT_ID('D','U','H',' ')
The following example iterates through all the DUHs in disan.dat:
DATAFILE *dat;
int n;
register_dat_duh();
dat = load_datafile("disan.dat");
for (n = 0; dat[n].type != DAT_END; n++) {
if (dat[n].type == DAT_DUH) {
DUH *duh = dat[n].dat;
/* Insert code here to play 'duh' or whatever you want to do. */
}
}
unload_datafile(dat);
void register_dat_it(void);
Inserting an IT file in an Allegro datafile is the same as inserting a DUH
file, except the type has to be "IT ", the registration function is
register_dat_it(), and the following definition is available for use
instead of DAT_DUH:
#define DAT_IT DAT_ID('I','T',' ',' ')
Once the datafile is loaded, the 'dat' field indeed points to a DUH
struct. There are no differences other than those listed above.
*************************************
*** Allegro DUH Playing Functions ***
*************************************
The functions in this section allow you to play back a DUH through
Allegro's sound system. You must call Allegro's install_sound() function
before you use them.
AL_DUH_PLAYER *al_start_duh(
DUH *duh, int n_channels, long pos, float volume, long bufsize, int freq
);
Starts playing the specified DUH.
An AL_DUH_PLAYER represents one instance of the DUH playing. If you wish,
you can have two or more AL_DUH_PLAYERs going at the same time, for the
same DUH or for different ones. Each uses one of Allegro's audio streams
and hence one voice.
At present, n_channels can either be 1 or 2 for monaural or stereo
respectively. If you use the debugging library, your program will abort if
other values are passed; otherwise weird things will happen.
The DUH will start playing from position 'pos'. 0 represents the start of
the DUH, and 65536 represents one second. Unlike other music systems, DUMB
will always make sure every note is there right from the start, provided
any custom signal types are designed properly. In other words, you can
start a DUH at a point halfway through a long note, and you will still
hear the long note.
The volume is a float. 1.0f is the pseudo-maximum. If you pass 1.0f, any
properly designed DUH file will play nice and loud, but will not clip. You
can pass a greater volume if you like, but be prepared for clipping to
occur. Of course you can pass smaller values to play the DUH more quietly,
and this will also resolve clipping issues in badly designed DUH files.
You will need to pass the AL_DUH_PLAYER to other functions when you need
to stop or pause the DUH, change its volume, or otherwise modify the way
it is playing. You will also need to pass it to al_poll_duh() at regular
intervals; if the sound is choppy, try calling al_poll_duh() more often.
'bufsize' is the number of samples that will be rendered at once. 1024 is
a suitable value for most purposes.
NOTE: IS THAT TRUE ON ALL SYSTEMS?
The greater this is, the less often you will have to call al_poll_duh() -
but when al_poll_duh() decides to fill the buffer, it will take longer
doing so. If your game exhibits regular brief freezes, try reducing the
buffer size. If the sound is choppy, however, you may have to increase it.
'freq' specifies the sampling frequency at which the DUH should be
rendered. At present there is no (official and portable) way of knowing
the frequency at which Allegro is mixing - but if you do know that
frequency, passing it here will give the highest quality sound. If you
reduce it, the DUH will sound less crisp but use less processor time.
When you have finished, you must pass the AL_DUH_PLAYER to al_stop_duh()
to free up memory. Do not destroy the DUH beforehand.
There is no real need to check the return value from this function. The
other functions can be called safely with null pointers, so if there is a
problem, your music will simply not play.
void al_stop_duh(AL_DUH_PLAYER *dp);
This will stop an AL_DUH_PLAYER. You must call this when you have finished
with it, before destroying the DUH. The pointer will no longer be valid on
return from this function.
void al_pause_duh(AL_DUH_PLAYER *dp);
This will pause an AL_DUH_PLAYER. Use al_resume_duh() when you want it to
continue.
void al_resume_duh(AL_DUH_PLAYER *dp);
Causes a paused AL_DUH_PLAYER to resume playing (see al_pause_duh()).
void al_duh_set_volume(AL_DUH_PLAYER *dp, float volume);
This will set the volume of an AL_DUH_PLAYER. See al_start_duh() for
details on the volume parameter.
int al_poll_duh(AL_DUH_PLAYER *dp);
An AL_DUH_PLAYER is not interrupt-driven. That means it will not play by
itself. You must keep it alive from your main program. Call this function
at regular intervals. If the sound crackles, try calling it more often.
(There is nothing you can do if Windows decides to play with the hard
disk; that will make your sound crackle no matter what you do.)
Normally this function will return zero. However, if it returns nonzero,
that means the AL_DUH_PLAYER will not generate any more sound. Indeed the
underlying audio stream and DUH_RENDERER have been destroyed. When this
happens, you can call al_stop_duh() whenever you wish - but you do not
have to. Note that this function will wait two buffers' worth of samples
before taking this action, allowing Allegro to mix the trailing sound
before the audio stream is destroyed. In other words, your music will not
be cut off prematurely.
In case you were wondering, it is definitely not safe to call
al_poll_duh() from an interrupt context. Not only is no part of DUMB
locked in memory, but many signals and many core library functions
allocate and free their memory on a call-by-call basis! Remember that any
disk access that occurs in interrupt context is likely to crash the
machine. (This limitation only applies to DOS at present, and is due to
the fact that the DOS file access functions are not re-entrant.
Multitasking systems are generally safe. However, even if you do not think
you are targeting DOS, it is worth considering that calling this function
regularly from your main loop is likely to be much more efficient than
relying on task switching to do it for you.)
long al_duh_get_position(AL_DUH_PLAYER *dp);
Tells you what position an AL_DUH_PLAYER is up to, or -1 if it is invalid
(perhaps owing to lack of memory). As usual, 65536 is one second. Note
that this is a whole number, whereas a fractional part is stored
internally; the sample will not be continuous if you terminate the
AL_DUH_PLAYER and then reinitiate it with the same position. Furthermore,
note that Allegro will not have mixed in all the sound up to this point;
if you wait for this to reach a certain position and then terminate the
AL_DUH_PLAYER, the sound will cut off too early.
NOTE: HOW TO GET AROUND THIS?
DUH_RENDERER *al_duh_get_renderer(AL_DUH_PLAYER *dp);
This function returns to you the DUH_RENDERER underlying the specified
AL_DUH_PLAYER. It has no practical use in this release, but it
TODO: Write this. Also see if it should go in howto.txt.
******************
*** Conclusion ***
******************
I conclude that... DUMB is the bestest music player in the world because...
Complete this sentence in fifteen words or fewer... D'OH!
Ben Davis
entheh@users.sf.net
IRC EFnet #dumb
See readme.txt for details on using IRC.