Tekkotsu Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

SoundManager.cc

Go to the documentation of this file.
00001 #include "Shared/Config.h"
00002 #include "SoundManager.h"
00003 #include "Shared/MarkScope.h"
00004 #include "WAV.h"
00005 #include "Events/EventRouter.h"
00006 #include <sys/types.h>
00007 #include <sys/stat.h>
00008 #include <unistd.h>
00009 #include <fstream>
00010 #ifdef PLATFORM_APERIOS
00011 #  include <OPENR/OSubject.h>
00012 #  include <OPENR/ObjcommEvent.h>
00013 #endif
00014 
00015 using namespace std;
00016 
00017 SoundManager * sndman=NULL;
00018 
00019 //!for convenience when locking each of the functions
00020 typedef MarkScope AutoLock;
00021 
00022 SoundManager::SoundManager()
00023 : mixerBuffer(0), mixerBufferSize(0), sndlist(),playlist(),chanlist(),mix_mode(Fast),queue_mode(Override),max_chan(4),lock(),sn(0)
00024 {}
00025 
00026 #ifdef PLATFORM_APERIOS
00027 void
00028 SoundManager::InitAccess(OSubject* subj) {
00029   subjs[ProcessID::getID()]=subj;
00030 }
00031 #else //PLATFORM_LOCAL
00032 void
00033 SoundManager::InitAccess(MessageQueueBase& sndbufq) {
00034   subjs[ProcessID::getID()]=&sndbufq;
00035 }
00036 #endif //PLATFORM-specific initialization
00037 
00038 SoundManager::~SoundManager() {
00039   stopPlay();
00040   if(!sndlist.empty())
00041     cerr << "Warning: SoundManager was deleted with active sound buffer references" << endl;
00042   while(!sndlist.empty()) {
00043     sndlist_t::index_t it=sndlist.begin();
00044     if(sndlist[it].rcr==NULL)
00045       cerr << sndlist[it].name << " was still inflight (IPC), with " << sndlist[it].ref << " sound references" << endl;
00046     else {
00047       cerr << sndlist[it].name << " was deleted, with " << sndlist[it].ref << " sound references and " << sndlist[it].rcr->NumberOfReference() << " region references (one will be removed)" << endl;
00048       sndlist[it].rcr->RemoveReference();
00049     }
00050     sndlist.erase(it);
00051   }
00052   delete[] mixerBuffer;
00053 }
00054 
00055 //!@todo this does one more copy than it really needs to
00056 SoundManager::Snd_ID
00057 SoundManager::loadFile(std::string const &name) {
00058   AutoLock autolock(lock);
00059   if (name.size() == 0) {
00060     cout << "SoundManager::loadFile() null filename" << endl;
00061     return invalid_Snd_ID;
00062   };
00063   std::string path(config->sound.makePath(name));
00064   Snd_ID id=lookupPath(path);
00065   if(id!=invalid_Snd_ID) {
00066     //    cout << "add reference to pre-existing" << endl;
00067     sndlist[id].ref++;
00068   } else {
00069     //    cout << "load new file" << endl;
00070     struct stat buf;
00071     if(stat(path.c_str(),&buf)==-1) {
00072       cout << "SoundManager::loadFile(): Sound file not found: " << path << endl;
00073       return invalid_Snd_ID;
00074     }
00075     byte * sndbuf=new byte[buf.st_size];
00076     std::ifstream file(path.c_str());
00077     file.read(reinterpret_cast<char*>(sndbuf),buf.st_size);
00078     WAV wav;
00079     WAVError error = wav.Set(sndbuf);
00080     if (error != WAV_SUCCESS) {
00081       printf("%s : %s %d: '%s'","SoundManager::loadFile()","wav.Set() FAILED",error, path.c_str());
00082       return invalid_Snd_ID;
00083     }
00084     if(wav.GetSamplingRate()!=config->sound.sample_rate || wav.GetBitsPerSample()!=config->sound.sample_bits) {
00085       printf("%s : %s %d","SoundManager::loadFile()","bad sample rate/bits", error);
00086       return invalid_Snd_ID;
00087     }
00088     cout << "Loading " << name << endl;
00089     id=loadBuffer(reinterpret_cast<char*>(wav.GetDataStart()),wav.GetDataEnd()-wav.GetDataStart());
00090     delete [] sndbuf;
00091     if(path.size()>MAX_NAME_LEN)
00092       strncpy(sndlist[id].name,path.substr(path.size()-MAX_NAME_LEN).c_str(),MAX_NAME_LEN);
00093     else
00094       strncpy(sndlist[id].name,path.c_str(),MAX_NAME_LEN);
00095   }
00096   return id;
00097 }
00098 
00099 SoundManager::Snd_ID
00100 SoundManager::loadBuffer(const char buf[], unsigned int len) {
00101   // cout << "SoundManager::loadBuffer() of " << len << " bytes" << endl;
00102   if(buf==NULL || len==0)
00103     return invalid_Snd_ID;
00104   AutoLock autolock(lock);
00105   //setup region
00106   RCRegion * region=initRegion(len+MSG_SIZE);
00107   //setup message
00108   SoundManagerMsg * msg=new (reinterpret_cast<SoundManagerMsg*>(region->Base())) SoundManagerMsg;
00109   Snd_ID msgid=sndlist.new_front();
00110   msg->setAdd(msgid,sn);
00111   //init sound structure
00112   sndlist[msg->getID()].rcr=NULL;  // set by SoundPlay upon reception
00113   sndlist[msg->getID()].data=NULL; // set by SoundPlay upon reception
00114   sndlist[msg->getID()].ref=1;
00115   sndlist[msg->getID()].len=len;
00116   sndlist[msg->getID()].sn=sn;
00117   //copy buffer, do any filtering needed here
00118   const byte* src=reinterpret_cast<const byte*>(buf);
00119   byte* dest=reinterpret_cast<byte*>(region->Base())+MSG_SIZE;
00120   byte* end=dest+len;
00121   if (config->sound.sample_bits==8)
00122     while (dest < end)
00123       *dest++ = *src++ ^ 0x80; // offset binary -> signed char 
00124   else
00125     while (dest < end)
00126       *dest++ = *src++;
00127   //cout << "SoundManager init region " << region->ID().key << endl;
00128   //send message
00129   if(ProcessID::getID()==ProcessID::SoundProcess) {
00130     //if SoundPlay is preloading files, don't need to do inter-object comm
00131     sndlist[msg->getID()].rcr=region;
00132     sndlist[msg->getID()].data=reinterpret_cast<byte*>(region->Base()+MSG_SIZE);    
00133   } else {
00134 #ifdef PLATFORM_APERIOS
00135     //cout << "Send new at " << get_time() << '-';
00136     subjs[ProcessID::getID()]->SetData(region);
00137     subjs[ProcessID::getID()]->NotifyObservers();
00138     //cout << get_time() << endl;
00139 #else
00140     subjs[ProcessID::getID()]->sendMessage(region);
00141     region->RemoveReference();
00142 #endif
00143   }
00144   return msgid;
00145 }
00146   
00147 void
00148 SoundManager::releaseFile(std::string const &name) {
00149   AutoLock autolock(lock);
00150   release(lookupPath(config->sound.makePath(name)));
00151 }
00152 
00153 void
00154 SoundManager::release(Snd_ID id) {
00155   if(id==invalid_Snd_ID)
00156     return;
00157   if(sndlist[id].ref==0) {
00158     cerr << "SoundManager::release() " << id << " extra release" << endl;
00159     return;
00160   }
00161   AutoLock autolock(lock);
00162   sndlist[id].ref--;
00163   if(sndlist[id].ref==0) {
00164     if(sndlist[id].rcr!=NULL) {
00165       //The sound buffer is attached in the sound process, we need to detach it.
00166       //Note that if this was NULL, we just assume that's because the message for the
00167       //region is still in transit from the originating process, and erase the sndlist entry.
00168       //Then when that buffer does arrive, we'll discover the sndlist entry is invalid and ignore it
00169       if(ProcessID::getID()==ProcessID::SoundProcess) {
00170         //we're currently running in sound process -- don't need to send ourselves an IPC message
00171         sndlist[id].rcr->RemoveReference();
00172         sndlist[id].rcr=NULL;
00173       } else {
00174         //we're currently running in a foreign process -- have to send message to sound process to release
00175         //setup region
00176         RCRegion * region=initRegion(MSG_SIZE);
00177         //setup message
00178         SoundManagerMsg * msg=new (reinterpret_cast<SoundManagerMsg*>(region->Base())) SoundManagerMsg;
00179         msg->setDelete(sndlist[id].rcr);
00180         //cout << "Sending delete msg for " << sndlist[id].name << endl;
00181         //send message
00182 #ifdef PLATFORM_APERIOS
00183         //cout << "Send delete at " << get_time() << '-';
00184         subjs[ProcessID::getID()]->SetData(region);
00185         subjs[ProcessID::getID()]->NotifyObservers();
00186         //cout << get_time() << endl;
00187 #else
00188         subjs[ProcessID::getID()]->sendMessage(region);
00189         region->RemoveReference();
00190 #endif
00191       }
00192     }
00193     //clean up sound data structure
00194     sndlist[id].sn=0; // we use '1' for the first issued, so 0 marks it as invalid
00195     sndlist.erase(id);
00196   }
00197 }
00198 
00199 SoundManager::Play_ID
00200 SoundManager::playFile(std::string const &name) {
00201   if(playlist.size()>=playlist_t::MAX_ENTRIES)
00202     return invalid_Play_ID; 
00203   AutoLock autolock(lock);
00204   Snd_ID sndid=loadFile(name);
00205   if(sndid==invalid_Snd_ID)
00206     return invalid_Play_ID;
00207   sndlist[sndid].ref--;
00208   cout << "Playing " << name /*<< " from process " << ProcessID::getID()*/ << endl;
00209   return play(sndid);
00210 }
00211 
00212 SoundManager::Play_ID
00213 SoundManager::playBuffer(const char buf[], unsigned int len) {
00214   if(playlist.size()>=playlist_t::MAX_ENTRIES || buf==NULL || len==0)
00215     return invalid_Play_ID; 
00216   AutoLock autolock(lock);
00217   Snd_ID sndid=loadBuffer(buf,len);
00218   if(sndid==invalid_Snd_ID)
00219     return invalid_Play_ID;
00220   sndlist[sndid].ref--;
00221   return play(sndid);
00222 }
00223   
00224 SoundManager::Play_ID
00225 SoundManager::play(Snd_ID id) {
00226         // cout << "Play " << id << endl;
00227   if(id==invalid_Snd_ID)
00228     return invalid_Play_ID;
00229   AutoLock autolock(lock);
00230   Play_ID playid=playlist.new_front();
00231   if(playid!=invalid_Play_ID) {
00232     sndlist[id].ref++;
00233     playlist[playid].snd_id=id;
00234     playlist[playid].offset=0;
00235     //playlist.size() should be greater than or equal to chanlist.size
00236     //so if we got a playid, we can get a channel slot.
00237     chanlist.push_front(playid);
00238 
00239     //setup message to "wake-up" 
00240     //(only really need if chanlist was empty)
00241     //    if(chanlist.size()==1) { //commented out because sometimes doesn't wake up, thinks it's playing but isn't
00242     if(ProcessID::getID()!=ProcessID::SoundProcess) {
00243       RCRegion * region=initRegion(MSG_SIZE);
00244       ASSERT(region!=NULL,"initRegion returned NULL");
00245       SoundManagerMsg * msg=new (reinterpret_cast<SoundManagerMsg*>(region->Base())) SoundManagerMsg;
00246       msg->setWakeup();
00247 #ifdef PLATFORM_APERIOS
00248       //cout << "Send wakeup at " << get_time() << '-';
00249       subjs[ProcessID::getID()]->SetData(region);
00250       subjs[ProcessID::getID()]->NotifyObservers();
00251       //cout << get_time() << endl;
00252 #else
00253       subjs[ProcessID::getID()]->sendMessage(region);
00254       region->RemoveReference();
00255 #endif
00256     }
00257     //    }
00258     
00259     if(sndlist[id].rcr!=NULL) {
00260       const char * name=sndlist[playlist[playid].snd_id].name;
00261       if(name[0]!='\0')
00262         erouter->postEvent(EventBase::audioEGID,playid,EventBase::activateETID,0,name,1);
00263       else
00264         erouter->postEvent(EventBase::audioEGID,playid,EventBase::activateETID,0);
00265     }
00266   }
00267   return playid;
00268 }
00269   
00270 SoundManager::Play_ID
00271 SoundManager::chainFile(Play_ID base, std::string const &next) {
00272        if(base==invalid_Play_ID)
00273     return playFile(next);
00274   Play_ID orig=base;
00275   while(playlist[base].next_id!=invalid_Play_ID)
00276     base=playlist[base].next_id;
00277   Play_ID nplay=playlist.new_front();
00278   if(nplay==invalid_Play_ID)
00279     return nplay;
00280   Snd_ID nsnd=loadFile(next);
00281   if(nsnd==invalid_Snd_ID) {
00282     playlist.pop_front();
00283     return invalid_Play_ID;
00284   }
00285   playlist[nplay].snd_id=nsnd;
00286   playlist[base].next_id=nplay;
00287   return orig;
00288 }
00289 
00290 SoundManager::Play_ID
00291 SoundManager::chainBuffer(Play_ID base, const char buf[], unsigned int len) {
00292   if(base==invalid_Play_ID || buf==NULL || len==0)
00293     return playBuffer(buf,len);
00294   Play_ID orig=base;
00295   while(playlist[base].next_id!=invalid_Play_ID)
00296     base=playlist[base].next_id;
00297   Play_ID nplay=playlist.new_front();
00298   if(nplay==invalid_Play_ID)
00299     return nplay;
00300   Snd_ID nsnd=loadBuffer(buf,len);
00301   if(nsnd==invalid_Snd_ID) {
00302     playlist.pop_front();
00303     return invalid_Play_ID;
00304   }
00305   playlist[nplay].snd_id=nsnd;
00306   playlist[base].next_id=nplay;
00307   return orig;
00308 }
00309 
00310 SoundManager::Play_ID
00311 SoundManager::chain(Play_ID base, Snd_ID next) {
00312   if(base==invalid_Play_ID || next==invalid_Snd_ID)
00313     return play(next);
00314   Play_ID orig=base;
00315   while(playlist[base].next_id!=invalid_Play_ID)
00316     base=playlist[base].next_id;
00317   Play_ID nplay=playlist.new_front();
00318   if(nplay==invalid_Play_ID)
00319     return nplay;
00320   playlist[nplay].snd_id=next;
00321   playlist[base].next_id=nplay;
00322   return orig;
00323 }
00324 
00325 void
00326 SoundManager::stopPlay() {
00327   while(!playlist.empty())
00328     stopPlay(playlist.begin());
00329 }
00330 
00331 void
00332 SoundManager::stopPlay(Play_ID id) {
00333   if(id==invalid_Play_ID)
00334     return;
00335   AutoLock autolock(lock);
00336   //cout << "Stopping sound " << id << ": " << sndlist[playlist[id].snd_id].name << endl;
00337   //we start at the back (oldest) since these are the most likely to be removed...
00338   for(chanlist_t::index_t it=chanlist.prev(chanlist.end()); it!=chanlist.end(); it=chanlist.prev(it))
00339     if(chanlist[it]==id) {
00340       std::string name=sndlist[playlist[id].snd_id].name;
00341       release(playlist[id].snd_id);
00342       playlist.erase(id);
00343       chanlist.erase(it);
00344       playlist[id].cumulative+=playlist[id].offset;
00345       unsigned int ms=playlist[id].cumulative/(config->sound.sample_bits/8)/(config->sound.sample_rate/1000);
00346       if(name.size()>0)
00347         erouter->postEvent(EventBase::audioEGID,id,EventBase::deactivateETID,ms,name,0);
00348       else
00349         erouter->postEvent(EventBase::audioEGID,id,EventBase::deactivateETID,ms);
00350       return;
00351     }
00352   cerr << "SoundManager::stopPlay(): " << id << " does not seem to be playing" << endl;
00353 }
00354 
00355 void
00356 SoundManager::pausePlay(Play_ID id) {
00357   if(id==invalid_Play_ID)
00358     return;
00359   AutoLock autolock(lock);
00360   for(chanlist_t::index_t it=chanlist.begin(); it!=chanlist.end(); it=chanlist.next(it))
00361     if(chanlist[it]==id) {
00362       chanlist.erase(it);
00363       return;
00364     }
00365 }
00366   
00367 void
00368 SoundManager::resumePlay(Play_ID id) {
00369   if(id==invalid_Play_ID)
00370     return;
00371   AutoLock autolock(lock);
00372   for(chanlist_t::index_t it=chanlist.begin(); it!=chanlist.end(); it=chanlist.next(it))
00373     if(chanlist[it]==id)
00374       return;
00375   chanlist.push_front(id);
00376 }
00377   
00378 void
00379 SoundManager::setMode(unsigned int max_channels, MixMode_t mixer_mode, QueueMode_t queuing_mode) {
00380   AutoLock autolock(lock);
00381   max_chan=max_channels;
00382   mix_mode=mixer_mode;
00383   queue_mode=queuing_mode;
00384 }
00385 
00386 unsigned int
00387 SoundManager::getRemainTime(Play_ID id) const {
00388   AutoLock autolock(lock);
00389   unsigned int t=0;
00390   while(id!=invalid_Play_ID) {
00391     t+=sndlist[playlist[id].snd_id].len-playlist[id].offset;
00392     id=playlist[id].next_id;
00393   }
00394   const unsigned int bytesPerMS=config->sound.sample_bits/8*config->sound.sample_rate/1000;
00395   return t/bytesPerMS;
00396 }
00397 
00398 void
00399 SoundManager::mixChannel(Play_ID channelId, void* buf, size_t destSize) {
00400   char *dest = (char*) buf;
00401   
00402   PlayState& channel = playlist[channelId];
00403   while (destSize > 0) {
00404     const SoundData& buffer = sndlist[channel.snd_id];
00405     const char* samples = ((char*) (buffer.data)) + channel.offset;
00406     const unsigned int samplesSize = buffer.len - channel.offset;
00407     if (samplesSize > destSize) {
00408       memcpy(dest, samples, destSize);
00409       channel.offset += destSize;
00410       dest += destSize;
00411       destSize = 0;
00412       return;
00413     } else {
00414       memcpy(dest, samples, samplesSize);
00415       channel.offset += samplesSize;
00416       dest += samplesSize;
00417       destSize -= samplesSize;
00418       if (endPlay(channelId)) {
00419         break;
00420       }
00421     }
00422   }
00423   if (destSize > 0) {
00424     memset(dest, 0, destSize);
00425   }
00426 }
00427 
00428 void
00429 SoundManager::mixChannelAdditively(Play_ID channelId, int bitsPerSample, MixMode_t mode,
00430                                    short scalingFactor, void* buf, size_t destSize)
00431 {
00432   PlayState& channel = playlist[channelId];
00433   while (destSize > 0) {
00434     const SoundData& buffer = sndlist[channel.snd_id];
00435     const unsigned int samplesSize = buffer.len - channel.offset;
00436     const unsigned int mixedSamplesSize =
00437       ((mode == Fast)
00438         ? ((samplesSize > destSize) ? destSize : samplesSize)
00439         : ((samplesSize > destSize / 2) ? destSize / 2 : samplesSize)); 
00440     
00441     if (bitsPerSample == 8) {
00442       //  8-bit mode
00443       const char* samples = (char*) (buffer.data + channel.offset);
00444       if (mode == Fast) {
00445         // 8-bit mixing
00446         char *dest = (char*) buf;
00447         for (size_t i = 0; i < mixedSamplesSize; i++) {
00448           *dest += samples[i] / scalingFactor;
00449           dest++;
00450         }
00451         destSize -= (char*) dest - (char*) buf;
00452         buf = dest;
00453       } else {
00454         // 16-bit mixing
00455         short* dest = (short*) buf;
00456         for (size_t i = 0; i < mixedSamplesSize; i++) {
00457           *dest += samples[i];
00458           *dest++;
00459         }
00460         destSize -= (char*) dest - (char*) buf;
00461         buf = dest;
00462       }
00463     } else {
00464       // 16-bit mode
00465       const short* samples = (short*) (buffer.data + channel.offset);
00466       if (mode == Fast) {
00467         // 16-bit mixing
00468         short* dest = (short*) buf;
00469         for (size_t i = 0; i < mixedSamplesSize / 2; i++) {
00470           *dest += samples[i] / scalingFactor;
00471           *dest++;
00472         }
00473         destSize -= (char*) dest - (char*) buf;
00474         buf = dest;
00475       } else {
00476         // 32-bit mixing
00477         int* dest = (int*) buf;
00478         for (size_t i = 0; i < mixedSamplesSize / 2; i++) {
00479           *dest += samples[i];
00480           *dest++;
00481         }
00482         destSize -= (char*) dest - (char*) buf;
00483         buf = dest;
00484       }
00485     }
00486     channel.offset += mixedSamplesSize;
00487     if (destSize == 0) {
00488       return;
00489     } else {
00490       if (endPlay(channelId)) {
00491         return;
00492       }
00493     }
00494   }
00495 }
00496 
00497 #ifdef PLATFORM_APERIOS
00498 unsigned int
00499 SoundManager::CopyTo(OSoundVectorData* data) {
00500   AutoLock autolock(lock);
00501   return CopyTo(data->GetData(0), data->GetInfo(0)->dataSize);
00502 }
00503 
00504 void
00505 SoundManager::ReceivedMsg(const ONotifyEvent& event) {
00506   //cout << "Got msg at " << get_time() << endl;
00507   for(int x=0; x<event.NumOfData(); x++)
00508     ProcessMsg(event.RCData(x));
00509 }
00510 #endif
00511 
00512 unsigned int SoundManager::CopyTo(void * dest, size_t destSize) {
00513   AutoLock autolock(lock);
00514 
00515   void * origdest=dest;
00516   size_t origDestSize=destSize;
00517   if(chanlist.size() == 0) {
00518     memset(dest, 0, destSize);
00519     return 0;
00520   }
00521   
00522   std::vector<Play_ID> channels;
00523   selectChannels(channels);
00524   
00525   if (channels.size() == 0) {
00526     // No channels to mix
00527     memset(dest, 0, destSize); 
00528   } else if (channels.size() == 1) {
00529     // One channel to mix
00530     mixChannel(channels.front(), dest, destSize);
00531   } else {
00532     // Several channels to mix  
00533     const MixMode_t mode = mix_mode;
00534     const int bitsPerSample = config->sound.sample_bits;
00535     if (mode == Quality) {
00536       // Quality mixing uses an intermediate buffer
00537       if ((mixerBuffer == 0) || (mixerBufferSize < destSize * 2)) {
00538         delete[] mixerBuffer;
00539         mixerBuffer = 0;
00540         mixerBufferSize = destSize * 2;
00541         mixerBuffer = new int[(mixerBufferSize / 4) + 1]; // makes sure it's int-aligned
00542       }
00543       memset(mixerBuffer, 0,  mixerBufferSize);
00544       dest = mixerBuffer;
00545       destSize *= 2;
00546     } else {
00547       // Fast mixing does not use the intermeridate buffer
00548       memset(dest, 0, destSize);
00549     }
00550     
00551     const int channelCount = channels.size();
00552     const short scalingFactor = (short) ((mode == Fast) ? channelCount : 1);  
00553     for(std::vector<Play_ID>::iterator i = channels.begin(); i != channels.end(); i++)
00554       mixChannelAdditively(*i, bitsPerSample, mode, scalingFactor, dest, destSize);
00555     
00556     if (mode == Quality) {
00557       // Quality mixing uses an intermediate buffer
00558       // Scale the buffer
00559       destSize /= 2;
00560       if (bitsPerSample == 8) {
00561         //  8-bit mode
00562         char* destChar = (char*) origdest;
00563         short* mixerBufferShort = (short*) mixerBuffer;
00564         for (size_t i = 0; i < destSize; i++) {
00565           destChar[i] = (char) (mixerBufferShort[i] / channelCount);
00566         } 
00567       } else {
00568         // 16-bit mode
00569         short* destShort = (short*) origdest;
00570         const size_t destSampleCount = destSize / 2; 
00571         for (size_t i = 0; i < destSampleCount; i++) {
00572           destShort[i] = (short) (mixerBuffer[i] / channelCount);
00573         }
00574       }
00575     }
00576   }
00577   
00578   updateChannels(channels, origDestSize);
00579   return channels.size(); 
00580 }
00581 
00582 void SoundManager::ProcessMsg(RCRegion * rcr) {
00583   SoundManagerMsg * msg = reinterpret_cast<SoundManagerMsg*>(rcr->Base());
00584   //cout << "Processing " << msg << ": " << rcr->ID().key << endl;
00585   switch(msg->type) {
00586     case SoundManagerMsg::add: {
00587       //cout << "it's an add of " << msg->id << ", sn=" << msg->sn << " (expecting " << sndlist[msg->id].sn << ", next sn is " << sn << ")" << endl;
00588       //first check msg->id's validity, in case it was deleted while in-flight
00589       /* //since Release marks deleted entries with serial number 0, we can get around this
00590       bool valid=false;
00591       for(Snd_ID it=sndlist.begin(); it!=sndlist.end(); it=sndlist.next(it)) {
00592         if(it==msg->id) {
00593           valid=true;
00594           break;
00595         }
00596       }
00597       if(!valid) {
00598         //was deleted while buffer was still in-flight, ignore this message
00599         break; //leaves switch statement, try next message if any
00600       }
00601       //now, even though the sound id is valid, verify the serial numbers match
00602       */
00603       if(sndlist[msg->id].sn!=msg->sn) {
00604         /*this means that the sound this message references was deleted while
00605         * in-flight, even if some crazy process (perhaps the same one) has
00606         * created and destroyed a number of sounds before we got this message */
00607         /* So although there is a sound for this id, it's a different sound than
00608         * the one this message refers to, and we should ignore this message */
00609         cerr << "Warning: serial numbers don't match... may be pathological sound usage (many load/releases very quickly)" << endl;
00610         break; //leaves switch statement, try