/* _______ ____ __ ___ ___ * \ _ \ \ / \ / \ \ / / ' ' ' * | | \ \ | | || | \/ | . . * | | | | | | || ||\ /| | * | | | | | | || || \/ | | ' ' ' * | | | | | | || || | | . . * | |_/ / \ \__// || | | * /_______/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, or a DUMBFILE module of your own, in order to load stand-alone music files. Optimised: -ldumb or /link dumb.lib Debugging: -ldumbd or /link dumbd.lib 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 (or provide one of your own). 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 -ldumb -lalleg or /link aldmb.lib alleg.lib dumb.lib Debugging: -laldmd -ldumbd -lalld or /link aldmd.lib alld.lib dumbd.lib aldmb or aldmd must be linked in first, so the symbols can be resolved when linking in the other two libraries. *************************** *** Version Information *** *************************** #define DUMB_MAJOR_VERSION #define DUMB_MINOR_VERSION #define DUMB_REVISION_VERSION Numeric constants representing this version of DUMB. If this were version 1.0, DUMB_MAJOR_VERSION would be 1 and DUMB_MINOR_VERSION would be 0. DUMB_REVISION_VERSION will be 0 on any significant releases, and will be incremented as releases with bugfixes and minor features are made. Typical usage: #if DUMB_MAJOR_VERSION < 1 #error This add-on requires DUMB v1.0 or higher. Please upgrade. #endif #define DUMB_VERSION A numeric constant which appears in the format MMmmrr when displayed in decimal (M for major, m for minor, r for revision). This is most useful for comparing version numbers; it has little other practical use. Typical usage: #if DUMB_VERSION < 801 #error This game requires DUMB v0.8.1 or higher. Please upgrade. #endif #if DUMB_VERSION < 10002 #error This game requires DUMB v1.0.2 or higher. Please upgrade. #endif #define DUMB_VERSION_STR String constant representing this version of DUMB. If this were Version 1.0, DUMB_VERSION_STR would be "1.0". DUMB_REVISION_VERSION will only appear on the end if it is nonzero; then DUMB_VERSION_STR might be "1.0.1". #define DUMB_NAME A string identifying DUMB and its version. If this were Version 1.0, DUMB_NAME might be "DUMB v1.0". This constant is suitable for use in your Credits screen if you wish to acknowledge the use of DUMB there. #define DUMB_YEAR #define DUMB_MONTH #define DUMB_DAY Numeric constants representing the year, month and day of this release of DUMB. All four digits are included in the year. Please note that DUMB_MONTH and DUMB_DAY were inadvertently swapped in the v0.8 release. #define DUMB_YEAR_STR4 #define DUMB_YEAR_STR2 #define DUMB_MONTH_STR2 #define DUMB_MONTH_STR1 #define DUMB_DAY_STR2 #define DUMB_DAY_STR1 String constants representing the year, month and day of this release of DUMB. DUMB_MONTH_STR2 and DUMB_DAY_STR2 include a leading zero if the month or day respectively are less than ten; the STR1 variations do not. DUMB_YEAR_STR2 contains only the two rightmost digits of the year, while DUMB_YEAR_STR4 contains all four. I recommend using DUMB_YEAR_STR4, especially so soon after the turn of the century (indeed the millennium). However, it is a matter of personal preference which you use. Please note that the month and day were inadvertently swapped in the v0.8 release. #define DUMB_DATE A numeric constant that appears in the form yyyymmdd when displayed in decimal. This is most useful for comparing release dates; it has little other practical use. WARNING: The month and day were inadvertently swapped in the v0.8 release. Please do not compare this constant against any date in 2002. In any case, DUMB_VERSION is probably more useful for this purpose. #define DUMB_DATE_STR The date as a string. The format is "d.m.yyyy", with dots used as separators, the day written first, four digits for the year, and no leading zeros on the day or month. This is my preferred format. If you don't like it, you can construct your own format using the other constants. For example, "mm/dd/yy" could be constructed as follows: DUMB_MONTH_STR2 "/" DUMB_DAY_STR2 "/" DUMB_YEAR_STR2 Please note that the month and day were inadvertently swapped in the v0.8 release. ************************* *** Basic Sample Type *** ************************* typedef int sample_t; DUMB works internally with 32-bit integer samples, with a 'normal range' from -0x800000 to 0x7FFFFF (as of DUMB v0.9.2; previously they ranged from -0x8000 to 0x7FFF). Any samples that exceed this range will eventually be clipped, and could cause integer overflow in extreme cases. *********************************** *** 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 avoid passing zero or negative numbers to malloc(), and avoid passing negative numbers to dumbfile_skip() and 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(). You must check the return value from this function. If it is NULL, the file could not be opened, and you must not pass the DUMBFILE to any other function. The debugging library will abort if you get this wrong; the optimised library will act weird. 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 data types, you can simplify this process by writing a function for each struct. dumbfile_getnc() returns the number of bytes successfully read, which will be less than n if an error occurs, and may be as low as zero. If dumbfile_getnc() returns -1, that means an error occurred on this DUMBFILE earlier, before this function was called. 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. ******************************** *** Memory File Input Module *** ******************************** DUMBFILE *dumbfile_open_memory(const char *data, long size); This function is useful if you have an image of a music file in memory. You might have such an image if you use dat2s to encode a datafile directly into the executable. Pass a pointer to the start of the memory, and the size of the image to make sure DUMB doesn't overrun the buffer. The resulting DUMBFILE will feed the contents of the image to you. Note that the pointer is of type 'char *'. Files are series of chars, and interpreting them directly as anything else isn't portable. ********************** *** DUH Management *** ********************** 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 calculated when the DUH is created, and this function simply lifts it from the struct. It may not truly correspond to the time for which the DUH will generate sound. For module files, it will represent the point at which the module first loops (or, in the case of some XM and MOD files, freezes). Any add-ons to DUMB will provide their own code for calculating this. The algorithm for calculating the length of a module file can be fooled, but only by very deliberate methods. In the early days, when modules could only be played by their editors and had to be exported to .wav or similar in order to be used elsewhere, musicians would sometimes make the player think it was looping when it wasn't in order to prevent their music from being exported properly. If the length of a module seems a lot less than it should be, the module is probably protected in this way. Getting around this protection reliably would be extremely difficult, but after considering it for a while I decided it would be better not to. The musician has a right to protect his or her music in this way, and I have no interest in actively breaking that protection. (On the other hand, some musicians were just showing off!) *********************************** *** IT, XM, S3M and MOD 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 256 (roughly speaking; in fact it will process one sample for each channel plus up to 192 extra samples that are continuing to play owing to Impulse Tracker's New Note Actions), and samples that have been cut will sound again as soon as the congestion clears. Samples are given priority according to their final volume after all factors affecting the volume of a sample have been considered. If you play two or more modules 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. Despite the name, this variable controls XM, S3M and MOD files as well as IT files. DUMB_IT_SIGDATA *duh_get_it_sigdata(DUH *duh); This function attempts to retrieve the DUMB_IT_SIGDATA struct from a DUH. This struct will exist for any IT, XM, S3M or MOD file, and you can use it to obtain or override module-specific information. If 'duh' is NULL, or if the DUH you pass contains something other than a music module, then this function will return NULL (which can safely be passed to any other function). DUMB_IT_SIGRENDERER *duh_get_it_sigrenderer(DUH_SIGRENDERER *sigrenderer); This function attempts to retrieve the DUMB_IT_SIGRENDERER struct from a DUH_SIGRENDERER. This struct will exist for any currently playing IT, XM, S3M or MOD file, and you can use it to obtain or override information specific to module playback. If 'sigrenderer' is NULL, or if the DUH_SIGRENDERER you pass is rendering something other than a music module, then this function will return NULL (which can safely be passed to any other function). DUH_SIGRENDERER *dumb_it_start_at_order (DUH *duh, int n_channels, int startorder); This function, given a DUH containing an IT, XM, S3M or MOD file, will start playing it at the specified order. If the DUH does not contain a module, this function will fail and return NULL. Note that starting at an arbitrary order may result in missing notes or other playback oddities. It should be used primarily for modules that contain multiple songs that start on different orders. If you wish just to start some music in the middle, consider using duh_start_sigrenderer() or al_start_duh() with the pos parameter set appropriately. void dumb_it_set_loop_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data), void *data); Installs a callback which will be called every time the module loops. You can pass any data pointer you like, and it will be passed to the callback for you. DUMB considers a file to loop when it reaches the end, or when a 'Jump to order' effect (Bxx in both IT/S3M and XM/MOD) jumps to the same order or a preceding order. This can result in the loop callback being called when the module isn't really looping, but this only happens if the module has a very deliberate design. See duh_get_length() for further musings on this subject. If your callback returns nonzero, the music will stop abruptly. Samples will be cut, and the main program will be notified that the DUH_SIGRENDERER has ended. Alternatively, if you pass the DUMB_IT_SIGRENDERER for 'data', or otherwise arrange for it to be available to the callback, then you can call: dumb_it_sr_set_speed(sigrenderer, 0); from inside the callback, and this will cause the music to freeze but samples will be able to continue playing. The xm_speed_zero callback will NOT be called in this case (see below for information on this callback). Note also that setting the speed in this way will work equally for IT and S3M files, even though a 'speed zero' effect can only exist in XM and MOD files. Beware when using this method; samples might not fade at all! A helper callback, dumb_it_callback_terminate(), is provided; installing this will cause the music to terminate when it tries to loop for the first time. Pass NULL to remove the callback function; the module will then loop as normal. void dumb_it_set_xm_speed_zero_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data), void *data); Installs a callback which is in many ways similar to the loop callback (see dumb_it_set_loop_callback()). This callback will be called whenever an F00 effect is encountered in a MOD or XM file, setting the speed to zero. If the callback returns nonzero, the music will terminate. If not, any currently playing samples will continue to play. You can pass any data pointer you like to this function, and it will be passed to your callback for you. The helper callback, dumb_it_callback_terminate(), will also work here; installing it will cause the music to terminate as soon as an F00 effect is encountered. Pass NULL to remove the callback function. void dumb_it_set_midi_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data, int channel, unsigned char byte), void *data); Installs a callback function which will be called whenever MIDI data are generated by an IT file. (No other module formats are capable of generating MIDI data, so your callback will never be called.) Zxx macros will generate MIDI data. These are most often used to set the parameters for IT's low-pass resonant filters, and DUMB will handle these messages by itself by default. See Impulse Tracker's documentation for the MIDI messages that control filters. However, Zxx macros can be used to send any kind of MIDI data. If you wish to interpret MIDI messages yourself, you can use this callback. Note that the only MIDI messages generated by DUMB at present are from Zxx macros; there are no messages for note start, stop, or anything else. If you return 1 from this callback, DUMB will subsequently ignore the byte of MIDI data. You can use this to prevent Zxx macros from controlling the filters, useful if they were intended to do something else. Note that this is NOT an effective way to disable filters, since instruments can have filter envelopes and initial filter parameters. DUMB provides no means to disable filters, as any IT file that uses them will sound wrong without them. If you want lower processor consumption, use a different piece of music. A helper callback, dumb_it_callback_midi_block(), is provided for blocking all MIDI messages and making Zxx macros do nothing. Pass NULL to remove the callback. int dumb_it_callback_terminate(void *data); This is a helper callback that can be installed with both dumb_it_set_loop_callback() and dumb_it_set_xm_speed_zero_callback(). In each case it will cause the music to terminate abruptly. int dumb_it_callback_midi_block(void *data, int channel, unsigned char byte); This helper callback, for use with dumb_it_set_midi_callback(), will absorb all MIDI messages, returning 1 to prevent DUMB from interpreting them itself. DUH *dumb_load_it(const char *filename); Loads the specified Impulse Tracker file, encapsulating it in a DUH struct. Once the file is loaded, it can be treated exactly the same as any other DUH in memory. If this fails it will return NULL, but you can safely pass this NULL value to DUMB's other functions, so you do not need to check the return value explicitly. 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(). WARNING: The behaviour of this function is undefined if you pass a DUMBFILE from which data have already been read; it is likely not to work. This oversight will be fixed in future releases. DUH *dumb_load_xm(const char *filename); Loads the specified Fast Tracker II file, encapsulating it in a DUH struct. Once the file is loaded, it can be treated exactly the same as any other DUH in memory. If this fails it will return NULL, but you can safely pass this NULL value to DUMB's other functions, so you do not need to check the return value explicitly. DUH *dumb_read_xm(DUMBFILE *f); Reads a Fast Tracker II file from an already open DUMBFILE. This leaves the DUMBFILE open, but the DUMBFILE may not be positioned at the end of the XM data. If you are embedding an XM in another file, you are advised to store the size of the XM file and make up for it at the end using dumbfile_pos(). Otherwise, this function is identical to dumb_load_xm(). WARNING: The behaviour of this function is undefined if you pass a DUMBFILE from which data have already been read; it is likely not to work. This oversight will be fixed in future releases. DUH *dumb_load_s3m(const char *filename); Loads the specified Scream Tracker 3 file, encapsulating it in a DUH struct. Once the file is loaded, it can be treated exactly the same as any other DUH in memory. If this fails it will return NULL, but you can safely pass this NULL value to DUMB's other functions, so you do not need to check the return value explicitly. DUH *dumb_read_s3m(DUMBFILE *f); Reads a Scream Tracker 3 file from an already open DUMBFILE. This leaves the DUMBFILE open, but the DUMBFILE may not be positioned at the end of the S3M data. If you are embedding an S3M in another file, you are advised to store the size of the S3M file and make up for it at the end using dumbfile_pos(). Otherwise, this function is identical to dumb_load_s3m(). WARNING: The behaviour of this function is undefined if you pass a DUMBFILE from which data have already been read; it is likely not to work. This oversight will be fixed in future releases. DUH *dumb_load_mod(const char *filename); Loads the specified Amiga module file, encapsulating it in a DUH struct. Once the file is loaded, it can be treated exactly the same as any other DUH in memory. If this fails it will return NULL, but you can safely pass this NULL value to DUMB's other functions, so you do not need to check the return value explicitly. DUH *dumb_read_mod(DUMBFILE *f); Reads an Amiga module file from an already open DUMBFILE. This leaves the DUMBFILE open, but the DUMBFILE may not be positioned at the end of the MOD data. If you are embedding a MOD in another file, you are advised to store the size of the MOD file and make up for it at the end using dumbfile_pos(). Otherwise, this function is identical to dumb_load_mod(). WARNING: The behaviour of this function is undefined if you pass a DUMBFILE from which data have already been read; it is likely not to work. This oversight will be fixed in future releases. int dumb_it_sd_get_n_orders(DUMB_IT_SIGDATA *sd); This function returns the number of orders in the module. int dumb_it_sd_get_initial_global_volume(DUMB_IT_SIGDATA *sd); void dumb_it_sd_set_initial_global_volume(DUMB_IT_SIGDATA *sd, int gv); These functions obtain and set the initial global volume for the module. This value ranges from 0 to 128 inclusive. The module can set the global volume itself during playback, so your change may not last throughout the playback. int dumb_it_sd_get_mixing_volume(DUMB_IT_SIGDATA *sd); void dumb_it_sd_set_mixing_volume(DUMB_IT_SIGDATA *sd, int mv); These functions obtain and set the mixing volume for the module. This value ranges from 0 to 128 inclusive, and does not change during playback. IT files have the mixing volume stored in them; for other formats it is set to 48 on loading. int dumb_it_sd_get_initial_speed(DUMB_IT_SIGDATA *sd); void dumb_it_sd_set_initial_speed(DUMB_IT_SIGDATA *sd, int speed); int dumb_it_sd_get_initial_tempo(DUMB_IT_SIGDATA *sd); void dumb_it_sd_set_initial_tempo(DUMB_IT_SIGDATA *sd, int tempo); These functions obtain and set the initial speed and tempo for the module. During module playback, everything happens on a tick. If a beat is 24 ticks, then the tempo is measured in beats per second. The speed is then the number of ticks per row. With a speed of 6, a beat is then four rows. Modules can set these values during playback, so your change may not last throughout the playback. MOD files have to set the speed and tempo on the first row if they want anything other than the default 6/125, so your change may not be noticed at all! int dumb_it_sd_get_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel); void dumb_it_sd_set_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel, int volume); These functions obtain and set the initial volume for the specified channel. The channel parameter is 0-based (contrary to the display in most trackers so be careful), and can range from 0 to DUMB_IT_N_CHANNELS - 1, i.e. from 0 to 63. Modules can set their channel volumes during playback, so your changes may not last throughout the playback. int dumb_it_sr_get_current_order(DUMB_IT_SIGRENDERER *sr); int dumb_it_sr_get_current_row(DUMB_IT_SIGRENDERER *sr); These functions return the current order and row of playback. Both are 0-based. If the DUMB_IT_SIGRENDERER is invalid, or has been terminated by a callback (see dumb_it_set_loop_callback() and dumb_it_set_xm_speed_zero_callback()), these functions will both return -1. int dumb_it_sr_get_global_volume(DUMB_IT_SIGRENDERER *sr); void dumb_it_sr_set_global_volume(DUMB_IT_SIGRENDERER *sr, int gv); These functions obtain and set the current global volume for the module. This value ranges from 0 to 128 inclusive. The module can set the global volume itself during playback, so your change may not last. int dumb_it_sr_get_tempo(DUMB_IT_SIGRENDERER *sr); void dumb_it_sr_set_tempo(DUMB_IT_SIGRENDERER *sr, int tempo); int dumb_it_sr_get_speed(DUMB_IT_SIGRENDERER *sr); void dumb_it_sr_set_speed(DUMB_IT_SIGRENDERER *sr, int speed); These functions obtain and set the current speed and tempo of the module. See the dumb_it_sd_*() equivalents of these functions for details on what the speed and tempo mean. Modules can set these values during playback, so your change may not last. int dumb_it_sr_get_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel); void dumb_it_sr_set_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel, int volume); These functions obtain and set the current volume for the specified channel. The channel parameter is 0-based (contrary to the display in most trackers so be careful), and can range from 0 to DUMB_IT_N_CHANNELS - 1, i.e. from 0 to 63. Modules can set their channel volumes during playback, so your changes may not last. void dumb_it_sr_get_channel_state(DUMB_IT_SIGRENDERER *sr, int channel, DUMB_IT_CHANNEL_STATE *state); Returns the current playback state of the given channel. If you pass a channel in the range 0 to DUMB_IT_N_CHANNELS-1 (0 to 63), you will get the state of the most recently played note on that physical channel, if it is still playing. For MOD, S3M and XM files, that's all there is to it. IT files can have more than one note playing on a single channel, courtesy of New Note Actions. This function also lets you query all the notes that have been forced into the background and are still playing. For this, set 'channel' to a value from DUMB_IT_N_CHANNELS to DUMB_IT_TOTAL_CHANNELS-1. DUMB_IT_TOTAL_CHANNELS is defined as follows: #define DUMB_IT_TOTAL_CHANNELS \ (DUMB_IT_N_CHANNELS + DUMB_IT_N_NNA_CHANNELS) Querying these background channels for MOD, S3M and XM files will not do any harm; the function will report that these channels are inactive. For all files, be sure not to query any channel numbers greater than or equal to DUMB_IT_TOTAL_CHANNELS. You must provide a pointer to a preallocated DUMB_IT_CHANNEL_STATE struct. The easiest way to do this is as follows: DUMB_IT_CHANNEL_STATE state; dumb_it_sr_get_channel_state(sr, channel, &state); or: DUMB_IT_CHANNEL_STATE state[IT_TOTAL_CHANNELS]; dumb_it_sr_get_channel_state(sr, channel, &state[channel]); This struct contains the following fields: int channel; int sample; int freq; float volume; unsigned char pan; signed char subpan; unsigned char filter_cutoff; unsigned char filter_subcutoff; unsigned char filter_resonance; The first field to check is 'sample'; if this is 0, then the channel is inactive and the other fields are undefined. Otherwise, it is the index of the currently playing sample, and is 1-based. The channel number is returned, 0-based. This will be the same as the channel number you passed, unless you are querying a background channel in which case it will represent the channel the note originated on. The freq field is the current playback frequency, taking into account all phenomena such as slides, vibrato and arpeggio. The volume field ranges from 0.0f to 1.0f. In practical terms, it will rarely reach 1.0f; if it does, the module is probably clipping a lot. This takes mixing volume into account, along with all the other volume phenomena in the IT file. The only one it doesn't take into account is the one you pass to duh_render() or duh_sigrenderer_get_samples(), or the one you passed to al_start_duh() (these are in fact the same thing). The pan field ranges from 0 to 64 for a normally panned sample, but will be 100 if the sample is playing using IT's surround mode where the right- hand channel is inverted. If you want a more accurate pan reading, use one of the following to get one: int scaled_pan = ((int)state.pan << 8) + state.subpan; float float_pan = state.pan + state.subpan / 256.0f; The first will give a scaled value ranging (strictly) from 0 to 64*256. The second will give a floating-point value whose scale corresponds to that of the pan field. These results will only be valid if surround mode is off, so you should check that pan <= 64 before using the above expressions. At the time of writing, pitch-pan separation and panning envelopes take advantage of the extra accuracy offered by subpan. Note that subpan is signed. This means applications that only look at the pan field will get an unbiased reading. The filter cut-off and resonance both range from 0 to 127. If the cut-off is 127 and the resonance is 0, then no filters are applied. These parameters only ever change from the default values for IT files. While IT allows you to set 127 different filter cut-off levels in the patterns and as a default value per instrument, it also allows you to create a filter envelope, which will result in an actual cut-off somewhere between 0 and the first-mentioned value. By the time this has been calculated, the actual cut-off may lie in between two levels on the original scale. If this is the case, filter_subcutoff will be nonzero and you can combine it with filter_cutoff. Typically you will want to use one of the following: int scaled_cutoff = ((int)state.filter_cutoff << 8) + state.filter_subcutoff; float float_cutoff = state.filter_cutoff + state.filter_subcutoff / 256.0f; The first will give you a scaled value whose maximum is 127*256. The second will give you a floating-point value whose scale corresponds to the scale used by filter_cutoff. These match the expressions given further up for pan and subpan, but in this case, filter_subcutoff is unsigned. Note that filter_subcutoff will always be zero if filter_cutoff is 127, so you need not check it if you simply wish to determine whether filters are being applied. ******************************* *** DUH Rendering Functions *** ******************************* Use these functions to generate samples from a DUH. First you call duh_start_sigrenderer() with the DUH, the number of channels you want and the position at which you want to start. Then you use duh_render() or duh_sigrenderer_get_samples() to generate the samples. You can call these functions as many times as you like, and they will generate as many or as few samples as you require. When you have finished, call duh_end_sigrenderer(). DUH_SIGRENDERER *duh_start_sigrenderer (DUH *duh, int sig, int n_channels, long pos); Starts a DUH_SIGRENDERER 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() or duh_sigrenderer_get_samples() for that. When you have finished with it, you must pass it to duh_end_sigrenderer(). You can use as many DUH_SIGRENDERER structs as you like at the same time. Set sig to 0 for now. 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 (assuming you aren't using any broken add-ons). In other words, you can start a DUH at a point halfway through a long note, and you will still hear the long note. void duh_sigrenderer_set_analyser_callback(DUH_SIGRENDERER *sigrenderer, DUH_SIGRENDERER_ANALYSER_CALLBACK callback, void *data); Installs a callback function which will be called every time the given sigrenderer is used to generate some samples. This can be used to create an oscilloscope or spectrum analyser. DUH_SIGRENDERER_ANALYSER_CALLBACK is defined as follows: typedef void (*DUH_SIGRENDERER_ANALYSER_CALLBACK)(void *data, const sample_t *const *samples, int n_channels, long length); If the above confuses you, see fnptr.txt. As for the 'samples' parameter, the first 'const' says that the samples are read-only; the second says that each channel's sample pointer is also read-only. If you don't understand this, don't worry about it. Beware: your callback function may occasionally be called with samples == NULL. This means the main program has decided to skip through the music without generating any data (see duh_sigrenderer_get_samples()). You should handle this case elegantly, typically by returning immediately, but you may wish to make a note of the fact that the music is being skipped, for whatever reason. Beware again: if the main program ever calls duh_sigrenderer_get_samples() on a buffer that isn't all silence, this callback function will be passed the existing buffer after mixing, and thus it will include the original data. This will not be an issue if you stick to duh_render(), which always starts with a buffer filled with silence. The samples array is two-dimensional. Refer to it as follows: samples[channel_number][sample_position] where 0 <= channel_number < n_channels, and 0 <= sample_position < length. In addition you can pass any 'data' pointer you like to duh_sigrenderer_set_analyser_callback(), and this pointer will be relayed to your callback function each time. To remove the callback function, pass NULL to duh_sigrenderer_set_analyser_callback(). int duh_sigrenderer_get_n_channels(DUH_SIGRENDERER *sigrenderer); Tells you how many channels a DUH_SIGRENDERER is set up to generate, or 0 if it is invalid (perhaps owing to lack of memory). This will be 1 for monaural sound or 2 for stereo, in this release. long duh_sigrenderer_get_position(DUH_SIGRENDERER *sigrenderer); Tells you what position a DUH_SIGRENDERER is up to, or -1 if it is invalid (perhaps owing to lack of memory). As usual, 65536 is one second. long duh_sigrenderer_get_samples(DUH_SIGRENDERER *sigrenderer, float volume, float delta, long size, sample_t **samples); Generates some samples in DUMB's internal 32-bit format (see sample_t; see also duh_render()). The samples buffer is a two-dimensional array, and can be allocated with create_sample_buffer(); see duh_sigrenderer_set_analyser_callback() for details. duh_sigrenderer_get_samples() mixes sample data with what's already in the buffer, so you have to call dumb_silence() first. 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 the possibility of distortion due to integer overflow. 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_sigrenderer_get_samples() if you wish, and it will continually return 0. If the DUH_SIGRENDERER is a null pointer, this function will generate precisely 0 samples. If you pass NULL for 'samples', the function will behave exactly the same as if you provided a sample buffer, except the samples won't be stored anywhere and the function will execute very quickly. This can be used to skip ahead in the audio. long duh_render(DUH_SIGRENDERER *sigrenderer, int bits, int unsign, float volume, float delta, long size, void *sptr); Generates some samples and converts them to an 8-bit or 16-bit format (see also duh_sigrenderer_get_samples()). Pass the DUH_SIGRENDERER as returned by duh_start_sigrenderer(). 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 and delta parameters work the same as for duh_sigrenderer_get_samples(). 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. If the DUH_SIGRENDERER is a null pointer, this function will generate precisely 0 samples. Unlike with duh_sigrenderer_get_samples(), you must specify a sample buffer. void duh_end_sigrenderer(DUH_SIGRENDERER *dr); Terminates a DUH_SIGRENDERER. Be sure to call this when you've finished with one. You can safely pass a null pointer. ******************************** *** 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()). Note that you don't have to call this function in order to load datafiles that contain music. 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. DUMBFILE *dumbfile_from_packfile(PACKFILE *p); This function is the same as dumbfile_open_packfile(), except it will check if p is NULL, and arrange for pack_fclose() to be called on the PACKFILE when you close the DUMBFILE. It can be seen as a function for converting a PACKFILE to a DUMBFILE, but it will only work for a PACKFILE you obtained with pack_fopen(), not pack_fopen_chunk(). If this function fails, which may happen if memory is short, then the PACKFILE will be closed immediately, so you need not worry about potential memory leaks or files being left open when this happens. The following is typical usage, and will open the compressed file foo.bin: DUMBFILE *f = dumbfile_from_packfile(pack_fopen("foo.bin", F_READ_PACKED)); This differs from calling dumb_register_packfiles() and dumbfile_open() in that the latter will only read uncompressed files (and is thus a method suitable for reading music modules). *********************************************** *** Allegro Datafile Registration Functions *** *********************************************** void dumb_register_dat_it(long type); If you wish to embed an IT file in an Allegro datafile, it is recommended that you use "IT " for the type. The grabber will have a box for the type when you insert a new object. The grabber will treat the IT file as binary data, which means the datafile will contain an exact copy of the IT file on disk. You must then call dumb_register_dat_it(DUMB_DAT_IT) 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 are using a different type for whatever reason, you can use Allegro's DAT_ID() macro for encoding it and passing it to this function. For example: dumb_register_dat_it(DAT_ID('B','L','A','H')); Assuming you used the recommended type, the following example iterates through all the ITs in disan.dat: DATAFILE *dat; int n; dumb_register_dat_it(); dat = load_datafile("disan.dat"); for (n = 0; dat[n].type != DAT_END; n++) { if (dat[n].type == DUMB_DAT_IT) { DUH *duh = dat[n].dat; /* Insert code here to play 'duh' or whatever you want to do. */ } } unload_datafile(dat); void dumb_register_dat_xm(long type); Inserting an XM file in an Allegro datafile is the same as inserting an IT file, except that the recommended type is "XM ", the registration function is dumb_register_dat_xm(), and the macro DUMB_DAT_XM is provided for the type. The intuitive process of substituting XM for IT in the above method will work. void dumb_register_dat_s3m(long type); Inserting an S3M file in an Allegro datafile is the same as inserting an IT file, except that the recommended type is "S3M ", the registration function is dumb_register_dat_s3m(), and the macro DUMB_DAT_S3M is provided for the type. The intuitive process of substituting S3M for IT in the above method will work. void dumb_register_dat_mod(long type); Inserting a MOD file in an Allegro datafile is the same as inserting an IT file, except that the recommended type is "MOD ", the registration function is dumb_register_dat_mod(), and the macro DUMB_DAT_MOD is provided for the type. The intuitive process of substituting MOD for IT in the above method will work. **************************************** *** Sample Buffer Allocation Helpers *** **************************************** Many parts of DUMB require sample buffers allocated in a special way. A pointer to one looks like this: sample_t **samples; and it can be indexed as follows: samples[channel_number][sample_position] where 0 <= channel_number < n_channels and 0 <= sample_position < length. The following helpers will allocate and deallocate such buffers for you. They will not initialise them, and DUMB always writes into these buffers by adding to what's already there, so you will generally have to call dumb_silence() too. sample_t **create_sample_buffer(int n_channels, long length); This will allocate a sample buffer to hold the specified number of samples for the specified number of channels. Don't forget to check the return value! You will generally have to initialise the buffer by calling dumb_silence(); the channels will be stored consecutively in memory, so the following technique is officially supported: dumb_silence(samples[0], n_channels * length); See dumb_silence() for general information on what this function does. void destroy_sample_buffer(sample_t **samples); This function does the obvious: it frees up a sample buffer when you've finished with it. It is safe to pass a null pointer to this function. ************************ *** Silencing Helper *** ************************ void dumb_silence(sample_t *samples, long length); This function simply stores 'length' samples' worth of silence in the array. It is typically used straight after allocating a sample buffer with create_sample_buffer(). ************************** *** Resampling Helpers *** ************************** Please forgive the odd section name; it has to do with DUMB's internal structure and the fact that the resampling algorithm is there not just for use in rendering module files but for use anywhere that a waveform needs resampling. Unfortunately DUMB's resampling algorithm is not ready to be documented and used yet. However, one thing can be documented, and that's the global variable controlling the resampling quality. (Ironically, even this variable has changed! See deprec.txt for information on what it used to do.) int dumb_resampling_quality; Allows you to control the quality of all resampling that takes place. This may be set to any DUMB_RQ_* constant (except DUMB_RQ_N_LEVELS). Higher values will sound better, but lower values will use up less processor time. You may compare any two DUMB_RQ_* constants or values using the integer inequalities <, <=, > and >=; higher numbers represent higher- quality algorithms. #define DUMB_RQ_ALIASING | --___ 'Aliasing' has very noticeable and usually unwanted |__--- __ overtones. It will occasionally produce acceptable | ___-- results for noisy (impure) samples (or for cheap speakers!), but usually you will want to pay for the extra processor time, which isn't much, and go for linear interpolation. #define DUMB_RQ_LINEAR | __ Linear interpolation is a pretty good algorithm in most | / \ /\ cases. When resampling down a few octaves, however, you |/ \/ \__ may begin to notice unwanted high frequencies. You can reduce these by switching to cubic interpolation, but it will cost you some processor time. #define DUMB_RQ_CUBIC Cubic interpolation looks like a smooth curve to the eye, and will produce good results in most cases. At present this is the highest quality offered by DUMB, and also the default. While this may seem extravagant, GCC 3.x and an AthlonXP handle it quite well - and the general trend is for processors to get better! #define DUMB_RQ_N_LEVELS This represents the number of resampling quality levels DUMB provides. Values of dumb_resampling_quality from 0 to DUMB_RQ_N_LEVELS - 1 are valid. You can use this constant if you wish to offer the resampling quality as an option for the user. ************************************* *** 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. The voice will be given priority 255 initially, so a build-up of sound effects will not cause your music streams to cut off (as long as you don't give all your sound effects priority 255!). You can change the priority of a stream with al_duh_set_priority(). See Allegro's documentation for more information on how voice priorities work. 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. 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. 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. You can safely call al_poll_duh() while the music is paused, and it will do nothing. 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_priority(AL_DUH_PLAYER *dp, int priority); This will set the priority of the audio stream underlying an AL_DUH_PLAYER. The priority is an integer ranging from 0 to 255. When too many samples play at the same time, those with lower priorities will be cut. 128 is the usual default with Allegro, but DUMB overrides the default for all AL_DUH_PLAYER structs: they will be set up initially with priority 255, so your music won't be cut (unless you play too many other streams or samples with priority 255). See Allegro's documentation for more information on priorities. 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_SIGRENDERER 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. This is an attempt to make sure your music does not get cut off prematurely, and it should work when using Allegro's mixer (the only option on DOS, the default on Linux as far as I know, but not the default on Windows). That said, if you immediately call Allegro's remove_sound() or exit your program, the music may get cut off. If you are using another mixer and experience problems, let me know (but I don't guarantee to be able to come up with an elegant solution, i.e. it might not get fixed). In case you were wondering, it is not safe on all platforms to call al_poll_duh() from an interrupt context (that means an Allegro timer handler). Not only is no part of DUMB locked in memory, but many parts of DUMB 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 is explained more fully in howto.txt. 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. If you are sure you don't want to target DOS, you can call al_poll_duh() from inside a timer handler, but I recommend including a construction like the following! #ifdef ALLEGRO_DOS #error calling al_poll_duh() from a timer handler will not work in DOS! #endif Furthermore, if you call al_poll_duh() from inside a timer handler, you must use a semaphore or other threading mechanism to make sure it is not executing when you call al_stop_duh(). If you don't know what a semaphore is, for Heaven's sake follow my advice and call al_poll_duh() from your main loop! 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. Please contact me if you need to get around this. AL_DUH_PLAYER *al_duh_encapsulate_sigrenderer (DUH_SIGRENDERER *sigrenderer, float volume, long bufsize, int freq); If you have a DUH_SIGRENDERER, and would like to start playing music from it through an Allegro audio stream, use this function. Beware that it may return NULL, in which case you will have to call duh_end_sigrenderer() yourself instead of relying on the encapsulating AL_DUH_PLAYER to do it for you. DUH_SIGRENDERER *al_duh_get_sigrenderer(AL_DUH_PLAYER *dp); This returns the DUH_SIGRENDERER contained in an AL_DUH_PLAYER, useful for controlling playback, installing callbacks, etc. DUH_SIGRENDERER *al_duh_decompose_to_sigrenderer(AL_DUH_PLAYER *dp); This destroys an AL_DUH_PLAYER, but preserves the DUH_SIGRENDERER it contains, and returns it to you. You can then continue rendering samples from the DUH_SIGRENDERER and do whatever you like with them. ********************* *** Thread Safety *** ********************* The following points should pretty much sum up the essence of DUMB's thread safety. If I haven't covered the one thing you'd like to do, please don't hesitate to ask about it. DOs: - You may load and use multiple DUHs in separate threads. - You may change dumb_resampling_quality and dumb_it_max_to_mix while another thread is generating samples. DON'Ts: - You may not generate samples from the same DUH in multiple threads, even if you are using separate DUH_RENDERERs (separate AL_DUH_PLAYERS). ****************** *** Conclusion *** ****************** "DUMB is the bestest music player in the world because ..." Complete this sentence in fifteen words or fewer and receive a free copy of DUMB! (Your Internet Service Provider may issue charges for your connection, required for download of the Product. Your electricity supplier may issue charges for the electricity consumed in writing the Product to a Permanent Storage Device. You may have been charged for a Permanent Storage Device on which to store the Product.) Ben Davis entheh@users.sf.net IRC EFnet #dumb See readme.txt for details on using IRC.