mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-24 11:20:54 +00:00
297 lines
14 KiB
Text
297 lines
14 KiB
Text
|
/* _______ ____ __ ___ ___
|
||
|
* \ _ \ \ / \ / \ \ / / ' ' '
|
||
|
* | | \ \ | | || | \/ | . .
|
||
|
* | | | | | | || ||\ /| |
|
||
|
* | | | | | | || || \/ | | ' ' '
|
||
|
* | | | | | | || || | | . .
|
||
|
* | |_/ / \ \__// || | |
|
||
|
* /_______/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.
|