DEADSOFTWARE

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