gzdoom/dumb/docs/duhspecs.txt

297 lines
14 KiB
Text
Raw Permalink Normal View History

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