/* _______ ____ __ ___ ___ * \ _ \ \ / \ / \ \ / / ' ' ' * | | \ \ | | || | \/ | . . * | | | | | | || ||\ /| | * | | | | | | || || \/ | | ' ' ' * | | | | | | || || | | . . * | |_/ / \ \__// || | | * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque * / \ * / . \ * duhspecs.txt - DUH File Specifications. / / \ \ * | < / \_ * Written by entheh, one of the few programmers | \/ /\ / * in existance who can spell correctly. \_ / > / * | \ / / * | ' / * \__/ */ Technical Details ================= WARNING: until this warning disappears, the DUH file format could change at any moment. This should not be of great concern, since DUH files are not designed to be edited directly, but will always be generated from some other format. However, it is our intention that this warning be removed before the first release. This document is written chiefly in the context of writing a DUH file, since the library already contains the necessary functionality to read and play a DUH file. DUH files are currently saved using Allegro's file compression routines. See Allegro's documentation and source code for details on this system. If you wish to port DUMB away from Allegro and wish to preserve the file compression capabilities, you will have to borrow the packfile source code from Allegro. If you are happy to do away with file compression, please store the following four-byte signature before the rest of the file: "slh." Alternatively, write your DUH file writer with Allegro, and open the file with F_WRITE_NOPACK. This will enable versions of the library using Allegro's file compression routines to load the file. If you are reading a DUH file and you detect the signature "slh!", then the file is compressed (and is not necessarily a DUH file). All numbers are little-endian unless specified otherwise. Allegro's pack_iget*() and pack_iput*() functions can be used to read and write data in this format. However, the four-byte signatures can be encoded into long ints with AL_ID() and read and written with pack_m*(). Overall Structure ================= Size Type Value Example C code to save to PACKFILE *f 4 ID "DUH!" pack_mputl(AL_ID('D','U','H','!'), f); 4 Int Number of signals pack_iputl(n_signals, f); For each signal { for (i = 0; i < n_signals; i++) { 4 ID Signal type pack_mputl(AL_ID('S','E','Q','U'), f); * - Signal data write_sequence(f); } } * The size of the data for any signal must either be constant or somehow encoded in the data themselves. The library contains functions to read various standard signal types, including "SAMP" and "SEQU" (sample and sequence respectively), and the formats for these types are laid out further down. If you wish to create your own signals, you must provide your own loading function for the signal. This will be described in more detail in a separate file. In order to play a DUH file, we simply play the first signal. Signals can construct their sound from the samples of other signals, and they in turn can use other signals. Thus a recursive structure is built up. Recursive cycles are not permitted. Signal: SAMP (Sample) ===================== Size Type Value Example C code to save to PACKFILE *f 4 Int Size pack_iputl(size, f); 1 Bits Flags pack_putc(flags, f); 1 ID Compression type pack_putc(compress, f); /* NOT IMPLEMENTED YET */ The flags are stored in a bit-field. Bit 0 indicates whether 16-bit samples (set) or 8-bit samples (clear) are stored in the file. In both cases, the samples are signed. NOTE: this bit might be replaced with a system allowing for various sample compression algorithms, or altered so there are different signal types for the purpose. If Bit 1 is set, the sample is a looping sample, and loops indefinitely. In this case the loop start point will be saved. The loop end point is not saved, and is assumed to be the end of the sample. (When creating DUH files from other formats which allow for the loop end to be earlier, you should truncate the sample.) If Bit 1 is not set, then Bit 2 may be set to indicate that the sample is looping but only loops a finite number of times before continuing to play normally. In this mode, both loop points (start and end) are saved in the file. The number of times to loop will be specified on an instance-by- instance basis using signal parameter #0, which should be set immediately (before any samples are rendered) if it is to be set at all. It defaults to 0 (so the sample just plays through normally). In fact this parameter's value is added to the loop count, but this is immaterial since there is no reason to specify it more than once. If Bit 1 is set, you should make sure Bit 2 is clear to allow for the possibility of future expansion. If Bit 3 is set, a ping-pong loop is used. When the sample reaches the loop end point, it starts to play backwards until it reaches the loop start point, at which time it will resume forward playback. When using a finite loop, every change of direction counts as one iteration. That means an odd loop count will cause the sample to proceed backwards when the looping ends. If neither Bit 1 nor Bit 2 is set, then neither loop point will be saved. In this case, you should also make sure Bit 3 is clear for the same reason as above. You may find the following definitions useful: #define SAMPFLAG_16BIT 1 #define SAMPFLAG_LOOP 2 #define SAMPFLAG_XLOOP 4 #define SAMPFLAG_PINGPONG 8 #define SAMPPARAM_N_LOOPS 0 Size Type Value Example C code to save to PACKFILE *f 4 Int Loop start pack_iputl(loop_start, f); 4 Int Loop end pack_iputl(loop_end, f); For a 16-bit sample: if (flags & SAMPFLAG_16BIT) for (n = 0; n < size; n++) x*2 Int Sample data pack_iputw(sample[n], f); For an 8-bit sample: else for (n = 0; n < size; n++) x*1 Int Sample data pack_putc(sample[n], f); /* Compression type is 0 for uncompressed PCM. */ Signal: SEQU (Sequence) ======================= Size Type Value Example C code to save to PACKFILE *f 4 Int Size size = pack_igetl(f); x - Sequencing data pack_fwrite(data, size, f); The sequence signal provides a medium in which other signals can be played at specific times for specific lengths. You can control the pitch, volume and other parameters for a signal, and these can change during the signal. A sequence consists of a series of commands. Each command is preceded by a time, which measures how long to wait before executing this command. A time of zero indicates that this command is simultaneous with the previous. A time of -1 indicates the end of the sequence. Note that signals do not stop playing when the end is reached. All times are measured in units such that 65536 corresponds to one second. The timing in DUMB is accurate to the nearest sample, and cannot be offset in the way it can with much mixing software, so you can rely on timing to achieve certain effects. Resampling should be accurate enough to satisfy the most acute musician's ear, but juggling pitches at this level of accuracy requires knowledge of temperaments such as many musicians do not have. The vast majority of people are satisfied with the even temperament. More on this later. Size Type Value Example C code to save to PACKFILE *f 4 Int Time pack_iputl(time, f); 1 ID Command pack_putc(SEQUENCE_START_SIGNAL, f); /******************************** Proposed change: Time is a short, encoded in 2 bytes. The value of 'time' is actually an unsigned offset from the time of the previous command. 0 means at the same time as the last command. If the time in between this signal and the previous one is larger than 65534 ticks, then the value 65535 is written, followed by 4 more bytes (uint) indicating the time offset. **********************************/ Here are definitions for the various commands: #define SEQUENCE_START_SIGNAL 0 #define SEQUENCE_SET_VOLUME 1 #define SEQUENCE_SET_PITCH 2 #define SEQUENCE_SET_PARAMETER 3 #define SEQUENCE_STOP_SIGNAL 4 Below are the details of what to write after each command code. The various fields are explained afterwards. Size Type Value Example C code to save to PACKFILE *f SEQUENCE_START_SIGNAL: 1 ID Reference pack_putc(ref, f); 4 Int Signal pack_iputl(signal, f); /* --> Can we drop this to 2 bytes? (65536 signals) */ 4 Int Starting position pack_iputl(pos, f); 2 Int Volume pack_iputw(volume, f); 2 Int Pitch pack_iputw(pitch, f); SEQUENCE_SET_VOLUME: 1 ID Reference pack_putc(ref, f); 2 Int Volume pack_iputw(volume, f); SEQUENCE_SET_PITCH: 1 ID Reference pack_putc(ref, f); 2 Int Pitch pack_iputw(pitch, f); SEQUENCE_SET_PARAMETER: 1 ID Reference pack_putc(ref, f); 1 ID Parameter ID pack_putc(id, f); 4 Int Value pack_iputl(value, f); SEQUENCE_STOP_SIGNAL: 1 ID Reference pack_putc(ref, f); When you initiate a signal, you must choose a reference number. If you want to modify the signal's volume, pitch or parameters, or stop the signal later, you must use this reference number to do so. Need more than 256 reference numbers? Use two sequences, and get your brain seen to. If you initiate a new signal with the same reference number, the reference will belong to the new signal. The old signal becomes anonymous, and will either continue to play indefinitely or stop of its own accord. Even if the new signal stops, the old one remains anonymous. DUMB will safely ignore operations on reference numbers not used by any signal, or which were used by a signal which has now stopped. Of course all signals will stop if the sequence itself is stopped. To initiate a signal, you must index the signal. The index is 0-based, so to initiate the fifth signal in the file you must specify 4. Out-of-range values will be handled safely, as will the case where a signal tries to generate itself directly or indirectly from its own samples (a recursive cycle). When you initiate a signal, you can specify a starting position. This will be passed directly to the appropriate signal's start_samples function, so for a SAMP (sample) signal it represents the sample on which to start, after any loops have been expanded (so you can start on the backwards-playing part of a ping-pong loop for example by careful choice of the starting position). Volume is probably the simplest parameter. It is on a linear scale ranging from 0 to 65535. Note that most music sounds more dramatic if the volume rises and falls exponentially or on a greater curve. Linear fades are more suitable for fading in and out, and do not sound dramatic in the least. Pitch is specified on what is perceived as a linear scale. It is in fact logarithmic, but you will not need to worry about this for most purposes. Pitch 0 represents that the sample will be played at 65536 Hz. (This is not strictly true, and will be explained further later.) In the likely case that your sample is not recorded at 65536 Hz, you will first need to calculate the central pitch. Use the following formula: pitch_centre = 12 * 256 * log(sampling_frequency / 65536.0) / log(2); If your programming language does not have a log function, look for ln, or any function that calculates the logarithm (to any base) of the number you give it. If you are lucky enough to find a logarithm to base 2, you can omit the final division since the divisor evaluates to 1. Once you have calculated pitch_centre, you can use it to play the sample at the frequency at which it was recorded. Each time you add or subtract 256, the sample will increase or decrease respectively in pitch by one semitone in the even temperament. (The even temperament was noted further up as being suitable for most musical applications.) One octave is represented by an interval of 12 * 256. If you wish to use another temperament, you can calculate the appropriate intervals in pitch as follows: pitch_interval = 12 * 256 * log(ratio) / log(2); where, for example, ratio = 1.5 for a perfect fifth. An octave is, of course, still represented by 12 * 256. The SEQUENCE_SET_PARAMETER command needs little explanation. Quite simply, the parameter ID and value you specify are passed on to the set_parameter function of the signal to which this reference belongs. Exactly what this does depends on the signal in question. Remember, a sequence is a signal in itself. Like all signals, it is subject to changes in pitch. Increasing the pitch of a sequence will also speed it up. This capability is used to allow DUH files to be rendered at different sampling frequencies, and it is also available for use by the musician. This means that samples are only played at 65536 Hz if the pitch of the sequence itself has not been adjusted.