{*************************************************************************
Unit: Audio.pas
Description: TAudio component for accessing waveform devices
Accessed Units: mmSystem.pas
Compiler: Delphi 1.02 (16 bit) and Delphi 3 (32 bit)
I/O: waveform device via Windows multimedia API
References - mmSystem.hlp (Win16) and mm.hlp (Win32)
- UDDF, Nov 1997 article "Low Level WaveIn Routine" by John Mertus
- UNDU, Sept 1997 article "Playing and Recording Sound in Delphi" by Darryl Gove
- Delphi Bug List, waveInClose error in mmSystem by Reinier Sterkenburg
- TJW's web site, "The Wave File Format" by Timothy J Weber
- Colin's web site, Mixer Control by Colin Wilson
Conditions of usage Freeware, use at own risk. Please report faults or comments to the author
Author Mr Hakan Bergzen, hakan_bergzen@hotmail.com
Ver Date Made by Change
1.0 980106 Hakan Bergzen (HBn) Basic version for Win16
1.0 980117 HBn Converted for Win32
2.0 980412 HBn Added wave file support
3.0 980702 HBn Corrected errors, reworked structure and
added functions
3.0 980716 HBn Added Mixer Control capability (32bit only)
3.1 980725 HBn Added wave_mapper, changed PlayFile procedure
and changed Mixer procedures
3.2 980823 HBn Extended RecordToFile functionality,
corrected errors
3.3x 9809xx-9811xx HBn Non-released test versions
4.0 981122 HBn Fixed consecutive playing of files,
stop while playing,
callback_function under WindowsNT,
modified PlayFile for various wav file formats,
Mixer functions internally,
added Meter reading,
OnMixerChange event,
Mixer status in Query,
faster start-up time in Play when using Left and Right TStreams,
TrigLevel and Split (in assembler),
less RAM required (PlayStream changed from
MemoryStream to FileStream),
fewer user instructions (no more need to use Open
and Close from the application)
4.1 990322 HBn Removed faults causing Delphi/Windows to crash
in some installations
**************************************************************************}
Unit Audio;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Forms, Classes,
mmSystem;
type
TChannels = (Mono, Stereo);
TBPS = (_8,_16);
const
DefaultAudioDeviceID = WAVE_MAPPER;
No_Buffers = 4;
ChannelsDefault = Mono;
BPSDefault = _8;
SPSDefault = 11025;
NoSamplesDefault = 8192;
{$IFDEF WIN32}
DefaultMixerDeviceID = 0;
Ver = '4.1 (32bit)';
{$ELSE}
Ver = '4.1 (16bit)';
{$ENDIF}
type
TNotifyAudioRecordEvent = procedure(Sender: TObject; LP,RP: Pointer; BufferSize: Word) of object;
TNotifyBufferPlayedEvent = procedure(Sender: TObject) of object;
TNotifyPlayedEvent = procedure(Sender: TObject) of object;
{$IFDEF WIN32}
TNotifyMixerChange = procedure(Sender:TObject;Destination,Source: Word) of object;
{$ENDIF}
TAudio = class;
{$IFDEF WIN32}
ValuesArray = array [0..1] of integer;
PMixDetails = ^TMixDetails;
TMixDetails = record
Destination,Source : Word;
Name : string;
VolControlID,MuteControlID, MeterControlID : dword;
Left, Right, Meter : Word;
CtrlType : Word;
Mute, Mono, Speakers, Available : boolean;
Next:PMixDetails;
end;
TMixerSettings = class(TPersistent)
private
FAudio : TAudio;
MixerHandle : HMIXER;
MixerStart : PMixDetails;
MixerReady : boolean;
MixerCallbackHandle : HWND;
FList : TStrings;
procedure InitiateControlDetails(var details:TMixerControlDetails;
ControlID,Channels:dword; pvalues:pointer);
function GetMixerSettings(MixerDeviceID:integer):boolean;
procedure MixerCallBack(var Msg:TMessage);
public
function GetName(Dest,Source:Word):string;
function SetControl(Dest,Source:Word; LeftVolume,RightVolume:Word; Mute:boolean):boolean;
function GetControl(Dest,Source:Word; var LeftVolume,RightVolume:Word; var Mute:boolean; var CtrlType:byte):boolean;
function GetMeter(Dest,Source:Word; var LeftVolume,RightVolume:dword):boolean;
function GetSources(Dest:Word):TStrings;
function GetDestinations:TStrings;
function Query(var Product,Formats:string):boolean;
end;
{$ENDIF}
TAudioSettings = class(TPersistent)
private
FAudio : TAudio;
pWaveHeader : array [0..No_Buffers-1] of PWAVEHDR;
pWaveBuffer : array [0..No_Buffers-1] of pointer;
pExtraBuffer : array [0..No_Buffers-1] of pointer; {Used to carry Right samples during Split channels}
ForwardIndex : Integer;
ReturnIndex : Integer;
ActiveBuffers : Integer;
DeviceOpen : Boolean;
private
FChannels : TChannels;
FBPS : TBPS;
FSPS : Word;
FNoSamples : Word;
{$IFDEF WIN32}
pWaveFmt : pWaveFormatEx;
{$ELSE}
pWaveFmt : pPCMWaveFormat;
{$ENDIF}
WaveBufSize : Word;
procedure SetChannels(Value:TChannels);
procedure SetBPS(Value:TBPS);
procedure SetSPS(Value:Word);
procedure InitWaveHeaders;
function AllocateMemory: Boolean;
procedure FreeMemory;
public
Active : Boolean;
published
property BitsPerSample: TBPS read FBPS write SetBPS default BPSDefault;
property Channels: TChannels read FChannels write SetChannels default ChannelsDefault;
property SampleRate: Word read FSPS write SetSPS default SPSDefault;
end;
PRecorder = ^TRecorder;
TRecorder = class(TAudioSettings)
private
WaveIn : HWAVEIN;
FPause : Boolean;
FSplit : Boolean;
FTrigLevel : Word;
FTriggered : Boolean;
RecStream : TFileStream;
RecToFile : Boolean;
AddNextInBufferHandle : hWnd;
procedure AddNextInBuffer2(var Msg: TMessage);
function AddNextInBuffer: Boolean;
procedure SetTrigLevel(Value:Word);
function TestTrigger(StartPtr:pointer; Size:Word):boolean;
procedure SetSplit(Value:Boolean);
procedure Split(var LP,RP:pointer; var Size:Word);
procedure GetError(iErr : Integer; Additional:string);
procedure SetNoSamples(Value:Word);
function Open : boolean;
function Close : boolean;
public
function Start : boolean;
function Stop : boolean;
procedure Pause;
procedure Restart;
procedure RecordToFile(FileName:string; LP,RP:TStream);
published
property NoSamples: Word read FNoSamples write SetNoSamples default NoSamplesDefault;
property SplitChannels: Boolean read FSplit write SetSplit default false;
property TrigLevel: Word read FTrigLevel write SetTrigLevel default 128;
property Triggered: Boolean read FTriggered write FTriggered default true;
end;
PPlayer = ^TPlayer;
TPlayer = class(TAudioSettings)
private
WaveOut : HWAVEIN;
FNoOfRepeats : Word;
ReadPlayStreamPos : LongInt;
PlayStream : TFileStream;
FPlayFile : boolean;
PlayFileStream : TFileStream;
FOldChannels : TChannels;
FOldBPS : TBPS;
FOldSPS : Word;
FinishedPlaying : boolean;
AddNextOutBufferHandle : hWnd;
CloseHandle : hWnd;
procedure AddNextOutBuffer2(var Msg: TMessage);
procedure Close2(var Msg: TMessage);
function Open : boolean;
procedure GetError(iErr : Integer; Additional:string);
function AddNextOutBuffer:longint;
public
procedure SetVolume(LeftVolume,RightVolume:Word);
procedure GetVolume(var LeftVolume,RightVolume:Word);
procedure Play(LP,RP:TStream; NoOfRepeats:Word);
procedure Stop;
procedure Pause;
procedure Reset;
procedure Restart;
procedure BreakLoop;
function PlayFile(FileName:string; NoOfRepeats:Word):boolean;
published
end;
TAudio = class(TComponent)
private
FVersion : string;
FDeviceID : Integer;
FSepCtrl : Boolean;
procedure SetDeviceID(Value:Integer);
procedure SetVersion(Value:string);
private
FOnAudioRecord : TNotifyAudioRecordEvent;
FRecorder : TRecorder;
private
FOnBufferPlayed : TNotifyBufferPlayedEvent;
FOnPlayed : TNotifyPlayedEvent;
FPlayer : TPlayer;
private
FWindowHandle : HWND;
WaveFmtSize : Integer;
{$IFDEF WIN32}
FMixerDeviceID : Integer;
FOnMixerChange : TNotifyMixerChange;
procedure SetMixerDeviceID(Value:Integer);
{$ENDIF}
procedure AudioCallBack(var Msg: TMessage);export;
public
{$IFDEF WIN32}
Mixer : TMixerSettings;
{$ENDIF}
ErrorMessage : string;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function Query(var Product,Formats:string):boolean;
published
property AudioDeviceID: Integer read FDeviceID write SetDeviceID default DefaultAudioDeviceID;
{$IFDEF WIN32}
property MixerDeviceID: Integer read FMixerDeviceID write SetMixerDeviceID default DefaultMixerDeviceID;
{$ENDIF}
property SeparateCtrls: Boolean read FSepCtrl write FSepCtrl default false;
property Player: TPlayer read FPlayer write FPlayer;
property Recorder: TRecorder read FRecorder write FRecorder;
property Version: string read FVersion write SetVersion;
property OnRecord: TNotifyAudioRecordEvent read FOnAudioRecord write FOnAudioRecord;
property OnBufferPlayed: TNotifyBufferPlayedEvent read FOnBufferPlayed write FOnBufferPlayed;
property OnPlayed: TNotifyPlayedEvent read FOnPlayed write FOnPlayed;
{$IFDEF WIN32}
property OnMixerChange:TNotifyMixerChange read FOnMixerChange write FOnMixerChange;
{$ENDIF}
end;
{$IFDEF WIN32}
{$ELSE}
function CorrectedwaveInClose(hWaveIn: HWaveIn): Word;
{$ENDIF}
procedure Register;
implementation
{$IFDEF WIN32}
{$ELSE}
function CorrectedwaveInClose; external 'MMSYSTEM' index 505;
{$ENDIF}
{------------- WinAPI CallBack routines --------------------------------}
{ Callback routine used for CALLBACK_WINDOW in waveInOpen and waveOutOpen }
procedure TAudio.AudioCallBack(var Msg: TMessage);
var LP,RP:pointer;
Size:Word;
begin
case Msg.Msg of
mm_wim_OPEN : FRecorder.Active:=true;
mm_wim_CLOSE : FRecorder.Active:=false;
mm_wim_DATA : begin
if FRecorder.Active then begin
LP:=FRecorder.pWaveBuffer[FRecorder.ReturnIndex Mod No_Buffers];
RP:=FRecorder.pExtraBuffer[FRecorder.ReturnIndex Mod No_Buffers];
Size:=FRecorder.pWaveHeader[FRecorder.ReturnIndex Mod No_Buffers]^.dwBytesRecorded;
if (not(FRecorder.FPause) and FRecorder.TestTrigger(LP,Size)) then begin
if FRecorder.RecToFile then FRecorder.RecStream.write(LP^,Size);
if Assigned(FOnAudioRecord) then begin
if FRecorder.FSplit then begin
FRecorder.Split(LP,RP,Size);
FOnAudioRecord(Self,LP,RP,Size);
end else FOnAudioRecord(Self,LP,nil,Size);
end;
end;
if (Size>0) then begin
PostMessage(FRecorder.AddNextInBufferHandle,wim_DATA,0,0);
{ FRecorder.AddNextInBuffer; }
FRecorder.ReturnIndex:=(FRecorder.ReturnIndex+1) mod No_Buffers;
end;
end;
end;
mm_wom_OPEN : FPlayer.Active:=true;
mm_wom_CLOSE : FPlayer.Active:=false;
mm_wom_DONE : if FPlayer.Active then begin
if (FPlayer.ForwardIndex=FPlayer.ReturnIndex) then begin
if not(FPlayer.FinishedPlaying) then begin
FPlayer.FinishedPlaying:=true;
PostMessage(FPlayer.CloseHandle,mm_wom_CLOSE,0,0);
end;
end else begin
if Assigned(FOnBufferPlayed) then FOnBufferPlayed(Self);
PostMessage(FPlayer.AddNextOutBufferHandle,wom_DONE,0,0);
FPlayer.ReturnIndex:=(FPlayer.ReturnIndex+1) mod No_Buffers;
dec(FPlayer.ActiveBuffers);
end;
end;
wm_QueryEndSession : Destroy; { only called if Callback_Window is used }
end;
end;
{------------- Internal/Private routines -------------------------------}
procedure TAudioSettings.InitWaveHeaders;
var
i : Integer;
begin
for i:=0 to No_Buffers-1 do begin
pWaveHeader[i]^.lpData:=pWaveBuffer[i];
pWaveHeader[i]^.dwBufferLength:=WaveBufSize;
pWaveHeader[i]^.dwBytesRecorded:=0;
pWaveHeader[i]^.dwUser:=0;
pWaveHeader[i]^.dwFlags:=0;
pWaveHeader[i]^.dwLoops:=0;
pWaveHeader[i]^.lpNext:=nil;
pWaveHeader[i]^.reserved:=0;
end;
end;
function TAudioSettings.AllocateMemory: Boolean;
var
i : Integer;
begin
pWaveFmt:=nil;
try
GetMem(pWaveFmt,FAudio.WaveFmtSize);
except
FAudio.ErrorMessage:='Not enough memory to allocate WaveFormat';
Result:=false;
Exit;
end;
if FBPS=_8 then pWaveFmt^.wBitsPerSample :=8
else pWaveFmt^.wBitsPerSample :=16;
{$IFDEF WIN32}
pWaveFmt^.cbSize:=0;
with pWaveFmt^ do begin
{$ELSE}
with pWaveFmt^.wf do begin
{$ENDIF}
wFormatTag:=WAVE_FORMAT_PCM;
if FChannels=Mono then nChannels:=1
else nChannels:=2;
nSamplesPerSec:=FSPS;
{ BlockAlign : e.g. 16-bit stereo PCM => 4 = 2 channels x 2 bytes/channel }
if FBPS=_8 then nBlockAlign:=(8 div 8)*nChannels
else nBlockAlign:=(16 div 8)*nChannels;
nAvgBytesPerSec:=nSamplesPerSec*nBlockAlign;
WaveBufSize:=FNoSamples*nBlockAlign;
end;
for i:=0 to No_Buffers-1 do begin
pWaveHeader[i]:=nil;
try
GetMem(pWaveHeader[i],sizeof(TWAVEHDR));
except
FAudio.ErrorMessage:='Not enough memory to allocate WaveHeader';
Result:=false;
Exit;
end;
pWaveBuffer[i]:=nil;
pExtraBuffer[i]:=nil;
try
GetMem(pWaveBuffer[i],WaveBufSize);
GetMem(pExtraBuffer[i],(WaveBufSize div 2));
except
FAudio.ErrorMessage:='Not enough memory to allocate Wave Buffer';
Result:=false;
Exit;
end;
pWaveHeader[i]^.lpData:=pWaveBuffer[i];
end;
Result:=true;
end;
procedure TAudioSettings.FreeMemory;
var
i : Integer;
begin
if (pWaveFmt = nil) then Exit
else begin
FreeMem(pWaveFmt,FAudio.WaveFmtSize);
pWaveFmt:=nil;
end;
for i:=0 to No_Buffers-1 do begin
if (pWaveBuffer[i]<>nil) then FreeMem(pWaveBuffer[i],WaveBufSize);
pWaveBuffer[i]:=nil;
if (pExtraBuffer[i]<>nil) then FreeMem(pExtraBuffer[i],(WaveBufSize div 2));
pExtraBuffer[i]:=nil;
if (pWaveHeader[i]<>nil) then FreeMem(pWaveHeader[i],sizeof(TWAVEHDR));
pWaveHeader[i]:=nil;
end;
end;
function TRecorder.TestTrigger(StartPtr:pointer; Size:Word):boolean;
var
{$IFDEF WIN32}
i : longint;
j :boolean;
k : Word;
{$ELSE}
BytesCounted : Word;
pb : ^byte;
ip : ^smallint;
{$ENDIF}
begin
{$IFDEF WIN32}
if not(FTriggered) and (Size>0) then begin
j:=FTriggered;
i:=Size;
k:=FTrigLevel;
if FBPS=_8 then begin
asm
mov eax,StartPtr
mov ecx,i
mov edx,0
@trig8:
mov dl,[eax]
cmp dx,k
jge @out8
add eax,1
pop ecx
loop @trig8
jmp @out88
@out8:
mov j,1
@out88:
end;
end else begin
asm
mov eax,StartPtr
mov ecx,i
shr ecx,1
mov edx,0
@trig16:
mov dx,[eax]
cmp dx,k
jge @out16
add eax,2
loop @trig16
jmp @out16a
@out16:
mov j,1
@out16a:
end;
end;
FTriggered:=j;
end;
{$ELSE}
if not(FTriggered) and (Size>0) then begin
if FBPS=_8 then begin
pb:=StartPtr;
repeat
if pb^>TrigLevel then FTriggered:=true;
inc(pb);
inc(BytesCounted);
until (BytesCounted>=Size) or FTriggered;
end else begin
ip:=StartPtr;
repeat
if ip^>TrigLevel then FTriggered:=true;
inc(ip);
inc(BytesCounted,2);
until (BytesCounted>=Size) or FTriggered;
end;
end;
{$ENDIF}
Result:=FTriggered;
end;
procedure TRecorder.Split(var LP,RP:pointer; var Size:Word);
var
i : longint;
lb,rb,pb : ^byte;
begin
if (Size>0) then begin
Size:=Size div 2;
lb:=LP; rb:=RP;
pb:=LP;
{$IFDEF WIN32}
i:=Size;
if FBPS=_8 then begin
asm
mov eax,lb
mov ecx,i
mov edx,rb
@split8:
push ecx
mov ecx,pb
mov cx,[ecx]
mov [eax],cl
mov [edx],ch
add dword ptr [pb],2
add eax,1
add edx,1
pop ecx
loop @split8
end;
end else begin
asm
mov eax,lb
mov ecx,i
shr ecx,1
mov edx,rb
@split16:
push ecx
mov ecx,pb
mov ecx,[ecx]
mov [eax],cx
shr ecx,16
mov [edx],cx
add dword ptr [pb],4
add eax,2
add edx,2
pop ecx
loop @split16
end;
end;
{$ELSE}
{ The lines below are replaced with the asm routine above
starting from (and including) i:=Size }
if FBPS=_8 then begin
for i:=1 to Size do begin
lb^:=pb^; inc(lb);inc(pb);
rb^:=pb^; inc(rb);inc(pb);
end;
end else begin
for i:=1 to (Size div 2) do begin
lb^:=pb^; inc(lb);inc(pb);
lb^:=pb^; inc(lb);inc(pb);
rb^:=pb^; inc(rb);inc(pb);
rb^:=pb^; inc(rb);inc(pb);
end;
end;
{$ENDIF}
end;
end;
procedure TRecorder.AddNextInBuffer2(var Msg: TMessage);
begin
if (Msg.Msg=wim_DATA) and DeviceOpen then AddNextInBuffer;
end;
function TRecorder.AddNextInBuffer: Boolean;
var
iErr : Integer;
begin
iErr:=waveInAddBuffer(WaveIn, pwaveheader[ForwardIndex], sizeof(TWAVEHDR));
if (iErr<>0) then begin
Stop;
GetError(iErr,'Error adding input buffer');
Result:=false;
Exit;
end;
ForwardIndex:=(ForwardIndex+1) mod No_Buffers;
Result:=true;
end;
procedure TRecorder.GetError(iErr : Integer; Additional:string);
var
ErrorText : string;
pError : PChar;
begin
GetMem(pError,256*2); { make sure there is ample space if WideChar is used }
waveInGetErrorText(iErr,pError,255);
ErrorText:=StrPas(pError);
FreeMem(pError,256*2);
if length(ErrorText)=0 then FAudio.ErrorMessage:=Additional
else FAudio.ErrorMessage:=Additional+' '+ErrorText;
end;
procedure TPlayer.AddNextOutBuffer2(var Msg: TMessage);
begin
if (Msg.Msg=wom_DONE) and DeviceOpen then AddNextOutBuffer;
end;
function TPlayer.AddNextOutBuffer:longint;
var iErr:integer;
WritePos:Longint;
begin
if (PlayStream<>nil) then begin
FinishedPlaying:=false;
WritePos:=PlayStream.Position;
PlayStream.Position:=ReadPlayStreamPos;
Result:=PlayStream.Read(pwaveheader[ForwardIndex]^.lpData^,WaveBufSize);
if (Result=0) and (FNoOfRepeats>0) then begin
dec(FNoOfRepeats,1);
PlayStream.Position:=0;
Result:=PlayStream.Read(pwaveheader[ForwardIndex]^.lpData^,WaveBufSize);
end;
ReadPlayStreamPos:=PlayStream.Position;
PlayStream.Position:=WritePos;
if Result>0 then begin
pwaveheader[ForwardIndex]^.dwBufferLength:=Result;
pwaveheader[ForwardIndex]^.dwFlags:=0;
pwaveheader[ForwardIndex]^.dwLoops:=0;
iErr:=waveOutPrepareHeader(WaveOut,pWaveHeader[ForwardIndex],sizeof(TWAVEHDR));
if iErr<>0 then begin
GetError(iErr,'');
Exit;
end;
iErr:=waveOutWrite(WaveOut, pwaveheader[ForwardIndex], sizeof(TWAVEHDR));
if iErr<>0 then begin
GetError(iErr,'');
Exit;
end;
ForwardIndex:=(ForwardIndex+1) mod No_Buffers;
inc(ActiveBuffers);
end else begin
PlayStream.Free;
PlayStream:=nil;
end;
end else Result:=0;
end;
procedure TPlayer.GetError(iErr : Integer; Additional:string);
var
ErrorText : string;
pError : PChar;
begin
GetMem(pError,256*2); { make sure there is ample space if WideChar is used }
waveOutGetErrorText(iErr,pError,255);
ErrorText:=StrPas(pError);
FreeMem(pError,256*2);
if length(ErrorText)=0 then FAudio.ErrorMessage:=Additional
else FAudio.ErrorMessage:=Additional+' '+ErrorText;
end;
{$IFDEF WIN32}
{ Mixer Controls only available in the 32bit version }
procedure TMixerSettings.InitiateControlDetails(var details:TMixerControlDetails;
ControlID,Channels:dword; pvalues:pointer);
begin
details.cbStruct := sizeof (details);
details.dwControlID := ControlID;
details.cChannels := Channels;
details.cMultipleItems := 0;
details.cbDetails := sizeof (dword);
details.paDetails := pvalues;
end;
function TMixerSettings.SetControl(Dest,Source:Word; LeftVolume,RightVolume:Word; Mute:boolean):boolean;
var P:PMixDetails;
err : integer;
values : ValuesArray;
details : TMixerControlDetails;
begin
Result:=false;
P:=MixerStart;
if MixerReady then begin
while (P<>nil) do begin
if ((P^.Destination=Dest) and (P^.Source=Source)) then begin
if P^.VolControlID<65535 then begin
if P^.Mono then begin
InitiateControlDetails(details,P^.VolControlID,1,@values);
end else begin
InitiateControlDetails(details,P^.VolControlID,2,@values);
end;
values[0]:= LeftVolume;
values[1]:= RightVolume;
err := mixerSetControlDetails (MixerHandle, @details, MIXER_SETCONTROLDETAILSF_VALUE);
if err<>MMSYSERR_NOERROR then begin
FAudio.ErrorMessage:='Volume SetControlError in Mixer';
exit;
end;
end;
if P^.MuteControlID<65535 then begin
InitiateControlDetails(details,P^.MuteControlID,1,@values);
if Mute then values[0]:= 1
else values[0]:=0;
err := mixerSetControlDetails (MixerHandle, @details, MIXER_SETCONTROLDETAILSF_VALUE);
if err<>MMSYSERR_NOERROR then begin
FAudio.ErrorMessage:='Mute SetControlError in Mixer';
exit;
end else Result:=true;
end else Result:=true;
Exit;
end;
P:=P^.Next;
end;
end;
end;
function TMixerSettings.GetControl(Dest,Source:Word; var LeftVolume,RightVolume:Word;
var Mute:boolean; var CtrlType:byte):boolean;
var P:PMixDetails;
err : integer;
values : ValuesArray;
details : TMixerControlDetails;
begin
Result:=false;
P:=MixerStart;
if MixerReady then begin
while (P<>nil) do begin
if ((P^.Destination=Dest) and (P^.Source=Source)) then begin
CtrlType:=byte(P^.CtrlType);
if P^.Mono then InitiateControlDetails(details,P^.VolControlID,1,@values)
else InitiateControlDetails(details,P^.VolControlID,2,@values);
err := mixerGetControlDetails (MixerHandle, @details, MIXER_GETCONTROLDETAILSF_VALUE);
if err<>MMSYSERR_NOERROR then begin
FAudio.ErrorMessage:='Volume GetControlError in Mixer';
exit;
end;
LeftVolume:=values[0];
if P^.Mono then RightVolume:=LeftVolume
else RightVolume:=values[1];
InitiateControlDetails(details,P^.MuteControlID,1,@values);
err := mixerGetControlDetails (MixerHandle, @details, MIXER_GETCONTROLDETAILSF_VALUE);
if err<>MMSYSERR_NOERROR then begin
FAudio.ErrorMessage:='Mute GetControlError in Mixer';
exit;
end;
if values[0]=0 then Mute:=false
else Mute:=true;
Result:=true;
Exit;
end;
P:=P^.Next;
end;
end;
end;
function TMixerSettings.GetMeter(Dest,Source:Word; var LeftVolume,RightVolume:dword):boolean;
var P:PMixDetails;
err : integer;
values, val2: PMixerControlDetailsSigned;
details : TMixerControlDetails;
begin
Result:=false;
P:=MixerStart;
if MixerReady then begin
while (P<>nil) do begin
if ((P^.Destination=Dest) and (P^.Source=Source) and (P^.Meter>0)) then begin
GetMem(values, 2*SizeOf(TMixerControlDetailsSigned));
InitiateControlDetails(details,P^.MeterControlID,P^.Meter,values);
err := mixerGetControlDetails (MixerHandle, @details, MIXER_GETCONTROLDETAILSF_VALUE);
if err<>MMSYSERR_NOERROR then exit;
val2:=values;
LeftVolume:=val2^.lValue;
if P^.Meter=1 then RightVolume:=LeftVolume
else begin
inc(val2);
RightVolume:=val2^.lValue;
end;
Result:=true;
FreeMem(values, 2*SizeOf(TMixerControlDetailsSigned));
Exit;
end;
P:=P^.Next;
end;
end;
end;
function TMixerSettings.GetName(Dest,Source:Word):string;
var P:PMixDetails;
begin
Result:='';
if MixerReady then begin
P:=MixerStart;
while (P<>nil) do begin
if ((P^.Destination=Dest) and (P^.Source=Source)) then begin
Result:=P^.Name;
Exit;
end;
P:=P^.Next;
end;
end;
end;
function TMixerSettings.GetSources(Dest:Word):TStrings;
var P:PMixDetails;
begin
P:=MixerStart;
FList.Clear;
if MixerReady then begin
while P<>nil do begin
if (P^.Destination=Dest) then begin
if P^.Available then FList.Insert(P^.Source,P^.Name)
else FList.Insert(P^.Source,'');
end;
P:=P^.Next;
end;
end;
Result:=FList;
end;
function TMixerSettings.GetDestinations:TStrings;
var P:PMixDetails;
begin
P:=MixerStart;
FList.Clear;
if MixerReady then begin
while P<>nil do begin
if (P^.Source=0) then FList.Insert(P^.Destination,P^.Name);
P:=P^.Next;
end;
end;
Result:=FList;
end;
function TMixerSettings.Query(var Product,Formats:string):boolean;
var
PMix : PMixDetails;
i : integer;
begin
Result:=false;
Product:=''; Formats:='';
if MixerReady then begin
if (mixerGetNumDevs=0) then begin
Formats:='Mixer not present';
end else begin
PMix:=MixerStart;
if PMix<>nil then Product:=PMix.Name;
Formats:='Mixer devices present: '+IntToStr(mixerGetNumDevs)+'. DeviceID '+
IntToStr(FAudio.FMixerDeviceID)+' has:';
i:=0; PMix:=PMix^.Next;
while PMix<>nil do begin
if (PMix.Destination=i) then begin
Formats:=Formats+#13#10+PMix.Name+': ';
i:=i+1;
end else begin
Formats:=Formats+PMix.Name+', ';
end;
PMix:=PMix^.Next;
end;
Result:=true;
end;
end;
end;
procedure TMixerSettings.MixerCallBack(var Msg:TMessage);
var P : PMixDetails;
Found : boolean;
begin
if (Msg.Msg = MM_MIXM_CONTROL_CHANGE) and MixerReady then begin
if (Assigned(FAudio.OnMixerChange)) then begin
FAudio.OnMixerChange(Self,word(Msg.wParam),word(Msg.lParam));
Found:=false;
P:=MixerStart;
while (P<>nil) and not(Found) do begin
if (P^.VolControlID=Msg.lParam) or (P^.MuteControlID=Msg.lParam) then begin
Found:=true;
FAudio.OnMixerChange(Self,P^.Destination,P^.Source);
end;
P:=P^.Next;
end;
end;
end;
end;
function TMixerSettings.GetMixerSettings(MixerDeviceID:integer):boolean;
var
j, k, err : Integer;
caps : TMixerCaps;
lineInfo, connectionInfo : TMixerLine;
PMix:PMixDetails;
Data : ValuesArray;
speakers : boolean;
procedure UpdateLinkedList(Update:Word; var P:PMixDetails; Destination, Source : dword; Name : string;
ControlID : dword; Data : ValuesArray; Mono, Speakers:boolean);
var
TempDest,TempSource : word;
begin
if (P<>nil) or (Update=0) then begin
case Update of
0 : begin
new(P);
P^.Next:=nil; P^.Available:=false; P^.Mono:=false;
P^.Destination:=65535;
P^.Source:=65535;
P^.Name:=Name;
P^.Speakers:=Speakers;
P^.VolControlID:=65535; P^.Left:=0; P^.Right:=0;
P^.MuteControlID:=65535; P^.Mute:=false;
P^.MeterControlID:=65535; P^.Meter:=0;
P^.CtrlType:=0;
end;
1 : begin
TempDest:=P^.Destination; TempSource:=P^.Source;
new(P^.Next); P:=P^.Next;
P^.Next:=nil; P^.Available:=false; P^.Mono:=false;
if (word(Destination)<>TempDest) then begin
TempDest:=word(Destination);
TempSource:=0;
end else TempSource:=(TempSource+1) mod 65536;
P^.Destination:=TempDest; P^.Source:=TempSource;
P^.Name:=Name;
P^.Speakers:=Speakers;
P^.VolControlID:=65535; P^.Left:=0; P^.Right:=0;
P^.MuteControlID:=65535; P^.Mute:=false;
P^.MeterControlID:=65535; P^.Meter:=0;
P^.CtrlType:=128;
end;
2 : begin
if P^.MuteControlID=65535 then begin
P^.MuteControlID:=ControlID;
if Data[0]=0 then P^.Mute:=false
else P^.Mute:=true;
P^.Available:=true;
P^.CtrlType:=(P^.CtrlType and 127);
end;
end;
3 : begin
P^.VolControlID:=ControlID;
P^.Left:=Data[0];
if Mono then begin
P^.Mono:=true;
P^.CtrlType:=P^.CtrlType+64;
end else P^.Right:=Data[1];
P^.Available:=true;
end;
4 : begin
P^.MeterControlID:=ControlID;
if Mono then P^.Meter:=1
else P^.Meter:=2;
end;
end;
end;
end;
function GetControl(var PMixer:PMixDetails; MixLine:TMixerLine; speakers:boolean):boolean;
var err,j:integer;
mixerLineControls : TMixerLineControls;
p, controls : PMixerControl;
details : TMixerControlDetails;
values : ValuesArray;
begin
UpdateLinkedList(1,PMixer,MixLine.dwDestination,MixLine.dwSource,
StrPas(MixLine.szName),word(MixLine.dwComponentType),Data,false,speakers);
mixerLineControls.cbStruct := sizeof (mixerLineControls);
mixerLineControls.dwLineID := MixLine.dwLineID;
mixerLineControls.cControls := MixLine.cControls;
mixerLineControls.cbmxctrl := sizeof (TMixerControl);
if MixLine.cControls>0 then begin
GetMem (controls, sizeof (TMixerControlW) * MixLine.cControls); { make sure to reserve ample space even for WideChar }
mixerLineControls.pamxctrl := controls;
err:=mixerGetLineControls (MixerHandle, @mixerLineControls, MIXER_GETLINECONTROLSF_ALL);
if err=MMSYSERR_NOERROR then begin
p := controls;
for j := 0 to mixerLineControls.cControls - 1 do begin
if (p^.dwControlType=MIXERCONTROL_CONTROLTYPE_VOLUME) then begin
InitiateControlDetails(details,p^.dwControlID,MixLine.cChannels,@values);
if mixerGetControlDetails (MixerHandle, @details, MIXER_GETCONTROLDETAILSF_VALUE) = MMSYSERR_NOERROR then
UpdateLinkedList(3,PMixer,0,0,'',details.dwControlID,values,(MixLine.cChannels=1),speakers);
end else begin
if (p^.dwControlType=MIXERCONTROL_CONTROLTYPE_MUTE) then begin
InitiateControlDetails(details,p^.dwControlID,1,@values);
if mixerGetControlDetails (MixerHandle, @details, MIXER_GETCONTROLDETAILSF_VALUE) = MMSYSERR_NOERROR then
UpdateLinkedList(2,PMixer,0,0,'',details.dwControlID,values,false,speakers);
end else begin
if (p^.dwControlType=MIXERCONTROL_CONTROLTYPE_PEAKMETER) then begin
InitiateControlDetails(details,p^.dwControlID,MixLine.cChannels,@values);
if mixerGetControlDetails (MixerHandle, @details, MIXER_GETCONTROLDETAILSF_VALUE) = MMSYSERR_NOERROR then
UpdateLinkedList(4,PMixer,0,0,'',details.dwControlID,values,(MixLine.cChannels=1),speakers);
end;
end;
end;
Inc (p);
end;
Result:=true;
end else Result:=false;
FreeMem (controls, sizeof (TMixerControlW) * MixLine.cControls);
end else Result:=true;
end;
begin
Result:=false; MixerStart:=nil; PMix:=nil;
if mixerGetNumDevs=0 then begin
exit;
end else begin
MixerGetDevCaps (MixerDeviceID, @caps, sizeof (caps));
err:= mixerOpen (@MixerHandle, MixerDeviceID, MixerCallbackHandle, 0, CALLBACK_WINDOW OR MIXER_OBJECTF_MIXER);
if err = MMSYSERR_NOERROR then begin
UpdateLinkedList(0,MixerStart,dword(-1),dword(-2),StrPas(caps.szPname),0,Data,false,false);
PMix:=MixerStart;
for j := 0 to caps.cDestinations - 1 do begin
lineInfo.cbStruct := sizeof (lineInfo);
lineInfo.dwDestination := j;
lineinfo.dwSource:=0; { Added this line 990318/HBn }
Result:=false;
err:=mixerGetLineInfo (MixerHandle, @lineInfo, MIXER_GETLINEINFOF_DESTINATION);
if err = MMSYSERR_NOERROR then begin
speakers:=(lineInfo.dwComponentType=MIXERLINE_COMPONENTTYPE_DST_SPEAKERS);
GetControl(PMix,lineInfo,speakers);
for k := 0 to lineInfo.cConnections - 1 do begin
connectionInfo.cbStruct := sizeof (connectionInfo);
connectionInfo.dwDestination := j;
connectionInfo.dwSource := k;
Result:=false;
err:=mixerGetLineInfo (MixerHandle, @connectionInfo, MIXER_GETLINEINFOF_SOURCE);
if err = MMSYSERR_NOERROR then GetControl(PMix,connectionInfo,speakers)
else exit;
end;
Result:=true;
end else exit;
end;
end;
end;
end;
{$ENDIF}
{------------- Public methods ---------------------------------------}
constructor TAudio.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDeviceID:=DefaultAudioDeviceID;
FSepCtrl:=false;
FVersion:=Ver;
FRecorder:=TRecorder.Create; FRecorder.FAudio:=Self;
FRecorder.Active:=false;
FRecorder.FBPS:=BPSDefault;
FRecorder.FNoSamples:=NoSamplesDefault;
FRecorder.FChannels:=ChannelsDefault;
FRecorder.FSPS:=SPSDefault;
FRecorder.AddNextInBufferHandle:= AllocateHWnd(FRecorder.AddNextInBuffer2);
FPlayer:=TPlayer.Create; FPlayer.FAudio:=Self;
FPlayer.Active:=false;
FPlayer.FBPS:=BPSDefault;
FPlayer.FNoSamples:=NoSamplesDefault;
FPlayer.FChannels:=ChannelsDefault;
FPlayer.FSPS:=SPSDefault;
FPlayer.PlayStream:=nil;
FPlayer.FPlayFile:=false;
FPlayer.ActiveBuffers:=0;
FPlayer.AddNextOutBufferHandle:= AllocateHWnd(FPlayer.AddNextOutBuffer2);
FPlayer.CloseHandle:=AllocateHWnd(FPlayer.Close2);
FWindowHandle:=AllocateHWnd(AudioCallBack);
{$IFDEF WIN32}
WaveFmtSize:=SizeOf(TWaveFormatEx);
Mixer:=TMixerSettings.Create;
Mixer.MixerReady:=false;
Mixer.FAudio:=Self;
FMixerDeviceID:=DefaultMixerDeviceID;
Mixer.FList:=TStringList.Create;
Mixer.MixerStart:=nil;
Mixer.MixerCallbackHandle:=AllocateHWnd(Mixer.MixerCallback);
if Mixer.GetMixerSettings(FMixerDeviceID) then Mixer.MixerReady:=true;
{$ELSE}
WaveFmtSize:=SizeOf(TPCMWaveFormat);
{$ENDIF}
FRecorder.RecToFile:=false; ErrorMessage:='';
if (waveInGetNumDevs<1) then Exit;
if not(FRecorder.AllocateMemory) then Exit;
if (waveOutGetNumDevs<1) then Exit;
if not(FPlayer.AllocateMemory) then Exit;
end;
destructor TAudio.Destroy;
var i:longint;
{$IFDEF WIN32}
P1,P2 :PMixDetails;
{$ENDIF}
begin
FPlayer.Stop;
FRecorder.Stop;
{$IFDEF WIN32}
Mixer.FList.Free;
if Mixer.MixerStart<>nil then mixerClose(Mixer.MixerHandle);
P1:=Mixer.MixerStart;
while P1<>nil do begin
P2:=P1.Next;
Dispose(P1);
P1:=P2;
end;
if Mixer.MixerCallbackHandle<>0 then DeAllocateHwnd(Mixer.MixerCallbackHandle);
Mixer.Free;
{$ENDIF}
with FRecorder do begin
if RecToFile and (RecStream<>nil) then begin
i:=RecStream.Size-8; { size of file }
RecStream.Position:=4;
RecStream.write(i,4);
i:=i-$24; { size of data }
RecStream.Position:=40;
RecStream.write(i,4);
RecStream.Free;
RecToFile:=false;
end;
{ Close; }
FreeMemory;
if AddNextInBufferHandle<>0 then DeallocateHWnd(AddNextInBufferHandle);
Free;
end;
with FPlayer do begin
FreeMemory;
if AddNextOutBufferHandle<>0 then DeallocateHWnd(AddNextOutBufferHandle);
if CloseHandle<>0 then DeallocateHWnd(CloseHandle);
Free;
end;
if FWindowHandle<>0 then DeAllocateHWnd(FWindowHandle);
inherited Destroy;
end;
function TAudio.Query(var Product,Formats:string):boolean;
var Caps : PWaveOutCaps;
i1,i2,j1,j2 : Word;
iErr : Integer;
begin
Result:=false;
Product:=''; Formats:='';
if (waveInGetNumDevs<=FDeviceID) or (waveOutGetNumDevs<=FDeviceID) then begin
ErrorMessage:='No waveform device available';
Exit;
end else begin
GetMem(Caps,SizeOf(TWaveOutCapsW));
iErr:=waveOutGetDevCaps(FDeviceID,Caps,SizeOf(TWaveOutCaps));
if (iErr<>0) then begin
FPlayer.GetError(iErr,'');
Exit;
end else begin
Product:=StrPas(Caps^.szPname);
Formats:='';
if ((Caps^.dwFormats and WAVE_FORMAT_1M08)>0) then Formats:='11.025';
if ((Caps^.dwFormats and WAVE_FORMAT_2M08)>0) then Formats:=Formats+'/22.05';
if ((Caps^.dwFormats and WAVE_FORMAT_1M08)>0) then Formats:=Formats+'/44.1';
Formats:=Formats+' kHz, ';
if ((Caps^.dwFormats and WAVE_FORMAT_1M08)>0) then Formats:=Formats+'Mono';
if ((Caps^.dwFormats and WAVE_FORMAT_1S08)>0) then Formats:=Formats+'/Stereo';
if ((Caps^.dwFormats and WAVE_FORMAT_1M08)>0) then Formats:=Formats+', 8';
if ((Caps^.dwFormats and WAVE_FORMAT_1M16)>0) then Formats:=Formats+'/16';
Formats:=Formats+'-bit, Playback Controls: ';
if ((Caps^.dwSupport and WAVECAPS_LRVOLUME)>0) then Formats:=Formats+'Separate L/R Volume'
else if ((Caps^.dwSupport and WAVECAPS_VOLUME)>0) then Formats:=Formats+'Volume';
FPlayer.GetVolume(i1,i2);
FPlayer.SetVolume((i1+10) mod 65535,(i2+10) mod 65535);
FPlayer.GetVolume(j1,j2);
FPlayer.SetVolume(i1,i2);
if not((j1=((i1+10) mod 65535)) and (j2=((i2+10) mod 65535))) then
Formats:=Formats+' (not controllable with this DeviceID driver)';
if ((Caps^.dwSupport and WAVECAPS_PITCH)>0) then Formats:=Formats+', Pitch';
if ((Caps^.dwSupport and WAVECAPS_PLAYBACKRATE)>0) then Formats:=Formats+', Rate';
if ((Caps^.dwSupport and WAVECAPS_SYNC)>0) then Formats:=Formats+', Synchronous Device';
FRecorder.FPause:=true;
FRecorder.Close;
if (FPlayer.Open and FRecorder.Open) then begin
if (FPlayer.DeviceOpen and FRecorder.DeviceOpen) then Formats:='Full-duplex support, '+Formats
else Formats:='Half-duplex support, '+Formats;
end else Formats:='Half-duplex support, '+Formats;
FRecorder.Close;
FRecorder.FPause:=false;
PostMessage(FPlayer.CloseHandle,mm_wom_CLOSE,0,0);
end;
if Caps<>nil then FreeMem(Caps,SizeOf(TWaveOutCapsW));
end;
Result:=true;
end;
{ Callback routine used for CALLBACK_FUNCTION in waveInOpen }
{$IFDEF WIN32}
procedure RecorderCallBack(hW:HWAVEIN; uMsg,dwInstance,dwParam1,dwParam2 : DWORD); stdcall;
{$ELSE}
procedure RecorderCallBack(hW:HWAVEIN; uMsg,dwInstance,dwParam1,dwParam2 : LongInt); stdcall;
{$ENDIF}
var LP,RP:pointer;
Size:Word;
RecPtr : PRecorder;
begin
RecPtr := Pointer(dwInstance);
with RecPtr^ do begin
case uMsg of
wim_OPEN : Active:=true;
wim_CLOSE : Active:=false;
wim_DATA : begin
if Active then begin
LP:=pWaveBuffer[ReturnIndex Mod No_Buffers];
RP:=pExtraBuffer[ReturnIndex Mod No_Buffers];
Size:=pWaveHeader[ReturnIndex Mod No_Buffers]^.dwBytesRecorded;
if (not(FPause) and TestTrigger(LP,Size)) then begin
if RecToFile then RecStream.write(LP^,Size);
if Assigned(FAudio.FOnAudioRecord) then begin
if FSplit then begin
Split(LP,RP,Size);
FAudio.FOnAudioRecord(RecPtr^,LP,RP,Size);
end else FAudio.FOnAudioRecord(RecPtr^,LP,nil,Size);
end;
end;
if (Size>0) then begin
PostMessage(AddNextInBufferHandle,wim_DATA,0,0);
ReturnIndex:=(ReturnIndex+1) mod No_Buffers;
end;
end;
end;
end;
end;
end;
function TRecorder.Open : boolean;
var
iErr, i : Integer;
begin
if not(DeviceOpen) then begin
Result:=false;
ForwardIndex:=0;
ReturnIndex:=0;
{$IFDEF WIN32}
iErr:=waveInOpen(@WaveIn,FAudio.FDeviceID, pWaveFmt,dword(@RecorderCallBack),
dword(@FAudio.FRecorder), CALLBACK_FUNCTION+WAVE_ALLOWSYNC);
{ iErr:=waveInOpen(@WaveIn,FAudio.FDeviceID, pWaveFmt,FAudio.FWindowHandle,0, CALLBACK_WINDOW+WAVE_ALLOWSYNC); }
{$ELSE}
{ iErr:=waveInOpen(@WaveIn,FAudio.FDeviceID, @pWaveFmt^.wf,LongInt(@RecorderCallBack),
LongInt(@FAudio.FRecorder), CALLBACK_FUNCTION+WAVE_ALLOWSYNC); }
{ Problem to get CALLBACK_FUNCTION to work in 16bit version }
iErr:=waveInOpen(@WaveIn,FAudio.FDeviceID, @pWaveFmt^.wf,FAudio.FWindowHandle,0, CALLBACK_WINDOW+WAVE_ALLOWSYNC);
{$ENDIF}
if (iErr<>0) then begin
Close;
GetError(iErr,'Could not open the input device for recording: ');
Exit;
end;
DeviceOpen:=true;
InitWaveHeaders;
for i:=0 to No_Buffers-1 do begin
iErr:=waveInPrepareHeader(WaveIn, pWaveHeader[i], sizeof(TWAVEHDR));
if (iErr<>0) then begin
Close;
GetError(iErr,'Error preparing header for recording: ');
Exit;
end;
end;
if not(AddNextInBuffer) then begin
FAudio.ErrorMessage:='Error adding next input buffer';
Exit;
end;
end;
Result:=true;
end;
function TRecorder.Close : boolean;
var
iErr,i : Integer;
begin
Result:=false;
if not(DeviceOpen) then begin
FAudio.ErrorMessage:='Recorder already closed';
Result:=true;
Exit;
end;
if (waveInReset(WaveIn)<>0) then begin
FAudio.ErrorMessage:='Error in waveInReset';
Exit;
end;
for i:=0 to No_Buffers-1 do begin
iErr:=waveInUnprepareHeader(WaveIn, pWaveHeader[i], sizeof(TWAVEHDR));
if (iErr<>0) then begin
GetError(iErr,'Error in waveInUnprepareHeader');
Exit;
end;
end;
{$IFDEF WIN32}
if (waveInClose(WaveIn)<>0) then begin
{$ELSE}
if (correctedwaveInClose(WaveIn)<>0) then begin
{$ENDIF}
FAudio.ErrorMessage:='Error closing input device';
Exit;
end;
DeviceOpen:=false;
Result:=true;
end;
function TRecorder.Start : boolean;
var
iErr, i : Integer;
begin
Result:=false;
if Open then begin
iErr:=WaveInStart(WaveIn);
if (iErr<>0) then begin
GetError(iErr,'Error starting wave record: ');
Close;
Result:=false;
Exit;
end;
for i:=1 to No_Buffers-1 do
if not(AddNextInBuffer) then begin
FAudio.ErrorMessage:='Error adding next input buffer';
Exit;
end;
Result:=true;
end;
end;
function TRecorder.Stop : boolean;
var i:longint;
begin
Active:=false;
Result:=Close;
if RecToFile then begin
i:=RecStream.Size-8; { size of file }
RecStream.Position:=4;
RecStream.write(i,4);
i:=i-$24; { size of data }
RecStream.Position:=40;
RecStream.write(i,4);
RecStream.Free;
RecToFile:=false;
end;
while Active do Application.ProcessMessages;
end;
procedure TRecorder.Pause;
begin
if DeviceOpen then FPause:=true;
end;
procedure TRecorder.Restart;
begin
if DeviceOpen then FPause:=false;
end;
procedure TRecorder.RecordToFile(FileName:string; LP,RP:TStream);
var temp:string;
i : LongInt;
T1,T2 : ^byte;
begin
if FileName<>'' then begin
RecToFile:=true;
RecStream:=TFileStream.Create(FileName,fmCreate);
temp:='RIFF';RecStream.write(temp[1],length(temp));
temp:=#0#0#0#0;RecStream.write(temp[1],length(temp)); { File size: to be updated }
temp:='WAVE';RecStream.write(temp[1],length(temp));
temp:='fmt ';RecStream.write(temp[1],length(temp));
temp:=#$10#0#0#0;RecStream.write(temp[1],length(temp)); { Fixed }
temp:=#1#0;RecStream.write(temp[1],length(temp)); { PCM format }
if FChannels=Mono then temp:=#1#0
else temp:=#2#0;
RecStream.write(temp[1],length(temp));
RecStream.write(FSPS,2);
temp:=#0#0;RecStream.write(temp[1],length(temp)); { SampleRate is given is dWord }
{$IFDEF WIN32}
with pWaveFmt^ do begin
{$ELSE}
with pWaveFmt^.wf do begin
{$ENDIF}
RecStream.write(nAvgBytesPerSec,4);
RecStream.write(nBlockAlign,2);
end;
RecStream.write(pWaveFmt^.wBitsPerSample,2);
temp:='data';RecStream.write(temp[1],length(temp));
temp:=#0#0#0#0;RecStream.write(temp[1],length(temp)); { Data size: to be updated }
if (LP<>nil) then begin
LP.Position:=0;
if (RP<>nil) and (RP.Size=LP.Size) then begin
RP.Position:=0;
GetMem(T1,1000); T2:=T1;
if FBPS=_8 then begin
for i:=1 to LP.Size do begin
LP.Read(T2^,1);inc(T2,1);
RP.Read(T2^,1); inc(T2,1);
if (i mod 500)=0 then begin
RecStream.Write(T1^,1000);
T2:=T1;
end;
end;
i:=LP.Size mod 500;
if i>0 then begin
RecStream.Write(T1^,i*2);
end;
end else begin
for i:=1 to (LP.Size div 2) do begin
LP.Read(T2^,2);inc(T2,2);
RP.Read(T2^,2); inc(T2,2);
if (i mod 250)=0 then begin
RecStream.Write(T1^,1000);
T2:=T1;
end;
end;
i:=(LP.Size div 2) mod 250;
if i>0 then begin
RecStream.Write(T1^,i*2);
end;
end;
FreeMem(T1,1000);
end else RecStream.CopyFrom(LP,LP.Size);
{
if (LP<>nil) then begin
LP.Position:=0;
if (RP<>nil) and (RP.Size=LP.Size) then begin
RP.Position:=0;
if FBPS=_8 then begin
for i:=1 to LP.Size do begin
RecStream.CopyFrom(LP,1);
RecStream.CopyFrom(RP,1);
end;
end else begin
for i:=1 to (LP.Size div 2) do begin
RecStream.CopyFrom(LP,2);
RecStream.CopyFrom(RP,2);
end;
end;
end else RecStream.CopyFrom(LP,LP.Size);
}
i:=RecStream.Size-8; { size of file }
RecStream.Position:=4;
RecStream.write(i,4);
i:=i-$24; { size of data }
RecStream.Position:=40;
RecStream.write(i,4);
RecStream.Free;
RecToFile:=false;
end;
end else RecToFile:=false;
end;
{ Callback routine used for CALLBACK_FUNCTION in waveOutOpen }
{$IFDEF WIN32}
procedure PlayerCallBack(hW:HWAVEOUT; uMsg,dwInstance,dwParam1,dwParam2 : DWORD); stdcall;
{$ELSE}
procedure PlayerCallBack(hW:HWAVEOUT; uMsg,dwInstance,dwParam1,dwParam2 : LongInt); stdcall;
{$ENDIF}
var PlayPtr : PPlayer;
begin
PlayPtr := Pointer(dwInstance);
with PlayPtr^ do begin
case uMsg of
wom_OPEN : Active:=true;
wom_CLOSE : Active:=false;
wom_DONE : if Active then begin
if (ForwardIndex=ReturnIndex) then begin
if not(FinishedPlaying) then begin
FinishedPlaying:=true;
PostMessage(CloseHandle,mm_wom_CLOSE,0,0);
end;
end else begin
if Assigned(FAudio.FOnBufferPlayed) then FAudio.FOnBufferPlayed(PlayPtr^);
PostMessage(AddNextOutBufferHandle,wom_DONE,0,0);
ReturnIndex:=(ReturnIndex+1) mod No_Buffers;
dec(ActiveBuffers);
end;
end;
end;
end;
end;
function TPlayer.Open : boolean;
var
iErr : Integer;
begin
if not(DeviceOpen) then begin
Result:=false;
ForwardIndex:=0;
ActiveBuffers:=0;
ReturnIndex:=1; { necessary since ForwardIndex always is one more than being sent }
{$IFDEF WIN32}
iErr:=waveOutOpen(@WaveOut,FAudio.FDeviceID, pWaveFmt,dword(@PlayerCallBack),
dword(@FAudio.FPlayer), CALLBACK_FUNCTION+WAVE_ALLOWSYNC);
{ iErr:=waveOutOpen(@WaveOut,FAudio.FDeviceID, pWaveFmt,FAudio.FWindowHandle,0, CALLBACK_WINDOW+WAVE_ALLOWSYNC); }
{$ELSE}
{ iErr:=waveOutOpen(@WaveOut,FAudio.FDeviceID, @pWaveFmt^.wf,LongInt(@PlayerCallBack),
LongInt(@FAudio.FPlayer), CALLBACK_FUNCTION+WAVE_ALLOWSYNC); }
{ Problem to get CALLBACK_FUNCTION to work in 16bit version }
iErr:=waveOutOpen(@WaveOut,FAudio.FDeviceID, @pWaveFmt^.wf,FAudio.FWindowHandle,0, CALLBACK_WINDOW+WAVE_ALLOWSYNC);
{$ENDIF}
if (iErr<>0) then begin
GetError(iErr,'Could not open the output device for playing: ');
Exit;
end;
DeviceOpen:=true;
InitWaveHeaders;
end;
Result:=true;
end;
procedure TPlayer.Play(LP,RP:TStream; NoOfRepeats:Word);
var i : LongInt;
T1,T2 : ^byte;
begin
if not(Open) then exit;
if (LP<>nil) and (LP.Size>0) then begin
if PlayStream=nil then begin
{ PlayStream:=TMemoryStream.Create; }
PlayStream:=TFileStream.Create('PLAY.TMP',fmCreate);
FNoOfRepeats:=NoOfRepeats;
ReadPlayStreamPos:=0;
end else PlayStream.Position:=PlayStream.Size;
if (FChannels=Stereo) and (RP<>nil) and (RP.Size=LP.Size) then begin
LP.Position:=0; RP.Position:=0;
GetMem(T1,1000); T2:=T1;
if FBPS=_8 then begin
for i:=1 to LP.Size do begin
LP.Read(T2^,1);inc(T2,1);
RP.Read(T2^,1); inc(T2,1);
if (i mod 500)=0 then begin
PlayStream.Write(T1^,1000);
T2:=T1;
end;
end;
i:=LP.Size mod 500;
if i>0 then begin
PlayStream.Write(T1^,i*2);
end;
end else begin
for i:=1 to (LP.Size div 2) do begin
LP.Read(T2^,2);inc(T2,2);
RP.Read(T2^,2); inc(T2,2);
if (i mod 250)=0 then begin
PlayStream.Write(T1^,1000);
T2:=T1;
end;
end;
i:=(LP.Size div 2) mod 250;
if i>0 then begin
PlayStream.Write(T1^,i*2);
end;
{ if FBPS=_8 then begin
for i:=1 to LP.Size do begin
PlayStream.CopyFrom(LP,1);
PlayStream.CopyFrom(RP,1);
end;
end else begin
for i:=1 to (LP.Size div 2) do begin
PlayStream.CopyFrom(LP,2);
PlayStream.CopyFrom(RP,2);
end;
end }
end;
FreeMem(T1,1000);
end else begin
LP.Position:=0;
PlayStream.CopyFrom(LP,LP.Size);
end;
if ReadPlayStreamPos=0 then
for i:=1 to No_Buffers do AddNextOutBuffer;
end;
end;
procedure TPlayer.Close2(var Msg: TMessage);
var
iErr, i : Integer;
begin
if not(DeviceOpen) then begin
FAudio.ErrorMessage:='Player already closed';
exit;
end;
for i:=0 to No_Buffers-1 do begin
iErr:=waveOutUnPrepareHeader(WaveOut, pWaveHeader[i], sizeof(TWAVEHDR));
if (iErr<>0) then begin
GetError(iErr,'Error unpreparing header for playing: ');
Exit;
end;
end;
iErr:=waveOutClose(WaveOut);
if (iErr<>0) then begin
GetError(iErr,'Error closing output device: ');
Exit;
end;
DeviceOpen:=false;
if (FPlayFile and (PlayStream=nil)) then begin
SetChannels(FOldChannels);
SetSPS(FOldSPS);
SetBPS(FOldBPS);
FPlayFile:=false;
end;
if Assigned(FAudio.FOnPlayed) then FAudio.FOnPlayed(Self);
end;
procedure TPlayer.Stop;
var iErr : integer;
begin
if not(DeviceOpen) then begin
FAudio.ErrorMessage:='Player already closed';
exit;
end;
if PlayStream<>nil then begin
PlayStream.Free;
PlayStream:=nil;
ForwardIndex:=ReturnIndex;
FAudio.ErrorMessage:='';
end;
if not(FinishedPlaying) then begin
iErr:=waveOutReset(WaveOut);
if (iErr<>0) then begin
FAudio.ErrorMessage:='Error in waveOutReset';
Exit;
end;
end;
while Active do Application.ProcessMessages;
end;
procedure TPlayer.Pause;
begin
if DeviceOpen then waveOutPause(WaveOut);
end;
procedure TPlayer.Restart;
begin
if DeviceOpen then waveOutRestart(WaveOut);
end;
procedure TPlayer.Reset;
begin
if DeviceOpen then waveOutReset(WaveOut);
end;
procedure TPlayer.BreakLoop;
begin
if DeviceOpen then waveOutBreakLoop(WaveOut);
end;
function TPlayer.PlayFile(FileName:string; NoOfRepeats:Word):boolean;
var temp:array[0..255] of byte;
i : integer;
Data:word;
DataSize:longint;
begin
Result:=false;
if FileName<>'' then begin
if (PlayStream=nil) then begin
FOldChannels:=FChannels;
FOldSPS:=FSPS;
FOldBPS:=FBPS;
end;
PlayFileStream:=TFileStream.Create(FileName,fmOpenRead);
PlayFileStream.Read(temp,22);
PlayFileStream.Read(temp,2);
if (temp[0]=2) then begin
if (FChannels<>Stereo) then begin
while FPlayFile do Application.ProcessMessages;
SetChannels(Stereo);
end;
end else begin
if (FChannels<>Mono) then begin
while FPlayFile do Application.ProcessMessages;
SetChannels(Mono);
end;
end;
PlayFileStream.Read(temp,2);
Data:=temp[1]*256+temp[0];
if (FSPS<>Data) then begin
while FPlayFile do Application.ProcessMessages;
SetSPS(Data);
end;
PlayFileStream.Read(temp,8);
PlayFileStream.Read(temp,2);
if (temp[0]>8) then begin
if (FBPS<>_16) then begin
while FPlayFile do Application.ProcessMessages;
SetBPS(_16);
end;
end else begin
if (FBPS<>_8) then begin
while FPlayFile do Application.ProcessMessages;
SetBPS(_8);
end;
end;
PlayFileStream.Read(temp,4); i:=0;
while ((temp[i]<>$64) or (temp[i+1]<>$61) or (temp[i+2]<>$74) or (temp[i+3]<>$61)) do begin
PlayFileStream.Read(temp[i+4],1);
inc(i);
end;
PlayFileStream.Read(DataSize,4);
FPlayFile:=true;
if PlayStream=nil then begin
if Open then begin
{ PlayStream:=TMemoryStream.Create; }
PlayStream:=TFileStream.Create('PLAY.TMP',fmCreate);
FNoOfRepeats:=NoOfRepeats;
ReadPlayStreamPos:=0;
end else begin
PlayFileStream.Free;
exit;
end;
end else begin
PlayStream.Position:=PlayStream.Size;
end;
PlayStream.CopyFrom(PlayFileStream,DataSize);
if ReadPlayStreamPos=0 then
for i:=1 to (No_Buffers-ActiveBuffers) do
AddNextOutBuffer;
PlayFileStream.Free;
Result:=true;
end;
end;
{------------- Property Controls ------------------------------------}
procedure TAudio.SetVersion(Value:string);
begin
FVersion:=Ver;
end;
procedure TAudioSettings.SetChannels(Value:TChannels);
begin
if FAudio.FSepCtrl then begin
if FChannels<>Value then begin
FChannels:=Value;
FreeMemory;
AllocateMemory;
end;
end else begin
if FAudio.Player.FChannels<>Value then begin
FAudio.Player.FChannels:=Value;
FAudio.Player.FreeMemory;
FAudio.Player.AllocateMemory;
end;
if FAudio.Recorder.FChannels<>Value then begin
FAudio.Recorder.FChannels:=Value;
FAudio.Recorder.FreeMemory;
FAudio.Recorder.AllocateMemory;
end;
end;
FAudio.Recorder.SetSplit(FAudio.FRecorder.FSplit);
end;
procedure TAudioSettings.SetBPS(Value:TBPS);
begin
if FAudio.FSepCtrl then begin
if FBPS<>Value then begin
FBPS:=Value;
FreeMemory;
AllocateMemory;
end;
end else begin
if FAudio.Player.FBPS<>Value then begin
FAudio.Player.FBPS:=Value;
FAudio.Player.FreeMemory;
FAudio.Player.AllocateMemory;
end;
if FAudio.Recorder.FBPS<>Value then begin
FAudio.Recorder.FBPS:=Value;
FAudio.Recorder.FreeMemory;
FAudio.Recorder.AllocateMemory;
end;
end;
end;
procedure TAudioSettings.SetSPS(Value:Word);
begin
if FAudio.FSepCtrl then begin
if FSPS<>Value then begin
FSPS:=Value;
FreeMemory;
AllocateMemory;
end;
end else begin
if FAudio.Player.FSPS<>Value then begin
FAudio.Player.FSPS:=Value;
FAudio.Player.FreeMemory;
FAudio.Player.AllocateMemory;
end;
if FAudio.Recorder.FSPS<>Value then begin
FAudio.Recorder.FSPS:=Value;
FAudio.Recorder.FreeMemory;
FAudio.Recorder.AllocateMemory;
end;
end;
end;
procedure TRecorder.SetNoSamples(Value:Word);
begin
if FAudio.Player.FNoSamples<>Value then begin
FAudio.Player.FNoSamples:=Value;
FAudio.Player.FreeMemory;
FAudio.Player.AllocateMemory;
end;
if FAudio.Recorder.FNoSamples<>Value then begin
FAudio.Recorder.FNoSamples:=Value;
FAudio.Recorder.FreeMemory;
FAudio.Recorder.AllocateMemory;
end;
end;
procedure TRecorder.SetSplit(Value:Boolean);
begin
if FChannels=Stereo then begin
if FSplit<>Value then FSplit:=Value;
end else FSplit:=false;
end;
procedure TRecorder.SetTrigLevel(Value:Word);
begin
if FTrigLevel<>Value then FTrigLevel:=Value;
end;
procedure TPlayer.GetVolume(var LeftVolume,RightVolume:Word);
var
iErr : Integer;
{$IFDEF WIN32}
Vol : dword;
{$ELSE}
Vol : longint;
{$ENDIF}
begin
iErr:=waveOutGetVolume(FAudio.FDeviceID,@Vol);
if (iErr<>0) then GetError(iErr,'');
LeftVolume:=Word(Vol and $FFFF);
RightVolume:=Word(Vol shr 16);
end;
procedure TPlayer.SetVolume(LeftVolume,RightVolume:Word);
var
iErr : Integer;
{$IFDEF WIN32}
Vol : dword;
{$ELSE}
Vol : longint;
{$ENDIF}
begin
Vol:=RightVolume;
Vol:=(Vol shl 16)+LeftVolume;
iErr:=waveOutSetVolume(FAudio.FDeviceID,Vol);
if (iErr<>0) then GetError(iErr,'');
end;
procedure TAudio.SetDeviceID(Value:Integer);
begin
if FDeviceID<>Value then begin
if Value>9 then FDeviceID:=WAVE_MAPPER
else FDeviceID:=Value;
FRecorder.FreeMemory;
FRecorder.AllocateMemory;
FPlayer.FreeMemory;
FPlayer.AllocateMemory;
end;
end;
{$IFDEF WIN32}
procedure TAudio.SetMixerDeviceID(Value:Integer);
begin
if FMixerDeviceID<>Value then begin
FMixerDeviceID:=Value;
if Mixer.GetMixerSettings(FMixerDeviceID) then Mixer.MixerReady:=true;
end;
end;
{$ENDIF}
procedure Register;
begin
RegisterComponents('Interface', [TAudio]);
end;
end.
|