| [4597] | 1 | /* | 
|---|
| [4504] | 2 |    orxonox - the future of 3D-vertical-scrollers | 
|---|
 | 3 |  | 
|---|
 | 4 |    Copyright (C) 2004 orx | 
|---|
 | 5 |  | 
|---|
 | 6 |    This program is free software; you can redistribute it and/or modify | 
|---|
 | 7 |    it under the terms of the GNU General Public License as published by | 
|---|
 | 8 |    the Free Software Foundation; either version 2, or (at your option) | 
|---|
 | 9 |    any later version. | 
|---|
 | 10 |  | 
|---|
 | 11 |    ### File Specific: | 
|---|
 | 12 |    main-programmer: Benjamin Grauer | 
|---|
 | 13 |    co-programmer: ... | 
|---|
 | 14 |  | 
|---|
 | 15 |    code has been taken from http://www.devmaster.net/articles.php?catID=6 | 
|---|
 | 16 |    The code has been applied to our needs, and many things have been changed. | 
|---|
 | 17 | */ | 
|---|
 | 18 |  | 
|---|
| [5386] | 19 | #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_SOUND | 
|---|
| [4504] | 20 |  | 
|---|
 | 21 | #include "sound_engine.h" | 
|---|
 | 22 |  | 
|---|
| [4960] | 23 | #include "class_list.h" | 
|---|
| [4504] | 24 |  | 
|---|
 | 25 | #include "p_node.h" | 
|---|
 | 26 | #include "list.h" | 
|---|
 | 27 | #include "resource_manager.h" | 
|---|
 | 28 | #include "debug.h" | 
|---|
| [5955] | 29 | #include "parser/ini_parser/ini_parser.h" | 
|---|
| [5427] | 30 | #include "globals.h" | 
|---|
| [4504] | 31 |  | 
|---|
 | 32 | using namespace std; | 
|---|
 | 33 |  | 
|---|
| [4597] | 34 |  | 
|---|
| [4504] | 35 | ////////////////// | 
|---|
 | 36 | /* SOUND-ENGINE */ | 
|---|
 | 37 | ////////////////// | 
|---|
 | 38 | /** | 
|---|
| [4836] | 39 |  *  standard constructor | 
|---|
| [4504] | 40 | */ | 
|---|
| [4597] | 41 | SoundEngine::SoundEngine () | 
|---|
| [4504] | 42 | { | 
|---|
| [4597] | 43 |   this->setClassID(CL_SOUND_ENGINE, "SoundEngine"); | 
|---|
 | 44 |   this->setName("SoundEngine"); | 
|---|
 | 45 |  | 
|---|
| [4504] | 46 |   this->listener = NULL; | 
|---|
| [4960] | 47 |   this->bufferList = NULL; | 
|---|
 | 48 |   this->sourceList = NULL; | 
|---|
| [5955] | 49 |  | 
|---|
 | 50 |   this->device = NULL; | 
|---|
 | 51 |   this->context = NULL; | 
|---|
 | 52 |  | 
|---|
 | 53 |   this->maxSourceCount = 32; | 
|---|
| [4504] | 54 | } | 
|---|
 | 55 |  | 
|---|
 | 56 | /** | 
|---|
| [4836] | 57 |  *  the singleton reference to this class | 
|---|
| [4504] | 58 | */ | 
|---|
 | 59 | SoundEngine* SoundEngine::singletonRef = NULL; | 
|---|
 | 60 |  | 
