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