DEADSOFTWARE

AL: update streams in a separate thread
[d2df-sdl.git] / src / engine / e_sound_al.inc
index 312b701bed68abae6a4cc1c31cfffd0cd2fed1bb..ac64ad41d7f8e2d6d83ce7a5c92070270c80d671 100644 (file)
@@ -2,8 +2,7 @@
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, version 3 of the License ONLY.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -91,13 +90,15 @@ procedure e_SoundUpdate();
 var
   e_SoundFormat: TSoundFormat; // desired sound format
   e_SoundsArray: array of TSoundRec = nil;
-  e_ZeroPosition: array [0..2] of ALfloat;
+  e_ZeroPosition: array [0..2] of ALfloat = (0, 0, 0);
   e_ALError: ALenum = 0;
+  e_SoundFont: string = '';
+  e_MusicLerp: Boolean = True;
 
 implementation
 
 uses
-  g_window, g_options, utils;
+  g_options, utils;
 
 const
   NUM_SOURCES = 255; // + 1 stereo
@@ -119,6 +120,28 @@ var
   alStreamData: array [0..STREAM_BUFSIZE-1] of Byte;
   alStreamAvail: Integer = NUM_STREAM_BUFFERS;
 
+{$IFNDEF OPENAL_SINGLETHREADED}
+var
+  StreamThread: TThreadID = NilThreadId;
+  StreamThreadRunning: Boolean = False;
+  StreamLock: TRTLCriticalSection;
+  StreamBufTime: Integer = 100; // time to sleep between buffer checks
+
+procedure UpdateStreamSource(Src: Integer); forward;
+
+function StreamThreadProc(Param: Pointer): PtrInt;
+begin
+  while StreamThreadRunning do
+  begin
+    EnterCriticalSection(StreamLock);
+    UpdateStreamSource(MUSIC_SOURCE);
+    LeaveCriticalSection(StreamLock);
+    Sleep(StreamBufTime);
+  end;
+  Result := 0;
+end;
+{$ENDIF}
+
 function CheckALError(): Boolean;
 begin
   e_ALError := alGetError();
@@ -213,6 +236,12 @@ begin
   else
     alStreamAvail := NUM_STREAM_BUFFERS;
 
+  {$IFNDEF OPENAL_SINGLETHREADED}
+  InitCriticalSection(StreamLock);
+  StreamThreadRunning := True;
+  StreamThread := BeginThread(Addr(StreamThreadProc));
+  {$ENDIF}
+
   Result := True;
 end;
 
@@ -264,12 +293,70 @@ begin
   alGetSourcei(S, AL_SOURCE_STATE, Result);
 end;
 
