(*
  this file is a part of audio components suite v 2.3.
  copyright (c) 2002-2005 andrei borovsky. all rights reserved.
  see the license file for more details.
  you can contact me at mail@z0m3ie.de
*)

{
$Log: acs_mad.pas,v $
Revision 1.1  2005/12/19 18:36:38  z0m3ie
*** empty log message ***

Revision 1.2  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.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.3  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)

Revision 1.2  2005/08/22 20:17:01  z0m3ie
changed Headers to log
changed mail adress

}

unit acs_mad;

interface

uses

  ACS_Types, Classes, SysUtils, Math, MAD;

type

  TRawPCMWaveHeader = record
    RIFF: array [0..3] of Char;
    FileSize: Integer;
    RIFFType: array [0..3] of Char;
    FmtChunkId: array [0..3] of Char;
    FmtChunkSize: Integer;
    FormatTag: Word;
    Channels: Word;
    SampleRate: Integer;
    BytesPerSecond: Integer;
    BlockAlign: Word;
    BitsPerSample: Word;
    DataChunkId: array [0..3] of Char;
    DataSize: Integer;
  end;

  TMADProgressEvent = procedure(Sender : TComponent) of object;
  TMADDoneEvent = procedure(Sender : TComponent; Success : Boolean) of object;

  TMADThread = class(TThread)
  private
    _Free : Boolean;
    Progr : Integer;
    Owner : TComponent;
    FDecoder :  mad_decoder;
    FInputStream : TStream;
    FOutputStream : TStream;
    HasFirstFrame : Boolean;
    FSR : Integer;
    FChan : Integer;
    FBitrate : Integer;
    FValid : Boolean;
    Data : PACSBuffer8;
    InputDone : Boolean;
    WaveHdr : TRawPCMWaveHeader;
    FSize : Integer;
    FMADProgress : TMADProgressEvent;
    FMADDone : TMADDoneEvent;
    WhenDone : procedure of object;
  protected
    procedure Execute; override;
  public
    constructor Create(AOwner : TComponent; InputStream, OutputStream : TStream);
    destructor Destroy; override;
  end;

implementation

   function InputFunc(CData : Pointer; Stream : p_mad_stream) : Integer; cdecl;
   var
     MT : TMADThread;
     Len : Integer;
   begin

     MT := TMADThread(CData);

     if MT.InputDone then
     begin
       Result := MAD_FLOW_STOP;
       Exit;
     end;
     MT.InputDone := True;