|---|
 | 61 | /** | 
|---|
| [4836] | 62 |  *  standard deconstructor | 
|---|
| [5293] | 63 |  */ | 
|---|
| [4597] | 64 | SoundEngine::~SoundEngine () | 
|---|
| [4504] | 65 | { | 
|---|
 | 66 |   // deleting all the SoundSources | 
|---|
| [4960] | 67 |   if(this->sourceList != NULL) | 
|---|
 | 68 |   { | 
|---|
| [5779] | 69 |     while (this->sourceList->size() > 0) | 
|---|
 | 70 |       delete dynamic_cast<SoundSource*>(this->sourceList->front()); | 
|---|
| [4960] | 71 |   } | 
|---|
| [4504] | 72 |  | 
|---|
| [5955] | 73 |   while(!this->ALSources.empty()) | 
|---|
 | 74 |   { | 
|---|
 | 75 |     alDeleteSources(1, &this->ALSources.top()); | 
|---|
 | 76 |     this->ALSources.pop(); | 
|---|
 | 77 |   } | 
|---|
 | 78 |  | 
|---|
| [4504] | 79 |   // deleting all the SoundBuffers | 
|---|
| [4960] | 80 |   if (this->bufferList != NULL) | 
|---|
 | 81 |   { | 
|---|
| [5779] | 82 |     while(this->bufferList->size() > 0) | 
|---|
 | 83 |       ResourceManager::getInstance()->unload(dynamic_cast<SoundBuffer*>(this->bufferList->front())); | 
|---|
| [4960] | 84 |   } | 
|---|
| [5293] | 85 |  | 
|---|
| [4504] | 86 |   // removing openAL from AudioResource | 
|---|
| [5473] | 87 |   //! @todo this should be terminated through alc | 
|---|
 | 88 |   //alutExit(); | 
|---|
| [5293] | 89 |  | 
|---|
 | 90 |   SoundEngine::singletonRef = NULL; | 
|---|
| [4504] | 91 | } | 
|---|
 | 92 |  | 
|---|
 | 93 | /** | 
|---|
| [4985] | 94 |  * loads the settings of the SoundEngine from an ini-file | 
|---|
 | 95 |  * @param iniParser the IniParser of the inifile | 
|---|
 | 96 |  */ | 
|---|
 | 97 | void SoundEngine::loadSettings(IniParser* iniParser) | 
|---|
 | 98 | { | 
|---|
| [5955] | 99 |   const char* channels = iniParser->getVar(CONFIG_NAME_AUDIO_CHANNELS, CONFIG_SECTION_AUDIO, "32"); | 
|---|
 | 100 |   this->maxSourceCount = atoi(channels); | 
|---|
| [4985] | 101 |   const char* musicVolume = iniParser->getVar(CONFIG_NAME_MUSIC_VOLUME, CONFIG_SECTION_AUDIO, "80"); | 
|---|
 | 102 |   this->musicVolume = atof(musicVolume)/100.0; | 
|---|
 | 103 |  | 
|---|
 | 104 |   const char* effectsVolume = iniParser->getVar(CONFIG_NAME_EFFECTS_VOLUME, CONFIG_SECTION_AUDIO, "80"); | 
|---|
 | 105 |   this->effectsVolume = atof(effectsVolume)/100.0; | 
|---|
 | 106 | } | 
|---|
 | 107 |  | 
|---|
 | 108 | /** | 
|---|
| [4836] | 109 |  *  creates a new SoundSource. | 
|---|
 | 110 |  * @param fileName The Name to load the SoundBuffer from | 
|---|
 | 111 |  * @param sourceNode The sourceNode to bind this SoundSource to. | 
|---|
 | 112 |  * @returns The newly created SoundSource | 
|---|
| [4504] | 113 |  | 
|---|
 | 114 |    acctualy this is nothing more than a wrapper around the ResourceManager. | 
|---|
 | 115 | */ | 
|---|
 | 116 | SoundSource* SoundEngine::createSource(const char* fileName, PNode* sourceNode) | 
|---|
 | 117 | { | 
|---|
| [4885] | 118 |   return new SoundSource(sourceNode, (SoundBuffer*)ResourceManager::getInstance()->load(fileName, WAV, RP_LEVEL)); | 
|---|
| [4504] | 119 | } | 
|---|
 | 120 |  | 
|---|
 | 121 | /** | 
|---|
| [4836] | 122 |  *  Sets the doppler values of openAL | 
|---|
 | 123 |  * @param dopplerFactor the extent of the doppler-effect | 
|---|
 | 124 |  * @param dopplerVelocity the Speed the sound travels | 
|---|
| [4504] | 125 | */ | 
|---|
 | 126 | void SoundEngine::setDopplerValues(ALfloat dopplerFactor, ALfloat dopplerVelocity) | 
|---|
 | 127 | { | 
|---|
 | 128 |   alDopplerFactor(dopplerFactor); | 
|---|
 | 129 |   alDopplerVelocity(dopplerVelocity); | 
|---|
 | 130 | } | 
|---|
 | 131 |  | 
|---|
 | 132 |  | 
