diff --git a/panda/src/audio/audioManager.cxx b/panda/src/audio/audioManager.cxx index 60ab6ba293..d978f3db24 100644 --- a/panda/src/audio/audioManager.cxx +++ b/panda/src/audio/audioManager.cxx @@ -209,7 +209,7 @@ setSpeakerSetup(SpeakerModeCategory cat) { //////////////////////////////////////////////////////////////////// // Function: AudioManager::update() -// Access: Published +// Access: Published, Virtual // Description: Must be called every frame. Failure to call this // every frame could cause problems for some audio // managers. @@ -296,7 +296,7 @@ float AudioManager::audio_3d_get_drop_off_factor() const { //////////////////////////////////////////////////////////////////// // Function: AudioManager::get_dls_pathname -// Access: Public +// Access: Published, Static // Description: Returns the full pathname to the DLS file, as // specified by the Config.prc file, or the default for // the current OS if appropriate. Returns empty string @@ -337,3 +337,23 @@ get_dls_pathname() { return Filename(); #endif } + +//////////////////////////////////////////////////////////////////// +// Function: AudioManager::output +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void AudioManager:: +output(ostream &out) const { + out << get_type(); +} + +//////////////////////////////////////////////////////////////////// +// Function: AudioManager::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void AudioManager:: +write(ostream &out) const { + out << (*this) << "\n"; +} diff --git a/panda/src/audio/audioManager.h b/panda/src/audio/audioManager.h index 2c2cc56478..52c121ee15 100644 --- a/panda/src/audio/audioManager.h +++ b/panda/src/audio/audioManager.h @@ -216,7 +216,10 @@ PUBLISHED: virtual void audio_3d_set_drop_off_factor(float factor); virtual float audio_3d_get_drop_off_factor() const; - Filename get_dls_pathname(); + static Filename get_dls_pathname(); + + virtual void output(ostream &out) const; + virtual void write(ostream &out) const; public: static void register_AudioManager_creator(Create_AudioManager_proc* proc); @@ -251,4 +254,10 @@ private: static TypeHandle _type_handle; }; +inline ostream & +operator << (ostream &out, const AudioManager &mgr) { + mgr.output(out); + return out; +} + #endif /* __AUDIO_MANAGER_H__ */ diff --git a/panda/src/audio/audioSound.cxx b/panda/src/audio/audioSound.cxx index a7f8cdbd1d..e979ad0324 100644 --- a/panda/src/audio/audioSound.cxx +++ b/panda/src/audio/audioSound.cxx @@ -74,7 +74,7 @@ get_3d_max_distance() const { } //////////////////////////////////////////////////////////////////// -// Function: AudioManager::add_dsp +// Function: AudioSound::add_dsp // Access: Published // Description: //////////////////////////////////////////////////////////////////// @@ -85,7 +85,7 @@ add_dsp(PT(AudioDSP) x) { } //////////////////////////////////////////////////////////////////// -// Function: AudioManager::remove_dsp +// Function: AudioSound::remove_dsp // Access: Published // Description: //////////////////////////////////////////////////////////////////// @@ -97,7 +97,7 @@ remove_dsp(PT(AudioDSP) x) { //////////////////////////////////////////////////////////////////// -// Function: AudioManager::getSpeakerMix +// Function: AudioSound::getSpeakerMix // Access: Published // Description: //////////////////////////////////////////////////////////////////// @@ -108,7 +108,7 @@ get_speaker_mix(int speaker) { } //////////////////////////////////////////////////////////////////// -// Function: AudioManager::setSpeakerMix +// Function: AudioSound::setSpeakerMix // Access: Published // Description: //////////////////////////////////////////////////////////////////// @@ -119,7 +119,7 @@ set_speaker_mix(float frontleft, float frontright, float center, float sub, floa } //////////////////////////////////////////////////////////////////// -// Function: AudioManager::get_priority +// Function: AudioSound::get_priority // Access: Published // Description: //////////////////////////////////////////////////////////////////// @@ -130,7 +130,7 @@ get_priority() { } //////////////////////////////////////////////////////////////////// -// Function: AudioManager::set_priority +// Function: AudioSound::set_priority // Access: Published // Description: //////////////////////////////////////////////////////////////////// @@ -140,3 +140,42 @@ set_priority(int priority) { ; } +//////////////////////////////////////////////////////////////////// +// Function: AudioSound::output +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void AudioSound:: +output(ostream &out) const { + out << get_type() << " " << get_name() << " " << status(); +} + +//////////////////////////////////////////////////////////////////// +// Function: AudioSound::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void AudioSound:: +write(ostream &out) const { + out << (*this) << "\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: AudioSound::SoundStatus::output operator +// Description: +//////////////////////////////////////////////////////////////////// +ostream & +operator << (ostream &out, AudioSound::SoundStatus status) { + switch (status) { + case AudioSound::BAD: + return out << "BAD"; + + case AudioSound::READY: + return out << "READY"; + + case AudioSound::PLAYING: + return out << "PLAYING"; + } + + return out << "**invalid AudioSound::SoundStatus(" << (int)status << ")**"; +} diff --git a/panda/src/audio/audioSound.h b/panda/src/audio/audioSound.h index 9ce4065e34..21b62b7940 100644 --- a/panda/src/audio/audioSound.h +++ b/panda/src/audio/audioSound.h @@ -28,137 +28,148 @@ class AudioManager; - class EXPCL_PANDA_AUDIO AudioSound : public TypedReferenceCount { - PUBLISHED: - virtual ~AudioSound(); - - // For best compatability, set the loop_count, - // volume, and balance, prior to calling play(). You may - // set them while they're playing, but it's implementation - // specific whether you get the results. - // - Calling play() a second time on the same sound before it is - // finished will start the sound again (creating a skipping or - // stuttering effect). - virtual void play() = 0; - virtual void stop() = 0; - - // loop: false = play once; true = play forever. - // inits to false. - virtual void set_loop(bool loop=true) = 0; - virtual bool get_loop() const = 0; - - // loop_count: 0 = forever; 1 = play once; n = play n times. - // inits to 1. - virtual void set_loop_count(unsigned long loop_count=1) = 0; - virtual unsigned long get_loop_count() const = 0; - - // Control time position within the sound. - // This is similar (in concept) to the seek position within - // a file. - // time in seconds: 0 = beginning; length() = end. - // inits to 0.0. - // - Unlike the other get_* and set_* calls for a sound, the - // current time position will change while the sound is playing. - // To play the same sound from a time offset a second time, - // explicitly set the time position again. When looping, the - // second and later loops will start from the beginning of the - // sound. - // - If a sound is playing, calling get_time() repeatedly will - // return different results over time. e.g.: - // float percent_complete = s.get_time() / s.length(); - virtual void set_time(float start_time=0.0) = 0; - virtual float get_time() const = 0; - - // 0 = minimum; 1.0 = maximum. - // inits to 1.0. - virtual void set_volume(float volume=1.0) = 0; - virtual float get_volume() const = 0; - - // -1.0 is hard left - // 0.0 is centered - // 1.0 is hard right - // inits to 0.0. - virtual void set_balance(float balance_right=0.0) = 0; - virtual float get_balance() const = 0; - - // play_rate is any positive float value. - // inits to 1.0. - virtual void set_play_rate(float play_rate=1.0f) = 0; - virtual float get_play_rate() const = 0; +PUBLISHED: + virtual ~AudioSound(); + + // For best compatability, set the loop_count, + // volume, and balance, prior to calling play(). You may + // set them while they're playing, but it's implementation + // specific whether you get the results. + // - Calling play() a second time on the same sound before it is + // finished will start the sound again (creating a skipping or + // stuttering effect). + virtual void play() = 0; + virtual void stop() = 0; + + // loop: false = play once; true = play forever. + // inits to false. + virtual void set_loop(bool loop=true) = 0; + virtual bool get_loop() const = 0; + + // loop_count: 0 = forever; 1 = play once; n = play n times. + // inits to 1. + virtual void set_loop_count(unsigned long loop_count=1) = 0; + virtual unsigned long get_loop_count() const = 0; + + // Control time position within the sound. + // This is similar (in concept) to the seek position within + // a file. + // time in seconds: 0 = beginning; length() = end. + // inits to 0.0. + // - Unlike the other get_* and set_* calls for a sound, the + // current time position will change while the sound is playing. + // To play the same sound from a time offset a second time, + // explicitly set the time position again. When looping, the + // second and later loops will start from the beginning of the + // sound. + // - If a sound is playing, calling get_time() repeatedly will + // return different results over time. e.g.: + // float percent_complete = s.get_time() / s.length(); + virtual void set_time(float start_time=0.0) = 0; + virtual float get_time() const = 0; + + // 0 = minimum; 1.0 = maximum. + // inits to 1.0. + virtual void set_volume(float volume=1.0) = 0; + virtual float get_volume() const = 0; + + // -1.0 is hard left + // 0.0 is centered + // 1.0 is hard right + // inits to 0.0. + virtual void set_balance(float balance_right=0.0) = 0; + virtual float get_balance() const = 0; + + // play_rate is any positive float value. + // inits to 1.0. + virtual void set_play_rate(float play_rate=1.0f) = 0; + virtual float get_play_rate() const = 0; - // inits to manager's state. - virtual void set_active(bool flag=true) = 0; - virtual bool get_active() const = 0; + // inits to manager's state. + virtual void set_active(bool flag=true) = 0; + virtual bool get_active() const = 0; - // Set (or clear) the event that will be thrown when the sound - // finishes playing. To clear the event, pass an empty string. - virtual void set_finished_event(const string& event) = 0; - virtual const string& get_finished_event() const = 0; - - // There is no set_name(), this is intentional. - virtual const string& get_name() const = 0; - - // return: playing time in seconds. - virtual float length() const = 0; + // Set (or clear) the event that will be thrown when the sound + // finishes playing. To clear the event, pass an empty string. + virtual void set_finished_event(const string& event) = 0; + virtual const string& get_finished_event() const = 0; + + // There is no set_name(), this is intentional. + virtual const string& get_name() const = 0; + + // return: playing time in seconds. + virtual float length() const = 0; - // Controls the position of this sound's emitter. - // px, py and pz are the emitter's position. - // vx, vy and vz are the emitter's velocity in UNITS PER SECOND (default: meters). - virtual void set_3d_attributes(float px, float py, float pz, - float vx, float vy, float vz); - virtual void get_3d_attributes(float *px, float *py, float *pz, - float *vx, float *vy, float *vz); + // Controls the position of this sound's emitter. + // px, py and pz are the emitter's position. + // vx, vy and vz are the emitter's velocity in UNITS PER SECOND (default: meters). + virtual void set_3d_attributes(float px, float py, float pz, + float vx, float vy, float vz); + virtual void get_3d_attributes(float *px, float *py, float *pz, + float *vx, float *vy, float *vz); - // Controls the distance (in units) that this sound begins to fall off. - // Also affects the rate it falls off. - // Default is 1.0 - // Closer/Faster, <1.0 - // Farther/Slower, >1.0 - virtual void set_3d_min_distance(float dist); - virtual float get_3d_min_distance() const; + // Controls the distance (in units) that this sound begins to fall off. + // Also affects the rate it falls off. + // Default is 1.0 + // Closer/Faster, <1.0 + // Farther/Slower, >1.0 + virtual void set_3d_min_distance(float dist); + virtual float get_3d_min_distance() const; - // Controls the maximum distance (in units) that this sound stops falling off. - // The sound does not stop at that point, it just doesn't get any quieter. - // You should rarely need to adjust this. - // Default is 1000000000.0 - virtual void set_3d_max_distance(float dist); - virtual float get_3d_max_distance() const; + // Controls the maximum distance (in units) that this sound stops falling off. + // The sound does not stop at that point, it just doesn't get any quieter. + // You should rarely need to adjust this. + // Default is 1000000000.0 + virtual void set_3d_max_distance(float dist); + virtual float get_3d_max_distance() const; - virtual bool add_dsp(PT(AudioDSP) x); - virtual bool remove_dsp(PT(AudioDSP) x); + virtual bool add_dsp(PT(AudioDSP) x); + virtual bool remove_dsp(PT(AudioDSP) x); - virtual float get_speaker_mix(int speaker); - virtual void set_speaker_mix(float frontleft, float frontright, float center, float sub, float backleft, float backright, float sideleft, float sideright); + virtual float get_speaker_mix(int speaker); + virtual void set_speaker_mix(float frontleft, float frontright, float center, float sub, float backleft, float backright, float sideleft, float sideright); - virtual int get_priority(); - virtual void set_priority(int priority); + virtual int get_priority(); + virtual void set_priority(int priority); - enum SoundStatus { BAD, READY, PLAYING }; - virtual SoundStatus status() const = 0; + enum SoundStatus { BAD, READY, PLAYING }; + virtual SoundStatus status() const = 0; - protected: - AudioSound(); + virtual void output(ostream &out) const; + virtual void write(ostream &out) const; - friend class AudioManager; +protected: + AudioSound(); - public: - static TypeHandle get_class_type() { - return _type_handle; - } - static void init_type() { - TypedReferenceCount::init_type(); - register_type(_type_handle, "AudioSound", - TypedReferenceCount::get_class_type()); - } - virtual TypeHandle get_type() const { - return get_class_type(); - } - virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + friend class AudioManager; - private: - static TypeHandle _type_handle; +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedReferenceCount::init_type(); + register_type(_type_handle, "AudioSound", + TypedReferenceCount::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; }; +inline ostream & +operator << (ostream &out, const AudioSound &sound) { + sound.output(out); + return out; +} + +EXPCL_PANDA_AUDIO ostream & +operator << (ostream &out, AudioSound::SoundStatus status); + #endif /* __AUDIOSOUND_H__ */ diff --git a/panda/src/audiotraits/Sources.pp b/panda/src/audiotraits/Sources.pp index 69141ae653..7a84a138b7 100644 --- a/panda/src/audiotraits/Sources.pp +++ b/panda/src/audiotraits/Sources.pp @@ -8,7 +8,7 @@ #define BUILD_TARGET $[HAVE_RAD_MSS] #define USE_PACKAGES rad_mss #define BUILDING_DLL BUILDING_MILES_AUDIO - #define LOCAL_LIBS audio event + #define LOCAL_LIBS audio event pipeline #define WIN_SYS_LIBS $[WIN_SYS_LIBS] user32.lib advapi32.lib winmm.lib #define COMBINED_SOURCES $[TARGET]_composite1.cxx @@ -16,10 +16,16 @@ #define SOURCES \ config_milesAudio.h \ milesAudioManager.h \ - milesAudioSound.I milesAudioSound.h + milesAudioSound.I milesAudioSound.h \ + milesAudioSample.I milesAudioSample.h \ + milesAudioSequence.I milesAudioSequence.h \ + milesAudioStream.I milesAudioStream.h \ + globalMilesManager.I globalMilesManager.h #define INCLUDED_SOURCES \ - config_milesAudio.cxx milesAudioManager.cxx milesAudioSound.cxx + config_milesAudio.cxx milesAudioManager.cxx milesAudioSound.cxx \ + milesAudioStream.cxx globalMilesManager.cxx milesAudioSample.cxx \ + milesAudioSequence.cxx #end lib_target diff --git a/panda/src/audiotraits/config_milesAudio.cxx b/panda/src/audiotraits/config_milesAudio.cxx index 202da1e8c6..da4b7da2b7 100644 --- a/panda/src/audiotraits/config_milesAudio.cxx +++ b/panda/src/audiotraits/config_milesAudio.cxx @@ -22,6 +22,9 @@ #include "config_milesAudio.h" #include "milesAudioManager.h" #include "milesAudioSound.h" +#include "milesAudioSample.h" +#include "milesAudioSequence.h" +#include "milesAudioStream.h" #include "pandaSystem.h" #include "dconfig.h" @@ -42,16 +45,26 @@ ConfigVariableInt miles_audio_expand_mp3_threshold "work around problems with Miles being unable to correctly " "report the length of, or seek within, a variable bit-rate encoded " "MP3 file. Any MP3 file whose length in bytes is less than " - "this value will be expanded.")); + "this value will be expanded. This only applies to files " + "within the miles-audio-preload-threshold.")); -ConfigVariableInt miles_audio_calc_mp3_threshold -("miles-audio-calc-mp3-threshold", 1048576, - PRC_DESC("This is a second fallback for miles-audio-expand-mp3-threshold. " - "Any MP3 file whose length in bytes is less than this value " - "will have its length calculated on demand, by running through " - "the entire file first. This works around a Miles bug in " - "which variable bit-rate encoded MP3 files do not report an " - "accurate length.")); +ConfigVariableInt miles_audio_preload_threshold +("miles-audio-preload-threshold", -1, + PRC_DESC("This is the last Miles fallback size, and should be no smaller " + "than both miles-audio-expand-mp3-threshold and " + "miles-audio-calc-mp3-threshold. Files that are smaller " + "than this number of bytes will be preloaded and kept " + "resident in memory, while files that are this size or larger " + "will be streamed from disk. Set this to -1 to preload " + "every file.")); + +ConfigVariableBool miles_audio_panda_threads +("miles-audio-panda-threads", true, + PRC_DESC("Set this true to service Miles background audio via Panda's " + "threading interface, instead of Miles' built-in threading " + "interface. This gives Panda more control over the threading, " + "and ensures better lock protection within Panda. This has " + "no meaning unless Panda is compiled with thread support.")); //////////////////////////////////////////////////////////////////// // Function: init_libMilesAudio @@ -73,6 +86,9 @@ init_libMilesAudio() { MilesAudioManager::init_type(); MilesAudioSound::init_type(); + MilesAudioSample::init_type(); + MilesAudioSequence::init_type(); + MilesAudioStream::init_type(); PandaSystem *ps = PandaSystem::get_global_ptr(); ps->add_system("Miles"); diff --git a/panda/src/audiotraits/config_milesAudio.h b/panda/src/audiotraits/config_milesAudio.h index 0342d3dc42..cf7f381fbd 100644 --- a/panda/src/audiotraits/config_milesAudio.h +++ b/panda/src/audiotraits/config_milesAudio.h @@ -32,7 +32,8 @@ NotifyCategoryDecl(milesAudio, EXPCL_MILES_AUDIO, EXPTP_MILES_AUDIO); extern ConfigVariableBool miles_audio_force_midi_reset; extern ConfigVariableInt miles_audio_expand_mp3_threshold; -extern ConfigVariableInt miles_audio_calc_mp3_threshold; +extern ConfigVariableInt miles_audio_preload_threshold; +extern ConfigVariableBool miles_audio_panda_threads; extern EXPCL_MILES_AUDIO void init_libMilesAudio(); diff --git a/panda/src/audiotraits/globalMilesManager.I b/panda/src/audiotraits/globalMilesManager.I new file mode 100644 index 0000000000..eae6117964 --- /dev/null +++ b/panda/src/audiotraits/globalMilesManager.I @@ -0,0 +1,29 @@ +// Filename: globalMilesManager.I +// Created by: drose (30Jul07) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::is_open +// Access: Public +// Description: Returns true if the Miles Sound System is open (and +// active), false if it is not. +//////////////////////////////////////////////////////////////////// +INLINE bool GlobalMilesManager:: +is_open() const { + return _is_open; +} diff --git a/panda/src/audiotraits/globalMilesManager.cxx b/panda/src/audiotraits/globalMilesManager.cxx new file mode 100644 index 0000000000..f1215b8e26 --- /dev/null +++ b/panda/src/audiotraits/globalMilesManager.cxx @@ -0,0 +1,489 @@ +// Filename: globalMilesManager.cxx +// Created by: drose (26Jul07) +// +//////////////////////////////////////////////////////////////////// +// +// 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 "globalMilesManager.h" + +#ifdef HAVE_RAD_MSS //[ + +#include "mutexHolder.h" +#include "milesAudioManager.h" +#include "milesAudioSample.h" +#include "milesAudioSequence.h" + +GlobalMilesManager *GlobalMilesManager::_global_ptr; + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::Constructor +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +GlobalMilesManager:: +GlobalMilesManager() : + _managers_lock("GlobalMilesManager::_managers_lock"), + _samples_lock("GlobalMilesManager::_samples_lock"), + _sequences_lock("GlobalMilesManager::_sequences_lock") +{ + _digital_driver = 0; + _midi_driver = 0; + _dls_device = 0; + _dls_file = 0; + _is_open = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::add_manager +// Access: Public +// Description: Records a new MilesAudioManager in the world. This +// will open the Miles API when the first audio manager +// is added. +//////////////////////////////////////////////////////////////////// +void GlobalMilesManager:: +add_manager(MilesAudioManager *manager) { + MutexHolder holder(_managers_lock); + _managers.insert(manager); + if (!_is_open) { + open_api(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::remove_manager +// Access: Public +// Description: Records that a MilesAudioManager is destructing. +// This will clsoe the Miles API when the last audio +// manager is removed. +//////////////////////////////////////////////////////////////////// +void GlobalMilesManager:: +remove_manager(MilesAudioManager *manager) { + MutexHolder holder(_managers_lock); + _managers.erase(manager); + if (_managers.empty() && _is_open) { + close_api(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::cleanup +// Access: Public +// Description: Calls cleanup() on all MilesAudioManagers, to cause a +// clean shutdown. +//////////////////////////////////////////////////////////////////// +void GlobalMilesManager:: +cleanup() { + MutexHolder holder(_managers_lock); + Managers::iterator mi; + for (mi = _managers.begin(); mi != _managers.end(); ++mi) { + (*mi)->cleanup(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::get_sample +// Access: Public +// Description: Gets a sample handle from the global pool for the +// digital output device, to be used with the indicated +// AudioSound. +// +// If successful, sets the sample handle and the index +// (which should later be used to release the sample) +// and returns true. If unsuccessful (because there are +// no more available handles), returns false. +// +// This is a very limited resource; you should only get +// a sample just before playing a sound. +//////////////////////////////////////////////////////////////////// +bool GlobalMilesManager:: +get_sample(HSAMPLE &sample, size_t &index, MilesAudioSample *sound) { + MutexHolder holder(_samples_lock); + + for (size_t i = 0; i < _samples.size(); ++i) { + SampleData &smp = _samples[i]; + if (AIL_sample_status(smp._sample) == SMP_DONE) { + if (smp._sound != NULL) { + // Tell the last sound that was using this sample that it's + // done now. + smp._sound->internal_stop(); + } + smp._sound = sound; + sample = smp._sample; + index = i; + return true; + } + } + + // No more already-allocated samples; get a new one from the system. + sample = AIL_allocate_sample_handle(_digital_driver); + if (sample == 0) { + return false; + } + + AIL_init_sample(sample, DIG_F_STEREO_16, 0); + index = _samples.size(); + + SampleData smp; + smp._sound = sound; + smp._sample = sample; + _samples.push_back(smp); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::release_sample +// Access: Public +// Description: Indicates that the indicated AudioSound no longer +// needs this sample. +//////////////////////////////////////////////////////////////////// +void GlobalMilesManager:: +release_sample(size_t index, MilesAudioSample *sound) { + MutexHolder holder(_samples_lock); + nassertv(index < _samples.size()); + + SampleData &smp = _samples[index]; + if (smp._sound == sound) { + smp._sound = NULL; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::get_sequence +// Access: Public +// Description: Gets a sequence handle from the global pool for the +// digital output device, to be used with the indicated +// AudioSound. +// +// If successful, sets the sequence handle and the index +// (which should later be used to release the sequence) +// and returns true. If unsuccessful (because there are +// no more available handles), returns false. +// +// This is a very limited resource; you should only get +// a sequence just before playing a sound. +//////////////////////////////////////////////////////////////////// +bool GlobalMilesManager:: +get_sequence(HSEQUENCE &sequence, size_t &index, MilesAudioSequence *sound) { + MutexHolder holder(_sequences_lock); + + for (size_t i = 0; i < _sequences.size(); ++i) { + SequenceData &seq = _sequences[i]; + if (AIL_sequence_status(seq._sequence) == SEQ_DONE) { + if (seq._sound != NULL) { + // Tell the last sound that was using this sequence that it's + // done now. + seq._sound->internal_stop(); + } + seq._sound = sound; + sequence = seq._sequence; + index = i; + return true; + } + } + + // No more already-allocated sequences; get a new one from the system. + sequence = AIL_allocate_sequence_handle(_midi_driver); + if (sequence == 0) { + return false; + } + + index = _sequences.size(); + + SequenceData seq; + seq._sound = sound; + seq._sequence = sequence; + _sequences.push_back(seq); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::release_sequence +// Access: Public +// Description: Indicates that the indicated AudioSound no longer +// needs this sequence. +//////////////////////////////////////////////////////////////////// +void GlobalMilesManager:: +release_sequence(size_t index, MilesAudioSequence *sound) { + MutexHolder holder(_sequences_lock); + nassertv(index < _sequences.size()); + + SequenceData &seq = _sequences[index]; + if (seq._sound == sound) { + seq._sound = NULL; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::force_midi_reset +// Access: Public +// Description: Sometimes Miles seems to leave midi notes hanging, +// even after stop is called, so call this method to +// perform an explicit reset using winMM.dll calls, just +// to ensure silence. +//////////////////////////////////////////////////////////////////// +void GlobalMilesManager:: +force_midi_reset() { + if (!miles_audio_force_midi_reset) { + audio_debug("MilesAudioManager::skipping force_midi_reset"); + return; + } + audio_debug("MilesAudioManager::force_midi_reset"); + +#ifdef WIN32 + if ((_midi_driver!=NULL) && (_midi_driver->deviceid != MIDI_NULL_DRIVER) && (_midi_driver->hMidiOut != NULL)) { + audio_debug("MilesAudioManager::calling midiOutReset"); + midiOutReset(_midi_driver->hMidiOut); + } +#endif +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::get_global_ptr +// Access: Public, Static +// Description: Returns the pointer to the one GlobalMilesManager +// object. +//////////////////////////////////////////////////////////////////// +GlobalMilesManager *GlobalMilesManager:: +get_global_ptr() { + if (_global_ptr == NULL) { + _global_ptr = new GlobalMilesManager; + } + return _global_ptr; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::open_api +// Access: Private +// Description: Called internally to initialize the Miles API. +//////////////////////////////////////////////////////////////////// +void GlobalMilesManager:: +open_api() { + audio_debug("GlobalMilesManager::open_api()") + nassertv(!_is_open); + + bool use_digital = (audio_play_wave || audio_play_mp3); + if (audio_play_midi && audio_software_midi) { + use_digital = true; + } + +#ifdef IS_OSX + audio_software_midi = true; +#endif + + audio_debug(" use_digital="<resolve_filename(dls_pathname, get_sound_path()) || + vfs->resolve_filename(dls_pathname, get_model_path()); + + _dls_data.clear(); + PT(VirtualFile) file = vfs->get_file(dls_pathname); + if (file == (VirtualFile *)NULL) { + milesAudio_cat.warning() + << "DLS file does not exist: " << dls_pathname << "\n"; + + } else if (!file->read_file(_dls_data, true)) { + milesAudio_cat.warning() + << "Could not read DLS file: " << dls_pathname << "\n"; + + } else if (_dls_data.empty()) { + milesAudio_cat.warning() + << "DLS file is empty: " << dls_pathname << "\n"; + + } else { + _dls_file = AIL_DLS_load_memory(_dls_device, &_dls_data[0], 0); + } + + if (_dls_file == 0) { + audio_error(" Could not get DLS file, switching to hardware MIDI."); + AIL_DLS_close(_dls_device, 0); + _dls_device = 0; + AIL_close_XMIDI_driver(_midi_driver); + _midi_driver = AIL_open_XMIDI_driver(0); + + } else { + audio_info(" using Miles software midi"); + } + } else { + _midi_driver = AIL_open_XMIDI_driver(0); + audio_info(" using Miles hardware midi"); + } + } + + _is_open = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::close_api +// Access: Private +// Description: Called internally to shut down the Miles API. +//////////////////////////////////////////////////////////////////// +void GlobalMilesManager:: +close_api() { + audio_debug("GlobalMilesManager::close_api()") + nassertv(_is_open); + + Samples::iterator si; + for (si = _samples.begin(); si != _samples.end(); ++si) { + SampleData &smp = (*si); + AIL_release_sample_handle(smp._sample); + } + _samples.clear(); + + Sequences::iterator qi; + for (qi = _sequences.begin(); qi != _sequences.end(); ++qi) { + SequenceData &smp = (*qi); + AIL_release_sequence_handle(smp._sequence); + } + _sequences.clear(); + + if (_dls_file != 0) { + AIL_DLS_unload(_dls_device, _dls_file); + _dls_file = 0; + } + + if (_dls_device != 0) { + AIL_DLS_close(_dls_device, 0); + _dls_device = 0; + } + + if (_midi_driver != 0) { + AIL_close_XMIDI_driver(_midi_driver); + _midi_driver = 0; + } + + if (_digital_driver != 0) { + AIL_close_digital_driver(_digital_driver); + _digital_driver = 0; + } + + AIL_shutdown(); + + _is_open = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::open_callback +// Access: Private, Static +// Description: This callback function is given to Miles to handle +// file I/O via the Panda VFS. It's only used to +// implemented streaming audio files, since in all other +// cases we open files directly. +//////////////////////////////////////////////////////////////////// +U32 AILCALLBACK GlobalMilesManager:: +open_callback(char const *filename, UINTa *file_handle) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + istream *strm = vfs->open_read_file(Filename::binary_filename(filename), true); + if (strm == NULL) { + // Failure. + return 0; + } + // Success. + (*file_handle) = (UINTa)strm; + return 1; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::close_callback +// Access: Private, Static +// Description: This callback function is given to Miles to handle +// file I/O via the Panda VFS. +//////////////////////////////////////////////////////////////////// +void AILCALLBACK GlobalMilesManager:: +close_callback(UINTa file_handle) { + istream *strm = (istream *)file_handle; + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + vfs->close_read_file(strm); +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::seek_callback +// Access: Private, Static +// Description: This callback function is given to Miles to handle +// file I/O via the Panda VFS. +//////////////////////////////////////////////////////////////////// +S32 AILCALLBACK GlobalMilesManager:: +seek_callback(UINTa file_handle, S32 offset, U32 type) { + istream *strm = (istream *)file_handle; + strm->clear(); + switch (type) { + case AIL_FILE_SEEK_BEGIN: + strm->seekg(offset, ios::beg); + break; + + case AIL_FILE_SEEK_CURRENT: + strm->seekg(offset, ios::cur); + break; + + case AIL_FILE_SEEK_END: + strm->seekg(offset, ios::end); + break; + } + + return strm->tellg(); +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobalMilesManager::read_callback +// Access: Private, Static +// Description: This callback function is given to Miles to handle +// file I/O via the Panda VFS. +//////////////////////////////////////////////////////////////////// +U32 AILCALLBACK GlobalMilesManager:: +read_callback(UINTa file_handle, void *buffer, U32 bytes) { + istream *strm = (istream *)file_handle; + strm->read((char *)buffer, bytes); + return strm->gcount(); +} + +#endif //] + diff --git a/panda/src/audiotraits/globalMilesManager.h b/panda/src/audiotraits/globalMilesManager.h new file mode 100644 index 0000000000..1e6772dba5 --- /dev/null +++ b/panda/src/audiotraits/globalMilesManager.h @@ -0,0 +1,114 @@ +// Filename: globalMilesManager.h +// Created by: drose (26Jul07) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + +#ifndef GLOBALMILESMANAGER_H +#define GLOBALMILESMANAGER_H + +#include "pandabase.h" +#ifdef HAVE_RAD_MSS //[ + +#include "mss.h" +#include "pset.h" +#include "pmutex.h" + +class MilesAudioSample; +class MilesAudioSequence; + +//////////////////////////////////////////////////////////////////// +// Class : GlobalMilesManager +// Description : This is a wrapper around the parts of the Miles API +// that should only be created once. This represents +// the global data common to all MilesAudioManagers. +//////////////////////////////////////////////////////////////////// +class EXPCL_MILES_AUDIO GlobalMilesManager { +private: + GlobalMilesManager(); + +public: + void add_manager(MilesAudioManager *manager); + void remove_manager(MilesAudioManager *manager); + void cleanup(); + INLINE bool is_open() const; + + bool get_sample(HSAMPLE &sample, size_t &index, MilesAudioSample *sound); + void release_sample(size_t index, MilesAudioSample *sound); + + bool get_sequence(HSEQUENCE &sequence, size_t &index, MilesAudioSequence *sound); + void release_sequence(size_t index, MilesAudioSequence *sound); + + void force_midi_reset(); + + static GlobalMilesManager *get_global_ptr(); + +public: + HDIGDRIVER _digital_driver; + HMDIDRIVER _midi_driver; + + // For software MIDI: + HDLSDEVICE _dls_device; + HDLSFILEID _dls_file; + pvector _dls_data; + +private: + void open_api(); + void close_api(); + + static U32 AILCALLBACK open_callback(char const *filename, UINTa *file_handle); + static void AILCALLBACK close_callback(UINTa file_handle); + static S32 AILCALLBACK seek_callback(UINTa file_handle, S32 offset, U32 type); + static U32 AILCALLBACK read_callback(UINTa file_handle, void *buffer, U32 bytes); + + +private: + bool _is_open; + + typedef pset Managers; + Managers _managers; + Mutex _managers_lock; + + class SampleData { + public: + HSAMPLE _sample; + MilesAudioSample *_sound; + }; + + typedef pvector Samples; + Samples _samples; + Mutex _samples_lock; + + class SequenceData { + public: + HSEQUENCE _sequence; + MilesAudioSequence *_sound; + }; + + typedef pvector Sequences; + Sequences _sequences; + Mutex _sequences_lock; + + static GlobalMilesManager *_global_ptr; +}; + +#include "globalMilesManager.I" + +#endif //] + +#endif + + + diff --git a/panda/src/audiotraits/milesAudioManager.cxx b/panda/src/audiotraits/milesAudioManager.cxx index 1e165e88ba..14f5c3e882 100644 --- a/panda/src/audiotraits/milesAudioManager.cxx +++ b/panda/src/audiotraits/milesAudioManager.cxx @@ -17,38 +17,34 @@ // //////////////////////////////////////////////////////////////////// -#include "pandabase.h" +#include "milesAudioManager.h" + #ifdef HAVE_RAD_MSS //[ #include "milesAudioSound.h" -#include "milesAudioManager.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 TypeHandle MilesAudioManager::_type_handle; -int MilesAudioManager::_active_managers = 0; -bool MilesAudioManager::_miles_active = false; -HDLSFILEID MilesAudioManager::_dls_field = NULL; - -// This is the list of all MilesAudioManager objects in the world. It -// must be a pointer rather than a concrete object, so it won't be -// destructed at exit time before we're done removing things from it. -MilesAudioManager::Managers *MilesAudioManager::_managers; - PT(AudioManager) Create_AudioManager() { audio_debug("Create_AudioManager() Miles."); return new MilesAudioManager(); } //////////////////////////////////////////////////////////////////// -// Function: MilesAudioManager::MilesAudioManager +// Function: MilesAudioManager::Constructor // Access: Public // Description: Create an audio manager. This may open the Miles // sound system if there were no other MilesAudioManager @@ -56,13 +52,12 @@ PT(AudioManager) Create_AudioManager() { // Miles resources. //////////////////////////////////////////////////////////////////// MilesAudioManager:: -MilesAudioManager() { +MilesAudioManager() : _streams_lock("MilesAudioManager::_streams_lock"), + _streams_cvar(_streams_lock) +{ audio_debug("MilesAudioManager::MilesAudioManager(), this = " << (void *)this); - if (_managers == (Managers *)NULL) { - _managers = new Managers; - } - _managers->insert(this); + GlobalMilesManager::get_global_ptr()->add_manager(this); audio_debug(" audio_active="<cleanup(); audio_debug("shutdown() finished"); } //////////////////////////////////////////////////////////////////// // Function: MilesAudioManager::is_valid -// Access: +// 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. @@ -228,99 +143,9 @@ is_valid() { return _is_valid && check; } -//////////////////////////////////////////////////////////////////// -// 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. -//////////////////////////////////////////////////////////////////// -PT(MilesAudioManager::SoundData) MilesAudioManager:: -load(Filename file_name) { - // We used to use callbacks to hook AIL_quick_load() into the vfs - // system directly. The theory was that that would enable - // AIL_quick_load() to stream the file from disk, avoiding the need - // to keep the whole thing in memory at once. But it turns out that - // AIL_quick_load() always reads the whole file anyway, so it's a - // moot point. - - // Nowadays we don't mess around with that callback nonsense, and - // just read the whole file directly. Not only is it simpler, but - // preloading the sound files allows us to optionally convert MP3 to - // WAV format in-memory at load time. - - PT(SoundData) sd = new SoundData; - - VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); - if (!vfs->read_file(file_name, sd->_raw_data, true)) { - milesAudio_cat.warning() - << "Unable to read " << file_name << "\n"; - return NULL; - } - if (sd->_raw_data.empty()) { - milesAudio_cat.warning() - << "File " << file_name << " is empty\n"; - return NULL; - } - - sd->_basename = file_name.get_basename(); - sd->_file_type = - AIL_file_type(&sd->_raw_data[0], sd->_raw_data.size()); - - 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; - } - AIL_mem_free_lock(wav_data); - - } else { - audio_debug("unable to expand " << sd->_basename); - } - } - - sd->_audio = AIL_quick_load_mem(&sd->_raw_data[0], sd->_raw_data.size()); - - if (!sd->_audio) { - audio_error(" MilesAudioManager::load failed "<< AIL_last_error()); - return NULL; - } - - // We still need to keep around the raw data value, since - // AIL_quick_load_mem() doesn't make a copy. - - return sd; -} - //////////////////////////////////////////////////////////////////// // Function: MilesAudioManager::get_sound -// Access: Public +// Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// PT(AudioSound) MilesAudioManager:: @@ -371,16 +196,30 @@ get_sound(const string& file_name, bool) { } } // Create an AudioSound from the sound: - PT(AudioSound) audioSound = 0; + PT(AudioSound) audioSound; + if (sd != (SoundData *)NULL) { most_recently_used((*si).first); - PT(MilesAudioSound) milesAudioSound - =new MilesAudioSound(this, sd, (*si).first); - nassertr(milesAudioSound, 0); - milesAudioSound->set_active(_active); - bool inserted = _sounds_on_loan.insert(milesAudioSound).second; - nassertr(inserted, milesAudioSound.p()); - audioSound=milesAudioSound; + 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 { @@ -395,7 +234,7 @@ get_sound(const string& file_name, bool) { //////////////////////////////////////////////////////////////////// // Function: MilesAudioManager::uncache_sound -// Access: Public +// Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// void MilesAudioManager:: @@ -422,8 +261,344 @@ uncache_sound(const string& file_name) { } //////////////////////////////////////////////////////////////////// -// Function: MilesAudioManager::uncache_a_sound +// Function: MilesAudioManager::clear_cache +// Access: Public, Virtual +// Description: Clear out the sound cache. +//////////////////////////////////////////////////////////////////// +void MilesAudioManager:: +clear_cache() { + audio_debug("MilesAudioManager::clear_cache()"); + if (_is_valid) { assert(is_valid()); } + _sounds.clear(); + _lru.clear(); + if (_is_valid) { assert(is_valid()); } +} + +//////////////////////////////////////////////////////////////////// +// 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) { + audio_debug("MilesAudioManager::set_cache_limit(count="< count) { + uncache_a_sound(); + } + _cache_limit=count; + assert(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="<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="<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="<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) { + _concurrent_sound_limit = limit; + 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: Private, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioManager:: +reduce_sounds_playing_to(unsigned int count) { + int limit = _sounds_playing.size() - count; + while (limit-- > 0) { + SoundsPlaying::iterator sound = _sounds_playing.begin(); + assert(sound != _sounds_playing.end()); + (**sound).stop(); + } +} + +//////////////////////////////////////////////////////////////////// +// 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(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioManager::release_sound +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioManager:: +release_sound(MilesAudioSound *audioSound) { + audio_debug("MilesAudioManager::release_sound(audioSound=\"" + <get_name()<<"\"), this = " << (void *)this); + 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); + 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(); + } + + 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 { + 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 { + out << (*this) << "\n"; + AudioSet::iterator ai; + for (ai = _sounds_on_loan.begin(); ai != _sounds_on_loan.end(); ++ai) { + MilesAudioSound *sound = (*ai); + out << " " << *sound << "\n"; + } +} + + +//////////////////////////////////////////////////////////////////// +// 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: +//////////////////////////////////////////////////////////////////// +void MilesAudioManager:: +most_recently_used(const string &path) { + audio_debug("MilesAudioManager::most_recently_used(path=\"" + < count) { - uncache_a_sound(); - } - _cache_limit=count; - assert(is_valid()); -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioManager::get_cache_limit -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -unsigned int MilesAudioManager:: -get_cache_limit() const { - audio_debug("MilesAudioManager::get_cache_limit() returning " - <<_cache_limit); - return _cache_limit; -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioManager::release_sound -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -void MilesAudioManager:: -release_sound(MilesAudioSound* audioSound) { - audio_debug("MilesAudioManager::release_sound(audioSound=\"" - <get_name()<<"\"), this = " << (void *)this); - 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::set_volume -// Access: Public -// Description: set the overall volume -//////////////////////////////////////////////////////////////////// -void MilesAudioManager:: -set_volume(float volume) { - audio_debug("MilesAudioManager::set_volume(volume="<force_midi_reset(); } } -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioManager::set_concurrent_sound_limit -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -void MilesAudioManager:: -set_concurrent_sound_limit(unsigned int limit) { - _concurrent_sound_limit = limit; - reduce_sounds_playing_to(_concurrent_sound_limit); -} //////////////////////////////////////////////////////////////////// -// Function: MilesAudioManager::get_concurrent_sound_limit -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -unsigned int MilesAudioManager:: -get_concurrent_sound_limit() const { - return _concurrent_sound_limit; -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioManager::reduce_sounds_playing_to +// Function: MilesAudioManager::load // Access: Private -// Description: +// Description: Reads a sound file and allocates a SoundData pointer +// for it. Returns NULL if the sound file cannot be +// loaded. //////////////////////////////////////////////////////////////////// -void MilesAudioManager:: -reduce_sounds_playing_to(unsigned int count) { - int limit = _sounds_playing.size() - count; - while (limit-- > 0) { - SoundsPlaying::iterator sound = _sounds_playing.begin(); - assert(sound != _sounds_playing.end()); - (**sound).stop(); - } -} +PT(MilesAudioManager::SoundData) MilesAudioManager:: +load(const Filename &file_name) { + PT(SoundData) sd = new SoundData; -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioManager::stop_all_sounds -// Access: Public -// 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::force_midi_reset -// Access: Private -// Description: ?????. -//////////////////////////////////////////////////////////////////// -void MilesAudioManager:: -force_midi_reset() { - if (!miles_audio_force_midi_reset) { - audio_debug("MilesAudioManager::skipping force_midi_reset"); - return; - } - audio_debug("MilesAudioManager::force_midi_reset"); - - // sometimes Miles seems to leave midi notes hanging, even after - // stop is called, so perform an explicit reset using winMM.dll - // calls, just to ensure silence. - - HMDIDRIVER hMid=NULL; - AIL_quick_handles(0, &hMid, 0); - if ((hMid!=NULL) && (hMid->deviceid != MIDI_NULL_DRIVER) && (hMid->hMidiOut != NULL)) { - audio_debug("MilesAudioManager::calling midiOutReset"); -#ifdef WIN32 - midiOutReset(hMid->hMidiOut); -#endif - } -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioManager::cleanup -// Access: Private -// 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); - if (!_cleanup_required) { - return; + 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; } - // 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(); + if (file->get_file_size() == 0) { + milesAudio_cat.warning() + << "File " << file_name << " is empty\n"; + return NULL; } - clear_cache(); - nassertv(_active_managers > 0); - --_active_managers; - audio_debug(" _active_managers="<<_active_managers); + sd->_basename = file_name.get_basename(); - if (_active_managers == 0) { - if (_dls_field != NULL) { - HDLSDEVICE dls; - AIL_quick_handles(0, 0, &dls); - audio_debug(" AIL_DLS_unload()"); - AIL_DLS_unload(dls, _dls_field); - _dls_field = NULL; + 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; } - if (_miles_active) { - audio_debug(" AIL_quick_shutdown()"); - AIL_quick_shutdown(); - _miles_active = false; + 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. } - _cleanup_required = false; - audio_debug("MilesAudioManager::cleanup() finished"); + + 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); } //////////////////////////////////////////////////////////////////// @@ -767,7 +846,6 @@ cleanup() { //////////////////////////////////////////////////////////////////// MilesAudioManager::SoundData:: SoundData() : - _audio(0), _raw_data(MilesAudioManager::get_class_type()), _has_length(false) { @@ -780,12 +858,6 @@ SoundData() : //////////////////////////////////////////////////////////////////// MilesAudioManager::SoundData:: ~SoundData() { - if (_audio != 0) { - if (_miles_active) { - AIL_quick_unload(_audio); - } - _audio = 0; - } } //////////////////////////////////////////////////////////////////// @@ -797,40 +869,36 @@ 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; - if (_file_type == AILFILETYPE_MPEG_L3_AUDIO && - (int)_raw_data.size() < miles_audio_calc_mp3_threshold && - !_raw_data.empty()) { - // If it's an mp3 file, we may not trust Miles to compute its - // length correctly (Miles doesn't correctly compute the length - // of VBR MP3 files). So in that case, decompress the whole - // file to determine its precise length. - audio_debug("Computing length of " << _basename); - - void *wav_data; - U32 wav_data_size; - if (AIL_decompress_ASI(&_raw_data[0], _raw_data.size(), - _basename.c_str(), &wav_data, &wav_data_size, - NULL)) { - AILSOUNDINFO info; - if (AIL_WAV_info(wav_data, &info)) { - _length = (float)info.samples / (float)info.rate; - audio_debug(info.samples << " samples at " << info.rate - << "; length is " << _length << " seconds."); - _has_length = true; - } - - AIL_mem_free_lock(wav_data); + } 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; - if (!_has_length) { - // If it's not an mp3 file, or we don't care about precalcing - // mp3 files, just ask Miles to do it. - _length = ((float)AIL_quick_ms_length(_audio)) * 0.001f; + } 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); - audio_debug("Miles reports length of " << _length - << " for " << _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; + } } } diff --git a/panda/src/audiotraits/milesAudioManager.h b/panda/src/audiotraits/milesAudioManager.h index 455bb226cc..4d0a9f8964 100644 --- a/panda/src/audiotraits/milesAudioManager.h +++ b/panda/src/audiotraits/milesAudioManager.h @@ -29,6 +29,9 @@ #include "pmap.h" #include "pdeque.h" #include "pvector.h" +#include "thread.h" +#include "pmutex.h" +#include "conditionVar.h" class MilesAudioSound; @@ -37,35 +40,69 @@ public: // See AudioManager.h for documentation. MilesAudioManager(); - ~MilesAudioManager(); - + virtual ~MilesAudioManager(); + virtual void shutdown(); - bool is_valid(); + virtual bool is_valid(); - virtual PT(AudioSound) get_sound(const string& file_name, bool positional = false); - void uncache_sound(const string& file_name); - void clear_cache(); - void set_cache_limit(unsigned int count); - unsigned int get_cache_limit() const; + virtual PT(AudioSound) get_sound(const string &file_name, bool positional = false); + virtual void uncache_sound(const string &file_name); + virtual void clear_cache(); + virtual void set_cache_limit(unsigned int count); + virtual unsigned int get_cache_limit() const; - void set_volume(float volume); - float get_volume() const; + virtual void set_volume(float volume); + virtual float get_volume() const; void set_play_rate(float play_rate); float get_play_rate() const; - void set_active(bool active); - bool get_active() const; + virtual void set_active(bool active); + virtual bool get_active() const; - void set_concurrent_sound_limit(unsigned int limit = 0); - unsigned int get_concurrent_sound_limit() const; + virtual void set_concurrent_sound_limit(unsigned int limit = 0); + virtual unsigned int get_concurrent_sound_limit() const; - void reduce_sounds_playing_to(unsigned int count); + virtual void reduce_sounds_playing_to(unsigned int count); - void stop_all_sounds(); + virtual void stop_all_sounds(); + + virtual void update(); + + // Tell the manager that the sound dtor was called. + void release_sound(MilesAudioSound *audioSound); + void cleanup(); + + virtual void output(ostream &out) const; + virtual void write(ostream &out) const; private: + void start_service_stream(HSTREAM stream); + void stop_service_stream(HSTREAM stream); + + void most_recently_used(const string &path); + void uncache_a_sound(); + + void starting_sound(MilesAudioSound *audio); + void stopping_sound(MilesAudioSound *audio); + + class SoundData; + PT(SoundData) load(const Filename &file_name); + + void thread_main(volatile bool &keep_running); + void do_service_streams(); + +private: + class StreamThread : public Thread { + public: + StreamThread(MilesAudioManager *mgr); + virtual void thread_main(); + + MilesAudioManager *_mgr; + volatile bool _keep_running; + }; + // The sound cache: class SoundData : public ReferenceCount { public: @@ -73,8 +110,7 @@ private: ~SoundData(); float get_length(); - string _basename; - HAUDIO _audio; + Filename _basename; S32 _file_type; pvector _raw_data; bool _has_length; @@ -83,7 +119,7 @@ private: typedef pmap SoundMap; SoundMap _sounds; - typedef pset AudioSet; + typedef pset AudioSet; // The offspring of this manager: AudioSet _sounds_on_loan; @@ -100,38 +136,16 @@ private: bool _active; int _cache_limit; bool _cleanup_required; - // keep a count for startup and shutdown: - static int _active_managers; - static bool _miles_active; unsigned int _concurrent_sound_limit; bool _is_valid; bool _hasMidiSounds; - // Optional Downloadable Sound field for software midi - static HDLSFILEID _dls_field; - - typedef pset Managers; - static Managers *_managers; - - PT(SoundData) load(Filename file_name); - // Tell the manager that the sound dtor was called. - void release_sound(MilesAudioSound* audioSound); - - void most_recently_used(const string& path); - void uncache_a_sound(); - - void starting_sound(MilesAudioSound* audio); - void stopping_sound(MilesAudioSound* audio); - - // get the default dls file path: - void get_gm_file_path(string& result); - - void force_midi_reset(); - void cleanup(); - - friend class MilesAudioSound; - + typedef pvector Streams; + PT(StreamThread) _stream_thread; + Streams _streams; + Mutex _streams_lock; + ConditionVar _streams_cvar; public: static TypeHandle get_class_type() { @@ -149,6 +163,11 @@ public: private: static TypeHandle _type_handle; + + friend class MilesAudioSound; + friend class MilesAudioSample; + friend class MilesAudioSequence; + friend class MilesAudioStream; }; EXPCL_MILES_AUDIO PT(AudioManager) Create_AudioManager(); diff --git a/panda/src/audiotraits/milesAudioSample.I b/panda/src/audiotraits/milesAudioSample.I new file mode 100644 index 0000000000..26f04ca660 --- /dev/null +++ b/panda/src/audiotraits/milesAudioSample.I @@ -0,0 +1,24 @@ +// Filename: milesAudioSample.I +// 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 . +// +//////////////////////////////////////////////////////////////////// + + + + + + diff --git a/panda/src/audiotraits/milesAudioSample.cxx b/panda/src/audiotraits/milesAudioSample.cxx new file mode 100644 index 0000000000..5194e15e18 --- /dev/null +++ b/panda/src/audiotraits/milesAudioSample.cxx @@ -0,0 +1,309 @@ +// Filename: milesAudioSample.cxx +// Created by: skyler (June 6, 2001) +// +//////////////////////////////////////////////////////////////////// +// +// 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 "milesAudioSample.h" + +#ifdef HAVE_RAD_MSS //[ + +#include "milesAudioManager.h" + + +TypeHandle MilesAudioSample::_type_handle; + +#undef miles_audio_debug + +#ifndef NDEBUG //[ +#define miles_audio_debug(x) \ + audio_debug("MilesAudioSample \""<release_sound(this); + miles_audio_debug("~MilesAudioSample() done"); +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::play +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioSample:: +play() { + miles_audio_debug("play()"); + if (_active) { + if (_sd->_raw_data.empty()) { + milesAudio_cat.warning() + << "Could not play " << _file_name << ": no data\n"; + } else { + stop(); + _manager->starting_sound(this); + + nassertv(_sample == 0); + + GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr(); + if (!mgr->get_sample(_sample, _sample_index, this)){ + milesAudio_cat.warning() + << "Could not play " << _file_name << ": too many open samples\n"; + _sample = 0; + } else { + AIL_set_named_sample_file(_sample, _sd->_basename.c_str(), + &_sd->_raw_data[0], _sd->_raw_data.size(), + 0); + _original_playback_rate = AIL_sample_playback_rate(_sample); + + set_volume(_volume); + set_play_rate(_play_rate); + AIL_set_sample_loop_count(_sample, _loop_count); + AIL_start_sample(_sample); + } + } + } else { + // In case _loop_count gets set to forever (zero): + audio_debug(" paused "<<_file_name ); + _paused = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::stop +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioSample:: +stop() { + miles_audio_debug("stop()"); + _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(). + + if (_sample != 0) { + AIL_end_sample(_sample); + + GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr(); + mgr->release_sample(_sample_index, this); + + _sample = 0; + _sample_index = 0; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::set_time +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioSample:: +set_time(float time) { + miles_audio_debug("set_time(time="< max_time) { + milesAudio_cat.warning() + << "set_time(" << time << ") requested for sound of length " + << max_time << "\n"; + time = max_time; + } + + S32 time_ms = (S32)(1000.0f * time); + AIL_set_sample_ms_position(_sample, time_ms); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::get_time +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +float MilesAudioSample:: +get_time() const { + if (_sample == 0) { + return 0.0f; + } + + S32 current_ms; + AIL_sample_ms_position(_sample, NULL, ¤t_ms); + float time = float(current_ms * 0.001f); + + return time; +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::set_volume +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioSample:: +set_volume(float volume) { + miles_audio_debug("set_volume(volume="<get_volume(); + + // Change to Miles volume, range 0 to 1.0: + F32 milesVolume = volume; + milesVolume = min(milesVolume, 1.0f); + milesVolume = max(milesVolume, 0.0f); + + // Convert balance of -1.0..1.0 to 0-1.0: + F32 milesBalance = (F32)((_balance + 1.0f) * 0.5f); + + AIL_set_sample_volume_pan(_sample, milesVolume, milesBalance); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::set_balance +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioSample:: +set_balance(float balance_right) { + miles_audio_debug("set_balance(balance_right="<get_play_rate(); + + // wave and mp3 use sample rate (e.g. 44100) + S32 speed = (S32)(play_rate * (float)_original_playback_rate); + AIL_set_sample_playback_rate(_sample, speed); + audio_debug(" play_rate for this wav or mp3 is now " << speed); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::length +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +float MilesAudioSample:: +length() const { + return _sd->get_length(); +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::status +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +AudioSound::SoundStatus MilesAudioSample:: +status() const { + if (_sample == 0) { + return AudioSound::READY; + } + switch (AIL_sample_status(_sample)) { + case SMP_DONE: + case SMP_STOPPED: + case SMP_FREE: + return AudioSound::READY; + + case SMP_PLAYING: + case SMP_PLAYINGBUTRELEASED: + return AudioSound::PLAYING; + + default: + return AudioSound::BAD; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::cleanup +// Access: Public, Virtual +// Description: Stops the sound from playing and releases any +// associated resources, in preparation for releasing +// the sound or shutting down the sound system. +//////////////////////////////////////////////////////////////////// +void MilesAudioSample:: +cleanup() { + stop(); +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSample::internal_stop +// Access: Private +// Description: Called by the GlobalMilesManager when it is detected +// that this particular sound has already stopped, and +// its sample handle will be recycled. +//////////////////////////////////////////////////////////////////// +void MilesAudioSample:: +internal_stop() { + _sample = 0; + _sample_index = 0; +} + +#endif //] diff --git a/panda/src/audiotraits/milesAudioSample.h b/panda/src/audiotraits/milesAudioSample.h new file mode 100644 index 0000000000..498bc93415 --- /dev/null +++ b/panda/src/audiotraits/milesAudioSample.h @@ -0,0 +1,93 @@ +// Filename: milesAudioSample.h +// 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 . +// +//////////////////////////////////////////////////////////////////// + +#ifndef MILESAUDIOSAMPLE_H +#define MILESAUDIOSAMPLE_H + +#include "pandabase.h" +#ifdef HAVE_RAD_MSS //[ + +#include "milesAudioSound.h" +#include "milesAudioManager.h" +#include "mss.h" + +//////////////////////////////////////////////////////////////////// +// Class : MilesAudioSample +// Description : A sound file, such as a WAV or MP3 file, that is +// preloaded into memory and played from memory. +//////////////////////////////////////////////////////////////////// +class EXPCL_MILES_AUDIO MilesAudioSample : public MilesAudioSound { +private: + MilesAudioSample(MilesAudioManager *manager, + MilesAudioManager::SoundData *sd, + const string &file_name); + +public: + virtual ~MilesAudioSample(); + + virtual void play(); + virtual void stop(); + + virtual void set_time(float time=0.0f); + virtual float get_time() const; + + virtual void set_volume(float volume=1.0f); + virtual void set_balance(float balance_right=0.0f); + virtual void set_play_rate(float play_rate=1.0f); + + virtual float length() const; + + virtual AudioSound::SoundStatus status() const; + + virtual void cleanup(); + +private: + void internal_stop(); + + PT(MilesAudioManager::SoundData) _sd; + HSAMPLE _sample; + size_t _sample_index; + S32 _original_playback_rate; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + MilesAudioSound::init_type(); + register_type(_type_handle, "MilesAudioSample", + MilesAudioSound::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class GlobalMilesManager; + friend class MilesAudioManager; +}; + +#include "milesAudioSample.I" + +#endif //] + +#endif /* MILESAUDIOSAMPLE_H */ diff --git a/panda/src/audiotraits/milesAudioSequence.I b/panda/src/audiotraits/milesAudioSequence.I new file mode 100644 index 0000000000..272d745f9d --- /dev/null +++ b/panda/src/audiotraits/milesAudioSequence.I @@ -0,0 +1,18 @@ +// Filename: milesAudioSequence.I +// Created by: drose (31Jul07) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + diff --git a/panda/src/audiotraits/milesAudioSequence.cxx b/panda/src/audiotraits/milesAudioSequence.cxx new file mode 100644 index 0000000000..0be1da19be --- /dev/null +++ b/panda/src/audiotraits/milesAudioSequence.cxx @@ -0,0 +1,299 @@ +// Filename: milesAudioSequence.cxx +// Created by: drose (31Jul07) +// +//////////////////////////////////////////////////////////////////// +// +// 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 "milesAudioSequence.h" + +#ifdef HAVE_RAD_MSS //[ + +#include "milesAudioManager.h" + + +TypeHandle MilesAudioSequence::_type_handle; + +#undef miles_audio_debug + +#ifndef NDEBUG //[ +#define miles_audio_debug(x) \ + audio_debug("MilesAudioSequence \""<release_sound(this); + miles_audio_debug("~MilesAudioSequence() done"); +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSequence::play +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioSequence:: +play() { + miles_audio_debug("play()"); + if (_active) { + stop(); + + if (_sd->_raw_data.empty()) { + milesAudio_cat.warning() + << "Could not play " << _file_name << ": no data\n"; + } else { + _manager->starting_sound(this); + nassertv(_sequence == 0); + + GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr(); + if (!mgr->get_sequence(_sequence, _sequence_index, this)){ + milesAudio_cat.warning() + << "Could not play " << _file_name << ": too many open sequences\n"; + _sequence = 0; + } else { + AIL_init_sequence(_sequence, &_sd->_raw_data[0], 0); + + set_volume(_volume); + set_play_rate(_play_rate); + AIL_set_sequence_loop_count(_sequence, _loop_count); + AIL_start_sequence(_sequence); + } + } + } else { + // In case _loop_count gets set to forever (zero): + audio_debug(" paused "<<_file_name ); + _paused = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSequence::stop +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioSequence:: +stop() { + miles_audio_debug("stop()"); + _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(). + + if (_sequence != 0) { + AIL_end_sequence(_sequence); + + GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr(); + mgr->release_sequence(_sequence_index, this); + + _sequence = 0; + _sequence_index = 0; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSequence::set_time +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioSequence:: +set_time(float time) { + miles_audio_debug("set_time(time="<get_volume(); + + // Change to Miles volume, range 0 to 127: + S32 milesVolume = (S32)(volume * 127.0f); + milesVolume = min(milesVolume, 127); + milesVolume = max(milesVolume, 0); + + AIL_set_sequence_volume(_sequence, milesVolume, 0); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSequence::set_balance +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioSequence:: +set_balance(float balance_right) { + miles_audio_debug("set_balance(balance_right="<get_play_rate(); + + S32 percent = (S32)(play_rate * 100.0f); + AIL_set_sequence_tempo(_sequence, percent, 0); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSequence::length +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +float MilesAudioSequence:: +length() const { + S32 length_ms; + AIL_sequence_ms_position(_sequence, &length_ms, NULL); + float time = (float)length_ms * 0.001f; + return time; +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSequence::status +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +AudioSound::SoundStatus MilesAudioSequence:: +status() const { + if (_sequence == 0) { + return AudioSound::READY; + } + switch (AIL_sequence_status(_sequence)) { + case SEQ_DONE: + case SEQ_STOPPED: + case SEQ_FREE: + return AudioSound::READY; + + case SEQ_PLAYING: + case SEQ_PLAYINGBUTRELEASED: + return AudioSound::PLAYING; + + default: + return AudioSound::BAD; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSequence::cleanup +// Access: Public, Virtual +// Description: Stops the sound from playing and releases any +// associated resources, in preparation for releasing +// the sound or shutting down the sound system. +//////////////////////////////////////////////////////////////////// +void MilesAudioSequence:: +cleanup() { + stop(); +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioSequence::internal_stop +// Access: Private +// Description: Called by the GlobalMilesManager when it is detected +// that this particular sound has already stopped, and +// its sequence handle will be recycled. +//////////////////////////////////////////////////////////////////// +void MilesAudioSequence:: +internal_stop() { + _sequence = 0; + _sequence_index = 0; +} + +#endif //] diff --git a/panda/src/audiotraits/milesAudioSequence.h b/panda/src/audiotraits/milesAudioSequence.h new file mode 100644 index 0000000000..26f7e6b611 --- /dev/null +++ b/panda/src/audiotraits/milesAudioSequence.h @@ -0,0 +1,91 @@ +// Filename: milesAudioSequence.h +// Created by: drose (31Jul07) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + +#ifndef MILESAUDIOSEQUENCE_H +#define MILESAUDIOSEQUENCE_H + +#include "pandabase.h" +#ifdef HAVE_RAD_MSS //[ + +#include "milesAudioSound.h" +#include "milesAudioManager.h" +#include "mss.h" + +//////////////////////////////////////////////////////////////////// +// Class : MilesAudioSequence +// Description : A MIDI file, preloaded and played from a memory +// buffer. MIDI files cannot be streamed. +//////////////////////////////////////////////////////////////////// +class EXPCL_MILES_AUDIO MilesAudioSequence : public MilesAudioSound { +private: + MilesAudioSequence(MilesAudioManager *manager, + MilesAudioManager::SoundData *sd, + const string &file_name); + +public: + virtual ~MilesAudioSequence(); + + virtual void play(); + virtual void stop(); + + virtual void set_time(float time=0.0f); + virtual float get_time() const; + + virtual void set_volume(float volume=1.0f); + virtual void set_balance(float balance_right=0.0f); + virtual void set_play_rate(float play_rate=1.0f); + + virtual float length() const; + + virtual AudioSound::SoundStatus status() const; + + virtual void cleanup(); + +private: + void internal_stop(); + + PT(MilesAudioManager::SoundData) _sd; + HSEQUENCE _sequence; + size_t _sequence_index; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + MilesAudioSound::init_type(); + register_type(_type_handle, "MilesAudioSequence", + MilesAudioSound::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class GlobalMilesManager; + friend class MilesAudioManager; +}; + +#include "milesAudioSequence.I" + +#endif //] + +#endif /* MILESAUDIOSEQUENCE_H */ diff --git a/panda/src/audiotraits/milesAudioSound.I b/panda/src/audiotraits/milesAudioSound.I index 552e741053..dd44752c30 100644 --- a/panda/src/audiotraits/milesAudioSound.I +++ b/panda/src/audiotraits/milesAudioSound.I @@ -1,6 +1,5 @@ // Filename: milesAudioSound.I -// Created by: skyler (June 6, 2001) -// Prior system by: cary +// Created by: drose (30Jul07) // //////////////////////////////////////////////////////////////////// // @@ -17,8 +16,3 @@ // //////////////////////////////////////////////////////////////////// - - - - - diff --git a/panda/src/audiotraits/milesAudioSound.cxx b/panda/src/audiotraits/milesAudioSound.cxx index 4db6e2f312..4c6b836094 100644 --- a/panda/src/audiotraits/milesAudioSound.cxx +++ b/panda/src/audiotraits/milesAudioSound.cxx @@ -1,5 +1,5 @@ // Filename: milesAudioSound.cxx -// Created by: skyler (June 6, 2001) +// Created by: drose (30Jul07) // //////////////////////////////////////////////////////////////////// // @@ -16,311 +16,70 @@ // //////////////////////////////////////////////////////////////////// -#include "pandabase.h" +#include "milesAudioSound.h" #ifdef HAVE_RAD_MSS //[ -#include "throw_event.h" -#include "milesAudioSound.h" #include "milesAudioManager.h" - TypeHandle MilesAudioSound::_type_handle; - -#define NEED_MILES_LENGTH_WORKAROUND - -#if (((MSS_MAJOR_VERSION == 6) && (MSS_MINOR_VERSION >= 5)) || (MSS_MAJOR_VERSION >= 7)) -#define MILES_6_5_OR_HIGHER -#endif +#undef miles_audio_debug #ifndef NDEBUG //[ -namespace { - //////////////////////////////////////////////////////////////////// - // Function: get_status_char - // Access: - // Description: - //////////////////////////////////////////////////////////////////// - char - get_status_char(HAUDIO audio) { - if (!audio) { - return '0'; // NULL. - } - switch (AIL_quick_status(audio)) { - case QSTAT_LOADED: - case QSTAT_DONE: - return 'r'; // Ready. - case QSTAT_PLAYING: - return 'p'; // Playing. - default: - return 'x'; // bad. - } - } - #define miles_audio_debug(x) \ - audio_debug("MilesAudioSound "<finished(); - if (sequence_callback) { - sequence_callback(S); - } - } - - //////////////////////////////////////////////////////////////////// - // Function: pandaAudioAilCallback_Sample - // Access: file scope - // Description: This function is part of a hack for finish callbacks - // when using the Miles quick API. - //////////////////////////////////////////////////////////////////// - void AILCALLBACK - pandaAudioAilCallback_Sample(HSAMPLE S) { - assert(S); - AutoAilLock milesLock; - audio_debug("pandaAudioAilCallback_Sample(HSAMPLE="<<((void*)S)<<")"); - MilesAudioSound* sound = (MilesAudioSound*)AIL_sample_user_data( - S, user_data_index); - assert(sound); - sound->finished(); - if (sample_callback) { - sample_callback(S); - } - } - - //////////////////////////////////////////////////////////////////// - // Function: panda_AIL_quick_set_finished_callback - // Access: file scope - // Description: This function is part of a hack for finish callbacks - // when using the Miles quick API. - // - // This will determine whether the sound is a MIDI or - // a wave sample and setup the correct callback. - //////////////////////////////////////////////////////////////////// - void - panda_AIL_quick_set_finished_callback(HAUDIO audio, MilesAudioSound* sound) { - audio_debug("panda_AIL_quick_set_finished_callback(audio="<<((void*)audio) - <<", sound="<<((void*)sound)<<")"); - if (!audio || !sound) { - return; - } - AutoAilLock milesLock; - if (audio->handle != NULL) { - switch (audio->type) { - case AIL_QUICK_XMIDI_TYPE: - audio_debug(" AIL_register_sequence_callback"); - AIL_set_sequence_user_data( - (HSEQUENCE)audio->handle, user_data_index, (long)sound); - sequence_callback=AIL_register_sequence_callback( - (HSEQUENCE)audio->handle, pandaAudioAilCallback_Sequence); - audio_debug( "AILCALLBACK "<<((void*)sequence_callback)); - break; - case AIL_QUICK_DIGITAL_TYPE: - case AIL_QUICK_MPEG_DIGITAL_TYPE: - audio_debug(" AIL_register_EOS_callback"); - AIL_set_sample_user_data( - (HSAMPLE)audio->handle, user_data_index, (long)sound); - sample_callback=AIL_register_EOS_callback( - (HSAMPLE)audio->handle, pandaAudioAilCallback_Sample); - audio_debug(" AILCALLBACK "<<((void*)sample_callback)); - break; - default: - audio_debug(" unknown audio type"); - break; - } - } - } - - S32 - Get_playback_rate(HAUDIO audio) { - if (audio) { - switch (audio->type) { - case AIL_QUICK_XMIDI_TYPE: - case AIL_QUICK_DLS_XMIDI_TYPE: - return AIL_sequence_tempo((HSEQUENCE)audio->handle); - break; - case AIL_QUICK_DIGITAL_TYPE: - case AIL_QUICK_MPEG_DIGITAL_TYPE: - if (AIL_quick_status(audio) == QSTAT_PLAYING) { - return AIL_sample_playback_rate((HSAMPLE)audio->handle); - } else { - //HACK: The play rate is returning 0 unless it's playing - if (AIL_quick_play(audio, 1)) { - S32 rate = AIL_sample_playback_rate((HSAMPLE)audio->handle); - AIL_quick_halt(audio); - return rate; - } - } - break; - default: - audio_debug("Get_playback_rate unknown audio type"); - break; - } - } - return 0; - } - - -} - //////////////////////////////////////////////////////////////////// -// Function: MilesAudioSound::MilesAudioSound -// Access: +// Function: MilesAudioSound::Constructor +// Access: Private // Description: //////////////////////////////////////////////////////////////////// MilesAudioSound:: -MilesAudioSound(MilesAudioManager* manager, - MilesAudioManager::SoundData *sd, string file_name, float length) - : _sd(sd), _manager(manager), _file_name(file_name), - _volume(1.0f), _balance(0), - _loop_count(1), _length(length), - _active(true), _paused(false) { - nassertv(sd != NULL); +MilesAudioSound(MilesAudioManager *manager, + const string &file_name) : + _manager(manager), + _file_name(file_name), + _volume(1.0f), _balance(0), _play_rate(1.0f), + _loop_count(1), + _active(true), _paused(false) +{ nassertv(!file_name.empty()); - audio_debug("MilesAudioSound(manager=0x"<<(void*)&manager - <<", sd=0x"<<(void*)sd<<", file_name="<_audio); - _play_rate = 1.0f; - _original_playback_rate = Get_playback_rate(_audio); - audio_debug("MilesAudioSound: original_playback_rate " << _original_playback_rate); -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioSound::~MilesAudioSound -// Access: -// Description: -//////////////////////////////////////////////////////////////////// -MilesAudioSound:: -~MilesAudioSound() { - miles_audio_debug("~MilesAudioSound()"); - cleanup(); - _manager->release_sound(this); - miles_audio_debug("~MilesAudioSound() done"); -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioSound::play -// Access: -// Description: -//////////////////////////////////////////////////////////////////// -void MilesAudioSound:: -play() { - #if 0 - if(_file_name.find(".mid")!=string::npos) { - miles_audio_debug("play() midi"); - } - #endif - - miles_audio_debug("play()"); - if (_active) { - if (status() == AudioSound::PLAYING) { - stop(); - } - nassertv(_audio); - _manager->starting_sound(this); - // Start playing: - if (AIL_quick_play(_audio, _loop_count)) { - //#*#panda_AIL_quick_set_finished_callback(_audio, this); - // assert(status()==PLAYING); - audio_debug(" started sound " << _file_name ); - } else { - audio_debug(" sound " << _file_name<<" failed to start, err: " <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(). - nassertv(_audio); - AIL_quick_halt(_audio); -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioSound::finished -// Access: -// Description: -//////////////////////////////////////////////////////////////////// -void MilesAudioSound:: -finished() { - miles_audio_debug("finished()"); - _manager->stopping_sound(this); - if (!_finished_event.empty()) { - throw_event(_finished_event); - } } //////////////////////////////////////////////////////////////////// // Function: MilesAudioSound::set_loop -// Access: +// Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// void MilesAudioSound:: set_loop(bool loop) { - miles_audio_debug("set_loop(loop="< max_time) { - milesAudio_cat.warning() - << "set_time(" << time << ") requested for sound of length " - << max_time << "\n"; - time = max_time; - } - - S32 millisecond_time=(S32)(1000*time); - AIL_quick_set_ms_position(_audio, millisecond_time); -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioSound:: -// Access: -// Description: -//////////////////////////////////////////////////////////////////// -float MilesAudioSound:: -get_time() const { - nassertr(_audio, 0.0f); - S32 millisecond_time=AIL_quick_ms_position(_audio); - float time=float(millisecond_time*.001); - miles_audio_debug("get_time() returning "<get_volume(); - #ifdef MILES_6_5_OR_HIGHER - // Change to Miles volume, range 0 to 1.0: - F32 milesVolume=volume; - milesVolume=min(milesVolume,1.0f); - milesVolume=max(milesVolume,0.0f); - #else - // Change to Miles volume, range 0 to 127: - S32 milesVolume=((S32)(127*volume))%128; - #endif - // Account for type: - S32 audioType=AIL_quick_type(_audio); - if ((audioType==AIL_QUICK_XMIDI_TYPE) || (audioType==AIL_QUICK_DLS_XMIDI_TYPE)) { - // ...it's a midi file. - - // 0 delay, set to this volume immediately - #ifdef MILES_6_5_OR_HIGHER - F32 midiVolDelay =0.0f; - #else - S32 midiVolDelay =0; - #endif - - AIL_quick_set_volume(_audio, milesVolume, midiVolDelay); - audio_debug(" volume for this midi is now "<get_play_rate(); - switch (_audio->type) { - case AIL_QUICK_XMIDI_TYPE: - case AIL_QUICK_DLS_XMIDI_TYPE: - // midi uses whole percentage values 100 == 100% - _audio->speed = (S32)(play_rate*100.0f); - if ((_audio->speed != -1) && (AIL_quick_status(_audio) == QSTAT_PLAYING)) { - AIL_set_sequence_tempo((HSEQUENCE)_audio->handle, _audio->speed, 0); - } - audio_debug(" play_rate for this midi is now "<<_audio->speed); - break; - case AIL_QUICK_DIGITAL_TYPE: - case AIL_QUICK_MPEG_DIGITAL_TYPE: - // wave and mp3 use sample rate (e.g. 44100) - _audio->speed = (S32)(play_rate*float(_original_playback_rate)); - if ((_audio->speed != -1) && (AIL_quick_status(_audio) == QSTAT_PLAYING)) { - AIL_set_sample_playback_rate((HSAMPLE)_audio->handle, _audio->speed); - } - audio_debug(" play_rate for this wav or mp3 is now "<<_audio->speed); - break; - } - } -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioSound::get_play_rate -// Access: -// Description: -//////////////////////////////////////////////////////////////////// -float MilesAudioSound:: -get_play_rate() const { - miles_audio_debug("get_play_rate() returning "<<_play_rate); - return _play_rate; -} - -//////////////////////////////////////////////////////////////////// -// Function: MilesAudioSound::set_balance -// Access: -// Description: -//////////////////////////////////////////////////////////////////// -void MilesAudioSound:: -set_balance(float balance_right) { - miles_audio_debug("set_balance(balance_right="<get_length(); +get_play_rate() const { + return _play_rate; } //////////////////////////////////////////////////////////////////// // Function: MilesAudioSound::set_active -// Access: +// Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// void MilesAudioSound:: set_active(bool active) { miles_audio_debug("set_active(active="<release_sound(this); + miles_audio_debug("~MilesAudioStream() done"); +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream::play +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioStream:: +play() { + miles_audio_debug("play()"); + if (_active) { + + _manager->starting_sound(this); + + if (_stream == 0) { + GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr(); + _stream = AIL_open_stream(mgr->_digital_driver, _path.c_str(), 0); + } else { + // We already had the stream open. Keep it open; just restart + // it. + AIL_pause_stream(_stream, 1); + _manager->stop_service_stream(_stream); + } + + if (_stream == 0) { + milesAudio_cat.warning() + << "Could not play " << _file_name << ": too many open streams\n"; + + } else { + // Start playing: + HSAMPLE sample = AIL_stream_sample_handle(_stream); + nassertv(sample != 0); + + _original_playback_rate = AIL_sample_playback_rate(sample); + set_volume(_volume); + set_play_rate(_play_rate); + + AIL_set_stream_loop_count(_stream, _loop_count); + + if (miles_audio_panda_threads) { + AIL_auto_service_stream(_stream, 0); + _manager->start_service_stream(_stream); + } else { + AIL_auto_service_stream(_stream, 1); + } + AIL_start_stream(_stream); + } + + } else { + // In case _loop_count gets set to forever (zero): + audio_debug(" paused "<<_file_name ); + _paused = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream::stop +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioStream:: +stop() { + miles_audio_debug("stop()"); + _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(). + if (_stream != 0) { + _manager->stop_service_stream(_stream); + + AIL_pause_stream(_stream, 1); + AIL_close_stream(_stream); + _stream = 0; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream:: +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioStream:: +set_time(float time) { + if (_stream != 0) { + S32 time_ms = (S32)(1000.0f * time); + + // Ensure we don't inadvertently run off the end of the sound. + S32 length_ms; + AIL_stream_ms_position(_stream, &length_ms, NULL); + time_ms = min(time_ms, length_ms); + + AIL_set_stream_ms_position(_stream, time_ms); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream:: +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +float MilesAudioStream:: +get_time() const { + nassertr(_stream, 0.0f); + + S32 current_ms; + AIL_stream_ms_position(_stream, NULL, ¤t_ms); + float time = float(current_ms * 0.001f); + + return time; +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream:: +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioStream:: +set_volume(float volume) { + _volume = volume; + + if (_stream != 0) { + HSAMPLE sample = AIL_stream_sample_handle(_stream); + nassertv(sample != 0); + + volume *= _manager->get_volume(); + + // Change to Miles volume, range 0 to 1.0: + F32 milesVolume = volume; + milesVolume = min(milesVolume, 1.0f); + milesVolume = max(milesVolume, 0.0f); + + // Convert balance of -1.0..1.0 to 0-1.0: + F32 milesBalance = (F32)((_balance + 1.0f) * 0.5f); + + AIL_set_sample_volume_pan(sample, milesVolume, milesBalance); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream::set_balance +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioStream:: +set_balance(float balance_right) { + _balance = balance_right; + + // Call set_volume to effect the change: + set_volume(_volume); +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream::set_play_rate +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void MilesAudioStream:: +set_play_rate(float play_rate) { + _play_rate = play_rate; + + if (_stream != 0) { + HSAMPLE sample = AIL_stream_sample_handle(_stream); + nassertv(sample != 0); + + play_rate *= _manager->get_play_rate(); + + // wave and mp3 use sample rate (e.g. 44100) + S32 speed = (S32)(play_rate * (float)_original_playback_rate); + AIL_set_sample_playback_rate(sample, speed); + audio_debug(" play_rate for this wav or mp3 is now " << speed); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream::length +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +float MilesAudioStream:: +length() const { + if (!_got_length) { + if (_stream == 0) { + GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr(); + ((MilesAudioStream *)this)->_stream = AIL_open_stream(mgr->_digital_driver, _path.c_str(), 0); + } + + S32 length_ms; + AIL_stream_ms_position(_stream, &length_ms, NULL); + _length = (float)length_ms * 0.001f; + _got_length = true; + } + + return _length; +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream::status +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +AudioSound::SoundStatus MilesAudioStream:: +status() const { + if (!_stream) { + return AudioSound::READY; + } + + switch (AIL_stream_status(_stream)) { + case SMP_STOPPED: + case SMP_DONE: + return AudioSound::READY; + case SMP_PLAYING: + case SMP_PLAYINGBUTRELEASED: + return AudioSound::PLAYING; + default: + return AudioSound::BAD; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: MilesAudioStream::cleanup +// Access: Public, Virtual +// Description: Called to release any resources associated with the +// sound. +//////////////////////////////////////////////////////////////////// +void MilesAudioStream:: +cleanup() { + if (_stream) { + stop(); + } +} + + +#endif //] diff --git a/panda/src/audiotraits/milesAudioStream.h b/panda/src/audiotraits/milesAudioStream.h new file mode 100644 index 0000000000..8111c07aa2 --- /dev/null +++ b/panda/src/audiotraits/milesAudioStream.h @@ -0,0 +1,91 @@ +// Filename: milesAudioStream.h +// Created by: drose (26Jul07) +// +//////////////////////////////////////////////////////////////////// +// +// 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 . +// +//////////////////////////////////////////////////////////////////// + +#ifndef MILESAUDIOSTREAM_H +#define MILESAUDIOSTREAM_H + +#include "pandabase.h" +#ifdef HAVE_RAD_MSS //[ + +#include "milesAudioSound.h" +#include "milesAudioManager.h" +#include "mss.h" + +//////////////////////////////////////////////////////////////////// +// Class : MilesAudioStream +// Description : This represents a sound file played by the Miles +// Sound System, similar to MilesAudioSample, except +// that it is streamed from disk instead of preloaded +// into memory. +//////////////////////////////////////////////////////////////////// +class EXPCL_MILES_AUDIO MilesAudioStream : public MilesAudioSound { +private: + MilesAudioStream(MilesAudioManager *manager, const string &file_name, + const Filename &path); + +public: + virtual ~MilesAudioStream(); + + virtual void play(); + virtual void stop(); + + virtual void set_time(float time=0.0f); + virtual float get_time() const; + + virtual void set_volume(float volume=1.0f); + virtual void set_balance(float balance_right=0.0f); + virtual void set_play_rate(float play_rate=1.0f); + + virtual float length() const; + + virtual AudioSound::SoundStatus status() const; + + virtual void cleanup(); + +private: + Filename _path; + HSTREAM _stream; + S32 _original_playback_rate; + mutable float _length; + mutable bool _got_length; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + MilesAudioSound::init_type(); + register_type(_type_handle, "MilesAudioStream", + MilesAudioSound::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class MilesAudioManager; +}; + +#include "milesAudioStream.I" + +#endif //] + +#endif diff --git a/panda/src/audiotraits/miles_audio_composite1.cxx b/panda/src/audiotraits/miles_audio_composite1.cxx index c0ffd54a96..e8dbf2fa8c 100644 --- a/panda/src/audiotraits/miles_audio_composite1.cxx +++ b/panda/src/audiotraits/miles_audio_composite1.cxx @@ -1,5 +1,9 @@ #include "config_milesAudio.cxx" #include "milesAudioManager.cxx" +#include "milesAudioSample.cxx" +#include "milesAudioSequence.cxx" #include "milesAudioSound.cxx" +#include "milesAudioStream.cxx" +#include "globalMilesManager.cxx"