From 1faf3afc9b8c30c47a3f3d6a92ff0b4b18cddefd Mon Sep 17 00:00:00 2001
From: tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>
Date: Wed, 5 Dec 2007 20:35:57 +0000
Subject: New portaudio interface added

git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@663 b956fd51-792f-4845-bead-9b4dfca2ff2c
---
 Game/Code/Classes/UAudio_portaudio.pas | 434 +++++++++++++++++++++++++++++++++
 1 file changed, 434 insertions(+)
 create mode 100644 Game/Code/Classes/UAudio_portaudio.pas

diff --git a/Game/Code/Classes/UAudio_portaudio.pas b/Game/Code/Classes/UAudio_portaudio.pas
new file mode 100644
index 00000000..9ed2107d
--- /dev/null
+++ b/Game/Code/Classes/UAudio_portaudio.pas
@@ -0,0 +1,434 @@
+unit UAudio_Portaudio;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+
+uses Classes,
+     {$IFDEF win32}
+     windows,
+     {$ENDIF}
+     Messages,
+     SysUtils,
+     {$IFNDEF FPC}
+     Forms,
+     {$ENDIF}
+     portaudio,
+     {$IFDEF UsePortmixer}
+     portmixer,
+     {$ENDIF}
+     ULog,
+     UMusic;
+
+implementation
+
+uses
+     {$IFDEF LAZARUS}
+     lclintf,
+     {$ENDIF}
+     URecord,
+     UIni,
+     UMain,
+     UCommon,
+     UThemes;
+{
+type
+  TPaHostApiIndex = PaHostApiIndex;
+  TPaDeviceIndex = PaDeviceIndex;
+  PPaStream = ^PaStreamPtr;
+  PPaStreamCallbackTimeInfo = ^PaStreamCallbackTimeInfo;
+  TPaStreamCallbackFlags = PaStreamCallbackFlags;
+  TPaHostApiTypeId = PaHostApiTypeId;
+  PPaHostApiInfo = ^PaHostApiInfo;
+  PPaDeviceInfo = ^PaDeviceInfo;
+  TPaError = PaError;
+  TPaStreamParameters = PaStreamParameters;
+}
+type
+  TAudio_Portaudio = class( TInterfacedObject, IAudioInput )
+    private
+      function GetPreferredApiIndex(): TPaHostApiIndex;
+    public
+      function  GetName: String;
+      procedure InitializeRecord;
+
+      procedure CaptureStart;
+      procedure CaptureStop;
+
+      procedure CaptureCard(Card: byte; CaptureSoundLeft, CaptureSoundRight: TSound);
+      procedure StopCard(Card: byte);
+  end;
+
+  TPortaudioSoundCard = class(TGenericSoundCard)
+    RecordStream:   PPaStream;
+    DeviceIndex:    TPaDeviceIndex;
+  end;
+
+function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
+      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+      inputDevice: Pointer): Integer; cdecl; forward;
+
+var
+  singleton_MusicPortaudio : IAudioInput;
+
+const
+  sampleRate:  Double = 44100.;
+
+{* the default API used by Portaudio is the least common denominator
+ * and might lack efficiency. ApiPreferenceOrder defines the order of
+ * preferred APIs to use. The first API-type in the list is tried first. If it's
+ * not available the next is tried, ...
+ * If none of the preferred APIs was found the default API is used.
+ * Pascal doesn't permit zero-length static arrays, so you can use paDefaultApi
+ * as an array's only member if you do not have any preferences.
+ * paDefaultApi also terminates a preferences list but this is optional.
+ *}
+const
+  paDefaultApi = -1;
+var
+  ApiPreferenceOrder:
+{$IF Defined(WIN32)}
+    // Note1: Portmixer has no mixer support for paASIO and paWASAPI at the moment
+    // Note2: Windows Default-API is MME
+    //array[0..0] of TPaHostApiTypeId = ( paDirectSound, paMME );
+    array[0..0] of TPaHostApiTypeId = ( paDirectSound );
+{$ELSEIF Defined(LINUX)}
+    // Note1: Portmixer has no mixer support for paJACK at the moment
+    // Note2: Not tested, but ALSA might be better than OSS.
+    array[0..1] of TPaHostApiTypeId = ( paALSA, paOSS );
+{$ELSEIF Defined(DARWIN)}
+    // Note: Not tested.
+    //array[0..0] of TPaHostApiTypeId = ( paCoreAudio );
+    array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
+{$ELSE}
+    array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
+{$IFEND}
+
+function TAudio_Portaudio.GetName: String;
+begin
+  result := 'Portaudio';
+end;
+
+function TAudio_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
+var
+  i: integer;
+begin
+  result := -1;
+
+  // select preferred sound-API
+  for i:= 0 to High(ApiPreferenceOrder) do
+  begin
+    if(ApiPreferenceOrder[i] <> paDefaultApi) then begin
+      // check if API is available
+      result := Pa_HostApiTypeIdToHostApiIndex(ApiPreferenceOrder[i]);
+      if(result >= 0) then
+        break;
+    end;
+  end;
+
+  // None of the preferred APIs is available -> use default
+  if(result < 0) then begin
+    result := Pa_GetDefaultHostApi();
+  end;
+end;
+
+// TODO: should be a function with boolean return type
+procedure TAudio_Portaudio.InitializeRecord;
+var
+  i:           integer;
+  apiIndex:    TPaHostApiIndex;
+  apiInfo:     PPaHostApiInfo;
+  deviceName:  string;
+  deviceIndex: TPaDeviceIndex;
+  deviceInfo:  PPaDeviceInfo;
+  inputCnt:    integer;
+  inputName:   string;
+  SC:          integer; // soundcard
+  SCI:         integer; // soundcard input
+  err:         TPaError;
+  errMsg:      string;
+  paSoundCard: TPortaudioSoundCard;
+  inputParams: TPaStreamParameters;
+  stream:      PPaStream;
+  {$IFDEF UsePortmixer}
+  mixer:       PPxMixer;
+  {$ENDIF}
+begin
+  // TODO: call Pa_Terminate() on termination
+  err := Pa_Initialize();
+  if(err <> paNoError) then begin
+    Log.CriticalError('Portaudio.InitializeRecord: ' + Pa_GetErrorText(err));
+    //Log.LogError('Portaudio.InitializeRecord: ' + Pa_GetErrorText(err));
+    // result := false;
+    Exit;
+  end;
+
+  apiIndex := GetPreferredApiIndex();
+  apiInfo := Pa_GetHostApiInfo(apiIndex);
+
+  SC := 0;
+
+  // init array-size to max. input-devices count
+  SetLength(Recording.SoundCard, apiInfo^.deviceCount); // fix deviceCountL
+  for i:= 0 to High(Recording.SoundCard) do
+  begin
+    // convert API-specific device-index to global index
+    deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(apiIndex, i);
+    deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+
+    // current device is no input device -> skip
+    if(deviceInfo^.maxInputChannels <= 0) then
+      continue;
+
+    // TODO: free object on termination
+    paSoundCard := TPortaudioSoundCard.Create();
+    Recording.SoundCard[SC] := paSoundCard;
+    
+    // retrieve device-name
+    deviceName := deviceInfo^.name;
+    paSoundCard.Description := deviceName;
+    paSoundCard.DeviceIndex := deviceIndex;
+
+    // setup desired input parameters
+    with inputParams do begin
+      device := deviceIndex;
+      channelCount := 2;
+      sampleFormat := paInt16;
+      suggestedLatency := deviceInfo^.defaultLowInputLatency;
+      hostApiSpecificStreamInfo := nil;
+    end;
+
+    // check if device supports our input-format
+    err := Pa_IsFormatSupported(@inputParams, nil, sampleRate);
+    if(err <> 0) then begin
+      // format not supported -> skip
+      errMsg := Pa_GetErrorText(err);
+      Log.LogError('Portaudio.InitializeRecord, device: "'+ deviceName +'" '
+                 + '('+ errMsg +')');
+      paSoundCard.Free();
+      continue;
+    end;
+
+    // TODO: retry with mono if stereo is not supported
+    // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
+    //       not be set correctly in OSS)
+
+    err := Pa_OpenStream(stream, @inputParams, nil, sampleRate,
+        paFramesPerBufferUnspecified, paNoFlag, @MicrophoneCallback, nil);
+    if(err <> paNoError) then begin
+      // unable to open device -> skip
+      errMsg := Pa_GetErrorText(err);
+      Log.LogError('Portaudio.InitializeRecord, device: "'+ deviceName +'" '
+                 + '('+ errMsg +')');
+      paSoundCard.Free();
+      continue;
+    end;
+
+
+    {$IFDEF UsePortmixer}
+
+    // use default mixer
+    mixer := Px_OpenMixer(stream, 0);
+
+    // get input count
+    inputCnt := Px_GetNumInputSources(mixer);
+    SetLength(paSoundCard.Input, inputCnt);
+
+    // get input names
+    for SCI := 0 to inputCnt-1 do
+    begin
+      inputName := Px_GetInputSourceName(mixer, SCI);
+      paSoundCard.Input[SCI].Name := inputName;
+    end;
+
+    Px_CloseMixer(mixer);
+
+    {$ELSE} // !UsePortmixer
+
+    //Pa_StartStream(stream);
+    // TODO: check if callback was called (this problem may occur on some devices)
+    //Pa_StopStream(stream);
+
+    Pa_CloseStream(stream);
+
+    // create a standard input source
+    SetLength(paSoundCard.Input, 1);
+    paSoundCard.Input[0].Name := 'Standard';
+
+    {$ENDIF}
+
+    // use default input source
+    paSoundCard.InputSelected := 0;
+
+    Inc(SC);
+  end;
+
+  // adjust size to actual input-device count
+  SetLength(Recording.SoundCard, SC);
+
+  Log.LogStatus('#Soundcards: ' + inttostr(SC), 'Portaudio');
+
+  {
+    SoundCard[SC].InputSelected := Mic[Device];
+  }
+end;
+
+// TODO: code is used by all IAudioInput implementors
+//   -> move to a common superclass (TAudioInput_Generic?)
+procedure TAudio_Portaudio.CaptureStart;
+var
+  S:  integer;
+  SC: integer;
+  PlayerLeft, PlayerRight: integer;
+  CaptureSoundLeft, CaptureSoundRight: TSound;
+begin
+  for S := 0 to High(Recording.Sound) do
+    Recording.Sound[S].BufferLong[0].Clear;
+
+  for SC := 0 to High(Ini.CardList) do begin
+    PlayerLeft  := Ini.CardList[SC].ChannelL-1;
+    PlayerRight := Ini.CardList[SC].ChannelR-1;
+    if PlayerLeft  >= PlayersPlay then PlayerLeft  := -1;
+    if PlayerRight >= PlayersPlay then PlayerRight := -1;
+    if (PlayerLeft > -1) or (PlayerRight > -1) then begin
+      if (PlayerLeft > -1) then
+        CaptureSoundLeft := Recording.Sound[PlayerLeft]
+      else
+        CaptureSoundLeft := nil;
+      if (PlayerRight > -1) then
+        CaptureSoundRight := Recording.Sound[PlayerRight]
+      else
+        CaptureSoundRight := nil;
+
+      CaptureCard(SC, CaptureSoundLeft, CaptureSoundRight);
+    end;
+  end;
+end;
+
+// TODO: code is used by all IAudioInput implementors
+//   -> move to a common superclass (TAudioInput_Generic?)
+procedure TAudio_Portaudio.CaptureStop;
+var
+  SC:   integer;
+  PlayerLeft:  integer;
+  PlayerRight: integer;
+begin
+
+  for SC := 0 to High(Ini.CardList) do begin
+    PlayerLeft  := Ini.CardList[SC].ChannelL-1;
+    PlayerRight := Ini.CardList[SC].ChannelR-1;
+    if PlayerLeft  >= PlayersPlay then PlayerLeft  := -1;
+    if PlayerRight >= PlayersPlay then PlayerRight := -1;
+    if (PlayerLeft > -1) or (PlayerRight > -1) then
+      StopCard(SC);
+  end;
+
+end;
+
+{*
+ * Portaudio input capture callback.
+ *}
+function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
+      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+      inputDevice: Pointer): Integer; cdecl;
+begin
+  Recording.HandleMicrophoneData(input, frameCount*4, inputDevice);
+  result := paContinue;
+end;
+
+{*
+ * Start input-capturing on Soundcard specified by Card.
+ * Params:
+ *   Card - soundcard index in Recording.SoundCard array
+ *   CaptureSoundLeft  - sound(-buffer) used for left channel capture data
+ *   CaptureSoundRight - sound(-buffer) used for right channel capture data
+ *}
+procedure TAudio_Portaudio.CaptureCard(Card: byte; CaptureSoundLeft, CaptureSoundRight: TSound);
+var
+  Error:       TPaError;
+  ErrorMsg:    string;
+  inputParams: TPaStreamParameters;
+  deviceInfo:  PPaDeviceInfo;
+  stream:      PPaStream;
+  paSoundCard: TPortaudioSoundCard;
+begin
+  Log.LogStatus('Cap1', 'Portaudio');
+
+  paSoundCard := TPortaudioSoundCard(Recording.SoundCard[Card]);
+  paSoundCard.CaptureSoundLeft  := CaptureSoundLeft;
+  paSoundCard.CaptureSoundRight := CaptureSoundRight;
+
+  // get input latency info
+  deviceInfo := Pa_GetDeviceInfo(paSoundCard.DeviceIndex);
+
+  // set input stream parameters
+  with inputParams do begin
+    device := paSoundCard.DeviceIndex;
+    channelCount := 2;
+    sampleFormat := paInt16;
+    suggestedLatency := deviceInfo^.defaultLowInputLatency;
+    hostApiSpecificStreamInfo := nil;
+  end;
+
+  Log.LogStatus(inttostr(paSoundCard.DeviceIndex), 'Portaudio');
+  Log.LogStatus(floattostr(deviceInfo^.defaultLowInputLatency), 'Portaudio');
+
+  // open input stream
+  Error := Pa_OpenStream(stream, @inputParams, nil, sampleRate,
+      paFramesPerBufferUnspecified, paNoFlag,
+      @MicrophoneCallback, Pointer(paSoundCard));
+  if(Error <> paNoError) then begin
+    ErrorMsg := Pa_GetErrorText(Error);
+    Log.CriticalError('TAudio_Portaudio.CaptureCard('+ IntToStr(Card) +'): Error opening stream: ' + ErrorMsg);
+    //Halt;
+  end;
+  
+  paSoundCard.RecordStream := stream;
+
+  // start capture
+  Error := Pa_StartStream(stream);
+  if(Error <> paNoError) then begin
+    Pa_CloseStream(stream);
+    ErrorMsg := Pa_GetErrorText(Error);
+    Log.CriticalError('TAudio_Portaudio.CaptureCard('+ IntToStr(Card) +'): Error starting stream: ' + ErrorMsg);
+    //Halt;
+  end;
+
+  //Readln;
+  Log.LogStatus('Cap2', 'Portaudio');
+end;
+
+{*
+ * Stop input-capturing on Soundcard specified by Card.
+ * Params:
+ *   Card - soundcard index in Recording.SoundCard array
+ *}
+procedure TAudio_Portaudio.StopCard(Card: byte);
+var
+  stream:      PPaStream;
+  paSoundCard: TPortaudioSoundCard;
+begin
+  paSoundCard := TPortaudioSoundCard(Recording.SoundCard[Card]);
+  stream := paSoundCard.RecordStream;
+  if(stream <> nil) then begin
+    Pa_StopStream(stream);
+    Pa_CloseStream(stream);
+  end;
+end;
+
+
+initialization
+  singleton_MusicPortaudio := TAudio_Portaudio.create();
+  writeln( 'UAudio_Portaudio - Register' );
+  AudioManager.add( singleton_MusicPortaudio );
+
+finalization
+  writeln( 'UAudio_Portaudio - UnRegister' );
+  AudioManager.Remove( singleton_MusicPortaudio );
+
+end.
-- 
cgit v1.2.3