|---|
| [5955] | 133 | void SoundEngine::popALSource(ALuint& source) | 
|---|
| [4504] | 134 | { | 
|---|
| [5955] | 135 |   if (source != 0) | 
|---|
 | 136 |     return; | 
|---|
 | 137 |   else | 
|---|
 | 138 |   { | 
|---|
| [4504] | 139 |  | 
|---|
| [5955] | 140 |     /// @TODO try to create more sources if needed | 
|---|
 | 141 |     if (!this->ALSources.empty()) | 
|---|
| [4504] | 142 |     { | 
|---|
| [5955] | 143 |  | 
|---|
 | 144 |       source = this->ALSources.top(); | 
|---|
 | 145 |       printf("test: : %d\n", source); | 
|---|
 | 146 |       this->ALSources.pop(); | 
|---|
| [4504] | 147 |     } | 
|---|
| [5779] | 148 |   } | 
|---|
| [4504] | 149 | } | 
|---|
 | 150 |  | 
|---|
 | 151 |  | 
|---|
 | 152 | /** | 
|---|
| [4836] | 153 |  *  updates all The positions, Directions and Velocities of all Sounds | 
|---|
| [4504] | 154 | */ | 
|---|
| [4746] | 155 | void SoundEngine::update() | 
|---|
| [4504] | 156 | { | 
|---|
 | 157 |  | 
|---|
 | 158 |   // updating the Listeners Position | 
|---|
 | 159 |   if (likely(this->listener != NULL)) | 
|---|
 | 160 |     { | 
|---|
 | 161 |       alListener3f(AL_POSITION, | 
|---|
| [4597] | 162 |                    this->listener->getAbsCoor().x, | 
|---|
 | 163 |                    this->listener->getAbsCoor().y, | 
|---|
 | 164 |                    this->listener->getAbsCoor().z); | 
|---|
| [4504] | 165 |       alListener3f(AL_VELOCITY, | 
|---|
| [4597] | 166 |                    this->listener->getVelocity().x, | 
|---|
 | 167 |                    this->listener->getVelocity().y, | 
|---|
 | 168 |                    this->listener->getVelocity().z); | 
|---|
| [4504] | 169 |       Vector absDirV = this->listener->getAbsDirV(); | 
|---|
 | 170 |       ALfloat orientation [6] = {1,0,0, absDirV.x, absDirV.y, absDirV.z}; | 
|---|
 | 171 |       alListenerfv(AL_ORIENTATION, orientation); | 
|---|
 | 172 |     } | 
|---|
 | 173 |   else | 
|---|
 | 174 |     PRINTF(2)("no listener defined\n"); | 
|---|
 | 175 |  | 
|---|
 | 176 |   // updating all the Sources positions | 
|---|
| [5955] | 177 |   if (likely(this->sourceList != NULL || (this->sourceList = ClassList::getList(CL_SOUND_SOURCE)) != NULL)) | 
|---|
| [4960] | 178 |   { | 
|---|
| [5955] | 179 |     list<BaseObject*>::const_iterator sourceIT; | 
|---|
| [5779] | 180 |     SoundSource* source; | 
|---|
 | 181 |     for (sourceIT = this->sourceList->begin(); sourceIT != this->sourceList->end(); sourceIT++) | 
|---|
| [4504] | 182 |     { | 
|---|
| [5779] | 183 |       source = static_cast<SoundSource*>(*sourceIT); | 
|---|
| [5955] | 184 |       if (source->isPlaying()) | 
|---|
| [4504] | 185 |       { | 
|---|
| [5955] | 186 |         int play; | 
|---|
 | 187 |         alGetSourcei(source->getID(), AL_SOURCE_STATE, &play); | 
|---|
 | 188 |         if(play == AL_PLAYING) | 
|---|
 | 189 |         { | 
|---|
 | 190 |           if (likely(source->getNode() != NULL)) | 
|---|
 | 191 |           { | 
|---|
 | 192 |             alSource3f(source->getID(), AL_POSITION, | 
|---|
 | 193 |                        source->getNode()->getAbsCoor().x, | 
|---|
 | 194 |                        source->getNode()->getAbsCoor().y, | 
|---|
 | 195 |                        source->getNode()->getAbsCoor().z); | 
|---|
 | 196 |             alSource3f(source->getID(), AL_VELOCITY, | 
|---|
 | 197 |                        source->getNode()->getVelocity().x, | 
|---|
 | 198 |                        source->getNode()->getVelocity().y, | 
|---|
 | 199 |                        source->getNode()->getVelocity().z); | 
|---|
 | 200 |           } | 
|---|
 | 201 |  | 
|---|
 | 202 |         } | 
|---|
 | 203 |         else | 
|---|
 | 204 |         { | 
|---|
 | 205 |           source->stop(); | 
|---|
 | 206 |         } | 
|---|
| [4504] | 207 |       } | 
|---|
 | 208 |     } | 
|---|
| [4960] | 209 |   } | 
|---|
| [4504] | 210 | } | 
|---|
 | 211 |  | 
