667 lines
27 KiB
Prolog
667 lines
27 KiB
Prolog
"8SVX" IFF 8-Bit Sampled Voice
|
||
Date: February 7, 1985
|
||
From: Steve Hayes and Jerry Morrison, Electronic Arts
|
||
Status: Adopted
|
||
|
||
1. Introduction
|
||
|
||
This memo is the IFF supplement for FORM "8SVX". An 8SVX is an IFF
|
||
"data section" or "FORM" (which can be an IFF file or a part of one)
|
||
containing a digitally sampled audio voice consisting of 8-bit samples.
|
||
A voice can be a one-shot sound orQwith repetition and pitch scalingQa
|
||
musical instrument. "EA IFF 85" is Electronic Arts' standard interchange
|
||
file format. [See "EA IFF 85" Standard for Interchange Format Files.]
|
||
|
||
The 8SVX format is designed for playback hardware that uses 8-bit
|
||
samples attenuated by a volume control for good overall signal-to-noise
|
||
ratio. So a FORM 8SVX stores 8-bit samples and a volume level.
|
||
|
||
A similar data format (or two) will be needed for higher resolution
|
||
samples (typically 12 or 16 bits). Properly converting a high resolution
|
||
sample down to 8 bits requires one pass over the data to find the
|
||
minimum and maximum values and a second pass to scale each sample
|
||
into the range -128 through 127. So it's reasonable to store higher
|
||
resolution data in a different FORM type and convert between them.
|
||
|
||
For instruments, FORM 8SVX can record a repeating waveform optionally
|
||
preceded by a startup transient waveform. These two recorded signals
|
||
can be pre-synthesized or sampled from an acoustic instrument. For
|
||
many instruments, this representation is compact. FORM 8SVX is less
|
||
practical for an instrument whose waveform changes from cycle to cycle
|
||
like a plucked string, where a long sample is needed for accurate
|
||
results.
|
||
|
||
FORM 8SVX can store an "envelope" or "amplitude contour" to enrichen
|
||
musical notes. A future voice FORM could also store amplitude, frequency,
|
||
and filter modulations.
|
||
|
||
FORM 8SVX is geared for relatively simple musical voices, where one
|
||
waveform per octave is sufficient, where the waveforms for the different
|
||
octaves follows a factor-of-two size rule, and where one envelope
|
||
is adequate for all octaves. You could store a more general voice
|
||
as a LIST containing one or more FORMs 8SVX per octave. A future voice
|
||
FORM could go beyond one "one-shot" waveform and one "repeat" waveform
|
||
per octave.
|
||
|
||
Section 2 defines the required property sound header "VHDR", optional
|
||
properties name "NAME", copyright "(c)J", and author "AUTH", the optional
|
||
annotation data chunk "ANNO", the required data chunk "BODY", and
|
||
optional envelope chunks "ATAK" and "RLSE". These are the "standard"
|
||
chunks. Specialized chunks for private or future needs can be added
|
||
later, e.g. to hold a frequency contour or Fourier series coefficients.
|
||
The 8SVX syntax is summarized in Appendix A as a regular expression
|
||
and in Appendix B as an example box diagram. Appendix C explains the
|
||
optional Fibonacci-delta compression algorithm.
|
||
|
||
Caution: The VHDR structure Voice8Header changed since draft proposal
|
||
#4! The new structure is incompatible with the draft version.
|
||
|
||
|
||
|
||
Reference:
|
||
|
||
"EA IFF 85" Standard for Interchange Format Files describes the underlying
|
||
conventions for all IFF files.
|
||
|
||
Amiga[tm] is a trademark of Commodore-Amiga, Inc.
|
||
|
||
Electronic Arts[tm] is a trademark of Electronic Arts.
|
||
|
||
MacWrite[tm] is a trademark of Apple Computer, Inc.
|
||
|
||
|
||
|
||
2. Standard Data and Property Chunks
|
||
|
||
FORM 8SVX stores all the waveform data in one body chunk "BODY". It
|
||
stores playback parameters in the required header chunk "VHDR". "VHDR"
|
||
and any optional property chunks "NAME", "(c)J", and "AUTH" must all
|
||
appear before the BODY chunk. Any of these properties may be shared
|
||
over a LIST of FORMs 8SVX by putting them in a PROP 8SVX. [See "EA
|
||
IFF 85" Standard for Interchange Format Files.]
|
||
|
||
Background
|
||
|
||
There are two ways to use FORM 8SVX: as a one-shot sampled sound or
|
||
as a sampled musical instrument that plays "notes". Storing both kinds
|
||
of sounds in the same kind of FORM makes it easy to play a one-shot
|
||
sound as a (staccato) instrument or an instrument as a (one-note)
|
||
sound.
|
||
|
||
A one-shot sound is a series of audio data samples with a nominal
|
||
playback rate and amplitude. The recipient program can optionally
|
||
adjust or modulate the amplitude and playback data rate.
|
||
|
||
For musical instruments, the idea is to store a sampled (or pre-synthesized)
|
||
waveform that will be parameterized by pitch, duration, and amplitude
|
||
to play each "note". The creator of the FORM 8SVX can supply a waveform
|
||
per octave over a range of octaves for this purpose. The intent is
|
||
to perform a pitch by selecting the closest octave's waveform and
|
||
scaling the playback data rate. An optional "one-shot" waveform supplies
|
||
an arbitrary startup transient, then a "repeat" waveform is iterated
|
||
as long as necessary to sustain the note.
|
||
|
||
A FORM 8SVX can also store an envelope to modulate the waveform. Envelopes
|
||
are mostly useful for variable-duration notes but could be used for
|
||
one-shot sounds, too.
|
||
|
||
The FORM 8SVX standard has some restrictions. For example, each octave
|
||
of data must be twice as long as the next higher octave. Most sound
|
||
driver software and hardware imposes additional restrictions. E.g.
|
||
the Amiga sound hardware requires an even number of samples in each
|
||
one-shot and repeat waveform.
|
||
|
||
Required Property VHDR
|
||
|
||
The required property "VHDR" holds a Voice8Header structure as defined
|
||
in these C declarations and following documentation. This structure
|
||
holds the playback parameters for the sampled waveforms in the BODY
|
||
chunk. (See "Data Chunk BODY", below, for the storage layout of these
|
||
waveforms.)
|
||
|
||
#define ID_8SVX MakeID('8', 'S', 'V', 'X')
|
||
#define ID_VHDR MakeID('V', 'H', 'D', 'R')
|
||
|
||
typedef LONG Fixed;
|
||
/* A fixed-point value, 16 bits to the left of the point and 16
|
||
* to the right. A Fixed is a number of 216ths, i.e. 65536ths. */
|
||
|
||
#define Unity 0x10000L /* Unity = Fixed 1.0 = maximum volume */
|
||
|
||
/* sCompression: Choice of compression algorithm applied to the samples. */
|
||
|
||
#define sCmpNone 0 /* not compressed */
|
||
#define sCmpFibDelta 1 /* Fibonacci-delta encoding (Appendix C) */
|
||
|
||
/* Can be more kinds in the future. */
|
||
|
||
typedef struct {
|
||
ULONG oneShotHiSamples, /* # samples in the high octave 1-shot part */
|
||
repeatHiSamples, /* # samples in the high octave repeat part */
|
||
samplesPerHiCycle; /* # samples/cycle in high octave, else 0 */
|
||
UWORD samplesPerSec; /* data sampling rate */
|
||
UBYTE ctOctave, /* # octaves of waveforms */
|
||
sCompression; /* data compression technique used */
|
||
Fixed volume; /* playback volume from 0 to Unity (full
|
||
* volume). Map this value into the output
|
||
* hardware's dynamic range. */
|
||
} Voice8Header;
|
||
|
||
[Implementation details. Fields are filed in the order shown. The
|
||
UBYTE fields are byte-packed (2 per 16-bit word). MakeID is a C macro
|
||
defined in the main IFF document and in the source file IFF.h.]
|
||
|
||
A FORM 8SVX holds waveform data for one or more octaves, each containing
|
||
a one-shot part and a repeat part. The fields oneShotHiSamples and
|
||
repeatHiSamples tell the number of audio samples in the two parts
|
||
of the highest frequency octave. Each successive (lower frequency)
|
||
octave contains twice as many data samples in both its one-shot and
|
||
repeat parts. One of these two parts can be empty across all octaves.
|
||
|
||
Note: Most audio output hardware and software has limitations. The
|
||
Amiga computer's sound hardware requires that all one-shot and repeat
|
||
parts have even numbers of samples. Amiga sound driver software would
|
||
have to adjust an odd-sized waveform, ignore an odd-sized lowest octave,
|
||
or ignore odd FORMs 8SVX altogether. Some other output devices require
|
||
all sample sizes to be powers of two.
|
||
|
||
The field samplesPerHiCycle tells the number of samples/cycle in the
|
||
highest frequency octave of data, or else 0 for "unknown". Each successive
|
||
(lower frequency) octave contains twice as many samples/cycle. The
|
||
samplesPerHiCycle value is needed to compute the data rate for a desired
|
||
playback pitch.
|
||
|
||
Actually, samplesPerHiCycle is an average number of samples/cycle.
|
||
If the one-shot part contains pitch bends, store the samples/cycle
|
||
of the repeat part in samplesPerHiCycle. The division repeatHiSamples/samplesPe
|
||
rHiCycle should yield an integer number of cycles. (When the repeat
|
||
waveform is repeated, a partial cycle would come out as a higher-frequency
|
||
cycle with a "click".)
|
||
|
||
More limitations: Some Amiga music drivers require samplesPerHiCycle
|
||
to be a power of two in order to play the FORM 8SVX as a musical instrument
|
||
in tune. They may even assume samplesPerHiCycle is a particular power
|
||
of two without checking. (If samplesPerHiCycle is different by a factor
|
||
of two, the instrument will just be played an octave too low or high.)
|
||
|
||
The field samplesPerSec gives the sound sampling rate. A program may
|
||
adjust this to achieve frequency shifts or vary it dynamically to
|
||
achieve pitch bends and vibrato. A program that plays a FORM 8SVX
|
||
as a musical instrument would ignore samplesPerSec and select a playback
|
||
rate for each musical pitch.
|
||
|
||
The field ctOctave tells how many octaves of data are stored in the
|
||
BODY chunk. See "Data Chunk BODY", below, for the layout of the octaves.
|
||
|
||
The field sCompression indicates the compression scheme, if any, that
|
||
was applied to the entire set of data samples stored in the BODY chunk.
|
||
This field should contain one of the values defined above. Of course,
|
||
the matching decompression algorithm must be applied to the BODY data
|
||
before the sound can be played. (The Fibonacci-delta encoding scheme
|
||
sCmpFibDelta is described in Appendix C.) Note that the whole series
|
||
of data samples is compressed as a unit.
|
||
|
||
The field volume gives an overall playback volume for the waveforms
|
||
(all octaves). It lets the 8-bit data samples use the full range -128
|
||
through 127 for good signal-to-noise ratio and be attenuated on playback
|
||
to the desired level. The playback program should multiply this value
|
||
by a "volume control" and perhaps by a playback envelope (see ATAK
|
||
and RLSE, below).
|
||
|
||
Recording a one-shot sound. To store a one-shot sound in a FORM 8SVX,
|
||
set oneShotHiSamples = number of samples, repeatHiSamples = 0 ,
|
||
samplesPerHiCycle = 0, samplesPerSec = sampling rate, and ctOctave = 1.
|
||
Scale the signal amplitude to the full sampling range -128 through 127. Set
|
||
volume so the sound will playback at the desired volume level. If
|
||
you set the samplesPerHiCycle field properly, the data can also be
|
||
used as a musical instrument.
|
||
|
||
Experiment with data compression. If the decompressed signal sounds
|
||
ok, store the compressed data in the BODY chunk and set sCompression
|
||
to the compression code number.
|
||
|
||
Recording a musical instrument. To store a musical instrument in a
|
||
FORM 8SVX, first record or synthesize as many octaves of data as you
|
||
want to make available for playback. Set ctOctaves to the count of
|
||
octaves. From the recorded data, excerpt an integral number of steady
|
||
state cycles for the repeat part and set repeatHiSamples and samplesPerHiCycle.
|
||
Either excerpt a startup transient waveform and set oneShotHiSamples,
|
||
or else set oneShotHiSamples to 0. Remember, the one-shot and repeat
|
||
parts of each octave must be twice as long as those of the next higher
|
||
octave. Scale the signal amplitude to the full sampling range and
|
||
set volume to adjust the instrument playback volume. If you set the
|
||
samplesPerSec field properly, the data can also be used as a one-shot
|
||
sound.
|
||
|
||
A distortion-introducing compressor like sCmpFibDelta is not recommended
|
||
for musical instruments, but you might try it anway.
|
||
|
||
Typically, creators of FORM 8SVX record an acoustic instrument at
|
||
just one frequency. Decimate (down- sample with filtering) to compute
|
||
higher octaves. Interpolate to compute lower octaves.
|
||
|
||
If you sample an acoustic instrument at different octaves, you may
|
||
find it hard to make the one-shot and repeat waveforms follow the
|
||
factor-of-two rule for octaves. To compensate, lengthen an octave's
|
||
one-shot part by appending replications of the repeating cycle or
|
||
prepending zeros. (This will have minimal impact on the sound's start
|
||
time.) You may be able to equalize the ratio one-shot-samples : repeat-samples
|
||
across all octaves.
|
||
|
||
Note that a "one-shot sound" may be played as a "musical instrument"
|
||
and vice versa. However, an instrument player depends on samplesPerHiCycle,
|
||
and a one-shot player depends on samplesPerSec.
|
||
|
||
Playing a one-shot sound. To play any FORM 8SVX data as a one-shot
|
||
sound, first select an octave if ctOctave > 1. (The lowest-frequency
|
||
octave has the greatest resolution.) Play the one-shot samples then
|
||
the repeat samples, scaled by volume, at a data rate of samplesPerSec.
|
||
Of course, you may adjust the playback rate and volume. You can play
|
||
out an envelope, too. (See ATAK and RLSE, below.)
|
||
|
||
Playing a musical note. To play a musical note using any FORM 8SVX,
|
||
first select the nearest octave of data from those available. Play
|
||
the one-shot waveform then cycle on the repeat waveform as long as
|
||
needed to sustain the note. Scale the signal by volume, perhaps also
|
||
by an envelope, and by a desired note volume. Select a playback data
|
||
rate s samples/second to achieve the desired frequency (in Hz):
|
||
frequency = sJ/JsamplesPerHiCycle
|
||
for the highest frequency octave.
|
||
|
||
The idea is to select an octave and one of 12 sampling rates (assuming
|
||
a 12-tone scale). If the FORM 8SVX doesn't have the right octave,
|
||
you can decimate or interpolate from the available data.
|
||
|
||
When it comes to musical instruments, FORM 8SVX is geared for a simple
|
||
sound driver. Such a driver uses a single table of 12 data rates to
|
||
reach all notes in all octaves. That's why 8SVX requires each octave
|
||
of data to have twice as many samples as the next higher octave. If
|
||
you restrict samplesPerHiCycle to a power of two, you can use a predetermined
|
||
table of data rates.
|
||
|
||
Optional Text Chunks NAME, (c), AUTH, ANNO
|
||
|
||
Several text chunks may be included in a FORM 8SVX to keep ancillary
|
||
information.
|
||
|
||
The optional property "NAME" names the voice, for instance "tubular
|
||
bells".
|
||
|
||
The optional property "(c)J" holds a copyright notice for the voice.
|
||
The chunk ID "(c)J" serves as the copyright characters ")J". E.g.
|
||
a "(c)J" chunk containing "1986 Electronic Arts" means ") 1986 Electronic
|
||
Arts".
|
||
|
||
The optional property "AUTH" holds the name of the instrument's "author"
|
||
or "creator".
|
||
|
||
The chunk types "NAME", "(c) ", and "AUTH" are property chunks. Putting
|
||
more than one NAME (or other) property in a FORM is redundant. Just
|
||
the last NAME counts. A property should be shorter than 256 characters.
|
||
Properties can appear in a PROP 8SVX to share them over a LIST of
|
||
FORMs 8SVX.
|
||
|
||
The optional data chunk "ANNO" holds any text annotations typed in
|
||
by the author.
|
||
|
||
An ANNO chunk is not a property chunk, so you can put more than one
|
||
in a FORM 8SVX. You can make ANNO chunks any length up to 231 - 1
|
||
characters, but 32767 is a practical limit. Since they're not properties,
|
||
ANNO chunks don't belong in a PROP 8SVX. That means they can't be
|
||
shared over a LIST of FORMs 8SVX.
|
||
|
||
Syntactically, each of these chunks contains an array of 8-bit ASCII
|
||
characters in the range " " (SP, hex 20) through "~" (tilde, hex 7F),
|
||
just like a standard "TEXT" chunk. [See "Strings, String Chunks, and
|
||
String Properties" in "EA IFF 85" Electronic Arts Interchange File
|
||
Format.] The chunk's ckSize field holds the count of characters.
|
||
|
||
#define ID_NAME MakeID('N', 'A', 'M', 'E')
|
||
/* NAME chunk contains a CHAR[], the voice's name. */
|
||
|
||
#define ID_Copyright MakeID('(', 'c', ')', ' ')
|
||
/* "(c) " chunk contains a CHAR[], the FORM's copyright notice. */
|
||
|
||
#define ID_AUTH MakeID('A', 'U', 'T', 'H')
|
||
/* AUTH chunk contains a CHAR[], the author's name. */
|
||
|
||
#define ID_ANNO MakeID('A', 'N', 'N', 'O')
|
||
/* ANNO chunk contains a CHAR[], author's text annotations. */
|
||
|
||
Remember to store a 0 pad byte after any odd-length chunk.
|
||
|
||
Optional Data Chunks ATAK and RLSE
|
||
|
||
The optional data chunks ATAK and RLSE together give a piecewise-linear
|
||
"envelope" or "amplitude contour". This contour may be used to modulate
|
||
the sound during playback. It's especially useful for playing musical
|
||
notes of variable durations. Playback programs may ignore the supplied
|
||
envelope or substitute another.
|
||
|
||
#define ID_ATAK MakeID('A', 'T', 'A', 'K')
|
||
#define ID_RLSE MakeID('R', 'L', 'S', 'E')
|
||
|
||
typedef struct {
|
||
UWORD duration; /* segment duration in milliseconds, > 0 */
|
||
Fixed dest; /* destination volume factor */
|
||
} EGPoint;
|
||
|
||
/* ATAK and RLSE chunks contain an EGPoint[], piecewise-linear envelope.*/
|
||
/* The envelope defines a function of time returning Fixed values. It's
|
||
* used to scale the nominal volume specified in the Voice8Header. */
|
||
|
||
To explain the meaning of the ATAK and RLSE chunks, we'll overview
|
||
the envelope generation algorithm. Start at 0 volume, step through
|
||
the ATAK contour, then hold at the sustain level (the last ATAK EGPoint's
|
||
dest), and then step through the RLSE contour. Begin the release at
|
||
the desired note stop time minus the total duration of the release
|
||
contour (the sum of the RLSE EGPoints' durations). The attack contour
|
||
should be cut short if the note is shorter than the release contour.
|
||
|
||
The envelope is a piecewise-linear function. The envelope generator
|
||
interpolates between the EGPoints.
|
||
|
||
Remember to multiply the envelope function by the nominal voice header
|
||
volume and by any desired note volume.
|
||
|
||
Figure 1 shows an example envelope. The attack period is described
|
||
by 4 EGPoints in an ATAK chunk. The release period is described by
|
||
4 EGPoints in a RLSE chunk. The sustain period in the middle just
|
||
holds the final ATAK level until it's time for the release.
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
Figure 1. Amplitude contour.
|
||
|
||
Note: The number of EGPoints in an ATAK or RLSE chunk is its ckSize
|
||
/ sizeof(EGPoint). In RAM, the playback program may terminate the
|
||
array with a 0 duration EGPoint.
|
||
|
||
Issue: Synthesizers also provide frequency contour (pitch bend), filtering
|
||
contour (wah-wah), amplitude oscillation (tremolo), frequency oscillation
|
||
(vibrato), and filtering oscillation (leslie). In the future, we may
|
||
define optional chunks to encode these modulations. The contours can
|
||
be encoded in linear segments. The oscillations can be stored as segments
|
||
with rate and depth parameters.
|
||
|
||
Data Chunk BODY
|
||
|
||
The BODY chunk contains the audio data samples.
|
||
|
||
#define ID_BODY MakeID('B', 'O', 'D', 'Y')
|
||
|
||
typedef character BYTE; /* 8 bit signed number, -128 through 127. */
|
||
|
||
/* BODY chunk contains a BYTE[], array of audio data samples. */
|
||
|
||
The BODY contains data samples grouped by octave. Within each octave
|
||
are one-shot and repeat portions. Figure 2 depicts this arrangement
|
||
of samples for an 8SVX where oneShotHiSamples = 24, repeatHiSamples
|
||
= 16, samplesPerHiCycle = 8, and ctOctave = 3. The major divisions
|
||
are octaves, the intermediate divisions separate the one-shot and
|
||
repeat portions, and the minor divisions are cycles.
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
Figure 2. BODY subdivisions.
|
||
|
||
In general, the BODY has ctOctave octaves of data. The highest frequency
|
||
octave comes first, comprising the fewest samples: oneShotHiSamples
|
||
+ repeatHiSamples. Each successive octave contains twice as many samples
|
||
as the next higher octave but the same number of cycles. The lowest
|
||
frequency octave comes last with the most samples: 2ctOctave-1 * (oneShotHiSamp
|
||
les + repeatHiSamples).
|
||
|
||
The number of samples in the BODY chunk is
|
||
|
||
0 (ctOctave-1)
|
||
(2 + ... + 2 * (oneShotHiSamples + repeatHiSamples)
|
||
|
||
Figure 3, below, looks closer at an example waveform within one octave
|
||
of a different BODY chunk. In this example, oneShotHiSamples / samplesPerHiCycl
|
||
e = 2 cycles and repeatHiSamples / samplesPerHiCycle = 1 cycle.
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
Figure 3. Example waveform.
|
||
|
||
To avoid playback "clicks", the one-shot part should begin with a
|
||
small sample value, and the one-shot part should flow smoothly into
|
||
the repeat part, and the end of the repeat part should flow smoothly
|
||
into the beginning of the repeat part.
|
||
|
||
If the VHDR field sCompression - sCmpNone, the BODY chunk is just
|
||
an array of data bytes to feed through the specified decompresser
|
||
function. All this stuff about sample sizes, octaves, and repeat parts
|
||
applies to the decompressed data.
|
||
|
||
Be sure to follow an odd-length BODY chunk with a 0 pad byte.
|
||
|
||
Other Chunks
|
||
|
||
Issue: In the future, we may define an optional chunk containing Fourier
|
||
series coefficients for a repeating waveform. An editor for this kind
|
||
of synthesized voice could modify the coefficients and regenerate
|
||
the waveform.
|
||
|
||
|
||
|
||
Appendix A. Quick Reference
|
||
|
||
Type Definitions
|
||
|
||
#define ID_8SVX MakeID('8', 'S', 'V', 'X')
|
||
#define ID_VHDR MakeID('V', 'H', 'D', 'R')
|
||
|
||
typedef LONG Fixed; /* A fixed-point value, 16 bits to the left
|
||
* of the point and 16 to the right. A Fixed
|
||
* is a number of 216ths, i.e. 65536ths. */
|
||
|
||
#define Unity 0x10000L /* Unity = Fixed 1.0 = maximum volume */
|
||
|
||
/* sCompression: Choice of compression algorithm applied to the samples */
|
||
|
||
#define sCmpNone 0 /* not compressed */
|
||
#define sCmpFibDelta 1 /* Fibonacci-delta encoding (Appendix C) */
|
||
/* Can be more kinds in the future. */
|
||
|
||
typedef struct {
|
||
ULONG oneShotHiSamples, /* # samples in the high octave 1-shot part */
|
||
repeatHiSamples, /* # samples in the high octave repeat part */
|
||
samplesPerHiCycle; /* # samples/cycle in high octave, else 0 */
|
||
UWORD samplesPerSec; /* data sampling rate */
|
||
UBYTE ctOctave, /* # octaves of waveforms */
|
||
sCompression; /* data compression technique used */
|
||
Fixed volume; /* playback volume from 0 to Unity (full
|
||
* volume). Map this value into the output
|
||
* hardware's dynamic range. */
|
||
} Voice8Header;
|
||
|
||
#define ID_NAME MakeID('N', 'A', 'M', 'E')
|
||
/* NAME chunk contains a CHAR[], the voice's name. */
|
||
|
||
#define ID_Copyright MakeID('(', 'c', ')', ' ')
|
||
/* "(c) " chunk contains a CHAR[], the FORM's copyright notice. */
|
||
|
||
#define ID_AUTH MakeID('A', 'U', 'T', 'H')
|
||
/* AUTH chunk contains a CHAR[], the author's name. */
|
||
|
||
#define ID_ANNO MakeID('A', 'N', 'N', 'O')
|
||
/* ANNO chunk contains a CHAR[], author's text annotations. */
|
||
|
||
#define ID_ATAK MakeID('A', 'T', 'A', 'K')
|
||
#define ID_RLSE MakeID('R', 'L', 'S', 'E')
|
||
|
||
typedef struct {
|
||
UWORD duration; /* segment duration in milliseconds, > 0 */
|
||
Fixed dest; /* destination volume factor */
|
||
} EGPoint;
|
||
|
||
/* ATAK and RLSE chunks contain an EGPoint[], piecewise-linear envelope. */
|
||
/* The envelope defines a function of time returning Fixed values. It's
|
||
* used to scale the nominal volume specified in the Voice8Header. */
|
||
|
||
#define ID_BODY MakeID('B', 'O', 'D', 'Y')
|
||
|
||
typedef character BYTE; /* 8 bit signed number, -128 through 127. */
|
||
|
||
/* BODY chunk contains a BYTE[], array of audio data samples. */
|
||
|
||
8SVX Regular Expression
|
||
|
||
Here's a regular expression summary of the FORM 8SVX syntax. This
|
||
could be an IFF file or part of one.
|
||
|
||
8SVX ::= "FORM" #{ "8SVX" VHDR [NAME] [Copyright] [AUTH] ANNO*
|
||
[ATAK] [RLSE] BODY }
|
||
|
||
VHDR ::= "VHDR" #{ Voice8Header }
|
||
NAME ::= "NAME" #{ CHAR* } [0]
|
||
Copyright ::= "(c) " #{ CHAR* } [0]
|
||
AUTH ::= "AUTH" #{ CHAR* } [0]
|
||
ANNO ::= "ANNO" #{ CHAR* } [0]
|
||
|
||
ATAK ::= "ATAK" #{ EGPoint* }
|
||
RLSE ::= "RLSE" #{ EGPoint* }
|
||
BODY ::= "FORM" #{ BYTE* } [0]
|
||
|
||
The token "#" represents a ckSize LONG count of the following {braced}
|
||
data bytes. E.g. a VHDR's "#" should equal sizeof(Voice8Header). Literal
|
||
items are shown in "quotes", [square bracket items] are optional,
|
||
and "*" means 0 or more replications. A sometimes-needed pad byte
|
||
is shown as "[0]".
|
||
|
||
Actually, the order of chunks in a FORM 8SVX is not as strict as this
|
||
regular expression indicates. The property chunks VHDR, NAME, Copyright,
|
||
and AUTH may actually appear in any order as long as they all precede
|
||
the BODY chunk. The optional data chunks ANNO, ATAK, and RLSE don't
|
||
have to precede the BODY chunk. And of course, new kinds of chunks
|
||
may appear inside a FORM 8SVX in the future.
|
||
|
||
|
||
Appendix B. 8SVX Example
|
||
|
||
Here's a box diagram for a simple example containing the three octave
|
||
BODY shown earlier in Figure 2.
|
||
|
||
+-----------------------------------+
|
||
|'FORM' 362 |
|
||
+-----------------------------------+
|
||
|'8SVX' |
|
||
+-----------------------------------+
|
||
| +-----------------------------+ |
|
||
| |'VHDR' 20 | |
|
||
| |24,16,8,10000,3,0,1.0 | |
|
||
| +-----------------------------+ |
|
||
| +-----------------------------+ |
|
||
| |'NAME' 11 | |
|
||
| |'bass guitar' | |
|
||
| +-----------------------------+ |
|
||
| 0 |
|
||
| +-----------------------------+ |
|
||
| |'(c)' 20 | |
|
||
| |1985 Electronic Arts | |
|
||
| +-----------------------------+ |
|
||
| +-----------------------------+ |
|
||
| |'BODY' 280 | |
|
||
| |1, 2, 3, 4, ... | |
|
||
| +-----------------------------+ |
|
||
+-----------------------------------+
|
||
|
||
The "0" after the NAME chunk is a pad byte.
|
||
|
||
|
||
|
||
Appendix B. Standards Committee
|
||
|
||
The following people contributed to the design of this IFF standard:
|
||
|
||
Bob "Kodiak" Burns, Commodore-Amiga
|
||
R. J. Mical, Commodore-Amiga
|
||
Jerry Morrison, Electronic Arts
|
||
Greg Riker, Electronic Arts
|
||
Steve Shaw, Electronic Arts
|
||
Barry Walsh, Commodore-Amiga
|
||
|
||
|
||
The "0" after the NAME chunk is a pad byte.
|
||
|
||
|
||
|
||
Appendix C. Fibonacci Delta Compression
|
||
|
||
This is Steve Hayes' Fibonacci Delta sound compression technique.
|
||
It's like the traditional delta encoding but encodes each delta in
|
||
a mere 4 bits. The compressed data is half the size of the original
|
||
data plus a 2-byte overhead for the initial value. This much compression
|
||
introduces some distortion, so try it out and use it with discretion.
|
||
|
||
To achieve a reasonable slew rate, this algorithm looks up each stored
|
||
4-bit value in a table of Fibonacci numbers. So very small deltas
|
||
are encoded precisely while larger deltas are approximated. When it
|
||
has to make approximations, the compressor should adjust all the values
|
||
(forwards and backwards in time) for minimum overall distortion.
|
||
|
||
Here is the decompressor written in the C programming language.
|
||
|
||
/* Fibonacci delta encoding for sound data. */
|
||
|
||
BYTE codeToDelta[16] = {-34,-21,-13,-8,-5,-3,-2,-1,0,1,2,3,5,8,13,21};
|
||
|
||
/* Unpack Fibonacci-delta encoded data from n byte source buffer into 2*n byte
|
||
* dest buffer, given initial data value x. It returns the last data value x
|
||
* so you can call it several times to incrementally decompress the data. */
|
||
|
||
short D1Unpack(source, n, dest, x)
|
||
BYTE source[], dest[];
|
||
LONG n;
|
||
BYTE x;
|
||
{
|
||
BYTE d;
|
||
LONG i, lim;
|
||
|
||
lim = n <<<< 1;
|
||
for (i = 0; i << lim; ++i)
|
||
{
|
||
/* Decode a data nybble; high nybble then low nybble. */
|
||
d = source[i >> 1]; /* get a pair of nybbles */
|
||
if (i & 1) /* select low or high nybble? */
|
||
d &= 0xf; /* mask to get the low nybble */
|
||
else
|
||
d >>= 4; /* shift to get the high nybble */
|
||
x += codeToDelta[d]; /* add in the decoded delta */
|
||
dest[i] = x; /* store a 1-byte sample */
|
||
}
|
||
return(x);
|
||
}
|
||
|
||
/* Unpack Fibonacci-delta encoded data from n byte source buffer into 2*(n-2)
|
||
* byte dest buffer. Source buffer has a pad byte, an 8-bit initial value,
|
||
* followed by n-2 bytes comprising 2*(n-2) 4-bit encoded samples. */
|
||
|
||
void DUnpack(source, n, dest)
|
||
BYTE source[], dest[];
|
||
LONG n;
|
||
{
|
||
D1Unpack(source + 2, n - 2, dest, source[1]);
|
||
}
|