aboutsummaryrefslogblamecommitdiffstats
path: root/Game/Code/Classes/UAudioPlayback_Portaudio.pas
blob: 2743fa863db75aa3ec75d8a34ec49271ffa481e4 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11










                              



           



              







                 



                                                        
                              
                        



                        
          





                                                                          
 








                                                     
                                   
 


                                                     



                                                                  





                           
                         








                                           





                                                                  

                                                              






                                                                       
                                     
                             
 
                         



                                               










                                                                      



                                                                  


                                        
                      





                                 


                                               



                                                        



                                                                       















                                                                               

                                    



                                       
                               
                         
                     
























                                                       



                                                                    



                              


                                                           
           



                                                                       
         
                                        
           




                                                                              
                
                                   
                    
     
                    
 


                                 
                                     
                                   
                                   

         





                                                                

                                        
       
                                                         
                                            
         


                                                    
           

                                                               

          
      

           




                            
                                              

                     


































                                                                                             



                                          
                              

             

                                  
      

                                



                                           
                     



                                          
                      



                                                      
                                   













                                                             



                                 








                                                                                     





                                                   
                                         
                            
       
                        


      

                                                    



                                   



                                                           

















                                                              

    






                                                                             
                                     



                                                 
                                                         



                       









                                                                                  






































                                                                                                         
                                                                  


                                     
                                                                 






























                                                                                       



                                                                
     
                  
 
                                    
 









                                                          
 







                                                                            
 
                                                             
 
                 

    




                                                                 
 
                                                        
     
                   

    
                                                              
     
                  
 
                                                                    
 










                                          

    
                                            
     





                       

    
                                                                                         

                                   
                                           
     
                
 

                                              
       

                                                                                                       

      


                                                            
 

                                        
 
                           

    
                                                              
     

                                          

    
                                                                   
     

                                 

    
                                                             
     

                                 

    


                                                                  
     
                  
 

                         
 


                                   
 

                      
 
                 

    
                                          
     
                 

    
                                                           
     

                                  

    
                                                    
     



                                       

    
                                               
     



                                     

    
                                        
     

                               

    
                                         
     

                               

    
                                        
     

                               

    
                                         
     




                                          

    
                                                    
     



                                                   


           
                                                                  


                                                         







                                                                           
                                                                                          
     
                           

    
                                                                           
     

                          

    
                                                                           
     

                          


    








                                                                        
unit UAudioPlayback_Portaudio;

interface

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

{$I switches.inc}


uses
  Classes,
  SysUtils,
  UMusic;

implementation

uses
  {$IFNDEF Win32}
  libc,
  {$ENDIF}
  sdl,
  portaudio,
  ULog,
  UIni,
  UMain;

type
  TPortaudioPlaybackStream = class(TAudioPlaybackStream)
    private
      Status:   TStreamStatus;
      Loop:     boolean;

      _volume: integer;

      procedure Reset();
    public
      DecodeStream: TAudioDecodeStream;

      constructor Create();
      destructor Destroy(); override;

      function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;

      procedure Play();                     override;
      procedure Pause();                    override;
      procedure Stop();                     override;
      procedure Close();                    override;
      function GetLoop(): boolean;          override;
      procedure SetLoop(Enabled: boolean);  override;
      function GetLength(): real;           override;
      function GetStatus(): TStreamStatus;  override;

      function IsLoaded(): boolean;

      function GetVolume(): integer;        override;
      procedure SetVolume(volume: integer); override;

      // functions delegated to the decode stream
      function GetPosition: real;
      procedure SetPosition(Time: real);
      function ReadData(Buffer: PChar; BufSize: integer): integer;
  end;

type
  TAudioMixerStream = class
    private
      activeStreams: TList;
      mixerBuffer: PChar;
      internalLock: PSDL_Mutex;

      _volume: integer;

      procedure Lock(); inline;
      procedure Unlock(); inline;

      function GetVolume(): integer;
      procedure SetVolume(volume: integer);
    public
      constructor Create();
      destructor Destroy(); override;
      procedure AddStream(stream: TAudioPlaybackStream);
      procedure RemoveStream(stream: TAudioPlaybackStream);
      function ReadData(Buffer: PChar; BufSize: integer): integer;

      property Volume: integer READ GetVolume WRITE SetVolume;
  end;

