X-Git-Url: https://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fengine%2Fe_sound_al.inc;h=ac64ad41d7f8e2d6d83ce7a5c92070270c80d671;hb=367241f47cc2babef77538b6a58c7c500add44e7;hp=d5701298b933e05c529d9ec15835d0b33f94cc66;hpb=b6261de6b40b4b79207cc01889b94e3237e1bba6;p=d2df-sdl.git diff --git a/src/engine/e_sound_al.inc b/src/engine/e_sound_al.inc index d570129..ac64ad4 100644 --- a/src/engine/e_sound_al.inc +++ b/src/engine/e_sound_al.inc @@ -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 @@ -24,9 +23,7 @@ uses type TSoundRec = record - Fmt: TSoundFormat; - Data: Pointer; - DataLen: Integer; + Loader: TSoundLoader; alBuffer: ALuint; isMusic: Boolean; Loops: Boolean; @@ -91,27 +88,59 @@ procedure e_ReleaseSoundSystem(); 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 = 250; + NUM_SOURCES = 255; // + 1 stereo + NUM_STREAM_BUFFERS = 8; + STREAM_BUFSIZE = 8192; MUSIC_SOURCE = 0; var + SoundMuted: Boolean = False; + CurStream: DWORD = NO_SOUND_ID; alDevice: PALCdevice = nil; alContext: PALCcontext = nil; - // sources for one-shot sounds - // TBasicSounds have a built in source that lives and dies with them - alSources: array of ALuint; - alOwners: array of TBasicSound; - SoundMuted: Boolean = False; + // sources for everything + alSources: array [0..NUM_SOURCES] of ALuint; + // last TBasicSound that has played on each source + alOwners: array [0..NUM_SOURCES] of TBasicSound; + // buffers for the music stream + alStreamBufs: array [0..NUM_STREAM_BUFFERS-1] of ALuint; + 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 @@ -136,8 +165,8 @@ end; function e_InitSoundSystem(NoOutput: Boolean = False): Boolean; var alExt, alRend, alVendor, alVer: string; - DevName: string = ''; - alAttrs: array [0..4] of ALCint = ( + WantDev: string = ''; + WantAttrs: array [0..4] of ALCint = ( ALC_STEREO_SOURCES, 1, ALC_MONO_SOURCES, NUM_SOURCES, 0 @@ -145,21 +174,21 @@ var begin Result := False; - DevName := alcGetString(nil, ALC_DEVICE_SPECIFIER); - e_LogWritefln('AL: available devices: %s', [DevName]); + WantDev := alcGetString(nil, ALC_DEVICE_SPECIFIER); + e_LogWritefln('AL: available devices: %s', [WantDev]); // TODO: open a dummy device when NoOutput is true or something - DevName := alcGetString(nil, ALC_DEFAULT_DEVICE_SPECIFIER); - e_LogWritefln('AL: trying to open device %s', [DevName]); + WantDev := alcGetString(nil, ALC_DEFAULT_DEVICE_SPECIFIER); + e_LogWritefln('AL: trying to open device %s', [WantDev]); - alDevice := alcOpenDevice(PChar(DevName)); + alDevice := alcOpenDevice(PChar(WantDev)); if alDevice = nil then begin - e_LogWritefln('AL: ERROR: could not open device %s: %s', [DevName, GetALError()]); + e_LogWritefln('AL: ERROR: could not open device %s: %s', [WantDev, GetALError()]); exit; end; - alContext := alcCreateContext(alDevice, alAttrs); + alContext := alcCreateContext(alDevice, WantAttrs); if alContext = nil then begin e_LogWritefln('AL: ERROR: could not create context: %s', [GetALError()]); @@ -170,29 +199,49 @@ begin alcMakeContextCurrent(alContext); + // TODO: actually parse these from alc attributes or something + e_SoundFormat.SampleRate := 48000; + e_SoundFormat.SampleBits := 16; + e_SoundFormat.Channels := 2; + alVendor := alGetString(AL_VENDOR); alRend := alGetString(AL_RENDERER); alVer := alGetString(AL_VERSION); alExt := alGetString(AL_EXTENSIONS); e_LogWriteln('AL INFO:'); - e_LogWriteln(' Version: ' + alVer); - e_LogWriteln(' Vendor: ' + alVendor); - e_LogWriteln(' Renderer: ' + alRend); - e_LogWriteln(' Device: ' + DevName); + e_LogWriteln(' Version: ' + alVer); + e_LogWriteln(' Vendor: ' + alVendor); + e_LogWriteln(' Renderer: ' + alRend); + e_LogWriteln(' Device: ' + WantDev); + e_LogWriteln(' Sample rate: ' + IntToStr(e_SoundFormat.SampleRate)); e_LogWriteln(' Extensions:'); e_LogWriteln(' ' + alExt); - SetLength(alSources, NUM_SOURCES + 1); // 0 is the music source - SetLength(alOwners, NUM_SOURCES + 1); // to avoid destructive operations on sources - ZeroMemory(@alSources[0], sizeof(alSources[0]) * Length(alSources)); - ZeroMemory(@alOwners[0], sizeof(alOwners[0]) * Length(alOwners)); + ZeroMemory(@alSources[0], sizeof(alSources)); + ZeroMemory(@alOwners[0], sizeof(alOwners)); + ZeroMemory(@alStreamBufs[0], sizeof(alStreamBufs)); + ZeroMemory(@alStreamData[0], sizeof(alStreamData)); + CurStream := NO_SOUND_ID; alGetError(); // reset the goddamn error state alGenSources(1, @alSources[0]); // generate the music source if CheckALError() then e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError()); + alStreamAvail := 0; + alGenBuffers(NUM_STREAM_BUFFERS, @alStreamBufs[0]); // generate buffers for the music stream + if CheckALError() then + e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError()) + else + alStreamAvail := NUM_STREAM_BUFFERS; + + {$IFNDEF OPENAL_SINGLETHREADED} + InitCriticalSection(StreamLock); + StreamThreadRunning := True; + StreamThread := BeginThread(Addr(StreamThreadProc)); + {$ENDIF} + Result := True; end; @@ -203,7 +252,7 @@ var begin if e_SoundsArray <> nil then for i := 0 to High(e_SoundsArray) do - if e_SoundsArray[i].alBuffer = 0 then + if (e_SoundsArray[i].alBuffer = 0) and (e_SoundsArray[i].Loader = nil) then begin Result := i; Exit; @@ -244,6 +293,66 @@ 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; @@ -254,8 +363,7 @@ begin find_id := FindESound(); - e_SoundsArray[find_id].Data := nil; - e_SoundsArray[find_id].DataLen := 0; + e_SoundsArray[find_id].Loader := nil; e_SoundsArray[find_id].isMusic := isMusic; e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop; e_SoundsArray[find_id].nRefs := 0; @@ -267,8 +375,7 @@ begin exit; end; - e_SoundsArray[find_id].Data := Loader.Load(FileName, e_SoundsArray[find_id].DataLen, e_SoundsArray[find_id].Fmt); - if e_SoundsArray[find_id].Data = nil then + if not Loader.Load(FileName, e_SoundsArray[find_id].Loops) then begin e_LogWritefln('Could not load sound `%s`', [FileName]); exit; @@ -276,32 +383,18 @@ begin alGetError(); // reset error state, god damn it - alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); - if CheckALError() then + if not isMusic then begin - e_LogWritefln('Could not create AL buffer for `%s`: %s', [FileName, GetALError()]); - Loader.Free(e_SoundsArray[find_id].Data); - exit; - end; - - alBufferData( - e_SoundsArray[find_id].alBuffer, - GetALSoundFormat(e_SoundsArray[find_id].Fmt), - e_SoundsArray[find_id].Data, - e_SoundsArray[find_id].DataLen, - e_SoundsArray[find_id].Fmt.SampleRate - ); - - // don't need this anymore - Loader.Free(e_SoundsArray[find_id].Data); - e_SoundsArray[find_id].Data := nil; - e_SoundsArray[find_id].DataLen := 0; - - if CheckALError() then + 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; + end + else begin - e_LogWriteln('AL: what the fuck: ' + GetALError()); - alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); - exit; + e_SoundsArray[find_id].alBuffer := 0; + e_SoundsArray[find_id].Loader := Loader; end; ID := find_id; @@ -318,21 +411,19 @@ begin find_id := FindESound(); - e_SoundsArray[find_id].Data := nil; - e_SoundsArray[find_id].DataLen := 0; + e_SoundsArray[find_id].Loader := nil; e_SoundsArray[find_id].isMusic := isMusic; e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop; e_SoundsArray[find_id].nRefs := 0; - Loader := e_GetSoundLoader(pData, Length); + Loader := e_GetSoundLoader(pData, LongWord(Length)); if Loader = nil then begin e_LogWritefln('Could not find loader for sound `%p`', [pData]); exit; end; - e_SoundsArray[find_id].Data := Loader.Load(pData, Length, e_SoundsArray[find_id].DataLen, e_SoundsArray[find_id].Fmt); - if e_SoundsArray[find_id].Data = nil then + if not Loader.Load(pData, LongWord(Length), e_SoundsArray[find_id].Loops) then begin e_LogWritefln('Could not load sound `%p`', [pData]); exit; @@ -340,34 +431,22 @@ begin alGetError(); // reset error state, god damn it - alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); - if CheckALError() then + if not isMusic then begin - e_LogWritefln('Could not create AL buffer for `%p`: %s', [pData, GetALError()]); - Loader.Free(e_SoundsArray[find_id].Data); - exit; - end; - - alBufferData( - e_SoundsArray[find_id].alBuffer, - GetALSoundFormat(e_SoundsArray[find_id].Fmt), - e_SoundsArray[find_id].Data, - e_SoundsArray[find_id].DataLen, - e_SoundsArray[find_id].Fmt.SampleRate - ); - - // don't need this anymore - Loader.Free(e_SoundsArray[find_id].Data); - e_SoundsArray[find_id].Data := nil; - e_SoundsArray[find_id].DataLen := 0; - - if CheckALError() then + 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; + end + else begin - e_LogWriteln('AL: what the fuck: ' + GetALError()); - alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer)); - exit; + 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; @@ -380,9 +459,11 @@ begin if ID > High(e_SoundsArray) then exit; - if e_SoundsArray[ID].isMusic then + if e_SoundsArray[ID].Loader <> nil then begin - // last source is for music + // first source is for streaming sounds + // it always exists + alOwners[MUSIC_SOURCE] := nil; Result := MUSIC_SOURCE; exit; end; @@ -407,14 +488,57 @@ begin end; procedure AssignSound(ID: DWORD; Src: ALuint); inline; +var + S: ALint; begin alGetError(); // reset error state - alSourcei(Src, AL_BUFFER, e_SoundsArray[ID].alBuffer); - alSourcei(Src, AL_SOURCE_RELATIVE, AL_TRUE); - if (e_SoundsArray[ID].Loops) then - alSourcei(Src, AL_LOOPING, AL_TRUE) + + 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.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 - alSourcei(Src, AL_LOOPING, AL_FALSE); + begin + // this is a full chunk, assign local buffer + alSourcei(Src, AL_BUFFER, e_SoundsArray[ID].alBuffer); + // these can loop + if (e_SoundsArray[ID].Loops) then + alSourcei(Src, AL_LOOPING, AL_TRUE) + else + alSourcei(Src, AL_LOOPING, AL_FALSE); + end; + + alSourcei(Src, AL_SOURCE_RELATIVE, AL_TRUE); end; function e_PlaySound(ID: DWORD): Integer; @@ -437,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); @@ -464,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); @@ -480,11 +608,12 @@ begin alDeleteBuffers(1, Addr(e_SoundsArray[ID].alBuffer)); e_SoundsArray[ID].alBuffer := 0; end; - if (e_SoundsArray[ID].Data <> nil) then + if (e_SoundsArray[ID].Loader <> nil) then begin - e_SoundsArray[ID].Fmt.Loader.Free(e_SoundsArray[ID].Data); - e_SoundsArray[ID].Data := nil; - e_SoundsArray[ID].DataLen := 0; + e_SoundsArray[ID].Loader.Free(); + e_SoundsArray[ID].Loader := nil; + if ID = CurStream then + CurStream := NO_SOUND_ID; end; end; @@ -543,10 +672,21 @@ begin e_DeleteSound(i); SetLength(e_SoundsArray, 0); e_SoundsArray := nil; + CurStream := NO_SOUND_ID; 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); @@ -557,11 +697,56 @@ begin alContext := nil; end; +procedure UpdateStreamSource(Src: Integer); +var + OutLen: LongWord; + Buf: ALuint; + S: Integer; +begin + if alSources[Src] = 0 then Exit; + + alGetError(); // reset error state + + alGetSourcei(alSources[Src], AL_BUFFERS_PROCESSED, S); + // unqueue processed buffers + if S > 0 then + begin + alSourceUnqueueBuffers(alSources[Src], S, @alStreamBufs[alStreamAvail]); + alStreamAvail := alStreamAvail + S; + end; + + alGetError(); // reset error state + + if (alStreamAvail > 0) and (CurStream <> NO_SOUND_ID) then + begin + // some buffers have freed up, advance stream playback + OutLen := e_SoundsArray[CurStream].Loader.FillBuffer(@alStreamData[0], STREAM_BUFSIZE); + if OutLen = 0 then Exit; // ran out of stream + Buf := alStreamBufs[alStreamAvail-1]; + Dec(alStreamAvail); + // upload + alBufferData( + Buf, + GetALSoundFormat(e_SoundsArray[CurStream].Loader.Format), + @alStreamData[0], + OutLen, + e_SoundsArray[CurStream].Loader.Format.SampleRate + ); + // attach + alSourceQueueBuffers(alSources[Src], 1, @Buf); + // restart if needed + S := GetALSourceState(alSources[Src]); + if (S = AL_STOPPED) or (S = AL_INITIAL) then + alSourcePlay(alSources[Src]); + end; +end; + procedure e_SoundUpdate(); var S: Integer; begin alGetError(); // reset error state + // clear out all stopped sources for S := 1 to High(alSources) do if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_STOPPED) then @@ -570,6 +755,11 @@ begin alSources[S] := 0; alOwners[S] := nil; end; + + {$IFDEF OPENAL_SINGLETHREADED} + // update the stream sources + UpdateStreamSource(MUSIC_SOURCE); + {$ENDIF} end; { TBasicSound: } @@ -646,6 +836,8 @@ end; procedure TBasicSound.Stop(); begin + if FID = CurStream then + CurStream := NO_SOUND_ID; if InvalidSource() then Exit; GetPosition(); @@ -687,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 @@ -703,6 +895,8 @@ begin if InvalidSource() then Exit; Pos[0] := Pan; + Pos[1] := 0; + Pos[2] := 0; alSourcefv(alSources[FSource], AL_POSITION, Pos); end;