|---|
 | 212 | /** | 
|---|
| [4836] | 213 |  *  Removes all the Buffers that are not anymore needed by any Sources | 
|---|
| [4504] | 214 | */ | 
|---|
| [4746] | 215 | void SoundEngine::flushUnusedBuffers() | 
|---|
| [4504] | 216 | { | 
|---|
| [5819] | 217 |   /// FIXME | 
|---|
| [5779] | 218 | /*  if(this->sourceList && this->bufferList) | 
|---|
| [4960] | 219 |   { | 
|---|
 | 220 |     tIterator<BaseObject>* bufferIterator = this->bufferList->getIterator(); | 
|---|
| [5115] | 221 |     SoundBuffer* enumBuffer = (SoundBuffer*)bufferIterator->firstElement(); | 
|---|
| [4960] | 222 |     while (enumBuffer) | 
|---|
| [4504] | 223 |     { | 
|---|
| [4960] | 224 |       tIterator<BaseObject>* sourceIterator = this->sourceList->getIterator(); | 
|---|
| [5115] | 225 |       SoundSource* enumSource = (SoundSource*)sourceIterator->firstElement(); | 
|---|
| [4504] | 226 |       while (enumSource) | 
|---|
| [4960] | 227 |       { | 
|---|
 | 228 |         if (enumBuffer == enumSource->getBuffer()) | 
|---|
 | 229 |           break; | 
|---|
 | 230 |         enumSource = (SoundSource*)sourceIterator->nextElement(); | 
|---|
 | 231 |       } | 
|---|
| [4504] | 232 |       delete sourceIterator; | 
|---|
 | 233 |       if (enumSource == NULL) | 
|---|
| [4597] | 234 |         ResourceManager::getInstance()->unload(enumBuffer); | 
|---|
| [4960] | 235 |       enumBuffer = (SoundBuffer*)bufferIterator->nextElement(); | 
|---|
| [4504] | 236 |     } | 
|---|
| [4960] | 237 |     delete bufferIterator; | 
|---|
| [5779] | 238 | }*/ /// FIXME | 
|---|
| [4504] | 239 | } | 
|---|
 | 240 |  | 
|---|
 | 241 | /** | 
|---|
| [5293] | 242 |  * flushes all the Buffers | 
|---|
 | 243 |  * deletes them from the BufferList, and also removes them via the ResourceManager. | 
|---|
 | 244 |  */ | 
|---|
| [4746] | 245 | void SoundEngine::flushAllBuffers() | 
|---|
| [4504] | 246 | { | 
|---|
| [4960] | 247 |   if (this->bufferList) | 
|---|
 | 248 |   { | 
|---|
| [5779] | 249 |     while (this->bufferList->size() > 0) | 
|---|
 | 250 |       ResourceManager::getInstance()->unload(static_cast<SoundBuffer*>(this->bufferList->front()), RP_LEVEL); | 
|---|
| [4960] | 251 |   } | 
|---|
| [4504] | 252 | } | 
|---|
 | 253 |  | 
|---|
 | 254 | /** | 
|---|
| [5293] | 255 |  * deletes all the Sources. | 
|---|
| [4830] | 256 |  */ | 
|---|
 | 257 | void SoundEngine::flushAllSources() | 
|---|
 | 258 | { | 
|---|
| [4960] | 259 |   if (this->sourceList) | 
|---|
| [4830] | 260 |   { | 
|---|
| [5779] | 261 |     while(this->sourceList->size() > 0) | 
|---|
 | 262 |       delete this->sourceList->front(); | 
|---|
| [4830] | 263 |   } | 
|---|
 | 264 | } | 
