unit AudioWAVPack;

interface

uses KOL, ATLCommon, TagAPE;

type TWAVPackAudioInfo = PAudioInfo;

function NewAudioWAVPack: TWAVPackAudioInfo;

implementation

uses ATLLib;

type
    TWavPack_Header3 = record
      ckID: array[0..3] of char;
      ckSize: longword;
      version: word;
      bits: word  ;
      flags: word;
      shift: word;
      total_samples: longword;
      crc: longword;
      crc2: longword;
      extension: array[0..3] of char;
      extra_bc: byte;
      extras: array[0..2] of char;
    end;
    TWavPack_Header4 = record
      ckID: array[0..3] of char;
      ckSize: longword;
      version: word;
      track_no: byte;
      index_no: byte;
      total_samples: longword;
      block_index: longword;
      block_samples: longword;
      flags: longword;
      crc: longword;
    end;

    TFmt_Chunk = record
      wformattag: word;
      wchannels: word;
      dwsamplespersec: longword;
      dwavgbytespersec: longword;
      wblockalign: word;
      wbitspersample: word;
    end;

    TRiff_Chunk = record
      id: array[0..3] of char;
      size: longword;
    end;

const

  //version 3 flags
  MONO_FLAG_v3	     = 1;	    // not stereo
  FAST_FLAG_v3	     = 2;	    // non-adaptive predictor and stereo mode
//  RAW_FLAG_v3	       = 4;	    // raw mode (no .wav header)
//  CALC_NOISE_v3	     = 8;	    // calc noise in lossy mode (no longer stored)
  HIGH_FLAG_v3	     = $10;	  // high quality mode (all modes)
//  BYTES_3_v3		     = $20;	  // files have 3-byte samples
//  OVER_20_v3		     = $40;	  // samples are over 20 bits
  WVC_FLAG_v3	       = $80;	  // create/use .wvc (no longer stored)
//  LOSSY_SHAPE_v3     = $100;  // noise shape (lossy mode only)
//  VERY_FAST_FLAG_v3	 = $200;  // double fast (no longer stored)
  NEW_HIGH_FLAG_v3	 = $400;  // new high quality mode (lossless only)
//  CANCEL_EXTREME_v3	 = $800;  // cancel EXTREME_DECORR
//  CROSS_DECORR_v3	   = $1000; // decorrelate chans (with EXTREME_DECORR flag)
//  NEW_DECORR_FLAG_v3 = $2000; // new high-mode decorrelator
//  JOINT_STEREO_v3	   = $4000; // joint stereo (lossy and high lossless)
  EXTREME_DECORR_v3	 = $8000; // extra decorrelation (+ enables other flags)

  sample_rates: array[0..14] of integer = (6000, 8000, 9600, 11025, 12000, 16000, 22050,
                                            24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000);
type
  TAudioWAVPack = object(TAudioInfo)
  end;

function AudioWAVPackReader(const AFileName: KOLstring; AudioInfo: TWAVPackAudioInfo): Boolean; forward;

function NewAudioWAVPack: TWAVPackAudioInfo;
begin
    New(Result, Create);
    with TAudioWAVPack(Result^) do begin
      TagAdd(tAPE, NewTagAPE);
      FAudioVendor:= aWAVPack;
      FExistsProps:= [apSampleRate, apBitrate, apChannels, apBits, apSamples, apDuration, apRatio];
      FReader:= AudioWAVPackReader;
    end;
end;

