FunCom ISS: Difference between revisions

From MultimediaWiki
Jump to navigation Jump to search
(link to samples)
(Not missing in FFmpeg anymore)
 
(3 intermediate revisions by one other user not shown)
Line 1: Line 1:
* Extensions: .ISS, .XARC
* Extensions: iss
* Company: Funcom Oslo A/S
* Company: FunCom Oslo A/S
* Samples: http://samples.mplayerhq.hu/game-formats/funcom-iss/
* Samples: http://samples.mplayerhq.hu/game-formats/funcom-iss/


ISS files are audio files with [[IMA ADPCM]] audio used in some games from a company named FunCom Oslo A/S, including [http://www.mobygames.com/game/windows/longest-journey The Longest Journey].


== Credit ==
== Credit ==
Line 8: Line 9:


== Byte Order ==
== Byte Order ==
All numbers are stored in little endian format.
All multi-byte numbers are stored in little endian format.
== ISS Audio Files ==


The music, sfx and speech in The Longest Journey are .ISS files, either
== ISS File Format ==
stand-alone or stored in XARC archives.
The music, sfx and speech in The Longest Journey are stored in .ISS files which can either occur as standalone files or stored in XARC archives. The ISS file has some kind of a textual header -- the header fields are just decimal string representations of corresponding field values, separated
The ISS file has some kind of a textual header -- the header fields are just
by spaces. To obtain the parameters of an ISS file, a program needs to parse this textual header while being mindful of issues related to parsing freeform text (like buffer overflows).
decimal string representations of correspondent fields values, separated
by spaces. So, to get the parameters of an ISS file, you should parse its
textual header, using the function like this one:


// read the file (f) into string (szValue) until the space character is
The following fields should be present in the textual header
// encountered (taking into account the string size (dwSize) and EOF)
void ReadUntilSpace(FILE* f, char *szValue, DWORD dwSize)
{
  char ch;
  DWORD i=0;
  while ((!feof(f)) && (i<dwSize-1))
  {
    fread(&ch,1,1,f);
    if (ch==' ') // space character (0x20)
      break;
    szValue[i++]=ch;
  }
  szValue[i]=0;
}


Of course, it's not the most optimal means to parse ISS header. Anyway,
* ''ID'' -- ID string, which is "IMA_ADPCM_Sound".
once you've got the way to obtain the textual fields of ISS header, you'll
* ''BlockSize'' -- the block size for the audio stream in the file (see below). The music (stereo) files usually set this value to 2048; the speech/sfx (mono) files usually set this value to 512. These values may vary, though.
see the following structure of that header (all numbers are decimal!):
* ''FileID'' -- the string ID of the given file. It seems to be the internal name for the file (which usually matches the file title for standalone files).
''szID'' ''dwBlockSize'' ''szFileID'' ''dwOutSize'' ''dwStereo'' ''dwUnknown1''
* ''OutSize'' -- total number of audio samples in the file. May be used for song length (in seconds) calculation. ''(audio samples or audio frames? --[[User:Multimedia Mike|Multimedia Mike]] 20:53, 2 April 2008 (EDT))''
''dwRateDevider'' ''dwUnknown2'' ''szVersion'' ''dwSize''
* ''Stereo'' -- this seems to be boolean stereo flag: if this is non-zero, the audio stream in the file is stereo (music), otherwise it's mono (sfx, speech).
* ''Unknown1'' -- the unknown field. All ISS files in The Longest Journey have this field set to 1.
* ''RateDivisor'' -- the value which determines sample rate for the file: the sample rate is equal to (44100 / RateDivisor). E.g., a RateDivisor of 2 indicates that the audio data should be played back at 22050 Hz.
* ''Unknown2'' -- the unknown field. All ISS files in The Longest Journey have this value set to 0.
* ''Version'' -- version ID string. All files in The Longest Journey have this set to "1.000". Note that this field may, in principle, vary.
* ''Size'' -- the size of the audio stream in the file. The total filesize is the sum of the header size plus this value.


''szID'' -- ID string, which is "IMA_ADPCM_Sound".
== ISS IMA ADPCM ==


''dwBlockSize'' -- the block size for the audio stream in the file (see below).
After the header, the ISS file contains [[IMA ADPCM]]-compressed sound data. The stream is divided into the blocks of ''BlockSize'' bytes. Each block in a mono stream begins with a header that contains the initial values for IMA ADPCM decompression:
The music (stereo) files has this value set to 2048 and the speech/sfx (mono)
files has this set to 512. Though, those values may vary.


''szFileID'' -- the string ID of the given file. It seems to be the internal
  UI16:  initial sample
name for the file (which coincides with the file title for stand-alone files).
  UI16:  initial index


''dwOutSize'' -- number of samples in the file. May be used for song length
In a stereo stream, each chunk begins with such headers, one for the left channel and one for the right:
(in seconds) calculation.


''dwStereo'' -- this seems to be boolean stereo flag: if this is not zero, the
  UI16: left channel initial sample
audio stream in the file is stereo (music), otherwise it's mono (sfx, speech).
  UI16:  left channel initial index
  UI16:  right channel initial sample
  UI16:  right channel initial index


''dwUnknown1'' -- the unknown field. All ISS files in The Longest Journey have
Use this initial state to set up the IMA ADPCM decoder. Then, march along the bytes in the block and decode the audio. If the audio is mono data, the low nibble is decoded first (bits 3-0) then the high nibble:
this field set to 1.


''dwRateDevider'' -- the value which determines sample rate for the file: the
    byte0 byte1 byte2 byte3 ...
sample rate is equal to (44100/dwRateDevider).
    n1n0 n3n2 n5n4 n7n6 ...  
 
''dwUnknown2'' -- the unknown field. All ISS files in The Longest Journey have
this value set to 0.
 
''szVersion'' -- version ID string, all files in The Longest Journey have this
set to "1.000". Note that this field may, in principle, vary.
 
''dwSize'' -- the size of the audio stream in the file. The total filesize is
the sum of the header size and this value.
 
The resolution is NOT specified in the header, so the default value (16-bit)
should be used.
 
After the ISSHeader IMA ADPCM compressed sound data comes. The stream is
devided into the blocks each being (''dwBlockSize'') long. Each block begins
with the small header, containing initial values for IMA ADPCM decompression:
 
struct ISSBlockHeader
{
  SHORT iSample;
  SHORT iIndex;
};
 
For mono audio stream there's only one such header, while for stereo stream
there're two consecutive headers -- for left and right channels. Right after
the block header (or two of them for stereo stream) comes IMA ADPCM compressed
audio data. You may find IMA ADPCM decompression scheme description further
in this document.
 
== IMA ADPCM Decompression Algorithm ==
 
During the decompression four LONG variables must be maintained for stereo
stream: ''lIndexLeft, lIndexRight, lCurSampleLeft, lCurSampleRight'' and two --
for mono stream: ''lIndex, lCurSample''. At the beginning of each block you must
initialize ''lCurSampleLeft/Right'' and ''lIndexLeft/Right'' variables to the values
from ISSBlockHeader.
Note that both LONG and SHORT here are signed (so initialize LONG variables
by the SHORT initial values with sign extention).
 
Here's the code which decompresses one byte of IMA ADPCM compressed
stereo stream. Other bytes are processed in the same way.
 
BYTE Input; // current byte of compressed data
BYTE Code;
LONG Delta;
Code=HINIBBLE(Input); // get HIGHER 4-bit nibble
Delta=StepTable[lIndexLeft]>>3;
if (Code & 4)
  Delta+=StepTable[lIndexLeft];
if (Code & 2)
  Delta+=StepTable[lIndexLeft]>>1;
if (Code & 1)
  Delta+=StepTable[lIndexLeft]>>2;
if (Code & 8) // sign bit
  lCurSampleLeft-=Delta;
else
  lCurSampleLeft+=Delta;
// clip sample
if (lCurSampleLeft>32767)
  lCurSampleLeft=32767;
else if (lCurSampleLeft<-32768)
  lCurSampleLeft=-32768;
lIndexLeft+=IndexAdjust[Code]; // adjust index
// clip index
if (lIndexLeft<0)
  lIndexLeft=0;
else if (lIndexLeft>88)
  lIndexLeft=88;
Code=LONIBBLE(Input); // get LOWER 4-bit nibble
Delta=StepTable[lIndexRight]>>3;
if (Code & 4)
  Delta+=StepTable[lIndexRight];
if (Code & 2)
  Delta+=StepTable[lIndexRight]>>1;
if (Code & 1)
  Delta+=StepTable[lIndexRight]>>2;
if (Code & 8) // sign bit
  lCurSampleRight-=Delta;
else
  lCurSampleRight+=Delta;
// clip sample
if (lCurSampleRight>32767)
  lCurSampleRight=32767;
else if (lCurSampleRight<-32768)
  lCurSampleRight=-32768;
lIndexRight+=IndexAdjust[Code]; // adjust index
// clip index
if (lIndexRight<0)
  lIndexRight=0;
else if (lIndexRight>88)
  lIndexRight=88;
// Now we've got lCurSampleLeft and lCurSampleRight which form one stereo
// sample and all is set for the next input byte...
Output((SHORT)lCurSampleLeft,(SHORT)lCurSampleRight); // send the sample to output
 
HINIBBLE and LONIBBLE are higher and lower 4-bit nibbles:
#define HINIBBLE(byte) ((byte) >> 4)
#define LONIBBLE(byte) ((byte) & 0x0F)
Note that depending on your compiler you may need to use additional nibble
separation in these defines, e.g. ''(((byte) >> 4) & 0x0F)''.
 
StepTable and IndexAdjust are the tables given in the next section of
this document.
 
Output() is just a placeholder for any action you would like to perform for
decompressed sample value.
 
Of course, this decompression routine may be greatly optimized.
 
As to mono sound, it's just analoguous:
 
Code=LONIBBLE(Input); // get LOWER 4-bit nibble
 
Delta=StepTable[lIndex]>>3;
if (Code & 4)
  Delta+=StepTable[lIndex];
if (Code & 2)
  Delta+=StepTable[lIndex]>>1;
if (Code & 1)
  Delta+=StepTable[lIndex]>>2;
if (Code & 8) // sign bit
  lCurSample-=Delta;
else
  lCurSample+=Delta;
// clip sample
if (lCurSample>32767)
  lCurSample=32767;
else if (lCurSample<-32768)
  lCurSample=-32768;
lIndex+=IndexAdjust[Code]; // adjust index
// clip index
if (lIndex<0)
  lIndex=0;
else if (lIndex>88)
  lIndex=88;
   
  Output((SHORT)lCurSample); // send the sample to output
 
  Code=HINIBBLE(Input); // get HIGHER 4-bit nibble
// ...just the same as above for higher nibble
Note that LOWER nibble is processed first for mono sound while for stereo
sound HIGHER nibble corresponds to LEFT channel.
 
== IMA ADPCM Tables ==
 
LONG IndexAdjust[]=
{
    -1,
    -1,
    -1,
    -1,
    2,
    4,
    6,
    8,
    -1,
    -1,
    -1,
    -1,
    2,
    4,
    6,
    8
};
LONG StepTable[]=
{
    7,   8,   9, 10, 11,    12,    13,    14,    16,
    17,    19,   21, 23, 25,    28,    31,    34,    37,
    41,    45,   50, 55, 60,    66,    73,    80,    88,
    97,    107,  118, 130, 143,  157,    173,  190,  209,
    230,  253,  279, 307, 337,  371,    408,  449,  494,
    544,  598,  658, 724, 796,  876,    963,  1060,  1166,
    1282,  1411,  1552,  1707, 1878,  2066,  2272,  2499,  2749,
    3024,  3327,  3660,  4026, 4428,  4871,  5358,  5894,  6484,
    7132,  7845,  8630,  9493, 10442, 11487,  12635, 13899, 15289,
    16818, 18500, 20350, 22385, 24623, 27086,  29794, 32767
};


If the audio is stereo data, the high nibble corresponds to the left channel while the low nibble corresponds to the right channel:


    byte0 byte1 byte2 byte3 ...
    L0 R0 L1 R1 L2 R2 L3 R3 ...


== ISS Audio Files in XARC Archives ==
== ISS Audio Files in XARC Archives ==


When stored in .XARC resources, ISS audio files are stored "as is", without
When stored in .XARC resources, ISS audio files are stored "as is", without compression or encryption. That means if you want to play/extract an ISS file from the XARC resource you just need to search for ''ID'' id-string ("IMA_ADPCM_Sound") and read the ISS header starting at the beginning position of the ID string. This will give you starting point of the file and the size
compression or encryption. That means if you want to play/extract ISS
of the file will be the sum of header size (its size may vary) and ''dwSize''.
file from the XARC resource you just need to search for (''szID'') id-string
("IMA_ADPCM_Sound") and read ISS header starting at the beginning position of
found id-string. This will give you starting point of the file and the size
of the file will be the sum of header size (its size may vary!) and (''dwSize'').
 
==PC Games Using FunCom ISS==
[http://www.mobygames.com/game/windows/longest-journey The Longest Journey]


[[Category:Audio Codecs]]
[[Category:Audio Codecs]]
[[Category:ADPCM Audio Codecs]]
[[Category:ADPCM Audio Codecs]]
[[Category:IMA ADPCM Audio Codecs]]
[[Category:Game Formats]]
[[Category:Game Formats]]
[[Category:Formats missing in FFmpeg]]

Latest revision as of 03:06, 21 January 2009

ISS files are audio files with IMA ADPCM audio used in some games from a company named FunCom Oslo A/S, including The Longest Journey.

Credit

The information on this page was originally based on one of the many format documents written up by Valery V. Anisimovsky, available on http://wotsit.org/ and many other sites across the internet.

Byte Order

All multi-byte numbers are stored in little endian format.

ISS File Format

The music, sfx and speech in The Longest Journey are stored in .ISS files which can either occur as standalone files or stored in XARC archives. The ISS file has some kind of a textual header -- the header fields are just decimal string representations of corresponding field values, separated by spaces. To obtain the parameters of an ISS file, a program needs to parse this textual header while being mindful of issues related to parsing freeform text (like buffer overflows).

The following fields should be present in the textual header

  • ID -- ID string, which is "IMA_ADPCM_Sound".
  • BlockSize -- the block size for the audio stream in the file (see below). The music (stereo) files usually set this value to 2048; the speech/sfx (mono) files usually set this value to 512. These values may vary, though.
  • FileID -- the string ID of the given file. It seems to be the internal name for the file (which usually matches the file title for standalone files).
  • OutSize -- total number of audio samples in the file. May be used for song length (in seconds) calculation. (audio samples or audio frames? --Multimedia Mike 20:53, 2 April 2008 (EDT))
  • Stereo -- this seems to be boolean stereo flag: if this is non-zero, the audio stream in the file is stereo (music), otherwise it's mono (sfx, speech).
  • Unknown1 -- the unknown field. All ISS files in The Longest Journey have this field set to 1.
  • RateDivisor -- the value which determines sample rate for the file: the sample rate is equal to (44100 / RateDivisor). E.g., a RateDivisor of 2 indicates that the audio data should be played back at 22050 Hz.
  • Unknown2 -- the unknown field. All ISS files in The Longest Journey have this value set to 0.
  • Version -- version ID string. All files in The Longest Journey have this set to "1.000". Note that this field may, in principle, vary.
  • Size -- the size of the audio stream in the file. The total filesize is the sum of the header size plus this value.

ISS IMA ADPCM

After the header, the ISS file contains IMA ADPCM-compressed sound data. The stream is divided into the blocks of BlockSize bytes. Each block in a mono stream begins with a header that contains the initial values for IMA ADPCM decompression:

 UI16:  initial sample
 UI16:  initial index

In a stereo stream, each chunk begins with such headers, one for the left channel and one for the right:

 UI16:  left channel initial sample
 UI16:  left channel initial index
 UI16:  right channel initial sample
 UI16:  right channel initial index

Use this initial state to set up the IMA ADPCM decoder. Then, march along the bytes in the block and decode the audio. If the audio is mono data, the low nibble is decoded first (bits 3-0) then the high nibble:

   byte0 byte1 byte2 byte3 ...
    n1n0  n3n2  n5n4  n7n6 ... 

If the audio is stereo data, the high nibble corresponds to the left channel while the low nibble corresponds to the right channel:

   byte0 byte1 byte2 byte3 ...
   L0 R0 L1 R1 L2 R2 L3 R3 ...

ISS Audio Files in XARC Archives

When stored in .XARC resources, ISS audio files are stored "as is", without compression or encryption. That means if you want to play/extract an ISS file from the XARC resource you just need to search for ID id-string ("IMA_ADPCM_Sound") and read the ISS header starting at the beginning position of the ID string. This will give you starting point of the file and the size of the file will be the sum of header size (its size may vary) and dwSize.