|---|
 | 265 |  | 
|---|
 | 266 | /** | 
|---|
| [4836] | 267 |  *  initializes Audio in general | 
|---|
| [4504] | 268 | */ | 
|---|
| [4746] | 269 | bool SoundEngine::initAudio() | 
|---|
| [4504] | 270 | { | 
|---|
 | 271 |   ALenum result; | 
|---|
| [5385] | 272 |   PRINTF(3)("Initialisazing openAL sound engine\n"); | 
|---|
 | 273 |   const char* defaultDevice =(const char*) alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); | 
|---|
 | 274 |   const char* deviceList = (const char*)alcGetString(NULL,ALC_DEVICE_SPECIFIER); | 
|---|
 | 275 |   const char* devWalk = deviceList; | 
|---|
| [5416] | 276 | //  if (alcIsExtensionPresent(NULL, (const ALCchar*)"ALC_ENUMERATION_EXT") == AL_TRUE) | 
|---|
 | 277 | { // try out enumeration extension | 
|---|
| [5386] | 278 |     PRINTF(3)("Enumeration-extension found\n"); | 
|---|
| [4504] | 279 |  | 
|---|
| [5386] | 280 |     PRINTF(3)("Default device: %s\n", defaultDevice); | 
|---|
| [5385] | 281 |     do | 
|---|
 | 282 |     { | 
|---|
| [5386] | 283 |       PRINTF(3)("%s\n", devWalk); | 
|---|
| [5385] | 284 |       devWalk += strlen(devWalk)+1; | 
|---|
 | 285 |     } while (devWalk[0] != '\0'); | 
|---|
| [4504] | 286 |  | 
|---|
| [5385] | 287 |  | 
|---|
| [5985] | 288 |  | 
|---|
| [5385] | 289 |   } | 
|---|
| [5819] | 290 |   // INITIALIZING THE DEVICE: | 
|---|
| [5988] | 291 |   ALubyte deviceName[] = | 
|---|
| [5819] | 292 | #ifdef __WIN32__ | 
|---|
| [5955] | 293 |       "Direct3D"; | 
|---|
| [5819] | 294 | #else | 
|---|
| [5955] | 295 |       "'( ( devices '( native null ) ) )"; | 
|---|
| [5819] | 296 | #endif | 
|---|
 | 297 |   // | 
|---|
 | 298 |   this->device = alcOpenDevice(deviceName); | 
|---|
| [5385] | 299 |  | 
|---|
| [5819] | 300 |   this->context = alcCreateContext(this->device, NULL); | 
|---|
 | 301 |  | 
|---|
| [5988] | 302 |   alcMakeContextCurrent(this->context); | 
|---|
| [5819] | 303 |  | 
|---|
| [4504] | 304 |   if ((result = alGetError()) != AL_NO_ERROR) | 
|---|
| [5955] | 305 |     PRINTF(2)("%s\n", SoundEngine::getALErrorString(result)); | 
|---|
| [4504] | 306 |  | 
|---|
 | 307 |   this->setDopplerValues(SOUND_DOPPLER_FACTOR, SOUND_DOPPLER_VELOCITY); | 
|---|
 | 308 | } | 
|---|
 | 309 |  | 
|---|
| [5955] | 310 |  | 
|---|
| [4504] | 311 | /** | 
|---|
| [5955] | 312 |  * Allocates openAL sources | 
|---|
 | 313 |  * @param count how many sources to allocate | 
|---|
 | 314 |  * @returns true on success, false if at least one source could not be allocated | 
|---|
 | 315 |  */ | 
|---|
 | 316 | bool SoundEngine::allocateSources(unsigned int count) | 
|---|
 | 317 | { | 
|---|
 | 318 |   ALenum result; | 
|---|
 | 319 |   // Setting default values. | 
|---|
 | 320 |   for (unsigned int i = 0; i < count; i++) | 
|---|
 | 321 |   { | 
|---|
 | 322 |     ALuint source; | 
|---|
 | 323 |  | 
|---|
 | 324 |     alGenSources(1, &source); | 
|---|
 | 325 |     if ((result = alGetError()) != AL_NO_ERROR) | 
|---|
 | 326 |       PRINTF(1)("Error Generating Sources: '%s'\n", SoundEngine::getALErrorString(result)); | 
|---|
 | 327 |  | 
|---|
 | 328 |     alSourcef (source, AL_PITCH,    1.0      ); | 
|---|
 | 329 |     alSourcef (source, AL_GAIN,     this->getEffectsVolume() ); | 
|---|
 | 330 |     alSourcei (source, AL_LOOPING,  AL_FALSE ); | 
|---|
 | 331 |     this->ALSources.push(source); | 
|---|
 | 332 |   } | 
|---|
 | 333 |   return true; | 
|---|
 | 334 | } | 