type
  TAudioPlayback_Portaudio = class( TInterfacedObject, IAudioPlayback )
    private
      MusicStream:        TPortaudioPlaybackStream;

      MixerStream: TAudioMixerStream;
      paStream:    PPaStream;

      FrameSize: integer;

      function InitializePortaudio(): boolean;
      function StartPortaudioStream(): boolean;

      function InitializeSDLAudio(): boolean;
      function StartSDLAudioStream(): boolean;
      procedure StopSDLAudioStream();
    public
      function  GetName: String;

      function InitializePlayback(): boolean;
      destructor Destroy; override;

      function Load(const Filename: String): TPortaudioPlaybackStream;

      procedure SetVolume(Volume: integer);
      procedure SetMusicVolume(Volume: integer);
      procedure SetLoop(Enabled: boolean);
      function Open(Filename: string): boolean; // true if succeed
      procedure Rewind;
      procedure SetPosition(Time: real);
      procedure Play;
      procedure Pause;

      procedure Stop;
      procedure Close;
      function Finished: boolean;
      function Length: real;
      function GetPosition: real;

      // Equalizer
      procedure GetFFTData(var data: TFFTData);

      // Interface for Visualizer
      function GetPCMData(var data: TPCMData): Cardinal;

      // Sounds
      function OpenSound(const Filename: String): TAudioPlaybackStream;
      procedure PlaySound(stream: TAudioPlaybackStream);
      procedure StopSound(stream: TAudioPlaybackStream);
  end;


function AudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
      userData: Pointer): Integer; cdecl; forward;

var
  singleton_AudioPlaybackPortaudio : IAudioPlayback;


{ TAudioMixerStream }

constructor TAudioMixerStream.Create();
begin
  activeStreams := TList.Create;
  internalLock := SDL_CreateMutex();
  _volume := 100;
end;

destructor TAudioMixerStream.Destroy();
begin
  if assigned(mixerBuffer) then
    Freemem(mixerBuffer);
  activeStreams.Free;
  SDL_DestroyMutex(internalLock);
end;

procedure TAudioMixerStream.Lock();
begin
  SDL_mutexP(internalLock);
end;

procedure TAudioMixerStream.Unlock();
begin
  SDL_mutexV(internalLock);
end;

function TAudioMixerStream.GetVolume(): integer;
begin
  Lock();
  result := _volume;
  Unlock();
end;

procedure TAudioMixerStream.SetVolume(volume: integer);
begin
  Lock();
  _volume := volume;
  Unlock();
end;

procedure TAudioMixerStream.AddStream(stream: TAudioPlaybackStream);
begin
  if not assigned(stream) then
    Exit;

  Lock();
  // check if stream is already in list to avoid duplicates
  if (activeStreams.IndexOf(Pointer(stream)) = -1) then
    activeStreams.Add(Pointer(stream));
  Unlock();
end;

procedure TAudioMixerStream.RemoveStream(stream: TAudioPlaybackStream);
begin
  Lock();
  activeStreams.Remove(Pointer(stream));
  Unlock();
end;

function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer;
var
  i: integer;
  size: integer;
  stream: TPortaudioPlaybackStream;
  appVolume: single;
begin
  result := BufSize;

  // zero target-buffer (silence)
  FillChar(Buffer^, BufSize, 0);

  // resize mixer-buffer if necessary
  ReallocMem(mixerBuffer, BufSize);
  if not assigned(mixerBuffer) then
    Exit;

  Lock();

  //writeln('Mix: ' + inttostr(activeStreams.Count));

  // use _volume instead of Volume to prevent recursive locking 
  appVolume := _volume / 100 * SDL_MIX_MAXVOLUME;

  for i := 0 to activeStreams.Count-1 do
  begin
    stream := TPortaudioPlaybackStream(activeStreams[i]);
    if (stream.GetStatus() = ssPlaying) then
    begin
      // fetch data from current stream
      size := stream.ReadData(mixerBuffer, BufSize);
      if (size > 0) then
      begin
        SDL_MixAudio(PUInt8(Buffer), PUInt8(mixerBuffer), size,
          Trunc(appVolume * stream.Volume / 100));
      end;
    end;
  end;

  Unlock();