function AudioWAVPackReader(const AFileName: KOLstring; AudioInfo: TWAVPackAudioInfo): Boolean;
  function ReadVer3(f: PStream): boolean;
  var chunk: TRiff_Chunk;
      wavchunk: array[0..3] of Char;
      fmt: TFmt_Chunk;
      hasfmt: Boolean;
      fpos: Int64;
      wvh3: TWavPack_Header3;
      Encoder: String;
  begin
      with TAudioWAVPack(AudioInfo^) do begin
        Result:= false;
        hasfmt:= false;
        // read and evaluate header
        FillChar(chunk, sizeof(chunk), 0);
        if (f.Read(chunk, sizeof(chunk)) <> SizeOf(chunk)) or
           (f.Read(wavchunk, sizeof(wavchunk)) <> SizeOf(wavchunk)) or
           (wavchunk <> 'WAVE') then exit;
        // start looking for chunks
        FillChar(chunk, SizeOf(chunk), 0);
        while (f.Position < f.Size) do begin
          if (f.read(chunk, sizeof(chunk)) < sizeof(chunk)) or (chunk.size <= 0) then break;
          fpos:= f.Position;

          if chunk.id = 'fmt ' then begin // Format chunk found read it
            if (chunk.size >= sizeof(fmt)) and (f.Read(fmt, sizeof(fmt)) = sizeof(fmt)) then begin
              hasfmt:= true;
              result:= True;
              FValid:= true;
    //          FFormatTag:= fmt.wformattag;
              FChannels:= fmt.wchannels;
              FSampleRate:= fmt.dwsamplespersec;
              FBits:= fmt.wbitspersample;
              FBitrate:= 8 * Round(fmt.dwavgbytespersec / 1000);
            end
            else break;
          end
          else
            if (chunk.id = 'data') and hasfmt then begin
              FillChar(wvh3, SizeOf(wvh3) ,0);
              f.Read(wvh3, SizeOf(wvh3));
              if wvh3.ckID = 'wvpk' then begin // wavpack header found
                result:= true;
                FValid:= true;
                FChannels:= 2 - (wvh3.flags and 1);  // mono flag
                FSamples:= wvh3.total_samples;

                // Encoder guess
                if wvh3.bits > 0 then begin
                  if (wvh3.flags and NEW_HIGH_FLAG_v3) > 0 then begin
                    Encoder:= 'hybrid';
                    if (wvh3.flags and WVC_FLAG_v3) > 0 then Encoder:= Encoder + ' lossless'
                    else Encoder:= Encoder + ' lossy';
                    if (wvh3.flags and EXTREME_DECORR_v3) > 0 then Encoder:= Encoder + ' (high)';
                  end
                  else
                    if (wvh3.flags and (HIGH_FLAG_v3 or FAST_FLAG_v3)) = 0 then Encoder:= Int2Str(wvh3.bits + 3) + '-bit lossy'
                    else begin
                      Encoder:= Int2Str(wvh3.bits + 3) + '-bit lossy';
                      if (wvh3.flags and HIGH_FLAG_v3) > 0 then Encoder:= Encoder + ' high'
                      else Encoder:= Encoder + ' fast';
                    end;
                end
                else begin
                  if (wvh3.flags and HIGH_FLAG_v3) = 0 then Encoder:= 'lossless (fast mode)'
                  else
                    if (wvh3.flags and EXTREME_DECORR_v3) > 0 then Encoder:= 'lossless (high mode)'
                    else Encoder:= 'lossless';
                end;
                {$IFDEF USE_OtherAudioProps}
                OtherProps.Add('Version='+Int2Str(wvh3.version));
                OtherProps.Add('Encoder='+Encoder);
                {$ENDIF}

                if FSampleRate <= 0 then FSampleRate:= 44100;
                FDuration:= wvh3.total_samples / FSampleRate;
                if FDuration > 0 then FBitrate:= 8* Round((FFileSize  - TagInfo[tAPE].TagSize  - wvh3.ckSize )/(FDuration*1000));
              end;
              Break;
            end
            else Break;
          f.Seek(fpos + chunk.size, spBegin);
        end;
      end;
  end;
  function ReadVer4(f: PStream): boolean;
  var wvh4: TWavPack_Header4;
      EncBuf : array[1..4096] of Byte;
      SmpRt, tempo: Integer;
      encoderbyte: Byte;
      Encoder: String;
  begin
      with TAudioWAVPack(AudioInfo^) do begin
        Result:= false;
        FillChar(wvh4, SizeOf(TWavPack_Header4), 0);
        f.Read(wvh4, SizeOf(TWavPack_Header4));
        if wvh4.ckID = 'wvpk' then begin // wavpack header found
          Result:= true;
          FValid:= true;
          FChannels:= 2 - (wvh4.flags and 4);  // mono flag

          FBits:= ((wvh4.flags and 3) * 16);   // bytes stored flag
          FSamples:= wvh4.total_samples;
          SmpRt:= (wvh4.flags and ($1F shl 23)) shr 23;
          if (SmpRt > 14) or (SmpRt < 0) then FSampleRate:= 44100
          else FSampleRate:= sample_rates[SmpRt];

          FDuration:= FSamples / FSampleRate;
          FBitrate:= 8 * Round((FFileSize - TagInfo[tApe].TagSize - SizeOf(TWavPack_Header4)) / (FDuration) / 1000);

          if ((wvh4.flags and 8) = 8) then Encoder:= 'hybrid lossy'
          else Encoder:= 'lossless';
      {
          if ((wvh4.flags and $20) > 0) then // MODE_HIGH
          begin
            Encoder:= FEncoder + ' (high)';
          end
          else if ((wvh4.flags and $40) > 0) then // MODE_FAST
          begin
            Encoder:= FEncoder + ' (fast)';
          end;
      }
          FillChar(EncBuf, SizeOf(EncBuf), 0);
          f.Read(EncBuf, SizeOf(EncBuf));
          for tempo:= 1 to 4094 do begin
            if EncBuf[tempo] = $65 then
              if EncBuf[tempo + 1] = $02 then begin
                EncoderByte:= EncBuf[tempo + 2];
                if encoderbyte and $0F >= 8 then Encoder:= Encoder + ' (high)'
                else
                  if encoderbyte and $0F = 0 then Encoder:= Encoder + ' (normal)'
                  else
                    if encoderbyte and $0F = 2 then Encoder:= Encoder + ' (fast)'
                    else
                      if encoderbyte and $0F = 6 then Encoder:= Encoder + ' (very fast)';
                Break;
              end;
          end;
          {$IFDEF USE_OtherAudioProps}
          OtherProps.Add('Version='+Int2Str(wvh4.version shr 8));
          OtherProps.Add('Encoder='+Encoder);
          OtherProps.Add('BlockSamples='+Int2Str(wvh4.block_samples));
          {$ENDIF}
        end;
      end;
  end;
var f: PStream;
    marker: array[0..3] of char;
begin
    with TAudioWAVPack(AudioInfo^) do begin
      Result:= False;
      try
        ResetData;
        TagInfo[tAPE].ReadFromFile(AFileName);
        try
          f:= OpenFileRead(AFileName);
          FFileSize:= f.Size;
          //read first bytes
          FillChar(marker, SizeOf(marker), 0);
          f.Read(marker, SizeOf(marker));
          f.Seek(0, spBegin);

          if marker = 'RIFF' then Result:= ReadVer3(f)
          else
            if marker = 'wvpk' then Result:= ReadVer4(f)
            else Exit;

          FRatio:= FFileSize / (FSamples * (FChannels * FBits / 8) + 44) * 100;
        finally
          f.Free;
        end;
      except end;
    end;
end;

end.
