WSS File Format: Difference between revisions
m (corrected code) |
m (→File Format) |
||
Line 29: | Line 29: | ||
|ulong | |ulong | ||
|CompressionType | |CompressionType | ||
|0 == none. 8== | |0 == none. 4 == Nibble compression. 8 == Byte compression | ||
|- | |- | ||
|8 | |8 |
Revision as of 22:23, 21 February 2012
Introduction
WSS files are used since OFP times to store sound data and its file format is pretty much like the RIFF WAVE file format used for .wav files.
For the purposes of description here, 'Sound' in both wav or wss is recorded as PCM data in 16 bit 'samples'. The quality of that sound is determined by the frequency of those 'samples' per second, referred to as the SampleRate.
Given this: a few important paramaters are define here:
- BitsPerSample: 16
- BytesPerSample: 2 (Inferred BitsPerSample/8)
Bis wss files can be a challenge to decode because quite a few 'official' bis sound files contain corrupted, or just plain wrong, headers. These generally cause a rpt error, but the engine seems to heuristically decode them.
File Format
Offset | Datatype | Content | Description |
---|---|---|---|
0 | char[4] | "WSS0" | file signature |
4 | ulong | CompressionType | 0 == none. 4 == Nibble compression. 8 == Byte compression |
8 | ushort | format | Always 1 (WAVE_FORMAT_PCM) |
10 | ushort | nChannels | 1=mono, 2=stereo |
12 | ulong | SampleRate | e.g. 44100Hz |
16 | ulong | BytesPerSecond | SampleRate * BlockAlign |
20 | ushort | BlockAlign | nChannels * BytesPerSample |
22 | ushort | BitsPerSecond | usually 16 |
24 | ushort | Output Size | See Below |
26 | byte[fileSize-26] | <soundData> | here the PCM data of the sound is stored |
Input Size
The Input Size is the bytes remaining in the file. For uncompressed data it is (logically) always an even number (since it is a count of nShorts / 2). However many Bis wss Delta4 encoded files are corrupt. While they specify uncompressed in the header, they are in fact encoded, and the only way of detecting same is by them having an odd number of bytes to decode. These files cause a rpt error but the engine heuristically decodes them anyway.
Output Size
This 'value' was similar in architecture at least to the wav "datasize" chunk. It appears to be unreliable in intent. And, the intent appears to have changed.
Originally it was very similar to the OutputSize specified by a (compressed) pbo. Specifically,
- = 0 'uncompressed'. Remaining data is the size.
- = input size. uncompressed.
- non zero. The resultant size to extract (in bytes)
The size in fact need not be known because output size is always a defined multiple of the input size.
For Delta4 nibble compression, the value has changed to mean the Delta seed value. But this too is has been corrupted.
In General:
- Uncompressed data will have this set to zero.
- Compressed bytes will have this set to a multiple of the input size or zero.
- Compressed Nibbles are delta seed.
None of above is reliable
Note that early versions of wss formatted files did not appear to have this field at all (for uncompressed). There is no way of telling
Decompression
Bis optionally use Delta8 (byte) or Delta4 (nibble) 'Lossy' Compression for mono pcm data only.
The facility exists to handle stereo compression but it isn't employed.
Byte Compression
Compression consists of single encoded byte 'samples' versus uncompressed short 'samples'. Each byte is, effectively, extrapolated to a short, thus making the compressed BYTE array, and the resulting decompressed SHORT array the same number of elements(length). The length is the remaining file length (in bytes) after the header.
C++ code
Function returns a short array, same size as it's compressed byte equivalent
#define LOG10 2.3025850929940456840 //ln(10)
#define LOG2 1.4426950408889634070 //log2(e)
#define MAGIC_NUMBER ((LOG10*LOG2)/28.12574042515172)
short* DeCompress(const char *CompressedData,int len)
{
short *snap,*OutputData;
if (!(snap=OutputData=new short[len])) return 0;
short LastVal=0;
for (;len--;CompressedData++)
{
if (*CompressedData)
{
double asFloat = abs(*CompressedData) *MAGIC_NUMBER;
double rnd = Round(asFloat);
asFloat = pow(2.0, asFloat - rnd) * pow(2, rnd);// mantissa -
if (*CompressedData < 0) asFloat *= -1;
int asInt = Round(asFloat)+LastVal;
if (asInt > SHRT_MAX ) asInt = SHRT_MAX ;
if (asInt < SHRT_MIN) asInt = SHRT_MIN;
LastVal=(short)asInt;
}
*OutputData++ = LastVal;
}
return snap;
}
C# code
If the <soundData> is compressed the following (C#) code can be used for decompression:
PCMData = new Int16[soundData.Length];
for (int j = 0; j < PCMData.Length; j++)
{
SByte srcSample = (SByte)soundData[j];
if (srcSample != 0)
{
double asFloat = Math.Abs(srcSample) / 28.12574042515172;
asFloat *= 2.3025850929940456840; //ln(10)
asFloat *= 1.4426950408889634070; //log2(e)
double rnd = Math.Round(asFloat);
double mantisse = Math.Pow(2.0, asFloat - rnd);
asFloat = mantisse * Math.Pow(2, rnd);
if (srcSample < 0) asFloat *= -1;
Int32 asInt = (int)Math.Round(asFloat);
asInt = (j == 0) ? asInt : (asInt + PCMData[j - 1]);
if (asInt > short.MaxValue) asInt = short.MaxValue;
if (asInt < short.MinValue) asInt = short.MinValue;
PCMData[j] = (Int16)asInt;
}
else PCMData[j] = (j == 0) ? (Int16)0 : PCMData[j - 1];
}
Nibble Compression
Although by no means the same thing, Nibble compression is an adaptation of IMA ADPCM compression. Each nibble represents a SHORT of PCMdata. Thus the resultant output size will be four times larger (in bytes).
C++ Code
// // all honor and glory to T_D //
static short PCMIndex[] = {-8192, -4096, -2048, -1024, -512, -256, -64, 0, 64, 256, 512, 1024, 2048, 4096, 8192};
static short limits(int PCM) { if(PCM > SHRT_MAX) PCM = SHRT_MAX; else if(PCM < SHRT_MIN) PCM = SHRT_MIN; return PCM; }
short * DeCompressNibble(const char *NibbleData,int len)//len in bytes { short *snap,*pcmData; if (!(snap=pcmData=new short[2*len])) throw GENERR_NOMEM; int delta = 0; while (len--) { byte nibble=*NibbleData++; *pcmData++ = limits(delta += PCMIndex[nibble >> 4]); *pcmData++ = limits(delta += PCMIndex[nibble & 0x0F]); } return snap; }