//Bits - ?
unit AudioMPEGplus;

interface

uses KOL, ATLCommon, TagID3v1, TagID3v2, TagAPE;

type TMPEGplusAudioInfo = PAudioInfo;

function NewAudioMPEGplus: TMPEGplusAudioInfo;

implementation

uses ATLLib;

const
  { ID code for stream version 7 and 7.1 }
  STREAM_VERSION_7_ID  = $072B504D;                    {MP+7}
  STREAM_VERSION_71_ID = $172B504D;                    {MP+71}
  STREAM_VERSION_8_ID  = $082B504D;                    {MP+8}
  mpp_samplerates : array[0..3] of integer = ( 44100, 48000, 37800, 32000 );

  { Used with ChannelModeID property }
  MPP_CM_STEREO = 1;                                   { Index for stereo mode }
  MPP_CM_JOINT_STEREO = 2;                       { Index for joint-stereo mode }

  { Channel mode names }
  MPP_MODE: array [0..2] of string = ('Unknown', 'Stereo', 'Joint Stereo');

  { Used with ProfileID property }
  MPP_PROFILE_QUALITY0 = 9;                            { '--quality 0' profile }
  MPP_PROFILE_QUALITY1 = 10;                           { '--quality 1' profile }
  MPP_PROFILE_TELEPHONE = 11;                            { 'Telephone' profile }
  MPP_PROFILE_THUMB = 1;                              { 'Thumb' (poor) quality }
  MPP_PROFILE_RADIO = 2;                            { 'Radio' (normal) quality }
  MPP_PROFILE_STANDARD = 3;                        { 'Standard' (good) quality }
  MPP_PROFILE_XTREME = 4;                       { 'Xtreme' (very good) quality }
  MPP_PROFILE_INSANE = 5;                       { 'Insane' (excellent) quality }
  MPP_PROFILE_BRAINDEAD = 6;                 { 'BrainDead' (excellent) quality }
  MPP_PROFILE_QUALITY9 = 7;                { '--quality 9' (excellent) quality }
  MPP_PROFILE_QUALITY10 = 8;              { '--quality 10' (excellent) quality }
  MPP_PROFILE_UNKNOWN = 0;                                   { Unknown profile }
  MPP_PROFILE_EXPERIMENTAL = 12;
  { Profile names }
  MPP_PROFILE: array [0..12] of string = ('Unknown', 'Thumb', 'Radio', 'Standard', 'Xtreme', 'Insane', 'BrainDead',
                                          '--quality 9', '--quality 10', '--quality 0', '--quality 1', 'Telephone', 'Experimental');


type
  { File header data - for internal use }
  THeaderByte = array [1..32] of Byte;                     { Data as byte array }
  THeaderInteger = array [1..8] of Integer;             { Data as integer array }

type
  TAudioMPEGplus = object(TAudioInfo)
  end;

function AudioMPEGplusReader(const AFileName: KOLstring; AudioInfo: TMPEGplusAudioInfo): Boolean; forward;

function NewAudioMPEGplus: TMPEGplusAudioInfo;
begin
    New(Result, Create);
    with TAudioMPEGplus(Result^) do begin
      TagAdd(tID3v1, NewTagID3v1);
      TagAdd(tID3v2, NewTagID3v2);
      TagAdd(tAPE, NewTagAPE);
      FAudioVendor:= aMPEGplus;
      FExistsProps:= [apSampleRate, apBitrate, apChannels, apBits, apSamples, apDuration, apRatio];
      FReader:= AudioMPEGplusReader;
    end;
end;

function AudioMPEGplusReader(const AFileName: KOLstring; AudioInfo: TMPEGplusAudioInfo): Boolean;
var Header: THeaderByte;
    Source: PStream;
    EncoderID, ProfileID, TagSize, StreamVersion: Integer;
    S: String;