end;


{ TPortaudioPlaybackStream }

constructor TPortaudioPlaybackStream.Create();
begin
  inherited Create();
  Reset();
end;

destructor TPortaudioPlaybackStream.Destroy();
begin
  Close();
  inherited Destroy();
end;

procedure TPortaudioPlaybackStream.Reset();
begin
  Status := ssStopped;
  Loop := false;
  DecodeStream := nil;
  _volume := 0;
end;

function TPortaudioPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
begin
  result := false;

  Reset();

  if not assigned(decodeStream) then
    Exit;
  Self.DecodeStream := decodeStream;

  _volume := 100;

  result := true;
end;

procedure TPortaudioPlaybackStream.Close();
begin
  Reset();
end;

procedure TPortaudioPlaybackStream.Play();
begin
  if (status <> ssPaused) then
  begin
    // rewind
    if assigned(DecodeStream) then
      DecodeStream.Position := 0;
  end;
  status := ssPlaying;
  //MixerStream.AddStream(Self);
end;

procedure TPortaudioPlaybackStream.Pause();
begin
  status := ssPaused;
end;

procedure TPortaudioPlaybackStream.Stop();
begin
  status := ssStopped;
end;

function TPortaudioPlaybackStream.IsLoaded(): boolean;
begin
  result := assigned(DecodeStream);
end;

function TPortaudioPlaybackStream.GetLoop(): boolean;
begin
  result := Loop;
end;

procedure TPortaudioPlaybackStream.SetLoop(Enabled: boolean);
begin
  Loop := Enabled;
end;

function TPortaudioPlaybackStream.GetLength(): real;
begin
  if assigned(DecodeStream) then
    result := DecodeStream.Length
  else
    result := -1;
end;

function TPortaudioPlaybackStream.GetStatus(): TStreamStatus;
begin
  result := status;
end;

function TPortaudioPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
begin
  if not assigned(DecodeStream) then
  begin
    result := -1;
    Exit;
  end;
  result := DecodeStream.ReadData(Buffer, BufSize);
  // end-of-file reached -> stop playback
  if (DecodeStream.EOF) then
  begin
    status := ssStopped;
  end;
end;

function TPortaudioPlaybackStream.GetPosition: real;
begin
  if assigned(DecodeStream) then
    result := DecodeStream.Position
  else
    result := -1;
end;

procedure TPortaudioPlaybackStream.SetPosition(Time: real);
begin
  if assigned(DecodeStream) then
    DecodeStream.Position := Time;
end;

function TPortaudioPlaybackStream.GetVolume(): integer;
begin
  result := _volume;
end;

procedure TPortaudioPlaybackStream.SetVolume(volume: integer);
begin
  // clamp volume
  if (volume > 100) then
    _volume := 100
  else if (volume < 0) then
    _volume := 0
  else
    _volume := volume;
end;


{ TAudioPlayback_Portaudio }

function AudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
    timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
    userData: Pointer): Integer; cdecl;
var
  playback: TAudioPlayback_Portaudio;
begin
  playback := TAudioPlayback_Portaudio(userData);
  with playback do
  begin
    MixerStream.ReadData(output, frameCount * FrameSize);
  end;
  result := paContinue;
end;

procedure SDLAudioCallback(userdata: Pointer; stream: PChar; len: integer); cdecl;
var
  playback: TAudioPlayback_Portaudio;
begin
  playback := TAudioPlayback_Portaudio(userdata);
  with playback do
  begin
    //MixerStream.ReadData(stream, len);
  end;
end;

function TAudioPlayback_Portaudio.GetName: String;
begin
  result := 'Portaudio_Playback';
end;

