DEADSOFTWARE

fmod -> sdl2 mixer, first part: we have music now; still no advanced stuff
[d2df-sdl.git] / src / engine / e_sound.pas
1 unit e_sound;
3 interface
5 uses
6 sdl2 in '../lib/sdl2/sdl2.pas',
7 SDL2_mixer in '../lib/sdl2/SDL2_mixer.pas',
8 e_log,
9 SysUtils;
11 type
12 TSoundRec = record
13 Data: Pointer;
14 Sound: PMix_Chunk;
15 Music: PMix_Music;
16 isMusic: Boolean;
17 end;
19 TBasicSound = class (TObject)
20 private
21 FChannel: Integer; // <0: no channel allocated
23 protected
24 FID: DWORD;
25 FMusic: Boolean;
26 FPosition: DWORD;
27 FPriority: Integer;
29 function RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
31 public
32 constructor Create();
33 destructor Destroy(); override;
34 procedure SetID(ID: DWORD);
35 procedure FreeSound();
36 function IsPlaying(): Boolean;
37 procedure Stop();
38 function IsPaused(): Boolean;
39 procedure Pause(Enable: Boolean);
40 function GetVolume(): Single;
41 procedure SetVolume(Volume: Single);
42 function GetPan(): Single;
43 procedure SetPan(Pan: Single);
44 function IsMuted(): Boolean;
45 procedure Mute(Enable: Boolean);
46 function GetPosition(): DWORD;
47 procedure SetPosition(aPos: DWORD);
48 procedure SetPriority(priority: Integer);
49 end;
51 const
52 NO_SOUND_ID = DWORD(-1);
54 function e_InitSoundSystem(): Boolean;
56 function e_LoadSound(FileName: string; var ID: DWORD; isMusic: Boolean): Boolean;
57 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: Boolean): Boolean;
59 // returns channel number or -1
60 function e_PlaySound(ID: DWORD): Integer;
61 function e_PlaySoundPan(ID: DWORD; Pan: Single): Integer;
62 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
63 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
65 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
66 procedure e_MuteChannels(Enable: Boolean);
67 procedure e_StopChannels();
69 procedure e_DeleteSound(ID: DWORD);
70 procedure e_RemoveAllSounds();
71 procedure e_ReleaseSoundSystem();
72 procedure e_SoundUpdate();
74 var
75 e_SoundsArray: array of TSoundRec = nil;
77 implementation
79 uses
80 g_window, g_options, BinEditor;
82 const
83 N_CHANNELS = 512;
84 N_MUSCHAN = N_CHANNELS+42;
86 var
87 SoundMuted: Boolean = False;
88 SoundInitialized: Boolean = False;
91 function e_InitSoundSystem(): Boolean;
92 var
93 res: Integer;
94 begin
95 if SoundInitialized then begin Result := true; Exit end;
97 Result := False;
98 SoundInitialized := False;
100 { // wow, this is actually MIDI player!
101 // we need module player
102 if (Mix_Init(MIX_INIT_MOD) and MIX_INIT_MOD) <> MIX_INIT_MOD then
103 begin
104 e_WriteLog('Error initializing SDL module player:', MSG_FATALERROR);
105 e_WriteLog(Mix_GetError(), MSG_FATALERROR);
106 //Exit;
107 end;
110 res := Mix_OpenAudio(44100, AUDIO_S16LSB, 2, 512);
111 if res = -1 then
112 begin
113 e_WriteLog('Error initializing SDL mixer:', MSG_FATALERROR);
114 e_WriteLog(Mix_GetError(), MSG_FATALERROR);
115 Exit;
116 end;
118 Mix_AllocateChannels(N_CHANNELS);
120 SoundInitialized := True;
121 Result := True;
122 end;
124 function e_isMusic (id: DWORD): Boolean;
125 begin
126 Result := False;
127 if (e_SoundsArray <> nil) and (id <= High(e_SoundsArray)) then
128 begin
129 Result := (e_SoundsArray[id].Music <> nil);
130 end;
131 end;
133 function e_isSound (id: DWORD): Boolean;
134 begin
135 Result := False;
136 if (e_SoundsArray <> nil) and (id <= High(e_SoundsArray)) then
137 begin
138 Result := (e_SoundsArray[id].Sound <> nil);
139 end;
140 end;
142 function FindESound(): DWORD;
143 var
144 i: Integer;
145 begin
146 if e_SoundsArray <> nil then
147 for i := 0 to High(e_SoundsArray) do
148 if (e_SoundsArray[i].Sound = nil) and (e_SoundsArray[i].Music = nil) then
149 begin
150 Result := i;
151 Exit;
152 end;
154 if e_SoundsArray = nil then
155 begin
156 SetLength(e_SoundsArray, 16);
157 Result := 0;
158 end
159 else
160 begin
161 Result := High(e_SoundsArray) + 1;
162 SetLength(e_SoundsArray, Length(e_SoundsArray) + 16);
163 end;
164 end;
166 function e_LoadSound(FileName: String; var ID: DWORD; isMusic: Boolean): Boolean;
167 var
168 find_id: DWORD;
169 begin
170 Result := False;
171 if not SoundInitialized then Exit;
173 if isMusic then e_WriteLog('Loading music '+FileName+'...', MSG_NOTIFY)
174 else e_WriteLog('Loading sound '+FileName+'...', MSG_NOTIFY);
176 find_id := FindESound();
178 e_SoundsArray[find_id].Data := nil;
179 e_SoundsArray[find_id].isMusic := isMusic;
181 if isMusic then
182 begin
183 e_SoundsArray[find_id].Music := Mix_LoadMUS(PAnsiChar(FileName));
184 if e_SoundsArray[find_id].Music = nil then Exit;
185 end
186 else
187 begin
188 e_SoundsArray[find_id].Sound := Mix_LoadWAV(PAnsiChar(FileName));
189 if e_SoundsArray[find_id].Sound = nil then Exit;
190 end;
192 ID := find_id;
194 Result := True;
195 end;
197 function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic: Boolean): Boolean;
198 var
199 find_id: DWORD;
200 rw: PSDL_RWops;
201 begin
202 Result := False;
203 if not SoundInitialized then Exit;
205 rw := SDL_RWFromConstMem(pData, Length);
206 if rw = nil then Exit;
208 find_id := FindESound();
210 e_SoundsArray[find_id].Data := pData;
211 e_SoundsArray[find_id].isMusic := isMusic;
213 if isMusic then
214 begin
215 e_SoundsArray[find_id].Music := Mix_LoadMUS_RW(rw, 0);
216 end
217 else
218 begin
219 e_SoundsArray[find_id].Sound := Mix_LoadWAV_RW(rw, 0);
220 end;
221 SDL_FreeRW(rw);
222 if (e_SoundsArray[find_id].Sound = nil) and (e_SoundsArray[find_id].Music = nil) then Exit;
224 ID := find_id;
226 Result := True;
227 end;
229 function e_PlaySound (ID: DWORD): Integer;
230 var
231 res: Integer = -1;
232 begin
233 Result := -1;
234 if not SoundInitialized then Exit;
236 if {(e_SoundsArray[ID].nRefs >= gMaxSimSounds) or} (e_SoundsArray[ID].Sound = nil) and (e_SoundsArray[ID].Music = nil) then Exit;
238 if e_SoundsArray[ID].Music <> nil then
239 begin
240 res := Mix_PlayMusic(e_SoundsArray[ID].Music, -1);
241 if res >= 0 then res := N_MUSCHAN;
242 Result := res;
243 Exit;
244 end;
246 if e_SoundsArray[ID].Sound <> nil then
247 res := Mix_PlayChannel(-1, e_SoundsArray[ID].Sound, 0);
249 if e_SoundsArray[ID].isMusic then
250 res := Mix_PlayChannel(-1, e_SoundsArray[ID].Sound, -1)
251 else
252 res := Mix_PlayChannel(-1, e_SoundsArray[ID].Sound, 0);
255 if SoundMuted and (res >= 0) then Mix_Volume(res, 0);
257 Result := res;
258 end;
260 function e_PlaySoundPan(ID: DWORD; Pan: Single): Integer;
261 var
262 chan: Integer;
263 l, r: UInt8;
264 begin
265 Result := -1;
266 chan := e_PlaySound(ID);
267 if (chan >= 0) and (chan <> N_MUSCHAN) then
268 begin
269 if Pan < -1.0 then Pan := -1.0 else if Pan > 1.0 then Pan := 1.0;
270 Pan := Pan+1.0; // 0..2
271 l := trunc(127.0*(2.0-Pan));
272 r := trunc(127.0*Pan);
273 Mix_SetPanning(chan, l, r);
274 end;
275 Result := chan;
276 end;
278 function e_PlaySoundVolume(ID: DWORD; Volume: Single): Integer;
279 var
280 chan: Integer;
281 begin
282 Result := -1;
283 chan := e_PlaySound(ID);
284 if (chan >= 0) and (chan <> N_MUSCHAN) then
285 begin
286 if Volume < 0 then Volume := 0 else if Volume > 1 then Volume := 1;
287 if not SoundMuted then Mix_Volume(chan, trunc(Volume*MIX_MAX_VOLUME));
288 end;
289 Result := chan;
290 end;
292 function e_PlaySoundPanVolume(ID: DWORD; Pan, Volume: Single): Integer;
293 var
294 chan: Integer;
295 l, r: UInt8;
296 begin
297 Result := -1;
298 chan := e_PlaySound(ID);
299 if (chan >= 0) and (chan <> N_MUSCHAN) then
300 begin
301 if Pan < -1.0 then Pan := -1.0 else if Pan > 1.0 then Pan := 1.0;
302 Pan := Pan+1.0; // 0..2
303 l := trunc(127.0*(2.0-Pan));
304 r := trunc(127.0*Pan);
305 Mix_SetPanning(chan, l, r);
306 if Volume < 0 then Volume := 0 else if Volume > 1 then Volume := 1;
307 if not SoundMuted then Mix_Volume(chan, trunc(Volume*MIX_MAX_VOLUME));
308 end;
309 Result := chan;
310 end;
312 procedure e_DeleteSound(ID: DWORD);
313 begin
314 if (e_SoundsArray[ID].Sound = nil) and (e_SoundsArray[ID].Music = nil) then Exit;
315 if e_SoundsArray[ID].Data <> nil then FreeMem(e_SoundsArray[ID].Data);
317 if e_SoundsArray[ID].Sound <> nil then Mix_FreeChunk(e_SoundsArray[ID].Sound);
318 if e_SoundsArray[ID].Music <> nil then Mix_FreeMusic(e_SoundsArray[ID].Music);
320 e_SoundsArray[ID].Sound := nil;
321 e_SoundsArray[ID].Music := nil;
322 e_SoundsArray[ID].Data := nil;
323 end;
325 //TODO
326 procedure e_ModifyChannelsVolumes(SoundMod: Single; setMode: Boolean);
328 var
329 i: Integer;
330 Chan: FMOD_CHANNEL;
331 vol: Single;
333 begin
334 // Mix_Volume(-1, volm);
336 for i := 0 to N_CHANNELS-1 do
337 begin
338 Chan := nil;
339 res := FMOD_System_GetChannel(F_System, i, Chan);
341 if (res = FMOD_OK) and (Chan <> nil) then
342 begin
343 res := FMOD_Channel_GetVolume(Chan, vol);
345 if res = FMOD_OK then
346 begin
347 if setMode then
348 vol := SoundMod
349 else
350 vol := vol * SoundMod;
352 res := FMOD_Channel_SetVolume(Chan, vol);
354 if res <> FMOD_OK then
355 begin
356 end;
357 end;
358 end;
359 end;
361 end;
363 //TODO
364 procedure e_MuteChannels(Enable: Boolean);
366 var
367 res: FMOD_RESULT;
368 i: Integer;
369 Chan: FMOD_CHANNEL;
371 begin
373 if Enable = SoundMuted then
374 Exit;
376 SoundMuted := Enable;
378 for i := 0 to N_CHANNELS-1 do
379 begin
380 Chan := nil;
381 res := FMOD_System_GetChannel(F_System, i, Chan);
383 if (res = FMOD_OK) and (Chan <> nil) then
384 begin
385 res := FMOD_Channel_SetMute(Chan, Enable);
387 if res <> FMOD_OK then
388 begin
389 end;
390 end;
391 end;
393 end;
395 procedure e_StopChannels();
396 begin
397 Mix_HaltChannel(-1);
398 Mix_HaltMusic();
399 end;
401 procedure e_RemoveAllSounds();
402 var
403 i: Integer;
404 begin
405 if SoundInitialized then e_StopChannels();
407 for i := 0 to High(e_SoundsArray) do
408 if e_SoundsArray[i].Sound <> nil then
409 e_DeleteSound(i);
411 SetLength(e_SoundsArray, 0);
412 e_SoundsArray := nil;
413 end;
415 procedure e_ReleaseSoundSystem();
416 begin
417 e_RemoveAllSounds();
419 if SoundInitialized then
420 begin
421 Mix_CloseAudio();
422 SoundInitialized := False;
423 end;
424 end;
426 procedure e_SoundUpdate();
427 begin
428 //FMOD_System_Update(F_System);
429 end;
431 { TBasicSound: }
433 constructor TBasicSound.Create();
434 begin
435 FID := NO_SOUND_ID;
436 FMusic := False;
437 FChannel := -1;
438 FPosition := 0;
439 FPriority := 128;
440 end;
442 destructor TBasicSound.Destroy();
443 begin
444 FreeSound();
445 inherited;
446 end;
448 procedure TBasicSound.FreeSound();
449 begin
450 if FID = NO_SOUND_ID then Exit;
451 Stop();
452 FID := NO_SOUND_ID;
453 FMusic := False;
454 FPosition := 0;
455 end;
457 // aPos: msecs
458 function TBasicSound.RawPlay(Pan: Single; Volume: Single; aPos: DWORD): Boolean;
459 begin
460 Result := False;
461 if (FID = NO_SOUND_ID) or not SoundInitialized then Exit;
462 Result := (e_PlaySoundPanVolume(FID, Pan, Volume) >= 0);
463 //TODO: aPos
464 end;
466 procedure TBasicSound.SetID(ID: DWORD);
467 begin
468 FreeSound();
469 FID := ID;
470 FMusic := e_SoundsArray[ID].isMusic;
471 end;
473 function TBasicSound.IsPlaying(): Boolean;
474 begin
475 Result := False;
476 if FChannel < 0 then Exit;
477 if e_isSound(FID) then Result := (Mix_Playing(FChannel) > 0)
478 else Result := (Mix_PlayingMusic() > 0);
479 end;
481 procedure TBasicSound.Stop();
483 begin
484 if FChannel < 0 then Exit;
485 //GetPosition();
486 if e_isSound(FID) then Mix_HaltChannel(FChannel) else Mix_HaltMusic();
487 FChannel := -1;
488 end;
490 function TBasicSound.IsPaused(): Boolean;
491 begin
492 Result := False;
493 if FChannel < 0 then Exit;
494 if e_isSound(FID) then Result := (Mix_Paused(FChannel) > 0) else Result := (Mix_PausedMusic() > 0);
495 end;
497 procedure TBasicSound.Pause(Enable: Boolean);
498 begin
499 if FChannel < 0 then Exit;
500 if IsPaused() then
501 begin
502 if Enable then
503 begin
504 if e_isSound(FID) then Mix_Resume(FChannel) else Mix_ResumeMusic();
505 end;
506 end
507 else
508 begin
509 if not Enable then
510 begin
511 if e_isSound(FID) then Mix_Pause(FChannel) else Mix_PauseMusic();
512 end;
513 end;
515 if Enable then
516 begin
517 res := FMOD_Channel_GetPosition(FChannel, FPosition, FMOD_TIMEUNIT_MS);
518 if res <> FMOD_OK then
519 begin
520 end;
521 end;
523 end;
525 //TODO
526 function TBasicSound.GetVolume(): Single;
527 begin
528 Result := 0.0;
529 if FChannel < 0 then Exit;
531 res := FMOD_Channel_GetVolume(FChannel, vol);
532 if res <> FMOD_OK then
533 begin
534 Exit;
535 end;
536 Result := vol;
538 end;
540 procedure TBasicSound.SetVolume(Volume: Single);
541 begin
542 if FChannel < 0 then Exit;
543 if Volume < 0 then Volume := 0 else if Volume > 1 then Volume := 1;
544 if e_isSound(FID) then Mix_Volume(FChannel, trunc(Volume*MIX_MAX_VOLUME))
545 else Mix_VolumeMusic(trunc(Volume*MIX_MAX_VOLUME))
546 end;
548 //TODO
549 function TBasicSound.GetPan(): Single;
550 begin
551 Result := 0.0;
552 if FChannel < 0 then Exit;
554 res := FMOD_Channel_GetPan(FChannel, pan);
555 if res <> FMOD_OK then
556 begin
557 Exit;
558 end;
559 Result := pan;
561 end;
563 procedure TBasicSound.SetPan(Pan: Single);
564 var
565 l, r: UInt8;
566 begin
567 if FChannel < 0 then Exit;
568 if not e_isSound(FID) then Exit;
569 if Pan < -1.0 then Pan := -1.0 else if Pan > 1.0 then Pan := 1.0;
570 Pan := Pan+1.0; // 0..2
571 l := trunc(127.0*(2.0-Pan));
572 r := trunc(127.0*Pan);
573 Mix_SetPanning(FChannel, l, r);
574 end;
576 //TODO
577 function TBasicSound.IsMuted(): Boolean;
578 begin
579 Result := False;
580 if FChannel < 0 then Exit;
582 res := FMOD_Channel_GetMute(FChannel, b);
583 if res <> FMOD_OK then
584 begin
585 Exit;
586 end;
587 Result := b;
589 end;
591 //TODO
592 procedure TBasicSound.Mute(Enable: Boolean);
593 begin
594 if FChannel < 0 then Exit;
596 res := FMOD_Channel_SetMute(FChannel, Enable);
597 if res <> FMOD_OK then
598 begin
599 end;
601 end;
603 //TODO
604 function TBasicSound.GetPosition(): DWORD;
605 begin
606 Result := 0;
607 if FChannel < 0 then Exit;
609 res := FMOD_Channel_GetPosition(FChannel, FPosition, FMOD_TIMEUNIT_MS);
610 if res <> FMOD_OK then
611 begin
612 Exit;
613 end;
614 Result := FPosition;
616 end;
618 //TODO
619 procedure TBasicSound.SetPosition(aPos: DWORD);
620 begin
621 FPosition := aPos;
622 if FChannel < 0 then Exit;
624 res := FMOD_Channel_SetPosition(FChannel, FPosition, FMOD_TIMEUNIT_MS);
625 if res <> FMOD_OK then
626 begin
627 end;
629 end;
631 //TODO
632 procedure TBasicSound.SetPriority(priority: Integer);
633 begin
635 if (FChannel <> nil) and (FPriority <> priority) and
636 (priority >= 0) and (priority <= 256) then
637 begin
638 FPriority := priority;
639 res := FMOD_Channel_SetPriority(FChannel, priority);
640 if res <> FMOD_OK then
641 begin
642 end;
643 end;
645 end;
647 end.