aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Game/Code/Classes/UAudioDecoder_FFMpeg.pas600
-rw-r--r--Game/Code/Classes/UAudioInput_Bass.pas286
-rw-r--r--Game/Code/Classes/UAudioInput_Portaudio.pas420
-rw-r--r--Game/Code/Classes/UAudioPlayback_Bass.pas460
-rw-r--r--Game/Code/Classes/UAudioPlayback_Portaudio.pas664
5 files changed, 2430 insertions, 0 deletions
diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
new file mode 100644
index 00000000..54055454
--- /dev/null
+++ b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
@@ -0,0 +1,600 @@
+unit UAudioDecoder_FFMpeg;
+
+(*******************************************************************************
+
+This unit is primarily based upon -
+ http://www.dranger.com/ffmpeg/ffmpegtutorial_all.html
+
+ and tutorial03.c
+
+ http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html
+
+*******************************************************************************)
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+
+uses Classes,
+ {$IFDEF win32}
+ windows,
+ {$ENDIF}
+ SysUtils,
+ SDL,
+ avcodec, // FFMpeg Audio file decoding
+ avformat,
+ avutil,
+ ULog,
+ UMusic;
+
+implementation
+
+uses
+ {$IFDEF LAZARUS}
+ lclintf,
+ {$ifndef win32}
+ libc,
+ {$endif}
+ {$ENDIF}
+ UIni,
+ UMain,
+ UThemes;
+
+
+type
+ PPacketQueue = ^TPacketQueue;
+ TPacketQueue = class
+ private
+ firstPkt,
+ lastPkt : PAVPacketList;
+ nbPackets : integer;
+ size : integer;
+ mutex : PSDL_Mutex;
+ cond : PSDL_Cond;
+ quit : boolean;
+
+ public
+ constructor Create();
+ destructor Destroy(); override;
+
+ function Put(pkt : PAVPacket): integer;
+ function Get(var pkt: TAVPacket; block: boolean): integer;
+ end;
+
+var
+ EOSPacket: TAVPacket;
+
+type
+ PAudioBuffer = ^TAudioBuffer;
+ TAudioBuffer = array[0 .. (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3 div 2)-1] of byte;
+
+type
+ TFFMpegDecodeStream = class(TAudioDecodeStream)
+ private
+ status: TStreamStatus;
+
+ EOS_Flag: boolean; // end-of-stream flag
+
+ parseThread: PSDL_Thread;
+ packetQueue: TPacketQueue;
+
+ // FFMpeg internal data
+ pFormatCtx : PAVFormatContext;
+ pCodecCtx : PAVCodecContext;
+ pCodec : PAVCodec;
+ ffmpegStreamID : Integer;
+ ffmpegStream : PAVStream;
+
+ // "static" vars for DecodeFrame
+ pkt : TAVPacket;
+ audio_pkt_data : PChar;
+ audio_pkt_size : integer;
+
+ // "static" vars for AudioCallback
+ audio_buf_index : cardinal;
+ audio_buf_size : cardinal;
+ audio_buf : TAudioBuffer;
+
+ function DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
+ public
+ constructor Create(pFormatCtx: PAVFormatContext;
+ pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
+ ffmpegStreamID : Integer; ffmpegStream: PAVStream);
+ destructor Destroy(); override;
+
+ procedure Close(); override;
+
+ function GetLength(): real; override;
+ function GetChannelCount(): cardinal; override;
+ function GetSampleRate(): cardinal; override;
+ function GetPosition: real; override;
+ procedure SetPosition(Time: real); override;
+ function IsEOS(): boolean; override;
+
+ function ReadData(Buffer: PChar; BufSize: integer): integer; override;
+ end;
+
+type
+ TAudioDecoder_FFMpeg = class( TInterfacedObject, IAudioDecoder )
+ private
+ class function FindAudioStreamID(pFormatCtx : PAVFormatContext): integer;
+ public
+ function GetName: String;
+
+ function InitializeDecoder(): boolean;
+ function Open(const Filename: string): TAudioDecodeStream;
+ end;
+
+function ParseAudio(streamPtr: Pointer): integer; cdecl; forward;
+
+var
+ singleton_AudioDecoderFFMpeg : IAudioDecoder;
+
+
+{ TFFMpegDecodeStream }
+
+constructor TFFMpegDecodeStream.Create(pFormatCtx: PAVFormatContext;
+ pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
+ ffmpegStreamID : Integer; ffmpegStream: PAVStream);
+begin
+ inherited Create();
+
+ status := sStopped;
+ packetQueue := TPacketQueue.Create();
+
+ audio_pkt_data := nil;
+ audio_pkt_size := 0;
+
+ audio_buf_index := 0;
+ audio_buf_size := 0;
+
+ FillChar(pkt, sizeof(TAVPacket), #0);
+
+ Self.pFormatCtx := pFormatCtx;
+ Self.pCodecCtx := pCodecCtx;
+ Self.pCodec := pCodec;
+ Self.ffmpegStreamID := ffmpegStreamID;
+ Self.ffmpegStream := ffmpegStream;
+
+ EOS_Flag := false;
+
+ parseThread := SDL_CreateThread(@ParseAudio, Self);
+end;
+
+destructor TFFMpegDecodeStream.Destroy();
+begin
+ packetQueue.Free();
+ //SDL_WaitThread(parseThread, nil);
+ inherited;
+end;
+
+procedure TFFMpegDecodeStream.Close();
+begin
+ // Close the codec
+ avcodec_close(pCodecCtx);
+
+ // Close the video file
+ av_close_input_file(pFormatCtx);
+
+ // TODO: abort thread
+end;
+
+function TFFMpegDecodeStream.GetLength(): real;
+begin
+ result := pFormatCtx^.duration / AV_TIME_BASE;
+end;
+
+function TFFMpegDecodeStream.GetChannelCount(): cardinal;
+begin
+ result := pCodecCtx^.channels;
+end;
+
+function TFFMpegDecodeStream.GetSampleRate(): cardinal;
+begin
+ result := pCodecCtx^.sample_rate;
+end;
+
+function TFFMpegDecodeStream.IsEOS(): boolean;
+begin
+ result := EOS_Flag;
+end;
+
+
+function ParseAudio(streamPtr: Pointer): integer; cdecl;
+var
+ packet: TAVPacket;
+ stream: TFFMpegDecodeStream;
+begin
+ stream := TFFMpegDecodeStream(streamPtr);
+
+ while (av_read_frame(stream.pFormatCtx, packet) >= 0) do
+ begin
+ //writeln( 'ffmpeg - av_read_frame' );
+
+ if (packet.stream_index = stream.ffmpegStreamID) then
+ begin
+ //writeln( 'packet_queue_put' );
+ stream.packetQueue.put(@packet);
+ end
+ else
+ begin
+ av_free_packet(packet);
+ end;
+ end;
+
+ //Writeln('Done: ' + inttostr(stream.packetQueue.nbPackets));
+
+ // signal end-of-stream
+ stream.packetQueue.put(@EOSPacket);
+
+ result := 0;
+end;
+
+function TFFMpegDecodeStream.DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
+var
+ len1,
+ data_size: integer;
+begin
+ result := -1;
+
+ if (EOS_Flag) then
+ exit;
+
+ while true do
+ begin
+ while (audio_pkt_size > 0) do
+ begin
+ //writeln( 'got audio packet' );
+ data_size := bufSize;
+
+ // TODO: should be avcodec_decode_audio2 but this wont link on my ubuntu box.
+ len1 := avcodec_decode_audio(pCodecCtx, @buffer,
+ data_size, audio_pkt_data, audio_pkt_size);
+
+ //writeln('avcodec_decode_audio : ' + inttostr( len1 ));
+
+ if(len1 < 0) then
+ begin
+ // if error, skip frame
+ //writeln( 'Skip audio frame' );
+ audio_pkt_size := 0;
+ break;
+ end;
+
+ Inc(audio_pkt_data, len1);
+ Dec(audio_pkt_size, len1);
+
+ if (data_size <= 0) then
+ begin
+ // No data yet, get more frames
+ continue;
+ end;
+
+ // We have data, return it and come back for more later
+ result := data_size;
+ exit;
+ end;
+
+ if (pkt.data <> nil) then
+ begin
+ av_free_packet(pkt);
+ end;
+
+ if (packetQueue.quit) then
+ exit;
+
+ if (packetQueue.Get(pkt, true) < 0) then
+ exit;
+
+ audio_pkt_data := PChar(pkt.data);
+ audio_pkt_size := pkt.size;
+
+ // check for end-of-stream
+ if (audio_pkt_data = PChar(EOSPacket.data)) then
+ begin
+ // end-of-stream reached -> set EOS-flag
+ EOS_Flag := true;
+ // note: buffer is not (even partially) filled -> no data to return
+ exit;
+ end;
+
+ //writeln( 'Audio Packet Size - ' + inttostr(audio_pkt_size) );
+ end;
+end;
+
+function TFFMpegDecodeStream.ReadData(Buffer : PChar; BufSize: integer): integer;
+var
+ outStream : TFFMpegDecodeStream;
+ len1,
+ audio_size : integer;
+ pSrc : Pointer;
+ len : integer;
+begin
+ len := BufSize;
+
+ // end-of-stream reached
+ if (EOS_Flag) then
+ exit;
+
+ while (len > 0) do begin
+ if (audio_buf_index >= audio_buf_size) then
+ begin
+ // We have already sent all our data; get more
+ audio_size := DecodeFrame(audio_buf, sizeof(TAudioBuffer));
+ //writeln('audio_decode_frame : '+ inttostr(audio_size));
+
+ if(audio_size < 0) then
+ begin
+ // If error, output silence
+ audio_buf_size := 1024;
+ FillChar(audio_buf, audio_buf_size, #0);
+ //writeln( 'Silence' );
+ end
+ else
+ begin
+ audio_buf_size := audio_size;
+ end;
+ audio_buf_index := 0;
+ end;
+
+ len1 := audio_buf_size - audio_buf_index;
+ if (len1 > len) then
+ len1 := len;
+
+ pSrc := PChar(@audio_buf) + audio_buf_index;
+ {$ifdef WIN32}
+ CopyMemory(Buffer, pSrc , len1);
+ {$else}
+ memcpy(Buffer, pSrc , len1);
+ {$endif}
+
+ Dec(len, len1);
+ Inc(PChar(Buffer), len1);
+ Inc(audio_buf_index, len1);
+ end;
+
+ result := BufSize;
+end;
+
+function TFFMpegDecodeStream.GetPosition(): real;
+var
+ bytes: integer;
+begin
+ Result := 0;
+end;
+
+procedure TFFMpegDecodeStream.SetPosition(Time: real);
+var
+ bytes: integer;
+begin
+end;
+
+
+{ TAudioDecoder_FFMpeg }
+
+function TAudioDecoder_FFMpeg.GetName: String;
+begin
+ result := 'FFMpeg_Decoder';
+end;
+
+function TAudioDecoder_FFMpeg.InitializeDecoder: boolean;
+begin
+ //Log.LogStatus('InitializeDecoder', 'UAudioDecoder_FFMpeg');
+
+ av_register_all();
+
+ // init end-of-stream package
+ av_init_packet(EOSPacket);
+ EOSPacket.data := Pointer(PChar('EOS'));
+
+ result := true;
+end;
+
+class function TAudioDecoder_FFMpeg.FindAudioStreamID(pFormatCtx : PAVFormatContext): integer;
+var
+ i : integer;
+ streamID: integer;
+ stream : PAVStream;
+begin
+ // Find the first audio stream
+ streamID := -1;
+
+ for i := 0 to pFormatCtx^.nb_streams-1 do
+ begin
+ //Log.LogStatus('aFormatCtx.streams[i] : ' + inttostr(i), 'UAudio_FFMpeg');
+ stream := pFormatCtx^.streams[i];
+
+ if ( stream.codec^.codec_type = CODEC_TYPE_AUDIO ) then
+ begin
+ //Log.LogStatus('Found Audio Stream', 'UAudio_FFMpeg');
+ streamID := i;
+ break;
+ end;
+ end;
+
+ result := streamID;
+end;
+
+function TAudioDecoder_FFMpeg.Open(const Filename: string): TAudioDecodeStream;
+var
+ pFormatCtx : PAVFormatContext;
+ pCodecCtx : PAVCodecContext;
+ pCodec : PAVCodec;
+ ffmpegStreamID : Integer;
+ ffmpegStream : PAVStream;
+ wanted_spec,
+ csIndex : integer;
+ stream : TFFMpegDecodeStream;
+begin
+ result := nil;
+
+ if (not FileExists(Filename)) then
+ begin
+ Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudio_FFMpeg');
+ exit;
+ end;
+
+ // Open audio file
+ if (av_open_input_file(pFormatCtx, PChar(Filename), nil, 0, nil) > 0) then
+ exit;
+
+ // Retrieve stream information
+ if (av_find_stream_info(pFormatCtx) < 0) then
+ exit;
+
+ dump_format(pFormatCtx, 0, pchar(Filename), 0);
+
+ ffmpegStreamID := FindAudioStreamID(pFormatCtx);
+ if (ffmpegStreamID < 0) then
+ exit;
+
+ //Log.LogStatus('Audio Stream ID is : '+ inttostr(ffmpegStreamID), 'UAudio_FFMpeg');
+
+ ffmpegStream := pFormatCtx.streams[ffmpegStreamID];
+ pCodecCtx := ffmpegStream^.codec;
+
+ pCodec := avcodec_find_decoder(pCodecCtx^.codec_id);
+ if (pCodec = nil) then
+ begin
+ Log.LogStatus('Unsupported codec!', 'UAudio_FFMpeg');
+ exit;
+ end;
+
+ avcodec_open(pCodecCtx, pCodec);
+ //writeln( 'Opened the codec' );
+
+ stream := TFFMpegDecodeStream.Create(pFormatCtx, pCodecCtx, pCodec,
+ ffmpegStreamID, ffmpegStream);
+
+ result := stream;
+end;
+
+
+{ TPacketQueue }
+
+constructor TPacketQueue.Create();
+begin
+ inherited;
+
+ firstPkt := nil;
+ lastPkt := nil;
+ nbPackets := 0;
+ size := 0;
+
+ mutex := SDL_CreateMutex();
+ cond := SDL_CreateCond();
+end;
+
+destructor TPacketQueue.Destroy();
+begin
+ SDL_DestroyMutex(mutex);
+ SDL_DestroyCond(cond);
+ inherited;
+end;
+
+function TPacketQueue.Put(pkt : PAVPacket): integer;
+var
+ pkt1 : PAVPacketList;
+begin
+ result := -1;
+
+ if (pkt <> @EOSPacket) then
+ if (av_dup_packet(pkt) < 0) then
+ exit;
+
+ pkt1 := av_malloc(sizeof(TAVPacketList));
+ if (pkt1 = nil) then
+ exit;
+
+ pkt1^.pkt := pkt^;
+ pkt1^.next := nil;
+
+
+ SDL_LockMutex(Self.mutex);
+ try
+
+ if (Self.lastPkt = nil) then
+ Self.firstPkt := pkt1
+ else
+ Self.lastPkt^.next := pkt1;
+
+ Self.lastPkt := pkt1;
+ inc(Self.nbPackets);
+
+ //Writeln('Put: ' + inttostr(nbPackets));
+
+ Self.size := Self.size + pkt1^.pkt.size;
+ SDL_CondSignal(Self.cond);
+
+ finally
+ SDL_UnlockMutex(Self.mutex);
+ end;
+
+ result := 0;
+end;
+
+function TPacketQueue.Get(var pkt: TAVPacket; block: boolean): integer;
+var
+ pkt1 : PAVPacketList;
+begin
+ result := -1;
+
+ SDL_LockMutex(Self.mutex);
+ try
+ while true do
+ begin
+ if (quit) then
+ exit;
+
+ pkt1 := Self.firstPkt;
+
+ if (pkt1 <> nil) then
+ begin
+ Self.firstPkt := pkt1.next;
+ if (Self.firstPkt = nil) then
+ Self.lastPkt := nil;
+ dec(Self.nbPackets);
+
+ //Writeln('Get: ' + inttostr(nbPackets));
+
+ Self.size := Self.size - pkt1^.pkt.size;
+ pkt := pkt1^.pkt;
+ av_free(pkt1);
+
+ result := 1;
+ break;
+ end
+ else
+ if (not block) then
+ begin
+ result := 0;
+ break;
+ end
+ else
+ begin
+ SDL_CondWait(Self.cond, Self.mutex);
+ end;
+ end;
+ finally
+ SDL_UnlockMutex(Self.mutex);
+ end;
+end;
+
+
+
+initialization
+ singleton_AudioDecoderFFMpeg := TAudioDecoder_FFMpeg.create();
+
+ //writeln( 'UAudioDecoder_FFMpeg - Register Decoder' );
+ AudioManager.add( singleton_AudioDecoderFFMpeg );
+
+finalization
+ AudioManager.Remove( singleton_AudioDecoderFFMpeg );
+
+
+end.
diff --git a/Game/Code/Classes/UAudioInput_Bass.pas b/Game/Code/Classes/UAudioInput_Bass.pas
new file mode 100644
index 00000000..9807ffc3
--- /dev/null
+++ b/Game/Code/Classes/UAudioInput_Bass.pas
@@ -0,0 +1,286 @@
+unit UAudioInput_Bass;
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+
+uses Classes,
+ {$IFDEF win32}
+ windows,
+ {$ENDIF}
+ SysUtils,
+ bass,
+ ULog,
+ UMusic;
+
+implementation
+
+uses
+ {$IFDEF LAZARUS}
+ lclintf,
+ {$ENDIF}
+ URecord,
+ UIni,
+ UMain,
+ UCommon,
+ UThemes;
+
+type
+ TAudioInput_Bass = class( TInterfacedObject, IAudioInput)
+ private
+ public
+ function GetName: String;
+
+ {IAudioInput interface}
+ procedure InitializeRecord;
+
+ procedure CaptureStart;
+ procedure CaptureStop;
+ procedure CaptureCard(Card: byte; CaptureSoundLeft, CaptureSoundRight: TSound);
+ procedure StopCard(Card: byte);
+ end;
+
+ TBassSoundCard = class(TGenericSoundCard)
+ RecordStream: HSTREAM;
+ end;
+
+var
+ singleton_AudioInputBass : IAudioInput;
+
+
+function TAudioInput_Bass.GetName: String;
+begin
+ result := 'BASS_Input';
+end;
+
+procedure TAudioInput_Bass.InitializeRecord;
+var
+ device: integer;
+ Descr: string;
+ input: integer;
+ input2: integer;
+ InputName: PChar;
+ Flags: integer;
+ mic: array[0..15] of integer;
+ SC: integer; // soundcard
+ SCI: integer; // soundcard input
+ No: integer;
+
+function isDuplicate(Desc: String): Boolean;
+var
+ I: Integer;
+begin
+ Result := False;
+ //Check for Soundcard with same Description
+ For I := 0 to SC-1 do
+ begin
+ if (Recording.SoundCard[I].Description = Desc) then
+ begin
+ Result := True;
+ Break;
+ end;
+ end;
+end;
+
+begin
+ with Recording do
+ begin
+ // checks for recording devices and puts them into an array
+ SetLength(SoundCard, 0);
+
+ SC := 0;
+ Descr := BASS_RecordGetDeviceDescription(SC);
+
+ while (Descr <> '') do
+ begin
+ //If there is another SoundCard with the Same ID, Search an available Name
+ if (IsDuplicate(Descr)) then
+ begin
+ No:= 1; //Count of SoundCards with same Name
+ Repeat
+ Inc(No)
+ Until not IsDuplicate(Descr + ' (' + InttoStr(No) + ')');
+
+ //Set Description
+ Descr := Descr + ' (' + InttoStr(No) + ')';
+ end;
+
+ SetLength(SoundCard, SC+1);
+
+ // TODO: free object on termination
+ SoundCard[SC] := TBassSoundCard.Create();
+ SoundCard[SC].Description := Descr;
+
+ //Get Recording Inputs
+ SCI := 0;
+ BASS_RecordInit(SC);
+
+ InputName := BASS_RecordGetInputName(SCI);
+
+ {$IFDEF DARWIN}
+ // Under MacOSX the SingStar Mics have an empty
+ // InputName. So, we have to add a hard coded
+ // Workaround for this problem
+ if (InputName = nil) and (Pos( 'USBMIC Serial#', Descr) > 0) then
+ begin
+ InputName := 'Microphone';
+ end;
+ {$ENDIF}
+
+ SetLength(SoundCard[SC].Input, 1);
+ SoundCard[SC].Input[SCI].Name := InputName;
+
+ // process each input
+ while (InputName <> nil) do
+ begin
+ Flags := BASS_RecordGetInput(SCI);
+ if (SCI >= 1) {AND (Flags AND BASS_INPUT_OFF = 0)} then
+ begin
+ SetLength(SoundCard[SC].Input, SCI+1);
+ SoundCard[SC].Input[SCI].Name := InputName;
+ end;
+
+ //Set Mic Index
+ if ((Flags and BASS_INPUT_TYPE_MIC) = 1) then
+ SoundCard[SC].MicInput := SCI;
+
+ Inc(SCI);
+ InputName := BASS_RecordGetInputName(SCI);
+ end;
+
+ BASS_RecordFree;
+
+ Inc(SC);
+ Descr := BASS_RecordGetDeviceDescription(SC);
+ end; // while
+ end; // with Recording
+end;
+
+// TODO: code is used by all IAudioInput implementors
+// -> move to a common superclass (TAudioInput_Generic?)
+procedure TAudioInput_Bass.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 TAudioInput_Bass.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;
+
+{*
+ * Bass input capture callback.
+ * Params:
+ * stream - BASS input stream
+ * buffer - buffer of captured samples
+ * len - size of buffer in bytes
+ * user - players associated with left/right channels
+ *}
+function MicrophoneCallback(stream: HSTREAM; buffer: Pointer;
+ len: Cardinal; Card: Cardinal): boolean; stdcall;
+begin
+ Recording.HandleMicrophoneData(buffer, len, Recording.SoundCard[Card]);
+ Result := true;
+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 TAudioInput_Bass.CaptureCard(Card: byte; CaptureSoundLeft, CaptureSoundRight: TSound);
+var
+ Error: integer;
+ ErrorMsg: string;
+ bassSoundCard: TBassSoundCard;
+begin
+ if not BASS_RecordInit(Card) then
+ begin
+ Error := BASS_ErrorGetCode;
+ ErrorMsg := IntToStr(Error);
+ if Error = BASS_ERROR_DX then ErrorMsg := 'No DX5';
+ if Error = BASS_ERROR_ALREADY then ErrorMsg := 'The device has already been initialized';
+ if Error = BASS_ERROR_DEVICE then ErrorMsg := 'The device number specified is invalid';
+ if Error = BASS_ERROR_DRIVER then ErrorMsg := 'There is no available device driver';
+ Log.LogError('Error initializing record [' + IntToStr(Card) + ']');
+ Log.LogError('TAudio_bass.CaptureCard: Error initializing record: ' + ErrorMsg);
+ end
+ else
+ begin
+ bassSoundCard := TBassSoundCard(Recording.SoundCard[Card]);
+ bassSoundCard.CaptureSoundLeft := CaptureSoundLeft;
+ bassSoundCard.CaptureSoundRight := CaptureSoundRight;
+
+ // capture in 44.1kHz/stereo/16bit and a 20ms callback period
+ bassSoundCard.RecordStream :=
+ BASS_RecordStart(44100, 2, MakeLong(0, 20) , @MicrophoneCallback, Card);
+ end;
+end;
+
+{*
+ * Stop input-capturing on Soundcard specified by Card.
+ * Params:
+ * Card - soundcard index in Recording.SoundCard array
+ *}
+procedure TAudioInput_Bass.StopCard(Card: byte);
+begin
+ BASS_RecordSetDevice(Card);
+ BASS_RecordFree;
+end;
+
+
+initialization
+ singleton_AudioInputBass := TAudioInput_Bass.create();
+ AudioManager.add( singleton_AudioInputBass );
+
+finalization
+ AudioManager.Remove( singleton_AudioInputBass );
+
+end.
diff --git a/Game/Code/Classes/UAudioInput_Portaudio.pas b/Game/Code/Classes/UAudioInput_Portaudio.pas
new file mode 100644
index 00000000..853fc35b
--- /dev/null
+++ b/Game/Code/Classes/UAudioInput_Portaudio.pas
@@ -0,0 +1,420 @@
+unit UAudioInput_Portaudio;
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+
+uses Classes,
+ SysUtils,
+ 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
+ TAudioInput_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_AudioInputPortaudio : 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 TAudioInput_Portaudio.GetName: String;
+begin
+ result := 'Portaudio';
+end;
+
+function TAudioInput_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 TAudioInput_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 TAudioInput_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 TAudioInput_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 TAudioInput_Portaudio.CaptureCard(Card: byte; CaptureSoundLeft, CaptureSoundRight: TSound);
+var
+ Error: TPaError;
+ ErrorMsg: string;
+ inputParams: TPaStreamParameters;
+ deviceInfo: PPaDeviceInfo;
+ stream: PPaStream;
+ paSoundCard: TPortaudioSoundCard;
+begin
+ 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;
+end;
+
+{*
+ * Stop input-capturing on Soundcard specified by Card.
+ * Params:
+ * Card - soundcard index in Recording.SoundCard array
+ *}
+procedure TAudioInput_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_AudioInputPortaudio := TAudioInput_Portaudio.create();
+ AudioManager.add( singleton_AudioInputPortaudio );
+
+finalization
+ AudioManager.Remove( singleton_AudioInputPortaudio );
+
+end.
diff --git a/Game/Code/Classes/UAudioPlayback_Bass.pas b/Game/Code/Classes/UAudioPlayback_Bass.pas
new file mode 100644
index 00000000..ee6b1ef9
--- /dev/null
+++ b/Game/Code/Classes/UAudioPlayback_Bass.pas
@@ -0,0 +1,460 @@
+unit UAudioPlayback_Bass;
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+
+uses Classes,
+ {$IFDEF win32}
+ windows,
+ {$ENDIF}
+ SysUtils,
+ bass,
+ ULog,
+ UMusic;
+
+implementation
+
+uses
+ {$IFDEF LAZARUS}
+ lclintf,
+ {$ENDIF}
+ URecord,
+ UIni,
+ UMain,
+ UCommon,
+ UThemes;
+
+type
+ TMPModes = (mpNotReady, mpStopped, mpPlaying, mpRecording, mpSeeking,
+ mpPaused, mpOpen);
+
+const
+ ModeStr: array[TMPModes] of string = ('Not ready', 'Stopped', 'Playing', 'Recording', 'Seeking', 'Paused', 'Open');
+
+type
+ TBassOutputStream = class(TAudioOutputStream)
+ Handle: HSTREAM;
+
+ constructor Create(); overload;
+ constructor Create(stream: HSTREAM); overload;
+ end;
+
+type
+ TAudioPlayback_Bass = class( TInterfacedObject, IAudioPlayback)
+ private
+ MusicStream: HSTREAM;
+
+ StartSoundStream: HSTREAM;
+ BackSoundStream: HSTREAM;
+ SwooshSoundStream: HSTREAM;
+ ChangeSoundStream: HSTREAM;
+ OptionSoundStream: HSTREAM;
+ ClickSoundStream: HSTREAM;
+ DrumSoundStream: HSTREAM;
+ HihatSoundStream: HSTREAM;
+ ClapSoundStream: HSTREAM;
+ ShuffleSoundStream: HSTREAM;
+
+ //Custom Sounds
+ CustomSounds: array of TCustomSoundEntry;
+ Loaded: boolean;
+ Loop: boolean;
+
+ public
+ function GetName: String;
+
+ {IAudioOutput interface}
+
+ procedure InitializePlayback;
+ procedure SetVolume(Volume: integer);
+ procedure SetMusicVolume(Volume: integer);
+ procedure SetLoop(Enabled: boolean);
+
+ function Open(Name: string): boolean; // true if succeed
+
+ procedure Rewind;
+ procedure Play;
+ procedure Pause; //Pause Mod
+ procedure Stop;
+ procedure Close;
+ function Finished: boolean;
+ function Length: real;
+ function GetPosition: real;
+ procedure SetPosition(Time: real);
+
+ procedure PlayStart;
+ procedure PlayBack;
+ procedure PlaySwoosh;
+ procedure PlayChange;
+ procedure PlayOption;
+ procedure PlayClick;
+ procedure PlayDrum;
+ procedure PlayHihat;
+ procedure PlayClap;
+ procedure PlayShuffle;
+ procedure StopShuffle;
+
+ function LoadSoundFromFile(var stream: HSTREAM; Name: string): boolean;
+
+ //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;
+
+var
+ singleton_AudioPlaybackBass : IAudioPlayback;
+
+
+constructor TBassOutputStream.Create();
+begin
+ inherited;
+end;
+
+constructor TBassOutputStream.Create(stream: HSTREAM);
+begin
+ Create();
+ Handle := stream;
+end;
+
+
+function TAudioPlayback_Bass.GetName: String;
+begin
+ result := 'BASS_Playback';
+end;
+
+procedure TAudioPlayback_Bass.InitializePlayback;
+var
+ Pet: integer;
+ S: integer;
+begin
+// Log.BenchmarkStart(4);
+// Log.LogStatus('Initializing Playback Subsystem', 'Music Initialize');
+
+ Loaded := false;
+ Loop := false;
+
+ if not BASS_Init(1, 44100, 0, 0, nil) then
+ begin
+ Log.LogError('Could not initialize BASS', 'Error');
+ Exit;
+ end;
+
+// Log.BenchmarkEnd(4); Log.LogBenchmark('--> Bass Init', 4);
+
+ // config playing buffer
+// BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
+// BASS_SetConfig(BASS_CONFIG_BUFFER, 100);
+
+// Log.LogStatus('Loading Sounds', 'Music Initialize');
+
+// Log.BenchmarkStart(4);
+ LoadSoundFromFile(StartSoundStream, SoundPath + 'Common Start.mp3');
+ LoadSoundFromFile(BackSoundStream, SoundPath + 'Common Back.mp3');
+ LoadSoundFromFile(SwooshSoundStream, SoundPath + 'menu swoosh.mp3');
+ LoadSoundFromFile(ChangeSoundStream, SoundPath + 'select music change music 50.mp3');
+ LoadSoundFromFile(OptionSoundStream, SoundPath + 'option change col.mp3');
+ LoadSoundFromFile(ClickSoundStream, SoundPath + 'rimshot022b.mp3');
+
+// LoadSoundFromFile(DrumSoundStream, SoundPath + 'bassdrumhard076b.mp3');
+// LoadSoundFromFile(HihatSoundStream, SoundPath + 'hihatclosed068b.mp3');
+// LoadSoundFromFile(ClapSoundStream, SoundPath + 'claps050b.mp3');
+
+// LoadSoundFromFile(ShuffleSoundStream, SoundPath + 'Shuffle.mp3');
+
+// Log.BenchmarkEnd(4);
+// Log.LogBenchmark('--> Loading Sounds', 4);
+end;
+
+procedure TAudioPlayback_Bass.SetVolume(Volume: integer);
+begin
+ //Old Sets Wave Volume
+ //BASS_SetVolume(Volume);
+ //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_Bass.SetMusicVolume(Volume: Integer);
+begin
+ //Max Volume Prevention
+ if Volume > 100 then
+ Volume := 100;
+
+ if Volume < 0 then
+ Volume := 0;
+
+ //Set Volume
+ BASS_ChannelSetAttributes (MusicStream, -1, Volume, -101);
+end;
+
+procedure TAudioPlayback_Bass.SetLoop(Enabled: boolean);
+begin
+ Loop := Enabled;
+end;
+
+function TAudioPlayback_Bass.Open(Name: string): boolean;
+begin
+ Loaded := false;
+ if FileExists(Name) then
+ begin
+ MusicStream := Bass_StreamCreateFile(false, pchar(Name), 0, 0, 0);
+
+ Loaded := true;
+ //Set Max Volume
+ SetMusicVolume (100);
+ end;
+
+ Result := Loaded;
+end;
+
+procedure TAudioPlayback_Bass.Rewind;
+begin
+ if Loaded then begin
+ end;
+end;
+
+procedure TAudioPlayback_Bass.SetPosition(Time: real);
+var
+ bytes: integer;
+begin
+ bytes := BASS_ChannelSeconds2Bytes(MusicStream, Time);
+ BASS_ChannelSetPosition(MusicStream, bytes);
+end;
+
+procedure TAudioPlayback_Bass.Play;
+begin
+ if Loaded then
+ begin
+ if Loop then
+ BASS_ChannelPlay(MusicStream, True); // start from beginning... actually bass itself does not loop, nor does this TAudio_bass Class
+
+ BASS_ChannelPlay(MusicStream, False); // for setting position before playing
+ end;
+end;
+
+procedure TAudioPlayback_Bass.Pause; //Pause Mod
+begin
+ if Loaded then begin
+ BASS_ChannelPause(MusicStream); // Pauses Song
+ end;
+end;
+
+procedure TAudioPlayback_Bass.Stop;
+begin
+ Bass_ChannelStop(MusicStream);
+end;
+
+procedure TAudioPlayback_Bass.Close;
+begin
+ Bass_StreamFree(MusicStream);
+end;
+
+function TAudioPlayback_Bass.Length: real;
+var
+ bytes: integer;
+begin
+ Result := 60;
+
+ bytes := BASS_ChannelGetLength(MusicStream);
+ Result := BASS_ChannelBytes2Seconds(MusicStream, bytes);
+end;
+
+function TAudioPlayback_Bass.getPosition: real;
+var
+ bytes: integer;
+begin
+ Result := 0;
+
+ bytes := BASS_ChannelGetPosition(MusicStream);
+ Result := BASS_ChannelBytes2Seconds(MusicStream, bytes);
+end;
+
+function TAudioPlayback_Bass.Finished: boolean;
+begin
+ Result := false;
+
+ if BASS_ChannelIsActive(MusicStream) = BASS_ACTIVE_STOPPED then
+ begin
+ Result := true;
+ end;
+end;
+
+procedure TAudioPlayback_Bass.PlayStart;
+begin
+ BASS_ChannelPlay(StartSoundStream, True);
+end;
+
+procedure TAudioPlayback_Bass.PlayBack;
+begin
+ BASS_ChannelPlay(BackSoundStream, True);// then
+end;
+
+procedure TAudioPlayback_Bass.PlaySwoosh;
+begin
+ BASS_ChannelPlay(SwooshSoundStream, True);
+end;
+
+procedure TAudioPlayback_Bass.PlayChange;
+begin
+ BASS_ChannelPlay(ChangeSoundStream, True);
+end;
+
+procedure TAudioPlayback_Bass.PlayOption;
+begin
+ BASS_ChannelPlay(OptionSoundStream, True);
+end;
+
+procedure TAudioPlayback_Bass.PlayClick;
+begin
+ BASS_ChannelPlay(ClickSoundStream, True);
+end;
+
+procedure TAudioPlayback_Bass.PlayDrum;
+begin
+ BASS_ChannelPlay(DrumSoundStream, True);
+end;
+
+procedure TAudioPlayback_Bass.PlayHihat;
+begin
+ BASS_ChannelPlay(HihatSoundStream, True);
+end;
+
+procedure TAudioPlayback_Bass.PlayClap;
+begin
+ BASS_ChannelPlay(ClapSoundStream, True);
+end;
+
+procedure TAudioPlayback_Bass.PlayShuffle;
+begin
+ BASS_ChannelPlay(ShuffleSoundStream, True);
+end;
+
+procedure TAudioPlayback_Bass.StopShuffle;
+begin
+ BASS_ChannelStop(ShuffleSoundStream);
+end;
+
+function TAudioPlayback_Bass.LoadSoundFromFile(var stream: HSTREAM; Name: string): boolean;
+var
+ L: Integer;
+begin
+ if FileExists(Name) then
+ begin
+ Log.LogStatus('Loading Sound: "' + Name + '"', 'LoadSoundFromFile');
+ try
+ stream := BASS_StreamCreateFile(False, pchar(Name), 0, 0, 0);
+
+ //Add CustomSound
+ L := High(CustomSounds) + 1;
+ SetLength (CustomSounds, L + 1);
+ CustomSounds[L].Filename := Name;
+ CustomSounds[L].Stream := TBassOutputStream.Create(stream);
+ except
+ Log.LogError('Failed to open using BASS', 'LoadSoundFromFile');
+ end;
+ end
+ else
+ begin
+ Log.LogError('Sound not found: "' + Name + '"', 'LoadSoundFromFile');
+ exit;
+ end;
+end;
+
+//Equalizer
+function TAudioPlayback_Bass.GetFFTData: TFFTData;
+var
+ Data: TFFTData;
+begin
+ //Get Channel Data Mono and 256 Values
+ BASS_ChannelGetData(MusicStream, @Result, BASS_DATA_FFT512);
+end;
+
+{*
+ * Copies interleaved PCM 16bit uint (maybe fake) stereo samples into data.
+ * Returns the number of frames (= stereo/mono sample)
+ *}
+function TAudioPlayback_Bass.GetPCMData(var data: TPCMData): Cardinal;
+var
+ info: BASS_CHANNELINFO;
+ nBytes: DWORD;
+begin
+ //Get Channel Data Mono and 256 Values
+ BASS_ChannelGetInfo(MusicStream, info);
+ ZeroMemory(@data, sizeof(TPCMData));
+
+ if (info.chans = 1) then
+ begin
+ // mono file -> add stereo channel
+ {
+ nBytes := BASS_ChannelGetData(Bass, @data[0], samples*sizeof(Smallint));
+ // interleave data
+ //CopyMemory(@data[1], @data[0], samples*sizeof(Smallint));
+ }
+ result := 0;
+ end
+ else
+ begin
+ // stereo file
+ nBytes := BASS_ChannelGetData(MusicStream, @data, sizeof(TPCMData));
+ end;
+ if(nBytes <= 0) then
+ result := 0
+ else
+ result := nBytes div sizeof(TPCMStereoSample);
+end;
+
+function TAudioPlayback_Bass.LoadCustomSound(const Filename: String): Cardinal;
+var
+ S: hStream;
+ 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;
+
+ if LoadSoundFromFile(S, SoundPath + Filename) then
+ Result := High(CustomSounds)
+ else
+ Result := 0;
+end;
+
+procedure TAudioPlayback_Bass.PlayCustomSound(const Index: Cardinal );
+begin
+ if Index <= High(CustomSounds) then
+ with CustomSounds[Index].Stream as TBassOutputStream do
+ begin
+ BASS_ChannelPlay(Handle, True);
+ end;
+end;
+
+
+initialization
+ singleton_AudioPlaybackBass := TAudioPlayback_Bass.create();
+ AudioManager.add( singleton_AudioPlaybackBass );
+
+finalization
+ AudioManager.Remove( singleton_AudioPlaybackBass );
+
+end.
diff --git a/Game/Code/Classes/UAudioPlayback_Portaudio.pas b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
new file mode 100644
index 00000000..36bebc8a
--- /dev/null
+++ b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
@@ -0,0 +1,664 @@
+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}
+ 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 ReadData(Buffer: PChar; BufSize: integer): integer;
+
+ function IsLoaded(): boolean;
+ end;
+
+type
+ TAudioMixerStream = class
+ private
+ activeStreams: TList;
+ 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; //Pause Mod
+
+ 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
+ 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;
+ stream: TPortaudioPlaybackStream;
+ dataAvailable: boolean;
+begin
+ dataAvailable := false;
+
+ // search for playing stream
+ if (activeStreams.Count > 0) then
+ begin
+ for i := 0 to activeStreams.Count-1 do
+ begin
+ stream := TPortaudioPlaybackStream(activeStreams[i]);
+ if (stream.getStatus = sPlaying) then
+ begin
+ dataAvailable := true;
+ break;
+ end;
+ end;
+
+ // fetch data from current stream
+ if (dataAvailable) then
+ begin
+ result := stream.ReadData(Buffer, BufSize);
+ end;
+ end;
+
+ // return silence
+ if ((not dataAvailable) or (result = -1)) then
+ begin
+ FillChar(Buffer^, BufSize, 0);
+ result := BufSize;
+ 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
+ status := sPlaying;
+end;
+
+procedure TPortaudioPlaybackStream.Pause();
+begin
+ status := sPaused;
+end;
+
+procedure TPortaudioPlaybackStream.Stop();
+begin
+ status := sStopped;
+end;
+
+procedure TPortaudioPlaybackStream.Close();
+begin
+ Stop();
+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-stream reached -> stop playback
+ if (decodeStream.EOS) then
+ begin
+ status := sStopped;
+ end;
+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
+ MusicStream.SetLoop(Enabled);
+end;
+
+function TAudioPlayback_Portaudio.Open(Filename: string): boolean;
+var
+ decodeStream: TAudioDecodeStream;
+begin
+ decodeStream := AudioDecoder.Open(Filename);
+ MusicStream := TPortaudioPlaybackStream.Create(decodeStream);
+
+ 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);
+var
+ bytes: integer;
+begin
+ if (MusicStream.IsLoaded) then
+ begin
+ MusicStream.SetPosition(Time);
+ 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; //Pause Mod
+begin
+ if (MusicStream <> nil) then
+ if (MusicStream.IsLoaded()) then begin
+ MusicStream.Pause(); // Pauses Song
+ end;
+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.Length: real;
+begin
+ Result := 0;
+ if assigned( MusicStream ) then
+ begin
+ Result := MusicStream.GetLength();
+ end;
+end;
+
+function TAudioPlayback_Portaudio.getPosition: real;
+var
+ bytes: integer;
+begin
+ Result := 0;
+
+(*
+ bytes := BASS_ChannelGetPosition(BASS);
+ Result := BASS_ChannelBytes2Seconds(BASS, bytes);
+*)
+end;
+
+function TAudioPlayback_Portaudio.Finished: boolean;
+begin
+ Result := false;
+
+(*
+ if BASS_ChannelIsActive(BASS) = BASS_ACTIVE_STOPPED then
+ begin
+ Result := true;
+ end;
+*)
+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);
+ 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.