function TAudioPlayback_Portaudio.InitializePortaudio(): boolean;
var
  paApi           : TPaHostApiIndex;
  paApiInfo       : PPaHostApiInfo;
  paOutParams     : TPaStreamParameters;
  paOutDevice     : TPaDeviceIndex;
  paOutDeviceInfo : PPaDeviceInfo;
  err             : TPaError;
begin
  result := false;

  Pa_Initialize();

  // FIXME: determine automatically
  {$IFDEF WIN32}
  paApi := Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
  {$ELSE}
  paApi := Pa_HostApiTypeIdToHostApiIndex(paALSA);
  {$ENDIF}
  if (paApi < 0) then
  begin
    Log.LogStatus('Pa_HostApiTypeIdToHostApiIndex: '+Pa_GetErrorText(paApi), 'UAudioPlayback_Portaudio');
    exit;
  end;

  paApiInfo := Pa_GetHostApiInfo(paApi);
  paOutDevice     := paApiInfo^.defaultOutputDevice;
  paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice);

  with paOutParams do begin
    device := paOutDevice;
    channelCount := 2;
    sampleFormat := paInt16;
    suggestedLatency := paOutDeviceInfo^.defaultHighOutputLatency;
    hostApiSpecificStreamInfo := nil;
  end;

  // set the size of one audio frame (2channel 16bit uint sample)
  FrameSize := 2 * sizeof(Smallint);

  err := Pa_OpenStream(paStream, nil, @paOutParams, 44100,
          paFramesPerBufferUnspecified,
          paNoFlag, @AudioCallback, Self);
  if(err <> paNoError) then begin
    Log.LogStatus('Pa_OpenStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
    exit;
  end;

  Log.LogStatus('Opened audio device', 'UAudioPlayback_Portaudio');

  result := true;
end;

function TAudioPlayback_Portaudio.StartPortaudioStream(): boolean;
var
  err: TPaError;
begin
  result := false;

  err := Pa_StartStream(paStream);
  if(err <> paNoError) then
  begin
    Log.LogStatus('Pa_StartStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
    exit;
  end;

  result := true;
end;

function TAudioPlayback_Portaudio.InitializeSDLAudio(): boolean;
var
  desiredAudioSpec, obtainedAudioSpec: TSDL_AudioSpec;
  err: integer;
begin
  result := false;

  SDL_InitSubSystem(SDL_INIT_AUDIO);

  FillChar(desiredAudioSpec, sizeof(desiredAudioSpec), 0);
  with desiredAudioSpec do
  begin
    freq := 44100;
    format := AUDIO_S16SYS;
    channels := 2;
    samples := 1024; // latency: 23 ms
    callback := @SDLAudioCallback;
    userdata := Self;
  end;

  // set the size of one audio frame (2channel 16bit uint sample)
  FrameSize := 2 * sizeof(Smallint);

  if(SDL_OpenAudio(@desiredAudioSpec, @obtainedAudioSpec) = -1) then
  begin
    Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'UAudioPlayback_SDL');
    exit;
  end;

  Log.LogStatus('Opened audio device', 'UAudioPlayback_SDL');

  result := true;
end;

function TAudioPlayback_Portaudio.StartSDLAudioStream(): boolean;
begin
  SDL_PauseAudio(0);
  result := true;
end;

procedure TAudioPlayback_Portaudio.StopSDLAudioStream();
begin
  SDL_CloseAudio();
end;

function TAudioPlayback_Portaudio.InitializePlayback: boolean;
begin
  result := false;

  //Log.LogStatus('InitializePlayback', 'UAudioPlayback_Portaudio');

  //if(not InitializePortaudio()) then
  if(not InitializeSDLAudio()) then
    Exit;

  MixerStream := TAudioMixerStream.Create;

  //if(not StartPortaudioStream()) then;
  if(not StartSDLAudioStream()) then
    Exit;

  result := true;
end;

destructor TAudioPlayback_Portaudio.Destroy;
begin
  StopSDLAudioStream();

  MixerStream.Free();
  MusicStream.Free();

  inherited Destroy();
end;

function TAudioPlayback_Portaudio.Load(const Filename: String): TPortaudioPlaybackStream;
var
  decodeStream: TAudioDecodeStream;
  playbackStream: TPortaudioPlaybackStream;
begin
  Result := nil;

  decodeStream := AudioDecoder.Open(Filename);
  if not assigned(decodeStream) then
  begin
    Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudioPlayback_Portaudio');
    Exit;
  end;

  playbackStream := TPortaudioPlaybackStream.Create();
  if (not playbackStream.SetDecodeStream(decodeStream)) then
    Exit;

  // FIXME: remove this line
  MixerStream.AddStream(playbackStream);

  result := playbackStream;
end;

procedure TAudioPlayback_Portaudio.SetVolume(Volume: integer);
begin
  // sets volume only for this application
  MixerStream.Volume := Volume;
end;

procedure TAudioPlayback_Portaudio.SetMusicVolume(Volume: Integer);
begin
  if assigned(MusicStream) then
    MusicStream.Volume := Volume;
end;

procedure TAudioPlayback_Portaudio.SetLoop(Enabled: boolean);
begin
  if assigned(MusicStream) then
    MusicStream.SetLoop(Enabled);
end;

function TAudioPlayback_Portaudio.Open(Filename: string): boolean;
var
  decodeStream: TAudioDecodeStream;
begin
  Result := false;

  // free old MusicStream
  MusicStream.Free();

  MusicStream := Load(Filename);
  if not assigned(MusicStream) then
    Exit;

  //Set Max Volume
  SetMusicVolume(100);

  Result := true;
end;

procedure TAudioPlayback_Portaudio.Rewind;
begin
  SetPosition(0);
end;

procedure TAudioPlayback_Portaudio.SetPosition(Time: real);
begin
  if assigned(MusicStream) then
    MusicStream.SetPosition(Time);
end;

function TAudioPlayback_Portaudio.GetPosition: real;
begin
  if assigned(MusicStream) then
    Result := MusicStream.GetPosition()
  else
    Result := -1;
end;

function TAudioPlayback_Portaudio.Length: real;
begin
  if assigned(MusicStream) then
    Result := MusicStream.GetLength()
  else
    Result := -1;
end;

procedure TAudioPlayback_Portaudio.Play;
begin
  if assigned(MusicStream) then
    MusicStream.Play();
end;

procedure TAudioPlayback_Portaudio.Pause;
begin
  if assigned(MusicStream) then
    MusicStream.Pause();
end;

procedure TAudioPlayback_Portaudio.Stop;
begin
  if assigned(MusicStream) then
    MusicStream.Stop();
end;

procedure TAudioPlayback_Portaudio.Close;
begin
  if assigned(MusicStream) then
  begin
    MixerStream.RemoveStream(MusicStream);
    MusicStream.Close();
  end;
end;

function TAudioPlayback_Portaudio.Finished: boolean;
begin
  if assigned(MusicStream) then
    Result := (MusicStream.GetStatus() = ssStopped)
  else
    Result := true;
end;

//Equalizer
procedure TAudioPlayback_Portaudio.GetFFTData(var data: TFFTData);
begin
  //Get Channel Data Mono and 256 Values
//  BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512);
end;

// Interface for Visualizer
function TAudioPlayback_Portaudio.GetPCMData(var data: TPCMData): Cardinal;
begin
  result := 0;
end;

function TAudioPlayback_Portaudio.OpenSound(const Filename: String): TAudioPlaybackStream;
begin
  result := Load(Filename);
end;

procedure TAudioPlayback_Portaudio.PlaySound(stream: TAudioPlaybackStream);
begin
  if assigned(stream) then
    stream.Play();
end;

procedure TAudioPlayback_Portaudio.StopSound(stream: TAudioPlaybackStream);
begin
  if assigned(stream) then
    stream.Stop();
end;


initialization
  singleton_AudioPlaybackPortaudio := TAudioPlayback_Portaudio.create();
  AudioManager.add( singleton_AudioPlaybackPortaudio );

finalization
  AudioManager.Remove( singleton_AudioPlaybackPortaudio );


end.