1377 lines
40 KiB
ObjectPascal
Raw Permalink Blame History

(*
this file is a part of audio components suite.
see the license file for more details.
you can contact me at mail@z0m3ie.de
$Log: acs_classes.pas,v $
Revision 1.22 2006/09/10 18:26:02 z0m3ie
*** empty log message ***
Revision 1.21 2006/09/04 14:40:15 z0m3ie
*** empty log message ***
Revision 1.20 2006/08/31 20:10:54 z0m3ie
*** empty log message ***
Revision 1.19 2006/07/09 16:40:34 z0m3ie
*** empty log message ***
Revision 1.18 2006/07/04 17:12:45 z0m3ie
ACS 2.4 alt wiederhergestellt (unterschiedliche Sampleformate ...)
Revision 1.7 2006/01/01 18:46:40 z0m3ie
*** empty log message ***
Revision 1.6 2005/12/30 13:24:27 z0m3ie
waveformat corrections, localizing
Revision 1.5 2005/12/30 11:10:57 z0m3ie
some corrections to lazarus-linux depending things
Revision 1.4 2005/12/27 05:43:03 z0m3ie
fixed little bug with TDoneThread
Revision 1.3 2005/12/26 17:31:38 z0m3ie
fixed some problems in acs_dsfiles
fixed some problems in acs_vorbis
reworked all buffers
Revision 1.2 2005/12/20 20:11:52 z0m3ie
updated package
Revision 1.1 2005/12/19 18:34:35 z0m3ie
*** empty log message ***
Revision 1.8 2005/12/04 16:54:33 z0m3ie
All classes are renamed, Style TACS... than T... to avoid conflicts with other components (eg TMixer is TACSMixer now)
Revision 1.7 2005/11/28 21:57:24 z0m3ie
mostly FileOut fixes
moved PBuffer to PBuffer8
set all to dynamically Buffering
Revision 1.6 2005/11/28 19:10:14 z0m3ie
*** empty log message ***
Revision 1.5 2005/10/02 16:51:46 z0m3ie
*** empty log message ***
Revision 1.4 2005/09/16 17:34:29 z0m3ie
*** empty log message ***
Revision 1.3 2005/09/15 20:59:38 z0m3ie
start translate the documentation in the source for pasdoc
Revision 1.2 2005/09/13 21:54:11 z0m3ie
acs is localizeable now (ACS_Strings)
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.8 2005/09/09 21:33:42 z0m3ie
linux corrections
Revision 1.7 2005/09/04 17:59:37 z0m3ie
moving CDIn support to AKRip mostly
begon to add mpegin support for Win with mpg123
Revision 1.6 2005/09/02 16:27:49 z0m3ie
*** empty log message ***
Revision 1.5 2005/08/28 20:31:18 z0m3ie
linux restructuring for 2.4
Revision 1.4 2005/08/28 18:35:53 z0m3ie
created Delphi package for 2.4
more Mixer stuff
updated some things for Delphi
Revision 1.3 2005/08/22 20:17:01 z0m3ie
changed Headers to log
changed mail adress
*)
{
@abstract(this unit introduces the base classes for acs)
@author(Andrei Borovsky (2003-2005))
@author(Christian Ulrich (2005))
}
unit acs_classes;
{$ifdef fpc}
{$mode delphi}
{$endif}
interface
uses
{$IFDEF MSWINDOWS}
Windows,Dialogs,
{$ENDIF}
ACS_Strings,Classes, SysUtils,ACS_Types;
type
TACSOutputStatus = (tosUndefined,tosPlaying, tosPaused, tosIdle);
TACSFileOutputMode = (foRewrite = 0, foAppend);
TACSOutputFunc = function(Abort : Boolean):Boolean of object;
//TThreadDoneEvent = procedure of object;
TACSThreadExceptionEvent = procedure(Sender : TComponent; E : Exception) of object;
TACSHandleThreadException = procedure(E : Exception) of object;
TACSOutputDoneEvent = procedure(Sender : TComponent) of object;
TACSOutputProgressEvent = procedure(Sender : TComponent) of object;
{$IFDEF LINUX}
// File access mask constants
const
famUserRead = 64;
famUserWrite = 128;
famGroupRead = 8;
famGroupWrite = 16;
famOthersRead = 1;
famOthersWrite = 2;
{$ENDIF}
type
{$IFNDEF FPC}
{$IFDEF LINUX}
TTPriority = Integer;
{$ENDIF}
{$IFDEF MSWINDOWS}
TTPriority = TThreadPriority;
{$ENDIF}
{$ELSE}
TTPriority = TThreadPriority;
{$ENDIF}
{Basic exception class for ACS}
EACSException = class(Exception)
end;
{Basic Thread class for ACS}
{ TACSThread }
TACSThread = class(TThread)
private
procedure CallOnProgress;
public
Parent : TObject;
Terminating : Boolean;
Stop : Boolean;
HandleException : TACSHandleThreadException;
Delay : Integer;
CS : TRTLCriticalSection;
constructor Create;
procedure DoPause;
procedure DoResume;
procedure Execute; override;
end;
TACSVerySmallThread = class(TThread)
public
FOnDone : TACSOutputDoneEvent;
Sender : TComponent;
procedure Execute; override;
procedure CallOnDone;
end;
{ TACSInput is the base class for all input and converter components.
}
{ TACSInput }
{ TACSCustomInput }
TACSCustomInput = class(TComponent)
protected
FPosition : Integer;
FSize : Integer;
FBusy : Boolean;
BufStart, BufEnd : Integer;
FBuffer : array of byte;
(* We don't declare the buffer variable here
because different descendants may need different buffer sizes *)
function GetBPS : Integer; virtual;
function GetCh : Integer; virtual;
function GetSR : Integer; virtual;
function GetTotalTime : real; virtual;
procedure SetBufferSize(S : Integer);virtual;
function GetBufferSize : Integer;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
{ This is an abstract method.
In TACSInput descendands this function reads the audio stream data into the Buffer.
Returns the number of bytes actually read.
Returns zero at the end of stream.
Note: usually this method is called internally by the output or converter component
to which the input component is assigned. You can call this method if you want to
get direct access to the audio stream. In such a case the sequence of calls should
look like this:
InputComponent.Init;
InputComponent.GetData(...); // in a loop
InputComponent.Flush;
}
function GetData(Buffer : Pointer; BufferSize : Integer): Integer; virtual; abstract;
{ This function is called from output component when the buffer must be filled.
}
procedure Reset; virtual;
{ This is an abstract method. In TACSInput descendands it prepares input
component for reading data.
}
procedure Init; virtual; abstract;
{ This is an abstract method. In TACSInput descendands it closes the current input,
clearing up all temporary structures alocated during data transfer.
Note: usually this method is called internally by the output or converter component to
which the input component is assigned. You can call this method if you want to get direct
access to the audio stream.
}
procedure Flush; virtual; abstract;
{ Read this property to determine the number of bits per sample in the input audio stream.
Possible values are 8 and 16.
}
property BitsPerSample : Integer read GetBPS;
property Position : Integer read FPosition;
{ Read this property to get the sample rate (sampling frequency) in Hz
for the input audio stream.
}
property SampleRate : Integer read GetSR;
{ Read Channles to determine the number of channels in the input audio stream.
Possible values are 1 (mono) and 2 (stereo).
}
property Channels : Integer read GetCh;
{ Read Size to determine the size of the input stream in bytes.
}
property Size : Integer read FSize;
{ Read this property to get the total time of the input stream in seconds.
If the total time cannot be determined this property contains 0.
}
property TotalTime : Real read GetTotalTime;
{ This property sets the buffersize of the component
}
property BufferSize : Integer read GetBufferSize write SetBufferSize;
property Busy : Boolean read FBusy;
end;
{ TACSOutput is the base class for all ACS output components.
}
{ TACSOutput }
TACSCustomOutput = class(TComponent)
protected
CanOutput : Boolean;
CurProgr : real;
Thread : TACSThread;
FInput : TACSCustomInput;
FOnDone : TACSOutputDoneEvent;
FOnProgress : TACSOutputProgressEvent;
Busy : Boolean; // Set to true by Run and to False by WhenDone.
FOnThreadException : TACSThreadExceptionEvent;
InputLock : Boolean;
FBufferSize : Integer;
FBuffer : PACSBuffer8;
function GetPriority : TTPriority;
function GetSuspend : Boolean;
function GetProgress : real;
procedure SetInput(vInput : TACSCustomInput); virtual;
procedure SetPriority(Priority : TTPriority);
procedure SetSuspend(v : Boolean);
procedure WhenDone;
function GetTE : Integer;
function GetStatus : TACSOutputStatus;
function GetDelay : Integer;
procedure SetDelay(Value : Integer);
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure HandleThreadException(E : Exception);
procedure SetBufferSize(S : Integer);
public
procedure Prepare; virtual; abstract; // Calls FInput.init
function DoOutput(Abort : Boolean):Boolean; virtual; abstract;
procedure Done; virtual; abstract; // Calls FInput.Flush
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
{$IFDEF MSWINDOWS}
procedure Abort;
{$ENDIF}
{ pauses the output (the output thread is suspended).
}
procedure Pause;virtual;
{ Resumes previously paused output.
}
procedure Resume;virtual;
{ This is the most important method in the output components.
After an input component has been assigned, call Run to start audio-processing chain.
}
procedure Run;
{ Stops the running output process.
}
procedure Stop;
{ Use this property to set the delay (in milliseconds) in output thread.
This property allows the user to reduce the stress the output thread puts
on the CPU (especially under Windows).
Be careful with this property when using TAudioOut component.
Assigning too large values to it can cause dropouts in audio playback.
}
property Delay : Integer read GetDelay write SetDelay;
{ Output components perform output in their own threads.
Use this property to set the priority for the thread.
}
property ThreadPriority : TTPriority read GetPriority write SetPriority;
{ Read Progress to get the output progress in percents.
This value is meaningful only after the input component has been set
and only if the input component can tell the size of its stream.
}
property Progress : real read GetProgress;
{ This property indicates the output component's current status. Possible values are:
tosPlaying: the component is working;
tosPaused: the component is paused (the Pause method was called);
tosIdle: the component is idle;
}
property Status : TACSOutputStatus read GetStatus;
property TimeElapsed : Integer read GetTE;
published
{ This property allows you to set the input component for the output component.
The valid input components must be descendants of TACSInput.
}
property Input : TACSCustomInput read Finput write SetInput;
{ If this property is set to True the output thread suspends every time the
output component becomes inactive, otherwise the thread executes permanently.
This property is introduced to simplify the debugging process ander
Kylix IDE. Output components perform output in their own threads.
The signals received by multi-threaded applications under IDE can cause problems.
Set this property to False when debugging your application
and to True in the final release.
}
property SuspendWhenIdle : Boolean read GetSuspend write SetSuspend;
{ This event is invoked when the output is finished.
Note: don't try to start other output job before OnDone event from the
previous job is triggered. An attempt to do so will cause the
"Component is buisy" exception.
It is a good practice to desable output-starting controls in the
method where the output component's Run method is called and to enable
them in the output component's OnDone event handler.
}
property OnDone : TACSOutputDoneEvent read FOnDone write FOndone;
{ This event is invoked every time the Progress property value changes.
Be careful when referencing GUI interface components in OnProgress event's handler.
}
property OnProgress : TACSOutputProgressEvent read FOnProgress write FOnProgress;
{ Thread procedure should never terminate.
For this reason all the exceptions that might arise in the thread
procedure are caught within the procedure itself.
The thread generates OnThreadException event to inform the application
that an exception has occurred.
Avoid any potential exception-rising actions in this event<6E>s handler!
Use the handler to reset application controls (if needed) and call the
input component<6E>s Reset method.
See the CD ripper demo for an example of handling thread exceptions.
}
property OnThreadException : TACSThreadExceptionEvent read FOnThreadException write FOnThreadException;
{ This Property sets the Buffersize of the component
}
property BufferSize : Integer read FBufferSize write SetBufferSize;
end;
{ TACSStreamedInput introduces Stream property.
This property allows input components that descend from TACSStreamedInput
to get data not only from files on disc but from any kind of stream.
The descendats of this class are TACSFileIn (and all its descendats except TMACIn),
and TStreamIn component that is designed to read raw audio data from streams.
}
TACSStreamedInput = class(TACSCustomInput)
protected
FStream : TStream;
FStreamAssigned : Boolean;
FSeekable : Boolean;
procedure SetStream(aStream : TStream);
public
{ If you write network applications with ACS, you are encouraged to use custom
streams as it is described in programmer's introduction
(see also OggStream demo).
Usually these custom streams will not be seekable.
Unfortunately TStream class has no way to indicate it is not seekable
(all standart TStream descendats are seekable).
So this property has been added to allow you to specify if the stream is seekable or not.
The default value of this property is True, and if the stream you're planning
to use with this component is not seekable, you have to set Seekable to False.
Note that if the stream is not seekable, such properties as Channels, SampleRate,
BitsPerSample, and Valid will not return correct values until the input
component starts performing the actual playback.
}
property Seekable : Boolean read FSeekable write FSeekable;
{ Use this property to set the input stream for the input component.
Remember that you have to create, destroy and position the input stream explicitly.
In TACSFileIn descendants the stream assigned to this property takes over
the FileName property, i. e. if both Stream and FileName property are assigned,
the stream and not the file will be used for the actual input.
To unassign this property set it to nil.
If the stream is seekable it will be reset to the beginning at the end of the playback.
}
property Stream : TStream read FStream write SetStream;
constructor Create(AOwner: TComponent); override;
end;
{ TACSStreamedOutput introduces Stream property.
This property allows output components that descend from TACSStreamedOutput
to store data not only to files on disc but to any kind of stream as well.
}
TACSStreamedOutput = class(TACSCustomOutput)
protected
{ Use this property to set the output stream for the corresponding output component.
Remember that you have to create, destroy and position the input stream explicitly.
In file-handling TACSStreamedOutput descendants the stream assigned to this property
takes over the FileName property, i. e. if both Stream and FileName property are assigned,
the stream and not the file will be used for the actual output.
To unassign this property set it to nil.
}
FStream : TStream;
FStreamAssigned : Boolean;
procedure SetStream(aStream : TStream);
public
property Stream : TStream read FStream write SetStream;
end;
{ This class introduces an simple handler for an file tag it can hold any streamable
piece of data or an string
}
TACSFileTag = CLASS
private
function GetName : string;virtual;
public
property Name : string read GetName;
function AsString : string;virtual;
function Streamable : Boolean;virtual;
procedure SaveToStream(Stream : TStream);virtual;
procedure LoadFromStream(Stream : TStream);virtual;
end;
{ This class introduces an base class for file tag lists
}
{ TACSFileInfo }
TACSFileInfo = CLASS
private
procedure ReadFromFile;virtual;
procedure SetStringTag(Idx : string; const AValue: TACSFileTag);
function GetStringTag(Idx : string): TACSFileTag;
public
procedure SaveToFile;virtual;
property Tags[Idx : string] : TACSFileTag read GetStringTag write SetStringTag;
end;
{ TACSFileIn is the base class for all input components that read data from files
(or from other streams in the corresponding file format).
It introduces such properties as FileName, StartSample, EndSample, Loop,
and Valid and Jump, Seek, SetStartTime, SetEndTime methods.
}
TACSCustomFileIn = class(TACSStreamedInput)
protected
FFileName : TFileName;
FOffset : real;
FOpened : Integer;
FValid : Boolean;
FBPS, FSR, FChan : Integer;
FTime : Integer;
FLoop : Boolean;
FStartSample, FEndSample : Integer;
FTotalSamples : Integer;
function GetBPS : Integer; override;
function GetCh : Integer; override;
function GetSR : Integer; override;
function GetTime : Integer;
function GetValid : Boolean;
(* Note on FSize calculation:
FSize is calculated in OpenFile method as the FULL file size.
More precise calculations regarding StartSample/EndSample are done in Init. *)
procedure OpenFile; virtual; abstract;
procedure CloseFile; virtual; abstract;
function GetTotalTime : real; override;
public
constructor Create(AOwner: TComponent); override;
procedure Reset; override;
procedure Flush; override;
procedure Init; override;
function Seek(SampleNum : Integer) : Boolean; virtual; abstract;
function SetStartTime(Minutes, Seconds : Integer) : Boolean;
function SetEndTime(Minutes, Seconds : Integer) : Boolean;
procedure Jump(Offs : real);
{ Read this property to determine the total playing time of the file in seconds.
}
property Time : Integer read GetTime;
{ Use this property to read the total number of samples in the file/stream assigned
to the component. The sample in this context means the minimum playable
portion of audio data. For mono 8-bit sound 1 sample = 1 byte,
while for stereo 16-bit sound 1 sample = 4 bytes.
}
property TotalSamples : Integer read FTotalSamples;
{ Read this property to determine if the file is valid.
It is a good practice to check this property before performing other
operations on audio stream.
}
property Valid : Boolean read GetValid;
published
{ Use this property to set the number of the end sample in the file that
the playback should stop at.
The value of -1 (default) tells the component to play the file up to the end.
The sample in this context means the minimum playable portion of audio data.
For mono 8-bit sound 1 sample = 1 byte,
while for stereo 16-bit sound 1 sample = 4 bytes.
You can get the total number of samples in the file by reading TotalSamples property.
Note that in some cases (for example, with ADPCM files) sample positioning is not quite precise.
}
property EndSample : Integer read FEndSample write FEndSample;
{ Use this property to set the name of the file to read data from.
}
property FileName : TFileName read FFileName write FFileName stored True;
{ The default value of Loop is False. If this property is set to True,
the file playback will be looped, in other words the file will start playing
again right after it is finished. Note, that if Loop is set to True, it will not
present end conditions to the corresponding output component.
You can stop the looped input component either by setting Loop to False,
or by calling Stop method of the corresponding output component.
}
property Loop : Boolean read FLoop write FLoop;
{ Use this property to set the number of the sample in the file that the
playback should start from. The value of 0 (default) tells the component
to play the file from the beginning.
The sample in this context means the minimum playable portion of audio data.
For mono 8-bit sound 1 sample = 1 byte, while for stereo 16-bit sound 1 sample = 4 bytes.
You can get the total number of samples in the file by reading TotalSamples property.
Note that in some cases (for example, with ADPCM files) sample positioning is not quite precise.
}
property StartSample : Integer read FStartSample write FStartSample;
end;
TACSCustomFileOut = class(TACSStreamedOutput)
protected
FFileName : TFileName;
FFileMode : TACSFileOutputMode;
FAccessMask : Integer;
procedure SetFileMode(aMode : TACSFileOutputMode); virtual;
public
constructor Create(AOwner: TComponent); override;
{$IFDEF LINUX}
{ Use this property to set the file access mask. The default value is $1B6,
which corresponds to rw-rw-rw- access mask.
}
property AccessMask : Integer read FAccessMask write FAccessMask;
{$ENDIF}
published
{ There are two possible values for this property: foRewrite and foAppend.
When foRewrite (default value) is set and the file with the specified
name already exists (or a stream is assigned) the contents of the file
(stream) is rewritten.
In the foAppend mode the new data is appended to the end of the existing
file (stream).
Currently only TWaveOut and TVorbisOut components support foAppend mode.
}
property FileMode : TACSFileOutputMode read FFileMode write SetFileMode;
{ Use this property to set the name of the file to save data to.
}
property FileName : TFileName read FFileName write FFileName;
end;
TACSCustomConverter = class(TACSCustomInput)
protected
InputLock : Boolean;
FInput : TACSCustomInput;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure SetInput(aInput : TACSCustomInput); virtual;
public
procedure UnlockInput;
published
property Input : TACSCustomInput read FInput write SetInput;
end;
const
STREAM_BUFFER_SIZE = $80000;
type
// Circular buffer with TStream interface
TACSBufferMode = (bmBlock, bmReport);
TACSBufferStream = class(TStream)
private
Buff : array[0..STREAM_BUFFER_SIZE-1] of Byte;
ReadCur, WriteCur : Integer;
FBufferMode : TACSBufferMode;
// fBreak : Boolean;
FBytesInBuffer : Integer;
BlockEventName : String;
// LockEventName : String;
FBytesRead : Integer;
{$IFDEF MSWINDOWS}
BlockEvent : THandle;
CS : TRTLCriticalSection;
{$ENDIF}
public
constructor Create;
destructor Destroy; override;
function Read(var Buffer; Count: Longint): Longint; override;
procedure Reset;
function Seek(Offset: Longint; Origin: Word): Longint; overload; override;
function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override;
function Write(const Buffer; Count: Longint): Longint; override;
property BytesInBuffer : Integer read FBytesInBuffer;
property BufferMode : TACSBufferMode read FBufferMode write FBufferMode;
end;
implementation
constructor TACSCustomInput.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Fposition := 0;
end;
destructor TACSCustomInput.Destroy;
begin
inherited Destroy;
end;
procedure TACSThread.Execute;
var
DoneThread : TACSVerySmallThread;
ParentComponent : TACSCustomOutput;
Res : Boolean;
begin
ParentComponent := TACSCustomOutput(Parent);
while not Terminating do
// if not bSuspend then
begin
EnterCriticalSection(CS);
if Delay > 5 then sleep(Delay);
try
if ParentComponent.Progress <> ParentComponent.CurProgr then
begin
ParentComponent.CurProgr := ParentComponent.Progress;
if Assigned(ParentComponent.FOnProgress) then Synchronize(CallOnProgress);
end;
Res := ParentComponent.DoOutput(Stop);
if Stop or (not Res) then
begin
Stop := False;
ParentComponent.WhenDone;
// This ensures that OnDone event is called outside this thread
DoneThread := TACSVerySmallThread.Create(True);
DoneThread.Sender := ParentComponent;
DoneThread.FOnDone := ParentComponent.FOnDone;
DoneThread.FreeOnTerminate := True;
Stop := True;
Res := True;
DoneThread.Resume;
//Dont Suspend the thread maybe Stop has been set to false during OnDone
//check this first
if (Stop or (not Res)) then
Self.Suspend;
end;
except
on E : Exception do
begin
Stop := False;
HandleException(E);
// if bSuspend then
Self.Suspend;
end;
end;
LeaveCriticalSection(CS);
end;
//DoOutput(True); // Why I'm doing this? I don't remember :-)
Terminating := False;
end;
constructor TACSCustomOutput.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Thread := TACSThread.Create;
Thread.Parent := Self;
// Thread.DoOutput := Self.DoOutput;
// Thread.FOnDone := Self.WhenDone;
Thread.FreeOnTerminate := True;
Thread.HandleException := HandleThreadException;
{$IFDEF MSWINDOWS}
SetSuspend(True);
InitializeCriticalSection(Thread.CS);
{$ENDIF}
end;
destructor TACSCustomOutput.Destroy;
begin
// if Thread.Suspended then
// Thread.Resume;
if not Thread.Suspended then
Thread.Terminating := True;
while Thread.Terminating do;
{$IFDEF MSWINDOWS}
DeleteCriticalSection(Thread.CS);
{$ENDIF}
inherited Destroy;
end;
procedure TACSCustomOutput.WhenDone;
begin
if not Busy then Exit;
CanOutput := False;
Done;
Busy := False;
end;
procedure TACSCustomOutput.Run;
begin
if Busy then raise EACSException.Create(strBusy);
if not Assigned(FInput) then raise EACSException.Create(strInputnotAssigned);
InputLock := False;
if not Thread.Suspended then Thread.Suspend;
try
Prepare;
Busy := True;
Thread.Stop := False;
CanOutput := True;
if Thread.Suspended then Thread.Resume;
except
on E : Exception do HandleThreadException(E);
end;
end;
procedure TACSCustomOutput.Stop;
begin
Thread.Stop := True;
end;
function TACSCustomOutput.GetStatus : TACSOutputStatus;
begin
if Busy then
begin
if Self.Thread.Suspended then Result := tosPaused
else Result := tosPlaying;
end else Result := tosIdle;
end;
procedure TACSCustomOutput.SetPriority(Priority : TTPriority);
begin
Thread.Priority := Priority;
end;
function TACSCustomOutput.GetPriority:TTPriority;
begin
Result := Thread.Priority;
end;
procedure TACSCustomOutput.SetInput(vInput : TACSCustomInput);
var
OldInput, NewInput : TACSCustomInput;
begin
if Busy then
begin
NewInput := vInput;
NewInput.Init;
OldInput := FInput;
while InputLock do;
InputLock := True;
FInput := NewInput;
InputLock := False;
OldInput.Flush;
end else
FInput := vInput;
end;
function TACSCustomOutput.GetProgress : real;
begin
if not Assigned(Finput) then
begin
Result := 0;
Exit;
end;
case Finput.Size of
0: Result := 0;
-1: Result := -1;
else Result := (FInput.Position/FInput.Size)*100;
end;
end;
procedure TACSCustomOutput.Pause;
begin
Thread.DoPause;
end;
procedure TACSCustomOutput.Resume;
begin
Thread.DoResume;
end;
function TACSCustomOutput.GetSuspend : Boolean;
begin
Result := Thread.Suspended;
end;
procedure TACSCustomOutput.SetSuspend(v : Boolean);
begin
Thread.Suspended:=v;
end;
constructor TACSStreamedInput.Create;
begin
inherited Create(AOwner);
FSeekable := True;
end;
function TACSCustomFileIn.GetBPS : Integer;
begin
if FSeekable then
begin
OpenFile;
Result := FBPS;
CloseFile;
end else Result := FBPS;
end;
function TACSCustomFileIn.GetCh : Integer;
begin
if FSeekable then
begin
OpenFile;
Result := FChan;
CloseFile;
end else Result := FChan;
end;
function TACSCustomFileIn.GetSR : Integer;
begin
if FSeekable then
begin
OpenFile;
Result := FSR;
CloseFile;
end else Result := FSR;
end;
function TACSCustomFileIn.GetTime : Integer;
begin
if FSeekable then
begin
OpenFile;
Result := FTime;
CloseFile;
end else Result := FTime;
end;
function TACSCustomFileIn.GetValid : Boolean;
begin
if (not FStreamAssigned) and (FileName = '') then
begin
Result := False;
end else
if FSeekable then
begin
OpenFile;
Result := FValid;
CloseFile;
end else Result := FValid;
end;
procedure TACSCustomFileIn.Init;
begin
if Busy then raise EACSException.Create(strBusy);
if not FStreamAssigned then
if FFileName = '' then raise EACSException.Create(strFilenamenotassigned);
OpenFile;
if StartSample <> 0 then Seek(StartSample);
if (StartSample <> 0) or (FEndSample <> -1) then
begin
FSize := FEndSample - FStartSample;
if FEndSample = -1 then FSize := FSize + FTotalSamples + 1;
FSize := FSize*(BitsPerSample shr 3)*FChan;
end;
FBusy := True;
BufStart := 1;
BufEnd := 0;
FPosition := 0;
end;
procedure TACSCustomFileIn.Flush;
begin
CloseFile;
FBusy := False;
end;
procedure TACSCustomFileIn.Jump(Offs : real);
begin
FOffset := Offs;
end;
function TACSCustomOutput.GetTE : Integer;
begin
if not Assigned(FInput) then
Result := 0
else
Result := Round(FInput.Position/((FInput.BitsPerSample shr 3) *FInput.Channels*FInput.SampleRate));
end;
function TACSCustomOutput.GetDelay : Integer;
begin
if Assigned(Thread) then Result := Thread.Delay
else Result := 0;
end;
procedure TACSCustomOutput.SetDelay(Value : Integer);
begin
if Assigned(Thread) then
if Value <= 100 then Thread.Delay := Value;
end;
function TACSCustomInput.GetBPS: Integer;
begin
Result := -1;
end;
function TACSCustomInput.GetCh: Integer;
begin
Result := -1;
end;
function TACSCustomInput.GetSR: Integer;
begin
Result := -1;
end;
function TACSCustomInput.GetTotalTime : real;
begin
Result := 0; // Default result for the streams.
end;
procedure TACSCustomInput.SetBufferSize(S: Integer);
begin
if Busy then
raise EACSException.Create(strBusy);
setlength(FBuffer,S);
end;
function TACSCustomInput.GetBufferSize: Integer;
begin
Result := length(FBuffer);
end;
function TACSCustomFileIn.GetTotalTime : real;
begin
OpenFile;
Result := 0;
if (SampleRate = 0) or (Channels = 0) or (BitsPerSample = 0) then Exit;
Result := Size/(SampleRate*Channels*(BitsPerSample shr 3));
CloseFile;
end;
procedure TACSStreamedInput.SetStream;
begin
FStream := aStream;
if FStream <> nil then FStreamAssigned := True
else FStreamAssigned := False;
end;
procedure TACSStreamedOutput.SetStream;
begin
FStream := aStream;
if FStream <> nil then FStreamAssigned := True
else FStreamAssigned := False;
end;
procedure TACSCustomOutput.Notification(AComponent: TComponent; Operation: TOperation);
begin
// Remove the following two lines if they cause troubles in your IDE
if (AComponent = FInput) and (Operation = opRemove )
then Input := nil;
inherited Notification(AComponent, Operation);
end;
procedure TACSCustomInput.Reset;
begin
try
Flush;
except
end;
FBusy := False;
end;
procedure TACSCustomOutput.HandleThreadException(E : Exception);
var
Conv : TACSCustomConverter;
begin
InputLock := False;
if Status <> tosIdle then
begin
try
if FInput is TACSCustomConverter then
begin
Conv := FInput as TACSCustomConverter;
Conv.UnlockInput;
end;
except
end;
try
Done;
except
end;
end;
CanOutput := False;
Busy := False;
if Assigned(FOnThreadException) then FOnThreadException(Self, E);
end;
procedure TACSCustomOutput.SetBufferSize(S : Integer);
begin
if Busy then
raise EACSException.Create(strBusy);
FBufferSize := S;
end;
procedure TACSCustomFileIn.Reset;
begin
inherited Reset;
FOpened := 0;
end;
constructor TACSCustomFileOut.Create;
begin
inherited Create(AOwner);
{$IFDEF LINUX}
FAccessMask := $1B6; // rw-rw-rw-
{$ENDIF}
end;
procedure TACSCustomFileOut.SetFileMode;
begin
FFileMode := foRewrite;
end;
procedure TACSCustomConverter.Notification;
begin
// Remove the following two lines if they cause troubles in your IDE
if (AComponent = FInput) and (Operation = opRemove )
then Input := nil;
inherited Notification(AComponent, Operation);
end;
procedure TACSCustomConverter.SetInput(aInput : TACSCustomInput);
var
OldInput, NewInput : TACSCustomInput;
begin
if aInput = Self then Exit;
if Busy then
begin
NewInput := aInput;
NewInput.Init;
OldInput := FInput;
while InputLock do;
InputLock := True;
FInput := NewInput;
InputLock := False;
OldInput.Flush;
end else
FInput := aInput;
end;
procedure TACSCustomConverter.UnlockInput;
var
Conv : TACSCustomConverter;
begin
InputLock := False;
if Assigned(FInput) then
if FInput is TACSCustomConverter then
begin
Conv := FInput as TACSCustomConverter;
Conv.UnlockInput;
end;
end;
function TACSCustomFileIn.SetStartTime(Minutes, Seconds : Integer) : Boolean;
var
Sample : Integer;
begin
Result := False;
if not FSeekable then Exit;
OpenFile;
CloseFile;
Sample := (Minutes*60+Seconds)*FSR;
if Sample > FTotalSamples then Exit;
FStartSample := Sample;
Result := True;
end;
function TACSCustomFileIn.SetEndTime(Minutes, Seconds : Integer) : Boolean;
var
Sample : Integer;
begin
Result := False;
if not FSeekable then Exit;
OpenFile;
CloseFile;
Sample := (Minutes*60+Seconds)*FSR;
if Sample > FTotalSamples then Exit;
FEndSample := Sample;
Result := True;
end;
constructor TACSCustomFileIn.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FStartSample := 0;
FEndSample := -1;
end;
procedure TACSVerySmallThread.Execute;
begin
Synchronize(CallOnDone);
end;
procedure TACSVerySmallThread.CallOnDone;
begin
if Assigned(FOnDone) then FOnDone(Sender);
end;
procedure TACSThread.CallOnProgress;
var
ParentComponent : TACSCustomOutput;
begin
ParentComponent := TACSCustomOutput(Parent);
ParentComponent.FOnProgress(ParentComponent);
end;
constructor TACSThread.Create;
begin
inherited Create(True);
end;
procedure TACSThread.DoPause;
begin
If not Suspended then Suspended := True;
end;
procedure TACSThread.DoResume;
begin
If Suspended then Suspended := False;
end;
{$IFDEF MSWINDOWS}
procedure TACSCustomOutput.Abort;
begin
TerminateThread(Thread.Handle, 0);
WhenDone;
end;
{$ENDIF}
constructor TACSBufferStream.Create;
begin
inherited Create;
{$IFDEF MSWINDOWS}
BlockEventName := 'Block' + IntToStr(LongWord(Self)); // this way we guarantee that the name is unique
BlockEvent := CreateEvent(nil, True, False, @BlockEventName[1]);
InitializeCriticalSection(CS);
{$ENDIF}
end;
destructor TACSBufferStream.Destroy;
begin
{$IFDEF MSWINDOWS}
DeleteCriticalSection(CS);
CloseHandle(BlockEvent);
{$ENDIF}
inherited Destroy;
end;
function TACSBufferStream.Write(const Buffer; Count: Longint): Longint;
var
addr : Pointer;
S1, S2 : Integer;
begin
if WriteCur >= ReadCur then
begin
{$IFDEF MSWINDOWS}
EnterCriticalSection(CS);
{$ENDIF}
S1 := STREAM_BUFFER_SIZE-WriteCur;
if (Count <= S1) then
begin
Move(Buffer, Buff[WriteCur], Count);
Inc(WriteCur, Count);
Inc(FBytesInBuffer, Count);
Result := Count;
end else
begin
Move(Buffer, Buff[WriteCur], S1);
addr := Pointer(Integer(@Buffer) + S1);
S2 := Count - S1;
if S2 > ReadCur then
S2 := ReadCur;
Move(addr^, Buff[0], S2);
WriteCur := S2;
Inc(FBytesInBuffer, S1+S2);
Result := S1+S2;
end;
end else
begin
S2 := ReadCur-WriteCur;
if Count < S2 then
S2 := Count;
Move(Buffer, Buff[WriteCur], S2);
Inc(WriteCur, S2);
Inc(FBytesInBuffer, S2);
Result := S2;
end;
{$IFDEF MSWINDOWS}
if Result <> 0 then
if FBufferMode = bmBlock then PulseEvent(BlockEvent);
LeaveCriticalSection(CS);
{$ENDIF}
end;
function TACSBufferStream.Read(var Buffer; Count : Integer) : Integer;
var
addr : Pointer;
S1, S2 : Integer;
begin
{$IFDEF MSWINDOWS}
if ReadCur = WriteCur then
if FBufferMode = bmBlock then
begin
WaitForSingleObject(BlockEvent, INFINITE);
ResetEvent(BlockEvent);
end;
EnterCriticalSection(CS);
{$ENDIF}
if ReadCur <= WriteCur then
begin
S2 := WriteCur - ReadCur;
if Count < S2 then S2 := Count;
Move(Buff[ReadCur], Buffer, S2);
Inc(ReadCur, S2);
Dec(FBytesInBuffer, S2);
Result := S2;
end else
begin
S1 := STREAM_BUFFER_SIZE-ReadCur;
if Count <= S1 then
begin
Move(Buff[ReadCur], Buffer, Count);
Inc(ReadCur, Count);
Dec(FBytesInBuffer, Count);
Result := Count;
end else
begin
S2 := WriteCur;
if Count - S1 < S2 then S2 := Count;
Move(Buff[ReadCur], Buffer, S1);
addr := Pointer(Integer(@Buffer) + S1);
Move(Buff[0], addr^, S2);
ReadCur := S2;
Dec(FBytesInBuffer, S1+S2);
Result := S1+S2;
end;
end;
Inc(FBytesRead, Result);
{$IFDEF MSWINDOWS}
LeaveCriticalSection(CS);
{$ENDIF}
end;
procedure TACSBufferStream.Reset;
begin
{$IFDEF MSWINDOWS}
EnterCriticalSection(CS);
{$ENDIF}
ReadCur := 0;
WriteCur := 0;
FBytesRead := 0;
{$IFDEF MSWINDOWS}
SetEvent(BlockEvent);
LeaveCriticalSection(CS);
{$ENDIF}
end;
// the following property is implemented 'cause tstreams position property uses them
function TACSBufferStream.Seek(Offset: Longint; Origin: Word) : Integer;
begin
if (Offset = 0) and (Origin = 0) then
Result := FBytesRead
else
raise EACSException.Create(strSeeknotimplemented);
end;
function TACSBufferStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
if (Offset = 0) and (Origin = soCurrent) then
Result := FBytesRead
else
raise EACSException.Create(strSeeknotimplemented);
end;
{ TACSFileInfo }
function TACSFileInfo.GetStringTag(Idx : string): TACSFileTag;
begin
end;
procedure TACSFileInfo.ReadFromFile;
begin
end;
procedure TACSFileInfo.SetStringTag(Idx : string; const AValue: TACSFileTag);
begin
end;
procedure TACSFileInfo.SaveToFile;
begin
end;
{ TACSFileTag }
function TACSFileTag.GetName: string;
begin
end;
function TACSFileTag.AsString: string;
begin
end;
function TACSFileTag.Streamable: Boolean;
begin
Result := False;
end;
procedure TACSFileTag.SaveToStream(Stream: TStream);
begin
end;
procedure TACSFileTag.LoadFromStream(Stream: TStream);
begin
end;
end.