1 //
2 // Copyright (C) 2004-2006 Jasmine Langridge, ja-reiko@users.sourceforge.net
3 // Copyright (C) 2015 Andrei Bondor, ab396356@users.sourceforge.net
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 //
19
20 // Available:
21 //
22 // USE_NULL
23 // USE_OPENAL
24 // USE_FMOD
25 // USE_SDL_MIXER
26 //
27 // ... and they're all squashed into this one file!
28
29 #ifdef UNIX
30 //#define USE_NULL
31 #define USE_OPENAL
32 #endif
33
34 #ifdef WIN32
35 //#define USE_NULL
36 //#define USE_OPENAL
37 #define USE_FMOD
38 #endif
39
40 #include "pengine.h"
41 #include "physfs_utils.h"
42
43 // I was half way through implementing SDL_mixer support when
44 // I realised it has no ability to change pitch / playback
45 // speed. Don't enable USE_SDL_MIXER.
46
47 //#define USE_SDL_MIXER
48
49 // This function is common to the various implementations
50
loadSample(const std::string & name,bool positional3D)51 PAudioSample *PSSAudio::loadSample(const std::string &name, bool positional3D)
52 {
53 PAudioSample *samp = samplist.find(name);
54
55 if (!samp)
56 {
57 try
58 {
59 samp = new PAudioSample(name, positional3D);
60 }
61 catch (PException &e)
62 {
63 if (PUtil::isDebugLevel(DEBUGLEVEL_ENDUSER))
64 PUtil::outLog() << "Failed to load " << name << ": " << e.what() << std::endl;
65
66 return nullptr;
67 }
68 samplist.add(samp);
69 }
70 return samp;
71 }
72
73 #ifdef USE_NULL
74
PSSAudio(PApp & parentApp)75 PSSAudio::PSSAudio(PApp &parentApp) : PSubsystem(parentApp)
76 {
77 PUtil::outLog() << "Initialising audio subsystem [NULL]" << std::endl;
78 }
79
~PSSAudio()80 PSSAudio::~PSSAudio()
81 {
82 PUtil::outLog() << "Shutting down audio subsystem" << std::endl;
83 samplist.clear();
84 }
85
tick()86 void PSSAudio::tick()
87 {
88 }
89
PAudioSample(const std::string & filename,bool positional3D)90 PAudioSample::PAudioSample(const std::string &filename, bool positional3D)
91 {
92 buffer = 0;
93
94 if (PUtil::isDebugLevel(DEBUGLEVEL_TEST))
95 PUtil::outLog() << "Loading sample \"" << filename << "\"" << std::endl;
96
97 unload();
98 name = filename;
99 }
100
unload()101 void PAudioSample::unload()
102 {
103 }
104
PAudioInstance(PAudioSample * _samp,bool looping)105 PAudioInstance::PAudioInstance(PAudioSample *_samp, bool looping) :
106 samp(_samp)
107 {
108 }
109
~PAudioInstance()110 PAudioInstance::~PAudioInstance()
111 {
112 }
113
114
update(const vec3f & pos,const vec3f & vel)115 void PAudioInstance::update(const vec3f &pos, const vec3f &vel)
116 {
117 }
118
setGain(float gain)119 void PAudioInstance::setGain(float gain)
120 {
121 }
122
setHalfDistance(float lambda)123 void PAudioInstance::setHalfDistance(float lambda)
124 {
125 }
126
setPitch(float pitch)127 void PAudioInstance::setPitch(float pitch)
128 {
129 }
130
131
play()132 void PAudioInstance::play()
133 {
134 }
135
stop()136 void PAudioInstance::stop()
137 {
138 }
139
isPlaying()140 bool PAudioInstance::isPlaying()
141 {
142 return false;
143 }
144
145 #endif // USE_NULL
146
147
148 #ifdef USE_OPENAL
149
150 #include <AL/al.h>
151 //#include <AL/alu.h> // not available in newest OpenAL
152 #include <AL/alut.h>
153
PSSAudio(PApp & parentApp)154 PSSAudio::PSSAudio(PApp &parentApp) : PSubsystem(parentApp)
155 {
156 PUtil::outLog() << "Initialising audio subsystem [OpenAL]" << std::endl;
157
158 if (alutInit(0, nullptr) != AL_TRUE)
159 throw MakePException("ALUT:alutInit() error: " + alutGetErrorString(alutGetError()));
160 }
161
~PSSAudio()162 PSSAudio::~PSSAudio()
163 {
164 PUtil::outLog() << "Shutting down audio subsystem" << std::endl;
165 samplist.clear();
166 alutExit();
167 }
168
tick()169 void PSSAudio::tick()
170 {
171 }
172
PAudioSample(const std::string & filename,bool positional3D)173 PAudioSample::PAudioSample(const std::string &filename, bool positional3D)
174 {
175 buffer = 0;
176 positional3D = positional3D; // unused (atm)
177
178 if (PUtil::isDebugLevel(DEBUGLEVEL_TEST))
179 PUtil::outLog() << "Loading sample \"" << filename << "\"" << std::endl;
180
181 unload();
182 /* load contents from file into memory using physfs functions */
183 name = filename;
184 PHYSFS_file *pfile = PHYSFS_openRead(filename.c_str());
185
186 if (!pfile)
187 {
188 throw MakePException ("Load failed: PhysFS: " + physfs_getErrorString());
189 }
190
191 int filesize = PHYSFS_fileLength(pfile);
192
193 char *wavbuffer = new char[filesize];
194
195 physfs_read(pfile, wavbuffer, sizeof(char), filesize);
196 PHYSFS_close(pfile);
197
198 /* create the alut buffer from memory contents */
199 this->buffer = alutCreateBufferFromFileImage(
200 reinterpret_cast<const ALvoid *>(wavbuffer),
201 filesize);
202
203 /* clean up */
204 delete [] wavbuffer;
205
206 /* check if loading was successful */
207 if (AL_NONE == this->buffer)
208 {
209 throw MakePException("Sample load failed:"
210 + alutGetErrorString(alutGetError()));
211 }
212 }
213
unload()214 void PAudioSample::unload()
215 {
216 if (buffer)
217 {
218 alDeleteBuffers(1, &buffer);
219 buffer = 0;
220 }
221 }
222
223
224
PAudioInstance(PAudioSample * _samp,bool looping)225 PAudioInstance::PAudioInstance(PAudioSample *_samp, bool looping)
226 {
227 samp = _samp;
228
229 alGenSources(1, &source);
230
231 alSourcei(source, AL_BUFFER, samp->buffer);
232 alSourcei(source, AL_LOOPING, looping ? AL_TRUE : AL_FALSE);
233
234 //alSourcePlay(source);
235 }
236
~PAudioInstance()237 PAudioInstance::~PAudioInstance()
238 {
239 if (isPlaying()) stop();
240 alDeleteSources(1, &source);
241 }
242
243
update(const vec3f & pos,const vec3f & vel)244 void PAudioInstance::update(const vec3f &pos, const vec3f &vel)
245 {
246 alSourcefv(source, AL_POSITION, (vec3f)pos);
247 alSourcefv(source, AL_VELOCITY, (vec3f)vel);
248 }
249
setGain(float gain)250 void PAudioInstance::setGain(float gain)
251 {
252 //alSourcef(source, AL_MIN_GAIN, gain);
253 //alSourcef(source, AL_MAX_GAIN, gain);
254 alSourcef(source, AL_GAIN, gain);
255 }
256
setHalfDistance(float lambda)257 void PAudioInstance::setHalfDistance(float lambda)
258 {
259 alSourcef(source, AL_REFERENCE_DISTANCE, lambda);
260 }
261
setPitch(float pitch)262 void PAudioInstance::setPitch(float pitch)
263 {
264 alSourcef(source, AL_PITCH, pitch);
265 }
266
267
play()268 void PAudioInstance::play()
269 {
270 alSourceRewind(source);
271 alSourcePlay(source);
272 }
273
stop()274 void PAudioInstance::stop()
275 {
276 alSourceStop(source);
277 }
278
isPlaying()279 bool PAudioInstance::isPlaying()
280 {
281 int state = AL_STOPPED;
282 alGetSourcei(source, AL_SOURCE_STATE, &state);
283 return (state == AL_PLAYING);
284 }
285
286 #endif // USE_OPENAL
287
288 #ifdef USE_FMOD
289
290 #include <fmod.h>
291 #include <fmod_errors.h>
292
293 namespace
294 {
295
296 FMOD_SYSTEM *fs;
297
298 // callbacks to integrate PhysFS with FMOD
299 FMOD_RESULT F_CALLBACK fmod_file_open(const char *name, unsigned int *filesize, void **handle, void *userdata);
300 FMOD_RESULT F_CALLBACK fmod_file_close(void *handle, void *userdata);
301 FMOD_RESULT F_CALLBACK fmod_file_read(void *handle, void *buffer, unsigned int sizebytes, unsigned int *bytesread, void *userdata);
302 FMOD_RESULT F_CALLBACK fmod_file_seek(void *handle, unsigned int pos, void *userdata);
303
fmod_file_open(const char * name,unsigned int * filesize,void ** handle,void * userdata)304 FMOD_RESULT F_CALLBACK fmod_file_open(const char *name, unsigned int *filesize, void **handle, void *userdata)
305 {
306 UNREFERENCED_PARAMETER(userdata);
307
308 if (PHYSFS_exists(name) == 0)
309 {
310 PUtil::outLog() << "PhysFS: file \"" << name << "\" was not found." << std::endl;
311 return FMOD_ERR_FILE_NOTFOUND;
312 }
313
314 *handle = PHYSFS_openRead(name);
315
316 if (*handle == nullptr)
317 {
318 PUtil::outLog() << "PhysFS: " << physfs_getErrorString() << std::endl;
319 return FMOD_ERR_FILE_BAD;
320 }
321
322 *filesize = PHYSFS_fileLength(reinterpret_cast<PHYSFS_File *> (*handle));
323 return FMOD_OK;
324 }
325
fmod_file_close(void * handle,void * userdata)326 FMOD_RESULT F_CALLBACK fmod_file_close(void *handle, void *userdata)
327 {
328 UNREFERENCED_PARAMETER(userdata);
329
330 if (PHYSFS_close(reinterpret_cast<PHYSFS_File *> (handle)) == 0)
331 PUtil::outLog() << "PhysFS: could not close a file." << std::endl;
332
333 return FMOD_OK;
334 }
335
fmod_file_read(void * handle,void * buffer,unsigned int sizebytes,unsigned int * bytesread,void * userdata)336 FMOD_RESULT F_CALLBACK fmod_file_read(void *handle, void *buffer, unsigned int sizebytes, unsigned int *bytesread, void *userdata)
337 {
338 UNREFERENCED_PARAMETER(userdata);
339
340 PHYSFS_File *hfile = reinterpret_cast<PHYSFS_File *> (handle);
341 PHYSFS_sint64 numbytes = physfs_read(hfile, buffer, sizeof(char), sizebytes);
342
343 if (numbytes == -1)
344 {
345 PUtil::outLog() << "PhysFS: " << physfs_getErrorString() << std::endl;
346 return FMOD_ERR_FILE_ENDOFDATA;
347 }
348
349 *bytesread = numbytes;
350 return FMOD_OK;
351 }
352
fmod_file_seek(void * handle,unsigned int pos,void * userdata)353 FMOD_RESULT F_CALLBACK fmod_file_seek(void *handle, unsigned int pos, void *userdata)
354 {
355 UNREFERENCED_PARAMETER(userdata);
356
357 if (PHYSFS_seek(reinterpret_cast<PHYSFS_File *> (handle), pos) == 0)
358 {
359 PUtil::outLog() << "PhysFS: " << physfs_getErrorString() << std::endl;
360 return FMOD_ERR_FILE_COULDNOTSEEK;
361 }
362
363 return FMOD_OK;
364 }
365
366 }
367
368 ///
369 /// @brief Initializes the FMOD audio subsystem.
370 /// @param [in,out] parentApp
371 /// @todo Put FMOD credit line in documentation?
372 ///
PSSAudio(PApp & parentApp)373 PSSAudio::PSSAudio(PApp &parentApp):
374 PSubsystem(parentApp)
375 {
376 PUtil::outLog() << "Initialising audio subsystem [FMOD]" << std::endl;
377 // PUtil::outLog() << "Audio Engine supplied by FMOD by Firelight Technologies." << std::endl;
378
379 FMOD_RESULT fr = FMOD_System_Create(&fs);
380
381 if (fr != FMOD_OK)
382 throw MakePException("FMOD initialisation failed: " + FMOD_ErrorString(fr));
383
384 fr = FMOD_System_Init(fs, 512, FMOD_INIT_NORMAL, nullptr);
385
386 if (fr != FMOD_OK)
387 throw MakePException("FMOD initialisation failed: " + FMOD_ErrorString(fr));
388
389 fr = FMOD_System_SetFileSystem(
390 fs,
391 fmod_file_open,
392 fmod_file_close,
393 fmod_file_read,
394 fmod_file_seek,
395 nullptr,
396 nullptr,
397 -1
398 );
399
400 if (fr != FMOD_OK)
401 throw MakePException("FMOD initialisation failed: " + FMOD_ErrorString(fr));
402 }
403
404 ///
405 /// @brief Shuts down the FMOD audio subsystem.
406 ///
~PSSAudio()407 PSSAudio::~PSSAudio()
408 {
409 PUtil::outLog() << "Shutting down audio subsystem" << std::endl;
410 samplist.clear();
411 FMOD_System_Release(fs);
412 }
413
414 ///
415 /// @brief Updates the FMOD system.
416 ///
tick()417 void PSSAudio::tick()
418 {
419 FMOD_System_Update(fs);
420 }
421
422 ///
423 /// @brief Loads an audio sample within the FMOD audio subsystem.
424 /// @param [in] filename The filename of the audio sample.
425 /// @param positional3D Flag to load file as 3D or 2D.
426 ///
PAudioSample(const std::string & filename,bool positional3D)427 PAudioSample::PAudioSample(const std::string &filename, bool positional3D):
428 buffer(nullptr)
429 {
430 if (PUtil::isDebugLevel(DEBUGLEVEL_TEST))
431 PUtil::outLog() << "Loading sample \"" << filename << "\"" << std::endl;
432
433 name = filename;
434
435 FMOD_RESULT fr = FMOD_System_CreateSound(fs, filename.c_str(),
436 /*FMOD_UNIQUE |*/ (positional3D ? FMOD_3D : FMOD_2D), nullptr, &buffer);
437
438 if (fr != FMOD_OK)
439 throw MakePException("Sample load failed: " + FMOD_ErrorString(fr));
440 }
441
442 ///
443 /// @brief Unloads the audio sample within the FMOD audio subsystem.
444 ///
unload()445 void PAudioSample::unload()
446 {
447 if (buffer != nullptr)
448 {
449 FMOD_Sound_Release(buffer);
450 buffer = nullptr;
451 }
452 }
453
454 ///
455 /// @brief Sets up an audio sample to be played within the FMOD audio subsystem.
456 /// @todo IN params should be pointer-to-const.
457 /// @param [in] _samp Sample to be played.
458 /// @param looping Flag to enable looping.
459 ///
PAudioInstance(PAudioSample * _samp,bool looping)460 PAudioInstance::PAudioInstance(PAudioSample *_samp, bool looping):
461 samp(_samp)
462 {
463 FMOD_System_PlaySound(fs, samp->buffer, nullptr, true, &source);
464 FMOD_Channel_GetFrequency(source, &reserved1);
465 FMOD_Channel_SetMode(source, looping ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF);
466 }
467
~PAudioInstance()468 PAudioInstance::~PAudioInstance()
469 {
470 if (isPlaying())
471 stop();
472 }
473
update(const vec3f & pos,const vec3f & vel)474 void PAudioInstance::update(const vec3f &pos, const vec3f &vel)
475 {
476 // TODO
477 UNREFERENCED_PARAMETER(pos);
478 UNREFERENCED_PARAMETER(vel);
479 }
480
481 ///
482 /// @brief Sets the gain of the channel.
483 /// @param gain The desired gain.
484 /// @pre `gain` must be a value within the [0.0, 0.1] interval.
485 ///
setGain(float gain)486 void PAudioInstance::setGain(float gain)
487 {
488 CLAMP(gain, 0.0f, 1.0f);
489 FMOD_Channel_SetVolume(source, gain);
490 }
491
setHalfDistance(float lambda)492 void PAudioInstance::setHalfDistance(float lambda)
493 {
494 // TODO
495 UNREFERENCED_PARAMETER(lambda);
496 }
497
498 ///
499 /// @brief Sets the pitch of the channel.
500 /// @param pitch The desired pitch.
501 ///
setPitch(float pitch)502 void PAudioInstance::setPitch(float pitch)
503 {
504 FMOD_Channel_SetFrequency(source, pitch * reserved1);
505 }
506
507 ///
508 /// @brief Unpauses the channel.
509 ///
play()510 void PAudioInstance::play()
511 {
512 FMOD_Channel_SetPaused(source, false);
513 }
514
515 ///
516 /// @brief Stops the channel.
517 ///
stop()518 void PAudioInstance::stop()
519 {
520 FMOD_Channel_Stop(source);
521 }
522
523 ///
524 /// @brief Checks if the channel is playing.
525 /// @returns Whether or not the channel is playing.
526 /// @retval true If the channel is playing.
527 /// @retval false If the channel isn't playing.
528 /// @todo Stop fooling around and remove the redundant comments above.
529 ///
isPlaying()530 bool PAudioInstance::isPlaying()
531 {
532 FMOD_BOOL fb;
533
534 FMOD_Channel_IsPlaying(source, &fb);
535 return static_cast<bool> (fb);
536 }
537
538 #endif // USE_FMOD
539
540 #ifdef USE_SDL_MIXER
541
542 #include <SDL_mixer.h>
543
PSSAudio(PApp & parentApp)544 PSSAudio::PSSAudio(PApp &parentApp) : PSubsystem(parentApp)
545 {
546 PUtil::outLog() << "Initialising audio subsystem [SDL_mixer]" << std::endl;
547
548 if (Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 1, 2048) != 0)
549 {
550 PUtil::outLog() << "SDL_mixer failed to initialise" << std::endl;
551 PUtil::outLog() << "SDL_mixer: " << Mix_GetError() << std::endl;
552 return PException ();
553 }
554 }
555
~PSSAudio()556 PSSAudio::~PSSAudio()
557 {
558 PUtil::outLog() << "Shutting down audio subsystem" << std::endl;
559
560 samplist.clear();
561
562 Mix_CloseAudio();
563 }
564
565
PAudioSample(const std::string & filename,bool positional3D)566 PAudioSample::PAudioSample(const std::string &filename, bool positional3D)
567 {
568 buffer = 0;
569
570 if (PUtil::isDebugLevel(DEBUGLEVEL_TEST))
571 PUtil::outLog() << "Loading sample \"" << filename << "\"" << std::endl;
572
573 unload();
574
575 name = filename;
576
577 PHYSFS_file *pfile = PHYSFS_openRead(filename.c_str());
578
579 if (!pfile)
580 {
581 PUtil::outLog() << "Load failed: PhysFS: " << physfs_getErrorString() << std::endl;
582 throw PFileException ();
583 }
584
585 buffer = (uint32) Mix_LoadWAV_RW(PUtil::allocPhysFSops(pfile), 1);
586
587 PHYSFS_close(pfile);
588
589 if (!buffer)
590 {
591 PUtil::outLog() << "Sample load failed" << std::endl;
592 PUtil::outLog() << "SDL_mixer: " << Mix_GetError() << std::endl;
593 throw PFileException ();
594 }
595 }
596
unload()597 void PAudioSample::unload()
598 {
599 if (buffer)
600 Mix_FreeChunk((Mix_Chunk *) buffer);
601 buffer = 0;
602 }
603 }
604
605
606 PAudioInstance::PAudioInstance(PAudioSample *_samp, bool looping) :
607 samp(_samp)
608 {
609 source = (uint32) Mix_PlayChannel(-1
610 (Mix_Chunk *)samp->buffer, looping ? -1 : 0);
611
612 *((float*)&reserved1) = (float)FSOUND_GetFrequency((int)source);
613
614 FSOUND_SetLoopMode((int)source,
615 looping ? FSOUND_LOOP_NORMAL : FSOUND_LOOP_OFF);
616 }
617
618 PAudioInstance::~PAudioInstance()
619 {
620 if (source != -1)
621 {
622 }
623 }
624
625
626 void PAudioInstance::update(const vec3f &pos, const vec3f &vel)
627 {
628 // TODO
629 }
630
631 void PAudioInstance::setGain(float gain)
632 {
633 }
634
635 void PAudioInstance::setHalfDistance(float lambda)
636 {
637 // TODO
638 }
639
640 void PAudioInstance::setPitch(float pitch)
641 {
642 }
643
644
645 void PAudioInstance::play()
646 {
647 }
648
649 void PAudioInstance::stop()
650 {
651 }
652
653 bool PAudioInstance::isPlaying()
654 {
655 return false;
656 }
657
658 #endif // USE_SDL_MIXER
659
660
661
662
663