/* _______ ____ __ ___ ___ * \ _ \ \ / \ / \ \ / / ' ' ' * | | \ \ | | || | \/ | . . * | | | | | | || ||\ /| | * | | | | | | || || \/ | | ' ' ' * | | | | | | || || | | . . * | |_/ / \ \__// || | | * /_______/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 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.