diff options
Diffstat (limited to '')
-rw-r--r-- | Game/Code/Classes/UAudioPlayback_Portaudio.pas | 728 |
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. |