//     Len := MT.FInputStream.Read(Data^, MT.FInputStream.Size);
     Len := MT.FInputStream.Size;
     mad_stream_buffer(Stream, MT.Data, Len);
     if not MT.Terminated then Result := MAD_FLOW_CONTINUE
     else Result := MAD_FLOW_STOP;
   end;

   function OutputFunc(CData : Pointer; Header : p_mad_header; pcm : p_mad_pcm) : Integer; cdecl;
   var
     MT : TMADThread;
     i, framesize : Integer;
     outsamples : array[0..2303] of SmallInt;
     text : array[0..4] of Char;
     CProgr : Integer;
   begin
     MT := TMADThread(CData);
     if not MT.HasFirstFrame then
     begin
       MT.FSR := pcm.samplerate;
       MT.FChan := pcm.channels;
       MT.FBitrate := Header.bitrate;
       framesize := Ceil(144*MT.FBitrate/MT.FSR);
       MT.FSize := Round(MT.FInputStream.Size/framesize*1152)*MT.FChan*2;
       MT.FValid := True;
       text := 'RIFF';
       Move(text[0], MT.WaveHdr.RIFF[0], 4);
       MT.WaveHdr.FileSize := MT.FSize + 44;
       text := 'WAVE';
       Move(text[0], MT.WaveHdr.RIFFType[0], 4);
       text := 'fmt ';
       Move(text[0], MT.WaveHdr.FmtChunkId[0], 4);
       MT.WaveHdr.FmtChunkSize := 16;
       MT.WaveHdr.FormatTag := 1;
       MT.WaveHdr.Channels := MT.FChan;
       MT.WaveHdr.SampleRate := MT.FSR;
       MT.WaveHdr.BitsPerSample := 16;
       MT.WaveHdr.BlockAlign := 2*MT.FChan;
       MT.WaveHdr.BytesPerSecond := MT.FSR * MT.WaveHdr.BlockAlign;
       text := 'data';
       Move(text[0], MT.WaveHdr.DataChunkId[0], 4);
       MT.WaveHdr.DataSize := MT.FSize;
       if MT.FOutputStream is TMemoryStream then
       begin
         MT.FOutputStream.Size :=MT.FSize + 44;
         MT.FOutputStream.Seek(0, soFromBeginning);
       end;
       MT.FOutputStream.Write(MT.WaveHdr, 44);
       MT.HasFirstFrame := True;
     end;
     if pcm.channels = 2 then
     begin
       for i := 0 to pcm.length -1 do
       begin
         if pcm.samples[0][i] >= MAD_F_ONE then
         pcm.samples[0][i] := MAD_F_ONE - 1;
         if pcm.samples[0][i] < -MAD_F_ONE then
         pcm.samples[0][i] := -MAD_F_ONE;
         pcm.samples[0][i] := pcm.samples[0][i] shr (MAD_F_FRACBITS + 1 - 16);
         outsamples[i shl 1] := pcm.samples[0][i];
         if pcm.samples[1][i] >= MAD_F_ONE then
         pcm.samples[1][i] := MAD_F_ONE - 1;
         if pcm.samples[1][i] < -MAD_F_ONE then
         pcm.samples[1][i] := -MAD_F_ONE;
         pcm.samples[1][i] := pcm.samples[1][i] shr (MAD_F_FRACBITS + 1 - 16);
         outsamples[(i shl 1)+1] := pcm.samples[1][i];
       end;
       MT.FOutputStream.Write(outsamples[0], pcm.length*4);
     end else
     begin
       for i := 0 to pcm.length -1 do
       begin
         if pcm.samples[0][i] >= MAD_F_ONE then
         pcm.samples[0][i] := MAD_F_ONE - 1;
         if pcm.samples[0][i] < -MAD_F_ONE then
         pcm.samples[0][i] := -MAD_F_ONE;
         pcm.samples[0][i] := pcm.samples[0][i] shr (MAD_F_FRACBITS + 1 - 16);
         outsamples[i] := pcm.samples[0][i];
       end;
       MT.FOutputStream.Write(outsamples[0], pcm.length*2);
     end;
     if MT.FSize <> 0 then
     begin
       CProgr := Round(MT.FOutputStream.Position/MT.FSize*100);
       if MT.Progr <> CProgr then
       begin
         MT.Progr := CProgr;
         if Assigned(MT.FMADProgress) then
         MT.FMADProgress(MT.Owner);
       end;
     end;
     if not MT.Terminated then Result := MAD_FLOW_CONTINUE
     else Result := MAD_FLOW_STOP;
   end;

   function ErrorFunc(CData : Pointer; Stream : p_mad_stream; Frame : p_mad_frame) : Integer; cdecl;
   begin
     Result := MAD_FLOW_CONTINUE;
   end;

   constructor TMADThread.Create;
   begin
     inherited Create(True);
     Owner := AOwner;
     FInputStream := InputStream;
     FOutputStream := OutputStream;
     FreeOnTerminate := False;
   end;

   destructor TMADThread.Destroy;
   begin
     if not _Free then
     begin
       Terminate;
       {$IFDEF WIN32}
       while not _Free do;
       {$ENDIF}
     end;
     inherited Destroy;
   end;

   procedure TMADThread.Execute;
   begin
     try
       GetMem(Data, FInputStream.Size);
       FInputStream.Read(Data[0], FInputStream.Size);
       mad_decoder_init(@FDecoder, Self, InputFunc, nil, nil, OutputFunc, ErrorFunc, nil);
       mad_decoder_run(@FDecoder, MAD_DECODER_MODE_SYNC);
       mad_decoder_finish(@FDecoder);
       FreeMem(Data);
       WhenDone;
       if Assigned(FMADDone) then FMADDone(Owner, FValid);
       _Free := True;
     except
       FreeMem(Data);
       WhenDone;
       _Free := True;
     end;
   end;

end.
