DEADSOFTWARE

fix random sound volume with OpenAL
[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 if not Loader.Load(FileName, e_SoundsArray[find_id].isMusic) then
292 begin
293 e_LogWritefln('Could not load sound `%s`', [FileName]);
294 exit;
295 end;
297 alGetError(); // reset error state, god damn it
299 if not Loader.Streaming then
300 begin
301 alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
302 if CheckALError() then
303 begin
304 e_LogWritefln('Could not create AL buffer for `%s`: %s', [FileName, GetALError()]);
305 Loader.Free();
306 exit;
307 end;
309 OutLen := Loader.GetAll(OutData);
310 alBufferData(
311 e_SoundsArray[find_id].alBuffer,
312 GetALSoundFormat(Loader.Format),
313 OutData,
314 OutLen,
315 Loader.Format.SampleRate
316 );
318 // don't need this anymore
319 Loader.Free();
320 Loader := nil;
322 if CheckALError() then
323 begin
324 e_LogWriteln('AL: what the fuck: ' + GetALError());
325 alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
326 e_SoundsArray[find_id].alBuffer := 0;
327 exit;
328 end;
329 end
330 else
331 begin
332 Loader.Looping := e_SoundsArray[find_id].Loops;
333 e_SoundsArray[find_id].alBuffer := 0;
334 e_SoundsArray[find_id].Loader := Loader;
335 end;
337 ID := find_id;
338 Result := True;
339 end;
341 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
342 var
343 find_id: DWORD;
344 Loader: TSoundLoader;
345 OutData: Pointer;
346 OutLen: LongWord;
347 begin
348 ID := NO_SOUND_ID;
349 Result := False;
351 find_id := FindESound();
353 e_SoundsArray[find_id].Loader := nil;
354 e_SoundsArray[find_id].isMusic := isMusic;
355 e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop;
356 e_SoundsArray[find_id].nRefs := 0;
358 Loader := e_GetSoundLoader(pData, LongWord(Length));
359 if Loader = nil then
360 begin
361 e_LogWritefln('Could not find loader for sound `%p`', [pData]);
362 exit;
363 end;
365 if not Loader.Load(pData, LongWord(Length), e_SoundsArray[find_id].isMusic) then
366 begin
367 e_LogWritefln('Could not load sound `%p`', [pData]);
368 exit;
369 end;
371 alGetError(); // reset error state, god damn it
373 if not Loader.Streaming then
374 begin
375 alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
376 if CheckALError() then
377 begin
378 e_LogWritefln('Could not create AL buffer for `%p`: %s', [pData, GetALError()]);
379 Loader.Free();
380 exit;
381 end;
383 OutLen := Loader.GetAll(OutData);
384 alBufferData(
385 e_SoundsArray[find_id].alBuffer,
386 GetALSoundFormat(Loader.Format),
387 OutData,
388 OutLen,
389 Loader.Format.SampleRate
390 );
392 // don't need this anymore
393 Loader.Free();
394 Loader := nil;
396 if CheckALError() then
397 begin
398 e_LogWriteln('AL: what the fuck: ' + GetALError());
399 alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
400 e_SoundsArray[find_id].alBuffer := 0;
401 exit;
402 end;
403 end
404 else
405 begin
406 Loader.Looping := e_SoundsArray[find_id].Loops;
407 e_SoundsArray[find_id].alBuffer := 0;
408 e_SoundsArray[find_id].Loader := Loader;
409 end;
411 ID := find_id;
412 Result := True;
413 end;
415 function FindSourceForSound(ID: DWORD): Integer;
416 var
417 S: Integer;
418 begin
419 Result := -1;
420 if ID > High(e_SoundsArray) then
421 exit;
423 if e_SoundsArray[ID].Loader <> nil then
424 begin
425 // first source is for streaming sounds
426 // it always exists
427 alOwners[MUSIC_SOURCE] := nil;
428 Result := MUSIC_SOURCE;
429 exit;
430 end;
432 for S := 1 to High(alSources) do
433 if alSources[S] = 0 then
434 begin
435 alOwners[S] := nil; // TBasicSounds will set this if needed
436 Result := S;
437 break;
438 end;
440 if Result = -1 then Exit; // no voices left
442 alGetError(); // reset error state
443 alGenSources(1, @alSources[Result]);
444 if CheckALError() then
445 begin
446 e_LogWriteln('AL: FindSourceForSound(): alGenSources() failed: ' + GetALError());
447 Result := -1;
448 end;
449 end;
451 procedure AssignSound(ID: DWORD; Src: ALuint); inline;
452 begin
453 alGetError(); // reset error state
455 if e_SoundsArray[ID].Loader <> nil then
456 begin
457 // this is a stream
458 // reset position
459 e_SoundsArray[ID].Loader.SetPosition(0);
460 if CurStream <> ID then // changing streams, stop the thing just in case
461 alSourceStop(Src);
462 // this shit is playing now
463 CurStream := ID;
464 end
465 else
466 begin
467 // this is a full chunk, assign local buffer
468 alSourcei(Src, AL_BUFFER, e_SoundsArray[ID].alBuffer);
469 // these can loop
470 if (e_SoundsArray[ID].Loops) then
471 alSourcei(Src, AL_LOOPING, AL_TRUE)
472 else
473 alSourcei(Src, AL_LOOPING, AL_FALSE);
474 end;
476 alSourcei(Src, AL_SOURCE_RELATIVE, AL_TRUE);
477 end;
479 function e_PlaySound(ID: DWORD): Integer;
480 begin
481 Result := FindSourceForSound(ID);
482 if Result >= 0 then
483 begin
484 AssignSound(ID, alSources[Result]);
485 alSourcef(alSources[Result], AL_GAIN, 1);
486 alSourcefv(alSources[Result], AL_POSITION, e_ZeroPosition);
487 alSourcePlay(alSources[Result]);
488 end;
489 end;
491 function e_PlaySoundPan(ID: DWORD; Pan: Single): Integer;
492 var
493 Pos: array [0..2] of ALfloat;
494 begin
495 Result := FindSourceForSound(ID);
496 if Result >= 0 then
497 begin
498 Pos[0] := Pan;
499 Pos[1] := 0;
500 Pos[2] := 0;
501 AssignSound(ID, alSources[Result]);
502 alSourcef(alSources[Result], AL_GAIN, 1);
503 alSourcefv(alSources[Result], AL_POSITION, Pos);
504 alSourcePlay(alSources[Result]);
505 end;
506 end;
508 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
509 begin
510 Result := FindSourceForSound(ID);
511 if Result >= 0 then
512 begin
513 AssignSound(ID, alSources[Result]);
514 alSourcef(alSources[Result], AL_GAIN, Volume);
515 alSourcefv(alSources[Result], AL_POSITION, e_ZeroPosition);
516 alSourcePlay(alSources[Result]);
517 end;
518 end;
520 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
521 var
522 Pos: array [0..2] of ALfloat;
523 begin
524 Result := FindSourceForSound(ID);
525 if Result >= 0 then
526 begin
527 Pos[0] := Pan;
528 Pos[1] := 0;
529 Pos[2] := 0;
530 AssignSound(ID, alSources[Result]);
531 alSourcefv(alSources[Result], AL_POSITION, Pos);
532 alSourcef(alSources[Result], AL_GAIN, Volume);
533 alSourcePlay(alSources[Result]);
534 end;
535 end;
537 procedure e_DeleteSound(ID: DWORD);
538 begin
539 if ID > High(e_SoundsArray) then
540 exit;
541 if (e_SoundsArray[ID].alBuffer <> 0) then
542 begin
543 alDeleteBuffers(1, Addr(e_SoundsArray[ID].alBuffer));
544 e_SoundsArray[ID].alBuffer := 0;
545 end;
546 if (e_SoundsArray[ID].Loader <> nil) then
547 begin
548 e_SoundsArray[ID].Loader.Free();
549 e_SoundsArray[ID].Loader := nil;
550 if ID = CurStream then
551 CurStream := NO_SOUND_ID;
552 end;
553 end;
555 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
556 var
557 S: Integer;
558 V: ALfloat;
559 begin
560 // TODO: replace manual volume calculations everywhere with
561 // alListenerf(AL_GAIN) or something
562 if setMode then
563 begin
564 for S := 1 to High(alSources) do
565 if alSources[S] <> 0 then
566 alSourcef(alSources[S], AL_GAIN, SoundMod)
567 end
568 else
569 begin
570 for S := 1 to High(alSources) do
571 if alSources[S] <> 0 then
572 begin
573 alGetSourcef(alSources[S], AL_GAIN, V);
574 alSourcef(alSources[S], AL_GAIN, V * SoundMod);
575 end;
576 end;
577 end;
579 procedure e_MuteChannels(Enable: Boolean);
580 begin
581 if Enable = SoundMuted then
582 Exit;
584 SoundMuted := Enable;
585 end;
587 procedure e_StopChannels();
588 var
589 S: Integer;
590 begin
591 alGetError(); // reset error state
592 for S := Low(alSources) to High(alSources) do
593 if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_PLAYING) then
594 begin
595 alSourceStop(alSources[S]);
596 alDeleteSources(1, @alSources[S]);
597 alSources[S] := 0;
598 end;
599 end;
601 procedure e_RemoveAllSounds();
602 var
603 i: Integer;
604 begin
605 for i := 0 to High(e_SoundsArray) do
606 if e_SoundsArray[i].alBuffer <> 0 then
607 e_DeleteSound(i);
608 SetLength(e_SoundsArray, 0);
609 e_SoundsArray := nil;
610 CurStream := NO_SOUND_ID;
611 end;
613 procedure e_ReleaseSoundSystem();
614 begin
615 e_RemoveAllSounds();
617 alcMakeContextCurrent(nil);
618 alcDestroyContext(alContext);
619 alcCloseDevice(alDevice);
621 alDevice := nil;
622 alContext := nil;
623 end;
625 procedure UpdateStreamSource(Src: Integer);
626 var
627 OutLen: LongWord;
628 Buf: ALuint;
629 S: Integer;
630 begin
631 if alSources[Src] = 0 then Exit;
633 alGetError(); // reset error state
635 alGetSourcei(alSources[Src], AL_BUFFERS_PROCESSED, S);
636 // unqueue processed buffers
637 if S > 0 then
638 begin
639 alSourceUnqueueBuffers(alSources[Src], S, @alStreamBufs[alStreamAvail]);
640 alStreamAvail := alStreamAvail + S;
641 end;
643 alGetError(); // reset error state
645 if (alStreamAvail > 0) and (CurStream <> NO_SOUND_ID) then
646 begin
647 // some buffers have freed up, advance stream playback
648 OutLen := e_SoundsArray[CurStream].Loader.FillBuffer(@alStreamData[0], STREAM_BUFSIZE);
649 if OutLen = 0 then Exit; // ran out of stream
650 Buf := alStreamBufs[alStreamAvail-1];
651 Dec(alStreamAvail);
652 // upload
653 alBufferData(
654 Buf,
655 GetALSoundFormat(e_SoundsArray[CurStream].Loader.Format),
656 @alStreamData[0],
657 OutLen,
658 e_SoundsArray[CurStream].Loader.Format.SampleRate
659 );
660 // attach
661 alSourceQueueBuffers(alSources[Src], 1, @Buf);
662 // restart if needed
663 S := GetALSourceState(alSources[Src]);
664 if (S = AL_STOPPED) or (S = AL_INITIAL) then
665 alSourcePlay(alSources[Src]);
666 end;
667 end;
669 procedure e_SoundUpdate();
670 var
671 S: Integer;
672 begin
673 alGetError(); // reset error state
675 // clear out all stopped sources
676 for S := 1 to High(alSources) do
677 if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_STOPPED) then
678 begin
679 alDeleteSources(1, @alSources[S]);
680 alSources[S] := 0;
681 alOwners[S] := nil;
682 end;
684 // update the stream sources
685 UpdateStreamSource(MUSIC_SOURCE);
686 end;
688 { TBasicSound: }
690 constructor TBasicSound.Create();
691 begin
692 FID := NO_SOUND_ID;
693 FMusic := False;
694 FSource := -1;
695 FPosition := 0;
696 FMuted := False;
697 FOldGain := 1;
698 end;
700 destructor TBasicSound.Destroy();
701 begin
702 FreeSound();
703 inherited;
704 end;
706 function TBasicSound.InvalidSource(): Boolean; inline;
707 begin
708 Result := (FSource < 0) or (alSources[FSource] = 0) or (alOwners[FSource] <> self);
709 end;
711 procedure TBasicSound.FreeSound();
712 begin
713 if FID = NO_SOUND_ID then
714 Exit;
716 Stop();
717 FID := NO_SOUND_ID;
718 FMusic := False;
719 FPosition := 0;
720 end;
722 function TBasicSound.RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
723 begin
724 Result := False;
725 if FID = NO_SOUND_ID then Exit;
727 if e_SoundsArray[FID].nRefs >= gMaxSimSounds then
728 begin
729 Result := True;
730 Exit;
731 end;
733 FSource := e_PlaySoundPanVolume(FID, Pan, Volume);
734 if FSource >= 0 then
735 begin
736 alOwners[FSource] := self;
737 Result := True;
738 end;
739 end;
741 procedure TBasicSound.SetID(ID: DWORD);
742 begin
743 FreeSound();
745 if ID > High(e_SoundsArray) then
746 exit;
748 FID := ID;
749 FMusic := e_SoundsArray[ID].isMusic;
750 end;
752 function TBasicSound.IsPlaying(): Boolean;
753 begin
754 Result := False;
755 if InvalidSource() then
756 Exit;
757 Result := GetALSourceState(alSources[FSource]) = AL_PLAYING;
758 end;
760 procedure TBasicSound.Stop();
761 begin
762 if FID = CurStream then
763 CurStream := NO_SOUND_ID;
764 if InvalidSource() then
765 Exit;
766 GetPosition();
767 alSourceStop(alSources[FSource]);
768 end;
770 function TBasicSound.IsPaused(): Boolean;
771 begin
772 Result := False;
773 if InvalidSource() then
774 Exit;
775 Result := GetALSourceState(alSources[FSource]) = AL_PAUSED;
776 end;
778 procedure TBasicSound.Pause(Enable: Boolean);
779 begin
780 if InvalidSource() then
781 Exit;
782 if Enable then
783 alSourcePause(alSources[FSource])
784 else
785 alSourcePlay(alSources[FSource]);
786 end;
788 function TBasicSound.GetVolume(): Single;
789 begin
790 Result := 0.0;
791 if InvalidSource() then
792 Exit;
793 alGetSourcef(alSources[FSource], AL_GAIN, Result);
794 end;
796 procedure TBasicSound.SetVolume(Volume: Single);
797 begin
798 if InvalidSource() then
799 Exit;
800 alSourcef(alSources[FSource], AL_GAIN, Volume);
801 end;
803 function TBasicSound.GetPan(): Single;
804 var
805 Pos: array [0..2] of ALfloat;
806 begin
807 Result := 0.0;
808 if InvalidSource() then
809 Exit;
810 alGetSourcefv(alSources[FSource], AL_POSITION, Pos);
811 Result := Pos[0];
812 end;
814 procedure TBasicSound.SetPan(Pan: Single);
815 var
816 Pos: array [0..2] of ALfloat;
817 begin
818 if InvalidSource() then
819 Exit;
820 Pos[0] := Pan;
821 Pos[1] := 0;
822 Pos[2] := 0;
823 alSourcefv(alSources[FSource], AL_POSITION, Pos);
824 end;
826 function TBasicSound.IsMuted(): Boolean;
827 begin
828 if InvalidSource() then
829 Result := False
830 else
831 Result := FMuted;
832 end;
834 procedure TBasicSound.Mute(Enable: Boolean);
835 begin
836 if InvalidSource() then
837 Exit;
838 if Enable then
839 begin
840 FOldGain := GetVolume();
841 FMuted := True;
842 SetVolume(0);
843 end
844 else if FMuted then
845 begin
846 FMuted := False;
847 SetVolume(FOldGain);
848 end;
849 end;
851 function TBasicSound.GetPosition(): DWORD;
852 var
853 Bytes: ALint;
854 begin
855 Result := 0;
856 if InvalidSource() then
857 Exit;
858 alGetSourcei(alSources[FSource], AL_BYTE_OFFSET, Bytes);
859 FPosition := Bytes;
860 Result := FPosition;
861 end;
863 procedure TBasicSound.SetPosition(aPos: DWORD);
864 begin
865 FPosition := aPos;
866 if InvalidSource() then
867 Exit;
868 alSourcei(alSources[FSource], AL_BYTE_OFFSET, aPos);
869 end;
871 procedure TBasicSound.SetPriority(priority: Integer);
872 begin
873 end;
875 end.