summaryrefslogtreecommitdiff
path: root/dumb/dumb-kode54/docs/duhspecs.txt
blob: 43b3d39c15df25e279841e207b601b01c79f703a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/*  _______         ____    __         ___    ___
 * \    _  \       \    /  \  /       \   \  /   /       '   '  '
 *  |  | \  \       |  |    ||         |   \/   |         .      .
 *  |  |  |  |      |  |    ||         ||\  /|  |
 *  |  |  |  |      |  |    ||         || \/ |  |         '  '  '
 *  |  |  |  |      |  |    ||         ||    |  |         .      .
 *  |  |_/  /        \  \__//          ||    |  |
 * /_______/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.