Angel 3.2
A 2D Game Prototyping Engine
SoundDevice.cpp
1 
2 // Copyright (C) 2008-2013, Shane Liesegang
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of the copyright holder nor the names of any
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 // POSSIBILITY OF SUCH DAMAGE.
29 
30 #include "stdafx.h"
31 #include "../Infrastructure/SoundDevice.h"
32 
33 #include <assert.h>
34 
35 #include "../Infrastructure/Log.h"
36 
37 #if ANGEL_DISABLE_FMOD
38  #include <vorbis/vorbisfile.h>
39  #include <cstdio>
40  #include <iostream>
41 #endif
42 
43 #if !ANGEL_DISABLE_FMOD
44  #define ANGEL_SOUND_CHECKED( call ) \
45  { \
46  FMOD_RESULT result = call; \
47  ERRCHECK(result); \
48  }
49 
50  // Helper function for FMOD errors.
51  void ERRCHECK(FMOD_RESULT result)
52  {
53  if (result != FMOD_OK)
54  {
55  sysLog.Printf("FMOD error! (%d) %s\n", result, FMOD_ErrorString(result));
56  }
57  }
58 #else
59  #define ANGEL_SOUND_CHECKED( call ) \
60  { \
61  call;\
62  ERRCHECK(__LINE__);\
63  }
64 
65  const char * OpenAL_ErrorString(ALenum errCode)
66  {
67  String retVal;
68  switch (errCode)
69  {
70  case AL_NO_ERROR:
71  retVal = "There is no current error.";
72  break;
73  case AL_INVALID_NAME:
74  retVal = "Invalid name parameter.";
75  break;
76  case AL_INVALID_ENUM:
77  retVal = "Invalid parameter.";
78  break;
79  case AL_INVALID_VALUE:
80  retVal = "Invalid enum parameter value.";
81  break;
82  case AL_INVALID_OPERATION:
83  retVal = "Invalid call.";
84  break;
85  case AL_OUT_OF_MEMORY:
86  retVal = "Unable to allocate memory.";
87  break;
88  default:
89  retVal = "Unknown error.";
90  }
91  return retVal.c_str();
92  }
93 
94  void ERRCHECK(int lineNumber)
95  {
96  ALenum errCode = alGetError();
97  while (errCode != AL_NO_ERROR)
98  {
99  sysLog.Printf("OpenAL error! Line %i: %s", lineNumber, OpenAL_ErrorString(errCode));
100  errCode = alGetError();
101  }
102  }
103 
104  // 64 KB buffers; consider increasing this if you're having trouble with streaming audio
105  // skipping, or decreasing it if you're running into memory problems (very unlikely)
106  #define ANGEL_OPENAL_BUFFER_SIZE 1024 * 64
107 #endif
108 
109 /* static */
110 SoundDevice* SoundDevice::s_soundDevice = NULL;
111 
112 /* static */
114 {
115  if (s_soundDevice == NULL)
116  {
117  s_soundDevice = new SoundDevice();
118  }
119 
120  return *s_soundDevice;
121 }
122 
123 #if !ANGEL_DISABLE_FMOD
124 /* static */
125  FMOD_RESULT F_CALLBACK SoundDevice::FMOD_SoundCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type, void *commanddata1, void *commanddata2) {
126  FMOD::Channel *soundChannel = (FMOD::Channel *)channel;
127 
128  if (theSound.soundCallback.GetInstance() && theSound.soundCallback.GetFunction())
129  {
130  theSound.soundCallback.Execute( reinterpret_cast<AngelSoundHandle>(soundChannel) );
131  }
132 
133  return FMOD_OK;
134  }
135 #endif
136 
137 void SoundDevice::Initialize()
138 {
139  if (_system != NULL)
140  {
141  sysLog.Log("ERROR: Sound system already initialized.");
142  return;
143  }
144 
145  #if !ANGEL_DISABLE_FMOD
146  unsigned int version;
147  FMOD_SPEAKERMODE speakermode;
148  FMOD_CAPS caps;
149 
150  /* Create a System object and initialize. */
151  ANGEL_SOUND_CHECKED( FMOD::System_Create(&_system) )
152  ANGEL_SOUND_CHECKED( _system->getVersion(&version) )
153 
154  if (version < FMOD_VERSION)
155  {
156  sysLog.Printf("Error! You are using an old version of FMOD %08x. This program requires %08x\n", version, FMOD_VERSION);
157  return;
158  }
159 
160  // On Linux, sometimes autodetection fails. Let the user override with an environment variable.
161  #ifdef __linux__
162  if (getenv("FMOD_OUTPUTTYPE"))
163  {
164  FMOD_OUTPUTTYPE outtype = FMOD_OUTPUTTYPE_AUTODETECT;
165  String FMOD_OUTPUTTYPE_VAR = getenv("FMOD_OUTPUTTYPE");
166  if (FMOD_OUTPUTTYPE_VAR == "ALSA")
167  outtype = FMOD_OUTPUTTYPE_ALSA;
168  else if (FMOD_OUTPUTTYPE_VAR == "OSS")
169  outtype = FMOD_OUTPUTTYPE_OSS;
170  else if (FMOD_OUTPUTTYPE_VAR == "ESD")
171  outtype = FMOD_OUTPUTTYPE_ESD;
172  else if (FMOD_OUTPUTTYPE_VAR == "NOSOUND")
173  outtype = FMOD_OUTPUTTYPE_NOSOUND;
174  else
175  sysLog.Printf("Error! Unknown FMOD Output Type...falling back to autodetection.\n");
176  ANGEL_SOUND_CHECKED( _system->setOutput(outtype) );
177  }
178  #endif // __linux__
179 
180  ANGEL_SOUND_CHECKED( _system->getDriverCaps(0, &caps, 0, &speakermode) )
181  /* Set the user selected speaker mode. */
182  ANGEL_SOUND_CHECKED( _system->setSpeakerMode(speakermode) )
183 
184  /* The user has the 'Acceleration' slider set to off! This is really bad for latency!. */
185  /* You might want to warn the user about this. */
186  if (caps & FMOD_CAPS_HARDWARE_EMULATED)
187  {
188  /* At 48khz, the latency between issuing an fmod command and hearing it will now be about 213ms. */
189  ANGEL_SOUND_CHECKED( _system->setDSPBufferSize(1024, 10) )
190  }
191 
192  /* Replace with whatever channel count and flags you use! */
193  FMOD_RESULT result = _system->init(100, FMOD_INIT_NORMAL, 0);
194 
195  /* Ok, the speaker mode selected isn't supported by this soundcard. Switch it back to stereo... */
196  if (result == FMOD_ERR_OUTPUT_CREATEBUFFER)
197  {
198  ANGEL_SOUND_CHECKED( _system->setSpeakerMode(FMOD_SPEAKERMODE_STEREO) )
199  /* Replace with whatever channel count and flags you use! */
200  ANGEL_SOUND_CHECKED( _system->init(100, FMOD_INIT_NORMAL, 0) )
201  }
202  #else
203  ALCdevice *device;
204  ALCcontext *context;
205 
206  device = alcOpenDevice (NULL);
207  if (device == NULL)
208  {
209  return; //AL_FALSE;
210  }
211 
212  context = alcCreateContext (device, NULL);
213  if (context == NULL)
214  {
215  alcCloseDevice (device);
216  return; //AL_FALSE;
217  }
218 
219  if (!alcMakeContextCurrent (context))
220  {
221  alcDestroyContext (context);
222  alcCloseDevice (device);
223  return; //AL_FALSE;
224  }
225 
226  //initialisationState = ALUTDeviceAndContext;
227  _system = context;
228 
229  ANGEL_SOUND_CHECKED( alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f) )
230  #endif // !ANGEL_DISABLE_FMOD
231 }
232 
234 {
235  #if !ANGEL_DISABLE_FMOD
236  // FMOD documentation on shutting down.
237  // Call System::release to close the output device and free all memory associated with that object.
238  // Channels are stopped, but sounds are not released. You will have to free them first. You do not have to stop channels yourself.
239  // You can of course do it if you want, it is just redundant, but releasing sounds is good programming practice anyway.
240  // You do not have to call System::close if you are releasing the system object. System::release internally calls System::close anyway.
241 
242  std::vector<FMOD::Sound*>::iterator it = _samples.begin();
243  while ( it != _samples.end() )
244  {
245  (*it++)->release();
246  }
247  _samples.clear();
248 
249  // Shutdown FMOD.
250  ANGEL_SOUND_CHECKED( _system->release() )
251 
252  // Give back the memory for the SoundDevice.
253  delete s_soundDevice;
254  #else
255  std::vector<ALuint>::iterator it = _sources.begin();
256  while (it != _sources.end())
257  {
258  alDeleteSources(1, &(*it));
259  it++;
260  }
261  it = _buffers.begin();
262  while (it != _buffers.end())
263  {
264  alDeleteBuffers(1, &(*it));
265  it++;
266  }
267 
268  ALCdevice *device;
269 
270  if (!alcMakeContextCurrent (NULL))
271  {
272  return; //AL_FALSE;
273  }
274 
275  device = alcGetContextsDevice (_system);
276  alcDestroyContext (_system);
277  if (alcGetError (device) != ALC_NO_ERROR)
278  {
279  return; //AL_FALSE;
280  }
281 
282  if (!alcCloseDevice (device))
283  {
284  return; //AL_FALSE;
285  }
286  #endif // !ANGEL_DISABLE_FMOD
287 }
288 
289 AngelSampleHandle SoundDevice::LoadSample(const String& filename, bool isStream)
290 {
291  #if !ANGEL_DISABLE_FMOD
292  int flags = FMOD_DEFAULT;
293  if (isStream )
294  flags |= FMOD_CREATESTREAM;
295 
296  FMOD::Sound* newSample;
297  ANGEL_SOUND_CHECKED( _system->createSound(filename.c_str(), flags, 0, &newSample) )
298 
299  // We don't try and stop duplicates. If they want to have multiples of the
300  // same stream going at once, they have to have unique samples loaded for each.
301  _samples.push_back(newSample);
302 
303  return newSample;
304  #else
305  int endian = 0;
306  int bitStream;
307  long bytes;
308  char initialBuffer[ANGEL_OPENAL_BUFFER_SIZE];
309  std::vector<char> fullBuffer;
310  FILE *f;
311  ALenum format;
312  ALsizei freq;
313  ALuint bufferID;
314 
315  vorbis_info *pInfo;
316  OggVorbis_File oggFile;
317 
318  f = fopen(filename.c_str(), "rb");
319 
320  if (f == NULL)
321  {
322  sysLog.Printf("ERROR: Could not find file named \"%s\"", filename.c_str());
323  return 0;
324  }
325 
326  ov_open(f, &oggFile, NULL, 0);
327  pInfo = ov_info(&oggFile, -1);
328 
329  if (pInfo == NULL)
330  {
331  sysLog.Printf("ERROR: \"%s\" is not a valid Ogg Vorbis file.", filename.c_str());
332  return 0;
333  }
334 
335  if (pInfo->channels == 1)
336  {
337  format = AL_FORMAT_MONO16;
338  }
339  else
340  {
341  format = AL_FORMAT_STEREO16;
342  }
343 
344  freq = pInfo->rate;
345 
346  if (!isStream) {
347  do
348  {
349  bytes = ov_read(&oggFile, initialBuffer, ANGEL_OPENAL_BUFFER_SIZE, endian, 2, 1, &bitStream);
350  fullBuffer.insert(fullBuffer.end(), initialBuffer, initialBuffer + bytes);
351  } while (bytes > 0);
352 
353  ov_clear(&oggFile);
354 
355  ANGEL_SOUND_CHECKED( alGenBuffers(1, &bufferID) )
356  ANGEL_SOUND_CHECKED( alBufferData(bufferID, format, &fullBuffer[0], static_cast<ALsizei> (fullBuffer.size()), freq) )
357 
358  _buffers.push_back(bufferID);
359 
360  return bufferID;
361  }
362  else
363  {
364  StreamingAudio sa;
365  ANGEL_SOUND_CHECKED( alGenBuffers(2, sa.buffers) )
366  sa.source = 0;
367  sa.file = oggFile;
368  sa.format = format;
369  sa.vorbisInfo = pInfo;
370 
371  _streams[sa.buffers[0]] = sa;
372 
373  _buffers.push_back(sa.buffers[0]);
374  _buffers.push_back(sa.buffers[1]);
375 
376  return sa.buffers[0];
377  }
378  #endif // !ANGEL_DISABLE_FMOD
379 }
380 
381 #if ANGEL_DISABLE_FMOD
382  bool SoundDevice::_isSampleStreamed(AngelSampleHandle sample)
383  {
384  std::map<ALuint, StreamingAudio>::iterator it = _streams.find(sample);
385  if (_streams.end() == it)
386  {
387  return false;
388  }
389  else
390  {
391  return true;
392  }
393  }
394 
395  bool SoundDevice::_streamAudio(ALuint buffer, StreamingAudio &sa)
396  {
397  // grab datums from the file
398  char bufferData[ANGEL_OPENAL_BUFFER_SIZE];
399  int size = 0;
400  int section;
401  int result;
402 
403  while (size < ANGEL_OPENAL_BUFFER_SIZE)
404  {
405  result = ov_read(&sa.file, bufferData + size, ANGEL_OPENAL_BUFFER_SIZE - size, 0, 2, 1, &section);
406 
407  if (result > 0)
408  {
409  size += result;
410  }
411  else
412  {
413  if (result < 0)
414  {
415  // error handling
416  }
417  else // result == 0
418  {
419  break;
420  }
421  }
422  }
423 
424  if (size == 0)
425  {
426  return false;
427  }
428  else
429  {
430  ANGEL_SOUND_CHECKED( alBufferData(buffer, sa.format, bufferData, size, sa.vorbisInfo->rate) )
431  return true;
432  }
433  }
434 #endif // ANGEL_DISABLE_FMOD
435 
436 AngelSoundHandle SoundDevice::PlaySound(AngelSampleHandle sample, float volume, bool looping, int flags)
437 {
438  #if !ANGEL_DISABLE_FMOD
439  if (sample == NULL)
440  {
441  sysLog.Log("ERROR: Invalid AngelSampleHandle.");
442  return NULL;
443  }
444 
445  if (flags)
446  sysLog.Log("WARNING: PlaySound doesn't use the passed in flags yet.");
447 
448  // Start paused, tweak setting and unpause.
449  FMOD::Channel* FMOD_Channel;
450  FMOD::Sound* FMOD_Sound = reinterpret_cast<FMOD::Sound*>(sample);
451  ANGEL_SOUND_CHECKED( _system->playSound(FMOD_CHANNEL_FREE, FMOD_Sound, true/*paused*/, &FMOD_Channel) )
452 
453  // Volume.
454  ANGEL_SOUND_CHECKED( FMOD_Channel->setVolume(volume) )
455 
456  // Looping.
457  int modeFlags = 0;
458  if (looping)
459  modeFlags |= FMOD_LOOP_NORMAL;
460  else
461  modeFlags |= FMOD_LOOP_OFF;
462  ANGEL_SOUND_CHECKED( FMOD_Channel->setMode(modeFlags) )
463 
464  // Unpause.
465  ANGEL_SOUND_CHECKED( FMOD_Channel->setPaused(false) )
466 
467  FMOD_Channel->setCallback(&SoundDevice::FMOD_SoundCallback);
468 
469  return FMOD_Channel;
470  #else
471  ALuint sourceID;
472  ANGEL_SOUND_CHECKED(alGenSources(1, &sourceID))
473  alSource3f(sourceID, AL_POSITION, 0.0f, 0.0f, 0.0f);
474  alSourcef(sourceID, AL_GAIN, volume);
475 
476  _sources.push_back(sourceID);
477 
478  if (!_isSampleStreamed(sample))
479  {
480  // Not streamed; just do the simple play
481  alSourcei(sourceID, AL_BUFFER, sample);
482  alSourcei(sourceID, AL_LOOPING, looping);
483  ANGEL_SOUND_CHECKED( alSourcePlay(sourceID) )
484  }
485  else
486  {
487  if (!_streamAudio(_streams[sample].buffers[0], _streams[sample]))
488  {
489  return 0;
490  }
491 
492  if (!_streamAudio(_streams[sample].buffers[1], _streams[sample]))
493  {
494  return 0;
495  }
496 
497  _streams[sample].source = sourceID;
498  _streams[sample].looped = looping;
499  ANGEL_SOUND_CHECKED( alSourceQueueBuffers(sourceID, 2, _streams[sample].buffers) )
500  ANGEL_SOUND_CHECKED( alSourcePlay(sourceID) )
501  }
502 
503  return sourceID;
504  #endif // !ANGEL_DISABLE_FMOD
505 }
506 
508 {
509  #if !ANGEL_DISABLE_FMOD
510  ANGEL_SOUND_CHECKED( _system->update() )
511  #else
512  std::map<ALuint, StreamingAudio>::iterator it = _streams.begin();
513  while (it != _streams.end())
514  {
515  if (it->second.source == 0)
516  {
517  continue;
518  }
519 
520  int processed;
521  bool active = true;
522  alGetSourcei(it->second.source, AL_BUFFERS_PROCESSED, &processed);
523 
524  while (processed--)
525  {
526  ALuint buffer;
527 
528  ANGEL_SOUND_CHECKED( alSourceUnqueueBuffers(it->second.source, 1, &buffer) )
529 
530  active = _streamAudio(buffer, it->second);
531 
532  ANGEL_SOUND_CHECKED( alSourceQueueBuffers(it->second.source, 1, &buffer) )
533 
534  // check to make sure we're still playing...
535  // (if we didn't get update calls for a while [like if the player was
536  // moving the window or something], we may have run out our existing
537  // buffers and gotten stopped by OpenAL)
538  if (!IsPlaying(it->second.source))
539  {
540  alSourcePlay(it->second.source);
541  break;
542  }
543  }
544 
545  if (!active)
546  {
547  // done streaming -- repeat?
548  if (it->second.looped)
549  {
550  // NOTE: Somewhere in here is the secret to avoiding popping
551  // in loops. If someone can suss it out, they would have
552  // glory and songs in their name. -- SJML
553 
554  //ANGEL_SOUND_CHECKED( alSourceUnqueueBuffers(it->second.source, 2, it->second.buffers) )
555  ov_raw_seek(&it->second.file, 0);
556  //_streamAudio(it->second.buffers[0], it->second);
557  //_streamAudio(it->second.buffers[1], it->second);
558  //ANGEL_SOUND_CHECKED( alSourceQueueBuffers(it->second.source, 2, it->second.buffers) )
559  }
560  else
561  {
562  if (theSound.soundCallback.GetInstance() && theSound.soundCallback.GetFunction())
563  {
564  theSound.soundCallback.Execute(it->second.source);
565  }
566  _streams.erase(it->second.source);
567  }
568  }
569 
570  it++;
571  }
572  #endif
573 }
574 
575 void SoundDevice::StopSound(AngelSoundHandle sound)
576 {
577  if (!sound)
578  return;
579 
580  #if !ANGEL_DISABLE_FMOD
581  FMOD::Channel* FMOD_Channel = reinterpret_cast<FMOD::Channel*>(sound);
582  ANGEL_SOUND_CHECKED( FMOD_Channel->stop() )
583  #else
584  ANGEL_SOUND_CHECKED( alSourceStop(sound) )
585  #endif
586 }
587 
588 void SoundDevice::PauseSound(AngelSoundHandle sound, bool paused)
589 {
590  if (!sound)
591  return;
592 
593  #if !ANGEL_DISABLE_FMOD
594  ANGEL_SOUND_CHECKED( reinterpret_cast<FMOD::Channel*>(sound)->setPaused(paused) )
595  #else
596  if (paused)
597  {
598  ANGEL_SOUND_CHECKED( alSourcePause(sound) )
599  }
600  else
601  {
602  ANGEL_SOUND_CHECKED( alSourcePlay(sound) )
603  }
604  #endif
605 }
606 
607 bool SoundDevice::IsPlaying(AngelSoundHandle sound)
608 {
609  if (!sound)
610  return false;
611 
612  #if !ANGEL_DISABLE_FMOD
613  bool playing = false;
614  ANGEL_SOUND_CHECKED( reinterpret_cast<FMOD::Channel*>(sound)->isPlaying(&playing) )
615 
616  return (playing);
617  #else
618  ALint state;
619  alGetSourcei(sound, AL_SOURCE_STATE, &state);
620  return (state == AL_PLAYING);
621  #endif
622 }
623 
624 bool SoundDevice::IsPaused(AngelSoundHandle sound)
625 {
626  if (!sound)
627  return false;
628 
629  #if !ANGEL_DISABLE_FMOD
630  bool paused = false;
631  ANGEL_SOUND_CHECKED( reinterpret_cast<FMOD::Channel*>(sound)->getPaused(&paused) )
632 
633  return (paused);
634  #else
635  ALint state;
636  alGetSourcei(sound, AL_SOURCE_STATE, &state);
637  return (state == AL_PAUSED);
638  #endif
639 }
640 
641 void SoundDevice::SetPan(AngelSoundHandle sound, float newPan)
642 {
643  if (!sound)
644  return;
645 
646  #if !ANGEL_DISABLE_FMOD
647  ANGEL_SOUND_CHECKED( reinterpret_cast<FMOD::Channel*>(sound)->setPan(newPan) )
648  #else
649  ALfloat pos[] = {newPan, 0.0f, 0.0f};
650  ANGEL_SOUND_CHECKED( alSourcefv(sound, AL_POSITION, pos) )
651  #endif
652 }
653 
654 void SoundDevice::SetVolume(AngelSoundHandle sound, float newVolume)
655 {
656  if (!sound)
657  return;
658 
659  #if !ANGEL_DISABLE_FMOD
660  ANGEL_SOUND_CHECKED( reinterpret_cast<FMOD::Channel*>(sound)->setVolume(newVolume) )
661  #else
662  ANGEL_SOUND_CHECKED( alSourcef(sound, AL_GAIN, newVolume) )
663  #endif
664 }