瀏覽代碼

finish callbacks

David Rose 18 年之前
父節點
當前提交
de34468d87

+ 24 - 0
panda/src/audiotraits/globalMilesManager.I

@@ -27,3 +27,27 @@ INLINE bool GlobalMilesManager::
 is_open() const {
   return _is_open;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: GlobalMilesManager::get_num_samples
+//       Access: Public
+//  Description: Returns the number of sample handles that have been
+//               allocated.
+////////////////////////////////////////////////////////////////////
+INLINE int GlobalMilesManager::
+get_num_samples() const {
+  MutexHolder holder(_samples_lock);
+  return _samples.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GlobalMilesManager::get_num_sequences
+//       Access: Public
+//  Description: Returns the number of sequence handles that have been
+//               allocated.
+////////////////////////////////////////////////////////////////////
+INLINE int GlobalMilesManager::
+get_num_sequences() const {
+  MutexHolder holder(_sequences_lock);
+  return _sequences.size();
+}

+ 7 - 0
panda/src/audiotraits/globalMilesManager.h

@@ -25,11 +25,16 @@
 #include "mss.h"
 #include "pset.h"
 #include "pmutex.h"
+#include "mutexHolder.h"
 
 #ifndef UINTa
 #define UINTa U32
 #endif
 
+#ifndef SINTa
+#define SINTa S32
+#endif
+
 class MilesAudioSample;
 class MilesAudioSequence;
 
@@ -51,9 +56,11 @@ public:
 
   bool get_sample(HSAMPLE &sample, size_t &index, MilesAudioSample *sound);
   void release_sample(size_t index, MilesAudioSample *sound);
+  INLINE int get_num_samples() const;
 
   bool get_sequence(HSEQUENCE &sequence, size_t &index, MilesAudioSequence *sound);
   void release_sequence(size_t index, MilesAudioSequence *sound);
+  INLINE int get_num_sequences() const;
 
   void force_midi_reset();
 

+ 157 - 64
panda/src/audiotraits/milesAudioManager.cxx

@@ -32,6 +32,7 @@
 #include "nullAudioSound.h"
 #include "string_utils.h"
 #include "mutexHolder.h"
+#include "reMutexHolder.h"
 
 #include <algorithm>
 
@@ -52,7 +53,9 @@ PT(AudioManager) Create_AudioManager() {
 //               Miles resources.
 ////////////////////////////////////////////////////////////////////
 MilesAudioManager::
-MilesAudioManager() : _streams_lock("MilesAudioManager::_streams_lock"),
+MilesAudioManager() : 
+  _lock("MilesAudioManager::_lock"),
+  _streams_lock("MilesAudioManager::_streams_lock"),
   _streams_cvar(_streams_lock)
 {
   audio_debug("MilesAudioManager::MilesAudioManager(), this = " 
@@ -68,6 +71,7 @@ MilesAudioManager() : _streams_lock("MilesAudioManager::_streams_lock"),
   _concurrent_sound_limit = 0;
   _is_valid = true;
   _hasMidiSounds = false;
+  _sounds_finished = false;
 
   // We used to hang a call to a force-shutdown function on atexit(),
   // so that any running sounds (particularly MIDI sounds) would be
@@ -125,22 +129,8 @@ shutdown() {
 ////////////////////////////////////////////////////////////////////
 bool MilesAudioManager::
 is_valid() {
-  bool check=true;
-  if (_sounds.size() != _lru.size()) {
-    audio_debug("-- Error _sounds.size() != _lru.size() --");
-    check=false;
-  } else {
-    LRU::const_iterator i=_lru.begin();
-    for (; i != _lru.end(); ++i) {
-      SoundMap::const_iterator smi=_sounds.find(**i);
-      if (smi == _sounds.end()) {
-        audio_debug("-- "<<**i<<" in _lru and not in _sounds --");
-        check=false;
-        break;
-      }
-    }
-  }
-  return _is_valid && check;
+  ReMutexHolder holder(_lock);
+  return do_is_valid();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -149,15 +139,15 @@ is_valid() {
 //  Description:
 ////////////////////////////////////////////////////////////////////
 PT(AudioSound) MilesAudioManager::
-get_sound(const string& file_name, bool) {
+get_sound(const string &file_name, bool) {
+  ReMutexHolder holder(_lock);
   audio_debug("MilesAudioManager::get_sound(file_name=\""<<file_name<<"\")");
 
-  if(!is_valid()) {
+  if (!do_is_valid()) {
      audio_debug("invalid MilesAudioManager returning NullSound");
      return get_null_sound();
   }
 
-  assert(is_valid());
   Filename path = file_name;
 
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
@@ -172,6 +162,7 @@ get_sound(const string& file_name, bool) {
     // ...found the sound in the cache.
     sd = (*si).second;
     audio_debug("  sound found in pool 0x" << (void*)sd);
+
   } else {
     // ...the sound was not found in the cache/pool.
     sd = load(path);
@@ -183,11 +174,11 @@ get_sound(const string& file_name, bool) {
       // The following is roughly like: _sounds[path] = sd;
       // But, it gives us an iterator into the map.
       pair<SoundMap::const_iterator, bool> ib
-          =_sounds.insert(SoundMap::value_type(path, sd));
+          = _sounds.insert(SoundMap::value_type(path, sd));
       if (!ib.second) {
         // The insert failed.
         audio_debug("  failed map insert of "<<path);
-        assert(is_valid());
+        nassertr(do_is_valid(), NULL);
         return get_null_sound();
       }
       // Set si, so that we can get a reference to the path
@@ -228,7 +219,7 @@ get_sound(const string& file_name, bool) {
   }
 
   audio_debug("  returning 0x" << (void*)audioSound);
-  assert(is_valid());
+  nassertr(do_is_valid(), NULL);
   return audioSound;
 }
 
@@ -238,10 +229,11 @@ get_sound(const string& file_name, bool) {
 //  Description:
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
-uncache_sound(const string& file_name) {
+uncache_sound(const string &file_name) {
   audio_debug("MilesAudioManager::uncache_sound(file_name=\""
       <<file_name<<"\")");
-  assert(is_valid());
+  ReMutexHolder holder(_lock);
+  nassertv(do_is_valid());
   Filename path = file_name;
 
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
@@ -249,15 +241,15 @@ uncache_sound(const string& file_name) {
     vfs->resolve_filename(path, get_model_path());
 
   audio_debug("  path=\""<<path<<"\"");
-  SoundMap::iterator i=_sounds.find(path);
+  SoundMap::iterator i = _sounds.find(path);
   if (i != _sounds.end()) {
-    assert(_lru.size()>0);
-    LRU::iterator lru_i=find(_lru.begin(), _lru.end(), &(i->first));
-    assert(lru_i != _lru.end());
+    nassertv(_lru.size() > 0);
+    LRU::iterator lru_i = find(_lru.begin(), _lru.end(), &(i->first));
+    nassertv(lru_i != _lru.end());
     _lru.erase(lru_i);
     _sounds.erase(i);
   }
-  assert(is_valid());
+  nassertv(do_is_valid());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -268,10 +260,8 @@ uncache_sound(const string& file_name) {
 void MilesAudioManager::
 clear_cache() {
   audio_debug("MilesAudioManager::clear_cache()");
-  if (_is_valid) { assert(is_valid()); }
-  _sounds.clear();
-  _lru.clear();
-  if (_is_valid) { assert(is_valid()); }
+  ReMutexHolder holder(_lock);
+  do_clear_cache();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -281,13 +271,15 @@ clear_cache() {
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 set_cache_limit(unsigned int count) {
+  ReMutexHolder holder(_lock);
+
   audio_debug("MilesAudioManager::set_cache_limit(count="<<count<<")");
-  assert(is_valid());
+  nassertv(do_is_valid());
   while (_lru.size() > count) {
     uncache_a_sound();
   }
   _cache_limit=count;
-  assert(is_valid());
+  nassertv(do_is_valid());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -308,10 +300,11 @@ get_cache_limit() const {
 void MilesAudioManager::
 set_volume(float volume) {
   audio_debug("MilesAudioManager::set_volume(volume="<<volume<<")");
-  if (_volume!=volume) {
+  ReMutexHolder holder(_lock);
+  if (_volume != volume) {
     _volume = volume;
     // Tell our AudioSounds to adjust:
-    AudioSet::iterator i=_sounds_on_loan.begin();
+    AudioSet::iterator i = _sounds_on_loan.begin();
     for (; i!=_sounds_on_loan.end(); ++i) {
       (*i)->set_volume((*i)->get_volume());
     }
@@ -336,11 +329,12 @@ get_volume() const {
 void MilesAudioManager::
 set_play_rate(float play_rate) {
   audio_debug("MilesAudioManager::set_play_rate(play_rate="<<play_rate<<")");
-  if (_play_rate!=play_rate) {
+  ReMutexHolder holder(_lock);
+  if (_play_rate != play_rate) {
     _play_rate = play_rate;
     // Tell our AudioSounds to adjust:
-    AudioSet::iterator i=_sounds_on_loan.begin();
-    for (; i!=_sounds_on_loan.end(); ++i) {
+    AudioSet::iterator i = _sounds_on_loan.begin();
+    for (; i != _sounds_on_loan.end(); ++i) {
       (*i)->set_play_rate((*i)->get_play_rate());
     }
   }
@@ -364,11 +358,12 @@ get_play_rate() const {
 void MilesAudioManager::
 set_active(bool active) {
   audio_debug("MilesAudioManager::set_active(flag="<<active<<")");
-  if (_active!=active) {
+  ReMutexHolder holder(_lock);
+  if (_active != active) {
     _active=active;
     // Tell our AudioSounds to adjust:
-    AudioSet::iterator i=_sounds_on_loan.begin();
-    for (; i!=_sounds_on_loan.end(); ++i) {
+    AudioSet::iterator i = _sounds_on_loan.begin();
+    for (; i != _sounds_on_loan.end(); ++i) {
       (*i)->set_active(_active);
     }
 
@@ -395,8 +390,9 @@ get_active() const {
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 set_concurrent_sound_limit(unsigned int limit) {
+  ReMutexHolder holder(_lock);
   _concurrent_sound_limit = limit;
-  reduce_sounds_playing_to(_concurrent_sound_limit);
+  do_reduce_sounds_playing_to(_concurrent_sound_limit);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -411,17 +407,13 @@ get_concurrent_sound_limit() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: MilesAudioManager::reduce_sounds_playing_to
-//       Access: Private, Virtual
+//       Access: Public, Virtual
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 reduce_sounds_playing_to(unsigned int count) {
-  int limit = _sounds_playing.size() - count;
-  while (limit-- > 0) {
-    SoundsPlaying::iterator sound = _sounds_playing.begin();
-    assert(sound != _sounds_playing.end());
-    (**sound).stop();
-  }
+  ReMutexHolder holder(_lock);
+  do_reduce_sounds_playing_to(count);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -444,12 +436,33 @@ stop_all_sounds() {
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 update() {
-  MutexHolder holder(_streams_lock);
+  {
+    MutexHolder holder(_streams_lock);
+    if (_stream_thread.is_null() && !_streams.empty()) {
+      // If we don't have a sub-thread, we have to service the streams
+      // in the main thread.
+      do_service_streams();
+    }
+  }
 
-  if (_stream_thread.is_null() && !_streams.empty()) {
-    // If we don't have a sub-thread, we have to service the streams
-    // in the main thread.
-    do_service_streams();
+  if (_sounds_finished) {
+    _sounds_finished = false;
+    
+    // If the _sounds_finished flag was set, we should scan our list
+    // of playing sounds and see if any of them have finished
+    // recently.  We don't do this in the finished callback, because
+    // that might have been called in a sub-thread (and we may not
+    // have threading supported--and mutex protection--compiled in).
+
+    SoundsPlaying::iterator si = _sounds_playing.begin();
+    while (si != _sounds_playing.end()) {
+      MilesAudioSound *sound = (*si);
+      ++si;
+
+      if (sound->status() == AudioSound::READY) {
+        sound->stop();
+      }
+    }
   }
 }
 
@@ -462,6 +475,7 @@ void MilesAudioManager::
 release_sound(MilesAudioSound *audioSound) {
   audio_debug("MilesAudioManager::release_sound(audioSound=\""
               <<audioSound->get_name()<<"\"), this = " << (void *)this);
+  ReMutexHolder holder(_lock);
   AudioSet::iterator ai = _sounds_on_loan.find(audioSound);
   nassertv(ai != _sounds_on_loan.end());
   _sounds_on_loan.erase(ai);
@@ -480,6 +494,7 @@ void MilesAudioManager::
 cleanup() {
   audio_debug("MilesAudioManager::cleanup(), this = " << (void *)this
               << ", _cleanup_required = " << _cleanup_required);
+  ReMutexHolder holder(_lock);
   if (!_cleanup_required) {
     return;
   }
@@ -490,7 +505,7 @@ cleanup() {
     (*ai)->cleanup();
   }
 
-  clear_cache();
+  do_clear_cache();
 
   // Now stop the thread, if it has been started.
   if (!_stream_thread.is_null()) {
@@ -519,6 +534,7 @@ cleanup() {
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 output(ostream &out) const {
+  ReMutexHolder holder(_lock);
   out << get_type() << ": " << _sounds_playing.size()
       << " / " << _sounds_on_loan.size() << " sounds playing / total"; 
 }
@@ -530,14 +546,87 @@ output(ostream &out) const {
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 write(ostream &out) const {
+  ReMutexHolder holder(_lock);
+
   out << (*this) << "\n";
   AudioSet::const_iterator ai;
   for (ai = _sounds_on_loan.begin(); ai != _sounds_on_loan.end(); ++ai) {
     MilesAudioSound *sound = (*ai);
     out << "  " << *sound << "\n";
   }
+
+  {
+    MutexHolder holder(_streams_lock);
+    out << _streams.size() << " streams opened.\n";
+    if (!_stream_thread.is_null()) {
+      out << "(Audio streaming thread has been started.)\n";
+    }
+  }
+
+  GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
+ 
+  int num_samples = mgr->get_num_samples();
+  out << num_samples << " sample handles allocated globally.\n";
+
+  int num_sequences = mgr->get_num_sequences();
+  out << num_sequences << " sequence handles allocated globally.\n";
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: MilesAudioManager::do_is_valid
+//       Access: Private
+//  Description: Implementation of is_valid().  Assumes the lock is
+//               already held.
+////////////////////////////////////////////////////////////////////
+bool MilesAudioManager::
+do_is_valid() {
+  bool check = true;
+  if (_sounds.size() != _lru.size()) {
+    audio_debug("-- Error _sounds.size() != _lru.size() --");
+    check = false;
+
+  } else {
+    LRU::const_iterator i = _lru.begin();
+    for (; i != _lru.end(); ++i) {
+      SoundMap::const_iterator smi = _sounds.find(**i);
+      if (smi == _sounds.end()) {
+        audio_debug("-- "<<**i<<" in _lru and not in _sounds --");
+        check = false;
+        break;
+      }
+    }
+  }
+  return _is_valid && check;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MilesAudioManager::do_reduce_sounds_playing_to
+//       Access: Private
+//  Description: Assumes the lock is already held.
+////////////////////////////////////////////////////////////////////
+void MilesAudioManager::
+do_reduce_sounds_playing_to(unsigned int count) {
+  int limit = _sounds_playing.size() - count;
+  while (limit-- > 0) {
+    SoundsPlaying::iterator sound = _sounds_playing.begin();
+    assert(sound != _sounds_playing.end());
+    (**sound).stop();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MilesAudioManager::do_clear_cache
+//       Access: Private
+//  Description: Assumes the lock is already held.
+////////////////////////////////////////////////////////////////////
+void MilesAudioManager::
+do_clear_cache() {
+  if (_is_valid) { nassertv(do_is_valid()); }
+  _sounds.clear();
+  _lru.clear();
+  if (_is_valid) { nassertv(do_is_valid()); }
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: MilesAudioManager::start_service_stream
@@ -580,7 +669,7 @@ stop_service_stream(HSTREAM stream) {
 ////////////////////////////////////////////////////////////////////
 //     Function: MilesAudioManager::most_recently_used
 //       Access: Private
-//  Description:
+//  Description: Assumes the lock is already held.
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 most_recently_used(const string &path) {
@@ -593,18 +682,18 @@ most_recently_used(const string &path) {
   // At this point, path should not exist in the _lru:
   assert(find(_lru.begin(), _lru.end(), &path) == _lru.end());
   _lru.push_back(&path);
-  assert(is_valid());
+  nassertv(do_is_valid());
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: MilesAudioManager::uncache_a_sound
 //       Access: Private
-//  Description:
+//  Description: Assumes the lock is already held.
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 uncache_a_sound() {
   audio_debug("MilesAudioManager::uncache_a_sound()");
-  assert(is_valid());
+  nassertv(do_is_valid());
   // uncache least recently used:
   assert(_lru.size()>0);
   LRU::reference path=_lru.front();
@@ -616,7 +705,7 @@ uncache_a_sound() {
     audio_debug("  uncaching \""<<i->first<<"\"");
     _sounds.erase(i);
   }
-  assert(is_valid());
+  nassertv(do_is_valid());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -626,8 +715,9 @@ uncache_a_sound() {
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 starting_sound(MilesAudioSound *audio) {
+  ReMutexHolder holder(_lock);
   if (_concurrent_sound_limit) {
-    reduce_sounds_playing_to(_concurrent_sound_limit);
+    do_reduce_sounds_playing_to(_concurrent_sound_limit);
   }
   _sounds_playing.insert(audio);
 }
@@ -641,6 +731,7 @@ starting_sound(MilesAudioSound *audio) {
 ////////////////////////////////////////////////////////////////////
 void MilesAudioManager::
 stopping_sound(MilesAudioSound *audio) {
+  ReMutexHolder holder(_lock);
   _sounds_playing.erase(audio);
   if (_hasMidiSounds && _sounds_playing.size() == 0) {
     GlobalMilesManager::get_global_ptr()->force_midi_reset();
@@ -654,6 +745,8 @@ stopping_sound(MilesAudioSound *audio) {
 //  Description: Reads a sound file and allocates a SoundData pointer
 //               for it.  Returns NULL if the sound file cannot be
 //               loaded.
+//
+//               Assumes the lock is already held.
 ////////////////////////////////////////////////////////////////////
 PT(MilesAudioManager::SoundData) MilesAudioManager::
 load(const Filename &file_name) {

+ 11 - 3
panda/src/audiotraits/milesAudioManager.h

@@ -31,6 +31,7 @@
 #include "pvector.h"
 #include "thread.h"
 #include "pmutex.h"
+#include "reMutex.h"
 #include "conditionVar.h"
 
 class MilesAudioSound;
@@ -65,7 +66,6 @@ public:
   virtual unsigned int get_concurrent_sound_limit() const;
 
   virtual void reduce_sounds_playing_to(unsigned int count);
-
   virtual void stop_all_sounds();
 
   virtual void update();
@@ -78,6 +78,10 @@ public:
   virtual void write(ostream &out) const;
 
 private:
+  bool do_is_valid();
+  void do_reduce_sounds_playing_to(unsigned int count);
+  void do_clear_cache();
+
   void start_service_stream(HSTREAM stream);
   void stop_service_stream(HSTREAM stream);
   
@@ -123,12 +127,12 @@ private:
   // The offspring of this manager:
   AudioSet _sounds_on_loan;
 
-  typedef pset<MilesAudioSound* > SoundsPlaying;
+  typedef pset<MilesAudioSound *> SoundsPlaying;
   // The sounds from this manager that are currently playing:
   SoundsPlaying _sounds_playing;
 
   // The Least Recently Used mechanism:
-  typedef pdeque<const string* > LRU;
+  typedef pdeque<const string *> LRU;
   LRU _lru;
   // State:
   float _volume;
@@ -141,6 +145,10 @@ private:
   bool _is_valid;
   bool _hasMidiSounds;
 
+  // This mutex protects everything above.
+  ReMutex _lock;
+  bool _sounds_finished;
+
   typedef pvector<HSTREAM> Streams;
   PT(StreamThread) _stream_thread;
   Streams _streams;

+ 31 - 0
panda/src/audiotraits/milesAudioSample.cxx

@@ -96,6 +96,8 @@ play() {
                                   &_sd->_raw_data[0], _sd->_raw_data.size(),
                                   0);
         _original_playback_rate = AIL_sample_playback_rate(_sample);
+        AIL_set_sample_user_data(_sample, 0, (SINTa)this);
+        AIL_register_EOS_callback(_sample, finish_callback);
 
         set_volume(_volume);
         set_play_rate(_play_rate);
@@ -293,6 +295,19 @@ cleanup() {
   stop();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MilesAudioSample::output
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void MilesAudioSample::
+output(ostream &out) const {
+  out << get_type() << " " << get_name() << " " << status();
+  if (!_sd.is_null()) {
+    out << " " << (_sd->_raw_data.size() + 1023) / 1024 << "K";
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MilesAudioSample::internal_stop
 //       Access: Private
@@ -306,4 +321,20 @@ internal_stop() {
   _sample_index = 0;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MilesAudioSample::finish_callback
+//       Access: Private, Static
+//  Description: This callback is made by Miles (possibly in a
+//               sub-thread) when the sample finishes.
+////////////////////////////////////////////////////////////////////
+void AILCALLBACK MilesAudioSample::
+finish_callback(HSAMPLE sample) {
+  MilesAudioSample *self = (MilesAudioSample *)AIL_sample_user_data(sample, 0);
+  if (milesAudio_cat.is_debug()) {
+    milesAudio_cat.debug()
+      << "finished " << *self << "\n";
+  }
+  self->_manager->_sounds_finished = true;
+}
+
 #endif //]

+ 2 - 0
panda/src/audiotraits/milesAudioSample.h

@@ -56,9 +56,11 @@ public:
   virtual AudioSound::SoundStatus status() const;
 
   virtual void cleanup();
+  virtual void output(ostream &out) const;
 
 private:
   void internal_stop();
+  static void AILCALLBACK finish_callback(HSAMPLE sample);
 
   PT(MilesAudioManager::SoundData) _sd;
   HSAMPLE _sample;

+ 18 - 0
panda/src/audiotraits/milesAudioSequence.cxx

@@ -92,6 +92,8 @@ play() {
         _sequence = 0;
       } else {
         AIL_init_sequence(_sequence, &_sd->_raw_data[0], 0);
+        AIL_set_sequence_user_data(_sequence, 0, (SINTa)this);
+        AIL_register_sequence_callback(_sequence, finish_callback);
 
         set_volume(_volume);
         set_play_rate(_play_rate);
@@ -296,4 +298,20 @@ internal_stop() {
   _sequence_index = 0;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MilesAudioSequence::finish_callback
+//       Access: Private, Static
+//  Description: This callback is made by Miles (possibly in a
+//               sub-thread) when the sequence finishes.
+////////////////////////////////////////////////////////////////////
+void AILCALLBACK MilesAudioSequence::
+finish_callback(HSEQUENCE sequence) {
+  MilesAudioSequence *self = (MilesAudioSequence *)AIL_sequence_user_data(sequence, 0);
+  if (milesAudio_cat.is_debug()) {
+    milesAudio_cat.debug()
+      << "finished " << *self << "\n";
+  }
+  self->_manager->_sounds_finished = true;
+}
+
 #endif //]

+ 1 - 0
panda/src/audiotraits/milesAudioSequence.h

@@ -58,6 +58,7 @@ public:
 
 private:
   void internal_stop();
+  static void AILCALLBACK finish_callback(HSEQUENCE sequence);
 
   PT(MilesAudioManager::SoundData) _sd;
   HSEQUENCE _sequence;

+ 40 - 21
panda/src/audiotraits/milesAudioStream.cxx

@@ -77,6 +77,14 @@ play() {
     if (_stream == 0) {
       GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
       _stream = AIL_open_stream(mgr->_digital_driver, _path.c_str(), 0);
+      if (_stream == 0) {
+        milesAudio_cat.warning()
+          << "Could not play " << _file_name << ": too many open streams\n";
+        return;
+      }
+      AIL_set_stream_user_data(_stream, 0, (SINTa)this);
+      AIL_register_stream_callback(_stream, finish_callback);
+
     } else {
       // We already had the stream open.  Keep it open; just restart
       // it.
@@ -84,29 +92,24 @@ play() {
       _manager->stop_service_stream(_stream);
     }
 
-    if (_stream == 0) {
-      milesAudio_cat.warning()
-        << "Could not play " << _file_name << ": too many open streams\n";
-
+    // Start playing:
+    nassertv(_stream != 0);
+    HSAMPLE sample = AIL_stream_sample_handle(_stream);
+    nassertv(sample != 0);
+    
+    _original_playback_rate = AIL_sample_playback_rate(sample);
+    set_volume(_volume);
+    set_play_rate(_play_rate);
+    
+    AIL_set_stream_loop_count(_stream, _loop_count);
+    
+    if (miles_audio_panda_threads) {
+      AIL_auto_service_stream(_stream, 0);
+      _manager->start_service_stream(_stream);
     } else {
-      // Start playing:
-      HSAMPLE sample = AIL_stream_sample_handle(_stream);
-      nassertv(sample != 0);
-      
-      _original_playback_rate = AIL_sample_playback_rate(sample);
-      set_volume(_volume);
-      set_play_rate(_play_rate);
-      
-      AIL_set_stream_loop_count(_stream, _loop_count);
-      
-      if (miles_audio_panda_threads) {
-        AIL_auto_service_stream(_stream, 0);
-        _manager->start_service_stream(_stream);
-      } else {
-        AIL_auto_service_stream(_stream, 1);
-      }
-      AIL_start_stream(_stream);
+      AIL_auto_service_stream(_stream, 1);
     }
+    AIL_start_stream(_stream);
 
   } else {
     // In case _loop_count gets set to forever (zero):
@@ -295,5 +298,21 @@ cleanup() {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MilesAudioStream::finish_callback
+//       Access: Private, Static
+//  Description: This callback is made by Miles (possibly in a
+//               sub-thread) when the stream finishes.
+////////////////////////////////////////////////////////////////////
+void AILCALLBACK MilesAudioStream::
+finish_callback(HSTREAM stream) {
+  MilesAudioStream *self = (MilesAudioStream *)AIL_stream_user_data(stream, 0);
+  if (milesAudio_cat.is_debug()) {
+    milesAudio_cat.debug()
+      << "finished " << *self << "\n";
+  }
+  self->_manager->_sounds_finished = true;
+}
+
 
 #endif //]

+ 2 - 0
panda/src/audiotraits/milesAudioStream.h

@@ -58,6 +58,8 @@ public:
   virtual void cleanup();
 
 private:
+  static void AILCALLBACK finish_callback(HSTREAM stream);
+
   Filename _path;
   HSTREAM _stream;
   S32 _original_playback_rate;