?
Solved

How do I record internet radio in a Delphi program?

Posted on 2003-03-09
12
Medium Priority
?
1,560 Views
Last Modified: 2008-02-26
I wish to build a program to record Internet radio as it streams in. I am aware that Total Recorder does this but I would like to be able to include this functionality in my Delphi program. In particular, I would like to intercept the data stream to the sound card and record it.
 
Can you help me, please

Morris
0
Comment
Question by:MorrisLockwood
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 3
  • 3
  • 2
  • +3
12 Comments
 
LVL 5

Expert Comment

by:Lukasz Lach
ID: 8099074
use bass.dll from u4seen (http://www.un4seen.com/):

var
  RadioStream : HSTREAM;
begin
  if not BASS_Init(-1, 44100, 0, Self.Handle) then
  begin
    ShowMessage('Bass init error');
    Exit;
  end;
  BASS_Start;
  if RadioStream <> 0 then BASS_StreamFree(RadioStream);
  RadioStream := BASS_StreamCreateURL('http://somehost:8000', 0, BASS_STREAM_AUTOFREE, 'local_filename.mp3');
  if RadioStream = 0 then
  begin
    ShowMessage('Connection to host not successful');
    Exit;
  end;
  BASS_StreamPlay(RadioStream, True, 0);
end;

Form1.OnDestroy:
  if RadioStream <> 0 then BASS_StreamFree(RadioStream);
  BASS_Free;
0
 
LVL 5

Expert Comment

by:Lukasz Lach
ID: 8099076
if you need any other help with bass.dll just ask :-)
0
 
LVL 1

Expert Comment

by:Wax0r
ID: 8101995
If you don't want to include a third party DLL file you can also do it in a different manner;

Simply add a HTTP component to your form (NMHTTP e.g.) and point it to host:port and tell it to get it, the mp3 stream will flow in as well, simply write it to file and you'll have the same effect but without the bass.dll requirement.

If you want to play it you can use the WMP (windows media player / directplay) stuff to play the mp3, also to avoid the bass.dll stuff.

I made a stream ripper a few days ago using the above method (without the playing as I didn't require that) but I am not at home at the moment so cannot post code for it. (Though the explanation should get u going just fine if it's what you're looking for).
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:MorrisLockwood
ID: 8102716
Dear anAKIN and WaxOr,

I tried the Bass dll with the URL : http://www.bbc.co.uk/radio/aod/radio4.shtml?fm (see http://www.bbc.co.uk/radio4 ) and got a Bass initialisation failed message.

I looked at NMHTTP but could not see how to take it forward. I would be grateful to see the code when you return home.

I think the problem  with the BBC site is that the try to restrict the receipt of the information to the RealOne Player.  I understand that the Total Recorder program intercepts the audio stream after the player and before the audio card. I assume I will have to do that.  What is your view?

Regards

Morris
0
 
LVL 1

Expert Comment

by:Wax0r
ID: 8102873
My fault basically,


I assumed you meant internet radio in the sense of streaming services like shoutcast (which pretty much implemented HTTP as their means of negotiation about what to get (including some custom headers ICY does such as ICY-GENRE, etc.).

Realplayer (or Real.) has it's own protocol I am sure which would obviously have it's own little 'talk' about whether or not the client is a legitimate realplayer client or not.

So I think you're right in your assumption that you'll have to catch it somewhere in between, but I've got no experience in that myself, nor do I have the resources here at work to
a) look at Total Recorder
b) Realplayer/etc.

Some simple thoughts on it would be to have a look at code that handles sound card activity etc. as I'm sure there's something out there, maybe things with Wave playing/recording as well..

Goodluck
(p.s. IF I look into it myself and find anything posting worthy I will let you know but chances are slim.)
0
 
LVL 5

Expert Comment

by:Lukasz Lach
ID: 8102915
bass.dll records radio sent by winamp and other, not RealPlayer...
0
 

Author Comment

by:MorrisLockwood
ID: 8103095
Thank you for your efforts.
Morris
0
 
LVL 1

Accepted Solution

by:
mattlaver earned 2000 total points
ID: 8113411
Use this component, works a treat!

{*************************************************************************
  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.
0
 

Author Comment

by:MorrisLockwood
ID: 8118014
Thank you, Matt

As I mentioned in my email, it works on one of my machines but not the other.

Any help to resolve or diagnose the problem will be much appreciated

Morris
0
 

Expert Comment

by:CleanupPing
ID: 9316873
MorrisLockwood:
This old question needs to be finalized -- accept an answer, split points, or get a refund.  For information on your options, please click here-> http:/help/closing.jsp#1 
EXPERTS:
Post your closing recommendations!  No comment means you don't care.
0
 
LVL 5

Expert Comment

by:snehanshu
ID: 10090505
Hi!
No comment has been added lately and this question is therefore classified abandoned.

If asker wishes to close the question, then refer to
http://www.experts-exchange.com/help/closing.jsp

Otherwise, I will leave a recommendation in the Cleanup topic area that this question is:

Answered by: mattlaver

Please leave any comments here within the next seven days. It is assumed that any participant not responding to this request is no longer interested in its final disposition.

PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER!

...Snehanshu
EE Cleanup Volunteer
0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
In this video, Percona Director of Solution Engineering Jon Tobin discusses the function and features of Percona Server for MongoDB. How Percona can help Percona can help you determine if Percona Server for MongoDB is the right solution for …
In this video, Percona Solutions Engineer Barrett Chambers discusses some of the basic syntax differences between MySQL and MongoDB. To learn more check out our webinar on MongoDB administration for MySQL DBA: https://www.percona.com/resources/we…
Suggested Courses

777 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question