aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes/UAudioPlayback_Bass.pas
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-07-02 07:50:39 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-07-02 07:50:39 +0000
commitfaf4c13bf41a17ce920a2194fc396f8bf7b44331 (patch)
tree0049a0dacad82a08b934167660bfabd6c8ea47a8 /Game/Code/Classes/UAudioPlayback_Bass.pas
parent67d0be6741c5466c786d8d389e34c83e1be7e3c0 (diff)
downloadusdx-faf4c13bf41a17ce920a2194fc396f8bf7b44331.tar.gz
usdx-faf4c13bf41a17ce920a2194fc396f8bf7b44331.tar.xz
usdx-faf4c13bf41a17ce920a2194fc396f8bf7b44331.zip
Audio/Video engine update:
- lyrics<->audio synchronisation (TSyncSource) - better resampling (optional support for libsamplerate) - cleaner termination of audio/video streams/devices - improved decoders and decoder infrastructure - many other improvements/cleanups Currently just for testing (not enabled by default): - Background music - Voice-Passthrough (hear what you sing) - Video VSync git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1157 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to 'Game/Code/Classes/UAudioPlayback_Bass.pas')
-rw-r--r--Game/Code/Classes/UAudioPlayback_Bass.pas686
1 files changed, 412 insertions, 274 deletions
diff --git a/Game/Code/Classes/UAudioPlayback_Bass.pas b/Game/Code/Classes/UAudioPlayback_Bass.pas
index 53fbd921..41a91173 100644
--- a/Game/Code/Classes/UAudioPlayback_Bass.pas
+++ b/Game/Code/Classes/UAudioPlayback_Bass.pas
@@ -13,64 +13,78 @@ implementation
uses
Classes,
SysUtils,
+ Math,
UIni,
UMain,
UMusic,
UAudioPlaybackBase,
UAudioCore_Bass,
ULog,
+ sdl,
bass;
type
PHDSP = ^HDSP;
type
- // Playback-stream decoded internally by BASS
TBassPlaybackStream = class(TAudioPlaybackStream)
private
Handle: HSTREAM;
+ NeedsRewind: boolean;
+ PausedSeek: boolean; // true if a seek was performed in pause state
+
+ procedure Reset();
+ function IsEOF(): boolean;
+ protected
+ function GetLatency(): double; override;
+ function GetLoop(): boolean; override;
+ procedure SetLoop(Enabled: boolean); override;
+ function GetLength(): real; override;
+ function GetStatus(): TStreamStatus; override;
+ function GetVolume(): single; override;
+ procedure SetVolume(Volume: single); override;
+ function GetPosition: real; override;
+ procedure SetPosition(Time: real); override;
public
- constructor Create(stream: HSTREAM);
+ constructor Create();
destructor Destroy(); override;
- procedure Reset();
+ function Open(SourceStream: TAudioSourceStream): boolean; override;
+ procedure Close(); override;
procedure Play(); override;
procedure Pause(); override;
procedure Stop(); override;
procedure FadeIn(Time: real; TargetVolume: single); override;
- procedure Close(); override;
+ procedure AddSoundEffect(Effect: TSoundEffect); override;
+ procedure RemoveSoundEffect(Effect: TSoundEffect); override;
- function GetLoop(): boolean; override;
- procedure SetLoop(Enabled: boolean); override;
- function GetLength(): real; override;
- function GetStatus(): TStreamStatus; override;
- function GetVolume(): single; override;
- procedure SetVolume(volume: single); override;
+ procedure GetFFTData(var Data: TFFTData); override;
+ function GetPCMData(var Data: TPCMData): Cardinal; override;
- procedure AddSoundEffect(effect: TSoundEffect); override;
- procedure RemoveSoundEffect(effect: TSoundEffect); override;
+ function GetAudioFormatInfo(): TAudioFormatInfo; override;
- function GetPosition: real; override;
- procedure SetPosition(Time: real); override;
+ function ReadData(Buffer: PChar; BufferSize: integer): integer;
- procedure GetFFTData(var data: TFFTData); override;
- function GetPCMData(var data: TPCMData): Cardinal; override;
+ property EOF: boolean READ IsEOF;
end;
- // Playback-stream decoded by an external decoder e.g. FFmpeg
- TBassExtDecoderPlaybackStream = class(TBassPlaybackStream)
+const
+ MAX_VOICE_DELAY = 0.020; // 20ms
+
+type
+ TBassVoiceStream = class(TAudioVoiceStream)
private
- DecodeStream: TAudioDecodeStream;
+ Handle: HSTREAM;
public
- procedure Stop(); override;
- procedure Close(); override;
- function GetLength(): real; override;
- function GetPosition: real; override;
- procedure SetPosition(Time: real); override;
+ function Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean; override;
+ procedure Close(); override;
- function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
+ procedure WriteData(Buffer: PChar; BufferSize: integer); override;
+ function ReadData(Buffer: PChar; BufferSize: integer): integer; override;
+ function IsEOF(): boolean; override;
+ function IsError(): boolean; override;
end;
type
@@ -78,12 +92,14 @@ type
private
function EnumDevices(): boolean;
protected
- function OpenStream(const Filename: string): TAudioPlaybackStream; override;
+ function GetLatency(): double; override;
+ function CreatePlaybackStream(): TAudioPlaybackStream; override;
public
function GetName: String; override;
function InitializePlayback(): boolean; override;
function FinalizePlayback: boolean; override;
procedure SetAppVolume(Volume: single); override;
+ function CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream; override;
end;
TBassOutputDevice = class(TAudioOutputDevice)
@@ -92,17 +108,107 @@ type
end;
var
- AudioCore: TAudioCore_Bass;
- singleton_AudioPlaybackBass : IAudioPlayback;
+ BassCore: TAudioCore_Bass;
{ TBassPlaybackStream }
-constructor TBassPlaybackStream.Create(stream: HSTREAM);
+function PlaybackStreamHandler(handle: HSTREAM; buffer: Pointer; length: DWORD; user: Pointer): DWORD;
+{$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF}
+var
+ PlaybackStream: TBassPlaybackStream;
+ BytesRead: integer;
+begin
+ PlaybackStream := TBassPlaybackStream(user);
+ if (not assigned (PlaybackStream)) then
+ begin
+ Result := BASS_STREAMPROC_END;
+ Exit;
+ end;
+
+ BytesRead := PlaybackStream.ReadData(buffer, length);
+ // check for errors
+ if (BytesRead < 0) then
+ Result := BASS_STREAMPROC_END
+ // check for EOF
+ else if (PlaybackStream.EOF) then
+ Result := BytesRead or BASS_STREAMPROC_END
+ // no error/EOF
+ else
+ Result := BytesRead;
+end;
+
+function TBassPlaybackStream.ReadData(Buffer: PChar; BufferSize: integer): integer;
+var
+ AdjustedSize: integer;
+ RequestedSourceSize, SourceSize: integer;
+ SkipCount: integer;
+ SourceFormatInfo: TAudioFormatInfo;
+ FrameSize: integer;
+ PadFrame: PChar;
+ //Info: BASS_INFO;
+ //Latency: double;
+begin
+ Result := -1;
+
+ if (not assigned(SourceStream)) then
+ Exit;
+
+ // sanity check
+ if (BufferSize = 0) then
+ begin
+ Result := 0;
+ Exit;
+ end;
+
+ SourceFormatInfo := SourceStream.GetAudioFormatInfo();
+ FrameSize := SourceFormatInfo.FrameSize;
+
+ // check how much data to fetch to be in synch
+ AdjustedSize := Synchronize(BufferSize, SourceFormatInfo);
+
+ // skip data if we are too far behind
+ SkipCount := AdjustedSize - BufferSize;
+ while (SkipCount > 0) do
+ begin
+ RequestedSourceSize := Min(SkipCount, BufferSize);
+ SourceSize := SourceStream.ReadData(Buffer, RequestedSourceSize);
+ // if an error or EOF occured stop skipping and handle error/EOF with the next ReadData()
+ if (SourceSize <= 0) then
+ break;
+ Dec(SkipCount, SourceSize);
+ end;
+
+ // get source data (e.g. from a decoder)
+ RequestedSourceSize := Min(AdjustedSize, BufferSize);
+ SourceSize := SourceStream.ReadData(Buffer, RequestedSourceSize);
+ if (SourceSize < 0) then
+ Exit;
+
+ // set preliminary result
+ Result := SourceSize;
+
+ // if we are to far ahead, fill output-buffer with last frame of source data
+ // Note that AdjustedSize is used instead of SourceSize as the SourceSize might
+ // be less than expected because of errors etc.
+ if (AdjustedSize < BufferSize) then
+ begin
+ // use either the last frame for padding or fill with zero
+ if (SourceSize >= FrameSize) then
+ PadFrame := @Buffer[SourceSize-FrameSize]
+ else
+ PadFrame := nil;
+
+ FillBufferWithFrame(@Buffer[SourceSize], BufferSize - SourceSize,
+ PadFrame, FrameSize);
+ Result := BufferSize;
+ end;
+end;
+
+constructor TBassPlaybackStream.Create();
begin
- inherited Create();
+ inherited;
Reset();
- Handle := stream;
end;
destructor TBassPlaybackStream.Destroy();
@@ -111,32 +217,99 @@ begin
inherited;
end;
-procedure TBassPlaybackStream.Reset();
+function TBassPlaybackStream.Open(SourceStream: TAudioSourceStream): boolean;
+var
+ FormatInfo: TAudioFormatInfo;
+ FormatFlags: DWORD;
begin
+ Result := false;
+
+ // close previous stream and reset state
+ Reset();
+
+ // sanity check if stream is valid
+ if not assigned(SourceStream) then
+ Exit;
+
+ Self.SourceStream := SourceStream;
+ FormatInfo := SourceStream.GetAudioFormatInfo();
+ if (not BassCore.ConvertAudioFormatToBASSFlags(FormatInfo.Format, FormatFlags)) then
+ begin
+ Log.LogError('Unhandled sample-format', 'TBassPlaybackStream.Open');
+ Exit;
+ end;
+
+ // create matching playback stream
+ Handle := BASS_StreamCreate(Round(FormatInfo.SampleRate), FormatInfo.Channels, formatFlags,
+ @PlaybackStreamHandler, Self);
+ if (Handle = 0) then
+ begin
+ Log.LogError('BASS_StreamCreate failed: ' + BassCore.ErrorGetString(BASS_ErrorGetCode()),
+ 'TBassPlaybackStream.Open');
+ Exit;
+ end;
+
+ Result := true;
+end;
+
+procedure TBassPlaybackStream.Close();
+begin
+ // stop and free stream
if (Handle <> 0) then
begin
Bass_StreamFree(Handle);
+ Handle := 0;
end;
- Handle := 0;
+
+ // Note: PerformOnClose must be called before SourceStream is invalidated
+ PerformOnClose();
+ // unset source-stream
+ SourceStream := nil;
+end;
+
+procedure TBassPlaybackStream.Reset();
+begin
+ Close();
+ NeedsRewind := false;
+ PausedSeek := false;
end;
procedure TBassPlaybackStream.Play();
var
- restart: boolean;
+ NeedsFlush: boolean;
begin
+ if (not assigned(SourceStream)) then
+ Exit;
+
+ NeedsFlush := true;
+
if (BASS_ChannelIsActive(Handle) = BASS_ACTIVE_PAUSED) then
- restart := false // resume from last position
- else
- restart := true; // start from the beginning
+ begin
+ // only paused (and not seeked while paused) streams are not flushed
+ if (not PausedSeek) then
+ NeedsFlush := false;
+ // paused streams do not need a rewind
+ NeedsRewind := false;
+ end;
+
+ // rewind if necessary. Cases that require no rewind are:
+ // - stream was created and never played
+ // - stream was paused and is resumed now
+ // - stream was stopped and set to a new position already
+ if (NeedsRewind) then
+ SourceStream.Position := 0;
+
+ NeedsRewind := true;
+ PausedSeek := false;
- BASS_ChannelPlay(Handle, restart);
+ // start playing and flush buffers on rewind
+ BASS_ChannelPlay(Handle, NeedsFlush);
end;
procedure TBassPlaybackStream.FadeIn(Time: real; TargetVolume: single);
begin
// start stream
- BASS_ChannelPlay(Handle, true);
-
+ Play();
// start fade-in: slide from fadeStart- to fadeEnd-volume in FadeInTime
BASS_ChannelSlideAttribute(Handle, BASS_ATTRIB_VOL, TargetVolume, Trunc(Time * 1000));
end;
@@ -151,9 +324,22 @@ begin
BASS_ChannelStop(Handle);
end;
-procedure TBassPlaybackStream.Close();
+function TBassPlaybackStream.IsEOF(): boolean;
begin
- Reset();
+ if (assigned(SourceStream)) then
+ Result := SourceStream.EOF
+ else
+ Result := true;
+end;
+
+function TBassPlaybackStream.GetLatency(): double;
+begin
+ // TODO: should we consider output latency for synching (needs BASS_DEVICE_LATENCY)?
+ //if (BASS_GetInfo(Info)) then
+ // Latency := Info.latency / 1000
+ //else
+ // Latency := 0;
+ Result := 0;
end;
function TBassPlaybackStream.GetVolume(): single;
@@ -162,7 +348,7 @@ var
begin
if (not BASS_ChannelGetAttribute(Handle, BASS_ATTRIB_VOL, lVolume)) then
begin
- Log.LogError('BASS_ChannelGetAttribute: ' + AudioCore.ErrorGetString(),
+ Log.LogError('BASS_ChannelGetAttribute: ' + BassCore.ErrorGetString(),
'TBassPlaybackStream.GetVolume');
Result := 0;
Exit;
@@ -170,235 +356,272 @@ begin
Result := Round(lVolume);
end;
-procedure TBassPlaybackStream.SetVolume(volume: single);
+procedure TBassPlaybackStream.SetVolume(Volume: single);
begin
// clamp volume
- if volume < 0 then
- volume := 0;
- if volume > 1.0 then
- volume := 1.0;
+ if Volume < 0 then
+ Volume := 0;
+ if Volume > 1.0 then
+ Volume := 1.0;
// set volume
- BASS_ChannelSetAttribute(Handle, BASS_ATTRIB_VOL, volume);
+ BASS_ChannelSetAttribute(Handle, BASS_ATTRIB_VOL, Volume);
end;
function TBassPlaybackStream.GetPosition: real;
var
- bytes: QWORD;
+ BufferPosByte: QWORD;
+ BufferPosSec: double;
begin
- bytes := BASS_ChannelGetPosition(Handle, BASS_POS_BYTE);
- Result := BASS_ChannelBytes2Seconds(Handle, bytes);
+ if assigned(SourceStream) then
+ begin
+ BufferPosByte := BASS_ChannelGetData(Handle, nil, BASS_DATA_AVAILABLE);
+ BufferPosSec := BASS_ChannelBytes2Seconds(Handle, BufferPosByte);
+ // decrease the decoding position by the amount buffered (and hence not played)
+ // in the BASS playback stream.
+ Result := SourceStream.Position - BufferPosSec;
+ end
+ else
+ begin
+ Result := -1;
+ end;
end;
procedure TBassPlaybackStream.SetPosition(Time: real);
var
- bytes: QWORD;
+ ChannelState: DWORD;
begin
- bytes := BASS_ChannelSeconds2Bytes(Handle, Time);
- BASS_ChannelSetPosition(Handle, bytes, BASS_POS_BYTE);
+ if assigned(SourceStream) then
+ begin
+ ChannelState := BASS_ChannelIsActive(Handle);
+ if (ChannelState = BASS_ACTIVE_STOPPED) then
+ begin
+ // if the stream is stopped, do not rewind when the stream is played next time
+ NeedsRewind := false
+ end
+ else if (ChannelState = BASS_ACTIVE_PAUSED) then
+ begin
+ // buffers must be flushed if in paused state but there is no
+ // BASS_ChannelFlush() function so we have to use BASS_ChannelPlay() called in Play().
+ PausedSeek := true;
+ end;
+
+ // set new position
+ SourceStream.Position := Time;
+ end;
end;
function TBassPlaybackStream.GetLength(): real;
-var
- bytes: QWORD;
begin
- bytes := BASS_ChannelGetLength(Handle, BASS_POS_BYTE);
- Result := BASS_ChannelBytes2Seconds(Handle, bytes);
+ if assigned(SourceStream) then
+ Result := SourceStream.Length
+ else
+ Result := -1;
end;
function TBassPlaybackStream.GetStatus(): TStreamStatus;
var
- state: DWORD;
+ State: DWORD;
begin
- state := BASS_ChannelIsActive(Handle);
- case state of
- BASS_ACTIVE_PLAYING:
- result := ssPlaying;
- BASS_ACTIVE_PAUSED:
- result := ssPaused;
+ State := BASS_ChannelIsActive(Handle);
+ case State of
+ BASS_ACTIVE_PLAYING,
BASS_ACTIVE_STALLED:
- result := ssBlocked;
+ Result := ssPlaying;
+ BASS_ACTIVE_PAUSED:
+ Result := ssPaused;
BASS_ACTIVE_STOPPED:
- result := ssStopped;
+ Result := ssStopped;
else
- result := ssUnknown;
+ begin
+ Log.LogError('Unknown status', 'TBassPlaybackStream.GetStatus');
+ Result := ssStopped;
+ end;
end;
end;
function TBassPlaybackStream.GetLoop(): boolean;
-var
- flags: DWORD;
begin
- // retrieve channel flags
- flags := BASS_ChannelFlags(Handle, 0, 0);
- if (flags = -1) then
- begin
- Log.LogError('BASS_ChannelFlags: ' + AudioCore.ErrorGetString(), 'TBassPlaybackStream.GetLoop');
+ if assigned(SourceStream) then
+ Result := SourceStream.Loop
+ else
Result := false;
- Exit;
- end;
- Result := (flags and BASS_SAMPLE_LOOP) <> 0;
end;
procedure TBassPlaybackStream.SetLoop(Enabled: boolean);
-var
- flags: DWORD;
begin
- // set/unset loop-flag
- if (Enabled) then
- flags := BASS_SAMPLE_LOOP
- else
- flags := 0;
-
- // set new flag-bits
- if (BASS_ChannelFlags(Handle, flags, BASS_SAMPLE_LOOP) = -1) then
- begin
- Log.LogError('BASS_ChannelFlags: ' + AudioCore.ErrorGetString(), 'TBassPlaybackStream.SetLoop');
- Exit;
- end;
+ if assigned(SourceStream) then
+ SourceStream.Loop := Enabled;
end;
-procedure DSPProcHandler(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: Pointer); {$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF}
+procedure DSPProcHandler(handle: HDSP; channel: DWORD; buffer: Pointer; length: DWORD; user: Pointer);
+{$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF}
var
- effect: TSoundEffect;
+ Effect: TSoundEffect;
begin
- effect := TSoundEffect(user);
- if assigned(effect) then
- effect.Callback(buffer, length);
+ Effect := TSoundEffect(user);
+ if assigned(Effect) then
+ Effect.Callback(buffer, length);
end;
-procedure TBassPlaybackStream.AddSoundEffect(effect: TSoundEffect);
+procedure TBassPlaybackStream.AddSoundEffect(Effect: TSoundEffect);
var
- dspHandle: HDSP;
+ DspHandle: HDSP;
begin
- if assigned(effect.engineData) then
+ if assigned(Effect.engineData) then
begin
Log.LogError('TSoundEffect.engineData already set', 'TBassPlaybackStream.AddSoundEffect');
Exit;
end;
- dspHandle := BASS_ChannelSetDSP(Handle, @DSPProcHandler, effect, 0);
- if (dspHandle = 0) then
+ DspHandle := BASS_ChannelSetDSP(Handle, @DSPProcHandler, Effect, 0);
+ if (DspHandle = 0) then
begin
- Log.LogError(AudioCore.ErrorGetString(), 'TBassPlaybackStream.AddSoundEffect');
+ Log.LogError(BassCore.ErrorGetString(), 'TBassPlaybackStream.AddSoundEffect');
Exit;
end;
- GetMem(effect.engineData, SizeOf(HDSP));
- PHDSP(effect.engineData)^ := dspHandle;
+ GetMem(Effect.EngineData, SizeOf(HDSP));
+ PHDSP(Effect.EngineData)^ := DspHandle;
end;
-procedure TBassPlaybackStream.RemoveSoundEffect(effect: TSoundEffect);
+procedure TBassPlaybackStream.RemoveSoundEffect(Effect: TSoundEffect);
begin
- if not assigned(effect.EngineData) then
+ if not assigned(Effect.EngineData) then
begin
Log.LogError('TSoundEffect.engineData invalid', 'TBassPlaybackStream.RemoveSoundEffect');
Exit;
end;
- if not BASS_ChannelRemoveDSP(Handle, PHDSP(effect.EngineData)^) then
+ if not BASS_ChannelRemoveDSP(Handle, PHDSP(Effect.EngineData)^) then
begin
- Log.LogError(AudioCore.ErrorGetString(), 'TBassPlaybackStream.RemoveSoundEffect');
+ Log.LogError(BassCore.ErrorGetString(), 'TBassPlaybackStream.RemoveSoundEffect');
Exit;
end;
- FreeMem(effect.engineData);
- effect.engineData := nil;
+ FreeMem(Effect.EngineData);
+ Effect.EngineData := nil;
end;
-procedure TBassPlaybackStream.GetFFTData(var data: TFFTData);
+procedure TBassPlaybackStream.GetFFTData(var Data: TFFTData);
begin
- // Get Channel Data Mono and 256 Values
- BASS_ChannelGetData(Handle, @data, BASS_DATA_FFT512);
+ // get FFT channel data (Mono, FFT512 -> 256 values)
+ BASS_ChannelGetData(Handle, @Data, BASS_DATA_FFT512);
end;
{*
* Copies interleaved PCM SInt16 stereo samples into data.
* Returns the number of frames
*}
-function TBassPlaybackStream.GetPCMData(var data: TPCMData): Cardinal;
+function TBassPlaybackStream.GetPCMData(var Data: TPCMData): Cardinal;
var
- info: BASS_CHANNELINFO;
+ Info: BASS_CHANNELINFO;
nBytes: DWORD;
begin
Result := 0;
- FillChar(data, sizeof(TPCMData), 0);
+ FillChar(Data, SizeOf(TPCMData), 0);
// no support for non-stereo files at the moment
- BASS_ChannelGetInfo(Handle, info);
- if (info.chans <> 2) then
+ BASS_ChannelGetInfo(Handle, Info);
+ if (Info.chans <> 2) then
Exit;
- nBytes := BASS_ChannelGetData(Handle, @data, sizeof(TPCMData));
+ nBytes := BASS_ChannelGetData(Handle, @Data, SizeOf(TPCMData));
if(nBytes <= 0) then
- result := 0
+ Result := 0
else
- result := nBytes div sizeof(TPCMStereoSample);
+ Result := nBytes div SizeOf(TPCMStereoSample);
end;
+function TBassPlaybackStream.GetAudioFormatInfo(): TAudioFormatInfo;
+begin
+ if assigned(SourceStream) then
+ Result := SourceStream.GetAudioFormatInfo()
+ else
+ Result := nil;
+end;
-{ TBassExtDecoderPlaybackStream }
-procedure TBassExtDecoderPlaybackStream.Stop();
+{ TBassVoiceStream }
+
+function TBassVoiceStream.Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean;
+var
+ Flags: DWORD;
begin
- inherited;
- // rewind
- if assigned(DecodeStream) then
- DecodeStream.Position := 0;
+ Result := false;
+
+ Close();
+
+ if (not inherited Open(ChannelMap, FormatInfo)) then
+ Exit;
+
+ // get channel flags
+ BassCore.ConvertAudioFormatToBASSFlags(FormatInfo.Format, Flags);
+
+ (*
+ // distribute the mics equally to both speakers
+ if ((ChannelMap and CHANNELMAP_LEFT) <> 0) then
+ Flags := Flags or BASS_SPEAKER_FRONTLEFT;
+ if ((ChannelMap and CHANNELMAP_RIGHT) <> 0) then
+ Flags := Flags or BASS_SPEAKER_FRONTRIGHT;
+ *)
+
+ // create the channel
+ Handle := BASS_StreamCreate(Round(FormatInfo.SampleRate), 1, Flags, STREAMPROC_PUSH, nil);
+
+ // start the channel
+ BASS_ChannelPlay(Handle, true);
+
+ Result := true;
end;
-procedure TBassExtDecoderPlaybackStream.Close();
+procedure TBassVoiceStream.Close();
begin
- // 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);
+ if (Handle <> 0) then
+ begin
+ BASS_ChannelStop(Handle);
+ BASS_StreamFree(Handle);
+ end;
+ inherited Close();
end;
-function TBassExtDecoderPlaybackStream.GetLength(): real;
+procedure TBassVoiceStream.WriteData(Buffer: PChar; BufferSize: integer);
+var QueueSize: DWORD;
begin
- if assigned(DecodeStream) then
- result := DecodeStream.Length
- else
- result := -1;
+ if ((Handle <> 0) and (BufferSize > 0)) then
+ begin
+ // query the queue size (normally 0)
+ QueueSize := BASS_StreamPutData(Handle, nil, 0);
+ // flush the buffer if the delay would be too high
+ if (QueueSize > MAX_VOICE_DELAY * FormatInfo.BytesPerSec) then
+ BASS_ChannelPlay(Handle, true);
+ // send new data to playback buffer
+ BASS_StreamPutData(Handle, Buffer, BufferSize);
+ end;
end;
-function TBassExtDecoderPlaybackStream.GetPosition: real;
+// Note: we do not need the read-function for the BASS implementation
+function TBassVoiceStream.ReadData(Buffer: PChar; BufferSize: integer): integer;
begin
- if assigned(DecodeStream) then
- result := DecodeStream.Position
- else
- result := -1;
+ Result := -1;
end;
-procedure TBassExtDecoderPlaybackStream.SetPosition(Time: real);
+function TBassVoiceStream.IsEOF(): boolean;
begin
- if assigned(DecodeStream) then
- DecodeStream.Position := Time;
+ Result := false;
end;
-function TBassExtDecoderPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
+function TBassVoiceStream.IsError(): boolean;
begin
- result := false;
-
- BASS_ChannelStop(Handle);
-
- if not assigned(decodeStream) then
- Exit;
- Self.DecodeStream := decodeStream;
-
- result := true;
+ Result := false;
end;
{ TAudioPlayback_Bass }
-function TAudioPlayback_Bass.GetName: String;
+function TAudioPlayback_Bass.GetName: String;
begin
- result := 'BASS_Playback';
+ Result := 'BASS_Playback';
end;
function TAudioPlayback_Bass.EnumDevices(): boolean;
@@ -408,23 +631,25 @@ var
Device: TBassOutputDevice;
DeviceInfo: BASS_DEVICEINFO;
begin
+ Result := true;
+
ClearOutputDeviceList();
// skip "no sound"-device (ID = 0)
BassDeviceID := 1;
- while true do
+ while (true) do
begin
- // Check for device
+ // check for device
if (not BASS_GetDeviceInfo(BassDeviceID, DeviceInfo)) then
- break;
+ Break;
- // Set device info
+ // set device info
Device := TBassOutputDevice.Create();
Device.Name := DeviceInfo.name;
Device.BassDeviceID := BassDeviceID;
- // Add device to list
+ // add device to list
SetLength(OutputDeviceList, BassDeviceID);
OutputDeviceList[BassDeviceID-1] := Device;
@@ -433,19 +658,17 @@ begin
end;
function TAudioPlayback_Bass.InitializePlayback(): boolean;
-var
- Pet: integer;
- S: integer;
begin
result := false;
- AudioCore := TAudioCore_Bass.GetInstance();
+ BassCore := TAudioCore_Bass.GetInstance();
EnumDevices();
//Log.BenchmarkStart(4);
//Log.LogStatus('Initializing Playback Subsystem', 'Music Initialize');
+ // TODO: use BASS_DEVICE_LATENCY to determine the latency
if not BASS_Init(-1, 44100, 0, 0, nil) then
begin
Log.LogError('Could not initialize BASS', 'TAudioPlayback_Bass.InitializePlayback');
@@ -469,125 +692,40 @@ begin
Result := true;
end;
-function DecodeStreamHandler(handle: HSTREAM; buffer: Pointer; length: DWORD; user: Pointer): DWORD; {$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF}
-var
- decodeStream: TAudioDecodeStream;
- bytes: integer;
+function TAudioPlayback_Bass.CreatePlaybackStream(): TAudioPlaybackStream;
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 := bytes;
+ Result := TBassPlaybackStream.Create();
+end;
+
+procedure TAudioPlayback_Bass.SetAppVolume(Volume: single);
+begin
+ // set volume for this application (ranges from 0..10000 since BASS 2.4)
+ BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Round(Volume*10000));
end;
-function TAudioPlayback_Bass.OpenStream(const Filename: string): TAudioPlaybackStream;
+function TAudioPlayback_Bass.CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
var
- stream: HSTREAM;
- playbackStream: TBassExtDecoderPlaybackStream;
- decodeStream: TAudioDecodeStream;
- formatInfo: TAudioFormatInfo;
- formatFlags: DWORD;
- channelInfo: BASS_CHANNELINFO;
- fileExt: string;
+ VoiceStream: TAudioVoiceStream;
begin
Result := nil;
- //Log.LogStatus('Loading Sound: "' + Filename + '"', 'LoadSoundFromFile');
- // TODO: use BASS_STREAM_PRESCAN for accurate seeking in VBR-files?
- // disadvantage: seeking will slow down.
- stream := BASS_StreamCreateFile(False, PChar(Filename), 0, 0, 0);
-
- // check if BASS opened some erroneously recognized file-formats
- if (stream <> 0) then
+ VoiceStream := TBassVoiceStream.Create();
+ if (not VoiceStream.Open(ChannelMap, FormatInfo)) then
begin
- if BASS_ChannelGetInfo(stream, channelInfo) then
- begin
- fileExt := ExtractFileExt(Filename);
- // BASS opens FLV-files (maybe others too) although it cannot handle them.
- // Setting BASS_CONFIG_VERIFY to the max. value (100000) does not help.
- if ((fileExt = '.flv') and (channelInfo.ctype = BASS_CTYPE_STREAM_MP1)) then
- begin
- BASS_StreamFree(stream);
- stream := 0;
- end;
- end;
+ VoiceStream.Free;
+ Exit;
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 + '", ' +
- AudioCore.ErrorGetString(), '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 AudioCore.ConvertAudioFormatToBASSFlags(formatInfo.Format, formatFlags)) then
- begin
- Log.LogError('Unhandled sample-format in "' + Filename + '"', 'TAudioPlayback_Bass.Load');
- FreeAndNil(decodeStream);
- Exit;
- end;
-
- stream := BASS_StreamCreate(Round(formatInfo.SampleRate), formatInfo.Channels, formatFlags,
- @DecodeStreamHandler, decodeStream);
- if (stream = 0) then
- begin
- Log.LogError('Failed to open "' + Filename + '", ' +
- AudioCore.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;
+ Result := VoiceStream;
end;
-procedure TAudioPlayback_Bass.SetAppVolume(Volume: single);
+function TAudioPlayback_Bass.GetLatency(): double;
begin
- // Sets Volume only for this Application (now ranges from 0..10000)
- BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Round(Volume*10000));
+ Result := 0;
end;
initialization
- singleton_AudioPlaybackBass := TAudioPlayback_Bass.create();
- AudioManager.add( singleton_AudioPlaybackBass );
-
-finalization
- AudioManager.Remove( singleton_AudioPlaybackBass );
+ MediaManager.Add(TAudioPlayback_Bass.Create);
end.