aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-05-09 19:19:28 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-05-09 19:19:28 +0000
commitb5a738fa52c8b0f2212deb5febd2d7f0b8f6544f (patch)
tree3c2812cffdd035b385d5b0f0f8f5ea0702973739 /Game/Code/Classes
parent37744cee627605db0675efd3a6e0c42bd51c48d6 (diff)
downloadusdx-b5a738fa52c8b0f2212deb5febd2d7f0b8f6544f.tar.gz
usdx-b5a738fa52c8b0f2212deb5febd2d7f0b8f6544f.tar.xz
usdx-b5a738fa52c8b0f2212deb5febd2d7f0b8f6544f.zip
- input-source selection works now (with bass or portaudio with portmixer)
- audio-effects (DSP) interface for audio-playback plus a simple voice removal example (does not sound that good) - FFMpeg support for BASS - audio-clock for FFMpeg for GetPosition and synchronisation - more compatible seeking in FFMpeg - clean termination of the audio interfaces/streams (especially ffmpeg) - Audio output device enumeration (selection will be added later to the sounds option screen) - display of threshold and volume in the record-options screen - threshold and volume can be changed with the 'T' (threshold) and '+'/'-' (source volume) keys - added a FadeIn() method to the IAudioPlayback interface - some minor changes to the audio classes/screens - new base-class for audio-playback classes (used by bass, portaudio and sdl) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1078 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to '')
-rw-r--r--Game/Code/Classes/UAudioCore_Bass.pas27
-rw-r--r--Game/Code/Classes/UAudioCore_Portaudio.pas25
-rw-r--r--Game/Code/Classes/UAudioDecoder_FFMpeg.pas546
-rw-r--r--Game/Code/Classes/UAudioInput_Bass.pas329
-rw-r--r--Game/Code/Classes/UAudioInput_Portaudio.pas255
-rw-r--r--Game/Code/Classes/UAudioPlaybackBase.pas220
-rw-r--r--Game/Code/Classes/UAudioPlayback_Bass.pas549
-rw-r--r--Game/Code/Classes/UAudioPlayback_Portaudio.pas248
-rw-r--r--Game/Code/Classes/UAudioPlayback_SDL.pas46
-rw-r--r--Game/Code/Classes/UAudioPlayback_SoftMixer.pas370
-rw-r--r--Game/Code/Classes/UIni.pas166
-rw-r--r--Game/Code/Classes/UMain.pas11
-rw-r--r--Game/Code/Classes/UMedia_dummy.pas38
-rw-r--r--Game/Code/Classes/UMusic.pas162
-rw-r--r--Game/Code/Classes/URecord.pas167
15 files changed, 2189 insertions, 970 deletions
diff --git a/Game/Code/Classes/UAudioCore_Bass.pas b/Game/Code/Classes/UAudioCore_Bass.pas
index 9c13b461..442c999b 100644
--- a/Game/Code/Classes/UAudioCore_Bass.pas
+++ b/Game/Code/Classes/UAudioCore_Bass.pas
@@ -9,8 +9,12 @@ interface
{$I switches.inc}
uses
+ {$IFDEF MSWINDOWS}
+ Windows,
+ {$ENDIF}
Classes,
- SysUtils;
+ SysUtils,
+ UMusic;
type
TAudioCore_Bass = class
@@ -18,6 +22,7 @@ type
public
class function ErrorGetString(): string; overload;
class function ErrorGetString(errCode: integer): string; overload;
+ class function ConvertAudioFormatToBASSFlags(Format: TAudioSampleFormat; out Flags: DWORD): boolean;
end;
@@ -113,4 +118,22 @@ begin
end;
end;
-end. \ No newline at end of file
+class function TAudioCore_Bass.ConvertAudioFormatToBASSFlags(Format: TAudioSampleFormat; out Flags: DWORD): boolean;
+begin
+ case Format of
+ asfS16:
+ Flags := 0;
+ asfFloat:
+ Flags := BASS_SAMPLE_FLOAT;
+ asfU8:
+ Flags := BASS_SAMPLE_8BITS;
+ else begin
+ Result := false;
+ Exit;
+ end;
+ end;
+
+ Result := true;
+end;
+
+end.
diff --git a/Game/Code/Classes/UAudioCore_Portaudio.pas b/Game/Code/Classes/UAudioCore_Portaudio.pas
index bb0635b3..cd228982 100644
--- a/Game/Code/Classes/UAudioCore_Portaudio.pas
+++ b/Game/Code/Classes/UAudioCore_Portaudio.pas
@@ -16,9 +16,12 @@ uses
type
TAudioCore_Portaudio = class
+ private
+ constructor Create();
public
- class function GetPreferredApiIndex(): TPaHostApiIndex;
- class function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
+ class function GetInstance(): TAudioCore_Portaudio;
+ function GetPreferredApiIndex(): TPaHostApiIndex;
+ function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
end;
implementation
@@ -57,10 +60,24 @@ const
array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
{$IFEND}
+var
+ Instance: TAudioCore_Portaudio;
{ TAudioInput_Portaudio }
-class function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
+constructor TAudioCore_Portaudio.Create();
+begin
+ inherited;
+end;
+
+class function TAudioCore_Portaudio.GetInstance(): TAudioCore_Portaudio;
+begin
+ if not assigned(Instance) then
+ Instance := TAudioCore_Portaudio.Create();
+ Result := Instance;
+end;
+
+function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
var
i: integer;
apiIndex: TPaHostApiIndex;
@@ -148,7 +165,7 @@ end;
* So we have to provide the possibility to manually select an output device
* in the UltraStar options if we want to use portaudio instead of SDL.
*)
-class function TAudioCore_Portaudio.TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
+function TAudioCore_Portaudio.TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
var
stream: PPaStream;
err: TPaError;
diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
index 209e1838..f1c9b364 100644
--- a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
+++ b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
@@ -36,8 +36,10 @@ uses
avutil,
avio, // used for url_ferror
mathematics, // used for av_rescale_q
+ rational,
SDL,
ULog,
+ UCommon,
UConfig;
type
@@ -50,35 +52,49 @@ type
size : integer;
mutex : PSDL_Mutex;
cond : PSDL_Cond;
- quit : boolean;
+ abortRequest: boolean;
public
constructor Create();
destructor Destroy(); override;
function Put(pkt : PAVPacket): integer;
+ function PutStatus(statusFlag: integer; statusInfo: Pointer): integer;
function Get(var pkt: TAVPacket; block: boolean): integer;
procedure Flush();
+ procedure Abort();
end;
const
MAX_AUDIOQ_SIZE = (5 * 16 * 1024);
-var
- EOFPacket: TAVPacket;
- FlushPacket: TAVPacket;
+const
+ STATUS_PACKET: PChar = 'STATUS_PACKET';
+const
+ PKT_STATUS_FLAG_EOF = 1;
+ PKT_STATUS_FLAG_FLUSH = 2;
+ PKT_STATUS_FLAG_ERROR = 3;
type
PAudioBuffer = ^TAudioBuffer;
+ // TODO: should (or must?) be aligned at a 2-byte boundary.
+ // ffmpeg provides a C-macro called DECLARE_ALIGNED for this.
+ // Records can be aligned with the $PACKRECORDS compiler-directive but
+ // are already aligned at a 2-byte boundary by default in FPC.
+ // But what about arrays, are they aligned by a 2-byte boundary too?
+ // Or maybe we have to define a fake record with only an array in it?
TAudioBuffer = array[0 .. (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3 div 2)-1] of byte;
type
TFFMpegDecodeStream = class(TAudioDecodeStream)
private
- _EOF: boolean; // end-of-stream flag
- _EOF_lock : PSDL_Mutex;
+ decoderLock : PSDL_Mutex;
+ parserLock : PSDL_Mutex;
+ myint: integer;
+
+ EOFState: boolean; // end-of-stream flag
+ ErrorState: boolean;
- internalLock : PSDL_Mutex;
resumeCond : PSDL_Cond;
quitRequest : boolean;
@@ -99,23 +115,30 @@ type
ffmpegStreamIndex : Integer;
ffmpegStream : PAVStream;
+ audioClock: double; // stream position in seconds
+
// state-vars for DecodeFrame
pkt : TAVPacket;
audio_pkt_data : PChar;
audio_pkt_size : integer;
// state-vars for AudioCallback
- audio_buf_index : cardinal;
- audio_buf_size : cardinal;
+ audio_buf_index : integer;
+ audio_buf_size : integer;
audio_buf : TAudioBuffer;
- procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
- procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
- function GetLockMutex(): PSDL_Mutex; {$IFDEF HasInline}inline;{$ENDIF}
+ procedure LockParser(); {$IFDEF HasInline}inline;{$ENDIF}
+ procedure UnlockParser(); {$IFDEF HasInline}inline;{$ENDIF}
+ function GetParserMutex(): PSDL_Mutex; {$IFDEF HasInline}inline;{$ENDIF}
+
+ procedure LockDecoder(); {$IFDEF HasInline}inline;{$ENDIF}
+ procedure UnlockDecoder(); {$IFDEF HasInline}inline;{$ENDIF}
+
+ procedure SetEOF(state: boolean); {$IFDEF HasInline}inline;{$ENDIF}
+ procedure SetError(state: boolean); {$IFDEF HasInline}inline;{$ENDIF}
procedure ParseAudio();
function DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
- procedure SetEOF(state: boolean);
public
constructor Create(pFormatCtx: PAVFormatContext;
pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
@@ -129,6 +152,7 @@ type
function GetPosition: real; override;
procedure SetPosition(Time: real); override;
function IsEOF(): boolean; override;
+ function IsError(): boolean; override;
function ReadData(Buffer: PChar; BufSize: integer): integer; override;
end;
@@ -141,6 +165,7 @@ type
function GetName: String;
function InitializeDecoder(): boolean;
+ function FinalizeDecoder(): boolean;
function Open(const Filename: string): TAudioDecodeStream;
end;
@@ -155,6 +180,8 @@ var
constructor TFFMpegDecodeStream.Create(pFormatCtx: PAVFormatContext;
pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
ffmpegStreamIndex : Integer; ffmpegStream: PAVStream);
+var
+ sampleFormat: TAudioSampleFormat;
begin
inherited Create();
@@ -174,41 +201,45 @@ begin
Self.ffmpegStreamIndex := ffmpegStreamIndex;
Self.ffmpegStream := ffmpegStream;
+ case pCodecCtx^.sample_fmt of
+ SAMPLE_FMT_U8: sampleFormat := asfU8;
+ SAMPLE_FMT_S16: sampleFormat := asfS16;
+ SAMPLE_FMT_S24: sampleFormat := asfS24;
+ SAMPLE_FMT_S32: sampleFormat := asfS32;
+ SAMPLE_FMT_FLT: sampleFormat := asfFloat;
+ else sampleFormat := asfS16; // try standard format
+ end;
+
formatInfo := TAudioFormatInfo.Create(
pCodecCtx^.channels,
pCodecCtx^.sample_rate,
- // pCodecCtx^.sample_fmt not yet used by FFMpeg -> use FFMpeg's standard format
- asfS16
+ sampleFormat
);
- _EOF := false;
- _EOF_lock := SDL_CreateMutex();
-
- internalLock := SDL_CreateMutex();
+ EOFState := false;
+ ErrorState := false;
+ decoderLock := SDL_CreateMutex();
+ parserLock := SDL_CreateMutex();
resumeCond := SDL_CreateCond();
parseThread := SDL_CreateThread(@DecodeThreadMain, Self);
end;
+{*
+ * Frees the decode-stream data.
+ * IMPORTANT: call Close() before freeing the decode-stream to avoid dead-locks.
+ * This wakes-up every waiting audio-thread waiting in the packet-queue while
+ * performing a ReadData() request.
+ * Then assure that no thread uses ReadData anymore (e.g. by stopping the audio-callback).
+ * Now you can free the decode-stream.
+ *}
destructor TFFMpegDecodeStream.Destroy();
begin
+ // wake-up and terminate threads
+ // Note: should be called by the caller before Destroy() was called instead
+ // to wake-up a waiting audio-callback thread in the packet-queue.
+ // Otherwise dead-locks are possible.
Close();
- inherited;
-end;
-
-procedure TFFMpegDecodeStream.Close();
-var
- status: integer;
-begin
- Lock();
- quitRequest := true;
- SDL_CondSignal(resumeCond);
- Unlock();
-
- if (parseThread <> nil) then
- begin
- SDL_WaitThread(parseThread, status);
- end;
// Close the codec
if (pCodecCtx <> nil) then
@@ -226,21 +257,64 @@ begin
FreeAndNil(packetQueue);
FreeAndNil(formatInfo);
+
+ SDL_DestroyMutex(decoderLock);
+ decoderLock := nil;
+ SDL_DestroyMutex(parserLock);
+ parserLock := nil;
+ SDL_DestroyCond(resumeCond);
+ resumeCond := nil;
+
+ inherited;
end;
-procedure TFFMpegDecodeStream.Lock();
+procedure TFFMpegDecodeStream.Close();
+var
+ status: integer;
begin
- SDL_mutexP(internalLock);
+ // wake threads waiting for packet-queue data
+ packetQueue.Abort();
+
+ // abort parse-thread
+ LockParser();
+ quitRequest := true;
+ SDL_CondSignal(resumeCond);
+ UnlockParser();
+ // and wait until it terminates
+ if (parseThread <> nil) then
+ begin
+ SDL_WaitThread(parseThread, status);
+ parseThread := nil;
+ end;
+
+ // NOTE: we cannot free the codecCtx or formatCtx here because
+ // a formerly waiting thread in the packet-queue might require them
+ // and crash if it tries to access them.
+end;
+
+procedure TFFMpegDecodeStream.LockParser();
+begin
+ SDL_mutexP(parserLock);
+end;
+
+procedure TFFMpegDecodeStream.UnlockParser();
+begin
+ SDL_mutexV(parserLock);
end;
-procedure TFFMpegDecodeStream.Unlock();
+function TFFMpegDecodeStream.GetParserMutex(): PSDL_Mutex;
begin
- SDL_mutexV(internalLock);
+ Result := parserLock;
end;
-function TFFMpegDecodeStream.GetLockMutex(): PSDL_Mutex;
+procedure TFFMpegDecodeStream.LockDecoder();
begin
- Result := internalLock;
+ SDL_mutexP(decoderLock);
+end;
+
+procedure TFFMpegDecodeStream.UnlockDecoder();
+begin
+ SDL_mutexV(decoderLock);
end;
function TFFMpegDecodeStream.GetLength(): real;
@@ -255,37 +329,59 @@ end;
function TFFMpegDecodeStream.IsEOF(): boolean;
begin
- SDL_mutexP(_EOF_lock);
- Result := _EOF;
- SDL_mutexV(_EOF_lock);
+ LockDecoder();
+ Result := EOFState;
+ UnlockDecoder();
end;
procedure TFFMpegDecodeStream.SetEOF(state: boolean);
begin
- SDL_mutexP(_EOF_lock);
- _EOF := state;
- SDL_mutexV(_EOF_lock);
+ LockDecoder();
+ EOFState := state;
+ UnlockDecoder();
+end;
+
+function TFFMpegDecodeStream.IsError(): boolean;
+begin
+ LockDecoder();
+ Result := ErrorState;
+ UnlockDecoder();
+end;
+
+procedure TFFMpegDecodeStream.SetError(state: boolean);
+begin
+ LockDecoder();
+ ErrorState := state;
+ UnlockDecoder();
end;
function TFFMpegDecodeStream.GetPosition(): real;
-//var
-// bytes: integer;
begin
+ // FIXME: the audio-clock might not be that accurate
// see: tutorial on synching (audio-clock)
- Result := 0;
+ Result := audioClock;
end;
procedure TFFMpegDecodeStream.SetPosition(Time: real);
-//var
-// bytes: integer;
begin
- Lock();
+ LockParser();
+
seekPos := Trunc(Time * AV_TIME_BASE);
- // FIXME: seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0
- seekFlags := 0;//AVSEEK_FLAG_BACKWARD;
+
+ seekFlags := 0;
+ // Note: the BACKWARD-flag seeks to the first position <= the position
+ // searched for. Otherwise e.g. position 0 might not be seeked correct.
+ // For some reason ffmpeg sometimes doesn't use position 0 but the key-frame
+ // following. In streams with few key-frames (like many flv-files) the next
+ // key-frame after 0 might be 5secs ahead.
+ if (Time < audioClock) then
+ seekFlags := AVSEEK_FLAG_BACKWARD;
+ seekFlags := AVSEEK_FLAG_ANY;
+
seekRequest := true;
SDL_CondSignal(resumeCond);
- Unlock();
+
+ UnlockParser();
end;
function DecodeThreadMain(streamPtr: Pointer): integer; cdecl;
@@ -300,68 +396,71 @@ end;
procedure TFFMpegDecodeStream.ParseAudio();
var
packet: TAVPacket;
+ statusPacket: PAVPacket;
seekTarget: int64;
- eofState: boolean;
+ stopParsing: boolean;
pbIOCtx: PByteIOContext;
+ err: integer;
+ index: integer;
begin
- eofState := false;
+ stopParsing := false;
while (true) do
begin
- Lock();
+ LockParser();
+
// wait if end-of-file reached
- if (eofState) then
+ if (stopParsing) then
begin
- if (not (seekRequest or quitRequest)) then
- begin
- // signal end-of-file
- packetQueue.put(@EOFPacket);
- // wait for reuse or destruction of stream
- repeat
- SDL_CondWait(resumeCond, GetLockMutex());
- until (seekRequest or quitRequest);
- end;
- eofState := false;
- SetEOF(false);
+ // wait for reuse or destruction of stream
+ while not (seekRequest or quitRequest) do
+ SDL_CondWait(resumeCond, GetParserMutex());
end;
if (quitRequest) then
begin
+ UnlockParser();
break;
end;
// handle seek-request
- if(seekRequest) then
+ if (seekRequest) then
begin
- // TODO: Do we need this?
- // The position is converted to AV_TIME_BASE and then to the stream-specific base.
- // Why not convert to the stream-specific one from the beginning.
+ // reset status
+ SetEOF(false);
+ SetError(false);
+ stopParsing := false;
+
seekTarget := av_rescale_q(seekPos, AV_TIME_BASE_Q, ffmpegStream^.time_base);
- if(av_seek_frame(pFormatCtx, ffmpegStreamIndex,
- seekTarget, seekFlags) < 0) then
+ err := av_seek_frame(pFormatCtx, ffmpegStreamIndex, seekTarget, seekFlags);
+ // seeking failed -> retry with the default stream (necessary for flv-videos and some ogg-files)
+ if (err < 0) then
+ err := av_seek_frame(pFormatCtx, -1, seekPos, seekFlags);
+ // check if seeking failed
+ if (err < 0) then
begin
- // this will crash in FPC due to a bug
- //Log.LogStatus({pFormatCtx^.filename +} ': error while seeking', 'UAudioDecoder_FFMpeg');
+ Log.LogStatus('Seek Error in "'+pFormatCtx^.filename+'"', 'UAudioDecoder_FFMpeg');
end
else
begin
packetQueue.Flush();
- packetQueue.Put(@FlushPacket);
+ packetQueue.PutStatus(PKT_STATUS_FLAG_FLUSH, nil);
end;
seekRequest := false;
end;
- Unlock();
+ UnlockParser();
-
- if(packetQueue.size > MAX_AUDIOQ_SIZE) then
+ if (packetQueue.size > MAX_AUDIOQ_SIZE) then
begin
SDL_Delay(10);
continue;
end;
- if(av_read_frame(pFormatCtx, packet) < 0) then
+ if (av_read_frame(pFormatCtx, packet) < 0) then
begin
+ // failed to read a frame, check reason
+
{$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)}
pbIOCtx := pFormatCtx^.pb;
{$ELSE}
@@ -369,39 +468,30 @@ begin
{$IFEND}
// check for end-of-file (eof is not an error)
- if(url_feof(pbIOCtx) <> 0) then
+ if (url_feof(pbIOCtx) <> 0) then
begin
- {$IFDEF DebugFFMpegDecode}
- DebugWriteln('feof');
- {$ENDIF}
- eofState := true;
+ // signal end-of-file
+ packetQueue.putStatus(PKT_STATUS_FLAG_EOF, nil);
+ stopParsing := true;
continue;
end;
// check for errors
- if(url_ferror(pbIOCtx) = 0) then
+ if (url_ferror(pbIOCtx) <> 0) then
begin
- {$IFDEF DebugFFMpegDecode}
- DebugWriteln('Errorf');
- {$ENDIF}
- // no error -> wait for user input
- SDL_Delay(100);
- continue;
- end
- else
- begin
- // an error occured -> abort
- // TODO: eof or quit?
- eofState := true;
+ // an error occured -> abort and wait for repositioning or termination
+ packetQueue.putStatus(PKT_STATUS_FLAG_ERROR, nil);
+ stopParsing := true;
continue;
end;
- end;
- //DebugWriteln( 'ffmpeg - av_read_frame' );
+ // no error -> wait for user input
+ SDL_Delay(100);
+ continue;
+ end;
- if(packet.stream_index = ffmpegStreamIndex) then
+ if (packet.stream_index = ffmpegStreamIndex) then
begin
- //DebugWriteln( 'packet_queue_put' );
packetQueue.put(@packet);
end
else
@@ -425,20 +515,22 @@ begin
begin
while (audio_pkt_size > 0) do
begin
- //DebugWriteln( 'got audio packet' );
data_size := bufSize;
- {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0
- len1 := avcodec_decode_audio2(pCodecCtx, @buffer,
- data_size, audio_pkt_data, audio_pkt_size);
- {$ELSE}
- // FIXME: with avcodec_decode_audio a package could contain several frames
- // this is not handled yet
- len1 := avcodec_decode_audio(pCodecCtx, @buffer,
- data_size, audio_pkt_data, audio_pkt_size);
- {$IFEND}
-
- //DebugWriteln('avcodec_decode_audio : ' + inttostr( len1 ));
+ try
+ {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0
+ len1 := avcodec_decode_audio2(pCodecCtx, @buffer,
+ data_size, audio_pkt_data, audio_pkt_size);
+ {$ELSE}
+ // FIXME: with avcodec_decode_audio a package could contain several frames
+ // this is not handled yet
+ len1 := avcodec_decode_audio(pCodecCtx, @buffer,
+ data_size, audio_pkt_data, audio_pkt_size);
+ {$IFEND}
+ except
+ Log.LogError('Exception at avcodec_decode_audio(2)!', 'TFFMpegDecodeStream.DecodeFrame');
+ len1 := -1;
+ end;
if(len1 < 0) then
begin
@@ -459,6 +551,10 @@ begin
continue;
end;
+ //pts := audioClock;
+ audioClock := audioClock + data_size /
+ (1.0 * formatInfo.FrameSize * formatInfo.SampleRate);
+
// we have data, return it and come back for more later
result := data_size;
exit;
@@ -469,87 +565,104 @@ begin
av_free_packet(@pkt);
end;
- if (packetQueue.quit) then
+ // do not use an aborted queue
+ if (packetQueue.abortRequest) then
exit;
+ // request a new packet and block if non available.
+ // If this fails, the queue was aborted.
if (packetQueue.Get(pkt, true) < 0) then
exit;
- audio_pkt_data := PChar(pkt.data);
- audio_pkt_size := pkt.size;
-
- if (audio_pkt_data = PChar(FlushPacket.data)) then
+ // handle Status-packet
+ if (PChar(pkt.data) = STATUS_PACKET) then
begin
- avcodec_flush_buffers(pCodecCtx);
- {$IFDEF DebugFFMpegDecode}
- DebugWriteln('Flush');
- {$ENDIF}
+ pkt.data := nil;
+ audio_pkt_data := nil;
+ audio_pkt_size := 0;
+
+ case (pkt.flags) of
+ PKT_STATUS_FLAG_FLUSH:
+ begin
+ avcodec_flush_buffers(pCodecCtx);
+ end;
+ PKT_STATUS_FLAG_EOF: // end-of-file
+ begin
+ SetEOF(true);
+ // buffer contains no data -> result = -1
+ exit;
+ end;
+ PKT_STATUS_FLAG_ERROR:
+ begin
+ SetError(true);
+ Log.LogStatus('I/O Error', 'TFFMpegDecodeStream.DecodeFrame');
+ exit;
+ end;
+ else
+ begin
+ Log.LogStatus('Unknown status', 'TFFMpegDecodeStream.DecodeFrame');
+ end;
+ end;
+
continue;
end;
- // check for end-of-file
- if (audio_pkt_data = PChar(EOFPacket.data)) then
+ audio_pkt_data := PChar(pkt.data);
+ audio_pkt_size := pkt.size;
+
+ // if available, update the audio clock with pts
+ if(pkt.pts <> AV_NOPTS_VALUE) then
begin
- // end-of-file reached -> set EOF-flag
- SetEOF(true);
- {$IFDEF DebugFFMpegDecode}
- DebugWriteln('EOF');
- {$ENDIF}
- // note: buffer is not (even partially) filled -> no data to return
- exit;
+ audioClock := av_q2d(ffmpegStream^.time_base) * pkt.pts;
end;
-
- //DebugWriteln( 'Audio Packet Size - ' + inttostr(audio_pkt_size) );
end;
end;
function TFFMpegDecodeStream.ReadData(Buffer : PChar; BufSize: integer): integer;
var
- //outStream : TFFMpegDecodeStream;
- len1,
- audio_size : integer;
- len : integer;
+ nBytesCopy: integer; // number of bytes to copy
+ nBytesRemain: integer; // number of bytes left (remaining) to read
begin
- len := BufSize;
result := -1;
- // end-of-file reached
+ // init number of bytes left to copy to the output buffer
+ nBytesRemain := BufSize;
+
+ // leave if end-of-file was reached previously
if EOF then
exit;
- while (len > 0) do begin
+ // copy data to output buffer
+ while (nBytesRemain > 0) do begin
+ // check if we need more data
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));
- //DebugWriteln('audio_decode_frame : '+ inttostr(audio_size));
-
- if(audio_size < 0) then
+ audio_buf_size := DecodeFrame(audio_buf, sizeof(TAudioBuffer));
+ // check for errors or EOF
+ if(audio_buf_size < 0) then
begin
- // if error, output silence
+ // fill decode-buffer with silence
audio_buf_size := 1024;
FillChar(audio_buf, audio_buf_size, #0);
- //DebugWriteln( '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;
+ // calc number of new bytes in the decode-buffer
+ nBytesCopy := audio_buf_size - audio_buf_index;
+ // resize copy-count if more bytes available than needed (remaining bytes are used the next time)
+ if (nBytesCopy > nBytesRemain) then
+ nBytesCopy := nBytesRemain;
- Move(audio_buf[audio_buf_index], Buffer[0], len1);
+ Move(audio_buf[audio_buf_index], Buffer[0], nBytesCopy);
- Dec(len, len1);
- Inc(Buffer, len1);
- Inc(audio_buf_index, len1);
+ Dec(nBytesRemain, nBytesCopy);
+ Inc(Buffer, nBytesCopy);
+ Inc(audio_buf_index, nBytesCopy);
end;
- result := BufSize;
+ Result := BufSize;
end;
@@ -557,7 +670,7 @@ end;
function TAudioDecoder_FFMpeg.GetName: String;
begin
- result := 'FFMpeg_Decoder';
+ Result := 'FFMpeg_Decoder';
end;
function TAudioDecoder_FFMpeg.InitializeDecoder: boolean;
@@ -566,15 +679,12 @@ begin
av_register_all();
- // init end-of-file package
- av_init_packet(EOFPacket);
- EOFPacket.data := Pointer(PChar('EOF'));
-
- // init flush package
- av_init_packet(FlushPacket);
- FlushPacket.data := Pointer(PChar('FLUSH'));
+ Result := true;
+end;
- result := true;
+function TAudioDecoder_FFMpeg.FinalizeDecoder(): boolean;
+begin
+ Result := true;
end;
class function TAudioDecoder_FFMpeg.FindAudioStreamIndex(pFormatCtx : PAVFormatContext): integer;
@@ -599,7 +709,7 @@ begin
end;
end;
- result := streamIndex;
+ Result := streamIndex;
end;
function TAudioDecoder_FFMpeg.Open(const Filename: string): TAudioDecodeStream;
@@ -609,11 +719,9 @@ var
pCodec : PAVCodec;
ffmpegStreamID : Integer;
ffmpegStream : PAVStream;
-// wanted_spec,
- //csIndex : integer;
stream : TFFMpegDecodeStream;
begin
- result := nil;
+ Result := nil;
if (not FileExists(Filename)) then
begin
@@ -622,19 +730,24 @@ begin
end;
// open audio file
- if (av_open_input_file(pFormatCtx, PChar(Filename), nil, 0, nil) > 0) then
+ if (av_open_input_file(pFormatCtx, PChar(Filename), nil, 0, nil) <> 0) then
exit;
+ // TODO: do we need to generate PTS values if they do not exist?
+ //pFormatCtx^.flags := pFormatCtx^.flags or AVFMT_FLAG_GENPTS;
+
// retrieve stream information
if (av_find_stream_info(pFormatCtx) < 0) then
exit;
+ // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end
+ pFormatCtx^.pb.eof_reached := 0;
+
{$IFDEF DebugFFMpegDecode}
dump_format(pFormatCtx, 0, pchar(Filename), 0);
{$ENDIF}
ffmpegStreamID := FindAudioStreamIndex(pFormatCtx);
- //Writeln('ID: ' + inttostr(ffmpegStreamID));
if (ffmpegStreamID < 0) then
exit;
@@ -656,26 +769,25 @@ begin
// detect bug-workarounds automatically
pCodecCtx^.workaround_bugs := FF_BUG_AUTODETECT;
+ // error resilience strategy (careful/compliant/agressive/very_aggressive)
+ //pCodecCtx^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT;
+ // allow non spec compliant speedup tricks.
+ //pCodecCtx^.flags2 := pCodecCtx^.flags2 or CODEC_FLAG2_FAST;
- // TODO: Not sure if these fields are for audio too
- //pCodecCtx^.lowres := lowres;
- //if (fast) then pCodecCtx^.flags2 := pCodecCtx^.flags2 or CODEC_FLAG2_FAST;
- //pCodecCtx^.skip_frame := skip_frame;
- //pCodecCtx^.skip_loop_filter := skip_loop_filter;
- //pCodecCtx^.error_resilience := error_resilience;
- //pCodecCtx^.error_concealment := error_concealment;
-
+ // Note: avcodec_open() is not thread-safe!
if (avcodec_open(pCodecCtx, pCodec) < 0) then
begin
Log.LogStatus('avcodec_open failed!', 'UAudio_FFMpeg');
exit;
end;
- //WriteLn( 'Opened the codec' );
+
+ // TODO: what about pCodecCtx^.start_time? Should we seek to this position here?
+ // ...
stream := TFFMpegDecodeStream.Create(pFormatCtx, pCodecCtx, pCodec,
ffmpegStreamID, ffmpegStream);
- result := stream;
+ Result := stream;
end;
@@ -702,15 +814,30 @@ begin
inherited;
end;
+procedure TPacketQueue.Abort();
+begin
+ SDL_LockMutex(mutex);
+
+ abortRequest := true;
+
+ SDL_CondSignal(cond);
+ SDL_UnlockMutex(mutex);
+end;
+
function TPacketQueue.Put(pkt : PAVPacket): integer;
var
pkt1 : PAVPacketList;
begin
result := -1;
- if ((pkt <> @EOFPacket) and (pkt <> @FlushPacket)) then
+ if (pkt = nil) then
+ exit;
+
+ if (PChar(pkt^.data) <> STATUS_PACKET) then
+ begin
if (av_dup_packet(pkt) < 0) then
exit;
+ end;
pkt1 := av_malloc(sizeof(TAVPacketList));
if (pkt1 = nil) then
@@ -719,10 +846,8 @@ begin
pkt1^.pkt := pkt^;
pkt1^.next := nil;
-
SDL_LockMutex(Self.mutex);
try
-
if (Self.lastPkt = nil) then
Self.firstPkt := pkt1
else
@@ -731,42 +856,58 @@ begin
Self.lastPkt := pkt1;
inc(Self.nbPackets);
- //DebugWriteln('Put: ' + inttostr(nbPackets));
-
Self.size := Self.size + pkt1^.pkt.size;
SDL_CondSignal(Self.cond);
-
finally
SDL_UnlockMutex(Self.mutex);
end;
- result := 0;
+ Result := 0;
+end;
+
+function TPacketQueue.PutStatus(statusFlag: integer; statusInfo: Pointer): integer;
+var
+ pkt: PAVPacket;
+begin
+ // create temp. package
+ pkt := av_malloc(SizeOf(TAVPacket));
+ if (pkt = nil) then
+ begin
+ Result := -1;
+ Exit;
+ end;
+ // init package
+ av_init_packet(pkt^);
+ pkt^.data := Pointer(STATUS_PACKET);
+ pkt^.flags := statusFlag;
+ pkt^.priv := statusInfo;
+ // put a copy of the package into the queue
+ Result := Put(pkt);
+ // data has been copied -> delete temp. package
+ av_free(pkt);
end;
function TPacketQueue.Get(var pkt: TAVPacket; block: boolean): integer;
var
pkt1 : PAVPacketList;
begin
- result := -1;
+ Result := -1;
SDL_LockMutex(Self.mutex);
try
while true do
begin
- if (quit) then
+ if (abortRequest) then
exit;
pkt1 := Self.firstPkt;
-
if (pkt1 <> nil) then
begin
- Self.firstPkt := pkt1.next;
+ Self.firstPkt := pkt1^.next;
if (Self.firstPkt = nil) then
Self.lastPkt := nil;
dec(Self.nbPackets);
- //DebugWriteln('Get: ' + inttostr(nbPackets));
-
Self.size := Self.size - pkt1^.pkt.size;
pkt := pkt1^.pkt;
av_free(pkt1);
@@ -774,8 +915,7 @@ begin
result := 1;
break;
end
- else
- if (not block) then
+ else if (not block) then
begin
result := 0;
break;
diff --git a/Game/Code/Classes/UAudioInput_Bass.pas b/Game/Code/Classes/UAudioInput_Bass.pas
index a62ff22e..db49749e 100644
--- a/Game/Code/Classes/UAudioInput_Bass.pas
+++ b/Game/Code/Classes/UAudioInput_Bass.pas
@@ -27,21 +27,32 @@ uses
type
TAudioInput_Bass = class(TAudioInputBase)
+ private
+ function EnumDevices(): boolean;
public
function GetName: String; override;
function InitializeRecord: boolean; override;
- destructor Destroy; override;
+ function FinalizeRecord: boolean; override;
end;
TBassInputDevice = class(TAudioInputDevice)
- public
- DeviceIndex: integer; // index in TAudioInputProcessor.Device[]
- BassDeviceID: integer; // DeviceID used by BASS
+ private
RecordStream: HSTREAM;
+ BassDeviceID: DWORD; // DeviceID used by BASS
+ SingleIn: boolean;
+
+ DeviceIndex: integer; // index in TAudioInputProcessor.Device[]
- function Init(): boolean;
+ function SetInputSource(SourceIndex: integer): boolean;
+ function GetInputSource(): integer;
+ public
+ function Open(): boolean;
+ function Close(): boolean;
function Start(): boolean; override;
- procedure Stop(); override;
+ function Stop(): boolean; override;
+
+ function GetVolume(): integer; override;
+ procedure SetVolume(Volume: integer); override;
end;
var
@@ -62,83 +73,226 @@ function MicrophoneCallback(stream: HSTREAM; buffer: Pointer;
len: Cardinal; Card: Cardinal): boolean; stdcall;
begin
AudioInputProcessor.HandleMicrophoneData(buffer, len,
- AudioInputProcessor.Device[Card]);
+ AudioInputProcessor.DeviceList[Card]);
Result := true;
end;
{ TBassInputDevice }
-function TBassInputDevice.Init(): boolean;
+function TBassInputDevice.GetInputSource(): integer;
+var
+ SourceCnt: integer;
+ i: integer;
+begin
+ // get input-source config (subtract virtual device to get BASS indices)
+ SourceCnt := Length(Source)-1;
+
+ // find source
+ Result := -1;
+ for i := 0 to SourceCnt-1 do
+ begin
+ // check if current source is selected
+ if ((BASS_RecordGetInput(i) and BASS_INPUT_OFF) = 0) then
+ begin
+ // selected source found
+ Result := i;
+ Exit;
+ end;
+ end;
+end;
+
+function TBassInputDevice.SetInputSource(SourceIndex: integer): boolean;
+var
+ SourceCnt: integer;
+ i: integer;
begin
Result := false;
- // TODO: Call once. Otherwise it's to slow
- if not BASS_RecordInit(BassDeviceID) then
+ // check for invalid source index
+ if (SourceIndex < 0) then
+ Exit;
+
+ // get input-source config (subtract virtual device to get BASS indices)
+ SourceCnt := Length(Source)-1;
+
+ // turn on selected source (turns off the others for single-in devices)
+ if (not BASS_RecordSetInput(SourceIndex, BASS_INPUT_ON)) then
begin
- Log.LogError('TBassInputDevice.Start: Error initializing device['+IntToStr(DeviceIndex)+']: ' +
- TAudioCore_Bass.ErrorGetString());
+ Log.LogError('BASS_RecordSetInput: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Start');
Exit;
end;
+ // turn off all other sources (not needed for single-in devices)
+ if (not SingleIn) then
+ begin
+ for i := 0 to SourceCnt-1 do
+ begin
+ if (i = SourceIndex) then
+ continue;
+ // deselect source if selected
+ if ((BASS_RecordGetInput(i) and BASS_INPUT_OFF) = 0) then
+ BASS_RecordSetInput(i, BASS_INPUT_OFF);
+ end;
+ end;
+
Result := true;
end;
-{*
- * Start input-capturing on this device.
- * TODO: call BASS_RecordInit only once
- *}
-function TBassInputDevice.Start(): boolean;
+function TBassInputDevice.Open(): boolean;
var
- flags: Word;
+ FormatFlags: DWORD;
+ SourceIndex: integer;
const
latency = 20; // 20ms callback period (= latency)
begin
Result := false;
- // recording already started -> stop first
- if (RecordStream <> 0) then
- Stop();
-
- if not Init() then
+ if not BASS_RecordInit(BassDeviceID) then
+ begin
+ Log.LogError('BASS_RecordInit[device:'+IntToStr(DeviceIndex)+']: ' +
+ TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Open');
Exit;
+ end;
- case AudioFormat.Format of
- asfS16: flags := 0;
- asfFloat: flags := BASS_SAMPLE_FLOAT;
- asfU8: flags := BASS_SAMPLE_8BITS;
- else begin
- Log.LogError('Unhandled sample-format', 'TBassInputDevice.Start');
- Exit;
- end;
+ if (not TAudioCore_Bass.ConvertAudioFormatToBASSFlags(AudioFormat.Format, FormatFlags)) then
+ begin
+ Log.LogError('Unhandled sample-format', 'TBassInputDevice.Open');
+ Exit;
end;
- // start capturing
+ // start capturing in paused state
RecordStream := BASS_RecordStart(Round(AudioFormat.SampleRate), AudioFormat.Channels,
- MakeLong(flags, latency),
+ MakeLong(FormatFlags or BASS_RECORD_PAUSE, latency),
@MicrophoneCallback, DeviceIndex);
if (RecordStream = 0) then
begin
+ Log.LogError('BASS_RecordStart: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Open');
BASS_RecordFree;
Exit;
end;
+ // save current source selection and select new source
+ SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+ if (SourceIndex = -1) then
+ begin
+ // nothing to do if default source is used
+ SourceRestore := -1;
+ end
+ else
+ begin
+ // store current source-index and select new source
+ SourceRestore := GetInputSource();
+ SetInputSource(SourceIndex);
+ end;
+
Result := true;
end;
-{*
- * Stop input-capturing on this device.
- *}
-procedure TBassInputDevice.Stop();
+{* Start input-capturing on this device. *}
+function TBassInputDevice.Start(): boolean;
+begin
+ Result := false;
+
+ // recording already started -> stop first
+ if (RecordStream <> 0) then
+ Stop();
+
+ // TODO: Do not open the device here (takes too much time).
+ if not Open() then
+ Exit;
+
+ if (not BASS_ChannelPlay(RecordStream, true)) then
+ begin
+ Log.LogError('BASS_ChannelPlay: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Start');
+ Exit;
+ end;
+
+ Result := true;
+end;
+
+{* Stop input-capturing on this device. *}
+function TBassInputDevice.Stop(): boolean;
begin
+ Result := false;
+
if (RecordStream = 0) then
Exit;
- // TODO: Don't free the device. Do this on close
- if (BASS_RecordSetDevice(BassDeviceID)) then
- BASS_RecordFree;
+ if (not BASS_RecordSetDevice(BassDeviceID)) then
+ Exit;
+
+ if (not BASS_ChannelStop(RecordStream)) then
+ begin
+ Log.LogError('BASS_ChannelStop: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Stop');
+ end;
+
+ // TODO: Do not close the device here (takes too much time).
+ Result := Close();
+end;
+
+function TBassInputDevice.Close(): boolean;
+begin
+ // restore source selection
+ if (SourceRestore >= 0) then
+ begin
+ SetInputSource(SourceRestore);
+ end;
+
+ // free data
+ if (not BASS_RecordFree()) then
+ begin
+ Log.LogError('BASS_RecordFree: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Close');
+ Result := false;
+ end
+ else
+ begin
+ Result := true;
+ end;
+
RecordStream := 0;
end;
+function TBassInputDevice.GetVolume(): integer;
+var
+ SourceIndex: integer;
+begin
+ SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+ if (SourceIndex = -1) then
+ begin
+ // if default source used find selected source
+ SourceIndex := GetInputSource();
+ if (SourceIndex = -1) then
+ begin
+ Result := 0;
+ Exit;
+ end;
+ end;
+
+ Result := LOWORD(BASS_RecordGetInput(SourceIndex));
+end;
+
+procedure TBassInputDevice.SetVolume(Volume: integer);
+var
+ SourceIndex: integer;
+begin
+ SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+ if (SourceIndex = -1) then
+ begin
+ // if default source used find selected source
+ SourceIndex := GetInputSource();
+ if (SourceIndex = -1) then
+ Exit;
+ end;
+
+ // clip volume to valid range
+ if (Volume > 100) then
+ Volume := 100
+ else if (Volume < 0) then
+ Volume := 0;
+
+ BASS_RecordSetInput(SourceIndex, BASS_INPUT_LEVEL or Volume);
+end;
+
{ TAudioInput_Bass }
@@ -147,7 +301,7 @@ begin
result := 'BASS_Input';
end;
-function TAudioInput_Bass.InitializeRecord(): boolean;
+function TAudioInput_Bass.EnumDevices(): boolean;
var
Descr: PChar;
SourceName: PChar;
@@ -157,12 +311,13 @@ var
DeviceIndex: integer;
SourceIndex: integer;
RecordInfo: BASS_RECORDINFO;
+ SelectedSourceIndex: integer;
begin
result := false;
DeviceIndex := 0;
BassDeviceID := 0;
- SetLength(AudioInputProcessor.Device, 0);
+ SetLength(AudioInputProcessor.DeviceList, 0);
// checks for recording devices and puts them into an array
while true do
@@ -171,7 +326,7 @@ begin
if (Descr = nil) then
break;
- // try to intialize the device
+ // try to initialize the device
if not BASS_RecordInit(BassDeviceID) then
begin
Log.LogStatus('Failed to initialize BASS Capture-Device['+inttostr(BassDeviceID)+']',
@@ -179,26 +334,25 @@ begin
end
else
begin
- SetLength(AudioInputProcessor.Device, DeviceIndex+1);
+ SetLength(AudioInputProcessor.DeviceList, DeviceIndex+1);
// TODO: free object on termination
BassDevice := TBassInputDevice.Create();
- AudioInputProcessor.Device[DeviceIndex] := BassDevice;
+ AudioInputProcessor.DeviceList[DeviceIndex] := BassDevice;
BassDevice.DeviceIndex := DeviceIndex;
BassDevice.BassDeviceID := BassDeviceID;
- BassDevice.Description := UnifyDeviceName(Descr, DeviceIndex);
+ BassDevice.Name := UnifyDeviceName(Descr, DeviceIndex);
// retrieve recording device info
BASS_RecordGetInfo(RecordInfo);
- // FIXME: does BASS use LSB/MSB or system integer values for 16bit?
-
// check if BASS has capture-freq. info
if (RecordInfo.freq > 0) then
begin
// use current input sample rate (available only on Windows Vista and OSX).
// Recording at this rate will give the best quality and performance, as no resampling is required.
+ // FIXME: does BASS use LSB/MSB or system integer values for 16bit?
BassDevice.AudioFormat := TAudioFormatInfo.Create(2, RecordInfo.freq, asfS16)
end
else
@@ -209,36 +363,59 @@ begin
BassDevice.AudioFormat := TAudioFormatInfo.Create(2, 44100, asfS16)
end;
+ // get info if multiple input-sources can be selected at once
+ BassDevice.SingleIn := RecordInfo.singlein;
+
+ // init list for capture buffers per channel
SetLength(BassDevice.CaptureChannel, BassDevice.AudioFormat.Channels);
- // get input sources
- SourceIndex := 0;
- BassDevice.MicSource := 0;
+ BassDevice.MicSource := -1;
+ BassDevice.SourceRestore := -1;
+
+ // add a virtual default source (will not change mixer-settings)
+ SetLength(BassDevice.Source, 1);
+ BassDevice.Source[0].Name := DEFAULT_SOURCE_NAME;
+
+ // add real input sources
+ SourceIndex := 1;
// process each input
while true do
begin
- SourceName := BASS_RecordGetInputName(SourceIndex);
- {$IFDEF DARWIN}
- // Patch for SingStar USB-Microphones:
- if ((SourceName = nil) and (SourceIndex = 0) and (Pos('Serial#', Descr) > 0)) then
- SourceName := 'Microphone'
- else
- break;
- {$ELSE}
- if (SourceName = nil) then
- break;
- {$ENDIF}
-
- SetLength(BassDevice.Source, SourceIndex+1);
- BassDevice.Source[SourceIndex].Name :=
- UnifyDeviceSourceName(SourceName, BassDevice.Description);
-
- // set mic index
+ SourceName := BASS_RecordGetInputName(SourceIndex-1);
+
+ {$IFDEF DARWIN}
+ // Under MacOSX the SingStar Mics have an empty InputName.
+ // So, we have to add a hard coded Workaround for this problem
+ // FIXME: - Do we need this anymore? Doesn't the (new) default source already solve this problem?
+ // - Normally a nil return value of BASS_RecordGetInputName() means end-of-list, so maybe
+ // BASS is not able to detect any mic-sources (the default source will work then).
+ // - Does BASS_RecordGetInfo() return true or false? If it returns true in this case
+ // we could use this value to check if the device exists.
+ // Please check that, eddie.
+ // If it returns false, then the source is not detected and it does not make sense to add a second
+ // fake device here.
+ // What about BASS_RecordGetInput()? Does it return a value <> -1?
+ // - Does it even work at all with this fake source-index, now that input switching works?
+ // This info was not used before (sources were never switched), so it did not matter what source-index was used.
+ // But now BASS_RecordSetInput() will probably fail.
+ if ((SourceName = nil) and (SourceIndex = 1) and (Pos('USBMIC Serial#', Descr) > 0)) then
+ SourceName := 'Microphone'
+ {$ENDIF}
+
+ if (SourceName = nil) then
+ break;
+
+ SetLength(BassDevice.Source, Length(BassDevice.Source)+1);
+ BassDevice.Source[SourceIndex].Name := SourceName;
+
+ // get input-source info
Flags := BASS_RecordGetInput(SourceIndex);
- if ((Flags <> -1) and ((Flags and BASS_INPUT_TYPE_MIC) <> 0)) then
+ if (Flags <> -1) then
begin
- BassDevice.MicSource := SourceIndex;
+ // is the current source a mic-source?
+ if ((Flags and BASS_INPUT_TYPE_MIC) <> 0) then
+ BassDevice.MicSource := SourceIndex;
end;
Inc(SourceIndex);
@@ -250,16 +427,22 @@ begin
Inc(DeviceIndex);
end;
-
+
Inc(BassDeviceID);
end;
result := true;
end;
-destructor TAudioInput_Bass.Destroy;
+function TAudioInput_Bass.InitializeRecord(): boolean;
+begin
+ Result := EnumDevices();
+end;
+
+function TAudioInput_Bass.FinalizeRecord(): boolean;
begin
- inherited;
+ CaptureStop;
+ Result := inherited FinalizeRecord;
end;
diff --git a/Game/Code/Classes/UAudioInput_Portaudio.pas b/Game/Code/Classes/UAudioInput_Portaudio.pas
index 90ac41b1..183c482d 100644
--- a/Game/Code/Classes/UAudioInput_Portaudio.pas
+++ b/Game/Code/Classes/UAudioInput_Portaudio.pas
@@ -29,19 +29,30 @@ uses
type
TAudioInput_Portaudio = class(TAudioInputBase)
+ private
+ AudioCore: TAudioCore_Portaudio;
+ function EnumDevices(): boolean;
public
function GetName: String; override;
function InitializeRecord: boolean; override;
- destructor Destroy; override;
+ function FinalizeRecord: boolean; override;
end;
TPortaudioInputDevice = class(TAudioInputDevice)
- public
- RecordStream: PPaStream;
+ private
+ RecordStream: PPaStream;
+ {$IFDEF UsePortmixer}
+ Mixer: PPxMixer;
+ {$ENDIF}
PaDeviceIndex: TPaDeviceIndex;
-
+ public
+ function Open(): boolean;
+ function Close(): boolean;
function Start(): boolean; override;
- procedure Stop(); override;
+ function Stop(): boolean; override;
+
+ function GetVolume(): integer; override;
+ procedure SetVolume(Volume: integer); override;
end;
function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
@@ -58,12 +69,12 @@ var
{ TPortaudioInputDevice }
-function TPortaudioInputDevice.Start(): boolean;
+function TPortaudioInputDevice.Open(): boolean;
var
Error: TPaError;
- ErrorMsg: string;
inputParams: TPaStreamParameters;
deviceInfo: PPaDeviceInfo;
+ SourceIndex: integer;
begin
Result := false;
@@ -90,35 +101,148 @@ begin
@MicrophoneCallback, Pointer(Self));
if(Error <> paNoError) then
begin
- ErrorMsg := Pa_GetErrorText(Error);
- Log.LogError('Error opening stream: ' + ErrorMsg, 'TPortaudioInputDevice.Start');
+ Log.LogError('Error opening stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Open');
Exit;
end;
+ {$IFDEF UsePortmixer}
+ // open default mixer
+ Mixer := Px_OpenMixer(RecordStream, 0);
+ if (Mixer = nil) then
+ begin
+ Log.LogError('Error opening mixer: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Open');
+ end
+ else
+ begin
+ // save current source selection and select new source
+ SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+ if (SourceIndex = -1) then
+ begin
+ // nothing to do if default source is used
+ SourceRestore := -1;
+ end
+ else
+ begin
+ // store current source-index and select new source
+ SourceRestore := Px_GetCurrentInputSource(Mixer); // -1 in error case
+ Px_SetCurrentInputSource(Mixer, SourceIndex);
+ end;
+ end;
+ {$ENDIF}
+
+ Result := true;
+end;
+
+function TPortaudioInputDevice.Start(): boolean;
+var
+ Error: TPaError;
+begin
+ Result := false;
+
+ // recording already started -> stop first
+ if (RecordStream <> nil) then
+ Stop();
+
+ // TODO: Do not open the device here (takes too much time).
+ if (not Open()) then
+ Exit;
+
// start capture
Error := Pa_StartStream(RecordStream);
if(Error <> paNoError) then
begin
- Pa_CloseStream(RecordStream);
- ErrorMsg := Pa_GetErrorText(Error);
- Log.LogError('Error starting stream: ' + ErrorMsg, 'TPortaudioInputDevice.Start');
+ Log.LogError('Error starting stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Start');
+ Close();
+ RecordStream := nil;
Exit;
end;
-
+
Result := true;
end;
-procedure TPortaudioInputDevice.Stop();
+function TPortaudioInputDevice.Stop(): boolean;
+var
+ Error: TPaError;
begin
- if assigned(RecordStream) then
+ Result := false;
+
+ if (RecordStream = nil) then
+ Exit;
+
+ // Note: do NOT call Pa_StopStream here!
+ // It gets stuck on devices with non-working callback as Pa_StopStream
+ // waits until all buffers have been handled (which never occurs in that case).
+ Error := Pa_AbortStream(RecordStream);
+ if (Error <> paNoError) then
begin
- // Note: do NOT call Pa_StopStream here!
- // It gets stuck on devices with non-working callback as Pa_StopStream
- // waits until all buffers have been handled (which never occurs in that
- // case).
- // Pa_CloseStream internally calls Pa_AbortStream which works as expected.
- Pa_CloseStream(RecordStream);
+ Log.LogError('Pa_AbortStream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Stop');
end;
+
+ Result := Close();
+end;
+
+function TPortaudioInputDevice.Close(): boolean;
+var
+ Error: TPaError;
+begin
+ {$IFDEF UsePortmixer}
+ if (Mixer <> nil) then
+ begin
+ // restore source selection
+ if (SourceRestore >= 0) then
+ begin
+ Px_SetCurrentInputSource(Mixer, SourceRestore);
+ end;
+
+ // close mixer
+ Px_CloseMixer(Mixer);
+ Mixer := nil;
+ end;
+ {$ENDIF}
+
+ Error := Pa_CloseStream(RecordStream);
+ if (Error <> paNoError) then
+ begin
+ Log.LogError('Pa_CloseStream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Close');
+ Result := false;
+ end
+ else
+ begin
+ Result := true;
+ end;
+
+ RecordStream := nil;
+end;
+
+function TPortaudioInputDevice.GetVolume(): integer;
+begin
+ Result := 0;
+ {$IFDEF UsePortmixer}
+ if (Mixer <> nil) then
+ begin
+ Result := Round(Px_GetInputVolume(Mixer) * 100);
+ // clip to valid range
+ if (Result > 100) then
+ Result := 100
+ else if (Result < 0) then
+ Result := 0;
+ end;
+ {$ENDIF}
+end;
+
+procedure TPortaudioInputDevice.SetVolume(Volume: integer);
+begin
+ {$IFDEF UsePortmixer}
+ if (Mixer <> nil) then
+ begin
+ // clip to valid range
+ if (Volume > 100) then
+ Volume := 100
+ else if (Volume < 0) then
+ Volume := 0;
+ Px_SetInputVolume(Mixer, Volume / 100);
+ end;
+ {$ENDIF}
end;
@@ -129,7 +253,7 @@ begin
result := 'Portaudio';
end;
-function TAudioInput_Portaudio.InitializeRecord(): boolean;
+function TAudioInput_Portaudio.EnumDevices(): boolean;
var
i: integer;
paApiIndex: TPaHostApiIndex;
@@ -148,29 +272,21 @@ var
sampleRate: double;
latency: TPaTime;
{$IFDEF UsePortmixer}
- sourceIndex: integer;
mixer: PPxMixer;
sourceCnt: integer;
+ sourceIndex: integer;
sourceName: string;
{$ENDIF}
cbPolls: integer;
cbWorks: boolean;
begin
- result := false;
-
- // initialize portaudio
- err := Pa_Initialize();
- if(err <> paNoError) then
- begin
- Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord');
- Exit;
- end;
+ Result := false;
// choose the best available Audio-API
- paApiIndex := TAudioCore_Portaudio.GetPreferredApiIndex();
+ paApiIndex := AudioCore.GetPreferredApiIndex();
if(paApiIndex = -1) then
begin
- Log.LogError('No working Audio-API found', 'TAudioInput_Portaudio.InitializeRecord');
+ Log.LogError('No working Audio-API found', 'TAudioInput_Portaudio.EnumDevices');
Exit;
end;
@@ -179,8 +295,8 @@ begin
SC := 0;
// init array-size to max. input-devices count
- SetLength(AudioInputProcessor.Device, paApiInfo^.deviceCount);
- for i:= 0 to High(AudioInputProcessor.Device) do
+ SetLength(AudioInputProcessor.DeviceList, paApiInfo^.deviceCount);
+ for i:= 0 to High(AudioInputProcessor.DeviceList) do
begin
// convert API-specific device-index to global index
deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
@@ -199,11 +315,11 @@ begin
channelCnt := 2;
paDevice := TPortaudioInputDevice.Create();
- AudioInputProcessor.Device[SC] := paDevice;
+ AudioInputProcessor.DeviceList[SC] := paDevice;
// retrieve device-name
deviceName := deviceInfo^.name;
- paDevice.Description := deviceName;
+ paDevice.Name := deviceName;
paDevice.PaDeviceIndex := deviceIndex;
sampleRate := deviceInfo^.defaultSampleRate;
@@ -213,6 +329,8 @@ begin
latency := deviceInfo^.defaultLowInputLatency;
// setup desired input parameters
+ // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
+ // not be set correctly in OSS)
with inputParams do
begin
device := deviceIndex;
@@ -223,10 +341,10 @@ begin
end;
// check souncard and adjust sample-rate
- if (not TAudioCore_Portaudio.TestDevice(@inputParams, nil, sampleRate)) then
+ if (not AudioCore.TestDevice(@inputParams, nil, sampleRate)) then
begin
// ignore device if it does not work
- Log.LogError('Device "'+paDevice.Description+'" does not work',
+ Log.LogError('Device "'+paDevice.Name+'" does not work',
'TAudioInput_Portaudio.EnumDevices');
paDevice.Free();
continue;
@@ -266,11 +384,19 @@ begin
);
SetLength(paDevice.CaptureChannel, paDevice.AudioFormat.Channels);
- Log.LogStatus('InputDevice "'+paDevice.Description+'"@' +
+ Log.LogStatus('InputDevice "'+paDevice.Name+'"@' +
IntToStr(paDevice.AudioFormat.Channels)+'x'+
FloatToStr(paDevice.AudioFormat.SampleRate)+'Hz ('+
FloatTostr(inputParams.suggestedLatency)+'sec)' ,
- 'Portaudio.InitializeRecord');
+ 'Portaudio.EnumDevices');
+
+ // portaudio does not provide a source-type check
+ paDevice.MicSource := -1;
+ paDevice.SourceRestore := -1;
+
+ // add a virtual default source (will not change mixer-settings)
+ SetLength(paDevice.Source, 1);
+ paDevice.Source[0].Name := DEFAULT_SOURCE_NAME;
{$IFDEF UsePortmixer}
// use default mixer
@@ -278,52 +404,55 @@ begin
// get input count
sourceCnt := Px_GetNumInputSources(mixer);
- SetLength(paDevice.Source, sourceCnt);
+ SetLength(paDevice.Source, sourceCnt+1);
// get input names
- for sourceIndex := 0 to sourceCnt-1 do
+ for sourceIndex := 1 to sourceCnt do
begin
- sourceName := Px_GetInputSourceName(mixer, sourceIndex);
+ sourceName := Px_GetInputSourceName(mixer, sourceIndex-1);
paDevice.Source[sourceIndex].Name := sourceName;
end;
Px_CloseMixer(mixer);
- {$ELSE} // not UsePortmixer
- // create a standard input source
- SetLength(paDevice.Source, 1);
- paDevice.Source[0].Name := 'Standard';
{$ENDIF}
// close test-stream
Pa_CloseStream(stream);
- // use default input source
- paDevice.SourceSelected := 0;
-
Inc(SC);
end;
// adjust size to actual input-device count
- SetLength(AudioInputProcessor.Device, SC);
+ SetLength(AudioInputProcessor.DeviceList, SC);
- Log.LogStatus('#Soundcards: ' + inttostr(SC), 'Portaudio');
+ Log.LogStatus('#Input-Devices: ' + inttostr(SC), 'Portaudio');
- result := true;
+ Result := true;
end;
-destructor TAudioInput_Portaudio.Destroy;
+function TAudioInput_Portaudio.InitializeRecord(): boolean;
var
- i: integer;
- //paSoundCard: TPortaudioInputDevice;
+ err: TPaError;
begin
- Pa_Terminate();
- for i := 0 to High(AudioInputProcessor.Device) do
+ AudioCore := TAudioCore_Portaudio.GetInstance();
+
+ // initialize portaudio
+ err := Pa_Initialize();
+ if(err <> paNoError) then
begin
- AudioInputProcessor.Device[i].Free();
+ Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord');
+ Result := false;
+ Exit;
end;
- AudioInputProcessor.Device := nil;
- inherited Destroy;
+ Result := EnumDevices();
+end;
+
+function TAudioInput_Portaudio.FinalizeRecord: boolean;
+begin
+ CaptureStop;
+ Pa_Terminate();
+ Result := inherited FinalizeRecord();
end;
{*
diff --git a/Game/Code/Classes/UAudioPlaybackBase.pas b/Game/Code/Classes/UAudioPlaybackBase.pas
new file mode 100644
index 00000000..f1fe26f9
--- /dev/null
+++ b/Game/Code/Classes/UAudioPlaybackBase.pas
@@ -0,0 +1,220 @@
+unit UAudioPlaybackBase;
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+uses
+ UMusic;
+
+type
+ TAudioPlaybackBase = class(TInterfacedObject, IAudioPlayback)
+ protected
+ OutputDeviceList: TAudioOutputDeviceList;
+ MusicStream: TAudioPlaybackStream;
+ // open sound or music stream (used by Open() and OpenSound())
+ function OpenStream(const Filename: string): TAudioPlaybackStream; virtual; abstract;
+ procedure ClearOutputDeviceList();
+ public
+ function GetName: String; virtual; abstract;
+
+ function Open(const Filename: string): boolean; // true if succeed
+ procedure Close;
+
+ procedure Play;
+ procedure Pause;
+ procedure Stop;
+ procedure FadeIn(Time: real; TargetVolume: integer);
+
+ procedure SetPosition(Time: real);
+ function GetPosition: real;
+
+ function InitializePlayback: boolean; virtual; abstract;
+ function FinalizePlayback: boolean; virtual;
+
+ // function SetOutputDevice(Device: TAudioOutputDevice): boolean;
+ function GetOutputDeviceList(): TAudioOutputDeviceList;
+
+ procedure SetAppVolume(Volume: integer); virtual; abstract;
+ procedure SetVolume(Volume: integer);
+ procedure SetLoop(Enabled: boolean);
+
+ procedure Rewind;
+ function Finished: boolean;
+ function Length: real;
+
+ // Sounds
+ function OpenSound(const Filename: String): TAudioPlaybackStream;
+ procedure PlaySound(stream: TAudioPlaybackStream);
+ procedure StopSound(stream: TAudioPlaybackStream);
+
+ // Equalizer
+ procedure GetFFTData(var data: TFFTData);
+
+ // Interface for Visualizer
+ function GetPCMData(var data: TPCMData): Cardinal;
+ end;
+
+
+implementation
+
+uses
+ SysUtils;
+
+{ TAudioPlaybackBase }
+
+function TAudioPlaybackBase.FinalizePlayback: boolean;
+begin
+ FreeAndNil(MusicStream);
+ ClearOutputDeviceList();
+end;
+
+function TAudioPlaybackBase.Open(const Filename: string): boolean;
+begin
+ // free old MusicStream
+ MusicStream.Free;
+
+ MusicStream := OpenStream(Filename);
+ if not assigned(MusicStream) then
+ begin
+ Result := false;
+ Exit;
+ end;
+
+ //MusicStream.AddSoundEffect(TVoiceRemoval.Create());
+
+ Result := true;
+end;
+
+procedure TAudioPlaybackBase.Close;
+begin
+ if assigned(MusicStream) then
+ MusicStream.Close();
+end;
+
+procedure TAudioPlaybackBase.Play;
+begin
+ if assigned(MusicStream) then
+ MusicStream.Play();
+end;
+
+procedure TAudioPlaybackBase.Pause;
+begin
+ if assigned(MusicStream) then
+ MusicStream.Pause();
+end;
+
+procedure TAudioPlaybackBase.Stop;
+begin
+ if assigned(MusicStream) then
+ MusicStream.Stop();
+end;
+
+function TAudioPlaybackBase.Length: real;
+begin
+ if assigned(MusicStream) then
+ Result := MusicStream.Length
+ else
+ Result := 0;
+end;
+
+function TAudioPlaybackBase.GetPosition: real;
+begin
+ if assigned(MusicStream) then
+ Result := MusicStream.Position
+ else
+ Result := 0;
+end;
+
+procedure TAudioPlaybackBase.SetPosition(Time: real);
+begin
+ if assigned(MusicStream) then
+ MusicStream.Position := Time;
+end;
+
+procedure TAudioPlaybackBase.Rewind;
+begin
+ SetPosition(0);
+end;
+
+function TAudioPlaybackBase.Finished: boolean;
+begin
+ if assigned(MusicStream) then
+ Result := (MusicStream.Status = ssStopped)
+ else
+ Result := true;
+end;
+
+procedure TAudioPlaybackBase.SetVolume(Volume: Integer);
+begin
+ if assigned(MusicStream) then
+ MusicStream.Volume := Volume;
+end;
+
+procedure TAudioPlaybackBase.FadeIn(Time: real; TargetVolume: integer);
+begin
+ if assigned(MusicStream) then
+ MusicStream.FadeIn(Time, TargetVolume);
+end;
+
+procedure TAudioPlaybackBase.SetLoop(Enabled: boolean);
+begin
+ if assigned(MusicStream) then
+ MusicStream.Loop := Enabled;
+end;
+
+// Equalizer
+procedure TAudioPlaybackBase.GetFFTData(var data: TFFTData);
+begin
+ if assigned(MusicStream) then
+ MusicStream.GetFFTData(data);
+end;
+
+{*
+ * Copies interleaved PCM SInt16 stereo samples into data.
+ * Returns the number of frames
+ *}
+function TAudioPlaybackBase.GetPCMData(var data: TPCMData): Cardinal;
+begin
+ if assigned(MusicStream) then
+ Result := MusicStream.GetPCMData(data)
+ else
+ Result := 0;
+end;
+
+function TAudioPlaybackBase.OpenSound(const Filename: string): TAudioPlaybackStream;
+begin
+ Result := OpenStream(Filename);
+end;
+
+procedure TAudioPlaybackBase.PlaySound(stream: TAudioPlaybackStream);
+begin
+ if assigned(stream) then
+ stream.Play();
+end;
+
+procedure TAudioPlaybackBase.StopSound(stream: TAudioPlaybackStream);
+begin
+ if assigned(stream) then
+ stream.Stop();
+end;
+
+procedure TAudioPlaybackBase.ClearOutputDeviceList();
+var
+ DeviceIndex: integer;
+begin
+ for DeviceIndex := 0 to High(OutputDeviceList) do
+ OutputDeviceList[DeviceIndex].Free();
+ SetLength(OutputDeviceList, 0);
+end;
+
+function TAudioPlaybackBase.GetOutputDeviceList(): TAudioOutputDeviceList;
+begin
+ Result := OutputDeviceList;
+end;
+
+end.
diff --git a/Game/Code/Classes/UAudioPlayback_Bass.pas b/Game/Code/Classes/UAudioPlayback_Bass.pas
index d47990a8..0fca9c72 100644
--- a/Game/Code/Classes/UAudioPlayback_Bass.pas
+++ b/Game/Code/Classes/UAudioPlayback_Bass.pas
@@ -8,36 +8,40 @@ interface
{$I switches.inc}
+implementation
uses
Classes,
SysUtils,
- UMusic;
-
-implementation
-
-uses
UIni,
UMain,
- ULog,
+ UMusic,
+ UAudioPlaybackBase,
UAudioCore_Bass,
+ ULog,
bass;
type
+ PHDSP = ^HDSP;
+
+type
+ // Playback-stream decoded internally by BASS
TBassPlaybackStream = class(TAudioPlaybackStream)
private
Handle: HSTREAM;
- Loop: boolean;
public
- constructor Create(); overload;
- constructor Create(stream: HSTREAM); overload;
+ constructor Create(stream: HSTREAM);
+ destructor Destroy(); override;
procedure Reset();
procedure Play(); override;
procedure Pause(); override;
procedure Stop(); override;
+ procedure FadeIn(Time: real; TargetVolume: integer); override;
+
procedure Close(); override;
+
function GetLoop(): boolean; override;
procedure SetLoop(Enabled: boolean); override;
function GetLength(): real; override;
@@ -45,79 +49,95 @@ type
function GetVolume(): integer; override;
procedure SetVolume(volume: integer); override;
- function GetPosition: real;
- procedure SetPosition(Time: real);
+ procedure AddSoundEffect(effect: TSoundEffect); override;
+ procedure RemoveSoundEffect(effect: TSoundEffect); override;
+
+ function GetPosition: real; override;
+ procedure SetPosition(Time: real); override;
- function IsLoaded(): boolean;
+ procedure GetFFTData(var data: TFFTData); override;
+ function GetPCMData(var data: TPCMData): Cardinal; override;
end;
-type
- TAudioPlayback_Bass = class( TInterfacedObject, IAudioPlayback)
+ // Playback-stream decoded by an external decoder e.g. FFmpeg
+ TBassExtDecoderPlaybackStream = class(TBassPlaybackStream)
private
- MusicStream: TBassPlaybackStream;
-
- function Load(const Filename: string): TBassPlaybackStream;
+ DecodeStream: TAudioDecodeStream;
public
- function GetName: String;
-
- {IAudioOutput interface}
-
- function InitializePlayback(): boolean;
- procedure SetVolume(Volume: integer);
- procedure SetMusicVolume(Volume: integer);
- procedure SetLoop(Enabled: boolean);
-
- function Open(const Filename: 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 Stop(); override;
+ procedure Close(); override;
+ function GetLength(): real; override;
+ function GetPosition: real; override;
+ procedure SetPosition(Time: real); override;
- //Equalizer
- procedure GetFFTData(var data: TFFTData);
+ function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
+ end;
- // Interface for Visualizer
- function GetPCMData(var data: TPCMData): Cardinal;
+type
+ TAudioPlayback_Bass = class(TAudioPlaybackBase)
+ private
+ function EnumDevices(): boolean;
+ protected
+ function OpenStream(const Filename: string): TAudioPlaybackStream; override;
+ public
+ function GetName: String; override;
+ function InitializePlayback(): boolean; override;
+ function FinalizePlayback: boolean; override;
+ procedure SetAppVolume(Volume: integer); override;
+ end;
- // Sounds
- function OpenSound(const Filename: String): TAudioPlaybackStream;
- procedure PlaySound(stream: TAudioPlaybackStream);
- procedure StopSound(stream: TAudioPlaybackStream);
+ TBassOutputDevice = class(TAudioOutputDevice)
+ private
+ BassDeviceID: DWORD; // DeviceID used by BASS
end;
var
singleton_AudioPlaybackBass : IAudioPlayback;
-constructor TBassPlaybackStream.Create();
+{ TBassPlaybackStream }
+
+constructor TBassPlaybackStream.Create(stream: HSTREAM);
begin
- inherited;
+ inherited Create();
Reset();
+ Handle := stream;
end;
-constructor TBassPlaybackStream.Create(stream: HSTREAM);
+destructor TBassPlaybackStream.Destroy();
begin
- Create();
- Handle := stream;
+ Close();
+ inherited;
end;
procedure TBassPlaybackStream.Reset();
begin
- Loop := false;
if (Handle <> 0) then
+ begin
Bass_StreamFree(Handle);
+ end;
Handle := 0;
end;
procedure TBassPlaybackStream.Play();
+var
+ restart: boolean;
begin
- BASS_ChannelPlay(Handle, Loop);
+ if (BASS_ChannelIsActive(Handle) = BASS_ACTIVE_PAUSED) then
+ restart := false // resume from last position
+ else
+ restart := true; // start from the beginning
+
+ BASS_ChannelPlay(Handle, restart);
+end;
+
+procedure TBassPlaybackStream.FadeIn(Time: real; TargetVolume: integer);
+begin
+ // start stream
+ BASS_ChannelPlay(Handle, true);
+
+ // start fade-in: slide from fadeStart- to fadeEnd-volume in FadeInTime
+ BASS_ChannelSlideAttributes(Handle, -1, TargetVolume, -101, Trunc(Time * 1000));
end;
procedure TBassPlaybackStream.Pause();
@@ -136,9 +156,11 @@ begin
end;
function TBassPlaybackStream.GetVolume(): integer;
+var
+ volume: cardinal;
begin
- Result := 0;
- BASS_ChannelSetAttributes(Handle, PInteger(nil)^, Result, PInteger(nil)^);
+ BASS_ChannelGetAttributes(Handle, PCardinal(nil)^, volume, PInteger(nil)^);
+ Result := volume;
end;
procedure TBassPlaybackStream.SetVolume(volume: integer);
@@ -168,19 +190,9 @@ begin
BASS_ChannelSetPosition(Handle, bytes);
end;
-function TBassPlaybackStream.GetLoop(): boolean;
-begin
- result := Loop;
-end;
-
-procedure TBassPlaybackStream.SetLoop(Enabled: boolean);
-begin
- Loop := Enabled;
-end;
-
function TBassPlaybackStream.GetLength(): real;
var
- bytes: integer;
+ bytes: integer;
begin
bytes := BASS_ChannelGetLength(Handle);
Result := BASS_ChannelBytes2Seconds(Handle, bytes);
@@ -205,212 +217,367 @@ begin
end;
end;
-function TBassPlaybackStream.IsLoaded(): boolean;
-begin
- Result := (Handle <> 0);
-end;
-
-
-function TAudioPlayback_Bass.GetName: String;
+function TBassPlaybackStream.GetLoop(): boolean;
+var
+ info: BASS_CHANNELINFO;
begin
- result := 'BASS_Playback';
+ if not BASS_ChannelGetInfo(Handle, info) then
+ begin
+ Log.LogError('BASS_ChannelGetInfo: ' + TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.GetLoop');
+ Result := false;
+ Exit;
+ end;
+ Result := (info.flags and BASS_SAMPLE_LOOP) <> 0;
end;
-function TAudioPlayback_Bass.InitializePlayback(): boolean;
+procedure TBassPlaybackStream.SetLoop(Enabled: boolean);
var
- Pet: integer;
- S: integer;
+ info: BASS_CHANNELINFO;
begin
- result := false;
-
- //Log.BenchmarkStart(4);
- //Log.LogStatus('Initializing Playback Subsystem', 'Music Initialize');
-
- if not BASS_Init(1, 44100, 0, 0, nil) then
+ // retrieve old flag-bits
+ if not BASS_ChannelGetInfo(Handle, info) then
begin
- Log.LogError('Could not initialize BASS', 'Error');
+ Log.LogError('BASS_ChannelGetInfo:' + TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.SetLoop');
Exit;
end;
- //Log.BenchmarkEnd(4); Log.LogBenchmark('--> Bass Init', 4);
+ // set/unset loop-flag
+ if (Enabled) then
+ info.flags := info.flags or BASS_SAMPLE_LOOP
+ else
+ info.flags := info.flags and not BASS_SAMPLE_LOOP;
- // config playing buffer
- //BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
- //BASS_SetConfig(BASS_CONFIG_BUFFER, 100);
+ // set new flag-bits
+ if not BASS_ChannelSetFlags(Handle, info.flags) then
+ begin
+ Log.LogError('BASS_ChannelSetFlags: ' + TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.SetLoop');
+ Exit;
+ end;
+end;
- result := true;
+procedure DSPProcHandler(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: DWORD); stdcall;
+var
+ effect: TSoundEffect;
+begin
+ effect := TSoundEffect(user);
+ if assigned(effect) then
+ effect.Callback(buffer, length);
end;
-function TAudioPlayback_Bass.Load(const Filename: string): TBassPlaybackStream;
+procedure TBassPlaybackStream.AddSoundEffect(effect: TSoundEffect);
var
- L: Integer;
- stream: HSTREAM;
+ dspHandle: HDSP;
begin
- Result := nil;
+ if assigned(effect.engineData) then
+ begin
+ Log.LogError('TSoundEffect.engineData already set', 'TBassPlaybackStream.AddSoundEffect');
+ Exit;
+ end;
- //Log.LogStatus('Loading Sound: "' + Filename + '"', 'LoadSoundFromFile');
- stream := BASS_StreamCreateFile(False, pchar(Filename), 0, 0, 0);
- if (stream = 0) then
+ // FIXME: casting of a pointer to Uint32 will fail on 64bit systems
+ dspHandle := BASS_ChannelSetDSP(Handle, @DSPProcHandler, DWORD(effect), 0);
+ if (dspHandle = 0) then
begin
- Log.LogError('Failed to open "' + Filename + '", ' +
- TAudioCore_Bass.ErrorGetString(BASS_ErrorGetCode()), 'TAudioPlayback_Bass.Load');
+ Log.LogError(TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.AddSoundEffect');
Exit;
end;
- Result := TBassPlaybackStream.Create(stream);
+ GetMem(effect.engineData, SizeOf(HDSP));
+ PHDSP(effect.engineData)^ := dspHandle;
end;
-procedure TAudioPlayback_Bass.SetVolume(Volume: integer);
+procedure TBassPlaybackStream.RemoveSoundEffect(effect: TSoundEffect);
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;
+ if not assigned(effect.EngineData) then
+ begin
+ Log.LogError('TSoundEffect.engineData invalid', 'TBassPlaybackStream.RemoveSoundEffect');
+ Exit;
+ end;
-procedure TAudioPlayback_Bass.SetMusicVolume(Volume: Integer);
-begin
- if assigned(MusicStream) then
- MusicStream.SetVolume(Volume);
+ if not BASS_ChannelRemoveDSP(Handle, PHDSP(effect.EngineData)^) then
+ begin
+ Log.LogError(TAudioCore_Bass.ErrorGetString(), 'TBassPlaybackStream.RemoveSoundEffect');
+ Exit;
+ end;
+
+ FreeMem(effect.engineData);
+ effect.engineData := nil;
end;
-procedure TAudioPlayback_Bass.SetLoop(Enabled: boolean);
+procedure TBassPlaybackStream.GetFFTData(var data: TFFTData);
begin
- if assigned(MusicStream) then
- MusicStream.Loop := Enabled;
+ // Get Channel Data Mono and 256 Values
+ BASS_ChannelGetData(Handle, @data, BASS_DATA_FFT512);
end;
-function TAudioPlayback_Bass.Open(const Filename: string): boolean;
+{*
+ * Copies interleaved PCM SInt16 stereo samples into data.
+ * Returns the number of frames
+ *}
+function TBassPlaybackStream.GetPCMData(var data: TPCMData): Cardinal;
var
- stream: HSTREAM;
+ info: BASS_CHANNELINFO;
+ nBytes: DWORD;
begin
- Result := false;
+ Result := 0;
- // free old MusicStream
- if assigned(MusicStream) then
- MusicStream.Free;
+ // Get Channel Data Mono and 256 Values
+ BASS_ChannelGetInfo(Handle, info);
+ FillChar(data, sizeof(TPCMData), 0);
- MusicStream := Load(Filename);
- if not assigned(MusicStream) then
+ // no support for non-stereo files at the moment
+ if (info.chans <> 2) then
Exit;
- //Set Max Volume
- SetMusicVolume(100);
-
- Result := true;
+ nBytes := BASS_ChannelGetData(Handle, @data, sizeof(TPCMData));
+ if(nBytes <= 0) then
+ result := 0
+ else
+ result := nBytes div sizeof(TPCMStereoSample);
end;
-procedure TAudioPlayback_Bass.Rewind;
-begin
- SetPosition(0);
-end;
-procedure TAudioPlayback_Bass.Play;
-begin
- if assigned(MusicStream) then
- MusicStream.Play();
-end;
+{ TBassExtDecoderPlaybackStream }
-procedure TAudioPlayback_Bass.Pause;
+procedure TBassExtDecoderPlaybackStream.Stop();
begin
- if assigned(MusicStream) then
- MusicStream.Pause();
+ inherited;
+ // rewind
+ if assigned(DecodeStream) then
+ DecodeStream.Position := 0;
end;
-procedure TAudioPlayback_Bass.Stop;
+procedure TBassExtDecoderPlaybackStream.Close();
begin
- if assigned(MusicStream) then
- MusicStream.Stop();
+ // wake-up waiting audio-callback threads in the ReadData()-function
+ if assigned(decodeStream) then
+ DecodeStream.Close();
+ // stop audio-callback on this stream
+ inherited;
+ // free decoder-data
+ FreeAndNil(DecodeStream);
end;
-procedure TAudioPlayback_Bass.Close;
+function TBassExtDecoderPlaybackStream.GetLength(): real;
begin
- if assigned(MusicStream) then
- MusicStream.Close();
+ if assigned(DecodeStream) then
+ result := DecodeStream.Length
+ else
+ result := -1;
end;
-function TAudioPlayback_Bass.Length: real;
-var
- bytes: integer;
+function TBassExtDecoderPlaybackStream.GetPosition: real;
begin
- if assigned(MusicStream) then
- Result := MusicStream.GetLength()
+ if assigned(DecodeStream) then
+ result := DecodeStream.Position
else
- Result := -1;
+ result := -1;
end;
-function TAudioPlayback_Bass.GetPosition: real;
+procedure TBassExtDecoderPlaybackStream.SetPosition(Time: real);
begin
- if assigned(MusicStream) then
- Result := MusicStream.GetPosition()
- else
- Result := -1;
+ if assigned(DecodeStream) then
+ DecodeStream.Position := Time;
end;
-procedure TAudioPlayback_Bass.SetPosition(Time: real);
+function TBassExtDecoderPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
begin
- if assigned(MusicStream) then
- MusicStream.SetPosition(Time);
+ result := false;
+
+ BASS_ChannelStop(Handle);
+
+ if not assigned(decodeStream) then
+ Exit;
+ Self.DecodeStream := decodeStream;
+
+ result := true;
end;
-function TAudioPlayback_Bass.Finished: boolean;
+
+{ TAudioPlayback_Bass }
+
+function TAudioPlayback_Bass.GetName: String;
begin
- if assigned(MusicStream) then
- Result := (MusicStream.GetStatus() = ssStopped)
- else
- Result := true;
+ result := 'BASS_Playback';
end;
-// Equalizer
-procedure TAudioPlayback_Bass.GetFFTData(var data: TFFTData);
+function TAudioPlayback_Bass.EnumDevices(): boolean;
+var
+ BassDeviceID: DWORD;
+ DeviceIndex: integer;
+ Device: TBassOutputDevice;
+ Description: PChar;
begin
- // Get Channel Data Mono and 256 Values
- BASS_ChannelGetData(MusicStream.Handle, @data, BASS_DATA_FFT512);
+ ClearOutputDeviceList();
+
+ // skip "no sound"-device (ID = 0)
+ BassDeviceID := 1;
+
+ while true do
+ begin
+ // Check for device
+ Description := BASS_GetDeviceDescription(BassDeviceID);
+ if (Description = nil) then
+ break;
+
+ // Set device info
+ Device := TBassOutputDevice.Create();
+ Device.Name := Description;
+ Device.BassDeviceID := BassDeviceID;
+
+ // Add device to list
+ SetLength(OutputDeviceList, BassDeviceID);
+ OutputDeviceList[BassDeviceID-1] := Device;
+
+ Inc(BassDeviceID);
+ end;
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;
+function TAudioPlayback_Bass.InitializePlayback(): boolean;
var
- info: BASS_CHANNELINFO;
- nBytes: DWORD;
+ Pet: integer;
+ S: integer;
begin
- Result := 0;
+ result := false;
- // Get Channel Data Mono and 256 Values
- BASS_ChannelGetInfo(MusicStream.Handle, info);
- FillChar(data, sizeof(TPCMData), 0);
+ EnumDevices();
- // no support for non-stereo files at the moment
- if (info.chans <> 2) then
+ //Log.BenchmarkStart(4);
+ //Log.LogStatus('Initializing Playback Subsystem', 'Music Initialize');
+
+ if not BASS_Init(1, 44100, 0, 0, nil) then
+ begin
+ Log.LogError('Could not initialize BASS', 'Error');
Exit;
+ end;
- nBytes := BASS_ChannelGetData(MusicStream.Handle, @data, sizeof(TPCMData));
- if(nBytes <= 0) then
- result := 0
+ //Log.BenchmarkEnd(4); Log.LogBenchmark('--> Bass Init', 4);
+
+ // config playing buffer
+ //BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
+ //BASS_SetConfig(BASS_CONFIG_BUFFER, 100);
+
+ result := true;
+end;
+
+function DecodeStreamHandler(handle: HSTREAM; buffer: Pointer; length: DWORD; user: DWORD): DWORD; stdcall;
+var
+ decodeStream: TAudioDecodeStream;
+ bytes: integer;
+begin
+ decodeStream := TAudioDecodeStream(user);
+ bytes := decodeStream.ReadData(buffer, length);
+ // handle errors
+ if (bytes < 0) then
+ Result := BASS_STREAMPROC_END
+ // handle EOF
+ else if (DecodeStream.EOF) then
+ Result := bytes or BASS_STREAMPROC_END
else
- result := nBytes div sizeof(TPCMStereoSample);
+ Result := bytes;
end;
-function TAudioPlayback_Bass.OpenSound(const Filename: string): TAudioPlaybackStream;
+function TAudioPlayback_Bass.FinalizePlayback(): boolean;
begin
- result := Load(Filename);
+ Close;
+ BASS_Free;
+ inherited FinalizePlayback();
+ Result := true;
end;
-procedure TAudioPlayback_Bass.PlaySound(stream: TAudioPlaybackStream);
+function TAudioPlayback_Bass.OpenStream(const Filename: string): TAudioPlaybackStream;
+var
+ L: Integer;
+ stream: HSTREAM;
+ playbackStream: TBassExtDecoderPlaybackStream;
+ decodeStream: TAudioDecodeStream;
+ formatInfo: TAudioFormatInfo;
+ formatFlags: DWORD;
+ channelInfo: BASS_CHANNELINFO;
+ fileExt: string;
begin
- if assigned(stream) then
- stream.Play();
+ Result := nil;
+
+ //Log.LogStatus('Loading Sound: "' + Filename + '"', 'LoadSoundFromFile');
+ stream := BASS_StreamCreateFile(False, PChar(Filename), 0, 0, 0);
+
+ // check if BASS opened some erroneously recognized file-formats
+ if (stream <> 0) then
+ begin
+ if BASS_ChannelGetInfo(stream, channelInfo) then
+ begin
+ fileExt := ExtractFileExt(Filename);
+ // BASS opens FLV-files although it cannot handle them
+ if ((fileExt = '.flv') and (channelInfo.ctype = BASS_CTYPE_STREAM_MP1)) then
+ begin
+ BASS_StreamFree(stream);
+ stream := 0;
+ end;
+ end;
+ end;
+
+ // Check if BASS can handle the format or try another decoder otherwise
+ if (stream <> 0) then
+ begin
+ Result := TBassPlaybackStream.Create(stream);
+ end
+ else
+ begin
+ if (AudioDecoder = nil) then
+ begin
+ Log.LogError('Failed to open "' + Filename + '", ' +
+ TAudioCore_Bass.ErrorGetString(BASS_ErrorGetCode()), 'TAudioPlayback_Bass.Load');
+ Exit;
+ end;
+
+ decodeStream := AudioDecoder.Open(Filename);
+ if not assigned(decodeStream) then
+ begin
+ Log.LogStatus('Sound not found "' + Filename + '"', 'TAudioPlayback_Bass.Load');
+ Exit;
+ end;
+
+ formatInfo := decodeStream.GetAudioFormatInfo();
+ if (not TAudioCore_Bass.ConvertAudioFormatToBASSFlags(formatInfo.Format, formatFlags)) then
+ begin
+ Log.LogError('Unhandled sample-format in "' + Filename + '"', 'TAudioPlayback_Bass.Load');
+ FreeAndNil(decodeStream);
+ Exit;
+ end;
+
+ // FIXME: casting of a pointer to Uint32 will fail on 64bit systems
+ stream := BASS_StreamCreate(Round(formatInfo.SampleRate), formatInfo.Channels, formatFlags,
+ @DecodeStreamHandler, DWORD(decodeStream));
+ if (stream = 0) then
+ begin
+ Log.LogError('Failed to open "' + Filename + '", ' +
+ TAudioCore_Bass.ErrorGetString(BASS_ErrorGetCode()), 'TAudioPlayback_Bass.Load');
+ FreeAndNil(decodeStream);
+ Exit;
+ end;
+
+ playbackStream := TBassExtDecoderPlaybackStream.Create(stream);
+ if (not assigned(playbackStream)) then
+ begin
+ FreeAndNil(decodeStream);
+ Exit;
+ end;
+
+ if (not playbackStream.SetDecodeStream(decodeStream)) then
+ begin
+ FreeAndNil(playbackStream);
+ FreeAndNil(decodeStream);
+ Exit;
+ end;
+
+ Result := playbackStream;
+ end;
end;
-procedure TAudioPlayback_Bass.StopSound(stream: TAudioPlaybackStream);
+procedure TAudioPlayback_Bass.SetAppVolume(Volume: integer);
begin
- if assigned(stream) then
- stream.Stop();
+ // Sets Volume only for this Application
+ BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Volume);
end;
diff --git a/Game/Code/Classes/UAudioPlayback_Portaudio.pas b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
index 431fbd43..2c52c41e 100644
--- a/Game/Code/Classes/UAudioPlayback_Portaudio.pas
+++ b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
@@ -28,12 +28,21 @@ type
TAudioPlayback_Portaudio = class(TAudioPlayback_SoftMixer)
private
paStream: PPaStream;
+ AudioCore: TAudioCore_Portaudio;
+ function OpenDevice(deviceIndex: TPaDeviceIndex): boolean;
+ function EnumDevices(): boolean;
protected
function InitializeAudioPlaybackEngine(): boolean; override;
function StartAudioPlaybackEngine(): boolean; override;
procedure StopAudioPlaybackEngine(); override;
+ function FinalizeAudioPlaybackEngine(): boolean; override;
public
- function GetName: String; override;
+ function GetName: String; override;
+ end;
+
+ TPortaudioOutputDevice = class(TAudioOutputDevice)
+ private
+ PaDeviceIndex: TPaDeviceIndex;
end;
var
@@ -58,50 +67,39 @@ begin
result := 'Portaudio_Playback';
end;
-function TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine(): boolean;
+function TAudioPlayback_Portaudio.OpenDevice(deviceIndex: TPaDeviceIndex): boolean;
var
- paApiIndex : TPaHostApiIndex;
- paApiInfo : PPaHostApiInfo;
- paOutParams : TPaStreamParameters;
- paOutDevice : TPaDeviceIndex;
- paOutDeviceInfo : PPaDeviceInfo;
- err : TPaError;
- sampleRate : double;
+ deviceInfo : PPaDeviceInfo;
+ sampleRate : double;
+ outParams : TPaStreamParameters;
+ err : TPaError;
begin
- result := false;
+ Result := false;
- Pa_Initialize();
+ deviceInfo := Pa_GetDeviceInfo(deviceIndex);
- paApiIndex := TAudioCore_Portaudio.GetPreferredApiIndex();
- if(paApiIndex = -1) then
- begin
- Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine');
- Exit;
- end;
-
- paApiInfo := Pa_GetHostApiInfo(paApiIndex);
- paOutDevice := paApiInfo^.defaultOutputDevice;
- paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice);
+ Log.LogInfo('Audio-Output Device: ' + deviceInfo^.name, 'TAudioPlayback_Portaudio.OpenDevice');
- sampleRate := paOutDeviceInfo^.defaultSampleRate;
+ sampleRate := deviceInfo^.defaultSampleRate;
- with paOutParams do begin
- device := paOutDevice;
+ with outParams do
+ begin
+ device := deviceIndex;
channelCount := 2;
sampleFormat := paInt16;
- suggestedLatency := paOutDeviceInfo^.defaultLowOutputLatency;
+ suggestedLatency := deviceInfo^.defaultLowOutputLatency;
hostApiSpecificStreamInfo := nil;
end;
// check souncard and adjust sample-rate
- if not TAudioCore_Portaudio.TestDevice(nil, @paOutParams, sampleRate) then
+ if not AudioCore.TestDevice(nil, @outParams, sampleRate) then
begin
Log.LogStatus('TestDevice failed!', 'TAudioPlayback_Portaudio.OpenDevice');
exit;
end;
// open output stream
- err := Pa_OpenStream(paStream, nil, @paOutParams, sampleRate,
+ err := Pa_OpenStream(paStream, nil, @outParams, sampleRate,
paFramesPerBufferUnspecified,
paNoFlag, @PortaudioAudioCallback, Self);
if(err <> paNoError) then
@@ -112,12 +110,199 @@ begin
end;
FormatInfo := TAudioFormatInfo.Create(
- paOutParams.channelCount,
+ outParams.channelCount,
sampleRate,
asfS16 // FIXME: is paInt16 system-dependant or -independant?
);
- Log.LogStatus('Opened audio device', 'UAudioPlayback_Portaudio');
+ Result := true;
+end;
+
+function TAudioPlayback_Portaudio.EnumDevices(): boolean;
+var
+ i: integer;
+ paApiIndex: TPaHostApiIndex;
+ paApiInfo: PPaHostApiInfo;
+ deviceName: string;
+ deviceIndex: TPaDeviceIndex;
+ deviceInfo: PPaDeviceInfo;
+ channelCnt: integer;
+ SC: integer; // soundcard
+ err: TPaError;
+ errMsg: string;
+ paDevice: TPortaudioOutputDevice;
+ inputParams: TPaStreamParameters;
+ stream: PPaStream;
+ streamInfo: PPaStreamInfo;
+ sampleRate: double;
+ latency: TPaTime;
+ cbPolls: integer;
+ cbWorks: boolean;
+begin
+ Result := false;
+(*
+ // choose the best available Audio-API
+ paApiIndex := AudioCore.GetPreferredApiIndex();
+ if(paApiIndex = -1) then
+ begin
+ Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.EnumDevices');
+ Exit;
+ end;
+
+ paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+
+ SC := 0;
+
+ // init array-size to max. output-devices count
+ SetLength(OutputDeviceList, paApiInfo^.deviceCount);
+ for i:= 0 to High(OutputDeviceList) do
+ begin
+ // convert API-specific device-index to global index
+ deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
+ deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+
+ channelCnt := deviceInfo^.maxOutputChannels;
+
+ // current device is no output device -> skip
+ if (channelCnt <= 0) then
+ continue;
+
+ // portaudio returns a channel-count of 128 for some devices
+ // (e.g. the "default"-device), so we have to detect those
+ // fantasy channel counts.
+ if (channelCnt > 8) then
+ channelCnt := 2;
+
+ paDevice := TPortaudioOutputDevice.Create();
+ OutputDeviceList[SC] := paDevice;
+
+ // retrieve device-name
+ deviceName := deviceInfo^.name;
+ paDevice.Name := deviceName;
+ paDevice.PaDeviceIndex := deviceIndex;
+
+ if (deviceInfo^.defaultSampleRate > 0) then
+ sampleRate := deviceInfo^.defaultSampleRate
+ else
+ sampleRate := 44100;
+
+ // on vista and xp the defaultLowInputLatency may be set to 0 but it works.
+ // TODO: correct too low latencies (what is a too low latency, maybe < 10ms?)
+ latency := deviceInfo^.defaultLowInputLatency;
+
+ // setup desired input parameters
+ // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
+ // not be set correctly in OSS)
+ with inputParams do
+ begin
+ device := deviceIndex;
+ channelCount := channelCnt;
+ sampleFormat := paInt16;
+ suggestedLatency := latency;
+ hostApiSpecificStreamInfo := nil;
+ end;
+
+ // check if mic-callback works (might not be called on some devices)
+ if (not TAudioCore_Portaudio.TestDevice(@inputParams, nil, sampleRate)) then
+ begin
+ // ignore device if callback did not work
+ Log.LogError('Device "'+paDevice.Name+'" does not respond',
+ 'TAudioInput_Portaudio.InitializeRecord');
+ paDevice.Free();
+ continue;
+ end;
+
+ // open device for further info
+ err := Pa_OpenStream(stream, @inputParams, nil, sampleRate,
+ paFramesPerBufferUnspecified, paNoFlag, @MicrophoneTestCallback, nil);
+ if(err <> paNoError) then
+ begin
+ // unable to open device -> skip
+ errMsg := Pa_GetErrorText(err);
+ Log.LogError('Device error: "'+ deviceName +'" ('+ errMsg +')',
+ 'TAudioInput_Portaudio.InitializeRecord');
+ paDevice.Free();
+ continue;
+ end;
+
+ // adjust sample-rate (might be changed by portaudio)
+ streamInfo := Pa_GetStreamInfo(stream);
+ if (streamInfo <> nil) then
+ begin
+ if (sampleRate <> streamInfo^.sampleRate) then
+ begin
+ Log.LogStatus('Portaudio changed Samplerate from ' + FloatToStr(sampleRate) +
+ ' to ' + FloatToStr(streamInfo^.sampleRate),
+ 'TAudioInput_Portaudio.InitializeRecord');
+ sampleRate := streamInfo^.sampleRate;
+ end;
+ end;
+
+ // create audio-format info and resize capture-buffer array
+ paDevice.AudioFormat := TAudioFormatInfo.Create(
+ channelCnt,
+ sampleRate,
+ asfS16
+ );
+ SetLength(paDevice.CaptureChannel, paDevice.AudioFormat.Channels);
+
+ Log.LogStatus('InputDevice "'+paDevice.Name+'"@' +
+ IntToStr(paDevice.AudioFormat.Channels)+'x'+
+ FloatToStr(paDevice.AudioFormat.SampleRate)+'Hz ('+
+ FloatTostr(inputParams.suggestedLatency)+'sec)' ,
+ 'Portaudio.InitializeRecord');
+
+ // close test-stream
+ Pa_CloseStream(stream);
+
+ Inc(SC);
+ end;
+
+ // adjust size to actual input-device count
+ SetLength(OutputDeviceList, SC);
+
+ Log.LogStatus('#Output-Devices: ' + inttostr(SC), 'Portaudio');
+
+ Result := true;
+ *)
+end;
+
+function TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine(): boolean;
+var
+ paApiIndex : TPaHostApiIndex;
+ paApiInfo : PPaHostApiInfo;
+ paOutDevice : TPaDeviceIndex;
+ err: TPaError;
+begin
+ result := false;
+
+ AudioCore := TAudioCore_Portaudio.GetInstance();
+
+ // initialize portaudio
+ err := Pa_Initialize();
+ if(err <> paNoError) then
+ begin
+ Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord');
+ Exit;
+ end;
+
+ paApiIndex := AudioCore.GetPreferredApiIndex();
+ if(paApiIndex = -1) then
+ begin
+ Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine');
+ Exit;
+ end;
+
+ EnumDevices();
+
+ paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+ Log.LogInfo('Audio-Output API-Type: ' + paApiInfo^.name, 'TAudioPlayback_Portaudio.OpenDevice');
+
+ paOutDevice := paApiInfo^.defaultOutputDevice;
+ if (not OpenDevice(paOutDevice)) then
+ begin
+ Exit;
+ end;
result := true;
end;
@@ -147,6 +332,11 @@ begin
Pa_StopStream(paStream);
end;
+function TAudioPlayback_Portaudio.FinalizeAudioPlaybackEngine(): boolean;
+begin
+ Pa_Terminate();
+ Result := true;
+end;
initialization
diff --git a/Game/Code/Classes/UAudioPlayback_SDL.pas b/Game/Code/Classes/UAudioPlayback_SDL.pas
index 6fc22242..ed5a208b 100644
--- a/Game/Code/Classes/UAudioPlayback_SDL.pas
+++ b/Game/Code/Classes/UAudioPlayback_SDL.pas
@@ -25,12 +25,15 @@ uses
type
TAudioPlayback_SDL = class(TAudioPlayback_SoftMixer)
+ private
+ function EnumDevices(): boolean;
protected
function InitializeAudioPlaybackEngine(): boolean; override;
function StartAudioPlaybackEngine(): boolean; override;
procedure StopAudioPlaybackEngine(); override;
+ function FinalizeAudioPlaybackEngine(): boolean; override;
public
- function GetName: String; override;
+ function GetName: String; override;
procedure MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer); override;
end;
@@ -53,14 +56,37 @@ begin
result := 'SDL_Playback';
end;
+function TAudioPlayback_SDL.EnumDevices(): boolean;
+begin
+ // Note: SDL does not provide Device-Selection capabilities (will be introduced in 1.3)
+ ClearOutputDeviceList();
+ SetLength(OutputDeviceList, 1);
+ OutputDeviceList[0] := TAudioOutputDevice.Create();
+ OutputDeviceList[0].Name := '[SDL Default-Device]';
+ Result := true;
+end;
+
function TAudioPlayback_SDL.InitializeAudioPlaybackEngine(): boolean;
var
desiredAudioSpec, obtainedAudioSpec: TSDL_AudioSpec;
-// err: integer; // Auto Removed, Unused Variable
+ SampleBufferSize: integer;
begin
result := false;
- SDL_InitSubSystem(SDL_INIT_AUDIO);
+ EnumDevices();
+
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO) = -1) then
+ begin
+ Log.LogError('SDL_InitSubSystem failed!', 'TAudioPlayback_SDL.InitializeAudioPlaybackEngine');
+ exit;
+ end;
+
+ SampleBufferSize := IAudioOutputBufferSizeVals[Ini.AudioOutputBufferSizeIndex];
+ if (SampleBufferSize <= 0) then
+ begin
+ // Automatic setting defaults to 1024 samples
+ SampleBufferSize := 1024;
+ end;
FillChar(desiredAudioSpec, sizeof(desiredAudioSpec), 0);
with desiredAudioSpec do
@@ -68,14 +94,14 @@ begin
freq := 44100;
format := AUDIO_S16SYS;
channels := 2;
- samples := Ini.SDLBufferSize;
+ samples := SampleBufferSize;
callback := @SDLAudioCallback;
userdata := Self;
end;
if(SDL_OpenAudio(@desiredAudioSpec, @obtainedAudioSpec) = -1) then
begin
- Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'UAudioPlayback_SDL');
+ Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'TAudioPlayback_SDL.InitializeAudioPlaybackEngine');
exit;
end;
@@ -85,7 +111,7 @@ begin
asfS16
);
- Log.LogStatus('Opened audio device', 'UAudioPlayback_SDL');
+ Log.LogStatus('Opened audio device', 'TAudioPlayback_SDL.InitializeAudioPlaybackEngine');
result := true;
end;
@@ -98,7 +124,14 @@ end;
procedure TAudioPlayback_SDL.StopAudioPlaybackEngine();
begin
+ SDL_PauseAudio(1);
+end;
+
+function TAudioPlayback_SDL.FinalizeAudioPlaybackEngine(): boolean;
+begin
SDL_CloseAudio();
+ SDL_QuitSubSystem(SDL_INIT_AUDIO);
+ Result := true;
end;
procedure TAudioPlayback_SDL.MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer);
@@ -116,5 +149,4 @@ initialization
finalization
AudioManager.Remove( singleton_AudioPlaybackSDL );
-
end.
diff --git a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
index f65b3d9a..431653d0 100644
--- a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
+++ b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
@@ -13,7 +13,8 @@ uses
Classes,
SysUtils,
sdl,
- UMusic;
+ UMusic,
+ UAudioPlaybackBase;
type
TAudioPlayback_SoftMixer = class;
@@ -35,9 +36,14 @@ type
InternalLock: PSDL_Mutex;
+ SoundEffects: TList;
+
+ FadeInStartTime, FadeInTime: cardinal;
+ FadeInStartVolume, FadeInTargetVolume: integer;
+
procedure Reset();
- class function ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16;
+ class function ConvertAudioFormatToSDL(Format: TAudioSampleFormat; out SDLFormat: UInt16): boolean;
function InitFormatConversion(): boolean;
procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
@@ -51,24 +57,26 @@ type
procedure Play(); override;
procedure Pause(); override;
procedure Stop(); override;
+ procedure FadeIn(Time: real; TargetVolume: integer); 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;
+ function GetLoop(): boolean; override;
+ procedure SetLoop(Enabled: boolean); override;
+ function GetPosition: real; override;
+ procedure SetPosition(Time: real); override;
- // functions delegated to the decode stream
- function GetPosition: real;
- procedure SetPosition(Time: real);
function ReadData(Buffer: PChar; BufSize: integer): integer;
- function GetPCMData(var data: TPCMData): Cardinal;
- procedure GetFFTData(var data: TFFTData);
+ function GetPCMData(var data: TPCMData): Cardinal; override;
+ procedure GetFFTData(var data: TFFTData); override;
+
+ procedure AddSoundEffect(effect: TSoundEffect); override;
+ procedure RemoveSoundEffect(effect: TSoundEffect); override;
end;
TAudioMixerStream = class
@@ -79,7 +87,7 @@ type
mixerBuffer: PChar;
internalLock: PSDL_Mutex;
- _volume: integer;
+ appVolume: integer;
procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
@@ -96,9 +104,8 @@ type
property Volume: integer READ GetVolume WRITE SetVolume;
end;
- TAudioPlayback_SoftMixer = class( TInterfacedObject, IAudioPlayback )
+ TAudioPlayback_SoftMixer = class(TAudioPlaybackBase)
private
- MusicStream: TGenericPlaybackStream;
MixerStream: TAudioMixerStream;
protected
FormatInfo: TAudioFormatInfo;
@@ -106,45 +113,21 @@ type
function InitializeAudioPlaybackEngine(): boolean; virtual; abstract;
function StartAudioPlaybackEngine(): boolean; virtual; abstract;
procedure StopAudioPlaybackEngine(); virtual; abstract;
+ function FinalizeAudioPlaybackEngine(): boolean; virtual; abstract;
procedure AudioCallback(buffer: PChar; size: integer); {$IFDEF HasInline}inline;{$ENDIF}
- public
- function GetName: String; virtual; abstract;
-
- function InitializePlayback(): boolean;
- destructor Destroy; override;
- function Load(const Filename: String): TGenericPlaybackStream;
-
- procedure SetVolume(Volume: integer);
- procedure SetMusicVolume(Volume: integer);
- procedure SetLoop(Enabled: boolean);
- function Open(const 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);
+ function OpenStream(const Filename: String): TAudioPlaybackStream; override;
+ public
+ function GetName: String; override; abstract;
+ function InitializePlayback(): boolean; override;
+ function FinalizePlayback: boolean; override;
- // Interface for Visualizer
- function GetPCMData(var data: TPCMData): Cardinal;
+ procedure SetAppVolume(Volume: integer); override;
function GetMixer(): TAudioMixerStream; {$IFDEF HasInline}inline;{$ENDIF}
function GetAudioFormatInfo(): TAudioFormatInfo;
procedure MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer); virtual;
-
- // Sounds
- function OpenSound(const Filename: String): TAudioPlaybackStream;
- procedure PlaySound(stream: TAudioPlaybackStream);
- procedure StopSound(stream: TAudioPlaybackStream);
end;
implementation
@@ -161,11 +144,13 @@ uses
constructor TAudioMixerStream.Create(Engine: TAudioPlayback_SoftMixer);
begin
+ inherited Create();
+
Self.Engine := Engine;
activeStreams := TList.Create;
internalLock := SDL_CreateMutex();
- _volume := 100;
+ appVolume := 100;
end;
destructor TAudioMixerStream.Destroy();
@@ -174,6 +159,7 @@ begin
Freemem(mixerBuffer);
activeStreams.Free;
SDL_DestroyMutex(internalLock);
+ inherited;
end;
procedure TAudioMixerStream.Lock();
@@ -189,14 +175,14 @@ end;
function TAudioMixerStream.GetVolume(): integer;
begin
Lock();
- result := _volume;
+ result := appVolume;
Unlock();
end;
procedure TAudioMixerStream.SetVolume(volume: integer);
begin
Lock();
- _volume := volume;
+ appVolume := volume;
Unlock();
end;
@@ -270,8 +256,8 @@ begin
if (size > 0) then
begin
// mix stream-data with mixer-buffer
- // Note: use _volume (Application-Volume) instead of Volume to prevent recursive locking
- Engine.MixBuffers(Buffer, mixerBuffer, size, _volume * stream.Volume div 100);
+ // Note: use Self.appVolume instead of Self.Volume to prevent recursive locking
+ Engine.MixBuffers(Buffer, mixerBuffer, size, appVolume * stream.Volume div 100);
end;
end;
@@ -292,6 +278,8 @@ begin
inherited Create();
Self.Engine := Engine;
internalLock := SDL_CreateMutex();
+ SoundEffects := TList.Create;
+ Status := ssStopped;
Reset();
end;
@@ -299,20 +287,34 @@ destructor TGenericPlaybackStream.Destroy();
begin
Close();
SDL_DestroyMutex(internalLock);
- inherited Destroy();
+ FreeAndNil(SoundEffects);
+ inherited;
end;
procedure TGenericPlaybackStream.Reset();
begin
+ // wake-up sleeping audio-callback threads in the ReadData()-function
+ if assigned(decodeStream) then
+ decodeStream.Close();
+
+ // stop audio-callback on this stream
Stop();
+
+ // reset and/or free data
+
Loop := false;
+
// TODO: use DecodeStream.Unref() instead of Free();
FreeAndNil(DecodeStream);
+
FreeMem(SampleBuffer);
SampleBuffer := nil;
SampleBufferPos := 0;
BytesAvail := 0;
+
_volume := 0;
+ SoundEffects.Clear;
+ FadeInTime := 0;
end;
procedure TGenericPlaybackStream.Lock();
@@ -325,24 +327,27 @@ begin
SDL_mutexV(internalLock);
end;
-class function TGenericPlaybackStream.ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16;
+class function TGenericPlaybackStream.ConvertAudioFormatToSDL(Format: TAudioSampleFormat; out SDLFormat: UInt16): boolean;
begin
- case fmt of
- asfU8: Result := AUDIO_U8;
- asfS8: Result := AUDIO_S8;
- asfU16LSB: Result := AUDIO_U16LSB;
- asfS16LSB: Result := AUDIO_S16LSB;
- asfU16MSB: Result := AUDIO_U16MSB;
- asfS16MSB: Result := AUDIO_S16MSB;
- asfU16: Result := AUDIO_U16;
- asfS16: Result := AUDIO_S16;
- else Result := 0;
+ case Format of
+ asfU8: SDLFormat := AUDIO_U8;
+ asfS8: SDLFormat := AUDIO_S8;
+ asfU16LSB: SDLFormat := AUDIO_U16LSB;
+ asfS16LSB: SDLFormat := AUDIO_S16LSB;
+ asfU16MSB: SDLFormat := AUDIO_U16MSB;
+ asfS16MSB: SDLFormat := AUDIO_S16MSB;
+ asfU16: SDLFormat := AUDIO_U16;
+ asfS16: SDLFormat := AUDIO_S16;
+ else begin
+ Result := false;
+ Exit;
+ end;
end;
+ Result := true;
end;
function TGenericPlaybackStream.InitFormatConversion(): boolean;
var
- //err: integer;
srcFormat: UInt16;
dstFormat: UInt16;
srcFormatInfo: TAudioFormatInfo;
@@ -353,10 +358,8 @@ begin
srcFormatInfo := DecodeStream.GetAudioFormatInfo();
dstFormatInfo := Engine.GetAudioFormatInfo();
- srcFormat := ConvertAudioFormatToSDL(srcFormatInfo.Format);
- dstFormat := ConvertAudioFormatToSDL(dstFormatInfo.Format);
-
- if ((srcFormat = 0) or (dstFormat = 0)) then
+ if (not ConvertAudioFormatToSDL(srcFormatInfo.Format, srcFormat) or
+ not ConvertAudioFormatToSDL(dstFormatInfo.Format, dstFormat)) then
begin
Log.LogError('Audio-format not supported by SDL', 'TSoftMixerPlaybackStream.InitFormatConversion');
Exit;
@@ -399,7 +402,7 @@ procedure TGenericPlaybackStream.Play();
var
mixer: TAudioMixerStream;
begin
- if (status <> ssPaused) then
+ if (status = ssPlaying) then
begin
// rewind
if assigned(DecodeStream) then
@@ -412,6 +415,15 @@ begin
mixer.AddStream(Self);
end;
+procedure TGenericPlaybackStream.FadeIn(Time: real; TargetVolume: integer);
+begin
+ FadeInTime := Trunc(Time * 1000);
+ FadeInStartTime := SDL_GetTicks();
+ FadeInStartVolume := _volume;
+ FadeInTargetVolume := TargetVolume;
+ Play();
+end;
+
procedure TGenericPlaybackStream.Pause();
var
mixer: TAudioMixerStream;
@@ -427,16 +439,18 @@ procedure TGenericPlaybackStream.Stop();
var
mixer: TAudioMixerStream;
begin
+ if (status = ssStopped) then
+ Exit;
+
status := ssStopped;
mixer := Engine.GetMixer();
if (mixer <> nil) then
mixer.RemoveStream(Self);
-end;
-function TGenericPlaybackStream.IsLoaded(): boolean;
-begin
- result := assigned(DecodeStream);
+ // rewind (note: DecodeStream might be closed already, but this is not a problem)
+ if assigned(DecodeStream) then
+ DecodeStream.Position := 0;
end;
function TGenericPlaybackStream.GetLoop(): boolean;
@@ -480,6 +494,7 @@ var
remFrameBytes: integer;
copyCnt: integer;
BytesNeeded: integer;
+ i: integer;
begin
Result := -1;
@@ -543,7 +558,9 @@ begin
// end-of-file reached -> stop playback
if (DecodeStream.EOF) then
+ begin
Stop();
+ end;
// resample decoded data
cvt.buf := PUint8(SampleBuffer);
@@ -552,6 +569,15 @@ begin
Exit;
SampleBufferCount := cvt.len_cvt;
+
+ // apply effects
+ for i := 0 to SoundEffects.Count-1 do
+ begin
+ if (SoundEffects[i] <> nil) then
+ begin
+ TSoundEffect(SoundEffects[i]).Callback(SampleBuffer, SampleBufferCount);
+ end;
+ end;
finally
Unlock();
end;
@@ -664,11 +690,28 @@ begin
// resize data to a 0..1 range
for i := 0 to High(TFFTData) do
begin
- // TODO: this might need some work
data[i] := Sqrt(data[i]) / 100;
end;
end;
+procedure TGenericPlaybackStream.AddSoundEffect(effect: TSoundEffect);
+begin
+ if (not assigned(effect)) then
+ Exit;
+ Lock();
+ // check if effect is already in list to avoid duplicates
+ if (SoundEffects.IndexOf(Pointer(effect)) = -1) then
+ SoundEffects.Add(Pointer(effect));
+ Unlock();
+end;
+
+procedure TGenericPlaybackStream.RemoveSoundEffect(effect: TSoundEffect);
+begin
+ Lock();
+ SoundEffects.Remove(effect);
+ Unlock();
+end;
+
function TGenericPlaybackStream.GetPosition: real;
begin
if assigned(DecodeStream) then
@@ -684,12 +727,37 @@ begin
end;
function TGenericPlaybackStream.GetVolume(): integer;
+var
+ FadeAmount: Single;
begin
- result := _volume;
+ Lock();
+ // adjust volume if fading is enabled
+ if (FadeInTime > 0) then
+ begin
+ FadeAmount := (SDL_GetTicks() - FadeInStartTime) / FadeInTime;
+ // check if fade-target is reached
+ if (FadeAmount >= 1) then
+ begin
+ // target reached -> stop fading
+ FadeInTime := 0;
+ _volume := FadeInTargetVolume;
+ end
+ else
+ begin
+ // fading in progress
+ _volume := Trunc(FadeAmount*FadeInTargetVolume + (1-FadeAmount)*FadeInStartVolume);
+ end;
+ end;
+ // return current volume
+ Result := _volume;
+ Unlock();
end;
procedure TGenericPlaybackStream.SetVolume(volume: integer);
begin
+ Lock();
+ // stop fading
+ FadeInTime := 0;
// clamp volume
if (volume > 100) then
_volume := 100
@@ -697,6 +765,7 @@ begin
_volume := 0
else
_volume := volume;
+ Unlock();
end;
@@ -719,15 +788,17 @@ begin
result := true;
end;
-destructor TAudioPlayback_SoftMixer.Destroy;
+function TAudioPlayback_SoftMixer.FinalizePlayback: boolean;
begin
+ Close;
StopAudioPlaybackEngine();
- FreeAndNil(MusicStream);
FreeAndNil(MixerStream);
FreeAndNil(FormatInfo);
- inherited Destroy();
+ FinalizeAudioPlaybackEngine();
+ inherited FinalizePlayback;
+ Result := true;
end;
procedure TAudioPlayback_SoftMixer.AudioCallback(buffer: PChar; size: integer);
@@ -745,13 +816,16 @@ begin
Result := FormatInfo;
end;
-function TAudioPlayback_SoftMixer.Load(const Filename: String): TGenericPlaybackStream;
+function TAudioPlayback_SoftMixer.OpenStream(const Filename: String): TAudioPlaybackStream;
var
decodeStream: TAudioDecodeStream;
playbackStream: TGenericPlaybackStream;
begin
Result := nil;
+ if (AudioDecoder = nil) then
+ Exit;
+
decodeStream := AudioDecoder.Open(Filename);
if not assigned(decodeStream) then
begin
@@ -760,110 +834,28 @@ begin
end;
playbackStream := TGenericPlaybackStream.Create(Self);
+ if (not assigned(playbackStream)) then
+ begin
+ FreeAndNil(decodeStream);
+ Exit;
+ end;
+
if (not playbackStream.SetDecodeStream(decodeStream)) then
+ begin
+ FreeAndNil(playbackStream);
+ FreeAndNil(decodeStream);
Exit;
+ end;
result := playbackStream;
end;
-procedure TAudioPlayback_SoftMixer.SetVolume(Volume: integer);
+procedure TAudioPlayback_SoftMixer.SetAppVolume(Volume: integer);
begin
// sets volume only for this application
MixerStream.Volume := Volume;
end;
-procedure TAudioPlayback_SoftMixer.SetMusicVolume(Volume: Integer);
-begin
- if assigned(MusicStream) then
- MusicStream.Volume := Volume;
-end;
-
-procedure TAudioPlayback_SoftMixer.SetLoop(Enabled: boolean);
-begin
- if assigned(MusicStream) then
- MusicStream.SetLoop(Enabled);
-end;
-
-function TAudioPlayback_SoftMixer.Open(const Filename: string): boolean;
-//var
-// decodeStream: TAudioDecodeStream;
-begin
- Result := false;
-
- // free old MusicStream
- MusicStream.Free();
- // and load new one
- MusicStream := Load(Filename);
- if not assigned(MusicStream) then
- Exit;
-
- //Set Max Volume
- SetMusicVolume(100);
-
- Result := true;
-end;
-
-procedure TAudioPlayback_SoftMixer.Rewind;
-begin
- SetPosition(0);
-end;
-
-procedure TAudioPlayback_SoftMixer.SetPosition(Time: real);
-begin
- if assigned(MusicStream) then
- MusicStream.SetPosition(Time);
-end;
-
-function TAudioPlayback_SoftMixer.GetPosition: real;
-begin
- if assigned(MusicStream) then
- Result := MusicStream.GetPosition()
- else
- Result := -1;
-end;
-
-function TAudioPlayback_SoftMixer.Length: real;
-begin
- if assigned(MusicStream) then
- Result := MusicStream.GetLength()
- else
- Result := -1;
-end;
-
-procedure TAudioPlayback_SoftMixer.Play;
-begin
- if assigned(MusicStream) then
- MusicStream.Play();
-end;
-
-procedure TAudioPlayback_SoftMixer.Pause;
-begin
- if assigned(MusicStream) then
- MusicStream.Pause();
-end;
-
-procedure TAudioPlayback_SoftMixer.Stop;
-begin
- if assigned(MusicStream) then
- MusicStream.Stop();
-end;
-
-procedure TAudioPlayback_SoftMixer.Close;
-begin
- if assigned(MusicStream) then
- begin
- MusicStream.Close();
- end;
-end;
-
-function TAudioPlayback_SoftMixer.Finished: boolean;
-begin
- if assigned(MusicStream) then
- Result := (MusicStream.GetStatus() = ssStopped)
- else
- Result := true;
-end;
-
procedure TAudioPlayback_SoftMixer.MixBuffers(dst, src: PChar; size: Cardinal; volume: Integer);
var
SampleIndex: Cardinal;
@@ -916,38 +908,4 @@ begin
end;
end;
-//Equalizer
-procedure TAudioPlayback_SoftMixer.GetFFTData(var data: TFFTData);
-begin
- if assigned(MusicStream) then
- MusicStream.GetFFTData(data);
-end;
-
-// Interface for Visualizer
-function TAudioPlayback_SoftMixer.GetPCMData(var data: TPCMData): Cardinal;
-begin
- if assigned(MusicStream) then
- Result := MusicStream.GetPCMData(data)
- else
- Result := 0;
-end;
-
-function TAudioPlayback_SoftMixer.OpenSound(const Filename: String): TAudioPlaybackStream;
-begin
- Result := Load(Filename);
-end;
-
-procedure TAudioPlayback_SoftMixer.PlaySound(stream: TAudioPlaybackStream);
-begin
- if assigned(stream) then
- stream.Play();
-end;
-
-procedure TAudioPlayback_SoftMixer.StopSound(stream: TAudioPlaybackStream);
-begin
- if assigned(stream) then
- stream.Stop();
-end;
-
-
end.
diff --git a/Game/Code/Classes/UIni.pas b/Game/Code/Classes/UIni.pas
index 292a485a..bed93a83 100644
--- a/Game/Code/Classes/UIni.pas
+++ b/Game/Code/Classes/UIni.pas
@@ -65,8 +65,8 @@ type
ClickAssist: integer;
BeatClick: integer;
SavePlayback: integer;
- Threshold: integer;
- SDLBufferSize: integer;
+ ThresholdIndex: integer;
+ AudioOutputBufferSizeIndex: integer;
//Song Preview
PreviewVolume: integer;
@@ -105,14 +105,11 @@ type
LPT: integer;
procedure Load();
- procedure LoadSoundSettings();
-
procedure Save();
procedure SaveNames;
procedure SaveLevel;
end;
-
var
Ini: TIni;
IResolution: array of string;
@@ -121,7 +118,9 @@ var
ISkin: array of string;
const
- IPlayers: array[0..4] of string = ('1', '2', '3', '4', '6');
+ IPlayers: array[0..4] of string = ('1', '2', '3', '4', '6');
+ IPlayersVals: array[0..4] of integer = ( 1 , 2 , 3 , 4 , 6 );
+
IDifficulty: array[0..2] of string = ('Easy', 'Medium', 'Hard');
ITabs: array[0..1] of string = ('Off', 'On');
@@ -151,16 +150,25 @@ const
ISpectrograph: array[0..1] of string = ('Off', 'On');
IMovieSize: array[0..2] of string = ('Half', 'Full [Vid]', 'Full [BG+Vid]');
- IMicBoost: array[0..3] of string = ('Off', '+6dB', '+12dB', '+18dB');
IClickAssist: array[0..1] of string = ('Off', 'On');
IBeatClick: array[0..1] of string = ('Off', 'On');
ISavePlayback: array[0..1] of string = ('Off', 'On');
+
IThreshold: array[0..3] of string = ('5%', '10%', '15%', '20%');
- ISDLBufferSize: array[0..8] of string = ('256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
-
+ IThresholdVals: array[0..3] of single = (0.05, 0.10, 0.15, 0.20);
+
+ IAudioOutputBufferSize: array[0..9] of string = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
+ IAudioOutputBufferSizeVals: array[0..9] of integer = ( 0, 256, 512 , 1024 , 2048 , 4096 , 8192 , 16384 , 32768 , 65536 );
+
+ IAudioInputBufferSize: array[0..9] of string = ('Auto', '256', '512', '1024', '2048', '4096', '8192', '16384', '32768', '65536');
+ IAudioInputBufferSizeVals: array[0..9] of integer = ( 0, 256, 512 , 1024 , 2048 , 4096 , 8192 , 16384 , 32768 , 65536 );
+
//Song Preview
- IPreviewVolume: array[0..10] of string = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%');
- IPreviewFading: array[0..5] of string = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs');
+ IPreviewVolume: array[0..10] of string = ('Off', '10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%');
+ IPreviewVolumeVals: array[0..10] of single = ( 0, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00 );
+
+ IPreviewFading: array[0..5] of string = ('Off', '1 Sec', '2 Secs', '3 Secs', '4 Secs', '5 Secs');
+ IPreviewFadingVals: array[0..5] of integer = ( 0, 1, 2, 3, 4, 5 );
ILyricsFont: array[0..2] of string = ('Plain', 'OLine1', 'OLine2');
@@ -182,7 +190,9 @@ const
IJoypad: array[0..1] of string = ('Off', 'On');
ILPT: array[0..2] of string = ('Off', 'LCD', 'Lights');
- IChannel: array[0..6] of string = ('Off', '1', '2', '3', '4', '5', '6');
+ // Recording options
+ IChannelPlayer: array[0..6] of string = ('Off', '1', '2', '3', '4', '5', '6');
+ IMicBoost: array[0..3] of string = ('Off', '+6dB', '+12dB', '+18dB');
implementation
@@ -232,12 +242,10 @@ procedure TIni.LoadInputDeviceCfg(IniFile: TMemIniFile);
var
deviceIndex: integer;
deviceCfg: PInputDeviceConfig;
- device: TAudioInputDevice;
deviceIniIndex: integer;
deviceIniStr: string;
channelCount: integer;
channelIndex: integer;
- newDevice: boolean;
recordKeys: TStringList;
i: integer;
begin
@@ -289,63 +297,10 @@ begin
recordKeys.Free();
- // Input devices - append detected soundcards
- for deviceIndex := 0 to High(AudioInputProcessor.Device) do
- begin
- newDevice := true;
- for deviceIniIndex := 0 to High(InputDeviceConfig) do
- begin //Search for Card in List
- deviceCfg := @InputDeviceConfig[deviceIniIndex];
- device := AudioInputProcessor.Device[deviceIndex];
-
- if (deviceCfg.Name = Trim(device.Description)) then
- begin
- newDevice := false;
-
- // store highest channel index as an offset for the new channels
- channelIndex := High(deviceCfg.ChannelToPlayerMap);
- // add missing channels or remove non-existing ones
- SetLength(deviceCfg.ChannelToPlayerMap, device.AudioFormat.Channels);
- // initialize added channels to 0
- for i := channelIndex+1 to High(deviceCfg.ChannelToPlayerMap) do
- begin
- deviceCfg.ChannelToPlayerMap[i] := 0;
- end;
-
- // associate ini-index with device
- device.CfgIndex := deviceIniIndex;
- break;
- end;
- end;
-
- //If not in List -> Add
- if newDevice then
- begin
- // resize list
- SetLength(InputDeviceConfig, Length(InputDeviceConfig)+1);
- deviceCfg := @InputDeviceConfig[High(InputDeviceConfig)];
- device := AudioInputProcessor.Device[deviceIndex];
-
- // associate ini-index with device
- device.CfgIndex := High(InputDeviceConfig);
-
- deviceCfg.Name := Trim(device.Description);
- deviceCfg.Input := 0;
-
- channelCount := device.AudioFormat.Channels;
- SetLength(deviceCfg.ChannelToPlayerMap, channelCount);
-
- for channelIndex := 0 to channelCount-1 do
- begin
- // set default at first start of USDX (1st device, 1st channel -> player1)
- if ((channelIndex = 0) and (device.CfgIndex = 0)) then
- deviceCfg.ChannelToPlayerMap[0] := 1
- else
- deviceCfg.ChannelToPlayerMap[channelIndex] := 0;
- end;
- end;
- end;
-
+ // MicBoost
+ //MicBoost := GetArrayIndex(IMicBoost, IniFile.ReadString('Record', 'MicBoost', 'Off'));
+ // Threshold
+ // ThresholdIndex := GetArrayIndex(IThreshold, IniFile.ReadString('Record', 'Threshold', IThreshold[1]));
end;
procedure TIni.SaveInputDeviceCfg(IniFile: TIniFile);
@@ -369,6 +324,11 @@ begin
IntToStr(InputDeviceConfig[deviceIndex].ChannelToPlayerMap[channelIndex]));
end;
end;
+
+ // MicBoost
+ //IniFile.WriteString('Record', 'MicBoost', IMicBoost[MicBoost]);
+ // Threshold
+ //IniFile.WriteString('Record', 'Threshold', IThreshold[ThresholdIndex]);
end;
procedure TIni.Load();
@@ -385,14 +345,14 @@ var
//Result := copy (S,0,StrRScan (PChar(S),char('.'))+1);
Result := copy (S,0,Pos ('.ini',S)-1);
end;
-
+
// get index of value V in array a, returns -1 if value is not in array
function GetArrayIndex(const A: array of String; V: String; caseInsensitiv: Boolean = False): Integer;
var
i: Integer;
begin
Result := -1;
-
+
for i := 0 To High(A) do
if (A[i] = V) or (caseInsensitiv and (UpperCase(A[i]) = UpperCase(V))) then
begin
@@ -400,7 +360,20 @@ var
break;
end;
end;
-
+
+ // get index of IniSeaction:IniProperty in array SearchArray or return the default value
+ function ReadArrayIndex(const SearchArray: array of String; IniSection: String; IniProperty: String; Default: Integer): Integer;
+ var
+ StrValue: string;
+ begin
+ StrValue := IniFile.ReadString('Sound', 'AudioOutputBufferSize', SearchArray[Default]);
+ Result := GetArrayIndex(SearchArray, StrValue);
+ if (Result = -1) then
+ begin
+ Result := Default;
+ end;
+ end;
+
// swap two strings
procedure swap(var s1, s2: String);
var
@@ -564,9 +537,6 @@ begin
// MovieSize
MovieSize := GetArrayIndex(IMovieSize, IniFile.ReadString('Graphics', 'MovieSize', IMovieSize[2]));
- // MicBoost
- MicBoost := GetArrayIndex(IMicBoost, IniFile.ReadString('Sound', 'MicBoost', 'Off'));
-
// ClickAssist
ClickAssist := GetArrayIndex(IClickAssist, IniFile.ReadString('Sound', 'ClickAssist', 'Off'));
@@ -575,17 +545,10 @@ begin
// SavePlayback
SavePlayback := GetArrayIndex(ISavePlayback, IniFile.ReadString('Sound', 'SavePlayback', ISavePlayback[0]));
-
- // Threshold
- Threshold := GetArrayIndex(IThreshold, IniFile.ReadString('Sound', 'Threshold', IThreshold[1]));
- // SDLBufferSize
- SDLBufferSize := GetArrayIndex(ISDLBufferSize, IniFile.ReadString('Sound', 'SDLBufferSize', '1024'));
- if (SDLBufferSize = -1) then
- SDLBufferSize := 1024
- else
- SDLBufferSize := StrToInt(ISDLBufferSize[SDLBufferSize]);
-
+ // AudioOutputBufferSize
+ AudioOutputBufferSizeIndex := ReadArrayIndex(IAudioOutputBufferSize, 'Sound', 'AudioOutputBufferSize', 0);
+
//Preview Volume
PreviewVolume := GetArrayIndex(IPreviewVolume, IniFile.ReadString('Sound', 'PreviewVolume', IPreviewVolume[7]));
@@ -638,15 +601,17 @@ begin
Theme := GetArrayIndex(ITheme, IniFile.ReadString('Themes', 'Theme', 'DELUXE'), True);
if (Theme = -1) then
Theme := 0;
-
+
// Skin
Skin.onThemeChange;
-
+
SkinNo := GetArrayIndex(ISkin, IniFile.ReadString('Themes', 'Skin', ISkin[0]));
// Color
Color := GetArrayIndex(IColor, IniFile.ReadString('Themes', 'Color', IColor[0]));
-
+
+ LoadInputDeviceCfg(IniFile);
+
// LoadAnimation
LoadAnimation := GetArrayIndex(ILoadAnimation, IniFile.ReadString('Advanced', 'LoadAnimation', 'On'));
@@ -740,20 +705,14 @@ begin
// Movie Size
IniFile.WriteString('Graphics', 'MovieSize', IMovieSize[MovieSize]);
- // MicBoost
- IniFile.WriteString('Sound', 'MicBoost', IMicBoost[MicBoost]);
-
// ClickAssist
IniFile.WriteString('Sound', 'ClickAssist', IClickAssist[ClickAssist]);
// BeatClick
IniFile.WriteString('Sound', 'BeatClick', IBeatClick[BeatClick]);
- // Threshold
- IniFile.WriteString('Sound', 'Threshold', IThreshold[Threshold]);
-
- // SDLBufferSize
- IniFile.WriteString('Sound', 'SDLBufferSize', IntToStr(SDLBufferSize));
+ // AudioOutputBufferSize
+ IniFile.WriteString('Sound', 'AudioOutputBufferSize', IAudioOutputBufferSize[AudioOutputBufferSizeIndex]);
// Song Preview
IniFile.WriteString('Sound', 'PreviewVolume', IPreviewVolume[PreviewVolume]);
@@ -813,17 +772,8 @@ begin
IniFile.WriteString('Controller', 'Joypad', IJoypad[Joypad]);
IniFile.Free;
-
- end;
-end;
-procedure TIni.LoadSoundSettings;
-var
- IniFile: TMemIniFile;
-begin
- IniFile := TMemIniFile.Create(Filename);
- LoadInputDeviceCfg(IniFile);
- IniFile.Free;
+ end;
end;
procedure TIni.SaveNames;
diff --git a/Game/Code/Classes/UMain.pas b/Game/Code/Classes/UMain.pas
index 39265a6d..9c867a3c 100644
--- a/Game/Code/Classes/UMain.pas
+++ b/Game/Code/Classes/UMain.pas
@@ -223,13 +223,6 @@ begin
Log.BenchmarkEnd(1);
Log.LogBenchmark('Initializing Sound', 1);
- // Load Sound Settings from Ini
- Log.BenchmarkStart(1);
- Log.LogStatus('Load Sound Settings', 'Initialization');
- Ini.LoadSoundSettings;
- Log.BenchmarkEnd(1);
- Log.LogBenchmark('Load Sound Settings', 1);
-
// Theme
Log.BenchmarkStart(1);
Log.LogStatus('Load Themes', 'Initialization');
@@ -352,7 +345,9 @@ begin
// call an uninitialize routine for every initialize step
// or at least use the corresponding Free-Methods
- //TTF_quit();
+ FinalizeSound();
+
+ TTF_Quit();
SDL_Quit();
(*
diff --git a/Game/Code/Classes/UMedia_dummy.pas b/Game/Code/Classes/UMedia_dummy.pas
index dcdcd710..ad3aa94e 100644
--- a/Game/Code/Classes/UMedia_dummy.pas
+++ b/Game/Code/Classes/UMedia_dummy.pas
@@ -30,8 +30,9 @@ var
singleton_dummy : IVideoPlayback;
type
- Tmedia_dummy = class( TInterfacedObject, IVideoPlayback, IVideoVisualization, IAudioPlayback, IAudioInput )
+ TMedia_dummy = class( TInterfacedObject, IVideoPlayback, IVideoVisualization, IAudioPlayback, IAudioInput )
private
+ DummyOutputDeviceList: TAudioOutputDeviceList;
public
constructor create();
function GetName: String;
@@ -53,6 +54,7 @@ type
// IAudioInput
function InitializeRecord: boolean;
+ function FinalizeRecord: boolean;
procedure CaptureStart;
procedure CaptureStop;
procedure GetFFTData(var data: TFFTData);
@@ -60,8 +62,12 @@ type
// IAudioPlayback
function InitializePlayback: boolean;
+ function FinalizePlayback: boolean;
+
+ function GetOutputDeviceList(): TAudioOutputDeviceList;
+ procedure FadeIn(Time: real; TargetVolume: integer);
+ procedure SetAppVolume(Volume: integer);
procedure SetVolume(Volume: integer);
- procedure SetMusicVolume(Volume: integer);
procedure SetLoop(Enabled: boolean);
procedure Rewind;
@@ -124,7 +130,7 @@ procedure Tmedia_dummy.SetPosition(Time: real);
begin
end;
-function Tmedia_dummy.getPosition: real;
+function Tmedia_dummy.GetPosition: real;
begin
result := 0;
end;
@@ -135,6 +141,11 @@ begin
result := true;
end;
+function Tmedia_dummy.FinalizeRecord: boolean;
+begin
+ result := true;
+end;
+
procedure Tmedia_dummy.CaptureStart;
begin
end;
@@ -155,14 +166,27 @@ end;
// IAudioPlayback
function Tmedia_dummy.InitializePlayback: boolean;
begin
+ SetLength(DummyOutputDeviceList, 1);
+ DummyOutputDeviceList[0] := TAudioOutputDevice.Create();
+ DummyOutputDeviceList[0].Name := '[Dummy Device]';
result := true;
end;
-procedure Tmedia_dummy.SetVolume(Volume: integer);
+function Tmedia_dummy.FinalizePlayback: boolean;
+begin
+ result := true;
+end;
+
+function Tmedia_dummy.GetOutputDeviceList(): TAudioOutputDeviceList;
+begin
+ Result := DummyOutputDeviceList;
+end;
+
+procedure Tmedia_dummy.SetAppVolume(Volume: integer);
begin
end;
-procedure Tmedia_dummy.SetMusicVolume(Volume: integer);
+procedure Tmedia_dummy.SetVolume(Volume: integer);
begin
end;
@@ -170,6 +194,10 @@ procedure Tmedia_dummy.SetLoop(Enabled: boolean);
begin
end;
+procedure Tmedia_dummy.FadeIn(Time: real; TargetVolume: integer);
+begin
+end;
+
procedure Tmedia_dummy.Rewind;
begin
end;
diff --git a/Game/Code/Classes/UMusic.pas b/Game/Code/Classes/UMusic.pas
index 4e0291cf..9977661f 100644
--- a/Game/Code/Classes/UMusic.pas
+++ b/Game/Code/Classes/UMusic.pas
@@ -80,6 +80,7 @@ type
TFFTData = array[0..(FFTSize div 2)-1] of Single;
type
+ PPCMStereoSample = ^TPCMStereoSample;
TPCMStereoSample = array[0..1] of SmallInt;
TPCMData = array[0..511] of TPCMStereoSample;
@@ -124,6 +125,18 @@ type
end;
type
+ TSoundEffect = class
+ public
+ EngineData: Pointer; // can be used for engine-specific data
+ procedure Callback(Buffer: PChar; BufSize: integer); virtual; abstract;
+ end;
+
+ TVoiceRemoval = class(TSoundEffect)
+ public
+ procedure Callback(Buffer: PChar; BufSize: integer); override;
+ end;
+
+type
TAudioProcessingStream = class
public
procedure Close(); virtual; abstract;
@@ -131,40 +144,40 @@ type
TAudioPlaybackStream = class(TAudioProcessingStream)
protected
- function GetLoop(): boolean; virtual; abstract;
- procedure SetLoop(Enabled: boolean); virtual; abstract;
+ function GetPosition: real; virtual; abstract;
+ procedure SetPosition(Time: real); virtual; abstract;
function GetLength(): real; virtual; abstract;
function GetStatus(): TStreamStatus; virtual; abstract;
function GetVolume(): integer; virtual; abstract;
- procedure SetVolume(volume: integer); virtual; abstract;
+ procedure SetVolume(Volume: integer); virtual; abstract;
+ function GetLoop(): boolean; virtual; abstract;
+ procedure SetLoop(Enabled: boolean); virtual; abstract;
public
procedure Play(); virtual; abstract;
procedure Pause(); virtual; abstract;
procedure Stop(); virtual; abstract;
+ procedure FadeIn(Time: real; TargetVolume: integer); virtual; abstract;
+
+ procedure GetFFTData(var data: TFFTData); virtual; abstract;
+ function GetPCMData(var data: TPCMData): Cardinal; virtual; abstract;
+
+ procedure AddSoundEffect(effect: TSoundEffect); virtual; abstract;
+ procedure RemoveSoundEffect(effect: TSoundEffect); virtual; abstract;
- property Loop: boolean READ GetLoop WRITE SetLoop;
property Length: real READ GetLength;
+ property Position: real READ GetPosition WRITE SetPosition;
property Status: TStreamStatus READ GetStatus;
property Volume: integer READ GetVolume WRITE SetVolume;
+ property Loop: boolean READ GetLoop WRITE SetLoop;
end;
- (*
- TAudioMixerStream = class(TAudioProcessingStream)
- procedure AddStream(stream: TAudioProcessingStream);
- procedure RemoveStream(stream: TAudioProcessingStream);
- procedure SetMasterVolume(volume: cardinal);
- function GetMasterVolume(): cardinal;
- procedure SetStreamVolume(stream: TAudioProcessingStream; volume: cardinal);
- function GetStreamVolume(stream: TAudioProcessingStream): cardinal;
- end;
- *)
-
TAudioDecodeStream = class(TAudioProcessingStream)
protected
function GetLength(): real; virtual; abstract;
function GetPosition(): real; virtual; abstract;
procedure SetPosition(Time: real); virtual; abstract;
function IsEOF(): boolean; virtual; abstract;
+ function IsError(): boolean; virtual; abstract;
public
function ReadData(Buffer: PChar; BufSize: integer): integer; virtual; abstract;
function GetAudioFormatInfo(): TAudioFormatInfo; virtual; abstract;
@@ -175,6 +188,19 @@ type
end;
type
+ TAudioVoiceStream = class(TAudioProcessingStream)
+ public
+ function ReadData(Buffer: PChar; BufSize: integer): integer; virtual; abstract;
+ function GetAudioFormatInfo(): TAudioFormatInfo; virtual; abstract;
+ end;
+ // soundcard output-devices information
+ TAudioOutputDevice = class
+ public
+ Name: string; // soundcard name
+ end;
+ TAudioOutputDeviceList = array of TAudioOutputDevice;
+
+type
IGenericPlayback = Interface
['{63A5EBC3-3F4D-4F23-8DFB-B5165FCE33DD}']
function GetName: String;
@@ -208,9 +234,13 @@ type
IAudioPlayback = Interface( IGenericPlayback )
['{E4AE0B40-3C21-4DC5-847C-20A87E0DFB96}']
function InitializePlayback: boolean;
+ function FinalizePlayback: boolean;
+
+ function GetOutputDeviceList(): TAudioOutputDeviceList;
+ procedure SetAppVolume(Volume: integer);
procedure SetVolume(Volume: integer);
- procedure SetMusicVolume(Volume: integer);
procedure SetLoop(Enabled: boolean);
+ procedure FadeIn(Time: real; TargetVolume: integer);
procedure Rewind;
function Finished: boolean;
@@ -232,6 +262,7 @@ type
['{557B0E9A-604D-47E4-B826-13769F3E10B7}']
function GetName(): String;
function InitializeDecoder(): boolean;
+ function FinalizeDecoder(): boolean;
//function IsSupported(const Filename: string): boolean;
end;
@@ -251,6 +282,7 @@ type
['{A5C8DA92-2A0C-4AB2-849B-2F7448C6003A}']
function GetName: String;
function InitializeRecord: boolean;
+ function FinalizeRecord(): boolean;
procedure CaptureStart;
procedure CaptureStop;
@@ -288,6 +320,7 @@ var // TODO : JB --- THESE SHOULD NOT BE GLOBAL
procedure InitializeSound;
+procedure FinalizeSound;
function Visualization(): IVideoPlayback;
function VideoPlayback(): IVideoPlayback;
@@ -360,14 +393,32 @@ begin
result := singleton_AudioDecoder;
end;
-procedure AssignSingletonObjects();
+procedure FilterInterfaceList(const IID: TGUID; InList, OutList: TInterfaceList);
+var
+ i: integer;
+ obj: IInterface;
+begin
+ if (not assigned(OutList)) then
+ Exit;
+
+ OutList.Clear;
+ for i := 0 to InList.Count-1 do
+ begin
+ if assigned(InList[i]) then
+ begin
+ // add object to list if it implements the interface searched for
+ if (InList[i].QueryInterface(IID, obj) = 0) then
+ OutList.Add(obj);
+ end;
+ end;
+end;
+
+procedure AssignSingletonObjects();
var
lTmpInterface : IInterface;
iCount : Integer;
begin
lTmpInterface := nil;
-
-
for iCount := 0 to AudioManager.Count - 1 do
begin
@@ -410,7 +461,6 @@ begin
end;
end;
-
end;
procedure InitializeSound;
@@ -460,6 +510,8 @@ begin
end;
end;
+ // Update input-device list with registered devices
+ AudioInputProcessor.UpdateInputDeviceConfig();
// Load in-game sounds
SoundLib := TSoundLibrary.Create;
@@ -480,6 +532,50 @@ begin
end;
end;
+procedure FinalizeSound;
+var
+ i: integer;
+ AudioIntfList: TInterfaceList;
+begin
+ // stop, close and free sounds
+ SoundLib.Free;
+
+ // stop and close music stream
+ if (AudioPlayback <> nil) then
+ AudioPlayback.Close;
+
+ // stop any active captures
+ if (AudioInput <> nil) then
+ AudioInput.CaptureStop;
+
+ singleton_AudioPlayback := nil;
+ singleton_AudioDecoder := nil;
+ singleton_AudioInput := nil;
+
+ // create temporary interface list
+ AudioIntfList := TInterfaceList.Create();
+
+ // finalize audio playback interfaces (should be done before the decoders)
+ FilterInterfaceList(IAudioPlayback, AudioManager, AudioIntfList);
+ for i := 0 to AudioIntfList.Count-1 do
+ IAudioPlayback(AudioIntfList[i]).FinalizePlayback();
+
+ // finalize audio input interfaces
+ FilterInterfaceList(IAudioInput, AudioManager, AudioIntfList);
+ for i := 0 to AudioIntfList.Count-1 do
+ IAudioInput(AudioIntfList[i]).FinalizeRecord();
+
+ // finalize audio decoder interfaces
+ FilterInterfaceList(IAudioDecoder, AudioManager, AudioIntfList);
+ for i := 0 to AudioIntfList.Count-1 do
+ IAudioDecoder(AudioIntfList[i]).FinalizeDecoder();
+
+ AudioIntfList.Free;
+
+ // free audio interfaces
+ while (AudioManager.Count > 0) do
+ AudioManager.Delete(0);
+end;
{ TSoundLibrary }
@@ -535,6 +631,32 @@ begin
//Shuffle.Free;
end;
+procedure TVoiceRemoval.Callback(Buffer: PChar; BufSize: integer);
+var
+ FrameIndex, FrameSize: integer;
+ Value: integer;
+ Sample: PPCMStereoSample;
+begin
+ FrameSize := 2 * SizeOf(SmallInt);
+ for FrameIndex := 0 to (BufSize div FrameSize)-1 do
+ begin
+ Sample := PPCMStereoSample(Buffer);
+ // channel difference
+ Value := Sample[0] - Sample[1];
+ // clip
+ if (Value > High(SmallInt)) then
+ Value := High(SmallInt)
+ else if (Value < Low(SmallInt)) then
+ Value := Low(SmallInt);
+ // assign result
+ Sample[0] := Value;
+ Sample[1] := Value;
+ // increase to next frame
+ Inc(Buffer, FrameSize);
+ end;
+end;
+
+
initialization
begin
singleton_AudioManager := TInterfaceList.Create();
diff --git a/Game/Code/Classes/URecord.pas b/Game/Code/Classes/URecord.pas
index 6faac2b6..55dedd1f 100644
--- a/Game/Code/Classes/URecord.pas
+++ b/Game/Code/Classes/URecord.pas
@@ -55,7 +55,11 @@ type
property ToneString: string READ GetToneString;
end;
- TAudioInputDeviceSource = record
+const
+ DEFAULT_SOURCE_NAME = '[Default]';
+
+type
+ TAudioInputSource = record
Name: string;
end;
@@ -63,10 +67,10 @@ type
TAudioInputDevice = class
public
CfgIndex: integer; // index of this device in Ini.InputDeviceConfig
- Description: string; // soundcard name/description
- Source: array of TAudioInputDeviceSource; // soundcard input(-source)s
- SourceSelected: integer; // unused. What is this good for?
- MicSource: integer; // unused. What is this good for?
+ Name: string; // soundcard name
+ Source: array of TAudioInputSource; // soundcard input-sources
+ SourceRestore: integer; // source-index that will be selected after capturing (-1: not detected)
+ MicSource: integer; // source-index of mic (-1: none detected)
AudioFormat: TAudioFormatInfo; // capture format info (e.g. 44.1kHz SInt16 stereo)
CaptureChannel: array of TCaptureBuffer; // sound-buffer references used for mono or stereo channel's capture data
@@ -74,18 +78,26 @@ type
destructor Destroy; override;
procedure LinkCaptureBuffer(ChannelIndex: integer; Sound: TCaptureBuffer);
-
+
+ // TODO: add Open/Close functions so Start/Stop becomes faster
+ //function Open(): boolean; virtual; abstract;
+ //function Close(): boolean; virtual; abstract;
function Start(): boolean; virtual; abstract;
- procedure Stop(); virtual; abstract;
+ function Stop(): boolean; virtual; abstract;
+
+ function GetVolume(): integer; virtual; abstract;
+ procedure SetVolume(Volume: integer); virtual; abstract;
end;
TAudioInputProcessor = class
public
Sound: array of TCaptureBuffer; // sound-buffers for every player
- Device: array of TAudioInputDevice;
+ DeviceList: array of TAudioInputDevice;
constructor Create;
+ procedure UpdateInputDeviceConfig;
+
// handle microphone input
procedure HandleMicrophoneData(Buffer: Pointer; Size: Cardinal;
InputDevice: TAudioInputDevice);
@@ -96,10 +108,10 @@ type
Started: boolean;
protected
function UnifyDeviceName(const name: string; deviceIndex: integer): string;
- function UnifyDeviceSourceName(const name: string; const deviceName: string): string;
public
function GetName: String; virtual; abstract;
function InitializeRecord: boolean; virtual; abstract;
+ function FinalizeRecord: boolean; virtual;
procedure CaptureStart;
procedure CaptureStop;
@@ -140,8 +152,6 @@ end;
{ TAudioInputDevice }
destructor TAudioInputDevice.Destroy;
-//var
-// i: integer; // Auto Removed, Unused Variable
begin
Stop();
Source := nil;
@@ -214,6 +224,7 @@ begin
end;
// move old samples to the beginning of the array (if necessary)
+ // TODO: should be a ring-buffer instead
for SampleIndex := NumSamples to High(BufferArray) do
BufferArray[SampleIndex-NumSamples] := BufferArray[SampleIndex];
@@ -225,6 +236,10 @@ begin
// save capture-data to BufferLong if neccessary
if (Ini.SavePlayback = 1) then
begin
+ // this is just for debugging (approx 15MB per player for a 3min song!!!)
+ // For an in-game replay-mode we need to compress data so we do not
+ // waste that much memory. Maybe ogg-vorbis with voice-preset in fast-mode?
+ // Or we could use a faster but not that efficient lossless compression.
BufferNew.Seek(0, soBeginning);
BufferLong.CopyFrom(BufferNew, BufferNew.Size);
end;
@@ -250,14 +265,8 @@ begin
MaxVolume := Volume;
end;
- case Ini.Threshold of
- 0: Threshold := 0.05;
- 1: Threshold := 0.1;
- 2: Threshold := 0.15;
- 3: Threshold := 0.2;
- else Threshold := 0.1;
- end;
-
+ Threshold := IThresholdVals[Ini.ThresholdIndex];
+
// check if signal has an acceptable volume (ignore background-noise)
if MaxVolume >= Threshold then
begin
@@ -279,6 +288,7 @@ const
begin
// prepare to analyze
MaxWeight := -1;
+ MaxTone := 0; // this is not needed, but it satifies the compiler
// analyze halftones
// Note: at the lowest tone (~65Hz) and a buffer-size of 4096
@@ -376,6 +386,78 @@ begin
end;
end;
+// updates InputDeviceConfig with current input-device information
+// See: TIni.LoadInputDeviceCfg()
+procedure TAudioInputProcessor.UpdateInputDeviceConfig;
+var
+ deviceIndex: integer;
+ newDevice: boolean;
+ deviceIniIndex: integer;
+ deviceCfg: PInputDeviceConfig;
+ device: TAudioInputDevice;
+ channelCount: integer;
+ channelIndex: integer;
+ i: integer;
+begin
+ // Input devices - append detected soundcards
+ for deviceIndex := 0 to High(DeviceList) do
+ begin
+ newDevice := true;
+ //Search for Card in List
+ for deviceIniIndex := 0 to High(Ini.InputDeviceConfig) do
+ begin
+ deviceCfg := @Ini.InputDeviceConfig[deviceIniIndex];
+ device := DeviceList[deviceIndex];
+
+ if (deviceCfg.Name = Trim(device.Name)) then
+ begin
+ newDevice := false;
+
+ // store highest channel index as an offset for the new channels
+ channelIndex := High(deviceCfg.ChannelToPlayerMap);
+ // add missing channels or remove non-existing ones
+ SetLength(deviceCfg.ChannelToPlayerMap, device.AudioFormat.Channels);
+ // initialize added channels to 0
+ for i := channelIndex+1 to High(deviceCfg.ChannelToPlayerMap) do
+ begin
+ deviceCfg.ChannelToPlayerMap[i] := 0;
+ end;
+
+ // associate ini-index with device
+ device.CfgIndex := deviceIniIndex;
+ break;
+ end;
+ end;
+
+ //If not in List -> Add
+ if newDevice then
+ begin
+ // resize list
+ SetLength(Ini.InputDeviceConfig, Length(Ini.InputDeviceConfig)+1);
+ deviceCfg := @Ini.InputDeviceConfig[High(Ini.InputDeviceConfig)];
+ device := DeviceList[deviceIndex];
+
+ // associate ini-index with device
+ device.CfgIndex := High(Ini.InputDeviceConfig);
+
+ deviceCfg.Name := Trim(device.Name);
+ deviceCfg.Input := 0;
+
+ channelCount := device.AudioFormat.Channels;
+ SetLength(deviceCfg.ChannelToPlayerMap, channelCount);
+
+ for channelIndex := 0 to channelCount-1 do
+ begin
+ // set default at first start of USDX (1st device, 1st channel -> player1)
+ if ((channelIndex = 0) and (device.CfgIndex = 0)) then
+ deviceCfg.ChannelToPlayerMap[0] := 1
+ else
+ deviceCfg.ChannelToPlayerMap[channelIndex] := 0;
+ end;
+ end;
+ end;
+end;
+
{*
* Handle captured microphone input data.
* Params:
@@ -391,9 +473,7 @@ var
ChannelBuffer: PChar; // buffer handled as array of bytes (offset relative to channel)
SampleBuffer: PSmallIntArray; // buffer handled as array of samples
Boost: byte;
-// ChannelCount: integer; // Auto Removed, Unused Variable
ChannelIndex: integer;
-// ChannelOffset: integer; // Auto Removed, Unused Variable
CaptureChannel: TCaptureBuffer;
AudioFormat: TAudioFormatInfo;
FrameSize: integer;
@@ -418,7 +498,7 @@ begin
// results in the analysis phase otherwise)
if (AudioFormat.Format <> asfS16) then
begin
- // this only occurs if a developer choosed a wrong input sample-format
+ // this only occurs if a developer choosed an unsupported input sample-format
Log.CriticalError('TAudioInputProcessor.HandleMicrophoneData: Wrong sample-format');
Exit;
end;
@@ -472,6 +552,15 @@ end;
{ TAudioInputBase }
+function TAudioInputBase.FinalizeRecord: boolean;
+var
+ i: integer;
+begin
+ for i := 0 to High(AudioInputProcessor.DeviceList) do
+ AudioInputProcessor.DeviceList[i].Free();
+ AudioInputProcessor.DeviceList := nil;
+end;
+
{*
* Start capturing on all used input-device.
*}
@@ -493,9 +582,9 @@ begin
AudioInputProcessor.Sound[S].Clear;
// start capturing on each used device
- for DeviceIndex := 0 to High(AudioInputProcessor.Device) do
+ for DeviceIndex := 0 to High(AudioInputProcessor.DeviceList) do
begin
- Device := AudioInputProcessor.Device[DeviceIndex];
+ Device := AudioInputProcessor.DeviceList[DeviceIndex];
if not assigned(Device) then
continue;
DeviceCfg := @Ini.InputDeviceConfig[Device.CfgIndex];
@@ -536,13 +625,11 @@ end;
procedure TAudioInputBase.CaptureStop;
var
DeviceIndex: integer;
-// Player: integer; // Auto Removed, Unused Variable
Device: TAudioInputDevice;
-// DeviceCfg: PInputDeviceConfig; // Auto Removed, Unused Variable
begin
- for DeviceIndex := 0 to High(AudioInputProcessor.Device) do
+ for DeviceIndex := 0 to High(AudioInputProcessor.DeviceList) do
begin
- Device := AudioInputProcessor.Device[DeviceIndex];
+ Device := AudioInputProcessor.DeviceList[DeviceIndex];
if not assigned(Device) then
continue;
Device.Stop();
@@ -563,7 +650,7 @@ var
// search devices with same description
For i := 0 to deviceIndex-1 do
begin
- if (AudioInputProcessor.Device[i].Description = name) then
+ if (AudioInputProcessor.DeviceList[i].Name = name) then
begin
Result := True;
Break;
@@ -583,28 +670,6 @@ begin
end;
end;
-{*
- * Unifies an input-device's source name.
- * Note: the description member of the device must already be set when
- * calling this function.
- *}
-function TAudioInputBase.UnifyDeviceSourceName(const name: string; const deviceName: string): string;
-//var
-// Descr: string; // Auto Removed, Unused Variable
-begin
- result := name;
-
- {$IFDEF DARWIN}
- // Under MacOSX the SingStar Mics have an empty
- // InputName. So, we have to add a hard coded
- // Workaround for this problem
- if (name = '') and (Pos( 'USBMIC Serial#', deviceName) > 0) then
- begin
- result := 'Microphone';
- end;
- {$ENDIF}
-end;
-
end.