Browse Source

More improvements and bugfixes

Josh Yelon 18 years ago
parent
commit
a89589033b

+ 1 - 1
panda/src/audiotraits/fmodAudioManager.cxx

@@ -341,7 +341,7 @@ make_dsp(const FilterProperties::FilterConfig &conf) {
 void FmodAudioManager::
 update_dsp_chain(FMOD::DSP *head, FilterProperties *config) {
   const FilterProperties::ConfigVector &conf = config->get_config();
-  FMOD_RESULT res1,res2,res3,res4,res5,res6;
+  FMOD_RESULT res1,res2,res3,res4,res5;
   while (1) {
     int numinputs;
     res1 = head->getNumInputs(&numinputs);

+ 32 - 21
panda/src/audiotraits/fmodAudioSound.cxx

@@ -282,14 +282,19 @@ get_loop_count() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: FmodAudioSound::set_time
 //       Access: public
-//  Description: Sets the play position within the sound
+//  Description: Starts playing from the specified location.
 ////////////////////////////////////////////////////////////////////
 void FmodAudioSound::
 set_time(float start_time) {
   FMOD_RESULT result;
 
+  if (!_active) {
+    _paused = true;
+    return;
+  }
+  
   int startTime = (int)(start_time * 1000);
-
+  
   if (_channel != 0) {
     // try backing up current sound.
     result = _channel->setPosition( startTime , FMOD_TIMEUNIT_MS );
@@ -324,10 +329,8 @@ set_time(float start_time) {
     // add_dsp_on_channel();
     set_3d_attributes_on_channel();
 
-    if (_active) {
-      result = _channel->setPaused(false);
-      fmod_audio_errcheck("_channel->setPaused()", result);
-    }
+    result = _channel->setPaused(false);
+    fmod_audio_errcheck("_channel->setPaused()", result);
     
     _self_ref = this;
   }
@@ -785,13 +788,26 @@ status() const {
 ////////////////////////////////////////////////////////////////////
 void FmodAudioSound::
 set_active(bool active) {
-  _active = active;
+  if (_active != active) {
+    _active = active;
+    if (_active) {
+      // ...activate the sound.
+      if (_paused && get_loop_count()==0) {
+        // ...this sound was looping when it was paused.
+        _paused = false;
+        play();
+      }
 
-  if (status() == PLAYING) {
-    // If the sound is (or should be) playing, then pause or unpause
-    // it in the system.
-    FMOD_RESULT result = _channel->setPaused(!_active);
-    fmod_audio_errcheck("_channel->setPaused()", result);
+    } else {
+      // ...deactivate the sound.
+      if (status() == PLAYING) {
+        if (get_loop_count() == 0) {
+          // ...we're pausing a looping sound.
+          _paused = true;
+        }
+        stop();
+      }
+    }
   }
 }
 
@@ -809,14 +825,11 @@ get_active() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: FmodAudioSound::finished
 //       Access: public
-//  Description: NOT USED ANYMORE!!!
-//        Called by finishedCallback function when a sound
-//              terminates (but doesn't loop).
+//  Description: Not implemented.
 ////////////////////////////////////////////////////////////////////
 void FmodAudioSound::
 finished() {
-  audio_debug("FmodAudioSound::finished()");
-  audio_debug("NOT USED ANYMORE in FMOD-EX version of PANDA.");
+  audio_error("finished: not implemented under FMOD-EX");
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -829,8 +842,7 @@ finished() {
 ////////////////////////////////////////////////////////////////////
 void FmodAudioSound::
 set_finished_event(const string& event) {
-  audio_debug("FmodAudioSound::set_finished_event(event="<<event<<")");
-  audio_debug("NOT USED ANYMORE in FMOD-EX version of PANDA.");
+  audio_error("set_finished_event: not implemented under FMOD-EX");
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -843,8 +855,7 @@ set_finished_event(const string& event) {
 ////////////////////////////////////////////////////////////////////
 const string& FmodAudioSound::
 get_finished_event() const {
-  audio_debug("FmodAudioSound::get_finished_event() returning " << _finished_event );
-  audio_debug("NOT USED ANYMORE in FMOD-EX version of PANDA.");
+  audio_error("get_finished_event: not implemented under FMOD-EX");
   return _finished_event;
 }
 

+ 3 - 10
panda/src/audiotraits/fmodAudioSound.h

@@ -154,14 +154,9 @@ class EXPCL_FMOD_AUDIO FmodAudioSound : public AudioSound {
   void set_active(bool active=true);
   bool get_active() const;
 
-  //THESE ARE NOT USED ANYMORE.
-  //THEY ARE ONLY HERE BECAUSE THEY are still needed by Miles.
-  //THESE are stubs in FMOD-EX version
-  ////////////////////////////////////////////////////////////////////
   void finished();
   void set_finished_event(const string& event);
   const string& get_finished_event() const;
-  ////////////////////////////////////////////////////////////////////
 
  private:
   PT(FmodAudioManager) _manager;
@@ -199,7 +194,10 @@ class EXPCL_FMOD_AUDIO FmodAudioSound : public AudioSound {
   virtual void set_priority(int priority);
   
   bool _active;
+  bool _paused;
   
+  string _finished_event;
+
   // This reference-counting pointer is set to this while the sound is
   // playing, and cleared when we get an indication that the sound has
   // stopped.  This prevents a sound from destructing while it is
@@ -208,11 +206,6 @@ class EXPCL_FMOD_AUDIO FmodAudioSound : public AudioSound {
   // other mismanagement.
   PT(FmodAudioSound) _self_ref;
 
-  //THESE AREN'T USED ANYMORE.
-  //THEY ARE ONLY HERE BECAUSE THEY are still need by Miles.
-  //THESE are stubs in FMOD-EX version
-  string _finished_event;
-
   friend FMOD_RESULT F_CALLBACK sound_end_callback(FMOD_CHANNEL *  channel, 
                                                    FMOD_CHANNEL_CALLBACKTYPE  type, 
                                                    int  command, 

+ 154 - 151
panda/src/audiotraits/openalAudioManager.cxx

@@ -74,7 +74,6 @@ void alc_audio_errcheck(const char *context,ALCdevice* device) {
 //  Description: Factory Function
 ////////////////////////////////////////////////////////////////////
 PT(AudioManager) Create_AudioManager() {
-  audio_debug("Create_AudioManager() OpenAL.");
   return new OpenALAudioManager;
 }
 
@@ -86,8 +85,6 @@ PT(AudioManager) Create_AudioManager() {
 ////////////////////////////////////////////////////////////////////
 OpenALAudioManager::
 OpenALAudioManager() {
-  audio_debug("OpenALAudioManager::OpenALAudioManager(), this = " 
-              << (void *)this);
   if (_managers == (Managers *)NULL) {
     _managers = new Managers;
     _al_sources = new SourceCache;
@@ -95,9 +92,6 @@ OpenALAudioManager() {
 
   _managers->insert(this);
 
-  audio_debug("  audio_active="<<audio_active);
-  audio_debug("  audio_volume="<<audio_volume);
-
   _cleanup_required = true;
   _active = audio_active;
   _volume = audio_volume;
@@ -146,7 +140,6 @@ OpenALAudioManager() {
   // The shutdown call will do the right thing when it's called,
   // either way.
   ++_active_managers;
-  audio_debug("  _active_managers="<<_active_managers);
   nassertv(_active_managers>0);
 
   if (!_device || !_context) {
@@ -170,15 +163,11 @@ OpenALAudioManager() {
 ////////////////////////////////////////////////////////////////////
 OpenALAudioManager::
 ~OpenALAudioManager() {
-  audio_debug("OpenALAudioManager::~OpenALAudioManager(), this = " 
-              << (void *)this);
   nassertv(_managers != (Managers *)NULL);
   Managers::iterator mi = _managers->find(this);
   nassertv(mi != _managers->end());
   _managers->erase(mi);
-
   cleanup();
-  audio_debug("OpenALAudioManager::~OpenALAudioManager() finished");
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -192,7 +181,6 @@ OpenALAudioManager::
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
 shutdown() {
-  audio_debug("shutdown(), _openal_active = " << _openal_active);
   if (_managers != (Managers *)NULL) {
     Managers::iterator mi;
     for (mi = _managers->begin(); mi != _managers->end(); ++mi) {
@@ -201,7 +189,6 @@ shutdown() {
   }
 
   nassertv(_active_managers == 0);
-  audio_debug("shutdown() finished");
 }
 
 
@@ -267,77 +254,97 @@ can_load_audio(MovieAudioCursor *source) {
   int channels = source->audio_channels();
   int samples = (int)(source->length() * source->audio_rate());
   int bytes = samples * channels * 2;
-  if (bytes > 200000000) {
+  if (bytes > 200000) {
     return false;
   }
   return true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: OpenALAudioManager::load_sound_data
+//     Function: OpenALAudioManager::get_sound_data
 //       Access: Private
-//  Description: Reads a sound file and creates a SoundData.
+//  Description: Obtains a SoundData for the specified sound.
+//
 //               When you are done with the SoundData, you need
 //               to decrement the client count.
 ////////////////////////////////////////////////////////////////////
 OpenALAudioManager::SoundData *OpenALAudioManager::
-load_sound_data(MovieAudioCursor *source) {
-
-  nassertr(can_use_audio(source), NULL);
-  nassertr(can_load_audio(source), NULL);
+get_sound_data(MovieAudio *movie) {
+  const Filename &path = movie->get_filename();
   
-  make_current();
-  Filename path = source->get_source()->get_filename();
-  SoundData *sd = new SoundData(this, path);
+  // Search for an already-cached sample or an already-opened stream.
+  if (!path.empty()) {
+    
+    SampleCache::iterator lsmi=_sample_cache.find(path);
+    if (lsmi != _sample_cache.end()) {
+      SoundData *sd = (*lsmi).second;
+      increment_client_count(sd);
+      return sd;
+    }
+
+    ExpirationQueue::iterator exqi;
+    for (exqi=_expiring_streams.begin(); exqi!=_expiring_streams.end(); exqi++) {
+      SoundData *sd = (SoundData*)(*exqi);
+      if (sd->_movie->get_filename() == path) {
+        increment_client_count(sd);
+        return sd;
+      }
+    }
+  }
   
-  alGetError(); // clear errors
-  sd->_buffer = 0;
-  alGenBuffers(1, &sd->_buffer);
-  al_audio_errcheck("alGenBuffers");
-  if (sd->_buffer == 0) {
-    audio_error("Could not create an OpenAL buffer object");
+  PT(MovieAudioCursor) stream = movie->open();
+  if (stream == 0) {
+    audio_error("Cannot open file: "<<path);
     return NULL;
   }
-
-  int channels = source->audio_channels();
-  int samples = (int)(source->length() * source->audio_rate());
   
-  PN_int16 *data = new PN_int16[samples * channels];
-  source->read_samples(samples, data);
-  alBufferData(sd->_buffer,
-               (channels>1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
-               data, samples * channels * 2, source->audio_rate());
-  int err = alGetError();
-  if (err != AL_NO_ERROR) {
-    audio_error("alBufferData: " << alGetString(err));
-    alDeleteBuffers(1, &sd->_buffer);
+  if (!can_use_audio(stream)) {
+    audio_error("File is not in usable format: "<<path);
     return NULL;
   }
-  sd->_rate = source->audio_rate();
-  sd->_channels = source->audio_channels();
-  sd->_length = source->length();
-  _all_sound_data.insert(SoundDataSet::value_type(path, sd));
-  return sd;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: OpenALAudioManager::cached_sound_data
-//       Access: Private
-//  Description: Looks in the cache for the specified sound data,
-//               and returns it if present.
-//
-//               When you are done with the SoundData, you need
-//               to decrement the client count.
-////////////////////////////////////////////////////////////////////
-OpenALAudioManager::SoundData *OpenALAudioManager::
-cached_sound_data(const Filename &path) {
-  SoundDataSet::const_iterator si=_all_sound_data.find(path);
-  if (si != _all_sound_data.end()) {
-    SoundData *sd = (*si).second;
-    increment_client_count(sd);
-    return sd;
+  
+  SoundData *sd = new SoundData();
+  sd->_client_count = 1;
+  sd->_manager  = this;
+  sd->_movie    = movie;
+  sd->_rate     = stream->audio_rate();
+  sd->_channels = stream->audio_channels();
+  sd->_length   = stream->length();
+
+  audio_debug("Creating: " << sd->_movie->get_filename().get_basename());
+
+  if (can_load_audio(stream)) {
+    audio_debug(path.get_basename() << ": loading as sample");
+    make_current();
+    alGetError(); // clear errors
+    sd->_sample = 0;
+    alGenBuffers(1, &sd->_sample);
+    al_audio_errcheck("alGenBuffers");
+    if (sd->_sample == 0) {
+      audio_error("Could not create an OpenAL buffer object");
+      delete sd;
+      return NULL;
+    }
+    int channels = stream->audio_channels();
+    int samples = (int)(stream->length() * stream->audio_rate());
+    PN_int16 *data = new PN_int16[samples * channels];
+    stream->read_samples(samples, data);
+    alBufferData(sd->_sample,
+                 (channels>1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
+                 data, samples * channels * 2, stream->audio_rate());
+    int err = alGetError();
+    if (err != AL_NO_ERROR) {
+      audio_error("could not fill OpenAL buffer object with data");
+      delete sd;
+      return NULL;
+    }
+    _sample_cache.insert(SampleCache::value_type(path, sd));
+  } else {
+    audio_debug(path.get_basename() << ": loading as stream");
+    sd->_stream = stream;
   }
-  return NULL;
+  
+  return sd;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -361,39 +368,20 @@ get_sound(const string &file_name, bool positional) {
     return NULL;
   }
   
-  PT(MovieAudioCursor) cursor;
-  SoundData *sd = cached_sound_data(path);
-  if (sd == 0) {
-    cursor = MovieAudio::get(path)->open();
-    if (cursor == 0) {
-      audio_error("Cannot open file: "<<path);
-      return NULL;
-    }
-    if (!can_use_audio(cursor)) {
-      audio_error("Audio data not supported format: "<<path);
-      return NULL;
-    }
-    if (can_load_audio(cursor)) {
-      sd = load_sound_data(cursor);
-      cursor = 0;
-    }
-  }
+  PT(MovieAudio) mva = MovieAudio::get(path);
   
   PT(OpenALAudioSound) oas = 
-    new OpenALAudioSound(this, path, cursor, sd, positional);
+    new OpenALAudioSound(this, mva, positional);
   
-  if (oas->get_active()) {
-    _all_audio_sounds.insert(oas);
-    PT(AudioSound) res = (AudioSound*)(OpenALAudioSound*)oas;
-    return res;
-  }
-  return NULL;
+  _all_sounds.insert(oas);
+  PT(AudioSound) res = (AudioSound*)(OpenALAudioSound*)oas;
+  return res;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: OpenALAudioManager::uncache_sound
 //       Access: Public
-//  Description: Deletes a sound from the expiration queue.
+//  Description: Deletes a sample from the expiration queues.
 //               If the sound is actively in use, then the sound
 //               cannot be deleted, and this function has no effect.
 ////////////////////////////////////////////////////////////////////
@@ -405,15 +393,13 @@ uncache_sound(const string& file_name) {
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
   vfs->resolve_filename(path, get_sound_path()) ||
     vfs->resolve_filename(path, get_model_path());
-  
-  SoundDataSet::iterator sdi=_all_sound_data.find(path);
-  if (sdi != _all_sound_data.end()) {
-    SoundData *sd = (*sdi).second;
-    ExpirationQueue::iterator exi =
-      find(_expiration_queue.begin(), _expiration_queue.end(), sd);
-    if (exi != _expiration_queue.end()) {
-      _expiration_queue.erase(exi);
-      _all_sound_data.erase(sdi);
+
+  SampleCache::iterator sci = _sample_cache.find(path);
+  if (sci != _sample_cache.end()) {
+    SoundData *sd = (*sci).second;
+    if (sd->_client_count == 0) {
+      _expiring_samples.erase(sd->_expire);
+      _sample_cache.erase(sci);
       delete sd;
     }
   }
@@ -457,9 +443,9 @@ get_cache_limit() const {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
 release_sound(OpenALAudioSound* audioSound) {
-  AudioSoundSet::iterator ai = _all_audio_sounds.find(audioSound);
-  if (ai != _all_audio_sounds.end()) {
-    _all_audio_sounds.erase(ai);
+  AllSounds::iterator ai = _all_sounds.find(audioSound);
+  if (ai != _all_sounds.end()) {
+    _all_sounds.erase(ai);
   }
 }
 
@@ -470,13 +456,12 @@ release_sound(OpenALAudioSound* audioSound) {
 //        Sets listener gain
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::set_volume(float volume) {
-  audio_debug("OpenALAudioManager::set_volume(volume="<<volume<<")");
   if (_volume!=volume) {
     _volume = volume;
 
     // Tell our AudioSounds to adjust:
-    AudioSoundSet::iterator i=_all_audio_sounds.begin();
-    for (; i!=_all_audio_sounds.end(); ++i) {
+    AllSounds::iterator i=_all_sounds.begin();
+    for (; i!=_all_sounds.end(); ++i) {
       (**i).set_volume((**i).get_volume());
     }
 
@@ -499,7 +484,6 @@ void OpenALAudioManager::set_volume(float volume) {
 ////////////////////////////////////////////////////////////////////
 float OpenALAudioManager::
 get_volume() const {
-  audio_debug("OpenALAudioManager::get_volume() returning "<<_volume);
   return _volume;
 }
 
@@ -510,12 +494,11 @@ get_volume() const {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
 set_play_rate(float play_rate) {
-  audio_debug("OpenALAudioManager::set_play_rate(play_rate="<<play_rate<<")");
   if (_play_rate!=play_rate) {
     _play_rate = play_rate;
     // Tell our AudioSounds to adjust:
-    AudioSoundSet::iterator i=_all_audio_sounds.begin();
-    for (; i!=_all_audio_sounds.end(); ++i) {
+    AllSounds::iterator i=_all_sounds.begin();
+    for (; i!=_all_sounds.end(); ++i) {
       (**i).set_play_rate((**i).get_play_rate());
     }
   }
@@ -528,7 +511,6 @@ set_play_rate(float play_rate) {
 ////////////////////////////////////////////////////////////////////
 float OpenALAudioManager::
 get_play_rate() const {
-  audio_debug("OpenALAudioManager::get_play_rate() returning "<<_play_rate);
   return _play_rate;
 }
 
@@ -540,12 +522,11 @@ get_play_rate() const {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
 set_active(bool active) {
-  audio_debug("OpenALAudioManager::set_active(flag="<<active<<")");
   if (_active!=active) {
     _active=active;
     // Tell our AudioSounds to adjust:
-    AudioSoundSet::iterator i=_all_audio_sounds.begin();
-    for (; i!=_all_audio_sounds.end(); ++i) {
+    AllSounds::iterator i=_all_sounds.begin();
+    for (; i!=_all_sounds.end(); ++i) {
       (**i).set_active(_active);
     }
   }
@@ -558,7 +539,6 @@ set_active(bool active) {
 ////////////////////////////////////////////////////////////////////
 bool OpenALAudioManager::
 get_active() const {
-  audio_debug("OpenALAudioManager::get_active() returning "<<_active);
   return _active;
 }
 
@@ -661,8 +641,8 @@ audio_3d_set_distance_factor(float factor) {
     al_audio_errcheck("alDopplerFactor()");
   }
 
-  AudioSoundSet::iterator i=_all_audio_sounds.begin();
-  for (; i!=_all_audio_sounds.end(); ++i) {
+  AllSounds::iterator i=_all_sounds.begin();
+  for (; i!=_all_sounds.end(); ++i) {
     (**i).set_3d_min_distance((**i).get_3d_min_distance());
     (**i).set_3d_max_distance((**i).get_3d_max_distance());
   }
@@ -715,8 +695,8 @@ void OpenALAudioManager::
 audio_3d_set_drop_off_factor(float factor) {
   _drop_off_factor = factor;
 
-  AudioSoundSet::iterator i=_all_audio_sounds.begin();
-  for (; i!=_all_audio_sounds.end(); ++i) {
+  AllSounds::iterator i=_all_sounds.begin();
+  for (; i!=_all_sounds.end(); ++i) {
     (**i).set_3d_drop_off_factor((**i).get_3d_drop_off_factor());
   }
 }
@@ -842,7 +822,6 @@ reduce_sounds_playing_to(unsigned int count) {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
 stop_all_sounds() {
-  audio_debug("OpenALAudioManager::stop_all_sounds()");
   reduce_sounds_playing_to(0);
 }
 
@@ -853,7 +832,6 @@ stop_all_sounds() {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
 update() {
-  //audio_debug("OpenALAudioManager::update()");
 
   // See if any of our playing sounds have ended
   // we must first collect a seperate list of finished sounds and then
@@ -862,13 +840,19 @@ update() {
   // since finished() modifies _sounds_playing
   SoundsPlaying sounds_finished;
 
+  double rtc = TrueClock::get_global_ptr()->get_short_time();
   SoundsPlaying::iterator i=_sounds_playing.begin();
   for (; i!=_sounds_playing.end(); ++i) {
-    if ((**i).status()!=AudioSound::PLAYING) {
+    OpenALAudioSound *sound = (*i);
+    sound->pull_used_buffers();
+    sound->push_fresh_buffers();
+    sound->restart_stalled_audio();
+    sound->cache_time(rtc);
+    if (sound->status()!=AudioSound::PLAYING) {
       sounds_finished.insert(*i);
     }
   }
-
+  
   i=sounds_finished.begin();
   for (; i!=sounds_finished.end(); ++i) {
     (**i).finished();
@@ -885,19 +869,16 @@ update() {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
 cleanup() {
-  audio_debug("OpenALAudioManager::cleanup(), this = " << (void *)this
-              << ", _cleanup_required = " << _cleanup_required);
   if (!_cleanup_required) {
     return;
   }
 
-  AudioSoundSet sounds(_all_audio_sounds);
-  AudioSoundSet::iterator ai;
+  AllSounds sounds(_all_sounds);
+  AllSounds::iterator ai;
   for (ai = sounds.begin(); ai != sounds.end(); ++ai) {
     (*ai)->cleanup();
   }
   
-  assert(_all_sound_data.size() == _expiration_queue.size());
   clear_cache();
   
   nassertv(_active_managers > 0);
@@ -938,7 +919,6 @@ cleanup() {
     }
   }
   _cleanup_required = false;
-  audio_debug("OpenALAudioManager::cleanup() finished");
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -947,12 +927,15 @@ cleanup() {
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 OpenALAudioManager::SoundData::
-SoundData(OpenALAudioManager* manager, const Filename &path) :
-  _manager(manager),
-  _path(path),
-  _buffer(0),
-  _client_count(1),
-  _length(0.0)
+SoundData() :
+  _manager(0),
+  _movie(0),
+  _sample(0),
+  _stream(NULL),
+  _length(0.0),
+  _rate(0),
+  _channels(0),
+  _client_count(0)
 {
 }
 
@@ -963,12 +946,12 @@ SoundData(OpenALAudioManager* manager, const Filename &path) :
 ////////////////////////////////////////////////////////////////////
 OpenALAudioManager::SoundData::
 ~SoundData() {
-  if (_buffer != 0) {
+  if (_sample != 0) {
     if (_manager->_is_valid) {
       _manager->make_current();
-      alDeleteBuffers(1,&_buffer);
+      alDeleteBuffers(1,&_sample);
     }
-    _buffer = 0;
+    _sample = 0;
   }
 }
 
@@ -981,13 +964,14 @@ OpenALAudioManager::SoundData::
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
 increment_client_count(SoundData *sd) {
-  audio_debug("Incrementing client count: " << sd->_path);
   sd->_client_count += 1;
+  audio_debug("Incrementing: " << sd->_movie->get_filename().get_basename() << " " << sd->_client_count);
   if (sd->_client_count == 1) {
-    audio_debug("Removing from expiration queue: " << sd->_path);
-    ExpirationQueue::iterator p=find(_expiration_queue.begin(), _expiration_queue.end(), sd);
-    assert(p != _expiration_queue.end());
-    _expiration_queue.erase(p);
+    if (sd->_sample) {
+      _expiring_samples.erase(sd->_expire);
+    } else {
+      _expiring_streams.erase(sd->_expire);
+    }
   }
 }
 
@@ -1002,11 +986,18 @@ increment_client_count(SoundData *sd) {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
 decrement_client_count(SoundData *sd) {
-  audio_debug("Decrementing client count: " << sd->_path);
   sd->_client_count -= 1;
+  audio_debug("Decrementing: " << sd->_movie->get_filename().get_basename() << " " << sd->_client_count);
   if (sd->_client_count == 0) {
-    audio_debug("Adding to expiration queue: " << sd->_path);
-    _expiration_queue.push_back(sd);
+    if (sd->_sample) {
+      _expiring_samples.push_back(sd);
+      sd->_expire = _expiring_samples.end();
+      sd->_expire--;
+    } else {
+      _expiring_streams.push_back(sd);
+      sd->_expire = _expiring_streams.end();
+      sd->_expire--;
+    }
     discard_excess_cache(_cache_limit);
   }
 }
@@ -1018,13 +1009,25 @@ decrement_client_count(SoundData *sd) {
 //               number of sounds remaining is under the limit.
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioManager::
-discard_excess_cache(int limit) {
-  while (((int)_expiration_queue.size()) > limit) {
-    SoundData *sd = _expiration_queue.front();
-    audio_debug("Deleting head of sound cache: " << sd->_path);
+discard_excess_cache(int sample_limit) {
+  int stream_limit = 5;
+
+  while (((int)_expiring_samples.size()) > sample_limit) {
+    SoundData *sd = (SoundData*)(_expiring_samples.front());
+    assert(sd->_client_count == 0);
+    assert(sd->_expire == _expiring_samples.begin());
+    _expiring_samples.pop_front();
+    _sample_cache.erase(_sample_cache.find(sd->_movie->get_filename()));
+    audio_debug("Expiring: " << sd->_movie->get_filename().get_basename());
+    delete sd;
+  }
+
+  while (((int)_expiring_streams.size()) > stream_limit) {
+    SoundData *sd = (SoundData*)(_expiring_streams.front());
     assert(sd->_client_count == 0);
-    _expiration_queue.pop_front();
-    _all_sound_data.erase(sd->_path);
+    assert(sd->_expire == _expiring_streams.begin());
+    _expiring_streams.pop_front();
+    audio_debug("Expiring: " << sd->_movie->get_filename().get_basename());
     delete sd;
   }
 }

+ 31 - 21
panda/src/audiotraits/openalAudioManager.h

@@ -25,9 +25,9 @@
 #ifdef HAVE_OPENAL //[
 
 #include "audioManager.h"
-#include "pset.h"
+#include "plist.h"
 #include "pmap.h"
-#include "pdeque.h"
+#include "pset.h"
 #include "movieAudioCursor.h"
 
 //The Includes needed for OpenAL
@@ -115,12 +115,11 @@ class EXPCL_OPENAL_AUDIO OpenALAudioManager : public AudioManager {
 
 private:
   void make_current() const;
-  
+
   bool can_use_audio(MovieAudioCursor *source);
   bool can_load_audio(MovieAudioCursor *source);
   
-  SoundData *cached_sound_data(const Filename &file_name);
-  SoundData *load_sound_data(MovieAudioCursor *source);
+  SoundData *get_sound_data(MovieAudio *source);
 
   // Tell the manager that the sound dtor was called.
   void release_sound(OpenALAudioSound* audioSound);
@@ -135,6 +134,16 @@ private:
   
 private:
 
+  // An expiration queue is a list of SoundData
+  // that are no longer being used.  They are kept
+  // around for a little while, since it is common to
+  // stop using a sound for a brief moment and then
+  // quickly resume.
+  
+  typedef plist<void *> ExpirationQueue;
+  ExpirationQueue _expiring_samples;
+  ExpirationQueue _expiring_streams;
+
   // An AudioSound that uses a SoundData is called a "client"
   // of the SoundData.  The SoundData keeps track of how
   // many clients are using it.  When the number of clients
@@ -148,27 +157,28 @@ private:
 
   class SoundData {
   public:
-    SoundData(OpenALAudioManager* manager, const Filename &path);
+    SoundData();
     ~SoundData();
-    OpenALAudioManager* _manager;
-    Filename _path;
-    ALuint _buffer;
-    double _length;
-    int _rate;
-    int _channels;
-    int _client_count;
+    OpenALAudioManager*  _manager;
+    PT(MovieAudio)       _movie;
+    ALuint               _sample;
+    PT(MovieAudioCursor) _stream;
+    double               _length;
+    int                  _rate;
+    int                  _channels;
+    int                  _client_count;
+    ExpirationQueue::iterator _expire;
   };
-  typedef pmap<string, SoundData *> SoundDataSet;
-  SoundDataSet _all_sound_data;
-  
-  typedef pset<OpenALAudioSound *> AudioSoundSet;
-  AudioSoundSet _all_audio_sounds;
+
   
-  typedef pdeque<SoundData *> ExpirationQueue;
-  ExpirationQueue _expiration_queue;
+  typedef phash_map<string, SoundData *> SampleCache;
+  SampleCache _sample_cache;
   
-  typedef pset<OpenALAudioSound *> SoundsPlaying;
+  typedef phash_set<OpenALAudioSound *> SoundsPlaying;
   SoundsPlaying _sounds_playing;
+
+  typedef phash_set<OpenALAudioSound *> AllSounds;
+  AllSounds _all_sounds;
   
   // State:
   int _cache_limit;

+ 59 - 2
panda/src/audiotraits/openalAudioSound.I

@@ -16,8 +16,65 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::set_calibrated_clock
+//       Access: public
+//  Description: Sets the sound's calibrated clock.
+//
+//               OpenAL is not very accurate at reporting how much
+//               time has elapsed within a buffer.  However, it does
+//               accurately report when it has finished playing a
+//               buffer.  So we use a hybrid clock algorithm.
+//               When OpenAL is in the middle of a buffer,
+//               we use a real-time-clock to estimate how far the
+//               sound has gotten.  Each time OpenAL reaches the end
+//               of a buffer (which it does every 1/4 second or so),
+//               we calibrate our real-time-clock by speeding it up
+//               or slowing it down.
+////////////////////////////////////////////////////////////////////
+INLINE void OpenALAudioSound::
+set_calibrated_clock(double rtc, double t, double accel) {
+  _calibrated_clock_scale = _playing_rate * accel;
+  _calibrated_clock_base = rtc - (t / _calibrated_clock_scale);
+}
 
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::get_calibrated_clock
+//       Access: public
+//  Description: Returns the value of the calibrated clock.
+////////////////////////////////////////////////////////////////////
+INLINE double OpenALAudioSound::
+get_calibrated_clock(double rtc) const {
+  return (rtc - _calibrated_clock_base) * _calibrated_clock_scale;
+}
 
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::require_sound_data
+//       Access: Private
+//  Description: Makes sure the sound data record is present,
+//               and if not, obtains it.
+////////////////////////////////////////////////////////////////////
+void OpenALAudioSound::
+require_sound_data() {
+  if (_sd==0) {
+    _sd = _manager->get_sound_data(_movie);
+    if (_sd==0) {
+      audio_error("Could not open audio " << _movie->get_filename());
+      cleanup();
+    }
+  }
+}
 
-
-
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::release_sound_data
+//       Access: Private
+//  Description: Checks if the sound data record is present and
+//               releasable, and if so, releases it.
+////////////////////////////////////////////////////////////////////
+void OpenALAudioSound::
+release_sound_data() {
+  if ((_sd!=0) && (!_movie->get_filename().empty())) {
+    _manager->decrement_client_count(_sd);
+    _sd = 0;
+  }
+}

+ 376 - 161
panda/src/audiotraits/openalAudioSound.cxx

@@ -45,42 +45,48 @@ TypeHandle OpenALAudioSound::_type_handle;
 
 OpenALAudioSound::
 OpenALAudioSound(OpenALAudioManager* manager,
-                 const Filename &path,
-                 PT(MovieAudioCursor) stream,
-                 OpenALAudioManager::SoundData *sample,
+                 MovieAudio *movie,
                  bool positional) :
-  _sample(sample),
-  _stream(stream),
+  _movie(movie),
+  _sd(NULL),
+  _loops_completed(0),
+  _playing_rate(0.0),
+  _playing_loops(0),
   _source(0),
   _manager(manager),
-  _path(path),
+  _basename(movie->get_filename().get_basename()),
   _volume(1.0f),
   _balance(0),
-  _play_rate(1.0),
   _loop_count(1),
+  _length(0.0),
+  _start_time(0.0),
+  _play_rate(1.0),
+  _current_time(0.0),
   _active(true),
   _paused(false)
 {
-  //Inits 3D Attributes
-  
   _location[0] = 0;
   _location[1] = 0;
   _location[2] = 0;
-
+  
   _velocity[0] = 0;
   _velocity[1] = 0;
   _velocity[2] = 0;
-
+  
   _min_dist = 3.28f; _max_dist = 1000000000.0f;
   _drop_off_factor = 1.0f;
   
   _positional = positional;
-  if (_positional) {
-    if ((_sample && (_sample->_channels != 1)) ||
-        (_stream && (_stream->audio_channels() != 1))) {
-      audio_warning("Stereo sounds won't be spacialized: "<<path);
+  
+  require_sound_data();
+  if (_manager == 0) return;
+  _length = _sd->_length;
+  if (positional) {
+    if (_sd->_channels != 1) {
+      audio_warning("stereo sound " << movie->get_filename() << " will not be spatialized");
     }
   }
+  release_sound_data();
 }
 
 
@@ -108,13 +114,12 @@ cleanup() {
   if (_source) {
     stop();
   }
-  if (_sample) {
-    _manager->decrement_client_count(_sample);
-    _sample = 0;
+  if (_sd) {
+    _manager->decrement_client_count(_sd);
+    _sd = 0;
   }
   _manager->release_sound(this);
   _manager = 0;
-  _active = false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -124,9 +129,72 @@ cleanup() {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
 play() {
-  set_time(0.0);
-}
+  if (_manager == 0) return;
 
+  float px,py,pz,vx,vy,vz;
+  
+  if (!_active) {
+    _paused = true;
+    return;
+  }
+  
+  stop();
+
+  require_sound_data();
+  if (_manager == 0) return;
+  _manager->starting_sound(this);
+  
+  if (!_source) {
+    return;
+  }
+  
+  _manager->make_current();
+  
+  alGetError(); // clear errors
+  
+  // nonpositional sources are made relative to the listener so they don't move
+  alSourcei(_source,AL_SOURCE_RELATIVE,_positional?AL_FALSE:AL_TRUE);
+  al_audio_errcheck("alSourcei(_source,AL_SOURCE_RELATIVE)");
+  
+  // set source properties that we have stored
+  set_volume(_volume);
+  //set_balance(_balance);
+
+  set_3d_min_distance(_min_dist);
+  set_3d_max_distance(_max_dist);
+  set_3d_drop_off_factor(_drop_off_factor);
+  get_3d_attributes(&px,&py,&pz,&vx,&vy,&vz);
+  set_3d_attributes(px, py, pz, vx, vy, vz);
+  
+  _playing_loops = _loop_count;
+  if (_playing_loops == 0) {
+    _playing_loops = 1000000000;
+  }
+  _loops_completed = 0;
+
+  double play_rate = _play_rate * _manager->get_play_rate();
+  audio_debug("playing. Rate=" << play_rate);
+  alSourcef(_source, AL_PITCH, play_rate);
+  _playing_rate = play_rate;
+  
+  if (_sd->_sample) {
+    push_fresh_buffers();
+    alSourcef(_source, AL_SEC_OFFSET, _start_time);
+    _stream_queued[0]._time_offset = _start_time;
+    restart_stalled_audio();
+  } else {
+    audio_debug("Play: stream tell = " << _sd->_stream->tell() << " seeking " << _start_time); 
+    if (_sd->_stream->tell() != _start_time) {
+      _sd->_stream->seek(_start_time);
+    }
+    push_fresh_buffers();
+    restart_stalled_audio();
+  }
+  double rtc = TrueClock::get_global_ptr()->get_short_time();
+  set_calibrated_clock(rtc, _start_time, 1.0);
+  _current_time = _start_time;
+  _start_time = 0.0;
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: OpenALAudioSound::stop
@@ -135,24 +203,28 @@ play() {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
 stop() {
-  openal_audio_debug("stop()");
-  //nassertv(_source);
-  
+  if (_manager==0) return;
+
   if (_source) {
     _manager->make_current();
 
     alGetError(); // clear errors
     alSourceStop(_source);
-    al_audio_errcheck("alSourceStop(_source)");
+    al_audio_errcheck("stopping a source");
+    alSourcei(_source, AL_BUFFER, 0);
+    al_audio_errcheck("clear source buffers");
+    for (int i=0; i<((int)(_stream_queued.size())); i++) {
+      ALuint buffer = _stream_queued[i]._buffer;
+      if (buffer != _sd->_sample) {
+        alDeleteBuffers(1, &buffer);
+        al_audio_errcheck("deleting a buffer");
+      }
+    }
+    _stream_queued.resize(0);
   }
-
+  
   _manager->stopping_sound(this);
-  // The _paused flag should not be cleared here.  _paused is not like
-  // the Pause button on a cd/dvd player.  It is used as a flag to say
-  // that it was looping when it was set inactive.  There is no need to
-  // make this symmetrical with play().  set_active() is the 'owner' of
-  // _paused.  play() accesses _paused to help in the situation where
-  // someone calls play on an inactive sound().
+  release_sound_data();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -162,8 +234,8 @@ stop() {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
 finished() {
-  openal_audio_debug("finished()");
-  _manager->stopping_sound(this);
+  stop();
+  _current_time = _length;
   if (!_finished_event.empty()) {
     throw_event(_finished_event);
   }
@@ -186,7 +258,7 @@ set_loop(bool loop) {
 ////////////////////////////////////////////////////////////////////
 bool OpenALAudioSound::
 get_loop() const {
-  return (_loop_count != 1);
+  return (_loop_count == 0);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -196,27 +268,12 @@ get_loop() const {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
 set_loop_count(unsigned long loop_count) {
-  openal_audio_debug("set_loop_count(loop_count="<<loop_count<<")");
-
-  if (loop_count>1) {
-    audio_error("OpenALAudioSound::set_loop_count() doesn't support looping a finite number of times, 0 (infinite) or 1 only");
-    loop_count = 1;
-  }
+  if (_manager==0) return;
   
-  if (_loop_count==loop_count) {
-    return;
+  if (loop_count >= 1000000000) {
+    loop_count = 0;
   }
-  
   _loop_count=loop_count;
-  
-  if (_source) {
-    // I believe there is a race condition here.
-    _manager->make_current();
-    
-    alGetError(); // clear errors
-    alSourcei(_source,AL_LOOPING,_loop_count==0?AL_TRUE:AL_FALSE);
-    al_audio_errcheck("alSourcei(_source,AL_LOOPING)");
-  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -230,69 +287,249 @@ get_loop_count() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: OpenALAudioSound::set_time
+//     Function: OpenALAudioSound::restart_stalled_audio
 //       Access: public
-//  Description: Sets the play position within the sound
+//  Description: When streaming audio, the computer is supposed to 
+//               keep OpenAL's queue full.  However, there are times
+//               when the computer is running slow and the queue 
+//               empties prematurely.  In that case, OpenAL will stop.
+//               When the computer finally gets around to refilling
+//               the queue, it is necessary to tell OpenAL to resume
+//               playing.
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
-set_time(float time) {
-  float px,py,pz,vx,vy,vz;
-  
-  openal_audio_debug("play()");
-  if (!_active) {
-    _paused=true;
+restart_stalled_audio() {
+  ALenum status;
+  if (_stream_queued.size() == 0) {
     return;
   }
-  
-  if (status() == AudioSound::PLAYING) {
-    stop();
+  alGetError();
+  alGetSourcei(_source, AL_SOURCE_STATE, &status);
+  if (status != AL_PLAYING) {
+    alSourcePlay(_source);
   }
-  
-  _manager->starting_sound(this);
-  
-  if (!_source) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::queue_buffer
+//       Access: public
+//  Description: Pushes a buffer into the source queue.
+////////////////////////////////////////////////////////////////////
+void OpenALAudioSound::
+queue_buffer(ALuint buffer, int loop_index, double time_offset) {
+  // Now push the buffer into the stream queue.
+  alGetError();
+  alSourceQueueBuffers(_source,1,&buffer);
+  ALenum err = alGetError();
+  if (err != AL_NO_ERROR) {
+    audio_error("could not load sample buffer into the queue");
+    cleanup();
     return;
   }
+  QueuedBuffer buf;
+  buf._buffer = buffer;
+  buf._loop_index = loop_index;
+  buf._time_offset = time_offset;
+  _stream_queued.push_back(buf);
+  //  audio_debug("Buffer queued " << loop_index << " " << time_offset);
+}
 
-  // Setup source
-  _manager->make_current();
-  
-  alGetError(); // clear errors
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::make_buffer
+//       Access: public
+//  Description: Creates an OpenAL buffer object.
+////////////////////////////////////////////////////////////////////
+ALuint OpenALAudioSound::
+make_buffer(int samples, int channels, int rate, unsigned char *data) {
   
-  // Assign the buffer to the source
-  alSourcei(_source,AL_BUFFER,_sample->_buffer);
-  ALenum result = alGetError();
-  if (result!=AL_NO_ERROR) {
-    audio_error("alSourcei(_source,AL_BUFFER,_sample->_buffer): " << alGetString(result) );
-    stop();
-    return;
+  // Allocate a buffer to hold the data.
+  alGetError();
+  ALuint buffer;
+  alGenBuffers(1, &buffer);
+  if (alGetError() != AL_NO_ERROR) {
+    audio_error("could not allocate an OpenAL buffer object");
+    cleanup();
+    return 0;
   }
   
-  // nonpositional sources are made relative to the listener so they don't move
-  alSourcei(_source,AL_SOURCE_RELATIVE,_positional?AL_FALSE:AL_TRUE);
-  al_audio_errcheck("alSourcei(_source,AL_SOURCE_RELATIVE)");
-  
-  // set source properties that we have stored
-  set_volume(_volume);
-  //set_balance(_balance);
-  set_play_rate(_play_rate);
-  set_3d_min_distance(_min_dist);
-  set_3d_max_distance(_max_dist);
-  set_3d_drop_off_factor(_drop_off_factor);
-  get_3d_attributes(&px,&py,&pz,&vx,&vy,&vz);
-  set_3d_attributes(px, py, pz, vx, vy, vz);
+  // Now fill the buffer with the data provided.
+  alBufferData(buffer,
+               (channels>1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
+               data, samples * channels * 2, rate);
+  int err = alGetError();
+  if (err != AL_NO_ERROR) {
+    audio_error("could not fill OpenAL buffer object with data");
+    cleanup();
+    return 0;
+  }
   
-  set_loop_count(_loop_count);
+  return buffer;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::read_stream_data
+//       Access: public
+//  Description: Fills a buffer with data from the stream.
+//               Returns the number of samples stored in the buffer.
+////////////////////////////////////////////////////////////////////
+int OpenALAudioSound::
+read_stream_data(int bytelen, unsigned char *buffer) {
+
+  MovieAudioCursor *cursor = _sd->_stream;
+  double length = cursor->length();
+  int channels = cursor->audio_channels();
+  int rate = cursor->audio_rate();
+  int space = bytelen / (channels * 2);
+  int fill = 0;
   
-  openal_audio_debug("set_time(time="<<time<<")");
+  while (space && (_loops_completed < _playing_loops)) {
+    double t = cursor->tell();
+    double remain = length - t;
+    if (remain > 60.0) {
+      remain = 60.0;
+    }
+    int samples = (int)(remain * rate);
+    if (samples <= 0) {
+      _loops_completed += 1;
+      cursor->seek(0.0);
+      continue;
+    }
+    if (samples > space) {
+      samples = space;
+    }
+    cursor->read_samples(samples, (PN_int16 *)buffer);
+    size_t hval = AddHash::add_hash(0, (PN_uint8*)buffer, samples*channels*2);
+    audio_debug("Streaming " << cursor->get_source()->get_filename().get_basename() << " at " << t << " hash " << hval);
+    fill += samples;
+    space -= samples;
+    buffer += (samples * channels * 2);
+  }
+  return fill; 
+}
 
-  alSourcef(_source,AL_SEC_OFFSET,time);
-  al_audio_errcheck("alSourcef(_source,AL_SEC_OFFSET)");
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::correct_calibrated_clock
+//       Access: public
+//  Description: Compares the specified time to the value of the
+//               calibrated clock, and adjusts the calibrated
+//               clock speed to make it closer to the target value.
+//               This routine is quite careful to make sure that
+//               the calibrated clock moves in a smooth, monotonic
+//               way.
+////////////////////////////////////////////////////////////////////
+void OpenALAudioSound::
+correct_calibrated_clock(double rtc, double t) {
+  double cc = (rtc - _calibrated_clock_base) * _calibrated_clock_scale;
+  double diff = cc-t;
+  _calibrated_clock_decavg = (_calibrated_clock_decavg * 0.95) + (diff * 0.05);
+  if (diff > 0.5) {
+    set_calibrated_clock(rtc, t, 1.0);
+    _calibrated_clock_decavg = 0.0;
+  } else {
+    double scale = 1.0;
+    if ((_calibrated_clock_decavg > 0.01) && (diff > 0.01)) {
+      scale = 0.98;
+    }
+    if ((_calibrated_clock_decavg < -0.01) && (diff < -0.01)) {
+      scale = 1.03;
+    }
+    if ((_calibrated_clock_decavg < -0.05) && (diff < -0.05)) {
+      scale = 1.2;
+    }
+    if ((_calibrated_clock_decavg < -0.15) && (diff < -0.15)) {
+      scale = 1.5;
+    }
+    set_calibrated_clock(rtc, cc, scale);
+  }
+  cc = (rtc - _calibrated_clock_base) * _calibrated_clock_scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::pull_used_buffers
+//       Access: public
+//  Description: Pulls any used buffers out of OpenAL's queue.
+////////////////////////////////////////////////////////////////////
+void OpenALAudioSound::
+pull_used_buffers() {
+  while (_stream_queued.size()) {
+    ALuint buffer = 0;
+    alGetError();
+    alSourceUnqueueBuffers(_source, 1, &buffer);
+    int err = alGetError();
+    if (err == AL_NO_ERROR) {
+      if (_stream_queued[0]._buffer != buffer) {
+        audio_error("corruption in stream queue");
+        cleanup();
+        return;
+      }
+      _stream_queued.pop_front();
+      if (_stream_queued.size()) {
+        double al = _stream_queued[0]._time_offset + _stream_queued[0]._loop_index * _length;
+        double rtc = TrueClock::get_global_ptr()->get_short_time();
+        correct_calibrated_clock(rtc, al);
+      }
+      if (buffer != _sd->_sample) {
+        alDeleteBuffers(1,&buffer);
+      }
+    } else {
+      break;
+    }
+  }
+}
 
-  alSourcePlay(_source);
-  al_audio_errcheck("alSourcePlay(_source)");
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::push_fresh_buffers
+//       Access: public
+//  Description: Pushes fresh buffers into OpenAL's queue until
+//               the queue is "full" (ie, has plenty of data).
+////////////////////////////////////////////////////////////////////
+void OpenALAudioSound::
+push_fresh_buffers() {
+  static unsigned char data[65536];
   
-  audio_debug("  started sound " << _path );
+  if (_sd->_sample) {
+    while ((_loops_completed < _playing_loops) &&
+           (_stream_queued.size() < 100)) {
+      queue_buffer(_sd->_sample, _loops_completed, 0.0);
+      _loops_completed += 1;
+    }
+  } else {
+    MovieAudioCursor *cursor = _sd->_stream;
+    int channels = cursor->audio_channels();
+    int rate = cursor->audio_rate();
+    double space = 65536 / (channels * 2);
+    
+    // Calculate how many buffers to keep in the queue.
+    int fill_to = (int)((audio_buffering_seconds * rate) / space) + 1;
+    if (fill_to < 3) {
+      fill_to = 3;
+    }
+    
+    while ((_loops_completed < _playing_loops) &&
+           (((int)(_stream_queued.size())) < fill_to)) {
+      int loop_index = _loops_completed;
+      double time_offset = cursor->tell();
+      int samples = read_stream_data(65536, data);
+      if (samples == 0) {
+        break;
+      }
+      ALuint buffer = make_buffer(samples, channels, rate, data);
+      if (_manager == 0) return;
+      queue_buffer(buffer, loop_index, time_offset);
+      if (_manager == 0) return;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::set_time
+//       Access: public
+//  Description: The next time you call play, the sound will
+//               start from the specified offset.
+////////////////////////////////////////////////////////////////////
+void OpenALAudioSound::
+set_time(float time) {
+  _start_time = time;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -302,18 +539,27 @@ set_time(float time) {
 ////////////////////////////////////////////////////////////////////
 float OpenALAudioSound::
 get_time() const {
-  float time;
-  
-  if (_source) {
-    _manager->make_current();
-    alGetError(); // clear errors
-    alGetSourcef(_source,AL_SEC_OFFSET,&time);
-    al_audio_errcheck("alGetSourcef(_source,AL_SEC_OFFSET)");
-  } else {
+  if (_manager == 0) {
     return 0.0;
   }
-  
-  return time;
+  return _current_time;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioSound::cache_time
+//       Access: Private
+//  Description: Updates the current_time field of a playing sound.
+////////////////////////////////////////////////////////////////////
+void OpenALAudioSound::
+cache_time(double rtc) {
+  assert(_source != 0);
+  double t=get_calibrated_clock(rtc);
+  double max = _length * _playing_loops;
+  if (t >= max) {
+    _current_time = _length;
+  } else {
+    _current_time = fmod(t, _length);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -324,17 +570,11 @@ get_time() const {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
 set_volume(float volume) {
-  openal_audio_debug("set_volume(volume="<<volume<<")");
-
-  //nassertv(_source);
-  
   _volume=volume;
 
   if (_source) {
     volume*=_manager->get_volume();
-    
     _manager->make_current();
-
     alGetError(); // clear errors
     alSourcef(_source,AL_GAIN,volume);
     al_audio_errcheck("alSourcef(_source,AL_GAIN)");
@@ -348,7 +588,6 @@ set_volume(float volume) {
 ////////////////////////////////////////////////////////////////////
 float OpenALAudioSound::
 get_volume() const {
-  openal_audio_debug("get_volume() returning "<<_volume);
   return _volume;
 }
 
@@ -384,21 +623,7 @@ get_balance() const {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
 set_play_rate(float play_rate) {
-  openal_audio_debug("set_play_rate(play_rate="<<play_rate<<")");
-
-  //nassertv(_source);
-  
-  _play_rate=play_rate;
-
-  if (_source) {
-    play_rate*=_manager->get_play_rate();
-    
-    _manager->make_current();
-
-    alGetError(); // clear errors
-    alSourcef(_source,AL_PITCH,play_rate);
-    al_audio_errcheck("alSourcef(_source,AL_PITCH)");
-  }
+  _play_rate = play_rate;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -408,7 +633,6 @@ set_play_rate(float play_rate) {
 ////////////////////////////////////////////////////////////////////
 float OpenALAudioSound::
 get_play_rate() const {
-  openal_audio_debug("get_play_rate() returning "<<_play_rate);
   return _play_rate;
 }
 
@@ -419,23 +643,24 @@ get_play_rate() const {
 ////////////////////////////////////////////////////////////////////
 float OpenALAudioSound::
 length() const {
-  return _sample->_length;
+  return _length;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: OpenALAudioSound::set_3d_attributes
 //       Access: public
 //  Description: Set position and velocity of this sound
-//        NOW LISTEN UP!!! THIS IS IMPORTANT!
-//        Both Panda3D and OpenAL use a right handed coordinate system.
-//        But there is a major difference!
-//        In Panda3D the Y-Axis is going into the Screen and the Z-Axis is going up.
-//        In OpenAL the Y-Axis is going up and the Z-Axis is coming out of the screen.
-//        The solution is simple, we just flip the Y and Z axis and negate the Z, as we move coordinates
-//        from Panda to OpenAL and back.
-//        What does did mean to average Panda user?  Nothing, they shouldn't notice anyway.
-//        But if you decide to do any 3D audio work in here you have to keep it in mind.
-//        I told you, so you can't say I didn't.
+//
+//               Both Panda3D and OpenAL use a right handed
+//               coordinate system.  However, in Panda3D the
+//               Y-Axis is going into the Screen and the
+//               Z-Axis is going up.  In OpenAL the Y-Axis is
+//               going up and the Z-Axis is coming out of
+//               the screen.
+//
+//               The solution is simple, we just flip the Y
+//               and Z axis and negate the Z, as we move
+//               coordinates from Panda to OpenAL and back.
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
 set_3d_attributes(float px, float py, float pz, float vx, float vy, float vz) {
@@ -572,14 +797,11 @@ get_3d_drop_off_factor() const {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
 set_active(bool active) {
-  openal_audio_debug("set_active(active="<<active<<")");
   if (_active!=active) {
     _active=active;
     if (_active) {
       // ...activate the sound.
-      if (_paused
-          &&
-          _loop_count==0) {
+      if (_paused && _loop_count==0) {
         // ...this sound was looping when it was paused.
         _paused=false;
         play();
@@ -605,7 +827,6 @@ set_active(bool active) {
 ////////////////////////////////////////////////////////////////////
 bool OpenALAudioSound::
 get_active() const {
-  openal_audio_debug("get_active() returning "<<_active);
   return _active;
 }
 
@@ -616,7 +837,6 @@ get_active() const {
 ////////////////////////////////////////////////////////////////////
 void OpenALAudioSound::
 set_finished_event(const string& event) {
-  openal_audio_debug("set_finished_event(event="<<event<<")");
   _finished_event = event;
 }
 
@@ -627,7 +847,6 @@ set_finished_event(const string& event) {
 ////////////////////////////////////////////////////////////////////
 const string& OpenALAudioSound::
 get_finished_event() const {
-  openal_audio_debug("get_finished_event() returning "<<_finished_event);
   return _finished_event;
 }
 
@@ -638,33 +857,29 @@ get_finished_event() const {
 ////////////////////////////////////////////////////////////////////
 const string& OpenALAudioSound::
 get_name() const {
-  return _path;
+  return _basename;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: OpenALAudioSound::status
 //       Access: public
 //  Description: Get status of the sound.
+//
+//               This returns the status as of the
+//               last AudioManager::update.
 ////////////////////////////////////////////////////////////////////
 AudioSound::SoundStatus OpenALAudioSound::
 status() const {
-  ALenum status;
-  
   if (_source==0) {
-    //return AudioSound::BAD;
     return AudioSound::READY;
   }
   
   _manager->make_current();
   
-  alGetError(); // clear errors
-  alGetSourcei(_source,AL_SOURCE_STATE,&status);
-  al_audio_errcheck("alGetSourcei(_source,AL_SOURCE_STATE)");
-  
-  if (status == AL_PLAYING/* || status == AL_PAUSED*/) {
-    return AudioSound::PLAYING;
-  } else {
+  if (_stream_queued.size() == 0) {
     return AudioSound::READY;
+  } else {
+    return AudioSound::PLAYING;
   }
 }
 

+ 54 - 20
panda/src/audiotraits/openalAudioSound.h

@@ -28,6 +28,7 @@
 
 #include "audioSound.h"
 #include "movieAudioCursor.h"
+#include "trueClock.h"
 
 #include <al.h>
 #include <alc.h>
@@ -89,7 +90,7 @@ public:
   const string& get_finished_event() const;
 
   const string &get_name() const;
-            
+  
   // return: playing time in seconds.
   float length() const;
 
@@ -114,18 +115,39 @@ public:
 
 private:
   OpenALAudioSound(OpenALAudioManager* manager, 
-                   const Filename &path,
-                   PT(MovieAudioCursor) cursor,
-                   OpenALAudioManager::SoundData *sd,
+                   MovieAudio *movie,
                    bool positional);
+  INLINE void   set_calibrated_clock(double rtc, double t, double playrate);
+  INLINE double get_calibrated_clock(double rtc) const;
+  void          correct_calibrated_clock(double rtc, double t);
+  void          cache_time(double rtc);
   void cleanup();
-
+  void restart_stalled_audio();
+  void delete_queued_buffers();
+  ALuint make_buffer(int samples, int channels, int rate, unsigned char *data);
+  void queue_buffer(ALuint buffer, int loop_index, double time_offset);
+  int  read_stream_data(int bytelen, unsigned char *data);
+  void pull_used_buffers();
+  void push_fresh_buffers();
+  INLINE void require_sound_data();
+  INLINE void release_sound_data();
+  
 private:
+  
+  PT(MovieAudio) _movie;
+  OpenALAudioManager::SoundData *_sd;
 
-  // A Sound can have a sample or a stream, but not both.
-  OpenALAudioManager::SoundData *_sample;
-  PT(MovieAudioCursor) _stream;
-  ALuint _stream_buffers[3];
+  struct QueuedBuffer {
+    ALuint _buffer;
+    int    _loop_index;
+    double _time_offset;
+  };
+
+  int    _playing_loops;
+  float  _playing_rate;
+  
+  pdeque<QueuedBuffer> _stream_queued;
+  int                  _loops_completed;
   
   ALuint _source;
   PT(OpenALAudioManager) _manager;
@@ -133,7 +155,7 @@ private:
   float _volume; // 0..1.0
   float _balance; // -1..1
   float _play_rate; // 0..1.0
-
+  
   bool _positional;
   ALfloat _location[3];
   ALfloat _velocity[3];
@@ -141,28 +163,40 @@ private:
   float _min_dist;
   float _max_dist;
   float _drop_off_factor;
-
-  mutable float _length; // in seconds.
-  unsigned long _loop_count;
-
+  
+  double _length;
+  int    _loop_count;
+
+  // The calibrated clock is initialized when the
+  // sound starts playing, and is periodically corrected
+  // thereafter.  
+  double _calibrated_clock_base;
+  double _calibrated_clock_scale;
+  double _calibrated_clock_decavg;
+  
+  // The start_time field affects the next call to play.
+  double _start_time;
+
+  // The current_time field is updated every frame
+  // during the AudioManager update.  Updates need
+  // to be atomic, because get_time can be called
+  // in the cull thread.
+  float  _current_time;
+  
   // This is the string that throw_event() will throw
   // when the sound finishes playing.  It is not triggered
   // when the sound is stopped with stop().
   string _finished_event;
   
-  Filename _path;
+  Filename _basename;
 
   // _active is for things like a 'turn off sound effects' in
   // a preferences pannel.
   // _active is not about whether a sound is currently playing.
   // Use status() for info on whether the sound is playing.
   bool _active;
-
-  // _paused is not like the Pause button on a cd/dvd player.
-  // It is used as a flag to say that the sound was looping when
-  // itwas set inactive.
   bool _paused;
-
+  
  public:
   static TypeHandle get_class_type() {
     return _type_handle;