Crystal Caves Sound format
Jump to navigation
Jump to search
Crystal Caves Sound format
Format type | Sound |
---|---|
Hardware | PC Speaker |
Number of sounds | 12 |
Sampling rate | Unknown |
Channel count | 1 |
Bits per sample | Unknown |
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.