begin
    with TAudioMPEGplus(AudioInfo^) do begin
      Result:= False;
      try
        Source:= OpenFileRead(AFileName);
        try
          ResetData;
          ProfileID:= MPP_PROFILE_UNKNOWN;

          FFileSize:= Source.Size;

          TagInfo[tID3v1].ReadFromFile(AFileName);
          TagInfo[tID3v2].ReadFromFile(AFileName);
          TagInfo[tAPE].ReadFromFile(AFileName);
          TagSize:= TagInfo[tID3v1].TagSize + PTagID3v1(TagInfo[tID3v1]).Lyrics.TagSize + TagInfo[tID3v2].TagSize + TagInfo[tAPE].TagSize;

          Source.Seek(TagInfo[tID3v2].TagSize, spBegin);

          if Source.Read(Header, SizeOf(THeaderByte)) <> SizeOf(THeaderByte) then Exit;

          if THeaderInteger(Header)[1] = STREAM_VERSION_7_ID then StreamVersion:= 7
          else
            if THeaderInteger(Header)[1] = STREAM_VERSION_71_ID then StreamVersion:= 71
            else
              if THeaderInteger(Header)[1] = STREAM_VERSION_8_ID then StreamVersion:= 8
              else begin
                 StreamVersion:= (Header[1] shr 11) and $03FF;
                 if (StreamVersion < 4) or (StreamVersion > 6) then Exit;
              end;

          case StreamVersion of
            4..6: begin
              FBitRate:= THeaderInteger(Header)[1] shr 23;
  {            IS_used        = (Int) Bitstream_read (1);
              MS_used        = (Int) Bitstream_read (1);
              StreamVersion  = (Int) Bitstream_read(10);
              MaxBandDesired = (Int) Bitstream_read (5);
              Blockgroesse   = (Int) Bitstream_read (6);}
              if StreamVersion < 5 then FSamples:= THeaderInteger(Header)[2] shr 16 else FSamples:= THeaderInteger(Header)[2];
              FSampleRate:= 44100;
              EncoderID:= -1;
              if (Header[3] mod 128) = 0 then FChannels:= MPP_CM_STEREO else FChannels:= MPP_CM_JOINT_STEREO;
            end;
            else begin
              FBitRate:= 0;
              //Blockgroesse   = 1;
              FSamples:= THeaderInteger(Header)[2];

              {IS_used        = (Int) Bitstream_read (1);
              MS_used        = (Int) Bitstream_read (1);
              MaxBandDesired = (Int) Bitstream_read (6);}

              case ((Header[11] and $F0) shr 4) of
                  1: ProfileID:= MPP_PROFILE_EXPERIMENTAL;
                  5: ProfileID:= MPP_PROFILE_QUALITY0;
                  6: ProfileID:= MPP_PROFILE_QUALITY1;
                  7: ProfileID:= MPP_PROFILE_TELEPHONE;
                  8: ProfileID:= MPP_PROFILE_THUMB;
                  9: ProfileID:= MPP_PROFILE_RADIO;
                 10: ProfileID:= MPP_PROFILE_STANDARD;
                 11: ProfileID:= MPP_PROFILE_XTREME;
                 12: ProfileID:= MPP_PROFILE_INSANE;
                 13: ProfileID:= MPP_PROFILE_BRAINDEAD;
                 14: ProfileID:= MPP_PROFILE_QUALITY9;
                 15: ProfileID:= MPP_PROFILE_QUALITY10;
              end;

              FSampleRate:= mpp_samplerates[Header[11] and 3];
              if (Header[12] mod 128) < 64 then FChannels:= MPP_CM_STEREO else FChannels:= MPP_CM_JOINT_STEREO;

              // reading peak and gain values from the file (or use useful values if they are absent)
  {           PeakTitle = (Uint16_t)(1.18 * (Uint32_t)Bitstream_read (16));
              GainTitle = Bitstream_read (16);
              tmp       = Bitstream_read (16);
              if ( SecurePeakTitle = (tmp != 0) )
                  PeakTitle = tmp;
              GainAlbum = Bitstream_read (16);
              PeakAlbum = Bitstream_read (16);
              if ( PeakAlbum == 0 )
                  PeakAlbum = PeakTitle;

              // reading true gapless
              TrueGaplessPresent = Bitstream_read ( 1);
              LastValidSamples   = Bitstream_read (11);
              (void) Bitstream_read (20);
  }
              // reserved bytes for future use
              EncoderID:= Header[28];
            end;
          end;

          FBits:= 16;
          if (FBitrate = 0) and (FSamples > 0) then FBitrate:= Round(8 * (FFileSize - TagSize) * (FSampleRate/1000) / FSamples / 1152);

          FValid:= (FBitRate >= 3) and (FBitRate <= 480) and (FSamples > 0) and (FSampleRate > 0);
          if FValid then begin
            FDuration:= FSamples * 1152 / FSampleRate;
            FRatio:= (FFileSize - TagSize) / ((FSamples * 1152) * (2 * FBits / 8) + 44) * 100;
          end;

          {$IFDEF USE_OtherAudioProps}
          OtherProps.Add('Version='+Int2Str(StreamVersion));
          if EncoderID <> 0 then
            case (EncoderID mod 10) of
              0:        S:= format('%u.%u Release',     [EncoderID div 100, (EncoderID div 10) mod 10]);
              2,4,6,8 : S:= format('%u.%.2u Beta',      [EncoderID div 100, EncoderID mod 100] );
              else      S:= format('%u.%.2u --Alpha--', [EncoderID div 100, EncoderID mod 100] );
            end;


          OtherProps.Add('Encoder='+S);
          OtherProps.Add('EncoderID='+Int2Str(EncoderID));

          OtherProps.Add('Profile='+MPP_PROFILE[ProfileID]);
          OtherProps.Add('ProfileID='+Int2Str(ProfileID));
          {$ENDIF}
          FChannelMode:= MPP_MODE[FChannels];
          if FChannels > 0 then FChannels:= 2;

          Result:= True;
        finally
          Source.Free;
        end;
      except
      end;
    end;
end;

end.
