DEADSOFTWARE

Sound: OpenAL: Reclaim stream buffers on stream change
[d2df-sdl.git] / src / engine / e_sound_al.inc
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 interface
18 uses
19 AL,
20 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
21 e_soundfile,
22 e_log,
23 SysUtils;
25 type
26 TSoundRec = record
27 Loader: TSoundLoader;
28 alBuffer: ALuint;
29 isMusic: Boolean;
30 Loops: Boolean;
31 nRefs: Integer;
32 end;
34 TBasicSound = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
35 private
36 FSource: Integer;
37 FOldGain: ALfloat;
38 FMuted: Boolean;
40 function InvalidSource(): Boolean; inline;
42 protected
43 FID: DWORD;
44 FMusic: Boolean;
45 FPosition: DWORD;
47 function RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
49 public
50 constructor Create();
51 destructor Destroy(); override;
52 procedure SetID(ID: DWORD);
53 procedure FreeSound();
54 function IsPlaying(): Boolean;
55 procedure Stop();
56 function IsPaused(): Boolean;
57 procedure Pause(Enable: Boolean);
58 function GetVolume(): Single;
59 procedure SetVolume(Volume: Single);
60 function GetPan(): Single;
61 procedure SetPan(Pan: Single);
62 function IsMuted(): Boolean;
63 procedure Mute(Enable: Boolean);
64 function GetPosition(): DWORD;
65 procedure SetPosition(aPos: DWORD);
66 procedure SetPriority(priority: Integer);
67 end;
69 const
70 NO_SOUND_ID = DWORD(-1);
72 function e_InitSoundSystem(NoOutput: Boolean = False): Boolean;
74 function e_LoadSound(FileName: string; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
75 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
77 function e_PlaySound(ID: DWORD): Integer;
78 function e_PlaySoundPan(ID: DWORD; Pan: Single): Integer;
79 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
80 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
82 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
83 procedure e_MuteChannels(Enable: Boolean);
84 procedure e_StopChannels();
86 procedure e_DeleteSound(ID: DWORD);
87 procedure e_RemoveAllSounds();
88 procedure e_ReleaseSoundSystem();
89 procedure e_SoundUpdate();
91 var
92 e_SoundFormat: TSoundFormat; // desired sound format
93 e_SoundsArray: array of TSoundRec = nil;
94 e_ZeroPosition: array [0..2] of ALfloat = (0, 0, 0);
95 e_ALError: ALenum = 0;
97 implementation
99 uses
100 g_window, g_options, utils;
102 const
103 NUM_SOURCES = 255; // + 1 stereo
104 NUM_STREAM_BUFFERS = 8;
105 STREAM_BUFSIZE = 8192;
106 MUSIC_SOURCE = 0;
108 var
109 SoundMuted: Boolean = False;
110 CurStream: DWORD = NO_SOUND_ID;
111 alDevice: PALCdevice = nil;
112 alContext: PALCcontext = nil;
113 // sources for everything
114 alSources: array [0..NUM_SOURCES] of ALuint;
115 // last TBasicSound that has played on each source
116 alOwners: array [0..NUM_SOURCES] of TBasicSound;
117 // buffers for the music stream
118 alStreamBufs: array [0..NUM_STREAM_BUFFERS-1] of ALuint;
119 alStreamData: array [0..STREAM_BUFSIZE-1] of Byte;
120 alStreamAvail: Integer = NUM_STREAM_BUFFERS;
122 function CheckALError(): Boolean;
123 begin
124 e_ALError := alGetError();
125 Result := e_ALError <> AL_NO_ERROR;
126 end;
128 function GetALError(): string;
129 begin
130 Result := '';
131 case e_ALError of
132 AL_NO_ERROR: Result := '';
133 AL_INVALID_NAME: Result := 'AL_INVALID_NAME';
134 AL_INVALID_ENUM: Result := 'AL_INVALID_ENUM';
135 AL_INVALID_VALUE: Result := 'AL_INVALID_VALUE';
136 AL_INVALID_OPERATION: Result := 'AL_INVALID_OPERATION';
137 AL_OUT_OF_MEMORY: Result := 'AL_OUT_OF_MEMORY';
138 else Result := Format('unknown error %x', [e_ALError]);
139 end;
140 end;
142 function e_InitSoundSystem(NoOutput: Boolean = False): Boolean;
143 var
144 alExt, alRend, alVendor, alVer: string;
145 WantDev: string = '';
146 WantAttrs: array [0..4] of ALCint = (
147 ALC_STEREO_SOURCES, 1,
148 ALC_MONO_SOURCES, NUM_SOURCES,
150 );
151 begin
152 Result := False;
154 WantDev := alcGetString(nil, ALC_DEVICE_SPECIFIER);
155 e_LogWritefln('AL: available devices: %s', [WantDev]);
157 // TODO: open a dummy device when NoOutput is true or something
158 WantDev := alcGetString(nil, ALC_DEFAULT_DEVICE_SPECIFIER);
159 e_LogWritefln('AL: trying to open device %s', [WantDev]);
161 alDevice := alcOpenDevice(PChar(WantDev));
162 if alDevice = nil then
163 begin
164 e_LogWritefln('AL: ERROR: could not open device %s: %s', [WantDev, GetALError()]);
165 exit;
166 end;
168 alContext := alcCreateContext(alDevice, WantAttrs);
169 if alContext = nil then
170 begin
171 e_LogWritefln('AL: ERROR: could not create context: %s', [GetALError()]);
172 alcCloseDevice(alDevice);
173 alDevice := nil;
174 exit;
175 end;
177 alcMakeContextCurrent(alContext);
179 // TODO: actually parse these from alc attributes or something
180 e_SoundFormat.SampleRate := 48000;
181 e_SoundFormat.SampleBits := 16;
182 e_SoundFormat.Channels := 2;
184 alVendor := alGetString(AL_VENDOR);
185 alRend := alGetString(AL_RENDERER);
186 alVer := alGetString(AL_VERSION);
187 alExt := alGetString(AL_EXTENSIONS);
189 e_LogWriteln('AL INFO:');
190 e_LogWriteln(' Version: ' + alVer);
191 e_LogWriteln(' Vendor: ' + alVendor);
192 e_LogWriteln(' Renderer: ' + alRend);
193 e_LogWriteln(' Device: ' + WantDev);
194 e_LogWriteln(' Sample rate: ' + IntToStr(e_SoundFormat.SampleRate));
195 e_LogWriteln(' Extensions:');
196 e_LogWriteln(' ' + alExt);
198 ZeroMemory(@alSources[0], sizeof(alSources));
199 ZeroMemory(@alOwners[0], sizeof(alOwners));
200 ZeroMemory(@alStreamBufs[0], sizeof(alStreamBufs));
201 ZeroMemory(@alStreamData[0], sizeof(alStreamData));
202 CurStream := NO_SOUND_ID;
204 alGetError(); // reset the goddamn error state
205 alGenSources(1, @alSources[0]); // generate the music source
206 if CheckALError() then
207 e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError());
209 alStreamAvail := 0;
210 alGenBuffers(NUM_STREAM_BUFFERS, @alStreamBufs[0]); // generate buffers for the music stream
211 if CheckALError() then
212 e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError())
213 else
214 alStreamAvail := NUM_STREAM_BUFFERS;
216 Result := True;
217 end;
219 function FindESound(): DWORD;
220 var
221 i: Integer;
223 begin
224 if e_SoundsArray <> nil then
225 for i := 0 to High(e_SoundsArray) do
226 if (e_SoundsArray[i].alBuffer = 0) and (e_SoundsArray[i].Loader = nil) then
227 begin
228 Result := i;
229 Exit;
230 end;
232 if e_SoundsArray = nil then
233 begin
234 SetLength(e_SoundsArray, 16);
235 Result := 0;
236 end
237 else
238 begin
239 Result := High(e_SoundsArray) + 1;
240 SetLength(e_SoundsArray, Length(e_SoundsArray) + 16);
241 end;
242 end;
244 function GetALSoundFormat(Fmt: TSoundFormat): ALenum; inline;
245 begin
246 if Fmt.Channels = 2 then
247 begin
248 if Fmt.SampleBits = 16 then
249 Result := AL_FORMAT_STEREO16
250 else
251 Result := AL_FORMAT_STEREO8;
252 end
253 else
254 begin
255 if Fmt.SampleBits = 16 then
256 Result := AL_FORMAT_MONO16
257 else
258 Result := AL_FORMAT_MONO8;
259 end;
260 end;
262 function GetALSourceState(S: ALuint): ALint; inline;
263 begin
264 alGetSourcei(S, AL_SOURCE_STATE, Result);
265 end;
267 function e_LoadSound(FileName: String; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
268 var
269 find_id: DWORD;
270 Loader: TSoundLoader;
271 OutData: Pointer;
272 OutLen: LongWord;
273 begin
274 ID := NO_SOUND_ID;
275 Result := False;
277 find_id := FindESound();
279 e_SoundsArray[find_id].Loader := nil;
280 e_SoundsArray[find_id].isMusic := isMusic;
281 e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop;
282 e_SoundsArray[find_id].nRefs := 0;
284 Loader := e_GetSoundLoader(FileName);
285 if Loader = nil then
286 begin
287 e_LogWritefln('Could not find loader for sound `%s`', [FileName]);
288 exit;
289 end;
291 Loader.Looping := e_SoundsArray[find_id].Loops;
293 if not Loader.Load(FileName, e_SoundsArray[find_id].isMusic) then
294 begin
295 e_LogWritefln('Could not load sound `%s`', [FileName]);
296 exit;
297 end;
299 alGetError(); // reset error state, god damn it
301 if not Loader.Streaming then
302 begin
303 alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
304 if CheckALError() then
305 begin
306 e_LogWritefln('Could not create AL buffer for `%s`: %s', [FileName, GetALError()]);
307 Loader.Free();
308 exit;
309 end;
311 OutLen := Loader.GetAll(OutData);
312 alBufferData(
313 e_SoundsArray[find_id].alBuffer,
314 GetALSoundFormat(Loader.Format),
315 OutData,
316 OutLen,
317 Loader.Format.SampleRate
318 );
320 // don't need this anymore
321 Loader.Free();
322 Loader := nil;
324 if CheckALError() then
325 begin
326 e_LogWriteln('AL: what the fuck: ' + GetALError());
327 alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
328 e_SoundsArray[find_id].alBuffer := 0;
329 exit;
330 end;
331 end
332 else
333 begin
334 e_SoundsArray[find_id].alBuffer := 0;
335 e_SoundsArray[find_id].Loader := Loader;
336 end;
338 ID := find_id;
339 Result := True;
340 end;
342 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
343 var
344 find_id: DWORD;
345 Loader: TSoundLoader;
346 OutData: Pointer;
347 OutLen: LongWord;
348 begin
349 ID := NO_SOUND_ID;
350 Result := False;
352 find_id := FindESound();
354 e_SoundsArray[find_id].Loader := nil;
355 e_SoundsArray[find_id].isMusic := isMusic;
356 e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop;
357 e_SoundsArray[find_id].nRefs := 0;
359 Loader := e_GetSoundLoader(pData, LongWord(Length));
360 if Loader = nil then
361 begin
362 e_LogWritefln('Could not find loader for sound `%p`', [pData]);
363 exit;
364 end;
366 Loader.Looping := e_SoundsArray[find_id].Loops;
368 if not Loader.Load(pData, LongWord(Length), e_SoundsArray[find_id].isMusic) then
369 begin
370 e_LogWritefln('Could not load sound `%p`', [pData]);
371 exit;
372 end;
374 alGetError(); // reset error state, god damn it
376 if not Loader.Streaming then
377 begin
378 alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
379 if CheckALError() then
380 begin
381 e_LogWritefln('Could not create AL buffer for `%p`: %s', [pData, GetALError()]);
382 Loader.Free();
383 exit;
384 end;
386 OutLen := Loader.GetAll(OutData);
387 alBufferData(
388 e_SoundsArray[find_id].alBuffer,
389 GetALSoundFormat(Loader.Format),
390 OutData,
391 OutLen,
392 Loader.Format.SampleRate
393 );
395 // don't need this anymore
396 Loader.Free();
397 Loader := nil;
399 if CheckALError() then
400 begin
401 e_LogWriteln('AL: what the fuck: ' + GetALError());
402 alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
403 e_SoundsArray[find_id].alBuffer := 0;
404 exit;
405 end;
406 end
407 else
408 begin
409 e_SoundsArray[find_id].alBuffer := 0;
410 e_SoundsArray[find_id].Loader := Loader;
411 end;
413 ID := find_id;
414 Result := True;
415 end;
417 function FindSourceForSound(ID: DWORD): Integer;
418 var
419 S: Integer;
420 begin
421 Result := -1;
422 if ID > High(e_SoundsArray) then
423 exit;
425 if e_SoundsArray[ID].Loader <> nil then
426 begin
427 // first source is for streaming sounds
428 // it always exists
429 alOwners[MUSIC_SOURCE] := nil;
430 Result := MUSIC_SOURCE;
431 exit;
432 end;
434 for S := 1 to High(alSources) do
435 if alSources[S] = 0 then
436 begin
437 alOwners[S] := nil; // TBasicSounds will set this if needed
438 Result := S;
439 break;
440 end;
442 if Result = -1 then Exit; // no voices left
444 alGetError(); // reset error state
445 alGenSources(1, @alSources[Result]);
446 if CheckALError() then
447 begin
448 e_LogWriteln('AL: FindSourceForSound(): alGenSources() failed: ' + GetALError());
449 Result := -1;
450 end;
451 end;
453 procedure AssignSound(ID: DWORD; Src: ALuint); inline;
454 var
455 S: ALint;
456 begin
457 alGetError(); // reset error state
459 if e_SoundsArray[ID].Loader <> nil then
460 begin
461 // this is a stream
462 // reset position
463 e_SoundsArray[ID].Loader.SetPosition(0);
464 if CurStream <> ID then // changing streams
465 begin
466 alSourceStop(Src); // this should mark all buffers as processed
467 alGetSourcei(Src, AL_BUFFERS_PROCESSED, S);
468 // unqueue all buffers
469 if S > 0 then
470 begin
471 alSourceUnqueueBuffers(Src, S, @alStreamBufs[alStreamAvail]);
472 alStreamAvail := NUM_STREAM_BUFFERS;
473 end;
474 end;
475 // this shit is playing now
476 CurStream := ID;
477 end
478 else
479 begin
480 // this is a full chunk, assign local buffer
481 alSourcei(Src, AL_BUFFER, e_SoundsArray[ID].alBuffer);
482 // these can loop
483 if (e_SoundsArray[ID].Loops) then
484 alSourcei(Src, AL_LOOPING, AL_TRUE)
485 else
486 alSourcei(Src, AL_LOOPING, AL_FALSE);
487 end;
489 alSourcei(Src, AL_SOURCE_RELATIVE, AL_TRUE);
490 end;
492 function e_PlaySound(ID: DWORD): Integer;
493 begin
494 Result := FindSourceForSound(ID);
495 if Result >= 0 then
496 begin
497 AssignSound(ID, alSources[Result]);
498 alSourcef(alSources[Result], AL_GAIN, 1);
499 alSourcefv(alSources[Result], AL_POSITION, e_ZeroPosition);
500 alSourcePlay(alSources[Result]);
501 end;
502 end;
504 function e_PlaySoundPan(ID: DWORD; Pan: Single): Integer;
505 var
506 Pos: array [0..2] of ALfloat;
507 begin
508 Result := FindSourceForSound(ID);
509 if Result >= 0 then
510 begin
511 Pos[0] := Pan;
512 Pos[1] := 0;
513 Pos[2] := 0;
514 AssignSound(ID, alSources[Result]);
515 alSourcef(alSources[Result], AL_GAIN, 1);
516 alSourcefv(alSources[Result], AL_POSITION, Pos);
517 alSourcePlay(alSources[Result]);
518 end;
519 end;
521 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
522 begin
523 Result := FindSourceForSound(ID);
524 if Result >= 0 then
525 begin
526 AssignSound(ID, alSources[Result]);
527 alSourcef(alSources[Result], AL_GAIN, Volume);
528 alSourcefv(alSources[Result], AL_POSITION, e_ZeroPosition);
529 alSourcePlay(alSources[Result]);
530 end;
531 end;
533 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
534 var
535 Pos: array [0..2] of ALfloat;
536 begin
537 Result := FindSourceForSound(ID);
538 if Result >= 0 then
539 begin
540 Pos[0] := Pan;
541 Pos[1] := 0;
542 Pos[2] := 0;
543 AssignSound(ID, alSources[Result]);
544 alSourcefv(alSources[Result], AL_POSITION, Pos);
545 alSourcef(alSources[Result], AL_GAIN, Volume);
546 alSourcePlay(alSources[Result]);
547 end;
548 end;
550 procedure e_DeleteSound(ID: DWORD);
551 begin
552 if ID > High(e_SoundsArray) then
553 exit;
554 if (e_SoundsArray[ID].alBuffer <> 0) then
555 begin
556 alDeleteBuffers(1, Addr(e_SoundsArray[ID].alBuffer));
557 e_SoundsArray[ID].alBuffer := 0;
558 end;
559 if (e_SoundsArray[ID].Loader <> nil) then
560 begin
561 e_SoundsArray[ID].Loader.Free();
562 e_SoundsArray[ID].Loader := nil;
563 if ID = CurStream then
564 CurStream := NO_SOUND_ID;
565 end;
566 end;
568 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
569 var
570 S: Integer;
571 V: ALfloat;
572 begin
573 // TODO: replace manual volume calculations everywhere with
574 // alListenerf(AL_GAIN) or something
575 if setMode then
576 begin
577 for S := 1 to High(alSources) do
578 if alSources[S] <> 0 then
579 alSourcef(alSources[S], AL_GAIN, SoundMod)
580 end
581 else
582 begin
583 for S := 1 to High(alSources) do
584 if alSources[S] <> 0 then
585 begin
586 alGetSourcef(alSources[S], AL_GAIN, V);
587 alSourcef(alSources[S], AL_GAIN, V * SoundMod);
588 end;
589 end;
590 end;
592 procedure e_MuteChannels(Enable: Boolean);
593 begin
594 if Enable = SoundMuted then
595 Exit;
597 SoundMuted := Enable;
598 end;
600 procedure e_StopChannels();
601 var
602 S: Integer;
603 begin
604 alGetError(); // reset error state
605 for S := Low(alSources) to High(alSources) do
606 if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_PLAYING) then
607 begin
608 alSourceStop(alSources[S]);
609 alDeleteSources(1, @alSources[S]);
610 alSources[S] := 0;
611 end;
612 end;
614 procedure e_RemoveAllSounds();
615 var
616 i: Integer;
617 begin
618 for i := 0 to High(e_SoundsArray) do
619 if e_SoundsArray[i].alBuffer <> 0 then
620 e_DeleteSound(i);
621 SetLength(e_SoundsArray, 0);
622 e_SoundsArray := nil;
623 CurStream := NO_SOUND_ID;
624 end;
626 procedure e_ReleaseSoundSystem();
627 begin
628 e_RemoveAllSounds();
630 alcMakeContextCurrent(nil);
631 alcDestroyContext(alContext);
632 alcCloseDevice(alDevice);
634 alDevice := nil;
635 alContext := nil;
636 end;
638 procedure UpdateStreamSource(Src: Integer);
639 var
640 OutLen: LongWord;
641 Buf: ALuint;
642 S: Integer;
643 begin
644 if alSources[Src] = 0 then Exit;
646 alGetError(); // reset error state
648 alGetSourcei(alSources[Src], AL_BUFFERS_PROCESSED, S);
649 // unqueue processed buffers
650 if S > 0 then
651 begin
652 alSourceUnqueueBuffers(alSources[Src], S, @alStreamBufs[alStreamAvail]);
653 alStreamAvail := alStreamAvail + S;
654 end;
656 alGetError(); // reset error state
658 if (alStreamAvail > 0) and (CurStream <> NO_SOUND_ID) then
659 begin
660 // some buffers have freed up, advance stream playback
661 OutLen := e_SoundsArray[CurStream].Loader.FillBuffer(@alStreamData[0], STREAM_BUFSIZE);
662 if OutLen = 0 then Exit; // ran out of stream
663 Buf := alStreamBufs[alStreamAvail-1];
664 Dec(alStreamAvail);
665 // upload
666 alBufferData(
667 Buf,
668 GetALSoundFormat(e_SoundsArray[CurStream].Loader.Format),
669 @alStreamData[0],
670 OutLen,
671 e_SoundsArray[CurStream].Loader.Format.SampleRate
672 );
673 // attach
674 alSourceQueueBuffers(alSources[Src], 1, @Buf);
675 // restart if needed
676 S := GetALSourceState(alSources[Src]);
677 if (S = AL_STOPPED) or (S = AL_INITIAL) then
678 alSourcePlay(alSources[Src]);
679 end;
680 end;
682 procedure e_SoundUpdate();
683 var
684 S: Integer;
685 begin
686 alGetError(); // reset error state
688 // clear out all stopped sources
689 for S := 1 to High(alSources) do
690 if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_STOPPED) then
691 begin
692 alDeleteSources(1, @alSources[S]);
693 alSources[S] := 0;
694 alOwners[S] := nil;
695 end;
697 // update the stream sources
698 UpdateStreamSource(MUSIC_SOURCE);
699 end;
701 { TBasicSound: }
703 constructor TBasicSound.Create();
704 begin
705 FID := NO_SOUND_ID;
706 FMusic := False;
707 FSource := -1;
708 FPosition := 0;
709 FMuted := False;
710 FOldGain := 1;
711 end;
713 destructor TBasicSound.Destroy();
714 begin
715 FreeSound();
716 inherited;
717 end;
719 function TBasicSound.InvalidSource(): Boolean; inline;
720 begin
721 Result := (FSource < 0) or (alSources[FSource] = 0) or (alOwners[FSource] <> self);
722 end;
724 procedure TBasicSound.FreeSound();
725 begin
726 if FID = NO_SOUND_ID then
727 Exit;
729 Stop();
730 FID := NO_SOUND_ID;
731 FMusic := False;
732 FPosition := 0;
733 end;
735 function TBasicSound.RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
736 begin
737 Result := False;
738 if FID = NO_SOUND_ID then Exit;
740 if e_SoundsArray[FID].nRefs >= gMaxSimSounds then
741 begin
742 Result := True;
743 Exit;
744 end;
746 FSource := e_PlaySoundPanVolume(FID, Pan, Volume);
747 if FSource >= 0 then
748 begin
749 alOwners[FSource] := self;
750 Result := True;
751 end;
752 end;
754 procedure TBasicSound.SetID(ID: DWORD);
755 begin
756 FreeSound();
758 if ID > High(e_SoundsArray) then
759 exit;
761 FID := ID;
762 FMusic := e_SoundsArray[ID].isMusic;
763 end;
765 function TBasicSound.IsPlaying(): Boolean;
766 begin
767 Result := False;
768 if InvalidSource() then
769 Exit;
770 Result := GetALSourceState(alSources[FSource]) = AL_PLAYING;
771 end;
773 procedure TBasicSound.Stop();
774 begin
775 if FID = CurStream then
776 CurStream := NO_SOUND_ID;
777 if InvalidSource() then
778 Exit;
779 GetPosition();
780 alSourceStop(alSources[FSource]);
781 end;
783 function TBasicSound.IsPaused(): Boolean;
784 begin
785 Result := False;
786 if InvalidSource() then
787 Exit;
788 Result := GetALSourceState(alSources[FSource]) = AL_PAUSED;
789 end;
791 procedure TBasicSound.Pause(Enable: Boolean);
792 begin
793 if InvalidSource() then
794 Exit;
795 if Enable then
796 alSourcePause(alSources[FSource])
797 else
798 alSourcePlay(alSources[FSource]);
799 end;
801 function TBasicSound.GetVolume(): Single;
802 begin
803 Result := 0.0;
804 if InvalidSource() then
805 Exit;
806 alGetSourcef(alSources[FSource], AL_GAIN, Result);
807 end;
809 procedure TBasicSound.SetVolume(Volume: Single);
810 begin
811 if InvalidSource() then
812 Exit;
813 alSourcef(alSources[FSource], AL_GAIN, Volume);
814 end;
816 function TBasicSound.GetPan(): Single;
817 var
818 Pos: array [0..2] of ALfloat;
819 begin
820 Result := 0.0;
821 if InvalidSource() then
822 Exit;
823 alGetSourcefv(alSources[FSource], AL_POSITION, Pos);
824 Result := Pos[0];
825 end;
827 procedure TBasicSound.SetPan(Pan: Single);
828 var
829 Pos: array [0..2] of ALfloat;
830 begin
831 if InvalidSource() then
832 Exit;
833 Pos[0] := Pan;
834 Pos[1] := 0;
835 Pos[2] := 0;
836 alSourcefv(alSources[FSource], AL_POSITION, Pos);
837 end;
839 function TBasicSound.IsMuted(): Boolean;
840 begin
841 if InvalidSource() then
842 Result := False
843 else
844 Result := FMuted;
845 end;
847 procedure TBasicSound.Mute(Enable: Boolean);
848 begin
849 if InvalidSource() then
850 Exit;
851 if Enable then
852 begin
853 FOldGain := GetVolume();
854 FMuted := True;
855 SetVolume(0);
856 end
857 else if FMuted then
858 begin
859 FMuted := False;
860 SetVolume(FOldGain);
861 end;
862 end;
864 function TBasicSound.GetPosition(): DWORD;
865 var
866 Bytes: ALint;
867 begin
868 Result := 0;
869 if InvalidSource() then
870 Exit;
871 alGetSourcei(alSources[FSource], AL_BYTE_OFFSET, Bytes);
872 FPosition := Bytes;
873 Result := FPosition;
874 end;
876 procedure TBasicSound.SetPosition(aPos: DWORD);
877 begin
878 FPosition := aPos;
879 if InvalidSource() then
880 Exit;
881 alSourcei(alSources[FSource], AL_BYTE_OFFSET, aPos);
882 end;
884 procedure TBasicSound.SetPriority(priority: Integer);
885 begin
886 end;
888 end.