diff options
author | tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c> | 2008-07-02 07:50:39 +0000 |
---|---|---|
committer | tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c> | 2008-07-02 07:50:39 +0000 |
commit | faf4c13bf41a17ce920a2194fc396f8bf7b44331 (patch) | |
tree | 0049a0dacad82a08b934167660bfabd6c8ea47a8 /Game/Code/Classes/UAudioPlayback_Bass.pas | |
parent | 67d0be6741c5466c786d8d389e34c83e1be7e3c0 (diff) | |
download | usdx-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.pas | 686 |
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. |