|---|
 | 335 |  | 
|---|
 | 336 | /** | 
|---|
| [4836] | 337 |  *  Transforms AL-errors into something readable | 
|---|
 | 338 |  * @param err The error found | 
|---|
| [4504] | 339 | */ | 
|---|
| [5955] | 340 | const char* SoundEngine::getALErrorString(ALenum err) | 
|---|
| [4504] | 341 | { | 
|---|
 | 342 |   switch(err) | 
|---|
 | 343 |     { | 
|---|
 | 344 |     case AL_NO_ERROR: | 
|---|
| [5955] | 345 |       return ("AL_NO_ERROR"); | 
|---|
| [4504] | 346 |     case AL_INVALID_NAME: | 
|---|
| [5955] | 347 |       return ("AL_INVALID_NAME"); | 
|---|
| [4504] | 348 |     case AL_INVALID_ENUM: | 
|---|
| [5955] | 349 |       return ("AL_INVALID_ENUM"); | 
|---|
| [4504] | 350 |     case AL_INVALID_VALUE: | 
|---|
| [5955] | 351 |       return ("AL_INVALID_VALUE"); | 
|---|
| [4504] | 352 |     case AL_INVALID_OPERATION: | 
|---|
| [5955] | 353 |       return ("AL_INVALID_OPERATION"); | 
|---|
| [4504] | 354 |     case AL_OUT_OF_MEMORY: | 
|---|
| [5955] | 355 |       return ("AL_OUT_OF_MEMORY"); | 
|---|
| [4504] | 356 |     }; | 
|---|
 | 357 | } | 
|---|
 | 358 |  | 
|---|
| [4959] | 359 | void SoundEngine::listDevices() | 
|---|
 | 360 | { | 
|---|
 | 361 |   printf("%s\n",(const char*)alcGetString(NULL, ALC_DEVICE_SPECIFIER)); | 
|---|
 | 362 | } | 
|---|
 | 363 |  | 
|---|
| [4504] | 364 | /* | 
|---|
 | 365 | void SoundEngine::PrintALCErrorString(ALenum err) | 
|---|
 | 366 | { | 
|---|
 | 367 |   switch(err) | 
|---|
 | 368 |     { | 
|---|
 | 369 |     case ALC_NO_ERROR: | 
|---|
 | 370 |       PRINTF(4)("AL_NO_ERROR\n"); | 
|---|
 | 371 |       break; | 
|---|
 | 372 |  | 
|---|
 | 373 |     case ALC_INVALID_DEVICE: | 
|---|
 | 374 |       PRINTF(2)("ALC_INVALID_DEVICE\n"); | 
|---|
 | 375 |       break; | 
|---|
 | 376 |  | 
|---|
 | 377 |     case ALC_INVALID_CONTEXT: | 
|---|
 | 378 |       PRINTF(2)("ALC_INVALID_CONTEXT\n"); | 
|---|
 | 379 |       break; | 
|---|
 | 380 |  | 
|---|
 | 381 |     case ALC_INVALID_ENUM: | 
|---|
 | 382 |       PRINTF(2)("ALC_INVALID_ENUM\n"); | 
|---|
 | 383 |       break; | 
|---|
 | 384 |  | 
|---|
 | 385 |     case ALC_INVALID_VALUE: | 
|---|
 | 386 |       PRINTF(2)("ALC_INVALID_VALUE\n"); | 
|---|
 | 387 |       break; | 
|---|
 | 388 |  | 
|---|
 | 389 |     case ALC_OUT_OF_MEMORY: | 
|---|
 | 390 |       PRINTF(2)("ALC_OUT_OF_MEMORY\n"); | 
|---|
 | 391 |       break; | 
|---|
 | 392 |     }; | 
|---|
 | 393 | } | 
|---|
 | 394 | */ | 
|---|