unit AudioWAV;

interface

uses KOL, ATLCommon;

type TWavAudioInfo = PAudioInfo;

function NewAudioWAV: TWavAudioInfo;

//Эти процедуры не тестировал, они из версии 1.43 (27 August 2004) by Gambit.  
procedure TrimFromBeginning(const AFileName: KOLstring; const ASamples: Cardinal);
procedure TrimFromEnd(const AFileName: KOLstring; const ASamples: Cardinal);
procedure FindSilence(const AFileName: KOLstring; var AmountTrimBegin, AmountTrimEnd: Cardinal);

implementation

uses ATLLib;

type
  { WAV file header data }
  TWAVRecord = record
    { RIFF file header }
    RIFFHeader: array [1..4] of Char;                        { Must be "RIFF" }
    FileSize: Integer;                           { Must be "RealFileSize - 8" }
    WAVEHeader: array [1..4] of Char;                        { Must be "WAVE" }
    { Format information }
    FormatHeader: array [1..4] of Char;                      { Must be "fmt " }
    FormatSize: Cardinal;                                        { Format size }
    FormatID: Word;                                        { Format type code }
    ChannelNumber: Word;                                 { Number of channels }
    SampleRate: Integer;                                   { Sample rate (hz) }
    BytesPerSecond: Cardinal;                                   { Bytes/second }
    BlockAlign: Word;                                       { Block alignment }
    BitsPerSample: Word;                                        { Bits/sample }
    DataHeader: array [1..4] of Char;                         { Can be "data" }
    Samples: Cardinal;                     { Number of samples (optional) }
  end;

const
  DATA_CHUNK = 'data';

  WAV_FORMAT_UNKNOWN = 'Unknown';
  WAV_FORMAT_PCM = 'Windows PCM';
  WAV_FORMAT_ADPCM = 'Microsoft ADPCM';
  WAV_FORMAT_ALAW = 'A-LAW';
  WAV_FORMAT_MULAW = 'MU-LAW';
  WAV_FORMAT_DVI_IMA_ADPCM = 'DVI/IMA ADPCM';
  WAV_FORMAT_MP3 = 'MPEG Layer III';

type
  TAudioWAV = object(TAudioInfo) end;

function AudioWAVReader(const AFileName: KOLstring; AudioInfo: TWavAudioInfo): Boolean; forward;
function ReadAudioWAV(const AFileName: KOLstring; AudioInfo: TWavAudioInfo; var HeaderSize, FormatSize: Cardinal; var BlockAlign: Word): Boolean; forward;

function NewAudioWAV: TWavAudioInfo;
begin
    New(Result, Create);
    with TAudioWAV(Result^) do begin
      FAudioVendor:= aWav;
      FExistsProps:= [apSampleRate, apBitrate, apChannels, apBits, apSamples, apDuration, apRatio];
      FReader:= AudioWAVReader;
    end;
end;

function AudioWAVReader(const AFileName: KOLstring; AudioInfo: TWavAudioInfo): Boolean;
var HeaderSize, FormatSize: Cardinal;
    BlockAlign: Word;
begin
    Result:= ReadAudioWAV(AFileName, AudioInfo, HeaderSize, FormatSize, BlockAlign);
end;

function ReadAudioWAV(const AFileName: KOLstring; AudioInfo: TWavAudioInfo; var HeaderSize, FormatSize: Cardinal; var BlockAlign: Word): Boolean;
var SourceFile: PStream;
    WAVData: TWAVRecord;
    S: String;
begin
    with TAudioWAV(AudioInfo^) do begin
      Result:= False;
      try
        ResetData;
        FillChar(WAVData, SizeOf(WAVData), 0);

        try
          SourceFile:= OpenFileRead(AFileName);

          SourceFile.Read(WAVData, 36);
          if SourceFile.Size > (WAVData.FormatSize + 24) then
          begin
            SourceFile.Seek(WAVData.FormatSize + 24, spBegin);
            SourceFile.Read(WAVData.Samples, 4);
          end;

          if (WAVData.RIFFHeader = 'RIFF') and (WAVData.WAVEHeader = 'WAVE') and (WAVData.FormatHeader = 'fmt ') then begin

            if WAVData.DataHeader = DATA_CHUNK then HeaderSize:= 44 else HeaderSize:= WAVData.FormatSize + 28;
            FFileSize:= WAVData.FileSize + 8;
            if HeaderSize > FFileSize then HeaderSize:= FFileSize;

            BlockAlign:= WAVData.BlockAlign;
            FormatSize:= WAVData.FormatSize;

            FChannels:= WAVData.ChannelNumber;
            FBitrate:= Round(8 * WAVData.BytesPerSecond  / 1000);
            FBits:= WAVData.BitsPerSample;
            FSamples:= WAVData.Samples div BlockAlign;
            FSampleRate:= WAVData.SampleRate;

            case WAVData.FormatID of
              1: S:= WAV_FORMAT_PCM;
              2: S:= WAV_FORMAT_ADPCM;
              6: S:= WAV_FORMAT_ALAW;
              7: S:= WAV_FORMAT_MULAW;
              17: S:= WAV_FORMAT_DVI_IMA_ADPCM;
              85: S:= WAV_FORMAT_MP3;
              else S:= '';
            end;
            {$IFDEF USE_OtherAudioProps}
            OtherProps.Add('Format='+S);
            {$ENDIF}

            if (FSamples = 0) and (WAVData.BytesPerSecond > 0) then FDuration:= (FFileSize - HeaderSize) / WAVData.BytesPerSecond;
            if (FSamples > 0) and (FSampleRate > 0) then FDuration:= FSamples / FSampleRate;

            if FSamples = 0 then FRatio:= FFileSize / ((FFileSize - HeaderSize) / WAVData.BytesPerSecond * FSampleRate * (WAVData.ChannelNumber * FBits / 8) + 44) * 100
            else FRatio:= FFileSize / (FSamples * (WAVData.ChannelNumber * FBits / 8) + 44) * 100;

            FValid:= FChannels <> 0;
            Result:= true;
          end;
        finally
          SourceFile.Free;
        end;
      except
      end;
    end;
