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, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 interface
17 uses
18 AL,
19 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
20 e_soundfile,
21 e_log,
22 SysUtils;
24 type
25 TSoundRec = record
26 Loader: TSoundLoader;
27 alBuffer: ALuint;
28 isMusic: Boolean;
29 Loops: Boolean;
30 nRefs: Integer;
31 end;
33 TBasicSound = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
34 private
35 FSource: Integer;
36 FOldGain: ALfloat;
37 FMuted: Boolean;
39 function InvalidSource(): Boolean; inline;
41 protected
42 FID: DWORD;
43 FMusic: Boolean;
44 FPosition: DWORD;
46 function RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
48 public
49 constructor Create();
50 destructor Destroy(); override;
51 procedure SetID(ID: DWORD);
52 procedure FreeSound();
53 function IsPlaying(): Boolean;
54 procedure Stop();
55 function IsPaused(): Boolean;
56 procedure Pause(Enable: Boolean);
57 function GetVolume(): Single;
58 procedure SetVolume(Volume: Single);
59 function GetPan(): Single;
60 procedure SetPan(Pan: Single);
61 function IsMuted(): Boolean;
62 procedure Mute(Enable: Boolean);
63 function GetPosition(): DWORD;
64 procedure SetPosition(aPos: DWORD);
65 procedure SetPriority(priority: Integer);
66 end;
68 const
69 NO_SOUND_ID = DWORD(-1);
71 function e_InitSoundSystem(NoOutput: Boolean = False): Boolean;
73 function e_LoadSound(FileName: string; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
74 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
76 function e_PlaySound(ID: DWORD): Integer;
77 function e_PlaySoundPan(ID: DWORD; Pan: Single): Integer;
78 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
79 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
81 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
82 procedure e_MuteChannels(Enable: Boolean);
83 procedure e_StopChannels();
85 procedure e_DeleteSound(ID: DWORD);
86 procedure e_RemoveAllSounds();
87 procedure e_ReleaseSoundSystem();
88 procedure e_SoundUpdate();
90 var
91 e_SoundFormat: TSoundFormat; // desired sound format
92 e_SoundsArray: array of TSoundRec = nil;
93 e_ZeroPosition: array [0..2] of ALfloat = (0, 0, 0);
94 e_ALError: ALenum = 0;
95 e_SoundFont: string = '';
96 e_MusicLerp: Boolean = True;
98 implementation
100 uses
101 g_options, utils;
103 const
104 NUM_SOURCES = 255; // + 1 stereo
105 NUM_STREAM_BUFFERS = 8;
106 STREAM_BUFSIZE = 8192;
107 MUSIC_SOURCE = 0;
109 var
110 SoundMuted: Boolean = False;
111 CurStream: DWORD = NO_SOUND_ID;
112 alDevice: PALCdevice = nil;
113 alContext: PALCcontext = nil;
114 // sources for everything
115 alSources: array [0..NUM_SOURCES] of ALuint;
116 // last TBasicSound that has played on each source
117 alOwners: array [0..NUM_SOURCES] of TBasicSound;
118 // buffers for the music stream
119 alStreamBufs: array [0..NUM_STREAM_BUFFERS-1] of ALuint;
120 alStreamData: array [0..STREAM_BUFSIZE-1] of Byte;
121 alStreamAvail: Integer = NUM_STREAM_BUFFERS;
123 function CheckALError(): Boolean;
124 begin
125 e_ALError := alGetError();
126 Result := e_ALError <> AL_NO_ERROR;
127 end;
129 function GetALError(): string;
130 begin
131 Result := '';
132 case e_ALError of
133 AL_NO_ERROR: Result := '';
134 AL_INVALID_NAME: Result := 'AL_INVALID_NAME';
135 AL_INVALID_ENUM: Result := 'AL_INVALID_ENUM';
136 AL_INVALID_VALUE: Result := 'AL_INVALID_VALUE';
137 AL_INVALID_OPERATION: Result := 'AL_INVALID_OPERATION';
138 AL_OUT_OF_MEMORY: Result := 'AL_OUT_OF_MEMORY';
139 else Result := Format('unknown error %x', [e_ALError]);
140 end;
141 end;
143 function e_InitSoundSystem(NoOutput: Boolean = False): Boolean;
144 var
145 alExt, alRend, alVendor, alVer: string;
146 WantDev: string = '';
147 WantAttrs: array [0..4] of ALCint = (
148 ALC_STEREO_SOURCES, 1,
149 ALC_MONO_SOURCES, NUM_SOURCES,
150 0
151 );
152 begin
153 Result := False;
155 WantDev := alcGetString(nil, ALC_DEVICE_SPECIFIER);
156 e_LogWritefln('AL: available devices: %s', [WantDev]);
158 // TODO: open a dummy device when NoOutput is true or something
159 WantDev := alcGetString(nil, ALC_DEFAULT_DEVICE_SPECIFIER);
160 e_LogWritefln('AL: trying to open device %s', [WantDev]);
162 alDevice := alcOpenDevice(PChar(WantDev));
163 if alDevice = nil then
164 begin
165 e_LogWritefln('AL: ERROR: could not open device %s: %s', [WantDev, GetALError()]);
166 exit;
167 end;
169 alContext := alcCreateContext(alDevice, WantAttrs);
170 if alContext = nil then
171 begin
172 e_LogWritefln('AL: ERROR: could not create context: %s', [GetALError()]);
173 alcCloseDevice(alDevice);
174 alDevice := nil;
175 exit;
176 end;
178 alcMakeContextCurrent(alContext);
180 // TODO: actually parse these from alc attributes or something
181 e_SoundFormat.SampleRate := 48000;
182 e_SoundFormat.SampleBits := 16;
183 e_SoundFormat.Channels := 2;
185 alVendor := alGetString(AL_VENDOR);
186 alRend := alGetString(AL_RENDERER);
187 alVer := alGetString(AL_VERSION);
188 alExt := alGetString(AL_EXTENSIONS);
190 e_LogWriteln('AL INFO:');
191 e_LogWriteln(' Version: ' + alVer);
192 e_LogWriteln(' Vendor: ' + alVendor);
193 e_LogWriteln(' Renderer: ' + alRend);
194 e_LogWriteln(' Device: ' + WantDev);
195 e_LogWriteln(' Sample rate: ' + IntToStr(e_SoundFormat.SampleRate));
196 e_LogWriteln(' Extensions:');
197 e_LogWriteln(' ' + alExt);
199 ZeroMemory(@alSources[0], sizeof(alSources));
200 ZeroMemory(@alOwners[0], sizeof(alOwners));
201 ZeroMemory(@alStreamBufs[0], sizeof(alStreamBufs));
202 ZeroMemory(@alStreamData[0], sizeof(alStreamData));
203 CurStream := NO_SOUND_ID;
205 alGetError(); // reset the goddamn error state
206 alGenSources(1, @alSources[0]); // generate the music source
207 if CheckALError() then
208 e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError());
210 alStreamAvail := 0;
211 alGenBuffers(NUM_STREAM_BUFFERS, @alStreamBufs[0]); // generate buffers for the music stream
212 if CheckALError() then
213 e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError())
214 else
215 alStreamAvail := NUM_STREAM_BUFFERS;
217 Result := True;
218 end;
220 function FindESound(): DWORD;
221 var
222 i: Integer;
224 begin
225 if e_SoundsArray <> nil then
226 for i := 0 to High(e_SoundsArray) do
227 if (e_SoundsArray[i].alBuffer = 0) and (e_SoundsArray[i].Loader = nil) then
228 begin
229 Result := i;
230 Exit;
231 end;
233 if e_SoundsArray = nil then
234 begin
235 SetLength(e_SoundsArray, 16);
236 Result := 0;
237 end
238 else
239 begin
240 Result := High(e_SoundsArray) + 1;
241 SetLength(e_SoundsArray, Length(e_SoundsArray) + 16);
242 end;
243 end;
245 function GetALSoundFormat(Fmt: TSoundFormat): ALenum; inline;
246 begin
247 if Fmt.Channels = 2 then
248 begin
249 if Fmt.SampleBits = 16 then
250 Result := AL_FORMAT_STEREO16
251 else
252 Result := AL_FORMAT_STEREO8;
253 end
254 else
255 begin
256 if Fmt.SampleBits = 16 then
257 Result := AL_FORMAT_MONO16
258 else
259 Result := AL_FORMAT_MONO8;
260 end;
261 end;
263 function GetALSourceState(S: ALuint): ALint; inline;
264 begin
265 alGetSourcei(S, AL_SOURCE_STATE, Result);
266 end;
268 function LoadEntireSound(var Snd: TSoundRec; Loader: TSoundLoader): Boolean;
269 var
270 Frame: Pointer;
271 Data: Pointer;
272 Rx: LongWord;
273 DataLen, OldLen: LongWord;
274 const
275 CHUNK_SIZE = 65536 * 2 * 2;
276 begin
277 Result := False;
279 Frame := GetMem(CHUNK_SIZE);
280 if Frame = nil then exit;
282 Data := nil;
283 DataLen := 0;
285 repeat
286 Rx := Loader.FillBuffer(Frame, CHUNK_SIZE);
287 if Rx = 0 then break;
289 OldLen := DataLen;
290 DataLen := DataLen + Rx;
291 Data := ReAllocMem(Data, DataLen);
292 if Data = nil then begin FreeMem(Frame); exit; end;
294 Move(Frame^, (Data + OldLen)^, Rx);
295 until Loader.Finished();
297 FreeMem(Frame);
299 alGenBuffers(1, Addr(Snd.alBuffer));
300 if CheckALError() then
301 begin
302 e_LogWritefln('AL: Could not create AL buffer: %s', [GetALError()]);
303 FreeMem(Data);
304 exit;
305 end;
307 alBufferData(
308 Snd.alBuffer,
309 GetALSoundFormat(Loader.Format),
310 Data,
311 DataLen,
312 Loader.Format.SampleRate
313 );
315 FreeMem(Data);
317 if CheckALError() then
318 begin
319 e_LogWriteln('AL: Could not fill AL buffer: ' + GetALError());
320 alDeleteBuffers(1, Addr(Snd.alBuffer));
321 Snd.alBuffer := 0;
322 exit;
323 end;
325 Result := True;
326 end;
328 function e_LoadSound(FileName: String; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
329 var
330 find_id: DWORD;
331 Loader: TSoundLoader;
332 begin
333 ID := NO_SOUND_ID;
334 Result := False;
336 find_id := FindESound();
338 e_SoundsArray[find_id].Loader := nil;
339 e_SoundsArray[find_id].isMusic := isMusic;
340 e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop;
341 e_SoundsArray[find_id].nRefs := 0;
343 Loader := e_GetSoundLoader(FileName);
344 if Loader = nil then
345 begin
346 e_LogWritefln('Could not find loader for sound `%s`', [FileName]);
347 exit;
348 end;
350 if not Loader.Load(FileName, e_SoundsArray[find_id].Loops) then
351 begin
352 e_LogWritefln('Could not load sound `%s`', [FileName]);
353 exit;
354 end;
356 alGetError(); // reset error state, god damn it
358 if not isMusic then
359 begin
360 if not LoadEntireSound(e_SoundsArray[find_id], Loader) then
361 e_LogWritefln('AL: Could not buffer sound effect `%s`', [FileName]);
362 // don't need this anymore
363 Loader.Free();
364 Loader := nil;
365 end
366 else
367 begin
368 e_SoundsArray[find_id].alBuffer := 0;
369 e_SoundsArray[find_id].Loader := Loader;
370 end;
372 ID := find_id;
373 Result := True;
374 end;
376 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
377 var
378 find_id: DWORD;
379 Loader: TSoundLoader;
380 begin
381 ID := NO_SOUND_ID;
382 Result := False;
384 find_id := FindESound();
386 e_SoundsArray[find_id].Loader := nil;
387 e_SoundsArray[find_id].isMusic := isMusic;
388 e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop;
389 e_SoundsArray[find_id].nRefs := 0;
391 Loader := e_GetSoundLoader(pData, LongWord(Length));
392 if Loader = nil then
393 begin
394 e_LogWritefln('Could not find loader for sound `%p`', [pData]);
395 exit;
396 end;
398 if not Loader.Load(pData, LongWord(Length), e_SoundsArray[find_id].Loops) then
399 begin
400 e_LogWritefln('Could not load sound `%p`', [pData]);
401 exit;
402 end;
404 alGetError(); // reset error state, god damn it
406 if not isMusic then
407 begin
408 if not LoadEntireSound(e_SoundsArray[find_id], Loader) then
409 e_LogWritefln('AL: Could not buffer sound effect `%p`', [pData]);
410 // don't need this anymore
411 Loader.Free();
412 Loader := nil;
413 end
414 else
415 begin
416 e_SoundsArray[find_id].alBuffer := 0;
417 e_SoundsArray[find_id].Loader := Loader;
418 end;
420 // the calling side won't free this, the loader will get a copy, so fuck it
421 FreeMem(pData);
422 ID := find_id;
423 Result := True;
424 end;
426 function FindSourceForSound(ID: DWORD): Integer;
427 var
428 S: Integer;
429 begin
430 Result := -1;
431 if ID > High(e_SoundsArray) then
432 exit;
434 if e_SoundsArray[ID].Loader <> nil then
435 begin
436 // first source is for streaming sounds
437 // it always exists
438 alOwners[MUSIC_SOURCE] := nil;
439 Result := MUSIC_SOURCE;
440 exit;
441 end;
443 for S := 1 to High(alSources) do
444 if alSources[S] = 0 then
445 begin
446 alOwners[S] := nil; // TBasicSounds will set this if needed
447 Result := S;
448 break;
449 end;
451 if Result = -1 then Exit; // no voices left
453 alGetError(); // reset error state
454 alGenSources(1, @alSources[Result]);
455 if CheckALError() then
456 begin
457 e_LogWriteln('AL: FindSourceForSound(): alGenSources() failed: ' + GetALError());
458 Result := -1;
459 end;
460 end;
462 procedure AssignSound(ID: DWORD; Src: ALuint); inline;
463 var
464 S: ALint;
465 begin
466 alGetError(); // reset error state
468 if e_SoundsArray[ID].Loader <> nil then
469 begin
470 // this is a stream
471 // reset position
472 e_SoundsArray[ID].Loader.Restart();
473 if CurStream <> ID then // changing streams
474 begin
475 alSourceStop(Src); // this should mark all buffers as processed
476 alGetSourcei(Src, AL_BUFFERS_PROCESSED, S);
477 // unqueue all buffers
478 if S > 0 then
479 begin
480 alSourceUnqueueBuffers(Src, S, @alStreamBufs[alStreamAvail]);
481 alStreamAvail := NUM_STREAM_BUFFERS;
482 end;
483 end;
484 // this shit is playing now
485 CurStream := ID;
486 end
487 else
488 begin
489 // this is a full chunk, assign local buffer
490 alSourcei(Src, AL_BUFFER, e_SoundsArray[ID].alBuffer);
491 // these can loop
492 if (e_SoundsArray[ID].Loops) then
493 alSourcei(Src, AL_LOOPING, AL_TRUE)
494 else
495 alSourcei(Src, AL_LOOPING, AL_FALSE);
496 end;
498 alSourcei(Src, AL_SOURCE_RELATIVE, AL_TRUE);
499 end;
501 function e_PlaySound(ID: DWORD): Integer;
502 begin
503 Result := FindSourceForSound(ID);
504 if Result >= 0 then
505 begin
506 AssignSound(ID, alSources[Result]);
507 alSourcef(alSources[Result], AL_GAIN, 1);
508 alSourcefv(alSources[Result], AL_POSITION, e_ZeroPosition);
509 alSourcePlay(alSources[Result]);
510 end;
511 end;
513 function e_PlaySoundPan(ID: DWORD; Pan: Single): Integer;
514 var
515 Pos: array [0..2] of ALfloat;
516 begin
517 Result := FindSourceForSound(ID);
518 if Result >= 0 then
519 begin
520 Pos[0] := Pan;
521 Pos[1] := 0;
522 Pos[2] := 0;
523 AssignSound(ID, alSources[Result]);
524 alSourcef(alSources[Result], AL_GAIN, 1);
525 alSourcefv(alSources[Result], AL_POSITION, Pos);
526 alSourcePlay(alSources[Result]);
527 end;
528 end;
530 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
531 begin
532 Result := FindSourceForSound(ID);
533 if Result >= 0 then
534 begin
535 AssignSound(ID, alSources[Result]);
536 alSourcef(alSources[Result], AL_GAIN, Volume);
537 alSourcefv(alSources[Result], AL_POSITION, e_ZeroPosition);
538 alSourcePlay(alSources[Result]);
539 end;
540 end;
542 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
543 var
544 Pos: array [0..2] of ALfloat;
545 begin
546 Result := FindSourceForSound(ID);
547 if Result >= 0 then
548 begin
549 Pos[0] := Pan;
550 Pos[1] := 0;
551 Pos[2] := 0;
552 AssignSound(ID, alSources[Result]);
553 alSourcefv(alSources[Result], AL_POSITION, Pos);
554 alSourcef(alSources[Result], AL_GAIN, Volume);
555 alSourcePlay(alSources[Result]);
556 end;
557 end;
559 procedure e_DeleteSound(ID: DWORD);
560 begin
561 if ID > High(e_SoundsArray) then
562 exit;
563 if (e_SoundsArray[ID].alBuffer <> 0) then
564 begin
565 alDeleteBuffers(1, Addr(e_SoundsArray[ID].alBuffer));
566 e_SoundsArray[ID].alBuffer := 0;
567 end;
568 if (e_SoundsArray[ID].Loader <> nil) then
569 begin
570 e_SoundsArray[ID].Loader.Free();
571 e_SoundsArray[ID].Loader := nil;
572 if ID = CurStream then
573 CurStream := NO_SOUND_ID;
574 end;
575 end;
577 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
578 var
579 S: Integer;
580 V: ALfloat;
581 begin
582 // TODO: replace manual volume calculations everywhere with
583 // alListenerf(AL_GAIN) or something
584 if setMode then
585 begin
586 for S := 1 to High(alSources) do
587 if alSources[S] <> 0 then
588 alSourcef(alSources[S], AL_GAIN, SoundMod)
589 end
590 else
591 begin
592 for S := 1 to High(alSources) do
593 if alSources[S] <> 0 then
594 begin
595 alGetSourcef(alSources[S], AL_GAIN, V);
596 alSourcef(alSources[S], AL_GAIN, V * SoundMod);
597 end;
598 end;
599 end;
601 procedure e_MuteChannels(Enable: Boolean);
602 begin
603 if Enable = SoundMuted then
604 Exit;
606 SoundMuted := Enable;
607 end;
609 procedure e_StopChannels();
610 var
611 S: Integer;
612 begin
613 alGetError(); // reset error state
614 for S := Low(alSources) to High(alSources) do
615 if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_PLAYING) then
616 begin
617 alSourceStop(alSources[S]);
618 alDeleteSources(1, @alSources[S]);
619 alSources[S] := 0;
620 end;
621 end;
623 procedure e_RemoveAllSounds();
624 var
625 i: Integer;
626 begin
627 for i := 0 to High(e_SoundsArray) do
628 if e_SoundsArray[i].alBuffer <> 0 then
629 e_DeleteSound(i);
630 SetLength(e_SoundsArray, 0);
631 e_SoundsArray := nil;
632 CurStream := NO_SOUND_ID;
633 end;
635 procedure e_ReleaseSoundSystem();
636 begin
637 e_RemoveAllSounds();
639 alcMakeContextCurrent(nil);
640 alcDestroyContext(alContext);
641 alcCloseDevice(alDevice);
643 alDevice := nil;
644 alContext := nil;
645 end;
647 procedure UpdateStreamSource(Src: Integer);
648 var
649 OutLen: LongWord;
650 Buf: ALuint;
651 S: Integer;
652 begin
653 if alSources[Src] = 0 then Exit;
655 alGetError(); // reset error state
657 alGetSourcei(alSources[Src], AL_BUFFERS_PROCESSED, S);
658 // unqueue processed buffers
659 if S > 0 then
660 begin
661 alSourceUnqueueBuffers(alSources[Src], S, @alStreamBufs[alStreamAvail]);
662 alStreamAvail := alStreamAvail + S;
663 end;
665 alGetError(); // reset error state
667 if (alStreamAvail > 0) and (CurStream <> NO_SOUND_ID) then
668 begin
669 // some buffers have freed up, advance stream playback
670 OutLen := e_SoundsArray[CurStream].Loader.FillBuffer(@alStreamData[0], STREAM_BUFSIZE);
671 if OutLen = 0 then Exit; // ran out of stream
672 Buf := alStreamBufs[alStreamAvail-1];
673 Dec(alStreamAvail);
674 // upload
675 alBufferData(
676 Buf,
677 GetALSoundFormat(e_SoundsArray[CurStream].Loader.Format),
678 @alStreamData[0],
679 OutLen,
680 e_SoundsArray[CurStream].Loader.Format.SampleRate
681 );
682 // attach
683 alSourceQueueBuffers(alSources[Src], 1, @Buf);
684 // restart if needed
685 S := GetALSourceState(alSources[Src]);
686 if (S = AL_STOPPED) or (S = AL_INITIAL) then
687 alSourcePlay(alSources[Src]);
688 end;
689 end;
691 procedure e_SoundUpdate();
692 var
693 S: Integer;
694 begin
695 alGetError(); // reset error state
697 // clear out all stopped sources
698 for S := 1 to High(alSources) do
699 if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_STOPPED) then
700 begin
701 alDeleteSources(1, @alSources[S]);
702 alSources[S] := 0;
703 alOwners[S] := nil;
704 end;
706 // update the stream sources
707 UpdateStreamSource(MUSIC_SOURCE);
708 end;
710 { TBasicSound: }
712 constructor TBasicSound.Create();
713 begin
714 FID := NO_SOUND_ID;
715 FMusic := False;
716 FSource := -1;
717 FPosition := 0;
718 FMuted := False;
719 FOldGain := 1;
720 end;
722 destructor TBasicSound.Destroy();
723 begin
724 FreeSound();
725 inherited;
726 end;
728 function TBasicSound.InvalidSource(): Boolean; inline;
729 begin
730 Result := (FSource < 0) or (alSources[FSource] = 0) or (alOwners[FSource] <> self);
731 end;
733 procedure TBasicSound.FreeSound();
734 begin
735 if FID = NO_SOUND_ID then
736 Exit;
738 Stop();
739 FID := NO_SOUND_ID;
740 FMusic := False;
741 FPosition := 0;
742 end;
744 function TBasicSound.RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
745 begin
746 Result := False;
747 if FID = NO_SOUND_ID then Exit;
749 if e_SoundsArray[FID].nRefs >= gMaxSimSounds then
750 begin
751 Result := True;
752 Exit;
753 end;
755 FSource := e_PlaySoundPanVolume(FID, Pan, Volume);
756 if FSource >= 0 then
757 begin
758 alOwners[FSource] := self;
759 Result := True;
760 end;
761 end;
763 procedure TBasicSound.SetID(ID: DWORD);
764 begin
765 FreeSound();
767 if ID > High(e_SoundsArray) then
768 exit;
770 FID := ID;
771 FMusic := e_SoundsArray[ID].isMusic;
772 end;
774 function TBasicSound.IsPlaying(): Boolean;
775 begin
776 Result := False;
777 if InvalidSource() then
778 Exit;
779 Result := GetALSourceState(alSources[FSource]) = AL_PLAYING;
780 end;
782 procedure TBasicSound.Stop();
783 begin
784 if FID = CurStream then
785 CurStream := NO_SOUND_ID;
786 if InvalidSource() then
787 Exit;
788 GetPosition();
789 alSourceStop(alSources[FSource]);
790 end;
792 function TBasicSound.IsPaused(): Boolean;
793 begin
794 Result := False;
795 if InvalidSource() then
796 Exit;
797 Result := GetALSourceState(alSources[FSource]) = AL_PAUSED;
798 end;
800 procedure TBasicSound.Pause(Enable: Boolean);
801 begin
802 if InvalidSource() then
803 Exit;
804 if Enable then
805 alSourcePause(alSources[FSource])
806 else
807 alSourcePlay(alSources[FSource]);
808 end;
810 function TBasicSound.GetVolume(): Single;
811 begin
812 Result := 0.0;
813 if InvalidSource() then
814 Exit;
815 alGetSourcef(alSources[FSource], AL_GAIN, Result);
816 end;
818 procedure TBasicSound.SetVolume(Volume: Single);
819 begin
820 if InvalidSource() then
821 Exit;
822 alSourcef(alSources[FSource], AL_GAIN, Volume);
823 end;
825 function TBasicSound.GetPan(): Single;
826 var
827 Pos: array [0..2] of ALfloat = (0, 0, 0);
828 begin
829 Result := 0.0;
830 if InvalidSource() then
831 Exit;
832 alGetSourcefv(alSources[FSource], AL_POSITION, Pos);
833 Result := Pos[0];
834 end;
836 procedure TBasicSound.SetPan(Pan: Single);
837 var
838 Pos: array [0..2] of ALfloat;
839 begin
840 if InvalidSource() then
841 Exit;
842 Pos[0] := Pan;
843 Pos[1] := 0;
844 Pos[2] := 0;
845 alSourcefv(alSources[FSource], AL_POSITION, Pos);
846 end;
848 function TBasicSound.IsMuted(): Boolean;
849 begin
850 if InvalidSource() then
851 Result := False
852 else
853 Result := FMuted;
854 end;
856 procedure TBasicSound.Mute(Enable: Boolean);
857 begin
858 if InvalidSource() then
859 Exit;
860 if Enable then
861 begin
862 FOldGain := GetVolume();
863 FMuted := True;
864 SetVolume(0);
865 end
866 else if FMuted then
867 begin
868 FMuted := False;
869 SetVolume(FOldGain);
870 end;
871 end;
873 function TBasicSound.GetPosition(): DWORD;
874 var
875 Bytes: ALint;
876 begin
877 Result := 0;
878 if InvalidSource() then
879 Exit;
880 alGetSourcei(alSources[FSource], AL_BYTE_OFFSET, Bytes);
881 FPosition := Bytes;
882 Result := FPosition;
883 end;
885 procedure TBasicSound.SetPosition(aPos: DWORD);
886 begin
887 FPosition := aPos;
888 if InvalidSource() then
889 Exit;
890 alSourcei(alSources[FSource], AL_BYTE_OFFSET, aPos);
891 end;
893 procedure TBasicSound.SetPriority(priority: Integer);
894 begin
895 end;
897 end.