can load non-looping music; fixed looping ROUNDMUS
[d2df-sdl.git] / src / engine / e_sound_sdl.inc
blobeb42c8bfea70d0374e304fe7546847db33d60b49
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   sdl2,
20   SDL2_mixer,
21   {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
22   e_log,
23   SysUtils;
25 type
26   TSoundRec = record
27     Data: Pointer;
28     Sound: PMix_Chunk;
29     Music: PMix_Music;
30     isMusic: Boolean;
31     Loops: Boolean;
32     nRefs: Integer;
33   end;
35   TBasicSound = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
36   private
37     FChanNum: Integer; // <0: no channel allocated
39   protected
40     FID: DWORD;
41     FMusic: Boolean;
42     FPosition: DWORD;
43     FPriority: Integer;
45     function RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
46     function GetChan (): Integer;
48     property Channel: Integer read GetChan;
50   public
51     constructor Create();
52     destructor Destroy(); override;
53     procedure SetID(ID: DWORD);
54     procedure FreeSound();
55     function IsPlaying(): Boolean;
56     procedure Stop();
57     function IsPaused(): Boolean;
58     procedure Pause(Enable: Boolean);
59     function GetVolume(): Single;
60     procedure SetVolume(Volume: Single);
61     function GetPan(): Single;
62     procedure SetPan(Pan: Single);
63     function IsMuted(): Boolean;
64     procedure Mute(Enable: Boolean);
65     function GetPosition(): DWORD;
66     procedure SetPosition(aPos: DWORD);
67     procedure SetPriority(priority: Integer);
68   end;
70 const
71   NO_SOUND_ID = DWORD(-1);
73 function e_InitSoundSystem(NoOutput: Boolean = False): Boolean;
75 function e_LoadSound(FileName: string; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
76 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
78 // returns channel number or -1
79 function e_PlaySound(ID: DWORD): Integer;
80 function e_PlaySoundPan(ID: DWORD; Pan: Single): Integer;
81 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
82 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
84 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
85 procedure e_MuteChannels(Enable: Boolean);
86 procedure e_StopChannels();
88 procedure e_DeleteSound(ID: DWORD);
89 procedure e_RemoveAllSounds();
90 procedure e_ReleaseSoundSystem();
91 procedure e_SoundUpdate();
93 var
94   e_SoundsArray: array of TSoundRec = nil;
96 implementation
98 uses
99   g_window, g_options;
101 const
102   N_CHANNELS = 512;
103   N_MUSCHAN = N_CHANNELS+42;
105 type
106   TChanInfo = record
107     id: DWORD; // sound id
108     muted: Boolean;
109     oldvol: Integer; // for muted
110     pan: Single;
111   end;
114   SoundMuted: Boolean = False;
115   SoundInitialized: Boolean = False;
116   ChanSIds: array[0..N_CHANNELS] of TChanInfo;
117   MusVolume: Integer = MIX_MAX_VOLUME;
120 procedure chanFinished (chan: Integer); cdecl;
121 begin
122   //e_WriteLog(Format('chanFinished: %d', [chan]), TMsgType.Notify);
123   if (chan >= 0) and (chan < N_CHANNELS) then
124   begin
125     if ChanSIds[chan].id <> NO_SOUND_ID then
126     begin
127       if (ChanSIds[chan].id <= High(e_SoundsArray)) and (e_SoundsArray[ChanSIds[chan].id].nRefs > 0) then
128       begin
129         Dec(e_SoundsArray[ChanSIds[chan].id].nRefs);
130       end;
131       ChanSIds[chan].id := NO_SOUND_ID;
132     end;
133   end;
134 end;
137 procedure dumpMusicType (ms: PMix_Music);
138 begin
139   if ms = nil then
140   begin
141     e_WriteLog('MUSIC FORMAT: NONE', TMsgType.Notify);
142   end
143   else
144   begin
145     case Mix_GetMusicType(ms^) of
146       TMix_MusicType.MUS_NONE:
147         e_WriteLog('MUSIC FORMAT: NONE', TMsgType.Notify);
148       TMix_MusicType.MUS_CMD:
149         e_WriteLog('MUSIC FORMAT: CMD', TMsgType.Notify);
150       TMix_MusicType.MUS_WAV:
151         e_WriteLog('MUSIC FORMAT: WAV', TMsgType.Notify);
152       TMix_MusicType.MUS_MOD:
153         e_WriteLog('MUSIC FORMAT: MOD', TMsgType.Notify);
154       TMix_MusicType.MUS_MID:
155         e_WriteLog('MUSIC FORMAT: MID', TMsgType.Notify);
156       TMix_MusicType.MUS_OGG:
157         e_WriteLog('MUSIC FORMAT: OGG', TMsgType.Notify);
158       TMix_MusicType.MUS_MP3:
159         e_WriteLog('MUSIC FORMAT: MP3', TMsgType.Notify);
160       TMix_MusicType.MUS_MP3_MAD:
161         e_WriteLog('MUSIC FORMAT: MP3_MAD', TMsgType.Notify);
162       TMix_MusicType.MUS_FLAC:
163         e_WriteLog('MUSIC FORMAT: FLAC', TMsgType.Notify);
164       TMix_MusicType.MUS_MODPLUG:
165         e_WriteLog('MUSIC FORMAT: MODPLUG', TMsgType.Notify);
166       otherwise
167         e_WriteLog('MUSIC FORMAT: UNKNOWN', TMsgType.Notify);
168     end;
169   end;
170 end;
172 function e_InitSoundSystem(NoOutput: Boolean = False): Boolean;
174   res, i: Integer;
175   rfreq: Integer;
176   rformat: UInt16;
177   rchans: Integer;
178 begin
179   if SoundInitialized then begin Result := true; Exit end;
181   Result := False;
182   SoundInitialized := False;
184   if NoOutput then begin Result := true; Exit end;
186   // wow, this is actually MIDI player!
187   // we need module player
188   res := Mix_Init(MIX_INIT_FLAC or MIX_INIT_MOD or MIX_INIT_MODPLUG or MIX_INIT_MP3 or MIX_INIT_OGG or MIX_INIT_FLUIDSYNTH);
189   e_WriteLog(Format('SDL: res=0x%x', [res]), TMsgType.Notify);
190   if (res and MIX_INIT_FLAC) <> 0 then e_WriteLog('SDL: FLAC playback is active', TMsgType.Notify);
191   if (res and MIX_INIT_MOD) <> 0 then e_WriteLog('SDL: MOD playback is active', TMsgType.Notify);
192   if (res and MIX_INIT_MODPLUG) <> 0 then e_WriteLog('SDL: MODPLUG playback is active', TMsgType.Notify);
193   if (res and MIX_INIT_MP3) <> 0 then e_WriteLog('SDL: MP3 playback is active', TMsgType.Notify);
194   if (res and MIX_INIT_OGG) <> 0 then e_WriteLog('SDL: OGG playback is active', TMsgType.Notify);
195   if (res and MIX_INIT_FLUIDSYNTH) <> 0 then e_WriteLog('SDL: FLUIDSYNTH playback is active', TMsgType.Notify);
197   e_WriteLog(Format('SDL: initializing mixer at %d with buffer %d', [gsSDLSampleRate, gsSDLBufferSize]), TMsgType.Notify);
198   res := Mix_OpenAudio(gsSDLSampleRate, AUDIO_S16LSB, 2, gsSDLBufferSize);
199   if res = -1 then
200   begin
201     e_WriteLog('Error initializing SDL mixer:', TMsgType.Fatal);
202     e_WriteLog(Mix_GetError(), TMsgType.Fatal);
203     Exit;
204   end;
206   if Mix_QuerySpec(@rfreq, @rformat, @rchans) > 0 then
207   begin
208     e_WriteLog(Format('SDL: frequency=%d; format=%u; channels=%d', [rfreq, rformat, rchans]), TMsgType.Notify);
209   end;
211   for i := 0 to Mix_GetNumChunkDecoders()-1 do
212   begin
213     e_WriteLog(Format('SDL: chunk decoder %s is avalable', [Mix_GetChunkDecoder(i)]), TMsgType.Notify);
214   end;
215   for i := 0 to Mix_GetNumMusicDecoders()-1 do
216   begin
217     e_WriteLog(Format('SDL: music decoder %s is avalable', [Mix_GetMusicDecoder(i)]), TMsgType.Notify);
218   end;
220   Mix_AllocateChannels(N_CHANNELS);
221   Mix_ChannelFinished(chanFinished);
223   for i := 0 to N_CHANNELS-1 do
224   begin
225     ChanSIds[i].id := NO_SOUND_ID;
226     ChanSIds[i].muted := SoundMuted;
227     ChanSIds[i].oldvol := MIX_MAX_VOLUME;
228     ChanSIds[i].pan := 1.0;
229   end;
230   MusVolume := MIX_MAX_VOLUME;
232   SoundInitialized := True;
233   Result := True;
234 end;
236 function e_isMusic (id: DWORD): Boolean;
237 begin
238   Result := False;
239   if (e_SoundsArray <> nil) and (id <= High(e_SoundsArray)) then
240   begin
241     Result := (e_SoundsArray[id].Music <> nil);
242   end;
243 end;
245 function e_isSound (id: DWORD): Boolean;
246 begin
247   Result := False;
248   if (e_SoundsArray <> nil) and (id <= High(e_SoundsArray)) then
249   begin
250     Result := (e_SoundsArray[id].Sound <> nil);
251   end;
252 end;
254 function FindESound(): DWORD;
256   i: Integer;
257 begin
258   if e_SoundsArray <> nil then
259   begin
260     for i := 0 to High(e_SoundsArray) do
261       if (e_SoundsArray[i].Sound = nil) and (e_SoundsArray[i].Music = nil) then
262       begin
263         Result := i;
264         Exit;
265       end;
266   end;
267   if e_SoundsArray = nil then
268   begin
269     SetLength(e_SoundsArray, 16);
270     Result := 0;
271   end
272   else
273   begin
274     Result := High(e_SoundsArray) + 1;
275     SetLength(e_SoundsArray, Length(e_SoundsArray) + 16);
276   end;
277   for i := Result to High(e_SoundsArray) do
278   begin
279     e_SoundsArray[i].Sound := nil;
280     e_SoundsArray[i].Music := nil;
281     e_SoundsArray[i].Data := nil;
282     e_SoundsArray[i].isMusic := False;
283     e_SoundsArray[i].nRefs := 0;
284   end;
285 end;
287 function e_LoadSound(FileName: String; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
289   find_id: DWORD;
290 begin
291   ID := NO_SOUND_ID;
292   Result := False;
293   if not SoundInitialized then Exit;
295   if isMusic then e_WriteLog('Loading music '+FileName+'...', TMsgType.Notify)
296   else e_WriteLog('Loading sound '+FileName+'...', TMsgType.Notify);
298   {
299   if isMusic then
300   begin
301     e_WriteLog('IGNORING MUSIC FROM FILE', TMsgType.Warning);
302     Exit;
303   end;
304   }
306   find_id := FindESound();
308   e_SoundsArray[find_id].Data := nil;
309   e_SoundsArray[find_id].isMusic := isMusic;
310   e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop;
311   e_SoundsArray[find_id].nRefs := 0;
313   if isMusic then
314   begin
315     e_WriteLog(Format('  MUSIC SLOT: %u', [find_id]), TMsgType.Notify);
316     e_SoundsArray[find_id].Music := Mix_LoadMUS(PAnsiChar(FileName));
317     if e_SoundsArray[find_id].Music = nil then
318     begin
319       e_WriteLog(Format('ERROR LOADING MUSIC:', [find_id]), TMsgType.Warning);
320       e_WriteLog(Mix_GetError(), TMsgType.Warning);
321       Exit;
322     end;
323     dumpMusicType(e_SoundsArray[find_id].Music);
324   end
325   else
326   begin
327     e_SoundsArray[find_id].Sound := Mix_LoadWAV(PAnsiChar(FileName));
328     if e_SoundsArray[find_id].Sound = nil then Exit;
329   end;
331   ID := find_id;
333   Result := True;
334 end;
336 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: Boolean; ForceNoLoop: Boolean = False): Boolean;
338   find_id: DWORD;
339   rw: PSDL_RWops;
340   //pc: PChar;
341   isid3: Boolean;
342 begin
343   ID := NO_SOUND_ID;
344   Result := False;
345   if not SoundInitialized then Exit;
346   isid3 := False;
348   {
349   if isMusic then
350   begin
351     e_WriteLog('IGNORING MUSIC FROM MEMORY', TMsgType.Warning);
352     Exit;
353   end;
354   }
356   //FIXME: correctly skip ID3
357   {
358   pc := PChar(pData);
359   if (Length > $400) and (pc[0] = 'I') and (pc[1] = 'D') and (pc[2] = '3') then
360   begin
361     isid3 := True;
362     Inc(pc, $400);
363     pData := Pointer(pc);
364     Dec(Length, $400);
365     e_WriteLog('MUSIC: MP3 ID3 WORKAROUND APPLIED!', TMsgType.Warning);
366   end;
367   }
369   rw := SDL_RWFromConstMem(pData, Length);
370   if rw = nil then Exit;
372   find_id := FindESound();
374   e_SoundsArray[find_id].Data := pData;
375   if isid3 then e_SoundsArray[find_id].Data := nil;
376   e_SoundsArray[find_id].isMusic := isMusic;
377   e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop;
378   e_SoundsArray[find_id].nRefs := 0;
380   if isMusic then
381   begin
382     e_WriteLog(Format('  MUSIC SLOT: %u', [find_id]), TMsgType.Notify);
383     e_SoundsArray[find_id].Music := Mix_LoadMUS_RW(rw, 0);
384     if e_SoundsArray[find_id].Music = nil then
385     begin
386       e_WriteLog(Format('ERROR LOADING MUSIC:', [find_id]), TMsgType.Warning);
387       e_WriteLog(Mix_GetError(), TMsgType.Warning);
388     end
389     else
390     begin
391       dumpMusicType(e_SoundsArray[find_id].Music);
392     end;
393     //SDL_FreeRW(rw);
394     {
395     if e_SoundsArray[find_id].Music <> nil then
396     begin
397       Mix_FreeMusic(e_SoundsArray[find_id].Music);
398     end;
399     e_SoundsArray[find_id].Music := nil;
400     Exit;
401     }
402   end
403   else
404   begin
405     e_SoundsArray[find_id].Sound := Mix_LoadWAV_RW(rw, 0);
406   end;
407   //SDL_FreeRW(rw); // somehow it segfaults...
408   if (e_SoundsArray[find_id].Sound = nil) and (e_SoundsArray[find_id].Music = nil) then
409   begin
410     e_SoundsArray[find_id].Data := nil;
411     Exit;
412   end;
414   ID := find_id;
416   Result := True;
417 end;
419 function e_PlaySound (ID: DWORD): Integer;
421   res: Integer = -1;
422   loops: Integer = 0;
423 begin
424   Result := -1;
425   if not SoundInitialized then Exit;
427   if e_isSound(ID) then
428   begin
429     if e_SoundsArray[ID].nRefs >= gMaxSimSounds then Exit;
430     Inc(e_SoundsArray[ID].nRefs);
431     if e_SoundsArray[ID].Loops then loops := -1;
432     res := Mix_PlayChannel(-1, e_SoundsArray[ID].Sound, loops);
433     if res >= 0 then
434     begin
435       ChanSIds[res].id := ID;
436       ChanSIds[res].muted := SoundMuted;
437       if SoundMuted then Mix_Volume(res, 0) else Mix_Volume(res, ChanSIds[res].oldvol);
438       {
439       if e_SoundsArray[ID].isMusic then
440         res := Mix_PlayChannel(-1, e_SoundsArray[ID].Sound, -1)
441       else
442         res := Mix_PlayChannel(-1, e_SoundsArray[ID].Sound, 0);
443       }
444     end;
445   end
446   else
447   begin
448     if not e_isMusic(ID) then Exit;
449     Mix_HaltMusic();
450     if e_SoundsArray[ID].Loops then loops := -1;
451     res := Mix_PlayMusic(e_SoundsArray[ID].Music, loops);
452     if res >= 0 then res := N_MUSCHAN;
453     if SoundMuted then Mix_VolumeMusic(0) else Mix_VolumeMusic(MusVolume);
454     Result := res;
455   end;
457   Result := res;
458 end;
460 function e_chanSetPan (chan: Integer; Pan: Single): Boolean;
462   l, r: UInt8;
463 begin
464   Result := True;
465   if chan = N_MUSCHAN then
466   begin
467     // no panning for music
468   end
469   else if chan >= 0 then
470   begin
471     if Pan < -1.0 then Pan := -1.0 else if Pan > 1.0 then Pan := 1.0;
472     Pan := Pan+1.0; // 0..2
473     l := trunc(127.0*(2.0-Pan));
474     r := trunc(127.0*Pan);
475     Mix_SetPanning(chan, l, r);
476     ChanSIds[chan].pan := Pan;
477   end
478   else
479   begin
480     Result := False;
481   end;
482 end;
484 function e_chanSetVol (chan: Integer; Volume: Single): Boolean;
486   vol: Integer;
487 begin
488   Result := True;
489   if Volume < 0 then Volume := 0 else if Volume > 1 then Volume := 1;
490   vol := trunc(Volume*MIX_MAX_VOLUME);
491   if chan = N_MUSCHAN then
492   begin
493     MusVolume := vol;
494     if SoundMuted then Mix_VolumeMusic(0) else Mix_VolumeMusic(vol);
495   end
496   else if chan >= 0 then
497   begin
498     ChanSIds[chan].oldvol := vol;
499     if ChanSIds[chan].muted then Mix_Volume(chan, 0) else Mix_Volume(chan, vol);
500   end
501   else
502   begin
503     Result := False;
504   end;
505 end;
507 function e_PlaySoundPan(ID: DWORD; Pan: Single): Integer;
509   chan: Integer;
510 begin
511   Result := -1;
512   chan := e_PlaySound(ID);
513   e_chanSetPan(chan, Pan);
514   Result := chan;
515 end;
517 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
519   chan: Integer;
520 begin
521   Result := -1;
522   chan := e_PlaySound(ID);
523   e_chanSetVol(chan, Volume);
524   Result := chan;
525 end;
527 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
529   chan: Integer;
530 begin
531   Result := -1;
532   chan := e_PlaySound(ID);
533   e_chanSetPan(chan, Pan);
534   e_chanSetVol(chan, Volume);
535   Result := chan;
536 end;
538 procedure e_DeleteSound(ID: DWORD);
540   i: Integer;
541 begin
542   if ID > High(e_SoundsArray) then Exit;
543   if (e_SoundsArray[ID].Sound = nil) and (e_SoundsArray[ID].Music = nil) then Exit;
545   for i := 0 to N_CHANNELS-1 do
546   begin
547     if ChanSIds[i].id = ID then
548     begin
549       ChanSIds[i].id := NO_SOUND_ID;
550       Mix_HaltChannel(i);
551     end;
552   end;
554   if e_SoundsArray[ID].Sound <> nil then Mix_FreeChunk(e_SoundsArray[ID].Sound);
555   if e_SoundsArray[ID].Music <> nil then Mix_FreeMusic(e_SoundsArray[ID].Music);
556   if e_SoundsArray[ID].Data <> nil then FreeMem(e_SoundsArray[ID].Data);
558   e_SoundsArray[ID].Sound := nil;
559   e_SoundsArray[ID].Music := nil;
560   e_SoundsArray[ID].Data := nil;
561   e_SoundsArray[ID].nRefs := 0;
562 end;
564 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
566   i: Integer;
567   vol: Single;
568   ovol: Integer;
569 begin
570   for i := 0 to N_CHANNELS-1 do
571   begin
572     ovol := ChanSIds[i].oldvol;
573     if setMode then
574     begin
575       vol := SoundMod;
576     end
577     else
578     begin
579       vol := (MIX_MAX_VOLUME+0.0)/ovol;
580       vol := vol*SoundMod;
581     end;
582     if vol < 0 then vol := 0 else if vol > 1 then vol := 1;
583     ChanSIds[i].oldvol := trunc(vol*MIX_MAX_VOLUME);
584     //if i = 0 then e_WriteLog(Format('modifying volumes: vol=%f; newvol=%d', [vol, ChanSIds[i].oldvol]), TMsgType.Warning);
585     if ChanSIds[i].muted then Mix_Volume(i, 0) else Mix_Volume(i, ChanSIds[i].oldvol);
586   end;
587   ovol := Mix_VolumeMusic(-1);
588   if ovol >= 0 then
589   begin
590     if setMode then
591     begin
592       vol := SoundMod;
593     end
594     else
595     begin
596       vol := (MIX_MAX_VOLUME+0.0)/ovol;
597       vol := vol * SoundMod;
598     end;
599     if vol < 0 then vol := 0 else if vol > 1 then vol := 1;
600     MusVolume := trunc(vol*MIX_MAX_VOLUME);
601     if SoundMuted then Mix_VolumeMusic(0) else Mix_VolumeMusic(MusVolume);
602   end;
603 end;
605 procedure e_MuteChannels(Enable: Boolean);
607   i: Integer;
608 begin
609   //if Enable = SoundMuted then Exit;
610   SoundMuted := Enable;
611   for i := 0 to N_CHANNELS-1 do
612   begin
613     if ChanSIds[i].muted <> SoundMuted then
614     begin
615       ChanSIds[i].muted := SoundMuted;
616       //e_WriteLog(Format('gmuting sound for channel %d', [i]), TMsgType.Warning);
617       if ChanSIds[i].muted then Mix_Volume(i, 0) else Mix_Volume(i, ChanSIds[i].oldvol);
618     end;
619   end;
620   //if SoundMuted then e_WriteLog('muting music', TMsgType.Notify) else e_WriteLog(Format('unmuting music (%d)', [MusVolume]), TMsgType.Notify);
621   if SoundMuted then Mix_VolumeMusic(0) else Mix_VolumeMusic(MusVolume);
622 end;
624 procedure e_StopChannels();
626   i: Integer;
627 begin
628   Mix_HaltChannel(-1);
629   Mix_HaltMusic();
630   for i := 0 to High(e_SoundsArray) do e_SoundsArray[i].nRefs := 0;
631   for i := 0 to N_CHANNELS-1 do ChanSIds[i].id := NO_SOUND_ID;
632 end;
634 procedure e_RemoveAllSounds();
636   i: Integer;
637 begin
638   if SoundInitialized then e_StopChannels();
639   for i := 0 to High(e_SoundsArray) do e_DeleteSound(i);
640   SetLength(e_SoundsArray, 0);
641   e_SoundsArray := nil;
642 end;
644 procedure e_ReleaseSoundSystem();
645 begin
646   e_RemoveAllSounds();
647   if SoundInitialized then
648   begin
649     Mix_CloseAudio();
650     SoundInitialized := False;
651   end;
652 end;
654 procedure e_SoundUpdate();
655 begin
656   //FMOD_System_Update(F_System);
657 end;
660 { TBasicSound: }
662 constructor TBasicSound.Create();
663 begin
664   FID := NO_SOUND_ID;
665   FMusic := False;
666   FChanNum := -1;
667   FPosition := 0;
668   FPriority := 128;
669 end;
671 destructor TBasicSound.Destroy();
672 begin
673   FreeSound();
674   inherited;
675 end;
677 function TBasicSound.GetChan (): Integer;
678 begin
679   if (FID <> NO_SOUND_ID) and (FChanNum >= 0) and (FChanNum < N_CHANNELS) then
680   begin
681     if ChanSIds[FChanNum].id <> FID then FChanNum := -1;
682   end
683   else if e_isMusic(FID) then
684   begin
685     FChanNum := N_MUSCHAN;
686   end;
687   Result := FChanNum;
688 end;
690 procedure TBasicSound.FreeSound();
691 begin
692   if FID = NO_SOUND_ID then Exit;
693   Stop();
694   FID := NO_SOUND_ID;
695   FMusic := False;
696   FPosition := 0;
697   FChanNum := -1;
698 end;
700 // aPos: msecs
701 function TBasicSound.RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
702 begin
703   Result := False;
704   if (FID = NO_SOUND_ID) or not SoundInitialized then Exit;
705   FChanNum := e_PlaySoundPanVolume(FID, Pan, Volume);
706   Result := (FChanNum >= 0);
707   //if e_isMusic(FID) then e_WriteLog(Format('playing music (%u)', [FID]), TMsgType.Notify);
708   //TODO: aPos
709 end;
711 procedure TBasicSound.SetID(ID: DWORD);
712 begin
713   FreeSound();
714   FID := ID;
715   if ID <> NO_SOUND_ID then
716   begin
717     FMusic := e_SoundsArray[ID].isMusic;
718   end;
719   FChanNum := -1;
720 end;
722 function TBasicSound.IsPlaying(): Boolean;
724   chan: Integer;
725 begin
726   Result := False;
727   if e_isSound(FID) then
728   begin
729     //e_WriteLog(Format('IsPlaying: FID=%u; FChanNum=%d', [FID, FChanNum]), TMsgType.Warning);
730     chan := Channel;
731     if chan < 0 then
732     begin
733       //e_WriteLog(Format('IsPlaying: FID=%u; ONA', [FID]), TMsgType.Warning);
734       Exit;
735     end;
736     //Result := (Mix_Playing(chan) > 0)
737     //e_WriteLog(Format('IsPlaying: FID=%u; TAN', [FID]), TMsgType.Warning);
738     Result := True;
739   end
740   else if e_isMusic(FID) then
741   begin
742     Result := (Mix_PlayingMusic() > 0);
743   end;
744 end;
746 procedure TBasicSound.Stop();
748   chan: Integer;
749 begin
750   if e_isSound(FID) then
751   begin
752     chan := Channel;
753     if chan >= 0 then
754     begin
755       //GetPosition();
756       Mix_HaltChannel(chan);
757     end;
758   end
759   else if e_isMusic(FID) then
760   begin
761     Mix_HaltMusic();
762   end;
763   FChanNum := -1;
764 end;
766 function TBasicSound.IsPaused(): Boolean;
768   chan: Integer;
769 begin
770   Result := False;
771   if e_isSound(FID) then
772   begin
773     chan := Channel;
774     if chan < 0 then Exit;
775     Result := (Mix_Paused(chan) > 0);
776   end
777   else if e_isMusic(FID) then
778   begin
779     Result := (Mix_PausedMusic() > 0);
780   end;
781 end;
783 procedure TBasicSound.Pause(Enable: Boolean);
785   chan: Integer;
786   pl: Boolean;
787 begin
788   Enable := not Enable; // fuckin' double negation
789   if e_isSound(FID) then
790   begin
791     chan := Channel;
792     if chan < 0 then Exit;
793     pl := not (Mix_Paused(chan) > 0);
794     if pl <> Enable then
795     begin
796       if Enable then Mix_Resume(chan) else Mix_Pause(chan);
797     end;
798   end
799   else if e_isMusic(FID) then
800   begin
801     pl := not (Mix_PausedMusic() > 0);
802     if pl <> Enable then
803     begin
804       if Enable then Mix_ResumeMusic() else Mix_PauseMusic();
805     end;
806   end;
807   {
808   if Enable then
809   begin
810     res := FMOD_Channel_GetPosition(FChanNum, FPosition, FMOD_TIMEUNIT_MS);
811     if res <> FMOD_OK then
812     begin
813     end;
814   end;
815   }
816 end;
818 function TBasicSound.GetVolume(): Single;
820   chan: Integer;
821 begin
822   Result := 0.0;
823   if e_isSound(FID) then
824   begin
825     chan := Channel;
826     if chan < 0 then Exit;
827     Result := (ChanSIds[chan].oldvol+0.0)/(MIX_MAX_VOLUME+0.0);
828   end
829   else if e_isMusic(FID) then
830   begin
831     Result := (MusVolume+0.0)/(MIX_MAX_VOLUME+0.0);
832   end;
833 end;
835 procedure TBasicSound.SetVolume(Volume: Single);
837   chan: Integer;
838 begin
839   if e_isSound(FID) then
840   begin
841     chan := Channel;
842     if chan < 0 then Exit;
843     //e_WriteLog(Format('SetVolume: chan=%d; Volume=%f', [chan, Volume]), TMsgType.Warning);
844     e_chanSetVol(chan, Volume);
845   end
846   else if e_isMusic(FID) then
847   begin
848     //e_WriteLog(Format('SetVolume: chan=MUSIC; Volume=%f', [Volume]), TMsgType.Warning);
849     e_chanSetVol(N_MUSCHAN, Volume);
850   end;
851 end;
853 function TBasicSound.GetPan(): Single;
855   chan: Integer;
856 begin
857   Result := 1.0;
858   if e_isSound(FID) then
859   begin
860     chan := Channel;
861     if chan < 0 then Exit;
862     Result := ChanSIds[chan].pan;
863   end;
864 end;
866 procedure TBasicSound.SetPan(Pan: Single);
868   chan: Integer;
869 begin
870   if e_isSound(FID) then
871   begin
872     chan := Channel;
873     if chan < 0 then Exit;
874     e_chanSetPan(chan, Pan);
875   end;
876 end;
878 function TBasicSound.IsMuted(): Boolean;
880   chan: Integer;
881 begin
882   Result := False;
883   if e_isSound(FID) then
884   begin
885     chan := Channel;
886     if chan < 0 then Exit;
887     Result := ChanSIds[chan].muted;
888   end
889   else if e_isMusic(FID) then
890   begin
891     Result := SoundMuted;
892   end;
893 end;
895 procedure TBasicSound.Mute(Enable: Boolean);
897   chan: Integer;
898 begin
899   if e_isSound(FID) then
900   begin
901     chan := Channel;
902     if chan < 0 then Exit;
903     if ChanSIds[chan].muted <> Enable then
904     begin
905       //e_WriteLog(Format('muting sound for channel %d', [cnan]), TMsgType.Warning);
906       ChanSIds[chan].muted := Enable;
907       if ChanSIds[chan].muted then Mix_Volume(chan, 0) else Mix_Volume(chan, ChanSIds[chan].oldvol);
908     end;
909   end
910   else if e_isMusic(FID) then
911   begin
912     if Enable then Mix_VolumeMusic(0) else Mix_VolumeMusic(MusVolume);
913   end;
914 end;
916 //TODO
917 function TBasicSound.GetPosition(): DWORD;
918 begin
919   Result := 0;
921   if FChanNum < 0 then Exit;
922   res := FMOD_Channel_GetPosition(FChanNum, FPosition, FMOD_TIMEUNIT_MS);
923   if res <> FMOD_OK then
924   begin
925     Exit;
926   end;
927   Result := FPosition;
929 end;
931 //TODO
932 procedure TBasicSound.SetPosition(aPos: DWORD);
933 begin
934   FPosition := aPos;
936   if FChanNum < 0 then Exit;
937   res := FMOD_Channel_SetPosition(FChanNum, FPosition, FMOD_TIMEUNIT_MS);
938   if res <> FMOD_OK then
939   begin
940   end;
942 end;
944 //TODO
945 procedure TBasicSound.SetPriority(priority: Integer);
946 begin
948   if (FChanNum <> nil) and (FPriority <> priority) and
949      (priority >= 0) and (priority <= 256) then
950   begin
951     FPriority := priority;
952     res := FMOD_Channel_SetPriority(FChanNum, priority);
953     if res <> FMOD_OK then
954     begin
955     end;
956   end;
958 end;
960 end.