1 (* Copyright (C) Doom 2D: Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
35 TBasicSound = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
37 FChannel: FMOD_CHANNEL;
45 function RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
49 destructor Destroy(); override;
50 procedure SetID(ID: TSoundID);
51 procedure FreeSound();
52 function IsPlaying(): Boolean;
54 function IsPaused(): Boolean;
55 procedure Pause(Enable: Boolean);
56 function GetVolume(): Single;
57 procedure SetVolume(Volume: Single);
58 function GetPan(): Single;
59 procedure SetPan(Pan: Single);
60 function IsMuted(): Boolean;
61 procedure Mute(Enable: Boolean);
62 function GetPosition(): DWORD;
63 procedure SetPosition(aPos: DWORD);
64 procedure SetPriority(priority: Cardinal);
68 NO_SOUND_ID = TSoundID(-1);
70 function e_InitSoundSystem(NoOutput: Boolean = False): Boolean;
72 function e_LoadSound(FileName: String; var ID: TSoundID; isMusic: Boolean;
73 ForceNoLoop: Boolean = False): Boolean;
74 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: TSoundID; isMusic: Boolean;
75 ForceNoLoop: Boolean = False): Boolean;
77 function e_PlaySound(ID: TSoundID): Integer;
78 function e_PlaySoundPan(ID: TSoundID; Pan: Single): Integer;
79 function e_PlaySoundVolume(ID: TSoundID; Volume: Single): Integer;
80 function e_PlaySoundPanVolume(ID: TSoundID; 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: TSoundID);
87 procedure e_RemoveAllSounds();
88 procedure e_ReleaseSoundSystem();
89 procedure e_SoundUpdate();
92 e_SoundsArray: array of TSoundRec;
97 g_window, g_options, utils;
103 F_System: FMOD_SYSTEM;
107 function Channel_Callback(channel: FMOD_CHANNEL; callbacktype: FMOD_CHANNEL_CALLBACKTYPE;
108 commanddata1: Pointer; commanddata2: Pointer): FMOD_RESULT; winapi;
116 if callbacktype = FMOD_CHANNEL_CALLBACKTYPE_END then
118 Result := FMOD_Channel_GetCurrentSound(channel, sound);
119 if Result = FMOD_OK then
121 Result := FMOD_Sound_GetUserData(sound, Pointer(id));
122 if Result = FMOD_OK then
124 if id < Length(e_SoundsArray) then
125 if e_SoundsArray[id].nRefs > 0 then
126 e_SoundsArray[ID].nRefs -= 1;
132 function TryInitWithOutput(Output: FMOD_OUTPUTTYPE; OutputName: String): FMOD_RESULT;
134 e_WriteLog('Trying with ' + OutputName + '...', TMsgType.Warning);
135 Result := FMOD_System_SetOutput(F_System, Output);
136 if Result <> FMOD_OK then
138 e_WriteLog('Error setting FMOD output to ' + OutputName + '!', TMsgType.Warning);
139 e_WriteLog(FMOD_ErrorString(Result), TMsgType.Warning);
142 Result := FMOD_System_Init(F_System, N_CHANNELS, FMOD_INIT_NORMAL, nil);
143 if Result <> FMOD_OK then
145 e_WriteLog('Error initializing FMOD system!', TMsgType.Warning);
146 e_WriteLog(FMOD_ErrorString(Result), TMsgType.Warning);
151 function e_TrySS (Freq: Integer; forceNoSound: Integer): Boolean;
155 output: FMOD_OUTPUTTYPE;
160 e_WriteLog(Format('Trying to initialize FMOD with %d', [Freq]), TMsgType.Notify);
162 res := FMOD_System_Create(F_System);
163 if res <> FMOD_OK then
165 e_WriteLog('Error creating FMOD system:', TMsgType.Fatal);
166 e_WriteLog(FMOD_ErrorString(res), TMsgType.Fatal);
170 res := FMOD_System_GetVersion(F_System, ver);
171 if res <> FMOD_OK then
173 e_WriteLog('Error getting FMOD version:', TMsgType.Fatal);
174 e_WriteLog(FMOD_ErrorString(res), TMsgType.Fatal);
178 if ver < FMOD_VERSION then
180 e_WriteLog('FMOD library version is too old! Need '+IntToStr(FMOD_VERSION), TMsgType.Fatal);
184 res := FMOD_System_SetSoftwareFormat(F_System, Freq, FMOD_SOUND_FORMAT_PCM16, 0, 0, FMOD_DSP_RESAMPLER_LINEAR);
185 if res <> FMOD_OK then
187 e_WriteLog('Error setting FMOD software format!', TMsgType.Fatal);
188 e_WriteLog(FMOD_ErrorString(res), TMsgType.Fatal);
192 if forceNoSound = 2 then
194 res := TryInitWithOutput(FMOD_OUTPUTTYPE_NOSOUND, 'OUTPUTTYPE_NOSOUND');
195 if res <> FMOD_OK then
197 e_WriteLog('FMOD: Giving up, can''t init with NOSOUND.', TMsgType.Fatal);
203 res := FMOD_System_Init(F_System, N_CHANNELS, FMOD_INIT_NORMAL, nil);
204 if res <> FMOD_OK then
206 e_WriteLog('Error initializing FMOD system!', TMsgType.Warning);
207 e_WriteLog(FMOD_ErrorString(res), TMsgType.Warning);
210 res := TryInitWithOutput(FMOD_OUTPUTTYPE_ALSA, 'OUTPUTTYPE_ALSA');
211 if res <> FMOD_OK then
212 res := TryInitWithOutput(FMOD_OUTPUTTYPE_OSS, 'OUTPUTTYPE_OSS');
215 res := TryInitWithOutput(FMOD_OUTPUTTYPE_COREAUDIO, 'OUTPUTTYPE_COREAUDIO');
216 if res <> FMOD_OK then
217 res := TryInitWithOutput(FMOD_OUTPUTTYPE_SOUNDMANAGER, 'OUTPUTTYPE_SOUNDMANAGER');
219 if (res <> FMOD_OK) and (forceNoSound <> 1) then Exit;
220 if res <> FMOD_OK then
221 res := TryInitWithOutput(FMOD_OUTPUTTYPE_NOSOUND, 'OUTPUTTYPE_NOSOUND');
222 if res <> FMOD_OK then
224 e_WriteLog('FMOD: Giving up, can''t init any output.', TMsgType.Fatal);
230 res := FMOD_System_GetOutput(F_System, output);
231 if res <> FMOD_OK then
232 e_WriteLog('Error getting FMOD output!', TMsgType.Warning)
235 FMOD_OUTPUTTYPE_NOSOUND: e_WriteLog('FMOD Output Method: NOSOUND', TMsgType.Notify);
236 FMOD_OUTPUTTYPE_NOSOUND_NRT: e_WriteLog('FMOD Output Method: NOSOUND_NRT', TMsgType.Notify);
237 FMOD_OUTPUTTYPE_DSOUND: e_WriteLog('FMOD Output Method: DSOUND', TMsgType.Notify);
238 FMOD_OUTPUTTYPE_WINMM: e_WriteLog('FMOD Output Method: WINMM', TMsgType.Notify);
239 FMOD_OUTPUTTYPE_OPENAL: e_WriteLog('FMOD Output Method: OPENAL', TMsgType.Notify);
240 FMOD_OUTPUTTYPE_WASAPI: e_WriteLog('FMOD Output Method: WASAPI', TMsgType.Notify);
241 FMOD_OUTPUTTYPE_ASIO: e_WriteLog('FMOD Output Method: ASIO', TMsgType.Notify);
242 FMOD_OUTPUTTYPE_OSS: e_WriteLog('FMOD Output Method: OSS', TMsgType.Notify);
243 FMOD_OUTPUTTYPE_ALSA: e_Writelog('FMOD Output Method: ALSA', TMsgType.Notify);
244 FMOD_OUTPUTTYPE_SOUNDMANAGER: e_Writelog('FMOD Output Method: SOUNDMANAGER', TMsgType.Notify);
245 FMOD_OUTPUTTYPE_COREAUDIO: e_Writelog('FMOD Output Method: COREAUDIO', TMsgType.Notify);
246 else e_WriteLog('FMOD Output Method: Unknown', TMsgType.Notify);
249 res := FMOD_System_GetDriver(F_System, drv);
251 then e_WriteLog('Error getting FMOD driver!', TMsgType.Warning)
252 else e_WriteLog('FMOD driver id: '+IntToStr(drv), TMsgType.Notify);
257 function e_InitSoundSystem(NoOutput: Boolean = False): Boolean;
261 Result := e_TrySS(48000, 2);
264 Result := e_TrySS(48000, 0);
265 if not Result then Result := e_TrySS(44100, 1);
268 function FindESound(): TSoundID;
273 if e_SoundsArray <> nil then
274 for i := 0 to High(e_SoundsArray) do
275 if e_SoundsArray[i].Sound = nil then
281 if e_SoundsArray = nil then
283 SetLength(e_SoundsArray, 16);
288 Result := High(e_SoundsArray) + 1;
289 SetLength(e_SoundsArray, Length(e_SoundsArray) + 16);
293 function e_LoadSound(FileName: String; var ID: TSoundID; isMusic: Boolean;
294 ForceNoLoop: Boolean): Boolean;
302 e_WriteLog('Loading sound '+FileName+'...', TMsgType.Notify);
303 find_id := FindESound();
305 if isMusic and not ForceNoLoop
306 then bt := FMOD_LOOP_NORMAL
307 else bt := FMOD_LOOP_OFF;
310 res := FMOD_System_CreateSound(F_System, PAnsiChar(FileName), bt or FMOD_2D or FMOD_HARDWARE,
311 nil, e_SoundsArray[find_id].Sound)
313 res := FMOD_System_CreateStream(F_System, PAnsiChar(FileName), bt or FMOD_2D or FMOD_HARDWARE,
314 nil, e_SoundsArray[find_id].Sound);
316 if res <> FMOD_OK then
318 e_SoundsArray[find_id].Sound := nil;
322 res := FMOD_Sound_SetUserData(e_SoundsArray[find_id].Sound, Pointer(find_id));
323 if res <> FMOD_OK then
325 e_SoundsArray[find_id].Sound := nil;
329 e_SoundsArray[find_id].Data := nil;
330 e_SoundsArray[find_id].isMusic := isMusic;
331 e_SoundsArray[find_id].nRefs := 0;
337 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: TSoundID; isMusic: Boolean;
338 ForceNoLoop: Boolean): Boolean;
344 soundExInfo: FMOD_CREATESOUNDEXINFO;
348 find_id := FindESound();
350 sz := SizeOf(FMOD_CREATESOUNDEXINFO);
351 FillMemory(@soundExInfo, sz, 0);
352 soundExInfo.cbsize := sz;
353 soundExInfo.length := Length;
355 if isMusic and not ForceNoLoop
356 then bt := FMOD_LOOP_NORMAL
357 else bt := FMOD_LOOP_OFF;
360 res := FMOD_System_CreateSound(F_System, pData, bt or FMOD_2D or FMOD_HARDWARE
361 or FMOD_OPENMEMORY, @soundExInfo, e_SoundsArray[find_id].Sound)
363 res := FMOD_System_CreateStream(F_System, pData, bt or FMOD_2D or FMOD_HARDWARE
364 or FMOD_OPENMEMORY, @soundExInfo, e_SoundsArray[find_id].Sound);
366 if res <> FMOD_OK then
368 e_SoundsArray[find_id].Sound := nil;
372 res := FMOD_Sound_SetUserData(e_SoundsArray[find_id].Sound, Pointer(find_id));
373 if res <> FMOD_OK then
375 e_SoundsArray[find_id].Sound := nil;
379 e_SoundsArray[find_id].Data := pData;
380 e_SoundsArray[find_id].isMusic := isMusic;
381 e_SoundsArray[find_id].nRefs := 0;
387 function e_PlaySound(ID: TSoundID): Integer;
393 if e_SoundsArray[ID].nRefs >= gMaxSimSounds then
401 res := FMOD_System_PlaySound(F_System, FMOD_CHANNEL_FREE, e_SoundsArray[ID].Sound, False, Chan);
402 if res <> FMOD_OK then
405 res := FMOD_Channel_SetCallback(Chan, Channel_Callback);
406 if res <> FMOD_OK then
412 res := FMOD_Channel_SetMute(Chan, True);
413 if res <> FMOD_OK then
418 e_SoundsArray[ID].nRefs += 1;
422 function e_PlaySoundPan(ID: TSoundID; Pan: Single): Integer;
428 if e_SoundsArray[ID].nRefs >= gMaxSimSounds then
436 res := FMOD_System_PlaySound(F_System, FMOD_CHANNEL_FREE, e_SoundsArray[ID].Sound, False, Chan);
437 if res <> FMOD_OK then
440 res := FMOD_Channel_SetPan(Chan, Pan);
441 if res <> FMOD_OK then
445 res := FMOD_Channel_SetCallback(Chan, Channel_Callback);
446 if res <> FMOD_OK then
452 res := FMOD_Channel_SetMute(Chan, True);
453 if res <> FMOD_OK then
458 e_SoundsArray[ID].nRefs += 1;
462 function e_PlaySoundVolume(ID: TSoundID; Volume: Single): Integer;
468 if e_SoundsArray[ID].nRefs >= gMaxSimSounds then
476 res := FMOD_System_PlaySound(F_System, FMOD_CHANNEL_FREE, e_SoundsArray[ID].Sound, False, Chan);
477 if res <> FMOD_OK then
480 res := FMOD_Channel_SetVolume(Chan, Volume);
481 if res <> FMOD_OK then
485 res := FMOD_Channel_SetCallback(Chan, Channel_Callback);
486 if res <> FMOD_OK then
492 res := FMOD_Channel_SetMute(Chan, True);
493 if res <> FMOD_OK then
498 e_SoundsArray[ID].nRefs += 1;
502 function e_PlaySoundPanVolume(ID: TSoundID; Pan, Volume: Single): Integer;
508 if e_SoundsArray[ID].nRefs >= gMaxSimSounds then
516 res := FMOD_System_PlaySound(F_System, FMOD_CHANNEL_FREE, e_SoundsArray[ID].Sound, False, Chan);
517 if res <> FMOD_OK then
520 res := FMOD_Channel_SetPan(Chan, Pan);
521 if res <> FMOD_OK then
525 res := FMOD_Channel_SetVolume(Chan, Volume);
526 if res <> FMOD_OK then
530 res := FMOD_Channel_SetCallback(Chan, Channel_Callback);
531 if res <> FMOD_OK then
537 res := FMOD_Channel_SetMute(Chan, True);
538 if res <> FMOD_OK then
543 e_SoundsArray[ID].nRefs += 1;
547 procedure e_DeleteSound(ID: TSoundID);
552 if e_SoundsArray[ID].Sound = nil then
555 FreeMem(e_SoundsArray[ID].Data);
557 res := FMOD_Sound_Release(e_SoundsArray[ID].Sound);
558 if res <> FMOD_OK then
560 e_WriteLog('Error releasing sound:', TMsgType.Warning);
561 e_WriteLog(FMOD_ErrorString(res), TMsgType.Warning);
564 e_SoundsArray[ID].Sound := nil;
565 e_SoundsArray[ID].Data := nil;
568 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
576 for i := 0 to N_CHANNELS-1 do
579 res := FMOD_System_GetChannel(F_System, i, Chan);
581 if (res = FMOD_OK) and (Chan <> nil) then
583 res := FMOD_Channel_GetVolume(Chan, vol);
585 if res = FMOD_OK then
589 else vol *= SoundMod;
591 res := FMOD_Channel_SetVolume(Chan, vol);
593 if res <> FMOD_OK then
601 procedure e_MuteChannels(Enable: Boolean);
608 if Enable = SoundMuted then
611 SoundMuted := Enable;
613 for i := 0 to N_CHANNELS-1 do
616 res := FMOD_System_GetChannel(F_System, i, Chan);
618 if (res = FMOD_OK) and (Chan <> nil) then
620 res := FMOD_Channel_SetMute(Chan, Enable);
622 if res <> FMOD_OK then
629 procedure e_StopChannels();
636 for i := 0 to N_CHANNELS-1 do
639 res := FMOD_System_GetChannel(F_System, i, Chan);
641 if (res = FMOD_OK) and (Chan <> nil) then
643 res := FMOD_Channel_Stop(Chan);
645 if res <> FMOD_OK then
652 procedure e_RemoveAllSounds();
657 for i := 0 to High(e_SoundsArray) do
658 if e_SoundsArray[i].Sound <> nil then
661 SetLength(e_SoundsArray, 0);
664 procedure e_ReleaseSoundSystem();
671 res := FMOD_System_Close(F_System);
672 if res <> FMOD_OK then
674 e_WriteLog('Error closing FMOD system!', TMsgType.Fatal);
675 e_WriteLog(FMOD_ErrorString(res), TMsgType.Fatal);
679 res := FMOD_System_Release(F_System);
680 if res <> FMOD_OK then
682 e_WriteLog('Error releasing FMOD system!', TMsgType.Fatal);
683 e_WriteLog(FMOD_ErrorString(res), TMsgType.Fatal);
687 procedure e_SoundUpdate();
689 FMOD_System_Update(F_System);
694 constructor TBasicSound.Create();
703 destructor TBasicSound.Destroy();
709 procedure TBasicSound.FreeSound();
711 if FID = NO_SOUND_ID then
720 function TBasicSound.RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
726 if FID = NO_SOUND_ID then Exit;
728 if e_SoundsArray[FID].nRefs >= gMaxSimSounds then
734 res := FMOD_System_PlaySound(F_System, FMOD_CHANNEL_FREE, e_SoundsArray[FID].Sound, False,
736 if res <> FMOD_OK then
742 res := FMOD_Channel_SetPosition(FChannel, aPos, FMOD_TIMEUNIT_MS);
745 else FPosition := aPos;
747 res := FMOD_Channel_SetPan(FChannel, Pan);
748 if res <> FMOD_OK then
752 res := FMOD_Channel_SetVolume(FChannel, Volume);
753 if res <> FMOD_OK then
757 res := FMOD_Channel_SetCallback(FChannel, Channel_Callback);
758 if res <> FMOD_OK then
764 res := FMOD_Channel_SetMute(FChannel, True);
765 if res <> FMOD_OK then
770 e_SoundsArray[FID].nRefs += 1;
774 procedure TBasicSound.SetID(ID: TSoundID);
778 FMusic := e_SoundsArray[ID].isMusic;
781 function TBasicSound.IsPlaying(): Boolean;
789 if FChannel = nil then
792 res := FMOD_Channel_IsPlaying(FChannel, b);
793 if res <> FMOD_OK then
799 procedure TBasicSound.Stop();
804 if FChannel = nil then
809 res := FMOD_Channel_Stop(FChannel);
810 if res <> FMOD_OK then
817 function TBasicSound.IsPaused(): Boolean;
825 if FChannel = nil then
828 res := FMOD_Channel_GetPaused(FChannel, b);
829 if res <> FMOD_OK then
835 procedure TBasicSound.Pause(Enable: Boolean);
840 if FChannel = nil then
843 res := FMOD_Channel_SetPaused(FChannel, Enable);
844 if res <> FMOD_OK then
850 res := FMOD_Channel_GetPosition(FChannel, FPosition, FMOD_TIMEUNIT_MS);
851 if res <> FMOD_OK then
857 function TBasicSound.GetVolume(): Single;
865 if FChannel = nil then
868 res := FMOD_Channel_GetVolume(FChannel, vol);
869 if res <> FMOD_OK then
875 procedure TBasicSound.SetVolume(Volume: Single);
880 if FChannel = nil then
883 res := FMOD_Channel_SetVolume(FChannel, Volume);
884 if res <> FMOD_OK then
889 function TBasicSound.GetPan(): Single;
897 if FChannel = nil then
900 res := FMOD_Channel_GetPan(FChannel, pan);
901 if res <> FMOD_OK then
907 procedure TBasicSound.SetPan(Pan: Single);
912 if FChannel = nil then
915 res := FMOD_Channel_SetPan(FChannel, Pan);
916 if res <> FMOD_OK then
921 function TBasicSound.IsMuted(): Boolean;
929 if FChannel = nil then
932 res := FMOD_Channel_GetMute(FChannel, b);
933 if res <> FMOD_OK then
939 procedure TBasicSound.Mute(Enable: Boolean);
944 if FChannel = nil then
947 res := FMOD_Channel_SetMute(FChannel, Enable);
948 if res <> FMOD_OK then
953 function TBasicSound.GetPosition(): DWORD;
960 if FChannel = nil then
963 res := FMOD_Channel_GetPosition(FChannel, FPosition, FMOD_TIMEUNIT_MS);
964 if res <> FMOD_OK then
970 procedure TBasicSound.SetPosition(aPos: DWORD);
977 if FChannel = nil then
980 res := FMOD_Channel_SetPosition(FChannel, FPosition, FMOD_TIMEUNIT_MS);
981 if res <> FMOD_OK then
986 procedure TBasicSound.SetPriority(priority: Cardinal);
991 if (FChannel <> nil) and (FPriority <> priority) and (priority <= 256) then
993 FPriority := priority;
994 res := FMOD_Channel_SetPriority(FChannel, priority);
995 if res <> FMOD_OK then