(* the original version of this file is written by thomas la cour, http://www.top-house.dk/~nr161/delphi/ This file is a part of Audio Components Suite v 2.4 Copyright (c) 2002, 2003 Andrei Borovsky. All rights reserved. See the LICENSE file for more details. You can contact me at acs@compiler4.net *) { $Log: acs_mac.pas,v $ Revision 1.7 2006/07/04 17:12:45 z0m3ie ACS 2.4 alt wiederhergestellt (unterschiedliche Sampleformate ...) Revision 1.3 2005/12/30 11:10:57 z0m3ie some corrections to lazarus-linux depending things Revision 1.2 2005/12/26 17:31:39 z0m3ie fixed some problems in acs_dsfiles fixed some problems in acs_vorbis reworked all buffers Revision 1.1 2005/12/19 18:36:38 z0m3ie *** empty log message *** Revision 1.6 2005/12/18 17:01:54 z0m3ie delphi compatibility Revision 1.5 2005/12/04 16:54:34 z0m3ie All classes are renamed, Style TACS... than T... to avoid conflicts with other components (eg TMixer is TACSMixer now) Revision 1.4 2005/11/29 18:32:51 z0m3ie bugfixes for win32 version Revision 1.3 2005/11/28 21:57:24 z0m3ie mostly FileOut fixes moved PBuffer to PBuffer8 set all to dynamically Buffering Revision 1.2 2005/09/13 04:04:50 z0m3ie First release without Components for Fileformats only TFileIn and TFileOut are Visible Revision 1.1 2005/09/12 22:04:52 z0m3ie modified structure again, fileformats are now in an sperat folder. all File In/Out classes are capsulated from TFileIn and TFileOut Revision 1.1 2005/08/25 20:18:00 z0m3ie Version 2.4 restructure TCDPlayer removed (fits not in component structure) TMP3ToWavConverter removed (fits not in component structure) } unit acs_mac; {$ifdef linux}{$message error 'unit not supported'}{$endif linux} interface uses ACS_File,Classes, SysUtils, Windows, ACS_Classes, MACDll; type // Note by A.B.: It seems that APE compressor supports file output only. TMACOut = class(TACSCustomFileOut) private APECompress: TAPECompress; WaveFormatEx: TWaveFormatEx; EndOfStream: Boolean; FCompressionLevel: Integer; FMaxAudioBytes: Integer; procedure SetCompressionLevel(Value: Integer); protected procedure Done; override; function DoOutput(Abort: Boolean): Boolean; override; procedure Prepare; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published property CompressionLevel: LongInt read FCompressionLevel write SetCompressionLevel stored True; property MaxAudioBytes: Integer read FMaxAudioBytes write FMaxAudioBytes; end; (* Note by A.B.: Due to the reasons described above this component ignores streamed input *) TMACIn = class(TACSCustomFileIn) private APEDecompress: TAPEDecompress; EndOfStream: Boolean; function GetAverageBitrate: Integer; function GetCurrentBitrate: Integer; function GetCurrentBlock: Integer; function GetCurrentMS: Integer; function GetLengthMS: Integer; function GetTotalBlocks: Integer; protected function GetBPS: Integer; override; function GetCh: Integer; override; function GetSR: Integer; override; function GetTotalTime: real; override; procedure OpenFile; override; procedure CloseFile; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function GetData(Buffer: Pointer; BufferSize: Integer): Integer; override; function Seek(Sample : Integer) : Boolean; override; procedure Flush; override; procedure Init; override; property AverageBitrate: Integer read GetAverageBitrate; property CurrentBitrate: Integer read GetCurrentBitrate; property CurrentBlock: Integer read GetCurrentBlock; property CurrentMS: Integer read GetCurrentMS; property LengthMS: Integer read GetLengthMS; property TotalBlocks: Integer read GetTotalBlocks; end; implementation constructor TMACOut.Create; begin inherited Create(AOwner); FBufferSize := $10000; FCompressionLevel := COMPRESSION_LEVEL_NORMAL; FMaxAudioBytes := MAX_AUDIO_BYTES_UNKNOWN; if not (csDesigning in ComponentState) then begin if not MACLoaded then raise EACSException.Create(MACPath + ' library could not be loaded.'); end; end; destructor TMACOut.Destroy; begin if Assigned(APECompress) then APECompress.Free; inherited Destroy; end; procedure TMACOut.Prepare; var r: Integer; begin GetMem(FBuffer,FBufferSize); if FFileName = '' then raise EACSException.Create('File name is not assigned.'); FInput.Init; EndOfStream := False; APECompress := TAPECompress.Create; macFillWaveFormatEx(WaveFormatEx, FInput.SampleRate, FInput.BitsPerSample, FInput.Channels); r := APECompress.Start( PChar(FFileName), @WaveFormatEx, FMaxAudioBytes, FCompressionLevel, nil, CREATE_WAV_HEADER_ON_DECOMPRESSION); CanOutput := (r = 0); if r <> 0 then raise EACSException.Create('Error starting APECompress.' + #13#10 + macErrorExplanation(r)); end; procedure TMACOut.Done; begin APECompress.Finish(nil, 0, 0); APECompress.Free; APECompress := nil; FInput.Flush; FreeMem(FBuffer); end; function TMACOut.DoOutput(Abort: Boolean): Boolean; var Len, i, x, z: Integer; pBuffer: PByteArray; nAudioBytesLeft, nBufferBytesAvailable, nNoiseBytes, nRetVal: Integer; begin // No exceptions Here Result := True; if not CanOutput then Exit; if Abort or EndOfStream then begin (* We don't close file here to avoide exceptions if output componenet's Stop method is called *) Result := False; Exit; end; Len := Finput.GetData(@FBuffer[0], FBufferSize); x := 0; if Len <> 0 then begin nAudioBytesLeft := Len; while (nAudioBytesLeft > 0) do begin nBufferBytesAvailable := 0; pBuffer := APECompress.LockBuffer(nBufferBytesAvailable); nNoiseBytes := nBufferBytesAvailable; if nNoiseBytes > nAudioBytesLeft then nNoiseBytes := nAudioBytesLeft; //whats this ? schoult System.Move not be faster ? for z := 0 to nNoiseBytes - 1 do begin pBuffer[z] := FBuffer[x]; inc(x); end; nRetVal := APECompress.UnlockBuffer(nNoiseBytes, TRUE); if (nRetVal <> 0) then raise EACSException.Create('APECompress.UnlockBuffer Error: ' + inttostr(nRetVal)); dec(nAudioBytesLeft, nNoiseBytes); end end else EndOfStream := True; end; constructor TMACIn.Create; begin inherited Create(AOwner); BufferSize := $2000; if not (csDesigning in ComponentState) then begin if not MACLoaded then raise EACSException.Create(MACPath + ' library could not be loaded.'); end; end; destructor TMACIn.Destroy; begin if Assigned(APEDecompress) then APEDecompress.Free; inherited Destroy; end; procedure TMACIn.OpenFile; begin FValid := True; if FOpened = 0 then begin EndOfStream := False; APEDecompress := TAPEDecompress.Create(FileName); if APEDecompress.Handle <> 0 then begin FSize := APEDecompress.InfoWavTotalBytes; FSR := APEDecompress.InfoSampleRate; FBPS := APEDecompress.InfoBitsPerSample; FChan := APEDecompress.InfoChannels; FTime := APEDecompress.InfoLengthMS div 1000; // Round(ov_time_total(VFile, 0)); FTotalSamples := (FSize div (FBPS shr 3)) div FChan; end else begin FValid := False; FOpened := -1; end; end; Inc(FOpened); end; procedure TMACIn.CloseFile; begin if FOpened = 1 then begin if Assigned(APEDecompress) then APEDecompress.Free; APEDecompress := nil; end; if FOpened > 0 then Dec(FOpened); end; function TMACIn.GetData(Buffer: Pointer; BufferSize: Integer): Integer; var l, csize, offs: Integer; blocks: Integer; tmp: Double; begin if not Busy then raise EACSException.Create('The Stream is not opened'); if BufStart > BufEnd then begin if FOffset <> 0 then begin offs := Round((FOffset / 100) * FSize); FPosition := FPosition + offs; if FPosition < 0 then FPosition := 0 else if FPosition > FSize then FPosition := FSize; APEDecompress.Seek(FPosition shr 2); FOffset := 0; end; BufStart := 1; BufEnd := 0; if not EndOfStream then begin while BufEnd < BufferSize do begin //l := ov_read(VFile, @buf[BufEnd + 1], BUF_SIZE - BufEnd, 0, 2, 1, @cursec); blocks := (BufferSize - BufEnd) div 4; APEDecompress.GetData(@FBuffer[BufEnd], blocks, l); l := l * 4; if l <= 0 then begin EndOfStream := True; Break; end; Inc(BufEnd, l); if (FEndSample <> -1) then begin csize := (FEndSample-FStartSample)*(FBPS shr 3)*FChan; if (csize - FPosition) <= 0 then begin EndOfStream := True; Break; end; if (csize - FPosition) < BufEnd then begin BufEnd := csize - FPosition; Break; end; end; end; end; if EndOfStream and FLoop then begin Flush; Init; EndOfStream := False; while BufEnd < BufferSize do begin //l := ov_read(VFile, @buf[BufEnd + 1], BUF_SIZE - BufEnd, 0, 2, 1, @cursec); blocks := (BufferSize - BufEnd) div 4; APEDecompress.GetData(@FBuffer[BufEnd], blocks, l); l := l * 4; if l <= 0 then begin EndOfStream := True; Break; end; Inc(BufEnd, l); end; end; end; if BufferSize < (BufEnd - BufStart + 1) then Result := BufferSize else Result := BufEnd - BufStart + 1; Move(FBuffer[BufStart - 1], Buffer^, Result); Inc(BufStart, Result); Inc(FPosition, Result); end; function TMACIn.GetTotalTime: real; begin OpenFile; if Assigned(APEDecompress) then Result := APEDecompress.LengthMS / 1000; CloseFile; end; function TMACIn.GetAverageBitrate: Integer; begin if Assigned(APEDecompress) then Result := APEDecompress.AverageBitrate; end; function TMACIn.GetCurrentBitrate: Integer; begin if Assigned(APEDecompress) then Result := APEDecompress.CurrentBitrate; end; function TMACIn.GetCurrentBlock: Integer; begin if Assigned(APEDecompress) then Result := APEDecompress.CurrentBlock; end; function TMACIn.GetCurrentMS: Integer; begin if Assigned(APEDecompress) then Result := APEDecompress.CurrentMS; end; function TMACIn.GetLengthMS: Integer; begin if Assigned(APEDecompress) then Result := APEDecompress.LengthMS; end; function TMACIn.GetTotalBlocks: Integer; begin if Assigned(APEDecompress) then Result := APEDecompress.TotalBlocks; end; function TMACIn.GetBPS: Integer; begin OpenFile; Result := FBPS; CloseFile; end; function TMACIn.GetCh: Integer; begin OpenFile; Result := FChan; CloseFile; end; function TMACIn.GetSR: Integer; begin OpenFile; Result := FSR; CloseFile; end; procedure TMACOut.SetCompressionLevel(Value: Integer); begin case Value of COMPRESSION_LEVEL_FAST, COMPRESSION_LEVEL_NORMAL, COMPRESSION_LEVEL_HIGH, COMPRESSION_LEVEL_EXTRA_HIGH: FCompressionLevel := Value; else FCompressionLevel := COMPRESSION_LEVEL_NORMAL; end; end; procedure TMACIn.Flush; begin inherited Flush; end; procedure TMACIn.Init; begin inherited Init; BufStart := 1; BufEnd := 0; end; function TMACIn.Seek(Sample : Integer) : Boolean; begin Result := False; if not FSeekable then Exit; Result := True; OpenFile; APEDecompress.Seek(Sample); CloseFile; end; initialization FileFormats.Add('mac','Monkey Audio',TMACOut); FileFormats.Add('mac','Monkey Audio',TMACIn); end.