diff options
author | tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c> | 2008-05-09 19:19:28 +0000 |
---|---|---|
committer | tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c> | 2008-05-09 19:19:28 +0000 |
commit | b5a738fa52c8b0f2212deb5febd2d7f0b8f6544f (patch) | |
tree | 3c2812cffdd035b385d5b0f0f8f5ea0702973739 /Game/Code/Classes/UAudioPlayback_Bass.pas | |
parent | 37744cee627605db0675efd3a6e0c42bd51c48d6 (diff) | |
download | usdx-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 'Game/Code/Classes/UAudioPlayback_Bass.pas')
-rw-r--r-- | Game/Code/Classes/UAudioPlayback_Bass.pas | 549 |
1 files changed, 358 insertions, 191 deletions
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; |