aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes/UAudioPlayback_Portaudio.pas
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Game/Code/Classes/UAudioPlayback_Portaudio.pas728
1 files changed, 728 insertions, 0 deletions
diff --git a/Game/Code/Classes/UAudioPlayback_Portaudio.pas b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
new file mode 100644
index 00000000..59571d3d
--- /dev/null
+++ b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
@@ -0,0 +1,728 @@
+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.