+function LoadEntireSound(var Snd: TSoundRec; Loader: TSoundLoader): Boolean;
+var
+  Frame: Pointer;
+  Data: Pointer;
+  Rx: LongWord;
+  DataLen, OldLen: LongWord;
+const
+  CHUNK_SIZE = 65536 * 2 * 2;
+begin
+  Result := False;
+
+  Frame := GetMem(CHUNK_SIZE);
+  if Frame = nil then exit;
+
+  Data := nil;
+  DataLen := 0;
+
+  repeat
+    Rx := Loader.FillBuffer(Frame, CHUNK_SIZE);
+    if Rx = 0 then break;
+
+    OldLen := DataLen;
+    DataLen := DataLen + Rx;
+    Data := ReAllocMem(Data, DataLen);
+    if Data = nil then begin FreeMem(Frame); exit; end;
+
+    Move(Frame^, (Data + OldLen)^, Rx);
+  until Loader.Finished();
+
+  FreeMem(Frame);
+
+  alGenBuffers(1, Addr(Snd.alBuffer));
+  if CheckALError() then
+  begin
+    e_LogWritefln('AL: Could not create AL buffer: %s', [GetALError()]);
+    FreeMem(Data);
+    exit;
+  end;
+
+  alBufferData(
+    Snd.alBuffer,
+    GetALSoundFormat(Loader.Format),
+    Data,
+    DataLen,
+    Loader.Format.SampleRate
+  );
+
+  FreeMem(Data);
+
+  if CheckALError() then
+  begin
+    e_LogWriteln('AL: Could not fill AL buffer: ' + GetALError());
+    alDeleteBuffers(1, Addr(Snd.alBuffer));
+    Snd.alBuffer := 0;
+    exit;
+  end;
+
+  Result := True;
+end;
+
 function e_LoadSound(FileName: String; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
 var
   find_id: DWORD;
   Loader: TSoundLoader;
-  OutData: Pointer;
-  OutLen: LongWord;
 begin
   ID := NO_SOUND_ID;
   Result := False;
@@ -288,7 +375,7 @@ begin
     exit;
   end;
 
-  if not Loader.Load(FileName, e_SoundsArray[find_id].isMusic) then
+  if not Loader.Load(FileName, e_SoundsArray[find_id].Loops) then
   begin
     e_LogWritefln('Could not load sound `%s`', [FileName]);
     exit;
@@ -296,40 +383,16 @@ begin
 
   alGetError(); // reset error state, god damn it
 
-  if not Loader.Streaming then
+  if not isMusic then
   begin
-    alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
-    if CheckALError() then
-    begin
-      e_LogWritefln('Could not create AL buffer for `%s`: %s', [FileName, GetALError()]);
-      Loader.Free();
-      exit;
-    end;
-
-    OutLen := Loader.GetAll(OutData);
-    alBufferData(
-      e_SoundsArray[find_id].alBuffer,
-      GetALSoundFormat(Loader.Format),
-      OutData,
-      OutLen,
-      Loader.Format.SampleRate
-    );
-
+    if not LoadEntireSound(e_SoundsArray[find_id], Loader) then
+      e_LogWritefln('AL: Could not buffer sound effect `%s`', [FileName]);
     // don't need this anymore
     Loader.Free();
     Loader := nil;
-
-    if CheckALError() then
-    begin
-      e_LogWriteln('AL: what the fuck: ' + GetALError());
-      alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
-      e_SoundsArray[find_id].alBuffer := 0;
-      exit;
-    end;
   end
   else
   begin
-    Loader.Looping := e_SoundsArray[find_id].Loops;
     e_SoundsArray[find_id].alBuffer := 0;
     e_SoundsArray[find_id].Loader := Loader;
   end;
@@ -342,8 +405,6 @@ function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic:
 var
   find_id: DWORD;
   Loader: TSoundLoader;
-  OutData: Pointer;
-  OutLen: LongWord;
 begin
   ID := NO_SOUND_ID;
   Result := False;
@@ -362,7 +423,7 @@ begin
     exit;
   end;
 
-  if not Loader.Load(pData, LongWord(Length), e_SoundsArray[find_id].isMusic) then
+  if not Loader.Load(pData, LongWord(Length), e_SoundsArray[find_id].Loops) then
   begin
     e_LogWritefln('Could not load sound `%p`', [pData]);
     exit;
@@ -370,44 +431,22 @@ begin
 
   alGetError(); // reset error state, god damn it
 
-  if not Loader.Streaming then
+  if not isMusic then
   begin
-    alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
-    if CheckALError() then
-    begin
-      e_LogWritefln('Could not create AL buffer for `%p`: %s', [pData, GetALError()]);
-      Loader.Free();
-      exit;
-    end;
-
-    OutLen := Loader.GetAll(OutData);
-    alBufferData(
-      e_SoundsArray[find_id].alBuffer,
-      GetALSoundFormat(Loader.Format),
-      OutData,
-      OutLen,
-      Loader.Format.SampleRate
-    );
-
+    if not LoadEntireSound(e_SoundsArray[find_id], Loader) then
+      e_LogWritefln('AL: Could not buffer sound effect `%p`', [pData]);
     // don't need this anymore
     Loader.Free();
     Loader := nil;
-
-    if CheckALError() then
-    begin
-      e_LogWriteln('AL: what the fuck: ' + GetALError());
-      alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
-      e_SoundsArray[find_id].alBuffer := 0;
-      exit;
-    end;
   end
   else
   begin
-    Loader.Looping := e_SoundsArray[find_id].Loops;
     e_SoundsArray[find_id].alBuffer := 0;
     e_SoundsArray[find_id].Loader := Loader;
   end;
 
+  // the calling side won't free this, the loader will get a copy, so fuck it
+  FreeMem(pData);
   ID := find_id;
   Result := True;
 end;
@@ -449,18 +488,44 @@ begin
 end;
 
 procedure AssignSound(ID: DWORD; Src: ALuint); inline;
+var
+  S: ALint;
 begin
   alGetError(); // reset error state
 
   if e_SoundsArray[ID].Loader <> nil then
   begin
     // this is a stream
+    {$IFNDEF OPENAL_SINGLETHREADED}
+    // lock the stream so the stream thread doesn't shit itself
+    EnterCriticalSection(StreamLock);
+    // number of stereo samples / samplerate =
+    // time until buffer runs out
+    StreamBufTime :=
+      (STREAM_BUFSIZE div (2 * e_SoundsArray[ID].Loader.Format.SampleBits div 8)) div
+      (e_SoundsArray[ID].Loader.Format.SampleRate div 1000) - 1;
+    if StreamBufTime < 1 then StreamBufTime := 1;
+    e_LogWritefln('sleep time = %d', [StreamBufTime]);
+    {$ENDIF}
     // reset position
-    e_SoundsArray[ID].Loader.SetPosition(0);
-    if CurStream <> ID then // changing streams, stop the thing just in case
-      alSourceStop(Src);
+    e_SoundsArray[ID].Loader.Restart();
+    if CurStream <> ID then // changing streams
+    begin
+      alSourceStop(Src); // this should mark all buffers as processed
+      alGetSourcei(Src, AL_BUFFERS_PROCESSED, S);
+      // unqueue all buffers
+      if S > 0 then
+      begin
+        alSourceUnqueueBuffers(Src, S, @alStreamBufs[alStreamAvail]);
+        alStreamAvail := NUM_STREAM_BUFFERS;
+      end;
+    end;
     // this shit is playing now
     CurStream := ID;
+    {$IFNDEF OPENAL_SINGLETHREADED}
+    // unlock the stream
+    LeaveCriticalSection(StreamLock);
+    {$ENDIF}
   end
   else
   begin
@@ -496,6 +561,8 @@ begin
   if Result >= 0 then
   begin
     Pos[0] := Pan;
+    Pos[1] := 0;
+    Pos[2] := 0;
     AssignSound(ID, alSources[Result]);
     alSourcef(alSources[Result], AL_GAIN, 1);
     alSourcefv(alSources[Result], AL_POSITION, Pos);
@@ -523,6 +590,8 @@ begin
   if Result >= 0 then
   begin
     Pos[0] := Pan;
+    Pos[1] := 0;
+    Pos[2] := 0;
     AssignSound(ID, alSources[Result]);
     alSourcefv(alSources[Result], AL_POSITION, Pos);
     alSourcef(alSources[Result], AL_GAIN, Volume);
@@ -608,6 +677,16 @@ end;
 
 procedure e_ReleaseSoundSystem();
 begin
+  {$IFNDEF OPENAL_SINGLETHREADED}
+  if StreamThread <> NilThreadId then
+  begin
+    StreamThreadRunning := False;
+    WaitForThreadTerminate(StreamThread, 66666);
+    StreamThread := NilThreadId;
+    DoneCriticalSection(StreamLock);
+  end;
+  {$ENDIF}
+
   e_RemoveAllSounds();
 
   alcMakeContextCurrent(nil);
@@ -677,8 +756,10 @@ begin
       alOwners[S] := nil;
     end;
 
+  {$IFDEF OPENAL_SINGLETHREADED}
   // update the stream sources
   UpdateStreamSource(MUSIC_SOURCE);
+  {$ENDIF}
 end;
 
 { TBasicSound: }
@@ -798,7 +879,7 @@ end;
 
 function TBasicSound.GetPan(): Single;
 var
-  Pos: array [0..2] of ALfloat;
+  Pos: array [0..2] of ALfloat = (0, 0, 0);
 begin
   Result := 0.0;
   if InvalidSource() then
@@ -814,6 +895,8 @@ begin
   if InvalidSource() then
     Exit;
   Pos[0] := Pan;
+  Pos[1] := 0;
+  Pos[2] := 0;
   alSourcefv(alSources[FSource], AL_POSITION, Pos);
 end;