// Filename: milesAudioManager.cxx
// Created by:  skyler (June 6, 2001)
// Prior system by: cary
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "milesAudioManager.h"

#ifdef HAVE_RAD_MSS //[

#include "milesAudioSound.h"
#include "milesAudioSample.h"
#include "milesAudioStream.h"
#include "globalMilesManager.h"
#include "config_audio.h"
#include "config_util.h"
#include "config_express.h"
#include "virtualFileSystem.h"
#include "nullAudioSound.h"
#include "string_utils.h"
#include "mutexHolder.h"
#include "reMutexHolder.h"

#include <algorithm>


TypeHandle MilesAudioManager::_type_handle;

PT(AudioManager) Create_AudioManager() {
  audio_debug("Create_AudioManager() Miles.");
  return new MilesAudioManager();
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::Constructor
//       Access: Public
//  Description: Create an audio manager.  This may open the Miles
//               sound system if there were no other MilesAudioManager
//               instances.  Subsequent managers may use the same
//               Miles resources.
////////////////////////////////////////////////////////////////////
MilesAudioManager::
MilesAudioManager() : 
  _lock("MilesAudioManager::_lock"),
  _streams_lock("MilesAudioManager::_streams_lock"),
  _streams_cvar(_streams_lock)
{
  audio_debug("MilesAudioManager::MilesAudioManager(), this = " 
              << (void *)this);
  GlobalMilesManager::get_global_ptr()->add_manager(this);
  audio_debug("  audio_active="<<audio_active);
  audio_debug("  audio_volume="<<audio_volume);
  _cleanup_required = true;
  _active = audio_active;
  _volume = audio_volume;
  _play_rate = 1.0f;
  _cache_limit = audio_cache_limit;
  _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
  // silenced on exit, especially a sudden exit triggered by a Python
  // exception.  But that causes problems because Miles itself also
  // hangs a force-shutdown function on atexit(), and you can't call
  // AIL_cleanup() twice--that results in a crash.

  // Nowadays, we provide the AudioManager::shutdown() method instead,
  // which allows the app to force all sounds to stop cleanly before
  // we get to the atexit() stack.  In Python, we guarantee that this
  // method will be called in the sys.exitfunc chain.
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::Destructor
//       Access: Public, Virtual
//  Description: Clean up this audio manager and possibly release
//               the Miles resources that are reserved by the 
//               application (the later happens if this is the last
//               active manager).
////////////////////////////////////////////////////////////////////
MilesAudioManager::
~MilesAudioManager() {
  audio_debug("MilesAudioManager::~MilesAudioManager(), this = " 
              << (void *)this);
  cleanup();
  GlobalMilesManager::get_global_ptr()->remove_manager(this);

  audio_debug("MilesAudioManager::~MilesAudioManager() finished");
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::shutdown
//       Access: Public, Virtual
//  Description: Call this at exit time to shut down the audio system.
//               This will invalidate all currently-active
//               AudioManagers and AudioSounds in the system.  If you
//               change your mind and want to play sounds again, you
//               will have to recreate all of these objects.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
shutdown() {
  audio_debug("shutdown() started");
  GlobalMilesManager::get_global_ptr()->cleanup();
  audio_debug("shutdown() finished");
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::is_valid
//       Access: Public, Virtual
//  Description: This is mostly for debugging, but it it could be
//               used to detect errors in a release build if you
//               don't mind the cpu cost.
////////////////////////////////////////////////////////////////////
bool MilesAudioManager::
is_valid() {
  ReMutexHolder holder(_lock);
  return do_is_valid();
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::get_sound
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
PT(AudioSound) MilesAudioManager::
get_sound(const string &file_name, bool, int) {
  ReMutexHolder holder(_lock);
  audio_debug("MilesAudioManager::get_sound(file_name=\""<<file_name<<"\")");

  if (!do_is_valid()) {
     audio_debug("invalid MilesAudioManager returning NullSound");
     return get_null_sound();
  }

  Filename path = file_name;

  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
  vfs->resolve_filename(path, get_sound_path()) ||
    vfs->resolve_filename(path, get_model_path());
  audio_debug("  resolved file_name is '"<<path<<"'");

  PT(SoundData) sd;
  // Get the sound, either from the cache or from a loader:
  SoundMap::const_iterator si=_sounds.find(path);
  if (si != _sounds.end()) {
    // ...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);
    if (sd != (SoundData *)NULL) {
      while (_sounds.size() >= (unsigned int)_cache_limit) {
        uncache_a_sound();
      }
      // Put it in the pool:
      // 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));
      if (!ib.second) {
        // The insert failed.
        audio_debug("  failed map insert of "<<path);
        nassertr(do_is_valid(), NULL);
        return get_null_sound();
      }
      // Set si, so that we can get a reference to the path
      // for the MilesAudioSound.
      si=ib.first;
    }
  }
  // Create an AudioSound from the sound:
  PT(AudioSound) audioSound;

  if (sd != (SoundData *)NULL) {
    most_recently_used((*si).first);
    if (sd->_file_type == AILFILETYPE_MIDI ||
        sd->_file_type == AILFILETYPE_XMIDI ||
        sd->_file_type == AILFILETYPE_XMIDI_DLS ||
        sd->_file_type == AILFILETYPE_XMIDI_MLS) {
      // MIDI file.
      audioSound = new MilesAudioSequence(this, sd, file_name);

    } else if (!sd->_raw_data.empty()) {
      // WAV or MP3 file preloaded into memory.
      audioSound = new MilesAudioSample(this, sd, file_name);

    } else {
      // WAV or MP3 file streamed from disk.
      audioSound = new MilesAudioStream(this, file_name, path);
    }

    audioSound->set_active(_active);

    bool inserted = _sounds_on_loan.insert((MilesAudioSound *)audioSound.p()).second;
    nassertr(inserted, audioSound);

    _hasMidiSounds |= (file_name.find(".mid")!=string::npos);
  } else {
    // Couldn't load the file; just return a NullAudioSound.
    audioSound = new NullAudioSound;
  }

  audio_debug("  returning 0x" << (void*)audioSound);
  nassertr(do_is_valid(), NULL);
  return audioSound;
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::get_sound
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
PT(AudioSound) MilesAudioManager::
get_sound(MovieAudio *sound, bool, int) {
  nassert_raise("Miles audio manager does not support MovieAudio sources.");
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::uncache_sound
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
uncache_sound(const string &file_name) {
  audio_debug("MilesAudioManager::uncache_sound(file_name=\""
      <<file_name<<"\")");
  ReMutexHolder holder(_lock);
  nassertv(do_is_valid());
  Filename path = file_name;

  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
  vfs->resolve_filename(path, get_sound_path()) ||
    vfs->resolve_filename(path, get_model_path());

  audio_debug("  path=\""<<path<<"\"");
  SoundMap::iterator i = _sounds.find(path);
  if (i != _sounds.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);
  }
  nassertv(do_is_valid());
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::clear_cache
//       Access: Public, Virtual
//  Description: Clear out the sound cache.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
clear_cache() {
  audio_debug("MilesAudioManager::clear_cache()");
  ReMutexHolder holder(_lock);
  do_clear_cache();
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::set_cache_limit
//       Access: Public, Virtual
//  Description: Set the number of sounds that the cache can hold.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
set_cache_limit(unsigned int count) {
  ReMutexHolder holder(_lock);

  audio_debug("MilesAudioManager::set_cache_limit(count="<<count<<")");
  nassertv(do_is_valid());
  while (_lru.size() > count) {
    uncache_a_sound();
  }
  _cache_limit=count;
  nassertv(do_is_valid());
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::get_cache_limit
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
unsigned int MilesAudioManager::
get_cache_limit() const {
  return _cache_limit;
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::set_volume
//       Access: Public, Virtual
//  Description: set the overall volume
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
set_volume(float volume) {
  audio_debug("MilesAudioManager::set_volume(volume="<<volume<<")");
  ReMutexHolder holder(_lock);
  if (_volume != volume) {
    _volume = volume;
    // Tell our AudioSounds to adjust:
    AudioSet::iterator i = _sounds_on_loan.begin();
    for (; i!=_sounds_on_loan.end(); ++i) {
      (*i)->set_volume((*i)->get_volume());
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::get_volume
//       Access: Public, Virtual
//  Description: get the overall volume
////////////////////////////////////////////////////////////////////
float MilesAudioManager::
get_volume() const {
  return _volume;
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::set_play_rate
//       Access: Public
//  Description: set the overall play rate
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
set_play_rate(float play_rate) {
  audio_debug("MilesAudioManager::set_play_rate(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) {
      (*i)->set_play_rate((*i)->get_play_rate());
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::get_play_rate
//       Access: Public
//  Description: get the overall speed/pitch/play rate
////////////////////////////////////////////////////////////////////
float MilesAudioManager::
get_play_rate() const {
  return _play_rate;
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::set_active
//       Access: Public, Virtual
//  Description: turn on/off
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
set_active(bool active) {
  audio_debug("MilesAudioManager::set_active(flag="<<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) {
      (*i)->set_active(_active);
    }

    if ((!_active) && _hasMidiSounds) {
      GlobalMilesManager::get_global_ptr()->force_midi_reset();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::get_active
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
bool MilesAudioManager::
get_active() const {
  return _active;
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::set_concurrent_sound_limit
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
set_concurrent_sound_limit(unsigned int limit) {
  ReMutexHolder holder(_lock);
  _concurrent_sound_limit = limit;
  do_reduce_sounds_playing_to(_concurrent_sound_limit);
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::get_concurrent_sound_limit
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
unsigned int MilesAudioManager::
get_concurrent_sound_limit() const {
  return _concurrent_sound_limit;
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::reduce_sounds_playing_to
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
reduce_sounds_playing_to(unsigned int count) {
  ReMutexHolder holder(_lock);
  do_reduce_sounds_playing_to(count);
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::stop_all_sounds
//       Access: Public, Virtual
//  Description: Stop playback on all sounds managed by this manager.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
stop_all_sounds() {
  audio_debug("MilesAudioManager::stop_all_sounds()");
  reduce_sounds_playing_to(0);
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::update()
//       Access: Public, Virtual
//  Description: Must be called every frame.  Failure to call this
//               every frame could cause problems for some audio
//               managers.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
update() {
  {
    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 (_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();
      }
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::release_sound
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
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);

  audio_debug("MilesAudioManager::release_sound() finished");
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::cleanup
//       Access: Public
//  Description: Shuts down the audio manager and releases any
//               resources associated with it.  Also cleans up all
//               AudioSounds created via the manager.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
cleanup() {
  audio_debug("MilesAudioManager::cleanup(), this = " << (void *)this
              << ", _cleanup_required = " << _cleanup_required);
  ReMutexHolder holder(_lock);
  if (!_cleanup_required) {
    return;
  }

  // Be sure to cleanup associated sounds before cleaning up the manager:
  AudioSet::iterator ai;
  for (ai = _sounds_on_loan.begin(); ai != _sounds_on_loan.end(); ++ai) {
    (*ai)->cleanup();
  }

  do_clear_cache();

  // Now stop the thread, if it has been started.
  if (!_stream_thread.is_null()) {
    milesAudio_cat.info()
      << "Stopping audio streaming thread.\n";
    PT(StreamThread) old_thread;
    {
      MutexHolder holder(_streams_lock);
      nassertv(!_stream_thread.is_null());
      _stream_thread->_keep_running = false;
      _streams_cvar.signal();
      old_thread = _stream_thread;
      _stream_thread.clear();
    }
    old_thread->join();
  }

  _cleanup_required = false;
  audio_debug("MilesAudioManager::cleanup() finished");
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::output
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
output(ostream &out) const {
  ReMutexHolder holder(_lock);
  out << get_type() << ": " << _sounds_playing.size()
      << " / " << _sounds_on_loan.size() << " sounds playing / total"; 
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::write
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
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";
  }

  size_t total_preload = 0;
  size_t num_preloaded = 0;
  SoundMap::const_iterator si;
  for (si = _sounds.begin(); si != _sounds.end(); ++si) {
    if (!(*si).second->_raw_data.empty()) {
      ++num_preloaded;
      total_preload += (*si).second->_raw_data.size();
    }
  }
  out << num_preloaded << " of " << _sounds.size() << " sounds preloaded, size used is " << (total_preload + 1023) / 1024 << "K\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
//       Access: Private
//  Description: Adds the indicated stream to the list of streams to
//               be serviced by a Panda sub-thread.  This is in lieu
//               of Miles' auto-service-stream mechanism.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
start_service_stream(HSTREAM stream) {
  MutexHolder holder(_streams_lock);
  nassertv(find(_streams.begin(), _streams.end(), stream) == _streams.end());
  _streams.push_back(stream);
  _streams_cvar.signal();

  if (_stream_thread.is_null() && Thread::is_threading_supported()) {
    milesAudio_cat.info()
      << "Starting audio streaming thread.\n";
    _stream_thread = new StreamThread(this);
    _stream_thread->start(TP_low, true);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::stop_service_stream
//       Access: Private
//  Description: Removes the indicated stream from the list of streams
//               to be serviced by a Panda sub-thread.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
stop_service_stream(HSTREAM stream) {
  MutexHolder holder(_streams_lock);
  Streams::iterator si = find(_streams.begin(), _streams.end(), stream);
  if (si != _streams.end()) {
    _streams.erase(si);
  }
}
  

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::most_recently_used
//       Access: Private
//  Description: Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
most_recently_used(const string &path) {
  audio_debug("MilesAudioManager::most_recently_used(path=\""
      <<path<<"\")");
  LRU::iterator i=find(_lru.begin(), _lru.end(), &path);
  if (i != _lru.end()) {
    _lru.erase(i);
  }
  // At this point, path should not exist in the _lru:
  assert(find(_lru.begin(), _lru.end(), &path) == _lru.end());
  _lru.push_back(&path);
  nassertv(do_is_valid());
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::uncache_a_sound
//       Access: Private
//  Description: Assumes the lock is already held.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
uncache_a_sound() {
  audio_debug("MilesAudioManager::uncache_a_sound()");
  nassertv(do_is_valid());
  // uncache least recently used:
  assert(_lru.size()>0);
  LRU::reference path=_lru.front();
  SoundMap::iterator i = _sounds.find(*path);
  assert(i != _sounds.end());
  _lru.pop_front();

  if (i != _sounds.end()) {
    audio_debug("  uncaching \""<<i->first<<"\"");
    _sounds.erase(i);
  }
  nassertv(do_is_valid());
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::starting_sound
//       Access: Private
//  Description: Inform the manager that a sound is about to play.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
starting_sound(MilesAudioSound *audio) {
  ReMutexHolder holder(_lock);
  if (_concurrent_sound_limit) {
    do_reduce_sounds_playing_to(_concurrent_sound_limit);
  }
  _sounds_playing.insert(audio);
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::stopping_sound
//       Access: Private
//  Description: Inform the manager that a sound is finished or 
//               someone called stop on the sound (this should not
//               be called if a sound is only paused).
////////////////////////////////////////////////////////////////////
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();
  }
}


////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::load
//       Access: Private
//  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) {
  PT(SoundData) sd = new SoundData;

  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
  PT(VirtualFile) file = vfs->get_file(file_name);
  if (file == (VirtualFile *)NULL) {
    milesAudio_cat.warning()
      << "No such file: " << file_name << "\n";
    return NULL;
  }

  if (file->get_file_size() == 0) {
    milesAudio_cat.warning()
      << "File " << file_name << " is empty\n";
    return NULL;
  }

  sd->_basename = file_name.get_basename();

  string extension = sd->_basename.get_extension();
  if (extension == "pz") {
    extension = Filename(sd->_basename.get_basename_wo_extension()).get_extension();
  }
  bool is_midi_file = (downcase(extension) == "mid");

  if ((miles_audio_preload_threshold == -1 || file->get_file_size() < (off_t)miles_audio_preload_threshold) ||
      is_midi_file) {
    // If the file is sufficiently small, we'll preload it into
    // memory.  MIDI files cannot be streamed, so we always preload
    // them, regardless of size.

    if (!file->read_file(sd->_raw_data, true)) {
      milesAudio_cat.warning()
        << "Unable to read " << file_name << "\n";
      return NULL;
    }

    sd->_file_type = 
      AIL_file_type(&sd->_raw_data[0], sd->_raw_data.size());

    if (sd->_file_type == AILFILETYPE_MIDI) {
      // A standard MIDI file.  We have to convert this to XMIDI for
      // Miles.
      void *xmi;
      U32 xmi_size;
      if (AIL_MIDI_to_XMI(&sd->_raw_data[0], sd->_raw_data.size(),
                          &xmi, &xmi_size, 0)) {
        audio_debug("converted " << sd->_basename << " from standard MIDI ("
                    << sd->_raw_data.size() << " bytes) to XMIDI ("
                    << xmi_size << " bytes)");

        // Copy the data to our own buffer and free the
        // Miles-allocated data.
        sd->_raw_data.clear();
        sd->_raw_data.insert(sd->_raw_data.end(), 
                             (unsigned char *)xmi, (unsigned char *)xmi + xmi_size);
        AIL_mem_free_lock(xmi);
        sd->_file_type = AILFILETYPE_XMIDI;
      } else {
        milesAudio_cat.warning()
          << "Could not convert " << sd->_basename << " to XMIDI.\n";
      }
    }
    
    bool expand_to_wav = false;

    if (sd->_file_type != AILFILETYPE_MPEG_L3_AUDIO) {
      audio_debug(sd->_basename << " is not an mp3 file.");
    } else if ((int)sd->_raw_data.size() >= miles_audio_expand_mp3_threshold) {
      audio_debug(sd->_basename << " is too large to expand in-memory.");
    } else {
      audio_debug(sd->_basename << " will be expanded in-memory.");
      expand_to_wav = true;
    }
    
    if (expand_to_wav) {
      // Now convert the file to WAV format in-memory.  This is useful
      // to work around seek and length problems associated with
      // variable bit-rate MP3 encoding.
      void *wav_data;
      U32 wav_data_size;
      if (AIL_decompress_ASI(&sd->_raw_data[0], sd->_raw_data.size(),
                             sd->_basename.c_str(), &wav_data, &wav_data_size,
                             NULL)) {
        audio_debug("expanded " << sd->_basename << " from " << sd->_raw_data.size()
                    << " bytes to " << wav_data_size << " bytes.");
        
        if (wav_data_size != 0) {
          // Now copy the memory into our own buffers, and free the
          // Miles-allocated memory.
          sd->_raw_data.clear();
          sd->_raw_data.insert(sd->_raw_data.end(),
                               (unsigned char *)wav_data, (unsigned char *)wav_data + wav_data_size);
          sd->_file_type = AILFILETYPE_PCM_WAV;
          sd->_basename.set_extension("wav");
        }
        AIL_mem_free_lock(wav_data);
        
      } else {
        audio_debug("unable to expand " << sd->_basename);
      }
    }

  } else {
    // If the file is large, we'll stream it from disk instead of
    // preloading it.  This means we don't need to load any data at
    // this point.
  }

  return sd;
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::thread_main
//       Access: Private
//  Description: Called to service the streaming audio channels
//               currently playing on the audio manager.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
thread_main(volatile bool &keep_running) {
  MutexHolder holder(_streams_lock);

  while (keep_running) {
    if (_streams.empty()) {
      // If there are no streams to service, block on the condition
      // variable.
      _streams_cvar.wait();
    } else {
      do_service_streams();
    }

    // Now yield to be polite to the main application.
    _streams_lock.release();
    Thread::force_yield();
    _streams_lock.lock();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::do_service_streams
//       Access: Private
//  Description: Internal function to service all the streams.
//               Assumes _streams_lock is already held.
////////////////////////////////////////////////////////////////////
void MilesAudioManager::
do_service_streams() {
  size_t i = 0;
  while (i < _streams.size()) {
    HSTREAM stream = _streams[i];
    
    // We must release the lock while we are servicing stream i.
    _streams_lock.release();
    AIL_service_stream(stream, 0);
    Thread::consider_yield();
    _streams_lock.lock();
    
    ++i;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::StreamThread::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
MilesAudioManager::StreamThread::
StreamThread(MilesAudioManager *mgr) : 
  Thread("StreamThread", "StreamThread"),
  _mgr(mgr) 
{
  _keep_running = true;
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::StreamThread::thread_main
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void MilesAudioManager::StreamThread::
thread_main() {
  _mgr->thread_main(_keep_running);
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::SoundData::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
MilesAudioManager::SoundData::
SoundData() :
  _raw_data(MilesAudioManager::get_class_type()),
  _has_length(false)
{
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::SoundData::Destructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
MilesAudioManager::SoundData::
~SoundData() {
}

////////////////////////////////////////////////////////////////////
//     Function: MilesAudioManager::SoundData::Destructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
float MilesAudioManager::SoundData::
get_length() {
  if (!_has_length) {
    // Time to determine the length of the file.
    
    if (_raw_data.empty()) {
      _length = 0.0f;
      _has_length = true;

    } else if (_file_type == AILFILETYPE_MPEG_L3_AUDIO) {
      // If it's an mp3 file, we have to calculate its length by
      // walking through all of its frames.
      audio_debug("Computing length of mp3 file " << _basename);
      
      MP3_INFO info;
      AIL_inspect_MP3(&info, &_raw_data[0], _raw_data.size());
      _length = 0.0f;
      while (AIL_enumerate_MP3_frames(&info)) {
        _length += info.data_size * 8.0f / info.bit_rate;
      }
      _has_length = true;

    } else if (_file_type == AILFILETYPE_PCM_WAV ||
               _file_type == AILFILETYPE_ADPCM_WAV ||
               _file_type == AILFILETYPE_OTHER_ASI_WAV) {
      audio_debug("Getting length of wav file " << _basename);

      AILSOUNDINFO info;
      if (AIL_WAV_info(&_raw_data[0], &info)) {
        _length = (float)info.samples / (float)info.rate;
        audio_debug(info.samples << " samples at " << info.rate
                    << "; length is " << _length << " seconds.");
        _has_length = true;
      }
    }
  }

  return _length;
}

#endif //]
