aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code
diff options
context:
space:
mode:
Diffstat (limited to 'Game/Code')
-rw-r--r--Game/Code/Classes/UAudioDecoder_FFMpeg.pas170
-rw-r--r--Game/Code/Classes/UAudioInput_Bass.pas85
-rw-r--r--Game/Code/Classes/UAudioInput_Portaudio.pas2
-rw-r--r--Game/Code/Classes/UAudioPlayback_Portaudio.pas881
-rw-r--r--Game/Code/Classes/UAudioPlayback_SDL.pas113
-rw-r--r--Game/Code/Classes/UAudioPlayback_SoftMixer.pas809
-rw-r--r--Game/Code/Classes/UMusic.pas34
-rw-r--r--Game/Code/UltraStar.dpr8
-rw-r--r--Game/Code/switches.inc4
9 files changed, 1262 insertions, 844 deletions
diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
index 646e9eef..1bb9208a 100644
--- a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
+++ b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
@@ -22,18 +22,12 @@ interface
uses
Classes,
- {$IFDEF win32}
- windows,
- {$ENDIF}
SysUtils,
UMusic;
implementation
uses
- {$ifndef win32}
- libc,
- {$endif}
UIni,
UMain,
avcodec, // FFMpeg Audio file decoding
@@ -84,8 +78,8 @@ type
_EOF: boolean; // end-of-stream flag
_EOF_lock : PSDL_Mutex;
- lock : PSDL_Mutex;
- resumeCond : PSDL_Cond;
+ internalLock : PSDL_Mutex;
+ resumeCond : PSDL_Cond;
quitRequest : boolean;
@@ -96,6 +90,8 @@ type
parseThread: PSDL_Thread;
packetQueue: TPacketQueue;
+ formatInfo : TAudioFormatInfo;
+
// FFMpeg internal data
pFormatCtx : PAVFormatContext;
pCodecCtx : PAVCodecContext;
@@ -113,6 +109,11 @@ type
audio_buf_size : cardinal;
audio_buf : TAudioBuffer;
+ procedure Lock(); inline;
+ procedure Unlock(); inline;
+ function GetLockMutex(): PSDL_Mutex; inline;
+
+ procedure ParseAudio();
function DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
procedure SetEOF(state: boolean);
public
@@ -143,7 +144,7 @@ type
function Open(const Filename: string): TAudioDecodeStream;
end;
-function ParseAudio(streamPtr: Pointer): integer; cdecl; forward;
+function DecodeThreadMain(streamPtr: Pointer): integer; cdecl; forward;
var
singleton_AudioDecoderFFMpeg : IAudioDecoder;
@@ -173,29 +174,42 @@ begin
Self.ffmpegStreamIndex := ffmpegStreamIndex;
Self.ffmpegStream := ffmpegStream;
+ formatInfo := TAudioFormatInfo.Create(
+ pCodecCtx^.channels,
+ pCodecCtx^.sample_rate,
+ // pCodecCtx^.sample_fmt not yet used by FFMpeg -> use FFMpeg's standard format
+ asfS16
+ );
+
_EOF := false;
_EOF_lock := SDL_CreateMutex();
- lock := SDL_CreateMutex();
+ internalLock := SDL_CreateMutex();
resumeCond := SDL_CreateCond();
- parseThread := SDL_CreateThread(@ParseAudio, Self);
+ parseThread := SDL_CreateThread(@DecodeThreadMain, Self);
end;
destructor TFFMpegDecodeStream.Destroy();
begin
- //Close();
- //packetQueue.Free();
+ Close();
inherited;
end;
procedure TFFMpegDecodeStream.Close();
+var
+ status: integer;
begin
- // TODO: abort thread
- //quitRequest := true;
- //SDL_WaitThread(parseThread, nil);
+ Lock();
+ quitRequest := true;
+ SDL_CondSignal(resumeCond);
+ Unlock();
+
+ if (parseThread <> nil) then
+ begin
+ SDL_WaitThread(parseThread, status);
+ end;
- (*
// Close the codec
if (pCodecCtx <> nil) then
begin
@@ -209,26 +223,40 @@ begin
av_close_input_file(pFormatCtx);
pFormatCtx := nil;
end;
- *)
+
+ FreeAndNil(packetQueue);
+ FreeAndNil(formatInfo);
+end;
+
+procedure TFFMpegDecodeStream.Lock();
+begin
+ SDL_mutexP(internalLock);
+end;
+
+procedure TFFMpegDecodeStream.Unlock();
+begin
+ SDL_mutexV(internalLock);
+end;
+
+function TFFMpegDecodeStream.GetLockMutex(): PSDL_Mutex;
+begin
+ Result := internalLock;
end;
function TFFMpegDecodeStream.GetLength(): real;
begin
- result := pFormatCtx^.duration / AV_TIME_BASE;
+ Result := pFormatCtx^.duration / AV_TIME_BASE;
end;
function TFFMpegDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo;
begin
- result.Channels := pCodecCtx^.channels;
- result.SampleRate := pCodecCtx^.sample_rate;
- //result.Format := pCodecCtx^.sample_fmt; // sample_fmt not yet used by FFMpeg
- result.Format := asfS16; // use FFMpeg's standard format
+ Result := formatInfo;
end;
function TFFMpegDecodeStream.IsEOF(): boolean;
begin
SDL_mutexP(_EOF_lock);
- result := _EOF;
+ Result := _EOF;
SDL_mutexV(_EOF_lock);
end;
@@ -251,89 +279,94 @@ procedure TFFMpegDecodeStream.SetPosition(Time: real);
var
bytes: integer;
begin
- SDL_mutexP(lock);
+ Lock();
seekPos := Trunc(Time * AV_TIME_BASE);
// FIXME: seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0
seekFlags := 0;//AVSEEK_FLAG_BACKWARD;
seekRequest := true;
SDL_CondSignal(resumeCond);
- SDL_mutexV(lock);
+ Unlock();
end;
-function ParseAudio(streamPtr: Pointer): integer; cdecl;
+function DecodeThreadMain(streamPtr: Pointer): integer; cdecl;
var
- packet: TAVPacket;
stream: TFFMpegDecodeStream;
+begin
+ stream := TFFMpegDecodeStream(streamPtr);
+ stream.ParseAudio();
+ result := 0;
+end;
+
+procedure TFFMpegDecodeStream.ParseAudio();
+var
+ packet: TAVPacket;
seekTarget: int64;
eofState: boolean;
pbIOCtx: PByteIOContext;
begin
- stream := TFFMpegDecodeStream(streamPtr);
eofState := false;
while (true) do
begin
- //SafeWriteLn('Hallo');
-
- SDL_mutexP(stream.lock);
+ Lock();
// wait if end-of-file reached
if (eofState) then
begin
- if (not (stream.seekRequest or stream.quitRequest)) then
+ if (not (seekRequest or quitRequest)) then
begin
// signal end-of-file
- stream.packetQueue.put(@EOFPacket);
+ packetQueue.put(@EOFPacket);
// wait for reuse or destruction of stream
repeat
- SDL_CondWait(stream.resumeCond, stream.lock);
- until (stream.seekRequest or stream.quitRequest);
+ SDL_CondWait(resumeCond, GetLockMutex());
+ until (seekRequest or quitRequest);
end;
eofState := false;
- stream.SetEOF(false);
+ SetEOF(false);
end;
- if (stream.quitRequest) then
+ if (quitRequest) then
begin
break;
end;
// handle seek-request
- if(stream.seekRequest) then
+ if(seekRequest) then
begin
// TODO: Do we need this?
// The position is converted to AV_TIME_BASE and then to the stream-specific base.
// Why not convert to the stream-specific one from the beginning.
- seekTarget := av_rescale_q(stream.seekPos, AV_TIME_BASE_Q, stream.ffmpegStream^.time_base);
- if(av_seek_frame(stream.pFormatCtx, stream.ffmpegStreamIndex,
- seekTarget, stream.seekFlags) < 0) then
+ seekTarget := av_rescale_q(seekPos, AV_TIME_BASE_Q, ffmpegStream^.time_base);
+ if(av_seek_frame(pFormatCtx, ffmpegStreamIndex,
+ seekTarget, seekFlags) < 0) then
begin
// this will crash in FPC due to a bug
- //Log.LogStatus({stream.pFormatCtx^.filename +} ': error while seeking', 'UAudioDecoder_FFMpeg');
+ //Log.LogStatus({pFormatCtx^.filename +} ': error while seeking', 'UAudioDecoder_FFMpeg');
end
else
begin
- stream.packetQueue.Flush();
- stream.packetQueue.Put(@FlushPacket);
+ packetQueue.Flush();
+ packetQueue.Put(@FlushPacket);
end;
- stream.seekRequest := false;
+ seekRequest := false;
end;
- SDL_mutexV(stream.lock);
+ Unlock();
- if(stream.packetQueue.size > MAX_AUDIOQ_SIZE) then
+ if(packetQueue.size > MAX_AUDIOQ_SIZE) then
begin
SDL_Delay(10);
continue;
end;
- if(av_read_frame(stream.pFormatCtx, packet) < 0) then
+ if(av_read_frame(pFormatCtx, packet) < 0) then
begin
// check for end-of-file (eof is not an error)
{$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)}
- pbIOCtx := stream.pFormatCtx^.pb;
+ pbIOCtx := pFormatCtx^.pb;
{$ELSE}
- pbIOCtx := @stream.pFormatCtx^.pb;
+ pbIOCtx := @pFormatCtx^.pb;
{$IFEND}
if(url_feof(pbIOCtx) <> 0) then
@@ -362,20 +395,16 @@ begin
//SafeWriteLn( 'ffmpeg - av_read_frame' );
- if(packet.stream_index = stream.ffmpegStreamIndex) then
+ if(packet.stream_index = ffmpegStreamIndex) then
begin
//SafeWriteLn( 'packet_queue_put' );
- stream.packetQueue.put(@packet);
+ packetQueue.put(@packet);
end
else
begin
av_free_packet(@packet);
end;
end;
-
- SafeWriteLn('Done: ' + inttostr(stream.packetQueue.nbPackets));
-
- result := 0;
end;
function TFFMpegDecodeStream.DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
@@ -409,7 +438,7 @@ begin
if(len1 < 0) then
begin
- // if error, skip frame
+ // if error, skip frame
SafeWriteLn( 'Skip audio frame' );
audio_pkt_size := 0;
break;
@@ -420,11 +449,11 @@ begin
if (data_size <= 0) then
begin
- // No data yet, get more frames
+ // no data yet, get more frames
continue;
end;
- // We have data, return it and come back for more later
+ // we have data, return it and come back for more later
result := data_size;
exit;
end;
@@ -469,7 +498,6 @@ var
outStream : TFFMpegDecodeStream;
len1,
audio_size : integer;
- pSrc : Pointer;
len : integer;
begin
len := BufSize;
@@ -482,13 +510,13 @@ begin
while (len > 0) do begin
if (audio_buf_index >= audio_buf_size) then
begin
- // We have already sent all our data; get more
+ // we have already sent all our data; get more
audio_size := DecodeFrame(audio_buf, sizeof(TAudioBuffer));
//SafeWriteLn('audio_decode_frame : '+ inttostr(audio_size));
if(audio_size < 0) then
begin
- // If error, output silence
+ // if error, output silence
audio_buf_size := 1024;
FillChar(audio_buf, audio_buf_size, #0);
//SafeWriteLn( 'Silence' );
@@ -504,15 +532,10 @@ begin
if (len1 > len) then
len1 := len;
- pSrc := PChar(@audio_buf) + audio_buf_index;
- {$ifdef WIN32}
- CopyMemory(Buffer, pSrc , len1);
- {$else}
- memcpy(Buffer, pSrc , len1);
- {$endif}
+ Move(audio_buf[audio_buf_index], Buffer[0], len1);
Dec(len, len1);
- Inc(PChar(Buffer), len1);
+ Inc(Buffer, len1);
Inc(audio_buf_index, len1);
end;
@@ -550,7 +573,7 @@ var
streamIndex: integer;
stream : PAVStream;
begin
- // Find the first audio stream
+ // find the first audio stream
streamIndex := -1;
for i := 0 to pFormatCtx^.nb_streams-1 do
@@ -588,11 +611,11 @@ begin
exit;
end;
- // Open audio file
+ // open audio file
if (av_open_input_file(pFormatCtx, PChar(Filename), nil, 0, nil) > 0) then
exit;
- // Retrieve stream information
+ // retrieve stream information
if (av_find_stream_info(pFormatCtx) < 0) then
exit;
@@ -641,6 +664,7 @@ end;
destructor TPacketQueue.Destroy();
begin
+ Flush();
SDL_DestroyMutex(mutex);
SDL_DestroyCond(cond);
inherited;
diff --git a/Game/Code/Classes/UAudioInput_Bass.pas b/Game/Code/Classes/UAudioInput_Bass.pas
index 6d661258..f99b0885 100644
--- a/Game/Code/Classes/UAudioInput_Bass.pas
+++ b/Game/Code/Classes/UAudioInput_Bass.pas
@@ -144,43 +144,58 @@ begin
if (Descr = nil) then
break;
- SetLength(AudioInputProcessor.Device, DeviceIndex+1);
-
- // TODO: free object on termination
- BassDevice := TBassInputDevice.Create();
- AudioInputProcessor.Device[DeviceIndex] := BassDevice;
-
- BassDevice.DeviceIndex := DeviceIndex;
- BassDevice.BassDeviceID := BassDeviceID;
- BassDevice.Description := UnifyDeviceName(Descr, DeviceIndex);
-
- // get input sources
- SourceIndex := 0;
- BASS_RecordInit(BassDeviceID);
- BassDevice.MicInput := 0;
-
- // process each input
- while true do
+ // try to intialize the device
+ if not BASS_RecordInit(BassDeviceID) then
begin
- SourceName := BASS_RecordGetInputName(SourceIndex);
- if (SourceName = nil) then
- break;
-
- SetLength(BassDevice.Source, SourceIndex+1);
- BassDevice.Source[SourceIndex].Name :=
- UnifyDeviceSourceName(SourceName, BassDevice.Description);
-
- // set mic index
- Flags := BASS_RecordGetInput(SourceIndex);
- if ((Flags and BASS_INPUT_TYPE_MIC) <> 0) then
- BassDevice.MicInput := SourceIndex;
-
- Inc(SourceIndex);
+ Log.LogStatus('Failed to initialize BASS Capture-Device['+inttostr(BassDeviceID)+']',
+ 'TAudioInput_Bass.InitializeRecord');
+ end
+ else
+ begin
+ SetLength(AudioInputProcessor.Device, DeviceIndex+1);
+
+ // TODO: free object on termination
+ BassDevice := TBassInputDevice.Create();
+ AudioInputProcessor.Device[DeviceIndex] := BassDevice;
+
+ BassDevice.DeviceIndex := DeviceIndex;
+ BassDevice.BassDeviceID := BassDeviceID;
+ BassDevice.Description := UnifyDeviceName(Descr, DeviceIndex);
+
+ // get input sources
+ SourceIndex := 0;
+ BassDevice.MicInput := 0;
+
+ // process each input
+ while true do
+ begin
+ SourceName := BASS_RecordGetInputName(SourceIndex);
+ if (SourceName = nil) then
+ break;
+
+ SetLength(BassDevice.Source, SourceIndex+1);
+ BassDevice.Source[SourceIndex].Name :=
+ UnifyDeviceSourceName(SourceName, BassDevice.Description);
+
+ // set mic index
+ Flags := BASS_RecordGetInput(SourceIndex);
+ if ((Flags <> -1) and ((Flags and BASS_INPUT_TYPE_MIC) <> 0)) then
+ begin
+ BassDevice.MicInput := SourceIndex;
+ end;
+
+ Inc(SourceIndex);
+ end;
+
+ //Writeln('BASS_RecordFree');
+ // FIXME: this call hangs in FPC (windows) every 2nd time USDX is called.
+ // Maybe because the sound-device was not released properly?
+ BASS_RecordFree;
+ //Writeln('BASS_RecordFree - Done');
+
+ Inc(DeviceIndex);
end;
-
- BASS_RecordFree;
-
- Inc(DeviceIndex);
+
Inc(BassDeviceID);
end;
diff --git a/Game/Code/Classes/UAudioInput_Portaudio.pas b/Game/Code/Classes/UAudioInput_Portaudio.pas
index 753c69f6..665f1972 100644
--- a/Game/Code/Classes/UAudioInput_Portaudio.pas
+++ b/Game/Code/Classes/UAudioInput_Portaudio.pas
@@ -63,7 +63,7 @@ var
*}
const
paDefaultApi = -1;
-var
+const
ApiPreferenceOrder:
{$IF Defined(WIN32)}
// Note1: Portmixer has no mixer support for paASIO and paWASAPI at the moment
diff --git a/Game/Code/Classes/UAudioPlayback_Portaudio.pas b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
index 59571d3d..96fff957 100644
--- a/Game/Code/Classes/UAudioPlayback_Portaudio.pas
+++ b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
@@ -1,728 +1,153 @@
-unit UAudioPlayback_Portaudio;
-
-interface
-
-{$IFDEF FPC}
- {$MODE Delphi}
-{$ENDIF}
-
-{$I switches.inc}
-
-
-uses
- Classes,
- SysUtils,
- UMusic;
-
-implementation
-
-uses
- {$IFNDEF Win32}
- libc,
- {$ENDIF}
- sdl,
- portaudio,
- ULog,
- UIni,
- UMain;
-
-type
- TPortaudioPlaybackStream = class(TAudioPlaybackStream)
- private
- Status: TStreamStatus;
- Loop: boolean;
-
- _volume: integer;
-
- procedure Reset();
- public
- DecodeStream: TAudioDecodeStream;
-
- constructor Create();
- 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;
- end;
-
-type
- TAudioMixerStream = class
- private
- activeStreams: TList;
- mixerBuffer: PChar;
- internalLock: PSDL_Mutex;
-
- _volume: integer;
-
- procedure Lock(); inline;
- procedure Unlock(); inline;
-
- 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;
-
-type
- TAudioPlayback_Portaudio = class( TInterfacedObject, IAudioPlayback )
- private
- MusicStream: TPortaudioPlaybackStream;
-
- MixerStream: TAudioMixerStream;
- paStream: PPaStream;
-
- FrameSize: integer;
-
- function InitializePortaudio(): boolean;
- function StartPortaudioStream(): boolean;
-
- function InitializeSDLAudio(): boolean;
- function StartSDLAudioStream(): boolean;
- procedure StopSDLAudioStream();
- public
- function GetName: String;
-
- function InitializePlayback(): boolean;
- destructor Destroy; override;
-
- function Load(const Filename: String): TPortaudioPlaybackStream;
-
- procedure SetVolume(Volume: integer);
- procedure SetMusicVolume(Volume: integer);
- procedure SetLoop(Enabled: boolean);
- function Open(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;
-
- // Sounds
- function OpenSound(const Filename: String): TAudioPlaybackStream;
- procedure PlaySound(stream: TAudioPlaybackStream);
- procedure StopSound(stream: TAudioPlaybackStream);
- end;
-
-
-function AudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
- timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
- userData: Pointer): Integer; cdecl; forward;
-
-var
- singleton_AudioPlaybackPortaudio : IAudioPlayback;
-
-
-{ 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;
-
-procedure TAudioMixerStream.RemoveStream(stream: TAudioPlaybackStream);
-begin
- Lock();
- activeStreams.Remove(Pointer(stream));
- Unlock();
-end;
-
-function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer;
-var
- i: integer;
- size: integer;
- stream: TPortaudioPlaybackStream;
- appVolume: single;
-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();
-
- //writeln('Mix: ' + inttostr(activeStreams.Count));
-
- // use _volume instead of Volume to prevent recursive locking
- appVolume := _volume / 100 * SDL_MIX_MAXVOLUME;
-
- for i := 0 to activeStreams.Count-1 do
- begin
- stream := TPortaudioPlaybackStream(activeStreams[i]);
- if (stream.GetStatus() = ssPlaying) then
- begin
- // 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;
- end;
-
- Unlock();
-end;
-
-
-{ TPortaudioPlaybackStream }
-
-constructor TPortaudioPlaybackStream.Create();
-begin
- inherited Create();
- Reset();
-end;
-
-destructor TPortaudioPlaybackStream.Destroy();
-begin
- Close();
- inherited Destroy();
-end;
-
-procedure TPortaudioPlaybackStream.Reset();
-begin
- Status := ssStopped;
- Loop := false;
- DecodeStream := nil;
- _volume := 0;
-end;
-
-function TPortaudioPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
-begin
- result := false;
-
- Reset();
-
- if not assigned(decodeStream) then
- Exit;
- Self.DecodeStream := decodeStream;
-
- _volume := 100;
-
- result := true;
-end;
-
-procedure TPortaudioPlaybackStream.Close();
-begin
- Reset();
-end;
-
-procedure TPortaudioPlaybackStream.Play();
-begin
- if (status <> ssPaused) then
- begin
- // rewind
- if assigned(DecodeStream) then
- DecodeStream.Position := 0;
- end;
- status := ssPlaying;
- //MixerStream.AddStream(Self);
-end;
-
-procedure TPortaudioPlaybackStream.Pause();
-begin
- status := ssPaused;
-end;
-
-procedure TPortaudioPlaybackStream.Stop();
-begin
- status := ssStopped;
-end;
-
-function TPortaudioPlaybackStream.IsLoaded(): boolean;
-begin
- result := assigned(DecodeStream);
-end;
-
-function TPortaudioPlaybackStream.GetLoop(): boolean;
-begin
- result := Loop;
-end;
-
-procedure TPortaudioPlaybackStream.SetLoop(Enabled: boolean);
-begin
- Loop := Enabled;
-end;
-
-function TPortaudioPlaybackStream.GetLength(): real;
-begin
- if assigned(DecodeStream) then
- result := DecodeStream.Length
- else
- result := -1;
-end;
-
-function TPortaudioPlaybackStream.GetStatus(): TStreamStatus;
-begin
- result := status;
-end;
-
-function TPortaudioPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
-begin
- if not assigned(DecodeStream) then
- begin
- result := -1;
- Exit;
- end;
- result := DecodeStream.ReadData(Buffer, BufSize);
- // end-of-file reached -> stop playback
- if (DecodeStream.EOF) then
- begin
- status := ssStopped;
- end;
-end;
-
-function TPortaudioPlaybackStream.GetPosition: real;
-begin
- if assigned(DecodeStream) then
- result := DecodeStream.Position
- else
- result := -1;
-end;
-
-procedure TPortaudioPlaybackStream.SetPosition(Time: real);
-begin
- if assigned(DecodeStream) then
- DecodeStream.Position := Time;
-end;
-
-function TPortaudioPlaybackStream.GetVolume(): integer;
-begin
- result := _volume;
-end;
-
-procedure TPortaudioPlaybackStream.SetVolume(volume: integer);
-begin
- // clamp volume
- if (volume > 100) then
- _volume := 100
- else if (volume < 0) then
- _volume := 0
- else
- _volume := volume;
-end;
-
-
-{ TAudioPlayback_Portaudio }
-
-function AudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
- timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
- userData: Pointer): Integer; cdecl;
-var
- playback: TAudioPlayback_Portaudio;
-begin
- playback := TAudioPlayback_Portaudio(userData);
- with playback do
- begin
- MixerStream.ReadData(output, frameCount * FrameSize);
- end;
- result := paContinue;
-end;
-
-procedure SDLAudioCallback(userdata: Pointer; stream: PChar; len: integer); cdecl;
-var
- playback: TAudioPlayback_Portaudio;
-begin
- playback := TAudioPlayback_Portaudio(userdata);
- with playback do
- begin
- MixerStream.ReadData(stream, len);
- end;
-end;
-
-function TAudioPlayback_Portaudio.GetName: String;
-begin
- result := 'Portaudio_Playback';
-end;
-
-function TAudioPlayback_Portaudio.InitializePortaudio(): boolean;
-var
- paApi : TPaHostApiIndex;
- paApiInfo : PPaHostApiInfo;
- paOutParams : TPaStreamParameters;
- paOutDevice : TPaDeviceIndex;
- paOutDeviceInfo : PPaDeviceInfo;
- err : TPaError;
-begin
- result := false;
-
- Pa_Initialize();
-
- // FIXME: determine automatically
- {$IFDEF WIN32}
- paApi := Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
- {$ELSE}
- paApi := Pa_HostApiTypeIdToHostApiIndex(paALSA);
- {$ENDIF}
- if (paApi < 0) then
- begin
- Log.LogStatus('Pa_HostApiTypeIdToHostApiIndex: '+Pa_GetErrorText(paApi), 'UAudioPlayback_Portaudio');
- exit;
- end;
-
- paApiInfo := Pa_GetHostApiInfo(paApi);
- paOutDevice := paApiInfo^.defaultOutputDevice;
- paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice);
-
- with paOutParams do begin
- device := paOutDevice;
- channelCount := 2;
- sampleFormat := paInt16;
- suggestedLatency := paOutDeviceInfo^.defaultHighOutputLatency;
- hostApiSpecificStreamInfo := nil;
- end;
-
- // set the size of one audio frame (2channel 16bit uint sample)
- FrameSize := 2 * sizeof(Smallint);
-
- err := Pa_OpenStream(paStream, nil, @paOutParams, 44100,
- paFramesPerBufferUnspecified,
- paNoFlag, @AudioCallback, Self);
- if(err <> paNoError) then begin
- Log.LogStatus('Pa_OpenStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
- exit;
- end;
-
- Log.LogStatus('Opened audio device', 'UAudioPlayback_Portaudio');
-
- result := true;
-end;
-
-function TAudioPlayback_Portaudio.StartPortaudioStream(): boolean;
-var
- err: TPaError;
-begin
- result := false;
-
- err := Pa_StartStream(paStream);
- if(err <> paNoError) then
- begin
- Log.LogStatus('Pa_StartStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
- exit;
- end;
-
- result := true;
-end;
-
-function TAudioPlayback_Portaudio.InitializeSDLAudio(): boolean;
-var
- desiredAudioSpec, obtainedAudioSpec: TSDL_AudioSpec;
- err: integer;
-begin
- result := false;
-
- SDL_InitSubSystem(SDL_INIT_AUDIO);
-
- FillChar(desiredAudioSpec, sizeof(desiredAudioSpec), 0);
- with desiredAudioSpec do
- begin
- freq := 44100;
- format := AUDIO_S16SYS;
- channels := 2;
- samples := 1024; // latency: 23 ms
- callback := @SDLAudioCallback;
- userdata := Self;
- end;
-
- // set the size of one audio frame (2channel 16bit uint sample)
- FrameSize := 2 * sizeof(Smallint);
-
- if(SDL_OpenAudio(@desiredAudioSpec, @obtainedAudioSpec) = -1) then
- begin
- Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'UAudioPlayback_SDL');
- exit;
- end;
-
- Log.LogStatus('Opened audio device', 'UAudioPlayback_SDL');
-
- result := true;
-end;
-
-function TAudioPlayback_Portaudio.StartSDLAudioStream(): boolean;
-begin
- SDL_PauseAudio(0);
- result := true;
-end;
-
-procedure TAudioPlayback_Portaudio.StopSDLAudioStream();
-begin
- SDL_CloseAudio();
-end;
-
-function TAudioPlayback_Portaudio.InitializePlayback: boolean;
-begin
- result := false;
-
- //Log.LogStatus('InitializePlayback', 'UAudioPlayback_Portaudio');
-
- //if(not InitializePortaudio()) then
- if(not InitializeSDLAudio()) then
- Exit;
-
- MixerStream := TAudioMixerStream.Create;
-
- //if(not StartPortaudioStream()) then;
- if(not StartSDLAudioStream()) then
- Exit;
-
- result := true;
-end;
-
-destructor TAudioPlayback_Portaudio.Destroy;
-begin
- StopSDLAudioStream();
-
- MixerStream.Free();
- MusicStream.Free();
-
- inherited Destroy();
-end;
-
-function TAudioPlayback_Portaudio.Load(const Filename: String): TPortaudioPlaybackStream;
-var
- decodeStream: TAudioDecodeStream;
- playbackStream: TPortaudioPlaybackStream;
-begin
- Result := nil;
-
- decodeStream := AudioDecoder.Open(Filename);
- if not assigned(decodeStream) then
- begin
- Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudioPlayback_Portaudio');
- Exit;
- end;
-
- playbackStream := TPortaudioPlaybackStream.Create();
- if (not playbackStream.SetDecodeStream(decodeStream)) then
- Exit;
-
- // FIXME: remove this line
- MixerStream.AddStream(playbackStream);
-
- result := playbackStream;
-end;
-
-procedure TAudioPlayback_Portaudio.SetVolume(Volume: integer);
-begin
- // sets volume only for this application
- MixerStream.Volume := Volume;
-end;
-
-procedure TAudioPlayback_Portaudio.SetMusicVolume(Volume: Integer);
-begin
- if assigned(MusicStream) then
- MusicStream.Volume := Volume;
-end;
-
-procedure TAudioPlayback_Portaudio.SetLoop(Enabled: boolean);
-begin
- if assigned(MusicStream) then
- MusicStream.SetLoop(Enabled);
-end;
-
-function TAudioPlayback_Portaudio.Open(Filename: string): boolean;
-var
- decodeStream: TAudioDecodeStream;
-begin
- Result := false;
-
- // free old MusicStream
- MusicStream.Free();
-
- MusicStream := Load(Filename);
- if not assigned(MusicStream) then
- Exit;
-
- //Set Max Volume
- SetMusicVolume(100);
-
- Result := true;
-end;
-
-procedure TAudioPlayback_Portaudio.Rewind;
-begin
- SetPosition(0);
-end;
-
-procedure TAudioPlayback_Portaudio.SetPosition(Time: real);
-begin
- if assigned(MusicStream) then
- MusicStream.SetPosition(Time);
-end;
-
-function TAudioPlayback_Portaudio.GetPosition: real;
-begin
- if assigned(MusicStream) then
- Result := MusicStream.GetPosition()
- else
- Result := -1;
-end;
-
-function TAudioPlayback_Portaudio.Length: real;
-begin
- if assigned(MusicStream) then
- Result := MusicStream.GetLength()
- else
- Result := -1;
-end;
-
-procedure TAudioPlayback_Portaudio.Play;
-begin
- if assigned(MusicStream) then
- MusicStream.Play();
-end;
-
-procedure TAudioPlayback_Portaudio.Pause;
-begin
- if assigned(MusicStream) then
- MusicStream.Pause();
-end;
-
-procedure TAudioPlayback_Portaudio.Stop;
-begin
- if assigned(MusicStream) then
- MusicStream.Stop();
-end;
-
-procedure TAudioPlayback_Portaudio.Close;
-begin
- if assigned(MusicStream) then
- begin
- MixerStream.RemoveStream(MusicStream);
- MusicStream.Close();
- end;
-end;
-
-function TAudioPlayback_Portaudio.Finished: boolean;
-begin
- if assigned(MusicStream) then
- Result := (MusicStream.GetStatus() = ssStopped)
- else
- Result := true;
-end;
-
-//Equalizer
-procedure TAudioPlayback_Portaudio.GetFFTData(var data: TFFTData);
-begin
- //Get Channel Data Mono and 256 Values
-// BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512);
-end;
-
-// Interface for Visualizer
-function TAudioPlayback_Portaudio.GetPCMData(var data: TPCMData): Cardinal;
-begin
- result := 0;
-end;
-
-function TAudioPlayback_Portaudio.OpenSound(const Filename: String): TAudioPlaybackStream;
-begin
- result := Load(Filename);
-end;
-
-procedure TAudioPlayback_Portaudio.PlaySound(stream: TAudioPlaybackStream);
-begin
- if assigned(stream) then
- stream.Play();
-end;
-
-procedure TAudioPlayback_Portaudio.StopSound(stream: TAudioPlaybackStream);
-begin
- if assigned(stream) then
- stream.Stop();
-end;
-
-
-initialization
- singleton_AudioPlaybackPortaudio := TAudioPlayback_Portaudio.create();
- AudioManager.add( singleton_AudioPlaybackPortaudio );
-
-finalization
- AudioManager.Remove( singleton_AudioPlaybackPortaudio );
-
-
-end.
+unit UAudioPlayback_Portaudio;
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+
+uses
+ Classes,
+ SysUtils,
+ UMusic;
+
+implementation
+
+uses
+ portaudio,
+ UAudioPlayback_SoftMixer,
+ ULog,
+ UIni,
+ UMain;
+
+type
+ TAudioPlayback_Portaudio = class(TAudioPlayback_SoftMixer)
+ private
+ paStream: PPaStream;
+ protected
+ function InitializeAudioPlaybackEngine(): boolean; override;
+ function StartAudioPlaybackEngine(): boolean; override;
+ procedure StopAudioPlaybackEngine(); override;
+ public
+ function GetName: String; override;
+ end;
+
+var
+ singleton_AudioPlaybackPortaudio : IAudioPlayback;
+
+
+{ TAudioPlayback_Portaudio }
+
+function PortaudioAudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
+ timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+ userData: Pointer): Integer; cdecl;
+var
+ engine: TAudioPlayback_Portaudio;
+begin
+ engine := TAudioPlayback_Portaudio(userData);
+ engine.AudioCallback(output, frameCount * engine.FormatInfo.FrameSize);
+ result := paContinue;
+end;
+
+function TAudioPlayback_Portaudio.GetName: String;
+begin
+ result := 'Portaudio_Playback';
+end;
+
+function TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine(): boolean;
+var
+ paApi : TPaHostApiIndex;
+ paApiInfo : PPaHostApiInfo;
+ paOutParams : TPaStreamParameters;
+ paOutDevice : TPaDeviceIndex;
+ paOutDeviceInfo : PPaDeviceInfo;
+ err : TPaError;
+const
+ sampleFreq = 44100;
+begin
+ result := false;
+
+ Pa_Initialize();
+
+ // FIXME: determine automatically
+ {$IFDEF WIN32}
+ paApi := Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
+ {$ELSE}
+ paApi := Pa_HostApiTypeIdToHostApiIndex(paALSA);
+ {$ENDIF}
+ if (paApi < 0) then
+ begin
+ Log.LogStatus('Pa_HostApiTypeIdToHostApiIndex: '+Pa_GetErrorText(paApi), 'UAudioPlayback_Portaudio');
+ exit;
+ end;
+
+ paApiInfo := Pa_GetHostApiInfo(paApi);
+ paOutDevice := paApiInfo^.defaultOutputDevice;
+ paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice);
+
+ with paOutParams do begin
+ device := paOutDevice;
+ channelCount := 2;
+ sampleFormat := paInt16;
+ suggestedLatency := paOutDeviceInfo^.defaultHighOutputLatency;
+ hostApiSpecificStreamInfo := nil;
+ end;
+
+ err := Pa_OpenStream(paStream, nil, @paOutParams, sampleFreq,
+ paFramesPerBufferUnspecified,
+ paNoFlag, @PortaudioAudioCallback, Self);
+ if(err <> paNoError) then begin
+ Log.LogStatus('Pa_OpenStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
+ exit;
+ end;
+
+ FormatInfo := TAudioFormatInfo.Create(
+ paOutParams.channelCount,
+ sampleFreq,
+ asfS16 // FIXME: is paInt16 system-dependant or -independant?
+ );
+
+ Log.LogStatus('Opened audio device', 'UAudioPlayback_Portaudio');
+
+ result := true;
+end;
+
+function TAudioPlayback_Portaudio.StartAudioPlaybackEngine(): boolean;
+var
+ err: TPaError;
+begin
+ result := false;
+
+ if (paStream = nil) then
+ Exit;
+
+ err := Pa_StartStream(paStream);
+ if(err <> paNoError) then
+ begin
+ Log.LogStatus('Pa_StartStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
+ exit;
+ end;
+
+ result := true;
+end;
+
+procedure TAudioPlayback_Portaudio.StopAudioPlaybackEngine();
+begin
+ if (paStream <> nil) then
+ Pa_StopStream(paStream);
+end;
+
+
+
+initialization
+ singleton_AudioPlaybackPortaudio := TAudioPlayback_Portaudio.create();
+ AudioManager.add( singleton_AudioPlaybackPortaudio );
+
+finalization
+ AudioManager.Remove( singleton_AudioPlaybackPortaudio );
+
+
+end.
diff --git a/Game/Code/Classes/UAudioPlayback_SDL.pas b/Game/Code/Classes/UAudioPlayback_SDL.pas
new file mode 100644
index 00000000..5dc664fa
--- /dev/null
+++ b/Game/Code/Classes/UAudioPlayback_SDL.pas
@@ -0,0 +1,113 @@
+unit UAudioPlayback_SDL;
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+
+uses
+ Classes,
+ SysUtils,
+ UMusic;
+
+implementation
+
+uses
+ sdl,
+ UAudioPlayback_SoftMixer,
+ ULog,
+ UIni,
+ UMain;
+
+type
+ TAudioPlayback_SDL = class(TAudioPlayback_SoftMixer)
+ protected
+ function InitializeAudioPlaybackEngine(): boolean; override;
+ function StartAudioPlaybackEngine(): boolean; override;
+ procedure StopAudioPlaybackEngine(); override;
+ public
+ function GetName: String; override;
+ end;
+
+var
+ singleton_AudioPlaybackSDL : IAudioPlayback;
+
+
+{ TAudioPlayback_SDL }
+
+procedure SDLAudioCallback(userdata: Pointer; stream: PChar; len: integer); cdecl;
+var
+ engine: TAudioPlayback_SDL;
+begin
+ engine := TAudioPlayback_SDL(userdata);
+ engine.AudioCallback(stream, len);
+end;
+
+function TAudioPlayback_SDL.GetName: String;
+begin
+ result := 'SDL_Playback';
+end;
+
+function TAudioPlayback_SDL.InitializeAudioPlaybackEngine(): boolean;
+var
+ desiredAudioSpec, obtainedAudioSpec: TSDL_AudioSpec;
+ err: integer;
+begin
+ result := false;
+
+ SDL_InitSubSystem(SDL_INIT_AUDIO);
+
+ FillChar(desiredAudioSpec, sizeof(desiredAudioSpec), 0);
+ with desiredAudioSpec do
+ begin
+ freq := 44100;
+ format := AUDIO_S16SYS;
+ channels := 2;
+ samples := 1024; // latency: 23 ms
+ callback := @SDLAudioCallback;
+ userdata := Self;
+ end;
+
+ if(SDL_OpenAudio(@desiredAudioSpec, @obtainedAudioSpec) = -1) then
+ begin
+ Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'UAudioPlayback_SDL');
+ exit;
+ end;
+
+ FormatInfo := TAudioFormatInfo.Create(
+ obtainedAudioSpec.channels,
+ obtainedAudioSpec.freq,
+ asfS16
+ );
+
+ Log.LogStatus('Opened audio device', 'UAudioPlayback_SDL');
+
+ result := true;
+end;
+
+function TAudioPlayback_SDL.StartAudioPlaybackEngine(): boolean;
+begin
+ SDL_PauseAudio(0);
+ result := true;
+end;
+
+procedure TAudioPlayback_SDL.StopAudioPlaybackEngine();
+begin
+ SDL_CloseAudio();
+end;
+
+
+
+initialization
+ singleton_AudioPlaybackSDL := TAudioPlayback_SDL.create();
+ AudioManager.add( singleton_AudioPlaybackSDL );
+
+finalization
+ AudioManager.Remove( singleton_AudioPlaybackSDL );
+
+
+end.
diff --git a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
new file mode 100644
index 00000000..1dc28dcd
--- /dev/null
+++ b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas
@@ -0,0 +1,809 @@
+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;
+ 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(); inline;
+ procedure Unlock(); inline;
+ 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;
+ end;
+
+ TAudioMixerStream = class
+ private
+ activeStreams: TList;
+ mixerBuffer: PChar;
+ internalLock: PSDL_Mutex;
+
+ _volume: integer;
+
+ procedure Lock(); inline;
+ procedure Unlock(); inline;
+
+ 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); inline;
+ 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(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; inline;
+ 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);
+
+ // 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;
+
+ BytesAvail := cvt.len_cvt;
+ 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.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(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
+ //Get Channel Data Mono and 256 Values
+// BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512);
+end;
+
+// Interface for Visualizer
+function TAudioPlayback_SoftMixer.GetPCMData(var data: TPCMData): Cardinal;
+begin
+ 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.
diff --git a/Game/Code/Classes/UMusic.pas b/Game/Code/Classes/UMusic.pas
index 8bbd297a..9b8cd606 100644
--- a/Game/Code/Classes/UMusic.pas
+++ b/Game/Code/Classes/UMusic.pas
@@ -109,10 +109,27 @@ type
asfFloat // float
);
- TAudioFormatInfo = record
- Channels: byte;
- SampleRate: integer;
- Format: TAudioSampleFormat;
+const
+ // Size of one sample (one channel only) in bytes
+ AudioSampleSize: array[TAudioSampleFormat] of integer = (
+ 1, 1, // asfU8, asfS8
+ 2, 2, // asfU16LSB, asfS16LSB
+ 2, 2, // asfU16MSB, asfS16MSB
+ 2, 2, // asfU16, asfS16
+ 3, // asfS24
+ 4, // asfS32
+ 4 // asfFloat
+ );
+
+type
+ TAudioFormatInfo = class
+ public
+ Channels : byte;
+ SampleRate : integer;
+ Format : TAudioSampleFormat;
+ FrameSize : integer; // calculated on construction
+
+ constructor Create(Channels: byte; SampleRate: integer; Format: TAudioSampleFormat);
end;
type
@@ -307,6 +324,14 @@ var
singleton_AudioManager : TInterfaceList = nil;
+constructor TAudioFormatInfo.Create(Channels: byte; SampleRate: integer; Format: TAudioSampleFormat);
+begin
+ Self.Channels := Channels;
+ Self.SampleRate := SampleRate;
+ Self.Format := Format;
+ Self.FrameSize := AudioSampleSize[Format] * Channels;
+end;
+
function AudioManager: TInterfaceList;
begin
if singleton_AudioManager = nil then
@@ -404,7 +429,6 @@ begin
AssignSingletonObjects();
-
if VideoPlayback <> nil then
begin
end;
diff --git a/Game/Code/UltraStar.dpr b/Game/Code/UltraStar.dpr
index c0825d2f..55046af3 100644
--- a/Game/Code/UltraStar.dpr
+++ b/Game/Code/UltraStar.dpr
@@ -167,9 +167,17 @@ uses
{$IFDEF UsePortaudioInput}
UAudioInput_Portaudio in 'Classes\UAudioInput_Portaudio.pas',
{$ENDIF}
+{$IF Defined(UsePortaudioPlayback) or Defined(UseSDLPlayback)}
+ UFFT in 'lib\fft\UFFT.pas',
+ //samplerate in 'lib\samplerate\samplerate.pas',
+ UAudioPlayback_Softmixer in 'Classes\UAudioPlayback_SoftMixer.pas',
+{$IFEND}
{$IFDEF UsePortaudioPlayback}
UAudioPlayback_Portaudio in 'Classes\UAudioPlayback_Portaudio.pas',
{$ENDIF}
+{$IFDEF UseSDLPlayback}
+ UAudioPlayback_SDL in 'Classes\UAudioPlayback_SDL.pas',
+{$ENDIF}
//------------------------------
diff --git a/Game/Code/switches.inc b/Game/Code/switches.inc
index d0f187bb..33f00ce3 100644
--- a/Game/Code/switches.inc
+++ b/Game/Code/switches.inc
@@ -42,8 +42,8 @@
{$DEFINE UseBASSInput}
{$ELSE}
{$DEFINE UseFFMpegDecoder}
- {$DEFINE UsePortaudioPlayback}
- //{$DEFINE UseSDLPlayback}
+ //{$DEFINE UsePortaudioPlayback}
+ {$DEFINE UseSDLPlayback}
{$DEFINE UsePortaudioInput}
{$DEFINE UsePortmixer}
{$ENDIF}