aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Game/Code/Classes/UAudioPlayback_SoftMixer.pas1793
1 files changed, 899 insertions, 894 deletions
diff --git a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
index 29983778..75fddf95 100644
--- a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
+++ b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
@@ -1,894 +1,899 @@
-unit UAudioPlayback_SoftMixer;
-
-interface
-
-{$IFDEF FPC}
- {$MODE Delphi}
-{$ENDIF}
-
-{$I switches.inc}
-
-
-uses
- Classes,
- SysUtils,
- sdl,
- UMusic;
-
-type
- TAudioPlayback_SoftMixer = class;
-
- TSoftMixerPlaybackStream = class(TAudioPlaybackStream)
- private
- Engine: TAudioPlayback_SoftMixer;
-
- DecodeStream: TAudioDecodeStream;
- SampleBuffer : PChar;
- SampleBufferCount: integer; // number of available bytes in SampleBuffer
- SampleBufferPos : cardinal;
- BytesAvail: integer;
- cvt: TSDL_AudioCVT;
-
- Status: TStreamStatus;
- Loop: boolean;
- _volume: integer;
-
- InternalLock: PSDL_Mutex;
-
- procedure Reset();
-
- class function ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16;
- function InitFormatConversion(): boolean;
-
- procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
- procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
- public
- constructor Create(Engine: TAudioPlayback_SoftMixer);
- destructor Destroy(); override;
-
- function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
-
- procedure Play(); override;
- procedure Pause(); override;
- procedure Stop(); override;
- procedure Close(); override;
- function GetLoop(): boolean; override;
- procedure SetLoop(Enabled: boolean); override;
- function GetLength(): real; override;
- function GetStatus(): TStreamStatus; override;
-
- function IsLoaded(): boolean;
-
- function GetVolume(): integer; override;
- procedure SetVolume(Volume: integer); 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);
- end;
-
- TAudioMixerStream = class
- private
- activeStreams: TList;
- mixerBuffer: PChar;
- internalLock: PSDL_Mutex;
-
- _volume: integer;
-
- procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
- procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
-
- function GetVolume(): integer;
- procedure SetVolume(volume: integer);
- public
- constructor Create();
- destructor Destroy(); override;
- procedure AddStream(stream: TAudioPlaybackStream);
- procedure RemoveStream(stream: TAudioPlaybackStream);
- function ReadData(Buffer: PChar; BufSize: integer): integer;
-
- property Volume: integer READ GetVolume WRITE SetVolume;
- end;
-
- TAudioPlayback_SoftMixer = class( TInterfacedObject, IAudioPlayback )
- private
- MusicStream: TSoftMixerPlaybackStream;
- MixerStream: TAudioMixerStream;
- protected
- FormatInfo: TAudioFormatInfo;
-
- function InitializeAudioPlaybackEngine(): boolean; virtual; abstract;
- function StartAudioPlaybackEngine(): boolean; virtual; abstract;
- procedure StopAudioPlaybackEngine(); 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): TSoftMixerPlaybackStream;
-
- 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);
-
- // Interface for Visualizer
- function GetPCMData(var data: TPCMData): Cardinal;
-
- function GetMixer(): TAudioMixerStream; {$IFDEF HasInline}inline;{$ENDIF}
- function GetAudioFormatInfo(): TAudioFormatInfo;
-
- // Sounds
- function OpenSound(const Filename: String): TAudioPlaybackStream;
- procedure PlaySound(stream: TAudioPlaybackStream);
- procedure StopSound(stream: TAudioPlaybackStream);
- end;
-
-implementation
-
-uses
- Math,
- //samplerate,
- UFFT,
- ULog,
- UIni,
- UMain;
-
-{ TAudioMixerStream }
-
-constructor TAudioMixerStream.Create();
-begin
- activeStreams := TList.Create;
- internalLock := SDL_CreateMutex();
- _volume := 100;
-end;
-
-destructor TAudioMixerStream.Destroy();
-begin
- if assigned(mixerBuffer) then
- Freemem(mixerBuffer);
- activeStreams.Free;
- SDL_DestroyMutex(internalLock);
-end;
-
-procedure TAudioMixerStream.Lock();
-begin
- SDL_mutexP(internalLock);
-end;
-
-procedure TAudioMixerStream.Unlock();
-begin
- SDL_mutexV(internalLock);
-end;
-
-function TAudioMixerStream.GetVolume(): integer;
-begin
- Lock();
- result := _volume;
- Unlock();
-end;
-
-procedure TAudioMixerStream.SetVolume(volume: integer);
-begin
- Lock();
- _volume := volume;
- Unlock();
-end;
-
-procedure TAudioMixerStream.AddStream(stream: TAudioPlaybackStream);
-begin
- if not assigned(stream) then
- Exit;
-
- Lock();
- // check if stream is already in list to avoid duplicates
- if (activeStreams.IndexOf(Pointer(stream)) = -1) then
- activeStreams.Add(Pointer(stream));
- Unlock();
-end;
-
-(*
- * Sets the entry of stream in the activeStreams-List to nil
- * but does not remove it from the list (count is not changed!).
- * Otherwise iterations over the elements might fail due to a
- * changed count-property.
- * Call activeStreams.Pack() to remove the nil-pointers
- * or check for nil-pointers when accessing activeStreams.
- *)
-procedure TAudioMixerStream.RemoveStream(stream: TAudioPlaybackStream);
-var
- index: integer;
-begin
- Lock();
- index := activeStreams.IndexOf(Pointer(stream));
- if (index <> -1) then
- begin
- // remove entry but do not decrease count-property
- activeStreams[index] := nil;
- end;
- Unlock();
-end;
-
-function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer;
-var
- i: integer;
- size: integer;
- stream: TSoftMixerPlaybackStream;
- appVolume: single;
- needsPacking: boolean;
-begin
- result := BufSize;
-
- // zero target-buffer (silence)
- FillChar(Buffer^, BufSize, 0);
-
- // resize mixer-buffer if necessary
- ReallocMem(mixerBuffer, BufSize);
- if not assigned(mixerBuffer) then
- Exit;
-
- Lock();
-
- // calc application volume
- // use _volume instead of Volume to prevent recursive locking
- appVolume := _volume / 100 * SDL_MIX_MAXVOLUME;
-
- needsPacking := false;
-
- // mix streams to one stream
- for i := 0 to activeStreams.Count-1 do
- begin
- if (activeStreams[i] = nil) then
- begin
- needsPacking := true;
- continue;
- end;
-
- stream := TSoftMixerPlaybackStream(activeStreams[i]);
- // fetch data from current stream
- size := stream.ReadData(mixerBuffer, BufSize);
- if (size > 0) then
- begin
- SDL_MixAudio(PUInt8(Buffer), PUInt8(mixerBuffer), size,
- Trunc(appVolume * stream.Volume / 100));
- end;
- end;
-
- // remove nil-pointers from list
- if (needsPacking) then
- begin
- activeStreams.Pack();
- end;
-
- Unlock();
-end;
-
-
-{ TSoftMixerPlaybackStream }
-
-constructor TSoftMixerPlaybackStream.Create(Engine: TAudioPlayback_SoftMixer);
-begin
- inherited Create();
- Self.Engine := Engine;
- internalLock := SDL_CreateMutex();
- Reset();
-end;
-
-destructor TSoftMixerPlaybackStream.Destroy();
-begin
- Close();
- SDL_DestroyMutex(internalLock);
- inherited Destroy();
-end;
-
-procedure TSoftMixerPlaybackStream.Reset();
-begin
- Stop();
- Loop := false;
- // TODO: use DecodeStream.Unref() instead of Free();
- FreeAndNil(DecodeStream);
- FreeMem(SampleBuffer);
- SampleBuffer := nil;
- SampleBufferPos := 0;
- BytesAvail := 0;
- _volume := 0;
-end;
-
-procedure TSoftMixerPlaybackStream.Lock();
-begin
- SDL_mutexP(internalLock);
-end;
-
-procedure TSoftMixerPlaybackStream.Unlock();
-begin
- SDL_mutexV(internalLock);
-end;
-
-class function TSoftMixerPlaybackStream.ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16;
-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;
- end;
-end;
-
-function TSoftMixerPlaybackStream.InitFormatConversion(): boolean;
-var
- err: integer;
- srcFormat: UInt16;
- dstFormat: UInt16;
- srcFormatInfo: TAudioFormatInfo;
- dstFormatInfo: TAudioFormatInfo;
-begin
- Result := false;
-
- srcFormatInfo := DecodeStream.GetAudioFormatInfo();
- dstFormatInfo := Engine.GetAudioFormatInfo();
-
- srcFormat := ConvertAudioFormatToSDL(srcFormatInfo.Format);
- dstFormat := ConvertAudioFormatToSDL(dstFormatInfo.Format);
-
- if ((srcFormat = 0) or (dstFormat = 0)) then
- begin
- Log.LogError('Audio-format not supported by SDL', 'TSoftMixerPlaybackStream.InitFormatConversion');
- Exit;
- end;
-
- if (SDL_BuildAudioCVT(@cvt,
- srcFormat, srcFormatInfo.Channels, srcFormatInfo.SampleRate,
- dstFormat, dstFormatInfo.Channels, dstFormatInfo.SampleRate) = -1) then
- begin
- Log.LogError(SDL_GetError(), 'TSoftMixerPlaybackStream.InitFormatConversion');
- Exit;
- end;
-
- Result := true;
-end;
-
-function TSoftMixerPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
-begin
- result := false;
-
- Reset();
-
- if not assigned(decodeStream) then
- Exit;
- Self.DecodeStream := decodeStream;
- if not InitFormatConversion() then
- Exit;
-
- _volume := 100;
-
- result := true;
-end;
-
-procedure TSoftMixerPlaybackStream.Close();
-begin
- Reset();
-end;
-
-procedure TSoftMixerPlaybackStream.Play();
-var
- mixer: TAudioMixerStream;
-begin
- if (status <> ssPaused) then
- begin
- // rewind
- if assigned(DecodeStream) then
- DecodeStream.Position := 0;
- end;
- status := ssPlaying;
-
- mixer := Engine.GetMixer();
- if (mixer <> nil) then
- mixer.AddStream(Self);
-end;
-
-procedure TSoftMixerPlaybackStream.Pause();
-var
- mixer: TAudioMixerStream;
-begin
- status := ssPaused;
-
- mixer := Engine.GetMixer();
- if (mixer <> nil) then
- mixer.RemoveStream(Self);
-end;
-
-procedure TSoftMixerPlaybackStream.Stop();
-var
- mixer: TAudioMixerStream;
-begin
- status := ssStopped;
-
- mixer := Engine.GetMixer();
- if (mixer <> nil) then
- mixer.RemoveStream(Self);
-end;
-
-function TSoftMixerPlaybackStream.IsLoaded(): boolean;
-begin
- result := assigned(DecodeStream);
-end;
-
-function TSoftMixerPlaybackStream.GetLoop(): boolean;
-begin
- result := Loop;
-end;
-
-procedure TSoftMixerPlaybackStream.SetLoop(Enabled: boolean);
-begin
- Loop := Enabled;
-end;
-
-function TSoftMixerPlaybackStream.GetLength(): real;
-begin
- if assigned(DecodeStream) then
- result := DecodeStream.Length
- else
- result := -1;
-end;
-
-function TSoftMixerPlaybackStream.GetStatus(): TStreamStatus;
-begin
- result := status;
-end;
-
-{*
- * Note: 44.1kHz to 48kHz conversion or vice versa is not supported
- * by SDL at the moment. No conversion takes place in this cases.
- * This is because SDL just converts differences in powers of 2.
- * So the result might not be that accurate. Although this is not
- * audible in most cases it needs synchronization with the video
- * or the lyrics timer.
- * Using libsamplerate might give better results.
- *}
-function TSoftMixerPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
-var
- decodeBufSize: integer;
- sampleBufSize: integer;
- nBytesDecoded: integer;
- frameSize: integer;
- remFrameBytes: integer;
- copyCnt: integer;
- BytesNeeded: integer;
-begin
- Result := -1;
-
- BytesNeeded := BufSize;
-
- // copy remaining data from the last call to the result-buffer
- if (BytesAvail > 0) then
- begin
- copyCnt := Min(BufSize, BytesAvail);
- Move(SampleBuffer[SampleBufferPos], Buffer[0], copyCnt);
- Dec(BytesAvail, copyCnt);
- Dec(BytesNeeded, copyCnt);
- if (BytesNeeded = 0) then
- begin
- // Result-Buffer is full -> no need to decode more data.
- // The sample-buffer might even contain some data for the next call
- Inc(SampleBufferPos, copyCnt);
- Result := BufSize;
- Exit;
- end;
- end;
-
- if not assigned(DecodeStream) then
- Exit;
-
- // calc number of bytes to decode
- decodeBufSize := Ceil(BufSize / cvt.len_ratio);
- // assure that the decode-size is a multiple of the frame size
- frameSize := DecodeStream.GetAudioFormatInfo().FrameSize;
- remFrameBytes := decodeBufSize mod frameSize;
- if (remFrameBytes > 0) then
- decodeBufSize := decodeBufSize + (frameSize - remFrameBytes);
-
- Lock();
- try
- // calc buffer size
- sampleBufSize := decodeBufSize * cvt.len_mult;
-
- // resize buffer if necessary.
- // The required buffer-size will be smaller than the result-buffer
- // in most cases (if the decoded signal is mono or has a lesser bitrate).
- // If the output-rate is 44.1kHz and the decode-rate is 48kHz or 96kHz it
- // will be ~1.09 or ~2.18 times bigger. Those extra memory consumption
- // should be reasonable. If not we should call TDecodeStream.ReadData()
- // multiple times.
- // Note: we do not decrease the buffer by the count of bytes used from
- // the previous call of this function (bytesAvail). Otherwise the
- // buffer will be reallocated each time this function is called just to
- // add or remove a few bytes from the buffer.
- // By not doing this the buffer's size should be rather stable and it
- // will not be reallocated/resized at all if the BufSize params does not
- // change in consecutive calls.
- ReallocMem(SampleBuffer, sampleBufSize);
- if not assigned(SampleBuffer) then
- Exit;
-
- // decode data
- nBytesDecoded := DecodeStream.ReadData(SampleBuffer, decodeBufSize);
- if (nBytesDecoded = -1) then
- Exit;
-
- // end-of-file reached -> stop playback
- if (DecodeStream.EOF) then
- Stop();
-
- // resample decoded data
- cvt.buf := PUint8(SampleBuffer);
- cvt.len := nBytesDecoded;
- if (SDL_ConvertAudio(@cvt) = -1) then
- Exit;
-
- SampleBufferCount := cvt.len_cvt;
- finally
- Unlock();
- end;
-
- BytesAvail := SampleBufferCount;
- SampleBufferPos := 0;
-
- // copy data to result buffer
- copyCnt := Min(BytesNeeded, BytesAvail);
- Move(SampleBuffer[0], Buffer[BufSize - BytesNeeded], copyCnt);
- Dec(BytesAvail, copyCnt);
- Dec(BytesNeeded, copyCnt);
- Inc(SampleBufferPos, copyCnt);
-
- Result := BufSize - BytesNeeded;
-end;
-
-(* TODO: libsamplerate support
-function TSoftMixerPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
-var
- convState: PSRC_STATE;
- convData: SRC_DATA;
- error: integer;
-begin
- // Note: needs mono->stereo conversion, multi-channel->stereo, etc.
- // maybe we should use SDL for the channel-conversion stuff
- // and use libsamplerate afterwards for the frequency-conversion
-
- //convState := src_new(SRC_SINC_MEDIUM_QUALITY, 2, @error);
- //src_short_to_float_array(input, output, len);
- convData.
- if (src_process(convState, @convData) <> 0) then
- begin
- Log.LogError(src_strerror(src_error(convState)), 'TSoftMixerPlaybackStream.ReadData');
- Exit;
- end;
- src_float_to_short_array();
- //src_delete(convState);
-end;
-*)
-
-function TSoftMixerPlaybackStream.GetPCMData(var data: TPCMData): Cardinal;
-var size: integer;
-begin
- Result := 0;
-(*
- // just SInt16 stereo support for now
- if ((Engine.GetAudioFormatInfo().Format <> asfS16) or
- (Engine.GetAudioFormatInfo().Channels <> 2)) then
- begin
- Exit;
- end;
-
- // zero memory
- FillChar(data, SizeOf(data), 0);
-
- Lock();
- Result := Min(SizeOf(data), SampleBufferCount);
- if (Result > 0) then
- begin
- Move(SampleBuffer[0], data[0], Result);
- end;
- Unlock();
-*)
-end;
-
-procedure TSoftMixerPlaybackStream.GetFFTData(var data: TFFTData);
-var
- i: integer;
- Frames: integer;
- DataIn: PSingleArray;
- AudioFormat: TAudioFormatInfo;
-begin
- // only works with SInt16 and Float values at the moment
- AudioFormat := Engine.GetAudioFormatInfo();
-
- DataIn := AllocMem(FFTSize * SizeOf(Single));
- if (DataIn = nil) then
- Exit;
-
- Lock();
- // TODO: We just use the first Frames frames, the others are ignored.
- // This is OK for the equalizer display but not if we want to use
- // this function for voice-analysis someday (I don't think we want).
- Frames := Min(FFTSize, SampleBufferCount div AudioFormat.FrameSize);
- // use only first channel and convert data to float-values
- case AudioFormat.Format of
- asfS16:
- begin
- for i := 0 to Frames-1 do
- DataIn[i] := PSmallInt(@SampleBuffer[i*AudioFormat.FrameSize])^ / -Low(SmallInt);
- end;
- asfFloat:
- begin
- for i := 0 to Frames-1 do
- DataIn[i] := PSingle(@SampleBuffer[i*AudioFormat.FrameSize])^;
- end;
- end;
- Unlock();
-
- WindowFunc(fwfHanning, FFTSize, DataIn);
- PowerSpectrum(FFTSize, DataIn, @data);
- FreeMem(DataIn);
-
- // 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;
-
-function TSoftMixerPlaybackStream.GetPosition: real;
-begin
- if assigned(DecodeStream) then
- result := DecodeStream.Position
- else
- result := -1;
-end;
-
-procedure TSoftMixerPlaybackStream.SetPosition(Time: real);
-begin
- if assigned(DecodeStream) then
- DecodeStream.Position := Time;
-end;
-
-function TSoftMixerPlaybackStream.GetVolume(): integer;
-begin
- result := _volume;
-end;
-
-procedure TSoftMixerPlaybackStream.SetVolume(volume: integer);
-begin
- // clamp volume
- if (volume > 100) then
- _volume := 100
- else if (volume < 0) then
- _volume := 0
- else
- _volume := volume;
-end;
-
-
-{ TAudioPlayback_SoftMixer }
-
-function TAudioPlayback_SoftMixer.InitializePlayback: boolean;
-begin
- result := false;
-
- //Log.LogStatus('InitializePlayback', 'UAudioPlayback_SoftMixer');
-
- if(not InitializeAudioPlaybackEngine()) then
- Exit;
-
- MixerStream := TAudioMixerStream.Create;
-
- if(not StartAudioPlaybackEngine()) then
- Exit;
-
- result := true;
-end;
-
-destructor TAudioPlayback_SoftMixer.Destroy;
-begin
- StopAudioPlaybackEngine();
-
- FreeAndNil(MusicStream);
- FreeAndNil(MixerStream);
- FreeAndNil(FormatInfo);
-
- inherited Destroy();
-end;
-
-procedure TAudioPlayback_SoftMixer.AudioCallback(buffer: PChar; size: integer);
-begin
- MixerStream.ReadData(buffer, size);
-end;
-
-function TAudioPlayback_SoftMixer.GetMixer(): TAudioMixerStream;
-begin
- Result := MixerStream;
-end;
-
-function TAudioPlayback_SoftMixer.GetAudioFormatInfo(): TAudioFormatInfo;
-begin
- Result := FormatInfo;
-end;
-
-function TAudioPlayback_SoftMixer.Load(const Filename: String): TSoftMixerPlaybackStream;
-var
- decodeStream: TAudioDecodeStream;
- playbackStream: TSoftMixerPlaybackStream;
-begin
- Result := nil;
-
- decodeStream := AudioDecoder.Open(Filename);
- if not assigned(decodeStream) then
- begin
- Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudioPlayback_SoftMixer');
- Exit;
- end;
-
- playbackStream := TSoftMixerPlaybackStream.Create(Self);
- if (not playbackStream.SetDecodeStream(decodeStream)) then
- Exit;
-
- result := playbackStream;
-end;
-
-procedure TAudioPlayback_SoftMixer.SetVolume(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;
-
-//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.
+unit UAudioPlayback_SoftMixer;
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+
+uses
+ Classes,
+ SysUtils,
+ sdl,
+ UMusic;
+
+type
+ TAudioPlayback_SoftMixer = class;
+
+ TSoftMixerPlaybackStream = class(TAudioPlaybackStream)
+ private
+ Engine: TAudioPlayback_SoftMixer;
+
+ DecodeStream: TAudioDecodeStream;
+ SampleBuffer : PChar;
+ SampleBufferCount: integer; // number of available bytes in SampleBuffer
+ SampleBufferPos : cardinal;
+ BytesAvail: integer;
+ cvt: TSDL_AudioCVT;
+
+ Status: TStreamStatus;
+ Loop: boolean;
+ _volume: integer;
+
+ InternalLock: PSDL_Mutex;
+
+ procedure Reset();
+
+ class function ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16;
+ function InitFormatConversion(): boolean;
+
+ procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
+ procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
+ public
+ constructor Create(Engine: TAudioPlayback_SoftMixer);
+ destructor Destroy(); override;
+
+ function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
+
+ procedure Play(); override;
+ procedure Pause(); override;
+ procedure Stop(); override;
+ procedure Close(); override;
+ function GetLoop(): boolean; override;
+ procedure SetLoop(Enabled: boolean); override;
+ function GetLength(): real; override;
+ function GetStatus(): TStreamStatus; override;
+
+ function IsLoaded(): boolean;
+
+ function GetVolume(): integer; override;
+ procedure SetVolume(Volume: integer); 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);
+ end;
+
+ TAudioMixerStream = class
+ private
+ activeStreams: TList;
+ mixerBuffer: PChar;
+ internalLock: PSDL_Mutex;
+
+ _volume: integer;
+
+ procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
+ procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
+
+ function GetVolume(): integer;
+ procedure SetVolume(volume: integer);
+ public
+ constructor Create();
+ destructor Destroy(); override;
+ procedure AddStream(stream: TAudioPlaybackStream);
+ procedure RemoveStream(stream: TAudioPlaybackStream);
+ function ReadData(Buffer: PChar; BufSize: integer): integer;
+
+ property Volume: integer READ GetVolume WRITE SetVolume;
+ end;
+
+ TAudioPlayback_SoftMixer = class( TInterfacedObject, IAudioPlayback )
+ private
+ MusicStream: TSoftMixerPlaybackStream;
+ MixerStream: TAudioMixerStream;
+ protected
+ FormatInfo: TAudioFormatInfo;
+
+ function InitializeAudioPlaybackEngine(): boolean; virtual; abstract;
+ function StartAudioPlaybackEngine(): boolean; virtual; abstract;
+ procedure StopAudioPlaybackEngine(); 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): TSoftMixerPlaybackStream;
+
+ 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);
+
+ // Interface for Visualizer
+ function GetPCMData(var data: TPCMData): Cardinal;
+
+ function GetMixer(): TAudioMixerStream; {$IFDEF HasInline}inline;{$ENDIF}
+ function GetAudioFormatInfo(): TAudioFormatInfo;
+
+ // Sounds
+ function OpenSound(const Filename: String): TAudioPlaybackStream;
+ procedure PlaySound(stream: TAudioPlaybackStream);
+ procedure StopSound(stream: TAudioPlaybackStream);
+ end;
+
+implementation
+
+uses
+ Math,
+ //samplerate,
+ UFFT,
+ ULog,
+ UIni,
+ UMain;
+
+{ TAudioMixerStream }
+
+constructor TAudioMixerStream.Create();
+begin
+ activeStreams := TList.Create;
+ internalLock := SDL_CreateMutex();
+ _volume := 100;
+end;
+
+destructor TAudioMixerStream.Destroy();
+begin
+ if assigned(mixerBuffer) then
+ Freemem(mixerBuffer);
+ activeStreams.Free;
+ SDL_DestroyMutex(internalLock);
+end;
+
+procedure TAudioMixerStream.Lock();
+begin
+ SDL_mutexP(internalLock);
+end;
+
+procedure TAudioMixerStream.Unlock();
+begin
+ SDL_mutexV(internalLock);
+end;
+
+function TAudioMixerStream.GetVolume(): integer;
+begin
+ Lock();
+ result := _volume;
+ Unlock();
+end;
+
+procedure TAudioMixerStream.SetVolume(volume: integer);
+begin
+ Lock();
+ _volume := volume;
+ Unlock();
+end;
+
+procedure TAudioMixerStream.AddStream(stream: TAudioPlaybackStream);
+begin
+ if not assigned(stream) then
+ Exit;
+
+ Lock();
+ // check if stream is already in list to avoid duplicates
+ if (activeStreams.IndexOf(Pointer(stream)) = -1) then
+ activeStreams.Add(Pointer(stream));
+ Unlock();
+end;
+
+(*
+ * Sets the entry of stream in the activeStreams-List to nil
+ * but does not remove it from the list (count is not changed!).
+ * Otherwise iterations over the elements might fail due to a
+ * changed count-property.
+ * Call activeStreams.Pack() to remove the nil-pointers
+ * or check for nil-pointers when accessing activeStreams.
+ *)
+procedure TAudioMixerStream.RemoveStream(stream: TAudioPlaybackStream);
+var
+ index: integer;
+begin
+ Lock();
+ index := activeStreams.IndexOf(Pointer(stream));
+ if (index <> -1) then
+ begin
+ // remove entry but do not decrease count-property
+ activeStreams[index] := nil;
+ end;
+ Unlock();
+end;
+
+function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer;
+var
+ i: integer;
+ size: integer;
+ stream: TSoftMixerPlaybackStream;
+ appVolume: single;
+ needsPacking: boolean;
+begin
+ result := BufSize;
+
+ // zero target-buffer (silence)
+ FillChar(Buffer^, BufSize, 0);
+
+ // resize mixer-buffer if necessary
+ ReallocMem(mixerBuffer, BufSize);
+ if not assigned(mixerBuffer) then
+ Exit;
+
+ Lock();
+
+ // calc application volume
+ // use _volume instead of Volume to prevent recursive locking
+ appVolume := _volume / 100 * SDL_MIX_MAXVOLUME;
+
+ needsPacking := false;
+
+ // mix streams to one stream
+ for i := 0 to activeStreams.Count-1 do
+ begin
+ if (activeStreams[i] = nil) then
+ begin
+ needsPacking := true;
+ continue;
+ end;
+
+ stream := TSoftMixerPlaybackStream(activeStreams[i]);
+ // fetch data from current stream
+ size := stream.ReadData(mixerBuffer, BufSize);
+ if (size > 0) then
+ begin
+ SDL_MixAudio(PUInt8(Buffer), PUInt8(mixerBuffer), size,
+ Trunc(appVolume * stream.Volume / 100));
+ end;
+ end;
+
+ // remove nil-pointers from list
+ if (needsPacking) then
+ begin
+ activeStreams.Pack();
+ end;
+
+ Unlock();
+end;
+
+
+{ TSoftMixerPlaybackStream }
+
+constructor TSoftMixerPlaybackStream.Create(Engine: TAudioPlayback_SoftMixer);
+begin
+ inherited Create();
+ Self.Engine := Engine;
+ internalLock := SDL_CreateMutex();
+ Reset();
+end;
+
+destructor TSoftMixerPlaybackStream.Destroy();
+begin
+ Close();
+ SDL_DestroyMutex(internalLock);
+ inherited Destroy();
+end;
+
+procedure TSoftMixerPlaybackStream.Reset();
+begin
+ Stop();
+ Loop := false;
+ // TODO: use DecodeStream.Unref() instead of Free();
+ FreeAndNil(DecodeStream);
+ FreeMem(SampleBuffer);
+ SampleBuffer := nil;
+ SampleBufferPos := 0;
+ BytesAvail := 0;
+ _volume := 0;
+end;
+
+procedure TSoftMixerPlaybackStream.Lock();
+begin
+ SDL_mutexP(internalLock);
+end;
+
+procedure TSoftMixerPlaybackStream.Unlock();
+begin
+ SDL_mutexV(internalLock);
+end;
+
+class function TSoftMixerPlaybackStream.ConvertAudioFormatToSDL(fmt: TAudioSampleFormat): UInt16;
+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;
+ end;
+end;
+
+function TSoftMixerPlaybackStream.InitFormatConversion(): boolean;
+var
+ err: integer;
+ srcFormat: UInt16;
+ dstFormat: UInt16;
+ srcFormatInfo: TAudioFormatInfo;
+ dstFormatInfo: TAudioFormatInfo;
+begin
+ Result := false;
+
+ srcFormatInfo := DecodeStream.GetAudioFormatInfo();
+ dstFormatInfo := Engine.GetAudioFormatInfo();
+
+ srcFormat := ConvertAudioFormatToSDL(srcFormatInfo.Format);
+ dstFormat := ConvertAudioFormatToSDL(dstFormatInfo.Format);
+
+ if ((srcFormat = 0) or (dstFormat = 0)) then
+ begin
+ Log.LogError('Audio-format not supported by SDL', 'TSoftMixerPlaybackStream.InitFormatConversion');
+ Exit;
+ end;
+
+ if (SDL_BuildAudioCVT(@cvt,
+ srcFormat, srcFormatInfo.Channels, srcFormatInfo.SampleRate,
+ dstFormat, dstFormatInfo.Channels, dstFormatInfo.SampleRate) = -1) then
+ begin
+ Log.LogError(SDL_GetError(), 'TSoftMixerPlaybackStream.InitFormatConversion');
+ Exit;
+ end;
+
+ Result := true;
+end;
+
+function TSoftMixerPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
+begin
+ result := false;
+
+ Reset();
+
+ if not assigned(decodeStream) then
+ Exit;
+ Self.DecodeStream := decodeStream;
+ if not InitFormatConversion() then
+ Exit;
+
+ _volume := 100;
+
+ result := true;
+end;
+
+procedure TSoftMixerPlaybackStream.Close();
+begin
+ Reset();
+end;
+
+procedure TSoftMixerPlaybackStream.Play();
+var
+ mixer: TAudioMixerStream;
+begin
+ if (status <> ssPaused) then
+ begin
+ // rewind
+ if assigned(DecodeStream) then
+ DecodeStream.Position := 0;
+ end;
+ status := ssPlaying;
+
+ mixer := Engine.GetMixer();
+ if (mixer <> nil) then
+ mixer.AddStream(Self);
+end;
+
+procedure TSoftMixerPlaybackStream.Pause();
+var
+ mixer: TAudioMixerStream;
+begin
+ status := ssPaused;
+
+ mixer := Engine.GetMixer();
+ if (mixer <> nil) then
+ mixer.RemoveStream(Self);
+end;
+
+procedure TSoftMixerPlaybackStream.Stop();
+var
+ mixer: TAudioMixerStream;
+begin
+ status := ssStopped;
+
+ mixer := Engine.GetMixer();
+ if (mixer <> nil) then
+ mixer.RemoveStream(Self);
+end;
+
+function TSoftMixerPlaybackStream.IsLoaded(): boolean;
+begin
+ result := assigned(DecodeStream);
+end;
+
+function TSoftMixerPlaybackStream.GetLoop(): boolean;
+begin
+ result := Loop;
+end;
+
+procedure TSoftMixerPlaybackStream.SetLoop(Enabled: boolean);
+begin
+ Loop := Enabled;
+end;
+
+function TSoftMixerPlaybackStream.GetLength(): real;
+begin
+ if assigned(DecodeStream) then
+ result := DecodeStream.Length
+ else
+ result := -1;
+end;
+
+function TSoftMixerPlaybackStream.GetStatus(): TStreamStatus;
+begin
+ result := status;
+end;
+
+{*
+ * Note: 44.1kHz to 48kHz conversion or vice versa is not supported
+ * by SDL at the moment. No conversion takes place in this cases.
+ * This is because SDL just converts differences in powers of 2.
+ * So the result might not be that accurate. Although this is not
+ * audible in most cases it needs synchronization with the video
+ * or the lyrics timer.
+ * Using libsamplerate might give better results.
+ *}
+function TSoftMixerPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
+var
+ decodeBufSize: integer;
+ sampleBufSize: integer;
+ nBytesDecoded: integer;
+ frameSize: integer;
+ remFrameBytes: integer;
+ copyCnt: integer;
+ BytesNeeded: integer;
+begin
+ Result := -1;
+
+ BytesNeeded := BufSize;
+
+ // copy remaining data from the last call to the result-buffer
+ if (BytesAvail > 0) then
+ begin
+ copyCnt := Min(BufSize, BytesAvail);
+ Move(SampleBuffer[SampleBufferPos], Buffer[0], copyCnt);
+ Dec(BytesAvail, copyCnt);
+ Dec(BytesNeeded, copyCnt);
+ if (BytesNeeded = 0) then
+ begin
+ // Result-Buffer is full -> no need to decode more data.
+ // The sample-buffer might even contain some data for the next call
+ Inc(SampleBufferPos, copyCnt);
+ Result := BufSize;
+ Exit;
+ end;
+ end;
+
+ if not assigned(DecodeStream) then
+ Exit;
+
+ // calc number of bytes to decode
+ decodeBufSize := Ceil(BufSize / cvt.len_ratio);
+ // assure that the decode-size is a multiple of the frame size
+ frameSize := DecodeStream.GetAudioFormatInfo().FrameSize;
+ remFrameBytes := decodeBufSize mod frameSize;
+ if (remFrameBytes > 0) then
+ decodeBufSize := decodeBufSize + (frameSize - remFrameBytes);
+
+ Lock();
+ try
+ // calc buffer size
+ sampleBufSize := decodeBufSize * cvt.len_mult;
+
+ // resize buffer if necessary.
+ // The required buffer-size will be smaller than the result-buffer
+ // in most cases (if the decoded signal is mono or has a lesser bitrate).
+ // If the output-rate is 44.1kHz and the decode-rate is 48kHz or 96kHz it
+ // will be ~1.09 or ~2.18 times bigger. Those extra memory consumption
+ // should be reasonable. If not we should call TDecodeStream.ReadData()
+ // multiple times.
+ // Note: we do not decrease the buffer by the count of bytes used from
+ // the previous call of this function (bytesAvail). Otherwise the
+ // buffer will be reallocated each time this function is called just to
+ // add or remove a few bytes from the buffer.
+ // By not doing this the buffer's size should be rather stable and it
+ // will not be reallocated/resized at all if the BufSize params does not
+ // change in consecutive calls.
+ ReallocMem(SampleBuffer, sampleBufSize);
+ if not assigned(SampleBuffer) then
+ Exit;
+
+ // decode data
+ nBytesDecoded := DecodeStream.ReadData(SampleBuffer, decodeBufSize);
+ if (nBytesDecoded = -1) then
+ Exit;
+
+ // end-of-file reached -> stop playback
+ if (DecodeStream.EOF) then
+ Stop();
+
+ // resample decoded data
+ cvt.buf := PUint8(SampleBuffer);
+ cvt.len := nBytesDecoded;
+ if (SDL_ConvertAudio(@cvt) = -1) then
+ Exit;
+
+ SampleBufferCount := cvt.len_cvt;
+ finally
+ Unlock();
+ end;
+
+ BytesAvail := SampleBufferCount;
+ SampleBufferPos := 0;
+
+ // copy data to result buffer
+ copyCnt := Min(BytesNeeded, BytesAvail);
+ Move(SampleBuffer[0], Buffer[BufSize - BytesNeeded], copyCnt);
+ Dec(BytesAvail, copyCnt);
+ Dec(BytesNeeded, copyCnt);
+ Inc(SampleBufferPos, copyCnt);
+
+ Result := BufSize - BytesNeeded;
+end;
+
+(* TODO: libsamplerate support
+function TSoftMixerPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
+var
+ convState: PSRC_STATE;
+ convData: SRC_DATA;
+ error: integer;
+begin
+ // Note: needs mono->stereo conversion, multi-channel->stereo, etc.
+ // maybe we should use SDL for the channel-conversion stuff
+ // and use libsamplerate afterwards for the frequency-conversion
+
+ //convState := src_new(SRC_SINC_MEDIUM_QUALITY, 2, @error);
+ //src_short_to_float_array(input, output, len);
+ convData.
+ if (src_process(convState, @convData) <> 0) then
+ begin
+ Log.LogError(src_strerror(src_error(convState)), 'TSoftMixerPlaybackStream.ReadData');
+ Exit;
+ end;
+ src_float_to_short_array();
+ //src_delete(convState);
+end;
+*)
+
+function TSoftMixerPlaybackStream.GetPCMData(var data: TPCMData): Cardinal;
+var
+ nBytes: integer;
+begin
+ Result := 0;
+
+ // just SInt16 stereo support for now
+ if ((Engine.GetAudioFormatInfo().Format <> asfS16) or
+ (Engine.GetAudioFormatInfo().Channels <> 2)) then
+ begin
+ Exit;
+ end;
+
+ // zero memory
+ FillChar(data, SizeOf(data), 0);
+
+ // TODO: At the moment just the first samples of the SampleBuffer
+ // are returned, even if there is newer data in the upper samples.
+
+ Lock();
+ nBytes := Min(SizeOf(data), SampleBufferCount);
+ if (nBytes > 0) then
+ begin
+ Move(SampleBuffer[0], data, nBytes);
+ end;
+ Unlock();
+
+ Result := nBytes div SizeOf(TPCMStereoSample);
+end;
+
+procedure TSoftMixerPlaybackStream.GetFFTData(var data: TFFTData);
+var
+ i: integer;
+ Frames: integer;
+ DataIn: PSingleArray;
+ AudioFormat: TAudioFormatInfo;
+begin
+ // only works with SInt16 and Float values at the moment
+ AudioFormat := Engine.GetAudioFormatInfo();
+
+ DataIn := AllocMem(FFTSize * SizeOf(Single));
+ if (DataIn = nil) then
+ Exit;
+
+ Lock();
+ // TODO: We just use the first Frames frames, the others are ignored.
+ // This is OK for the equalizer display but not if we want to use
+ // this function for voice-analysis someday (I don't think we want).
+ Frames := Min(FFTSize, SampleBufferCount div AudioFormat.FrameSize);
+ // use only first channel and convert data to float-values
+ case AudioFormat.Format of
+ asfS16:
+ begin
+ for i := 0 to Frames-1 do
+ DataIn[i] := PSmallInt(@SampleBuffer[i*AudioFormat.FrameSize])^ / -Low(SmallInt);
+ end;
+ asfFloat:
+ begin
+ for i := 0 to Frames-1 do
+ DataIn[i] := PSingle(@SampleBuffer[i*AudioFormat.FrameSize])^;
+ end;
+ end;
+ Unlock();
+
+ WindowFunc(fwfHanning, FFTSize, DataIn);
+ PowerSpectrum(FFTSize, DataIn, @data);
+ FreeMem(DataIn);
+
+ // 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;
+
+function TSoftMixerPlaybackStream.GetPosition: real;
+begin
+ if assigned(DecodeStream) then
+ result := DecodeStream.Position
+ else
+ result := -1;
+end;
+
+procedure TSoftMixerPlaybackStream.SetPosition(Time: real);
+begin
+ if assigned(DecodeStream) then
+ DecodeStream.Position := Time;
+end;
+
+function TSoftMixerPlaybackStream.GetVolume(): integer;
+begin
+ result := _volume;
+end;
+
+procedure TSoftMixerPlaybackStream.SetVolume(volume: integer);
+begin
+ // clamp volume
+ if (volume > 100) then
+ _volume := 100
+ else if (volume < 0) then
+ _volume := 0
+ else
+ _volume := volume;
+end;
+
+
+{ TAudioPlayback_SoftMixer }
+
+function TAudioPlayback_SoftMixer.InitializePlayback: boolean;
+begin
+ result := false;
+
+ //Log.LogStatus('InitializePlayback', 'UAudioPlayback_SoftMixer');
+
+ if(not InitializeAudioPlaybackEngine()) then
+ Exit;
+
+ MixerStream := TAudioMixerStream.Create;
+
+ if(not StartAudioPlaybackEngine()) then
+ Exit;
+
+ result := true;
+end;
+
+destructor TAudioPlayback_SoftMixer.Destroy;
+begin
+ StopAudioPlaybackEngine();
+
+ FreeAndNil(MusicStream);
+ FreeAndNil(MixerStream);
+ FreeAndNil(FormatInfo);
+
+ inherited Destroy();
+end;
+
+procedure TAudioPlayback_SoftMixer.AudioCallback(buffer: PChar; size: integer);
+begin
+ MixerStream.ReadData(buffer, size);
+end;
+
+function TAudioPlayback_SoftMixer.GetMixer(): TAudioMixerStream;
+begin
+ Result := MixerStream;
+end;
+
+function TAudioPlayback_SoftMixer.GetAudioFormatInfo(): TAudioFormatInfo;
+begin
+ Result := FormatInfo;
+end;
+
+function TAudioPlayback_SoftMixer.Load(const Filename: String): TSoftMixerPlaybackStream;
+var
+ decodeStream: TAudioDecodeStream;
+ playbackStream: TSoftMixerPlaybackStream;
+begin
+ Result := nil;
+
+ decodeStream := AudioDecoder.Open(Filename);
+ if not assigned(decodeStream) then
+ begin
+ Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudioPlayback_SoftMixer');
+ Exit;
+ end;
+
+ playbackStream := TSoftMixerPlaybackStream.Create(Self);
+ if (not playbackStream.SetDecodeStream(decodeStream)) then
+ Exit;
+
+ result := playbackStream;
+end;
+
+procedure TAudioPlayback_SoftMixer.SetVolume(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;
+
+//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.