end;

procedure TrimFromBeginning(const AFileName: KOLstring; const ASamples: Cardinal);
var SourceFile: PStream;
    NewData, NewSamples, EraseOldData, NewFormatSize : Cardinal;
    HeaderSize, FormatSize: Cardinal;
    BlockAlign: Word;
    AudioInfo: TWavAudioInfo;
begin
    try
      if AFileName = '' then Exit;
      try
        SourceFile:= OpenFileWrite(AFileName);
        AudioInfo:= NewAudioWAV;
        if not ReadAudioWAV(AFileName, AudioInfo, HeaderSize, FormatSize, BlockAlign) then Exit;


        SourceFile.Seek(16, spBegin);
        NewFormatSize:= (ASamples * BlockAlign) + FormatSize;
        SourceFile.Write(NewFormatSize, SizeOf(NewFormatSize));

        SourceFile.Seek(HeaderSize - 8, spBegin);
        EraseOldData:= 0;
        SourceFile.Write(EraseOldData, SizeOf(EraseOldData));

        SourceFile.Seek(HeaderSize + (ASamples * BlockAlign) - 8, spBegin);
        NewData:= 1635017060;   // 'data'
        SourceFile.Write(NewData, SizeOf(NewData));
        NewSamples:= (AudioInfo.Samples - ASamples) * BlockAlign;
        SourceFile.Write(NewSamples, SizeOf(NewSamples));

        FormatSize:= NewFormatSize;
        TAudioWAV(AudioInfo^).FSamples:= TAudioWAV(AudioInfo^).FSamples - ASamples;
        HeaderSize:= FormatSize + 28;
        TAudioWAV(AudioInfo^).FFileSize:= SourceFile.Size;
      finally
        SourceFile.Free;
        AudioInfo.Free;
      end;
    except
    end;
end;

procedure TrimFromEnd(const AFileName: KOLstring; const ASamples: Cardinal);
var SourceFile: PStream;
    NewSamples, NewSize : Cardinal;
    HeaderSize, FormatSize: Cardinal;
    BlockAlign: Word;
    AudioInfo: TWavAudioInfo;
begin
    try
      if AFileName = '' then Exit;
      try
        SourceFile:= OpenFileWrite(AFileName);
        AudioInfo:= NewAudioWAV;
        if not ReadAudioWAV(AFileName, AudioInfo, HeaderSize, FormatSize, BlockAlign) then Exit;

        SourceFile.Seek(4, spBegin);

        NewSamples:= (TAudioWAV(AudioInfo^).FSamples - ASamples) * BlockAlign;
        NewSize:= NewSamples + HeaderSize - 8;

        SourceFile.Write(NewSize, SizeOf(NewSize));

        SourceFile.Seek(HeaderSize - 4, spBegin);
        SourceFile.Write(NewSamples, SizeOf(NewSamples));

        SourceFile.Size:= NewSamples + HeaderSize;

        TAudioWAV(AudioInfo^).FSamples:= TAudioWAV(AudioInfo^).FSamples - ASamples;
        TAudioWAV(AudioInfo^).FFileSize:= SourceFile.Size;
      finally
        SourceFile.Free;
        AudioInfo.Free;
      end;
    except
    end;
end;

procedure FindSilence(const AFileName: KOLstring; var AmountTrimBegin, AmountTrimEnd: Cardinal);
var SourceFile: PStream;
    ReadSample : Integer;
    HeaderSize, FormatSize: Cardinal;
    BlockAlign: Word;
    AudioInfo: TWavAudioInfo;
begin
    try
      AmountTrimBegin:= 0; AmountTrimEnd:= 0;
      if AFileName = '' then Exit;
      try
        SourceFile:= OpenFileWrite(AFileName);
        AudioInfo:= NewAudioWAV;
        if not ReadAudioWAV(AFileName, AudioInfo, HeaderSize, FormatSize, BlockAlign) then Exit;

        ReadSample:= 0;
        SourceFile.Seek(HeaderSize, spBegin);
        // this assumes 16bit stereo
        repeat
          SourceFile.Read(ReadSample, SizeOf(ReadSample));
          if ReadSample = 0 then Inc(AmountTrimBegin);
        until (ReadSample <> 0) or (SourceFile.Position >= SourceFile.Size);

        ReadSample:= 0;
        repeat
          // this assumes 16bit stereo
          SourceFile.Seek(TAudioWAV(AudioInfo^).FFileSize - ((AmountTrimEnd + 1) * 4), spBegin);
          SourceFile.Read(ReadSample, SizeOf(ReadSample));
          if ReadSample = 0 then Inc(AmountTrimEnd);
        until ReadSample <> 0;
      finally
        SourceFile.Free;
        AudioInfo.Free;
      end;
    except
    end;
end;

end.
