unit UAudioPlayback_Portaudio;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses Classes,
SysUtils,
UMusic;
implementation
uses
{$IFDEF LAZARUS}
lclintf,
{$ifndef win32}
libc,
{$endif}
{$ENDIF}
sdl,
portaudio,
ULog,
UIni,
UMain;
type
TPortaudioPlaybackStream = class(TAudioPlaybackStream)
private
status: TStreamStatus;
Loaded: boolean;
Loop: boolean;
public
decodeStream: TAudioDecodeStream;
constructor Create(decodeStream: TAudioDecodeStream);
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;
// 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;
public
constructor Create();
destructor Destroy(); override;
procedure AddStream(stream: TAudioPlaybackStream);
procedure RemoveStream(stream: TAudioPlaybackStream);
function ReadData(Buffer: PChar; BufSize: integer): integer;
end;
type
TAudioPlayback_Portaudio = class( TInterfacedObject, IAudioPlayback )
private
MusicStream: TPortaudioPlaybackStream;
StartSoundStream: TPortaudioPlaybackStream;
BackSoundStream: TPortaudioPlaybackStream;
SwooshSoundStream: TPortaudioPlaybackStream;
ChangeSoundStream: TPortaudioPlaybackStream;
OptionSoundStream: TPortaudioPlaybackStream;
ClickSoundStream: TPortaudioPlaybackStream;
DrumSoundStream: TPortaudioPlaybackStream;
HihatSoundStream: TPortaudioPlaybackStream;
ClapSoundStream: TPortaudioPlaybackStream;
ShuffleSoundStream: TPortaudioPlaybackStream;
//Custom Sounds
CustomSounds: array of TCustomSoundEntry;
mixerStream: TAudioMixerStream;
paStream: PPaStream;
public
FrameSize: integer;
function GetName: String;
function InitializePortaudio(): boolean;
function StartPortaudioStream(): boolean;
procedure InitializePlayback();
procedure SetVolume(Volume: integer);
procedure SetMusicVolume(Volume: integer);
procedure SetLoop(Enabled: boolean);
function Open(Filename: string): boolean; // true if succeed
function Load(Filename: string): TPortaudioPlaybackStream;
procedure Rewind;
procedure SetPosition(Time: real);
procedure Play;
procedure Pause;
procedure Stop;
procedure Close;
function Finished: boolean;
function Length: real;
function GetPosition: real;
procedure PlayStart;
procedure PlayBack;
procedure PlaySwoosh;
procedure PlayChange;
procedure PlayOption;
procedure PlayClick;
procedure PlayDrum;
procedure PlayHihat;
procedure PlayClap;
procedure PlayShuffle;
procedure StopShuffle;
//Equalizer
function GetFFTData: TFFTData;
// Interface for Visualizer
function GetPCMData(var data: TPCMData): Cardinal;
//Custom Sounds
function LoadCustomSound(const Filename: String): Cardinal;
procedure PlayCustomSound(const Index: Cardinal );
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;
end;
destructor TAudioMixerStream.Destroy();
begin
if (mixerBuffer <> nil) then
Freemem(mixerBuffer);
activeStreams.Free;
end;
procedure TAudioMixerStream.AddStream(stream: TAudioPlaybackStream);
begin
// check if stream is already in list to avoid duplicates
if (activeStreams.IndexOf(Pointer(stream)) = -1) then
activeStreams.Add(Pointer(stream));
end;
procedure TAudioMixerStream.RemoveStream(stream: TAudioPlaybackStream);
begin
activeStreams.Remove(Pointer(stream));
end;
function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer;
var
i: integer;
size: integer;
stream: TPortaudioPlaybackStream;
begin
result := BufSize;
// zero target-buffer (silence)
FillChar(Buffer^, BufSize, 0);
// resize mixer-buffer
ReallocMem(mixerBuffer, BufSize);
if (mixerBuffer = nil) then
Exit;
writeln('Mix: ' + inttostr(activeStreams.Count));
for i := 0 to activeStreams.Count-1 do
begin
stream := TPortaudioPlaybackStream(activeStreams[i]);
if (stream.GetStatus() = sPlaying) then
begin
// fetch data from current stream
size := stream.ReadData(mixerBuffer, BufSize);
if (size > 0) then
begin
SDL_MixAudio(PUInt8(Buffer), PUInt8(mixerBuffer), size, SDL_MIX_MAXVOLUME);
end;
end;
end;
end;
{ TPortaudioPlaybackStream }
constructor TPortaudioPlaybackStream.Create(decodeStream: TAudioDecodeStream);
begin
inherited Create();
status := sStopped;
if (decodeStream <> nil) then
begin
Self.decodeStream := decodeStream;
Loaded := true;
end;
end;
procedure TPortaudioPlaybackStream.Play();
begin
if (status <> sPaused) then
begin
// rewind
decodeStream.Position := 0;
end;
status := sPlaying;
//mixerStream.AddStream(Self);
end;
procedure TPortaudioPlaybackStream.Pause();
begin
status := sPaused;
end;
procedure TPortaudioPlaybackStream.Stop();
begin
status := sStopped;
end;
procedure TPortaudioPlaybackStream.Close();
begin
status := sStopped;
Loaded := false;
// TODO: cleanup decode-stream
end;
function TPortaudioPlaybackStream.IsLoaded(): boolean;
begin
result := Loaded;
end;
function TPortaudioPlaybackStream.GetLoop(): boolean;
begin
result := Loop;
end;
procedure TPortaudioPlaybackStream.SetLoop(Enabled: boolean);
begin
Loop := Enabled;
end;
function TPortaudioPlaybackStream.GetLength(): real;
begin
result := decodeStream.Length;
end;
function TPortaudioPlaybackStream.GetStatus(): TStreamStatus;
begin
result := status;
end;
function TPortaudioPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
begin
result := decodeStream.ReadData(Buffer, BufSize);
// end-of-file reached -> stop playback
if (decodeStream.EOF) then
begin
status := sStopped;
end;
end;
function TPortaudioPlaybackStream.GetPosition: real;
begin
result := decodeStream.Position;
end;
procedure TPortaudioPlaybackStream.SetPosition(Time: real);
begin
decodeStream.Position := Time;
end;
{ TAudioPlayback_Portaudio }
function AudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
userData: Pointer): Integer; cdecl;
var
playback : TAudioPlayback_Portaudio;
playbackStream : TPortaudioPlaybackStream;
decodeStream : TAudioDecodeStream;
begin
playback := TAudioPlayback_Portaudio(userData);
with playback do
begin
mixerStream.ReadData(output, frameCount * FrameSize);
end;
result := paContinue;
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^.defaultLowOutputLatency;
hostApiSpecificStreamInfo := nil;
end;
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;
procedure TAudioPlayback_Portaudio.InitializePlayback;
begin
Log.LogStatus('InitializePlayback', 'UAudioPlayback_Portaudio');
InitializePortaudio();
mixerStream := TAudioMixerStream.Create;
StartSoundStream := Load(SoundPath + 'Common start.mp3');
BackSoundStream := Load(SoundPath + 'Common back.mp3');
SwooshSoundStream := Load(SoundPath + 'menu swoosh.mp3');
ChangeSoundStream := Load(SoundPath + 'select music change music 50.mp3');
OptionSoundStream := Load(SoundPath + 'option change col.mp3');
ClickSoundStream := Load(SoundPath + 'rimshot022b.mp3');
// DrumSoundStream := Load(SoundPath + 'bassdrumhard076b.mp3');
// HihatSoundStream := Load(SoundPath + 'hihatclosed068b.mp3');
// ClapSoundStream := Load(SoundPath + 'claps050b.mp3');
// ShuffleSoundStream := Load(SoundPath + 'Shuffle.mp3');
StartPortaudioStream();
end;
procedure TAudioPlayback_Portaudio.SetVolume(Volume: integer);
begin
//New: Sets Volume only for this Application
(*
BASS_SetConfig(BASS_CONFIG_GVOL_SAMPLE, Volume);
BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Volume);
BASS_SetConfig(BASS_CONFIG_GVOL_MUSIC, Volume);
*)
end;
procedure TAudioPlayback_Portaudio.SetMusicVolume(Volume: Integer);
begin
//Max Volume Prevention
if Volume > 100 then
Volume := 100;
if Volume < 0 then
Volume := 0;
//Set Volume
// BASS_ChannelSetAttributes (Bass, -1, Volume, -101);
end;
procedure TAudioPlayback_Portaudio.SetLoop(Enabled: boolean);
begin
if (MusicStream <> nil) then
if (MusicStream.IsLoaded) then
MusicStream.SetLoop(Enabled);
end;
function TAudioPlayback_Portaudio.Open(Filename: string): boolean;
var
decodeStream: TAudioDecodeStream;
begin
decodeStream := AudioDecoder.Open(Filename);
MusicStream := TPortaudioPlaybackStream.Create(decodeStream);
// FIXME: remove this line
mixerStream.AddStream(MusicStream);
if(MusicStream.IsLoaded()) then
begin
//Set Max Volume
SetMusicVolume(100);
end;
Result := MusicStream.IsLoaded();
end;
procedure TAudioPlayback_Portaudio.Rewind;
begin
SetPosition(0);
end;
procedure TAudioPlayback_Portaudio.SetPosition(Time: real);
begin
if (MusicStream.IsLoaded) then
begin
MusicStream.SetPosition(Time);
end;
end;
function TAudioPlayback_Portaudio.GetPosition: real;
begin
if (MusicStream.IsLoaded) then
Result := MusicStream.GetPosition();
end;
function TAudioPlayback_Portaudio.Length: real;
begin
Result := 0;
if assigned( MusicStream ) then
if (MusicStream.IsLoaded) then
begin
Result := MusicStream.GetLength();
end;
end;
procedure TAudioPlayback_Portaudio.Play;
begin
if (MusicStream <> nil) then
if (MusicStream.IsLoaded) then
begin
if (MusicStream.GetLoop()) then
begin
end;
// start from beginning...
// actually bass itself does not loop, nor does this TAudio_FFMpeg Class
MusicStream.Play();
end;
end;
procedure TAudioPlayback_Portaudio.Pause;
begin
if (MusicStream <> nil) then
MusicStream.Pause();
end;
procedure TAudioPlayback_Portaudio.Stop;
begin
if MusicStream <> nil then
MusicStream.Stop();
end;
procedure TAudioPlayback_Portaudio.Close;
begin
if MusicStream <> nil then
MusicStream.Close();
end;
function TAudioPlayback_Portaudio.Finished: boolean;
begin
if MusicStream <> nil then
Result := (MusicStream.GetStatus() = sStopped);
end;
procedure TAudioPlayback_Portaudio.PlayStart;
begin
if StartSoundStream <> nil then
StartSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.PlayBack;
begin
if BackSoundStream <> nil then
BackSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.PlaySwoosh;
begin
if SwooshSoundStream <> nil then
SwooshSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.PlayChange;
begin
if ChangeSoundStream <> nil then
ChangeSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.PlayOption;
begin
if OptionSoundStream <> nil then
OptionSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.PlayClick;
begin
if ClickSoundStream <> nil then
ClickSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.PlayDrum;
begin
if DrumSoundStream <> nil then
DrumSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.PlayHihat;
begin
if HihatSoundStream <> nil then
HihatSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.PlayClap;
begin
if ClapSoundStream <> nil then
ClapSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.PlayShuffle;
begin
if ShuffleSoundStream <> nil then
ShuffleSoundStream.Play();
end;
procedure TAudioPlayback_Portaudio.StopShuffle;
begin
if ShuffleSoundStream <> nil then
ShuffleSoundStream.Stop();
end;
//Equalizer
function TAudioPlayback_Portaudio.GetFFTData: TFFTData;
var
data: TFFTData;
begin
//Get Channel Data Mono and 256 Values
// BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512);
result := data;
end;
// Interface for Visualizer
function TAudioPlayback_Portaudio.GetPCMData(var data: TPCMData): Cardinal;
begin
result := 0;
end;
function TAudioPlayback_Portaudio.Load(Filename: string): TPortaudioPlaybackStream;
var
decodeStream : TAudioDecodeStream;
playbackStream : TPortaudioPlaybackStream;
csIndex : integer;
begin
result := nil;
decodeStream := AudioDecoder.Open(Filename);
if (decodeStream = nil) then
begin
Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudioPlayback_Portaudio');
exit;
end;
playbackStream := TPortaudioPlaybackStream.Create(decodeStream);
// FIXME: remove this line
mixerStream.AddStream(playbackStream);
//Add CustomSound
csIndex := High(CustomSounds) + 1;
SetLength(CustomSounds, csIndex + 1);
CustomSounds[csIndex].Filename := Filename;
CustomSounds[csIndex].Stream := playbackStream;
result := playbackStream;
end;
function TAudioPlayback_Portaudio.LoadCustomSound(const Filename: String): Cardinal;
var
S: TAudioPlaybackStream;
I: Integer;
F: String;
begin
//Search for Sound in already loaded Sounds
F := UpperCase(SoundPath + FileName);
For I := 0 to High(CustomSounds) do
begin
if (UpperCase(CustomSounds[I].Filename) = F) then
begin
Result := I;
Exit;
end;
end;
S := Load(SoundPath + Filename);
if (S <> nil) then
Result := High(CustomSounds)
else
Result := 0;
end;
procedure TAudioPlayback_Portaudio.PlayCustomSound(const Index: Cardinal );
begin
if (Index <= High(CustomSounds)) then
CustomSounds[Index].Stream.Play();
end;
initialization
singleton_AudioPlaybackPortaudio := TAudioPlayback_Portaudio.create();
AudioManager.add( singleton_AudioPlaybackPortaudio );
finalization
AudioManager.Remove( singleton_AudioPlaybackPortaudio );
end.