aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-07-02 07:50:39 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-07-02 07:50:39 +0000
commitfaf4c13bf41a17ce920a2194fc396f8bf7b44331 (patch)
tree0049a0dacad82a08b934167660bfabd6c8ea47a8 /Game/Code/Classes/UAudioPlayback_SoftMixer.pas
parent67d0be6741c5466c786d8d389e34c83e1be7e3c0 (diff)
downloadusdx-faf4c13bf41a17ce920a2194fc396f8bf7b44331.tar.gz
usdx-faf4c13bf41a17ce920a2194fc396f8bf7b44331.tar.xz
usdx-faf4c13bf41a17ce920a2194fc396f8bf7b44331.zip
Audio/Video engine update:
- lyrics<->audio synchronisation (TSyncSource) - better resampling (optional support for libsamplerate) - cleaner termination of audio/video streams/devices - improved decoders and decoder infrastructure - many other improvements/cleanups Currently just for testing (not enabled by default): - Background music - Voice-Passthrough (hear what you sing) - Video VSync git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1157 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to 'Game/Code/Classes/UAudioPlayback_SoftMixer.pas')
-rw-r--r--Game/Code/Classes/UAudioPlayback_SoftMixer.pas1002
1 files changed, 611 insertions, 391 deletions
diff --git a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
index 714e19ae..31d0412b 100644
--- a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
+++ b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
@@ -13,6 +13,7 @@ uses
Classes,
SysUtils,
sdl,
+ URingBuffer,
UMusic,
UAudioPlaybackBase;
@@ -23,86 +24,90 @@ type
private
Engine: TAudioPlayback_SoftMixer;
- DecodeStream: TAudioDecodeStream;
- SampleBuffer : PChar;
+ SampleBuffer: PChar;
+ SampleBufferSize: integer;
SampleBufferCount: integer; // number of available bytes in SampleBuffer
- SampleBufferPos : cardinal;
- BytesAvail: integer;
- cvt: TSDL_AudioCVT;
+ SampleBufferPos: cardinal;
- Status: TStreamStatus;
- Loop: boolean;
+ SourceBuffer: PChar;
+ SourceBufferSize: integer;
+ SourceBufferCount: integer; // number of available bytes in SourceBuffer
+ Converter: TAudioConverter;
+ Status: TStreamStatus;
InternalLock: PSDL_Mutex;
-
SoundEffects: TList;
-
- _volume: single;
+ fVolume: single;
FadeInStartTime, FadeInTime: cardinal;
FadeInStartVolume, FadeInTargetVolume: single;
+ NeedsRewind: boolean;
+
procedure Reset();
- class function ConvertAudioFormatToSDL(Format: TAudioSampleFormat; out SDLFormat: UInt16): boolean;
+ procedure ApplySoundEffects(Buffer: PChar; BufferSize: integer);
function InitFormatConversion(): boolean;
+ procedure FlushBuffers();
- procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
- procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
+ procedure LockSampleBuffer(); {$IFDEF HasInline}inline;{$ENDIF}
+ procedure UnlockSampleBuffer(); {$IFDEF HasInline}inline;{$ENDIF}
+ protected
+ function GetLatency(): double; override;
+ function GetStatus(): TStreamStatus; override;
+ function GetVolume(): single; override;
+ procedure SetVolume(Volume: single); override;
+ function GetLength(): real; override;
+ function GetLoop(): boolean; override;
+ procedure SetLoop(Enabled: boolean); override;
+ function GetPosition: real; override;
+ procedure SetPosition(Time: real); override;
public
constructor Create(Engine: TAudioPlayback_SoftMixer);
destructor Destroy(); override;
- function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
+ 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;
+ function GetAudioFormatInfo(): TAudioFormatInfo; override;
- function GetLength(): real; override;
- function GetStatus(): TStreamStatus; override;
- function GetVolume(): single; override;
- procedure SetVolume(Volume: single); override;
- function GetLoop(): boolean; override;
- procedure SetLoop(Enabled: boolean); override;
- function GetPosition: real; override;
- procedure SetPosition(Time: real); override;
+ function ReadData(Buffer: PChar; BufferSize: integer): integer;
- function ReadData(Buffer: PChar; BufSize: integer): integer;
+ function GetPCMData(var Data: TPCMData): Cardinal; override;
+ procedure GetFFTData(var Data: TFFTData); override;
- function GetPCMData(var data: TPCMData): Cardinal; override;
- procedure GetFFTData(var data: TFFTData); override;
-
- procedure AddSoundEffect(effect: TSoundEffect); override;
- procedure RemoveSoundEffect(effect: TSoundEffect); override;
+ procedure AddSoundEffect(Effect: TSoundEffect); override;
+ procedure RemoveSoundEffect(Effect: TSoundEffect); override;
end;
TAudioMixerStream = class
private
Engine: TAudioPlayback_SoftMixer;
- activeStreams: TList;
- mixerBuffer: PChar;
- internalLock: PSDL_Mutex;
+ ActiveStreams: TList;
+ MixerBuffer: PChar;
+ InternalLock: PSDL_Mutex;
- appVolume: single;
+ AppVolume: single;
procedure Lock(); {$IFDEF HasInline}inline;{$ENDIF}
procedure Unlock(); {$IFDEF HasInline}inline;{$ENDIF}
function GetVolume(): single;
- procedure SetVolume(volume: single);
+ procedure SetVolume(Volume: single);
public
constructor Create(Engine: TAudioPlayback_SoftMixer);
destructor Destroy(); override;
- procedure AddStream(stream: TAudioPlaybackStream);
- procedure RemoveStream(stream: TAudioPlaybackStream);
- function ReadData(Buffer: PChar; BufSize: integer): integer;
+ procedure AddStream(Stream: TAudioPlaybackStream);
+ procedure RemoveStream(Stream: TAudioPlaybackStream);
+ function ReadData(Buffer: PChar; BufferSize: integer): integer;
- property Volume: single READ GetVolume WRITE SetVolume;
+ property Volume: single read GetVolume write SetVolume;
end;
TAudioPlayback_SoftMixer = class(TAudioPlaybackBase)
@@ -115,9 +120,9 @@ type
function StartAudioPlaybackEngine(): boolean; virtual; abstract;
procedure StopAudioPlaybackEngine(); virtual; abstract;
function FinalizeAudioPlaybackEngine(): boolean; virtual; abstract;
- procedure AudioCallback(buffer: PChar; size: integer); {$IFDEF HasInline}inline;{$ENDIF}
+ procedure AudioCallback(Buffer: PChar; Size: integer); {$IFDEF HasInline}inline;{$ENDIF}
- function OpenStream(const Filename: String): TAudioPlaybackStream; override;
+ function CreatePlaybackStream(): TAudioPlaybackStream; override;
public
function GetName: String; override; abstract;
function InitializePlayback(): boolean; override;
@@ -125,20 +130,46 @@ type
procedure SetAppVolume(Volume: single); override;
+ function CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream; override;
+
function GetMixer(): TAudioMixerStream; {$IFDEF HasInline}inline;{$ENDIF}
function GetAudioFormatInfo(): TAudioFormatInfo;
- procedure MixBuffers(dst, src: PChar; size: Cardinal; volume: Single); virtual;
+ procedure MixBuffers(DstBuffer, SrcBuffer: PChar; Size: Cardinal; Volume: Single); virtual;
+ end;
+
+type
+ TGenericVoiceStream = class(TAudioVoiceStream)
+ private
+ VoiceBuffer: TRingBuffer;
+ BufferLock: PSDL_Mutex;
+ PlaybackStream: TGenericPlaybackStream;
+ Engine: TAudioPlayback_SoftMixer;
+ public
+ constructor Create(Engine: TAudioPlayback_SoftMixer);
+
+ function Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean; override;
+ procedure Close(); override;
+ procedure WriteData(Buffer: PChar; BufferSize: integer); override;
+ function ReadData(Buffer: PChar; BufferSize: integer): integer; override;
+ function IsEOF(): boolean; override;
+ function IsError(): boolean; override;
end;
+const
+ SOURCE_BUFFER_FRAMES = 4096;
+
+const
+ MAX_VOICE_DELAY = 0.500; // 20ms
+
implementation
uses
Math,
- //samplerate,
- UFFT,
ULog,
UIni,
+ UFFT,
+ UAudioConverter,
UMain;
{ TAudioMixerStream }
@@ -149,123 +180,123 @@ begin
Self.Engine := Engine;
- activeStreams := TList.Create;
- internalLock := SDL_CreateMutex();
- appVolume := 1.0;
+ ActiveStreams := TList.Create;
+ InternalLock := SDL_CreateMutex();
+ AppVolume := 1.0;
end;
destructor TAudioMixerStream.Destroy();
begin
- if assigned(mixerBuffer) then
- Freemem(mixerBuffer);
- activeStreams.Free;
- SDL_DestroyMutex(internalLock);
+ if assigned(MixerBuffer) then
+ Freemem(MixerBuffer);
+ ActiveStreams.Free;
+ SDL_DestroyMutex(InternalLock);
inherited;
end;
procedure TAudioMixerStream.Lock();
begin
- SDL_mutexP(internalLock);
+ SDL_mutexP(InternalLock);
end;
procedure TAudioMixerStream.Unlock();
begin
- SDL_mutexV(internalLock);
+ SDL_mutexV(InternalLock);
end;
function TAudioMixerStream.GetVolume(): single;
begin
Lock();
- result := appVolume;
+ Result := AppVolume;
Unlock();
end;
-procedure TAudioMixerStream.SetVolume(volume: single);
+procedure TAudioMixerStream.SetVolume(Volume: single);
begin
Lock();
- appVolume := volume;
+ AppVolume := Volume;
Unlock();
end;
-procedure TAudioMixerStream.AddStream(stream: TAudioPlaybackStream);
+procedure TAudioMixerStream.AddStream(Stream: TAudioPlaybackStream);
begin
- if not assigned(stream) then
+ 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));
+ 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!).
+ * 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.
+ * changed Count-property.
+ * Call ActiveStreams.Pack() to remove the nil-pointers
+ * or check for nil-pointers when accessing ActiveStreams.
*)
-procedure TAudioMixerStream.RemoveStream(stream: TAudioPlaybackStream);
+procedure TAudioMixerStream.RemoveStream(Stream: TAudioPlaybackStream);
var
- index: integer;
+ Index: integer;
begin
Lock();
- index := activeStreams.IndexOf(Pointer(stream));
- if (index <> -1) then
+ Index := activeStreams.IndexOf(Pointer(Stream));
+ if (Index <> -1) then
begin
// remove entry but do not decrease count-property
- activeStreams[index] := nil;
+ ActiveStreams[Index] := nil;
end;
Unlock();
end;
-function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer;
+function TAudioMixerStream.ReadData(Buffer: PChar; BufferSize: integer): integer;
var
i: integer;
- size: integer;
- stream: TGenericPlaybackStream;
- needsPacking: boolean;
+ Size: integer;
+ Stream: TGenericPlaybackStream;
+ NeedsPacking: boolean;
begin
- result := BufSize;
+ Result := BufferSize;
// zero target-buffer (silence)
- FillChar(Buffer^, BufSize, 0);
+ FillChar(Buffer^, BufferSize, 0);
// resize mixer-buffer if necessary
- ReallocMem(mixerBuffer, BufSize);
- if not assigned(mixerBuffer) then
+ ReallocMem(MixerBuffer, BufferSize);
+ if not assigned(MixerBuffer) then
Exit;
Lock();
- needsPacking := false;
+ NeedsPacking := false;
// mix streams to one stream
- for i := 0 to activeStreams.Count-1 do
+ for i := 0 to ActiveStreams.Count-1 do
begin
- if (activeStreams[i] = nil) then
+ if (ActiveStreams[i] = nil) then
begin
- needsPacking := true;
+ NeedsPacking := true;
continue;
end;
- stream := TGenericPlaybackStream(activeStreams[i]);
+ Stream := TGenericPlaybackStream(ActiveStreams[i]);
// fetch data from current stream
- size := stream.ReadData(mixerBuffer, BufSize);
- if (size > 0) then
+ Size := Stream.ReadData(MixerBuffer, BufferSize);
+ if (Size > 0) then
begin
// mix stream-data with mixer-buffer
// Note: use Self.appVolume instead of Self.Volume to prevent recursive locking
- Engine.MixBuffers(Buffer, mixerBuffer, size, appVolume * stream.Volume);
+ Engine.MixBuffers(Buffer, MixerBuffer, Size, AppVolume * Stream.Volume);
end;
end;
// remove nil-pointers from list
- if (needsPacking) then
+ if (NeedsPacking) then
begin
- activeStreams.Pack();
+ ActiveStreams.Pack();
end;
Unlock();
@@ -278,7 +309,7 @@ constructor TGenericPlaybackStream.Create(Engine: TAudioPlayback_SoftMixer);
begin
inherited Create();
Self.Engine := Engine;
- internalLock := SDL_CreateMutex();
+ InternalLock := SDL_CreateMutex();
SoundEffects := TList.Create;
Status := ssStopped;
Reset();
@@ -287,342 +318,419 @@ end;
destructor TGenericPlaybackStream.Destroy();
begin
Close();
- SDL_DestroyMutex(internalLock);
+ SDL_DestroyMutex(InternalLock);
FreeAndNil(SoundEffects);
inherited;
end;
procedure TGenericPlaybackStream.Reset();
begin
- // wake-up sleeping audio-callback threads in the ReadData()-function
- if assigned(decodeStream) then
- decodeStream.Close();
-
- // stop audio-callback on this stream
- Stop();
-
- // reset and/or free data
-
- Loop := false;
+ SourceStream := nil;
- // TODO: use DecodeStream.Unref() instead of Free();
- FreeAndNil(DecodeStream);
+ FreeAndNil(Converter);
FreeMem(SampleBuffer);
SampleBuffer := nil;
SampleBufferPos := 0;
- BytesAvail := 0;
-
- _volume := 0;
- SoundEffects.Clear;
- FadeInTime := 0;
-end;
+ SampleBufferSize := 0;
+ SampleBufferCount := 0;
-procedure TGenericPlaybackStream.Lock();
-begin
- SDL_mutexP(internalLock);
-end;
+ FreeMem(SourceBuffer);
+ SourceBuffer := nil;
+ SourceBufferSize := 0;
+ SourceBufferCount := 0;
-procedure TGenericPlaybackStream.Unlock();
-begin
- SDL_mutexV(internalLock);
-end;
+ NeedsRewind := false;
-class function TGenericPlaybackStream.ConvertAudioFormatToSDL(Format: TAudioSampleFormat; out SDLFormat: UInt16): boolean;
-begin
- case Format of
- asfU8: SDLFormat := AUDIO_U8;
- asfS8: SDLFormat := AUDIO_S8;
- asfU16LSB: SDLFormat := AUDIO_U16LSB;
- asfS16LSB: SDLFormat := AUDIO_S16LSB;
- asfU16MSB: SDLFormat := AUDIO_U16MSB;
- asfS16MSB: SDLFormat := AUDIO_S16MSB;
- asfU16: SDLFormat := AUDIO_U16;
- asfS16: SDLFormat := AUDIO_S16;
- else begin
- Result := false;
- Exit;
- end;
- end;
- Result := true;
+ fVolume := 0;
+ SoundEffects.Clear;
+ FadeInTime := 0;
end;
-function TGenericPlaybackStream.InitFormatConversion(): boolean;
-var
- srcFormat: UInt16;
- dstFormat: UInt16;
- srcFormatInfo: TAudioFormatInfo;
- dstFormatInfo: TAudioFormatInfo;
+function TGenericPlaybackStream.Open(SourceStream: TAudioSourceStream): boolean;
begin
Result := false;
- srcFormatInfo := DecodeStream.GetAudioFormatInfo();
- dstFormatInfo := Engine.GetAudioFormatInfo();
+ Close();
- if (not ConvertAudioFormatToSDL(srcFormatInfo.Format, srcFormat) or
- not ConvertAudioFormatToSDL(dstFormatInfo.Format, dstFormat)) then
- begin
- Log.LogError('Audio-format not supported by SDL', 'TSoftMixerPlaybackStream.InitFormatConversion');
+ if (not assigned(SourceStream)) then
Exit;
- end;
+ Self.SourceStream := SourceStream;
- if (SDL_BuildAudioCVT(@cvt,
- srcFormat, srcFormatInfo.Channels, Round(srcFormatInfo.SampleRate),
- dstFormat, dstFormatInfo.Channels, Round(dstFormatInfo.SampleRate)) = -1) then
+ if (not InitFormatConversion()) then
begin
- Log.LogError(SDL_GetError(), 'TSoftMixerPlaybackStream.InitFormatConversion');
+ // reset decode-stream so it will not be freed on destruction
+ Self.SourceStream := nil;
Exit;
end;
+ SourceBufferSize := SOURCE_BUFFER_FRAMES * SourceStream.GetAudioFormatInfo().FrameSize;
+ GetMem(SourceBuffer, SourceBufferSize);
+ fVolume := 1.0;
+
Result := true;
end;
-function TGenericPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
+procedure TGenericPlaybackStream.Close();
begin
- result := false;
+ // stop audio-callback on this stream
+ Stop();
+ // Note: PerformOnClose must be called before SourceStream is invalidated
+ PerformOnClose();
+ // and free data
Reset();
+end;
- if not assigned(decodeStream) then
- Exit;
- Self.DecodeStream := decodeStream;
- if not InitFormatConversion() then
- Exit;
-
- _volume := 1.0;
+procedure TGenericPlaybackStream.LockSampleBuffer();
+begin
+ SDL_mutexP(InternalLock);
+end;
- result := true;
+procedure TGenericPlaybackStream.UnlockSampleBuffer();
+begin
+ SDL_mutexV(InternalLock);
end;
-procedure TGenericPlaybackStream.Close();
+function TGenericPlaybackStream.InitFormatConversion(): boolean;
+var
+ SrcFormatInfo: TAudioFormatInfo;
+ DstFormatInfo: TAudioFormatInfo;
begin
- Reset();
+ Result := false;
+
+ SrcFormatInfo := SourceStream.GetAudioFormatInfo();
+ DstFormatInfo := GetAudioFormatInfo();
+
+ // TODO: selection should not be done here, use a factory (TAudioConverterFactory) instead
+ {$IF Defined(UseFFMpegResample)}
+ Converter := TAudioConverter_FFMpeg.Create();
+ {$ELSEIF Defined(UseSRCResample)}
+ Converter := TAudioConverter_SRC.Create();
+ {$ELSE}
+ Converter := TAudioConverter_SDL.Create();
+ {$IFEND}
+
+ Result := Converter.Init(SrcFormatInfo, DstFormatInfo);
end;
procedure TGenericPlaybackStream.Play();
var
- mixer: TAudioMixerStream;
+ Mixer: TAudioMixerStream;
begin
- if (status = ssPlaying) then
- begin
- // rewind
- if assigned(DecodeStream) then
- DecodeStream.Position := 0;
- end;
- status := ssPlaying;
-
- mixer := Engine.GetMixer();
- if (mixer <> nil) then
- mixer.AddStream(Self);
+ // only paused streams are not flushed
+ if (Status = ssPaused) then
+ NeedsRewind := false;
+
+ // 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
+ SetPosition(0);
+
+ // update status
+ Status := ssPlaying;
+
+ NeedsRewind := true;
+
+ // add this stream to the mixer
+ Mixer := Engine.GetMixer();
+ if (Mixer <> nil) then
+ Mixer.AddStream(Self);
end;
procedure TGenericPlaybackStream.FadeIn(Time: real; TargetVolume: single);
begin
FadeInTime := Trunc(Time * 1000);
FadeInStartTime := SDL_GetTicks();
- FadeInStartVolume := _volume;
+ FadeInStartVolume := fVolume;
FadeInTargetVolume := TargetVolume;
Play();
end;
procedure TGenericPlaybackStream.Pause();
var
- mixer: TAudioMixerStream;
+ Mixer: TAudioMixerStream;
begin
- status := ssPaused;
+ if (Status <> ssPlaying) then
+ Exit;
+
+ Status := ssPaused;
- mixer := Engine.GetMixer();
- if (mixer <> nil) then
- mixer.RemoveStream(Self);
+ Mixer := Engine.GetMixer();
+ if (Mixer <> nil) then
+ Mixer.RemoveStream(Self);
end;
procedure TGenericPlaybackStream.Stop();
var
- mixer: TAudioMixerStream;
+ Mixer: TAudioMixerStream;
begin
- if (status = ssStopped) then
+ if (Status = ssStopped) then
Exit;
- status := ssStopped;
-
- mixer := Engine.GetMixer();
- if (mixer <> nil) then
- mixer.RemoveStream(Self);
+ Status := ssStopped;
- // rewind (note: DecodeStream might be closed already, but this is not a problem)
- if assigned(DecodeStream) then
- DecodeStream.Position := 0;
+ Mixer := Engine.GetMixer();
+ if (Mixer <> nil) then
+ Mixer.RemoveStream(Self);
end;
function TGenericPlaybackStream.GetLoop(): boolean;
begin
- result := Loop;
+ if assigned(SourceStream) then
+ Result := SourceStream.Loop
+ else
+ Result := false;
end;
procedure TGenericPlaybackStream.SetLoop(Enabled: boolean);
begin
- Loop := Enabled;
+ if assigned(SourceStream) then
+ SourceStream.Loop := Enabled;
end;
function TGenericPlaybackStream.GetLength(): real;
begin
- if assigned(DecodeStream) then
- result := DecodeStream.Length
+ if assigned(SourceStream) then
+ Result := SourceStream.Length
else
- result := -1;
+ Result := -1;
+end;
+
+function TGenericPlaybackStream.GetLatency(): double;
+begin
+ Result := Engine.GetLatency();
end;
function TGenericPlaybackStream.GetStatus(): TStreamStatus;
begin
- result := status;
+ 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 TGenericPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
-var
- decodeBufSize: integer;
- sampleBufSize: integer;
- nBytesDecoded: integer;
- frameSize: integer;
- remFrameBytes: integer;
- copyCnt: integer;
- BytesNeeded: integer;
- i: integer;
+function TGenericPlaybackStream.GetAudioFormatInfo(): TAudioFormatInfo;
begin
- Result := -1;
+ Result := Engine.GetAudioFormatInfo();
+end;
- BytesNeeded := BufSize;
+procedure TGenericPlaybackStream.FlushBuffers();
+begin
+ SampleBufferCount := 0;
+ SampleBufferPos := 0;
+ SourceBufferCount := 0;
+end;
- // copy remaining data from the last call to the result-buffer
- if (BytesAvail > 0) then
+procedure TGenericPlaybackStream.ApplySoundEffects(Buffer: PChar; BufferSize: integer);
+var
+ i: integer;
+begin
+ for i := 0 to SoundEffects.Count-1 do
begin
- copyCnt := Min(BufSize, BytesAvail);
- Move(SampleBuffer[SampleBufferPos], Buffer[0], copyCnt);
- Dec(BytesAvail, copyCnt);
- Dec(BytesNeeded, copyCnt);
- if (BytesNeeded = 0) then
+ if (SoundEffects[i] <> nil) 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;
+ TSoundEffect(SoundEffects[i]).Callback(Buffer, BufferSize);
end;
end;
+end;
- if not assigned(DecodeStream) then
- Exit;
+function TGenericPlaybackStream.ReadData(Buffer: PChar; BufferSize: integer): integer;
+var
+ ConversionInputCount: integer;
+ ConversionOutputSize: integer; // max. number of converted data (= buffer size)
+ ConversionOutputCount: integer; // actual number of converted data
+ SourceSize: integer;
+ RequestedSourceSize: integer;
+ NeededSampleBufferSize: integer;
+ BytesNeeded, BytesAvail: integer;
+ SourceFormatInfo, OutputFormatInfo: TAudioFormatInfo;
+ SourceFrameSize, OutputFrameSize: integer;
+ SkipOutputCount: integer; // number of output-data bytes to skip
+ SkipSourceCount: integer; // number of source-data bytes to skip
+ FillCount: integer; // number of bytes to fill with padding data
+ CopyCount: integer;
+ PadFrame: PChar;
+ i: integer;
+begin
+ Result := -1;
- // 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);
+ // sanity check for the source-stream
+ if (not assigned(SourceStream)) then
+ Exit;
+
+ SkipOutputCount := 0;
+ SkipSourceCount := 0;
+ FillCount := 0;
+
+ SourceFormatInfo := SourceStream.GetAudioFormatInfo();
+ SourceFrameSize := SourceFormatInfo.FrameSize;
+ OutputFormatInfo := GetAudioFormatInfo();
+ OutputFrameSize := OutputFormatInfo.FrameSize;
+
+ // synchronize (adjust buffer size)
+ BytesNeeded := Synchronize(BufferSize, OutputFormatInfo);
+ if (BytesNeeded > BufferSize) then
+ begin
+ SkipOutputCount := BytesNeeded - BufferSize;
+ BytesNeeded := BufferSize;
+ end
+ else if (BytesNeeded < BufferSize) then
+ begin
+ FillCount := BufferSize - BytesNeeded;
+ end;
- Lock();
+ // lock access to sample-buffer
+ LockSampleBuffer();
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;
+ // skip sample-buffer data
+ SampleBufferPos := SampleBufferPos + SkipOutputCount;
+ // size of available bytes in SampleBuffer after skipping
+ SampleBufferCount := SampleBufferCount - SampleBufferPos;
+ // update byte skip-count
+ SkipOutputCount := -SampleBufferCount;
- // end-of-file reached -> stop playback
- if (DecodeStream.EOF) then
+ // now that we skipped all buffered data from the last pass, we have to skip
+ // data directly after fetching it from the source-stream.
+ if (SkipOutputCount > 0) then
begin
- Stop();
+ SampleBufferCount := 0;
+ // convert skip-count to source-format units and resize to a multiple of
+ // the source frame-size.
+ SkipSourceCount := Round((SkipOutputCount * OutputFormatInfo.GetRatio(SourceFormatInfo)) /
+ SourceFrameSize) * SourceFrameSize;
+ SkipOutputCount := 0;
end;
- // resample decoded data
- cvt.buf := PUint8(SampleBuffer);
- cvt.len := nBytesDecoded;
- if (SDL_ConvertAudio(@cvt) = -1) then
- Exit;
+ // copy data to front of buffer
+ if ((SampleBufferCount > 0) and (SampleBufferPos > 0)) then
+ Move(SampleBuffer[SampleBufferPos], SampleBuffer[0], SampleBufferCount);
+ SampleBufferPos := 0;
- SampleBufferCount := cvt.len_cvt;
+ // resize buffer to a reasonable size
+ if (BufferSize > SampleBufferCount) then
+ begin
+ // Note: use BufferSize instead of BytesNeeded to minimize the need for resizing
+ SampleBufferSize := BufferSize;
+ ReallocMem(SampleBuffer, SampleBufferSize);
+ if (not assigned(SampleBuffer)) then
+ Exit;
+ end;
- // apply effects
- for i := 0 to SoundEffects.Count-1 do
+ // fill sample-buffer (fetch and convert one block of source data per loop)
+ while (SampleBufferCount < BytesNeeded) do
begin
- if (SoundEffects[i] <> nil) then
+ // move remaining source data from the previous pass to front of buffer
+ if (SourceBufferCount > 0) then
+ begin
+ Move(SourceBuffer[SourceBufferSize-SourceBufferCount],
+ SourceBuffer[0],
+ SourceBufferCount);
+ end;
+
+ SourceSize := SourceStream.ReadData(
+ @SourceBuffer[SourceBufferCount], SourceBufferSize-SourceBufferCount);
+ // break on error (-1) or if no data is available (0), e.g. while seeking
+ if (SourceSize <= 0) then
+ begin
+ // if we do not have data -> exit
+ if (SourceBufferCount = 0) then
+ begin
+ FlushBuffers();
+ Exit;
+ end;
+ // if we have some data, stop retrieving data from the source stream
+ // and use the data we have so far
+ Break;
+ end;
+
+ SourceBufferCount := SourceBufferCount + SourceSize;
+
+ // end-of-file reached -> stop playback
+ if (SourceStream.EOF) then
begin
- TSoundEffect(SoundEffects[i]).Callback(SampleBuffer, SampleBufferCount);
+ if (Loop) then
+ SourceStream.Position := 0
+ else
+ Stop();
end;
+
+ if (SkipSourceCount > 0) then
+ begin
+ // skip data and update source buffer count
+ SourceBufferCount := SourceBufferCount - SkipSourceCount;
+ SkipSourceCount := -SourceBufferCount;
+ // continue with next pass if we skipped all data
+ if (SourceBufferCount <= 0) then
+ begin
+ SourceBufferCount := 0;
+ Continue;
+ end;
+ end;
+
+ // calc buffer size (might be bigger than actual resampled byte count)
+ ConversionOutputSize := Converter.GetOutputBufferSize(SourceBufferCount);
+ NeededSampleBufferSize := SampleBufferCount + ConversionOutputSize;
+
+ // resize buffer if necessary
+ if (SampleBufferSize < NeededSampleBufferSize) then
+ begin
+ SampleBufferSize := NeededSampleBufferSize;
+ ReallocMem(SampleBuffer, SampleBufferSize);
+ if (not assigned(SampleBuffer)) then
+ begin
+ FlushBuffers();
+ Exit;
+ end;
+ end;
+
+ // resample source data (Note: ConversionInputCount might be adjusted by Convert())
+ ConversionInputCount := SourceBufferCount;
+ ConversionOutputCount := Converter.Convert(
+ SourceBuffer, @SampleBuffer[SampleBufferCount], ConversionInputCount);
+ if (ConversionOutputCount = -1) then
+ begin
+ FlushBuffers();
+ Exit;
+ end;
+
+ // adjust sample- and source-buffer count by the number of converted bytes
+ SampleBufferCount := SampleBufferCount + ConversionOutputCount;
+ SourceBufferCount := SourceBufferCount - ConversionInputCount;
end;
- finally
- Unlock();
- end;
- BytesAvail := SampleBufferCount;
- SampleBufferPos := 0;
+ // apply effects
+ ApplySoundEffects(SampleBuffer, SampleBufferCount);
- // 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);
+ // copy data to result buffer
+ CopyCount := Min(BytesNeeded, SampleBufferCount);
+ Move(SampleBuffer[0], Buffer[BufferSize - BytesNeeded], CopyCount);
+ Dec(BytesNeeded, CopyCount);
+ SampleBufferPos := CopyCount;
- Result := BufSize - BytesNeeded;
-end;
+ // release buffer lock
+ finally
+ UnlockSampleBuffer();
+ end;
-(* TODO: libsamplerate support
-function TGenericPlaybackStream.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
+ // pad the buffer with the last frame if we are to fast
+ if (FillCount > 0) then
begin
- Log.LogError(src_strerror(src_error(convState)), 'TSoftMixerPlaybackStream.ReadData');
- Exit;
+ if (CopyCount >= OutputFrameSize) then
+ PadFrame := @Buffer[CopyCount-OutputFrameSize]
+ else
+ PadFrame := nil;
+ FillBufferWithFrame(@Buffer[CopyCount], FillCount,
+ PadFrame, OutputFrameSize);
end;
- src_float_to_short_array();
- //src_delete(convState);
+
+ // BytesNeeded now contains the number of remaining bytes we were not able to fetch
+ Result := BufferSize - BytesNeeded;
end;
-*)
-function TGenericPlaybackStream.GetPCMData(var data: TPCMData): Cardinal;
+function TGenericPlaybackStream.GetPCMData(var Data: TPCMData): Cardinal;
var
- nBytes: integer;
+ ByteCount: integer;
begin
Result := 0;
@@ -634,23 +742,23 @@ begin
end;
// zero memory
- FillChar(data, SizeOf(data), 0);
+ 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
+ LockSampleBuffer();
+ ByteCount := Min(SizeOf(Data), SampleBufferCount);
+ if (ByteCount > 0) then
begin
- Move(SampleBuffer[0], data, nBytes);
+ Move(SampleBuffer[0], Data, ByteCount);
end;
- Unlock();
+ UnlockSampleBuffer();
- Result := nBytes div SizeOf(TPCMStereoSample);
+ Result := ByteCount div SizeOf(TPCMStereoSample);
end;
-procedure TGenericPlaybackStream.GetFFTData(var data: TFFTData);
+procedure TGenericPlaybackStream.GetFFTData(var Data: TFFTData);
var
i: integer;
Frames: integer;
@@ -658,16 +766,14 @@ var
AudioFormat: TAudioFormatInfo;
begin
// only works with SInt16 and Float values at the moment
- AudioFormat := Engine.GetAudioFormatInfo();
+ AudioFormat := GetAudioFormatInfo();
DataIn := AllocMem(FFTSize * SizeOf(Single));
if (DataIn = nil) then
Exit;
- Lock();
+ LockSampleBuffer();
// 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
@@ -682,56 +788,84 @@ begin
DataIn[i] := PSingle(@SampleBuffer[i*AudioFormat.FrameSize])^;
end;
end;
- Unlock();
+ UnlockSampleBuffer();
WindowFunc(fwfHanning, FFTSize, DataIn);
- PowerSpectrum(FFTSize, DataIn, @data);
+ PowerSpectrum(FFTSize, DataIn, @Data);
FreeMem(DataIn);
// resize data to a 0..1 range
for i := 0 to High(TFFTData) do
begin
- data[i] := Sqrt(data[i]) / 100;
+ Data[i] := Sqrt(Data[i]) / 100;
end;
end;
-procedure TGenericPlaybackStream.AddSoundEffect(effect: TSoundEffect);
+procedure TGenericPlaybackStream.AddSoundEffect(Effect: TSoundEffect);
begin
- if (not assigned(effect)) then
+ if (not assigned(Effect)) then
Exit;
- Lock();
+
+ LockSampleBuffer();
// check if effect is already in list to avoid duplicates
- if (SoundEffects.IndexOf(Pointer(effect)) = -1) then
- SoundEffects.Add(Pointer(effect));
- Unlock();
+ if (SoundEffects.IndexOf(Pointer(Effect)) = -1) then
+ SoundEffects.Add(Pointer(Effect));
+ UnlockSampleBuffer();
end;
-procedure TGenericPlaybackStream.RemoveSoundEffect(effect: TSoundEffect);
+procedure TGenericPlaybackStream.RemoveSoundEffect(Effect: TSoundEffect);
begin
- Lock();
- SoundEffects.Remove(effect);
- Unlock();
+ LockSampleBuffer();
+ SoundEffects.Remove(Effect);
+ UnlockSampleBuffer();
end;
function TGenericPlaybackStream.GetPosition: real;
+var
+ BufferedTime: double;
begin
- if assigned(DecodeStream) then
- result := DecodeStream.Position
+ if assigned(SourceStream) then
+ begin
+ LockSampleBuffer();
+
+ // calc the time of source data that is buffered (in the SampleBuffer and SourceBuffer)
+ // but not yet outputed
+ BufferedTime := (SampleBufferCount - SampleBufferPos) / Engine.FormatInfo.BytesPerSec +
+ SourceBufferCount / SourceStream.GetAudioFormatInfo().BytesPerSec;
+ // and subtract it from the source position
+ Result := SourceStream.Position - BufferedTime;
+
+ UnlockSampleBuffer();
+ end
else
- result := -1;
+ begin
+ Result := -1;
+ end;
end;
procedure TGenericPlaybackStream.SetPosition(Time: real);
begin
- if assigned(DecodeStream) then
- DecodeStream.Position := Time;
+ if assigned(SourceStream) then
+ begin
+ LockSampleBuffer();
+
+ SourceStream.Position := Time;
+ if (Status = ssStopped) then
+ NeedsRewind := false;
+ // do not use outdated data
+ FlushBuffers();
+
+ AvgSyncDiff := -1;
+
+ UnlockSampleBuffer();
+ end;
end;
function TGenericPlaybackStream.GetVolume(): single;
var
FadeAmount: Single;
begin
- Lock();
+ LockSampleBuffer();
// adjust volume if fading is enabled
if (FadeInTime > 0) then
begin
@@ -741,32 +875,131 @@ begin
begin
// target reached -> stop fading
FadeInTime := 0;
- _volume := FadeInTargetVolume;
+ fVolume := FadeInTargetVolume;
end
else
begin
// fading in progress
- _volume := FadeAmount*FadeInTargetVolume + (1-FadeAmount)*FadeInStartVolume;
+ fVolume := FadeAmount*FadeInTargetVolume + (1-FadeAmount)*FadeInStartVolume;
end;
end;
// return current volume
- Result := _volume;
- Unlock();
+ Result := fVolume;
+ UnlockSampleBuffer();
end;
-procedure TGenericPlaybackStream.SetVolume(volume: single);
+procedure TGenericPlaybackStream.SetVolume(Volume: single);
begin
- Lock();
+ LockSampleBuffer();
// stop fading
FadeInTime := 0;
// clamp volume
- if (volume > 1.0) then
- _volume := 1.0
- else if (volume < 0) then
- _volume := 0
+ if (Volume > 1.0) then
+ fVolume := 1.0
+ else if (Volume < 0) then
+ fVolume := 0
else
- _volume := volume;
- Unlock();
+ fVolume := Volume;
+ UnlockSampleBuffer();
+end;
+
+
+{ TGenericVoiceStream }
+
+constructor TGenericVoiceStream.Create(Engine: TAudioPlayback_SoftMixer);
+begin
+ inherited Create();
+ Self.Engine := Engine;
+end;
+
+function TGenericVoiceStream.Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean;
+var
+ BufferSize: integer;
+begin
+ Result := false;
+
+ Close();
+
+ if (not inherited Open(ChannelMap, FormatInfo)) then
+ Exit;
+
+ // Note:
+ // - use Self.FormatInfo instead of FormatInfo as the latter one might have a
+ // channel size of 2.
+ // - the buffer-size must be a multiple of the FrameSize
+ BufferSize := (Ceil(MAX_VOICE_DELAY * Self.FormatInfo.BytesPerSec) div Self.FormatInfo.FrameSize) *
+ Self.FormatInfo.FrameSize;
+ VoiceBuffer := TRingBuffer.Create(BufferSize);
+
+ BufferLock := SDL_CreateMutex();
+
+
+ // create a matching playback stream for the voice-stream
+ PlaybackStream := TGenericPlaybackStream.Create(Engine);
+ // link voice- and playback-stream
+ if (not PlaybackStream.Open(Self)) then
+ begin
+ PlaybackStream.Free;
+ Exit;
+ end;
+
+ // start voice passthrough
+ PlaybackStream.Play();
+
+ Result := true;
+end;
+
+procedure TGenericVoiceStream.Close();
+begin
+ // stop and free the playback stream
+ FreeAndNil(PlaybackStream);
+
+ // free data
+ FreeAndNil(VoiceBuffer);
+ if (BufferLock <> nil) then
+ SDL_DestroyMutex(BufferLock);
+
+ inherited Close();
+end;
+
+procedure TGenericVoiceStream.WriteData(Buffer: PChar; BufferSize: integer);
+begin
+ // lock access to buffer
+ SDL_mutexP(BufferLock);
+ try
+ if (VoiceBuffer = nil) then
+ Exit;
+ VoiceBuffer.Write(Buffer, BufferSize);
+ finally
+ SDL_mutexV(BufferLock);
+ end;
+end;
+
+function TGenericVoiceStream.ReadData(Buffer: PChar; BufferSize: integer): integer;
+begin
+ Result := -1;
+
+ // lock access to buffer
+ SDL_mutexP(BufferLock);
+ try
+ if (VoiceBuffer = nil) then
+ Exit;
+ Result := VoiceBuffer.Read(Buffer, BufferSize);
+ finally
+ SDL_mutexV(BufferLock);
+ end;
+end;
+
+function TGenericVoiceStream.IsEOF(): boolean;
+begin
+ SDL_mutexP(BufferLock);
+ Result := (VoiceBuffer = nil);
+ SDL_mutexV(BufferLock);
+end;
+
+function TGenericVoiceStream.IsError(): boolean;
+begin
+ Result := false;
end;
@@ -774,7 +1007,7 @@ end;
function TAudioPlayback_SoftMixer.InitializePlayback: boolean;
begin
- result := false;
+ Result := false;
//Log.LogStatus('InitializePlayback', 'UAudioPlayback_SoftMixer');
@@ -786,7 +1019,7 @@ begin
if(not StartAudioPlaybackEngine()) then
Exit;
- result := true;
+ Result := true;
end;
function TAudioPlayback_SoftMixer.FinalizePlayback: boolean;
@@ -802,9 +1035,9 @@ begin
Result := true;
end;
-procedure TAudioPlayback_SoftMixer.AudioCallback(buffer: PChar; size: integer);
+procedure TAudioPlayback_SoftMixer.AudioCallback(Buffer: PChar; Size: integer);
begin
- MixerStream.ReadData(buffer, size);
+ MixerStream.ReadData(Buffer, Size);
end;
function TAudioPlayback_SoftMixer.GetMixer(): TAudioMixerStream;
@@ -817,38 +1050,26 @@ begin
Result := FormatInfo;
end;
-function TAudioPlayback_SoftMixer.OpenStream(const Filename: String): TAudioPlaybackStream;
+function TAudioPlayback_SoftMixer.CreatePlaybackStream(): TAudioPlaybackStream;
+begin
+ Result := TGenericPlaybackStream.Create(Self);
+end;
+
+function TAudioPlayback_SoftMixer.CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
var
- decodeStream: TAudioDecodeStream;
- playbackStream: TGenericPlaybackStream;
+ VoiceStream: TGenericVoiceStream;
begin
Result := nil;
- if (AudioDecoder = nil) then
- Exit;
-
- decodeStream := AudioDecoder.Open(Filename);
- if not assigned(decodeStream) then
+ // create a voice stream
+ VoiceStream := TGenericVoiceStream.Create(Self);
+ if (not VoiceStream.Open(ChannelMap, FormatInfo)) then
begin
- Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudioPlayback_SoftMixer');
+ VoiceStream.Free;
Exit;
end;
- playbackStream := TGenericPlaybackStream.Create(Self);
- if (not assigned(playbackStream)) then
- begin
- FreeAndNil(decodeStream);
- Exit;
- end;
-
- if (not playbackStream.SetDecodeStream(decodeStream)) then
- begin
- FreeAndNil(playbackStream);
- FreeAndNil(decodeStream);
- Exit;
- end;
-
- result := playbackStream;
+ Result := VoiceStream;
end;
procedure TAudioPlayback_SoftMixer.SetAppVolume(Volume: single);
@@ -857,47 +1078,46 @@ begin
MixerStream.Volume := Volume;
end;
-procedure TAudioPlayback_SoftMixer.MixBuffers(dst, src: PChar; size: Cardinal; volume: Single);
+procedure TAudioPlayback_SoftMixer.MixBuffers(DstBuffer, SrcBuffer: PChar; Size: Cardinal; Volume: Single);
var
SampleIndex: Cardinal;
SampleInt: Integer;
SampleFlt: Single;
begin
-
- // TODO: optimize this code, e.g. with assembler (MMX)
-
SampleIndex := 0;
case FormatInfo.Format of
asfS16:
begin
- while (SampleIndex < size) do
+ while (SampleIndex < Size) do
begin
// apply volume and sum with previous mixer value
- SampleInt := PSmallInt(@dst[SampleIndex])^ + Round(PSmallInt(@src[SampleIndex])^ * volume);
+ SampleInt := PSmallInt(@DstBuffer[SampleIndex])^ +
+ Round(PSmallInt(@SrcBuffer[SampleIndex])^ * Volume);
// clip result
if (SampleInt > High(SmallInt)) then
SampleInt := High(SmallInt)
else if (SampleInt < Low(SmallInt)) then
SampleInt := Low(SmallInt);
// assign result
- PSmallInt(@dst[SampleIndex])^ := SampleInt;
+ PSmallInt(@DstBuffer[SampleIndex])^ := SampleInt;
// increase index by one sample
Inc(SampleIndex, SizeOf(SmallInt));
end;
end;
asfFloat:
begin
- while (SampleIndex < size) do
+ while (SampleIndex < Size) do
begin
// apply volume and sum with previous mixer value
- SampleFlt := PSingle(@dst[SampleIndex])^ + PSingle(@src[SampleIndex])^ * volume;
+ SampleFlt := PSingle(@DstBuffer[SampleIndex])^ +
+ PSingle(@SrcBuffer[SampleIndex])^ * Volume;
// clip result
if (SampleFlt > 1.0) then
SampleFlt := 1.0
else if (SampleFlt < -1.0) then
SampleFlt := -1.0;
// assign result
- PSingle(@dst[SampleIndex])^ := SampleFlt;
+ PSingle(@DstBuffer[SampleIndex])^ := SampleFlt;
// increase index by one sample
Inc(SampleIndex, SizeOf(Single));
end;