DEADSOFTWARE

Sound: OpenAL: Streams can now be non-looping; XMP tweaks
[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;
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 AssignSound(ID, alSources[Result]);
500 alSourcef(alSources[Result], AL_GAIN, 1);
501 alSourcefv(alSources[Result], AL_POSITION, Pos);
502 alSourcePlay(alSources[Result]);
503 end;
504 end;
506 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
507 begin
508 Result := FindSourceForSound(ID);
509 if Result >= 0 then
510 begin
511 AssignSound(ID, alSources[Result]);
512 alSourcef(alSources[Result], AL_GAIN, Volume);
513 alSourcefv(alSources[Result], AL_POSITION, e_ZeroPosition);
514 alSourcePlay(alSources[Result]);
515 end;
516 end;
518 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
519 var
520 Pos: array [0..2] of ALfloat;
521 begin
522 Result := FindSourceForSound(ID);
523 if Result >= 0 then
524 begin
525 Pos[0] := Pan;
526 AssignSound(ID, alSources[Result]);
527 alSourcefv(alSources[Result], AL_POSITION, Pos);
528 alSourcef(alSources[Result], AL_GAIN, Volume);
529 alSourcePlay(alSources[Result]);
530 end;
531 end;
533 procedure e_DeleteSound(ID: DWORD);
534 begin
535 if ID > High(e_SoundsArray) then
536 exit;
537 if (e_SoundsArray[ID].alBuffer <> 0) then
538 begin
539 alDeleteBuffers(1, Addr(e_SoundsArray[ID].alBuffer));
540 e_SoundsArray[ID].alBuffer := 0;
541 end;
542 if (e_SoundsArray[ID].Loader <> nil) then
543 begin
544 e_SoundsArray[ID].Loader.Free();
545 e_SoundsArray[ID].Loader := nil;
546 if ID = CurStream then
547 CurStream := NO_SOUND_ID;
548 end;
549 end;
551 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
552 var
553 S: Integer;
554 V: ALfloat;
555 begin
556 // TODO: replace manual volume calculations everywhere with
557 // alListenerf(AL_GAIN) or something
558 if setMode then
559 begin
560 for S := 1 to High(alSources) do
561 if alSources[S] <> 0 then
562 alSourcef(alSources[S], AL_GAIN, SoundMod)
563 end
564 else
565 begin
566 for S := 1 to High(alSources) do
567 if alSources[S] <> 0 then
568 begin
569 alGetSourcef(alSources[S], AL_GAIN, V);
570 alSourcef(alSources[S], AL_GAIN, V * SoundMod);
571 end;
572 end;
573 end;
575 procedure e_MuteChannels(Enable: Boolean);
576 begin
577 if Enable = SoundMuted then
578 Exit;
580 SoundMuted := Enable;
581 end;
583 procedure e_StopChannels();
584 var
585 S: Integer;
586 begin
587 alGetError(); // reset error state
588 for S := Low(alSources) to High(alSources) do
589 if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_PLAYING) then
590 begin
591 alSourceStop(alSources[S]);
592 alDeleteSources(1, @alSources[S]);
593 alSources[S] := 0;
594 end;
595 end;
597 procedure e_RemoveAllSounds();
598 var
599 i: Integer;
600 begin
601 for i := 0 to High(e_SoundsArray) do
602 if e_SoundsArray[i].alBuffer <> 0 then
603 e_DeleteSound(i);
604 SetLength(e_SoundsArray, 0);
605 e_SoundsArray := nil;
606 CurStream := NO_SOUND_ID;
607 end;
609 procedure e_ReleaseSoundSystem();
610 begin
611 e_RemoveAllSounds();
613 alcMakeContextCurrent(nil);
614 alcDestroyContext(alContext);
615 alcCloseDevice(alDevice);
617 alDevice := nil;
618 alContext := nil;
619 end;
621 procedure UpdateStreamSource(Src: Integer);
622 var
623 OutLen: LongWord;
624 Buf: ALuint;
625 S: Integer;
626 begin
627 if alSources[Src] = 0 then Exit;
629 alGetError(); // reset error state
631 alGetSourcei(alSources[Src], AL_BUFFERS_PROCESSED, S);
632 // unqueue processed buffers
633 if S > 0 then
634 begin
635 alSourceUnqueueBuffers(alSources[Src], S, @alStreamBufs[alStreamAvail]);
636 alStreamAvail := alStreamAvail + S;
637 end;
639 alGetError(); // reset error state
641 if (alStreamAvail > 0) and (CurStream <> NO_SOUND_ID) then
642 begin
643 // some buffers have freed up, advance stream playback
644 OutLen := e_SoundsArray[CurStream].Loader.FillBuffer(@alStreamData[0], STREAM_BUFSIZE);
645 if OutLen = 0 then Exit; // ran out of stream
646 Buf := alStreamBufs[alStreamAvail-1];
647 Dec(alStreamAvail);
648 // upload
649 alBufferData(
650 Buf,
651 GetALSoundFormat(e_SoundsArray[CurStream].Loader.Format),
652 @alStreamData[0],
653 OutLen,
654 e_SoundsArray[CurStream].Loader.Format.SampleRate
655 );
656 // attach
657 alSourceQueueBuffers(alSources[Src], 1, @Buf);
658 // restart if needed
659 S := GetALSourceState(alSources[Src]);
660 if (S = AL_STOPPED) or (S = AL_INITIAL) then
661 alSourcePlay(alSources[Src]);
662 end;
663 end;
665 procedure e_SoundUpdate();
666 var
667 S: Integer;
668 begin
669 alGetError(); // reset error state
671 // clear out all stopped sources
672 for S := 1 to High(alSources) do
673 if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_STOPPED) then
674 begin
675 alDeleteSources(1, @alSources[S]);
676 alSources[S] := 0;
677 alOwners[S] := nil;
678 end;
680 // update the stream sources
681 UpdateStreamSource(MUSIC_SOURCE);
682 end;
684 { TBasicSound: }
686 constructor TBasicSound.Create();
687 begin
688 FID := NO_SOUND_ID;
689 FMusic := False;
690 FSource := -1;
691 FPosition := 0;
692 FMuted := False;
693 FOldGain := 1;
694 end;
696 destructor TBasicSound.Destroy();
697 begin
698 FreeSound();
699 inherited;
700 end;
702 function TBasicSound.InvalidSource(): Boolean; inline;
703 begin
704 Result := (FSource < 0) or (alSources[FSource] = 0) or (alOwners[FSource] <> self);
705 end;
707 procedure TBasicSound.FreeSound();
708 begin
709 if FID = NO_SOUND_ID then
710 Exit;
712 Stop();
713 FID := NO_SOUND_ID;
714 FMusic := False;
715 FPosition := 0;
716 end;
718 function TBasicSound.RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
719 begin
720 Result := False;
721 if FID = NO_SOUND_ID then Exit;
723 if e_SoundsArray[FID].nRefs >= gMaxSimSounds then
724 begin
725 Result := True;
726 Exit;
727 end;
729 FSource := e_PlaySoundPanVolume(FID, Pan, Volume);
730 if FSource >= 0 then
731 begin
732 alOwners[FSource] := self;
733 Result := True;
734 end;
735 end;
737 procedure TBasicSound.SetID(ID: DWORD);
738 begin
739 FreeSound();
741 if ID > High(e_SoundsArray) then
742 exit;
744 FID := ID;
745 FMusic := e_SoundsArray[ID].isMusic;
746 end;
748 function TBasicSound.IsPlaying(): Boolean;
749 begin
750 Result := False;
751 if InvalidSource() then
752 Exit;
753 Result := GetALSourceState(alSources[FSource]) = AL_PLAYING;
754 end;
756 procedure TBasicSound.Stop();
757 begin
758 if FID = CurStream then
759 CurStream := NO_SOUND_ID;
760 if InvalidSource() then
761 Exit;
762 GetPosition();
763 alSourceStop(alSources[FSource]);
764 end;
766 function TBasicSound.IsPaused(): Boolean;
767 begin
768 Result := False;
769 if InvalidSource() then
770 Exit;
771 Result := GetALSourceState(alSources[FSource]) = AL_PAUSED;
772 end;
774 procedure TBasicSound.Pause(Enable: Boolean);
775 begin
776 if InvalidSource() then
777 Exit;
778 if Enable then
779 alSourcePause(alSources[FSource])
780 else
781 alSourcePlay(alSources[FSource]);
782 end;
784 function TBasicSound.GetVolume(): Single;
785 begin
786 Result := 0.0;
787 if InvalidSource() then
788 Exit;
789 alGetSourcef(alSources[FSource], AL_GAIN, Result);
790 end;
792 procedure TBasicSound.SetVolume(Volume: Single);
793 begin
794 if InvalidSource() then
795 Exit;
796 alSourcef(alSources[FSource], AL_GAIN, Volume);
797 end;
799 function TBasicSound.GetPan(): Single;
800 var
801 Pos: array [0..2] of ALfloat;
802 begin
803 Result := 0.0;
804 if InvalidSource() then
805 Exit;
806 alGetSourcefv(alSources[FSource], AL_POSITION, Pos);
807 Result := Pos[0];
808 end;
810 procedure TBasicSound.SetPan(Pan: Single);
811 var
812 Pos: array [0..2] of ALfloat;
813 begin
814 if InvalidSource() then
815 Exit;
816 Pos[0] := Pan;
817 alSourcefv(alSources[FSource], AL_POSITION, Pos);
818 end;
820 function TBasicSound.IsMuted(): Boolean;
821 begin
822 if InvalidSource() then
823 Result := False
824 else
825 Result := FMuted;
826 end;
828 procedure TBasicSound.Mute(Enable: Boolean);
829 begin
830 if InvalidSource() then
831 Exit;
832 if Enable then
833 begin
834 FOldGain := GetVolume();
835 FMuted := True;
836 SetVolume(0);
837 end
838 else if FMuted then
839 begin
840 FMuted := False;
841 SetVolume(FOldGain);
842 end;
843 end;
845 function TBasicSound.GetPosition(): DWORD;
846 var
847 Bytes: ALint;
848 begin
849 Result := 0;
850 if InvalidSource() then
851 Exit;
852 alGetSourcei(alSources[FSource], AL_BYTE_OFFSET, Bytes);
853 FPosition := Bytes;
854 Result := FPosition;
855 end;
857 procedure TBasicSound.SetPosition(aPos: DWORD);
858 begin
859 FPosition := aPos;
860 if InvalidSource() then
861 Exit;
862 alSourcei(alSources[FSource], AL_BYTE_OFFSET, aPos);
863 end;
865 procedure TBasicSound.SetPriority(priority: Integer);
866 begin
867 end;
869 end.