Crystal Caves Sound format

From ModdingWiki
Jump to: navigation, search
Crystal Caves Sound format
Format typeSound
HardwarePC Speaker
Number of sounds12
Sampling rateUnknown
Channel count1
Bits per sampleUnknown
Compressed?No
Tags?None
Games

Format

Files in Crystal Caves sound format do not contain a header or sound names, and sounds are of a fixed length. (610 bytes.)

The sounds are stored in the files CCx-y.SND (or SAMx0yE.SND for Secret Agent); 12 sounds to a file.

The sound data itself consists of an array of two-byte values each giving a tone frequency in Hertz. (Larger value, higher pitch.) with a value of -1 ending the sound. (There is one terminator at the sound play end and another at the sound data end, just to be sure.) The actual sound data takes up the last 600 bytes of the sound, the first 10 being a number of attributes as follows:

Data type Name Description
UINT16LE priority Whether or not sound will be interrupted by another sound if said sound starts playing while the first is. Sounds can only be interrupted by sounds that have an equal or higher value of this.
UINT16LE unknown ! Unknown
UINT16LE vibrate A divider for the vibrate; this changes a sound's tone by periodicly switching off the PC speaker. (But at a more rapid rate than would be achieved by simply adding silences to the sound data.
UINT32LE unknown2 ! Unknown
SINT16LE[300] data Sound data (frequencies in Hertz)

Implementation

{Sound player for Crystal Caves and Secret Agent}
{Created by K1n9_Duk3 in January 2015, based on a partial disassembly}
 
program CCSASound;
 
uses Dos, Crt;
 
type
  TSound = record
    fData: array [1..300] of Word;
    fPriority: Word;
    fUnknown1: Word;
    fVibrate: Word;
    fUnknown2: Word;
    fUnknown3: Word;
  end;
  TSoundFile = record
    fSounds: array [1..12] of TSound;
  end;
  PSoundFile = ^TSoundFile;
  PByte = ^Byte;
 
var
  OldInt: Pointer;
 
  SoundsOn, SoundIsPlaying: Boolean;
  TickCount: Word;
  sndIndex, sndNumberInFile, sndFileNumber: Word;
  sndFiles: array [1..3] of PSoundFile;
 
procedure SoundService; interrupt;
begin
  asm cli end;
  if (SoundIsPlaying) then begin
    Sound(sndFiles[sndFileNumber]^.fSounds[sndNumberInFile].fData[sndIndex]);
    Inc(sndIndex);
    if (sndIndex mod sndFiles[sndFileNumber]^.fSounds[sndNumberInFile].fVibrate <> 0)
      then NoSound;
    if (sndFiles[sndFileNumber]^.fSounds[sndNumberInFile].fData[sndIndex] = $FFFF) then begin
      SoundIsPlaying := FALSE;
      sndIndex := 1;
      NoSound;
    end;
  end;
  Inc(TickCount);
  asm sti end;
end;
 
procedure PlaySound(soundNum:Word);
var oldFileNum, oldSndNumInFile:Word;
begin
  oldFileNum := sndFileNumber;
  oldSndNumInFile := sndNumberInFile;
  if (SoundsOn) then begin
    if (SoundIsPlaying) then begin
      sndFileNumber := 1 + (soundNum-1) div 12;
      sndNumberInFile := 1 + (soundNum-1) mod 12;
      if (
        sndFiles[sndFileNumber]^.fSounds[sndNumberInFile].fPriority
        >=
        sndFiles[oldFileNum]^.fSounds[oldSndNumInFile].fPriority
      ) then begin
        sndIndex := 1;
        SoundIsPlaying := TRUE;
      end else begin
        sndFileNumber := oldFileNum;
        sndNumberInFile := oldSndNumInFile;
      end;
    end else begin
      sndFileNumber := 1 + (soundNum-1) div 12;
      sndIndex := 1;
      SoundIsPlaying := TRUE;
      sndNumberInFile := 1 + (soundNum-1) mod 12;
    end;
  end;
end;
 
procedure WaitSoundDone;
begin
     while (SoundIsPlaying) do;
end;
 
procedure StartSoundService;
begin
  SoundIsPlaying := FALSE;
  GetIntVec($1c, OldInt);
  SetIntVec($1c, Addr(SoundService));
  asm
    push bx
    push ax
    mov bx, 2147h
    cli
    mov al, 36h
    out 43h, al
    mov al, 0
    mov al, bl
    out 40h, al
    mov al, bh
    out 40h, al
    sti
    pop ax
    pop bx
  end;
end;
 
procedure StopSoundService;
begin;
  SoundIsPlaying := FALSE;
  SetIntVec($1c, OldInt);
  asm
    push ax
    cli
    mov al, 36h
    out 43h, al
    mov al, 0
    out 40h, al
    out 40h, al
    sti
    pop ax
  end;
end;
 
 
var REV_TABLE: array[0..255] of byte;
 
procedure DecryptData(dataptr: PByte; size:Word);
const
  XOR_KEY  : array [0..27] of byte = ($43, $6F, $70, $79, $72, $69, $67, $68, $74, $20, $31, $39, $39, $31,
                                      $20, $50, $65, $64, $65, $72, $20, $4A, $75, $6E, $67, $63, $6B, $00);
var
  counter: byte;
  i,b:Byte;
begin
  if REV_TABLE[1] <> $80 then
  for i:= 0 to 255 do begin
    b := ((i and $0F) shl 4) or ((i and $F0) shr 4);
    b := ((b and $33) shl 2) or ((b and $CC) shr 2);
    b := ((b and $55) shl 1) or ((b and $AA) shr 1);
    REV_TABLE[i] := b
  end;
  counter := 0;
  while size > 0 do begin
    dataptr^ := REV_TABLE[dataptr^] xor XOR_KEY[counter];
    Inc(counter);
    if counter = 28 then counter := 0;
    Inc(dataptr);
    Dec(size);
  end
end;
 
procedure LoadSoundsSA;
var
  source: file of TSoundFile;
  name: string;
  i: byte;
begin
  name := 'SAM101E.SND';
  for i := 1 to 3 do begin
    sndFiles[i] := New(PSoundFile);
    Assign(source, name);
    Reset(source);
    Read(source, sndFiles[i]^);
    Close(source);
    Inc(name[6]);
    DecryptData(Addr(sndFiles[i]^), 7320);
  end;
end;
 
procedure LoadSoundsCC;
var
  source: file of TSoundFile;
  name: string;
  i: byte;
begin
  name := 'CC1-1.SND';
  for i := 1 to 3 do begin
    sndFiles[i] := New(PSoundFile);
    Assign(source, name);
    Reset(source);
    Read(source, sndFiles[i]^);
    Close(source);
    Inc(name[5]);
  end;
end;
 
var i:Byte;
begin
  LoadSoundsCC;
  StartSoundService;
  SoundsOn := TRUE;
 
  for i:= 1 to 36 do begin
    PlaySound(i);
    writeln('Now playing sound #', i);
    WaitSoundDone;
    Delay(500);
  end;
 
  StopSoundService;
end.