unit uSound;

interface

uses Classes, SysUtils, GR32, Dialogs, Graphics;

type TSound = Class
private
        idxStream, mulStream : TMemoryStream;
public
        Loaded : Boolean;
        constructor Create;
        destructor Free;
        procedure Load( idxPath, mulPath : String );
        procedure Save( idxPath, mulPath : String );
        function Exists( Id : Integer ) : Boolean;
        procedure SaveSound( Id : Integer; Path : String );
        procedure LoadSound( Id : Integer; Path : String );
        procedure Delete( Id : Integer );
        function GetName( Id : Integer ) : String;
end;

var     Sound : TSound;

implementation

uses uUtilities, StrLib, uGaugeForm;

type    TIndexRecord = packed record
                Offset: Integer;
                Length: Integer;
                Extra: Integer
        end;

        TSoundHeader = packed record
                Name : Array[0..15] of Char;
                Unk1,
                Unk2,
                Unk3,
                Unk4 : Integer;
         end;
        TWaveChunk = packed Record
                WaveStr : Array[0..7] of Char;
                FmtLength : Integer;
                Tag : Word;
                Channels : Word;
                Rate : Integer;    // Samples per second
                BPS : Integer;     // Average Bytes per second
                Align : Word;      // Block size of data
                Bits : Word;       // Number of bits per sample of mono data
        end;

        TDataChunk = packed Record
                DataStr : Array[0..3] of Char;
                DataLength : Integer;
        end;

        TRiffHeader = packed Record
                RiffStr : Array[0..3] of Char;
                Length : Integer;
        end;

constructor TSound.Create;
begin
        idxStream := TMemoryStream.Create;
        mulStream := TMemoryStream.Create;
        Loaded := false;
end;

destructor TSound.Free;
begin
        idxStream.Free;
        mulStream.Free;
end;

procedure TSound.Load( idxPath, mulPath : String );
begin
        idxStream.LoadFromFile( idxPath );
        mulStream.LoadFromFile( mulPath );
        Loaded := true;
end;

procedure TSound.Save( idxPath, mulPath : String );
begin
        idxStream.SaveToFile( idxPath );
        mulStream.SaveToFile( mulPath );
end;

function TSound.Exists( Id : Integer ) : Boolean;
var     IndexRec : TIndexRecord;
begin
        idxStream.Seek( Id*12, soFromBeginning );
        idxStream.Read( IndexRec, sizeof( TIndexRecord ) );

        if ( IndexRec.Offset <> -1 ) and ( IndexRec.Length > 0 ) then
                Result := true
        else
                Result := false;
end;

procedure TSound.SaveSound( Id : Integer; Path : String );
var     IndexRecord : TIndexRecord;
        SoundHeader : TSoundHeader;
        RiffHeader : TRiffHeader;
        WaveChunk : TWaveChunk;
        DataChunk : TDataChunk;
        SoundFile : TFileStream;
        DataSize : Integer;
begin
        if not Exists( Id ) then exit;

        if FileExists( Path ) then
                DeleteFile( Path );
        SoundFile := TFileStream.Create( Path, fmCreate );
        SoundFile.Seek( 0, soFromBeginning );

        idxStream.Seek( Id*12, soFromBeginning );
        idxStream.Read( IndexRecord, 12 );
        mulStream.Seek( IndexRecord.Offset, soFromBeginning );

        DataSize := IndexRecord.Length - sizeof( TSoundHeader );

        RiffHeader.RiffStr := 'RIFF';
        RiffHeader.Length := DataSize + sizeof( TWaveChunk ) + sizeof( DataChunk );

        WaveChunk.WaveStr := 'WAVEfmt ';
        WaveChunk.FmtLength := Sizeof( TWaveChunk ) -12;
        WaveChunk.Tag := 1;
        WaveChunk.Channels := 2;
        WaveChunk.Bits := 16;
        WaveChunk.Rate := 11025;
        WaveChunk.BPS := WaveChunk.Rate*4;
        WaveChunk.Align := 4;

        DataChunk.DataStr := 'data';
        DataChunk.DataLength := DataSize;

        mulStream.Read( SoundHeader, sizeof( TSoundHeader ) );
        SoundFile.Write( RiffHeader, sizeof( TRiffHeader ) );
        SoundFile.Write( WaveChunk, sizeof( TWaveChunk ) );
        SoundFile.Write( DataChunk, sizeof( TDataChunk ) );
        SoundFile.CopyFrom( mulStream, DataSize );
        SoundFile.Free;
end;

function TSound.GetName( Id : Integer) : String;
var     IndexRecord : TIndexRecord;
        SoundHeader : TSoundHeader;
begin
        idxStream.Seek( Id*12, soFromBeginning );
        idxStream.Read( IndexRecord, 12 );
        mulStream.Seek( IndexRecord.Offset, soFromBeginning );
        mulStream.Read( SoundHeader, sizeof( TSoundHeader ) );
        Result := SoundHeader.Name;
end;

procedure TSound.LoadSound( Id : Integer; Path : String );
var     IndexRecord : TIndexRecord;
        SoundHeader : TSoundHeader;
        RiffHeader : TRiffHeader;
        WaveChunk : TWaveChunk;
        DataChunk : TDataChunk;
        SoundFile : TFileStream;
        DataSize : Integer;
begin
        SoundFile := TFileStream.Create( Path, fmOpenRead );
        SoundFile.Seek( 0, soFromBeginning );

        mulStream.Seek( 0, soFromEnd );
        IndexRecord.Offset := mulStream.Position;
        IndexRecord.Extra := Id+1;

        SoundFile.Read( RiffHeader, sizeof( TRiffHeader ) );
        SoundFile.Read( WaveChunk, sizeof( TWaveChunk ) );
        SoundFile.Read( DataChunk, sizeof( TDataChunk ) );

        DataSize := SoundFile.Size - sizeof( TRiffHeader ) - sizeof( TWaveChunk ) - sizeof( TDataChunk );
        StrPCopy( SoundHeader.Name, inttostr( Id ) );
        SoundHeader.Unk1 := 0;
        SoundHeader.Unk2 := 0;
        SoundHeader.Unk3 := 0;
        SoundHeader.Unk4 := 0;

        mulStream.Write( SoundHeader, sizeof( TSoundHeader ) );
        mulStream.CopyFrom( SoundFile, DataSize );
        SoundFile.Free;

        IndexRecord.Length := mulStream.Position - IndexRecord.Offset;

        idxStream.Seek( Id*12, soFromBeginning );
        idxStream.Write( IndexRecord, 12 );
end;

procedure TSound.Delete( Id : Integer );
var     IndexRec : TIndexRecord;
begin
        IndexRec.Offset := -1;
        IndexRec.Length := -1;
        IndexRec.Extra := -1;

        idxStream.Seek( Id*12, soFromBeginning );
        idxStream.Write( IndexRec, sizeof( TIndexRecord ) );
end;

end.
