Panda3D
Loading...
Searching...
No Matches
milesAudioManager.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file milesAudioManager.cxx
10 * @author skyler
11 * @date 2001-06-06
12 * Prior system by: cary
13 */
14
15#include "milesAudioManager.h"
16
17#ifdef HAVE_RAD_MSS //[
18
19#include "milesAudioSound.h"
20#include "milesAudioSample.h"
21#include "milesAudioStream.h"
22#include "globalMilesManager.h"
23#include "config_audio.h"
24#include "config_putil.h"
25#include "config_express.h"
26#include "virtualFileSystem.h"
27#include "nullAudioSound.h"
28#include "string_utils.h"
29#include "mutexHolder.h"
30#include "lightReMutexHolder.h"
31
32#include <algorithm>
33
34using std::string;
35
36
37TypeHandle MilesAudioManager::_type_handle;
38
39AudioManager *Create_MilesAudioManager() {
40 audio_debug("Create_MilesAudioManager()");
41 return new MilesAudioManager();
42}
43
44/**
45 * Create an audio manager. This may open the Miles sound system if there
46 * were no other MilesAudioManager instances. Subsequent managers may use the
47 * same Miles resources.
48 */
49MilesAudioManager::
50MilesAudioManager() :
51 _lock("MilesAudioManager::_lock"),
52 _streams_lock("MilesAudioManager::_streams_lock"),
53 _streams_cvar(_streams_lock)
54{
55 audio_debug("MilesAudioManager::MilesAudioManager(), this = "
56 << (void *)this);
57 GlobalMilesManager::get_global_ptr()->add_manager(this);
58 audio_debug(" audio_active="<<audio_active);
59 audio_debug(" audio_volume="<<audio_volume);
60 _cleanup_required = true;
61 _active = audio_active;
62 _volume = audio_volume;
63 _play_rate = 1.0f;
64 _cache_limit = audio_cache_limit;
65 _concurrent_sound_limit = 0;
66 _is_valid = true;
67 _hasMidiSounds = false;
68 _sounds_finished = false;
69
70 // We used to hang a call to a force-shutdown function on atexit(), so that
71 // any running sounds (particularly MIDI sounds) would be silenced on exit,
72 // especially a sudden exit triggered by a Python exception. But that
73 // causes problems because Miles itself also hangs a force-shutdown function
74 // on atexit(), and you can't call AIL_cleanup() twice--that results in a
75 // crash.
76
77 // Nowadays, we provide the AudioManager::shutdown() method instead, which
78 // allows the app to force all sounds to stop cleanly before we get to the
79 // atexit() stack. In Python, we guarantee that this method will be called
80 // in the sys.exitfunc chain.
81}
82
83/**
84 * Clean up this audio manager and possibly release the Miles resources that
85 * are reserved by the application (the later happens if this is the last
86 * active manager).
87 */
88MilesAudioManager::
89~MilesAudioManager() {
90 audio_debug("MilesAudioManager::~MilesAudioManager(), this = "
91 << (void *)this);
92 cleanup();
93 GlobalMilesManager::get_global_ptr()->remove_manager(this);
94
95 audio_debug("MilesAudioManager::~MilesAudioManager() finished");
96}
97
98/**
99 * Call this at exit time to shut down the audio system. This will invalidate
100 * all currently-active AudioManagers and AudioSounds in the system. If you
101 * change your mind and want to play sounds again, you will have to recreate
102 * all of these objects.
103 */
104void MilesAudioManager::
105shutdown() {
106 audio_debug("shutdown() started");
107 GlobalMilesManager::get_global_ptr()->cleanup();
108 audio_debug("shutdown() finished");
109}
110
111/**
112 * This is mostly for debugging, but it it could be used to detect errors in a
113 * release build if you don't mind the cpu cost.
114 */
115bool MilesAudioManager::
116is_valid() {
117 LightReMutexHolder holder(_lock);
118 return do_is_valid();
119}
120
121/**
122 *
123 */
124PT(AudioSound) MilesAudioManager::
125get_sound(const Filename &file_name, bool, int) {
126 LightReMutexHolder holder(_lock);
127 audio_debug("MilesAudioManager::get_sound(file_name=\""<<file_name<<"\")");
128
129 if (!do_is_valid()) {
130 audio_debug("invalid MilesAudioManager returning NullSound");
131 return get_null_sound();
132 }
133
134 Filename path = file_name;
135
137 vfs->resolve_filename(path, get_model_path());
138 audio_debug("Reading "<<path);
139 audio_debug(" resolved file_name is '"<<path<<"'");
140
141 PT(SoundData) sd;
142 // Get the sound, either from the cache or from a loader:
143 SoundMap::const_iterator si=_sounds.find(path);
144 if (si != _sounds.end()) {
145 // ...found the sound in the cache.
146 sd = (*si).second;
147 audio_debug(" sound found in pool 0x" << (void*)sd);
148
149 } else {
150 // ...the sound was not found in the cachepool.
151 sd = load(path);
152 if (sd != nullptr) {
153 while (_sounds.size() >= (unsigned int)_cache_limit) {
154 uncache_a_sound();
155 }
156 // Put it in the pool: The following is roughly like: _sounds[path] =
157 // sd; But, it gives us an iterator into the map.
158 std::pair<SoundMap::const_iterator, bool> ib
159 = _sounds.insert(SoundMap::value_type(path, sd));
160 if (!ib.second) {
161 // The insert failed.
162 audio_debug(" failed map insert of "<<path);
163 nassertr(do_is_valid(), nullptr);
164 return get_null_sound();
165 }
166 // Set si, so that we can get a reference to the path for the
167 // MilesAudioSound.
168 si=ib.first;
169 }
170 }
171 // Create an AudioSound from the sound:
172 PT(AudioSound) audioSound;
173
174 if (sd != nullptr) {
175 most_recently_used((*si).first);
176 if (sd->_file_type == AILFILETYPE_MIDI ||
177 sd->_file_type == AILFILETYPE_XMIDI ||
178 sd->_file_type == AILFILETYPE_XMIDI_DLS ||
179 sd->_file_type == AILFILETYPE_XMIDI_MLS) {
180 // MIDI file.
181 audioSound = new MilesAudioSequence(this, sd, file_name);
182
183 } else if (!sd->_raw_data.empty()) {
184 // WAV or MP3 file preloaded into memory.
185 audioSound = new MilesAudioSample(this, sd, file_name);
186
187 } else {
188 // WAV or MP3 file streamed from disk.
189 audioSound = new MilesAudioStream(this, file_name, path);
190 }
191
192 audioSound->set_active(_active);
193
194 bool inserted = _sounds_on_loan.insert((MilesAudioSound *)audioSound.p()).second;
195 nassertr(inserted, audioSound);
196
197 _hasMidiSounds |= (file_name.find(".mid")!=string::npos);
198 } else {
199 // Couldn't load the file; just return a NullAudioSound.
200 audioSound = new NullAudioSound;
201 }
202
203 audio_debug(" returning 0x" << (void*)audioSound);
204 nassertr(do_is_valid(), nullptr);
205 return audioSound;
206}
207
208/**
209 *
210 */
211PT(AudioSound) MilesAudioManager::
212get_sound(MovieAudio *sound, bool, int) {
213 nassert_raise("Miles audio manager does not support MovieAudio sources.");
214 return nullptr;
215}
216
217/**
218 *
219 */
220void MilesAudioManager::
221uncache_sound(const Filename &file_name) {
222 audio_debug("MilesAudioManager::uncache_sound(file_name=\""
223 <<file_name<<"\")");
224 LightReMutexHolder holder(_lock);
225 nassertv(do_is_valid());
226 Filename path = file_name;
227
229 vfs->resolve_filename(path, get_model_path());
230
231 audio_debug(" path=\""<<path<<"\"");
232 SoundMap::iterator i = _sounds.find(path);
233 if (i != _sounds.end()) {
234 nassertv(_lru.size() > 0);
235 LRU::iterator lru_i = find(_lru.begin(), _lru.end(), &(i->first));
236 nassertv(lru_i != _lru.end());
237 _lru.erase(lru_i);
238 _sounds.erase(i);
239 }
240 nassertv(do_is_valid());
241}
242
243/**
244 * Clear out the sound cache.
245 */
246void MilesAudioManager::
247clear_cache() {
248 audio_debug("MilesAudioManager::clear_cache()");
249 LightReMutexHolder holder(_lock);
250 do_clear_cache();
251}
252
253/**
254 * Set the number of sounds that the cache can hold.
255 */
256void MilesAudioManager::
257set_cache_limit(unsigned int count) {
258 LightReMutexHolder holder(_lock);
259
260 audio_debug("MilesAudioManager::set_cache_limit(count="<<count<<")");
261 nassertv(do_is_valid());
262 while (_lru.size() > count) {
263 uncache_a_sound();
264 }
265 _cache_limit=count;
266 nassertv(do_is_valid());
267}
268
269/**
270 *
271 */
272unsigned int MilesAudioManager::
273get_cache_limit() const {
274 return _cache_limit;
275}
276
277/**
278 * set the overall volume
279 */
280void MilesAudioManager::
281set_volume(PN_stdfloat volume) {
282 audio_debug("MilesAudioManager::set_volume(volume="<<volume<<")");
283 LightReMutexHolder holder(_lock);
284 if (_volume != volume) {
285 _volume = volume;
286 // Tell our AudioSounds to adjust:
287 AudioSet::iterator i = _sounds_on_loan.begin();
288 for (; i!=_sounds_on_loan.end(); ++i) {
289 (*i)->set_volume((*i)->get_volume());
290 }
291 }
292}
293
294/**
295 * get the overall volume
296 */
297PN_stdfloat MilesAudioManager::
298get_volume() const {
299 return _volume;
300}
301
302/**
303 * set the overall play rate
304 */
305void MilesAudioManager::
306set_play_rate(PN_stdfloat play_rate) {
307 audio_debug("MilesAudioManager::set_play_rate(play_rate="<<play_rate<<")");
308 LightReMutexHolder holder(_lock);
309 if (_play_rate != play_rate) {
310 _play_rate = play_rate;
311 // Tell our AudioSounds to adjust:
312 AudioSet::iterator i = _sounds_on_loan.begin();
313 for (; i != _sounds_on_loan.end(); ++i) {
314 (*i)->set_play_rate((*i)->get_play_rate());
315 }
316 }
317}
318
319/**
320 * get the overall speed/pitch/play rate
321 */
322PN_stdfloat MilesAudioManager::
323get_play_rate() const {
324 return _play_rate;
325}
326
327/**
328 * turn on/off
329 */
330void MilesAudioManager::
331set_active(bool active) {
332 audio_debug("MilesAudioManager::set_active(flag="<<active<<")");
333 LightReMutexHolder holder(_lock);
334 if (_active != active) {
335 _active=active;
336 // Tell our AudioSounds to adjust:
337 AudioSet::iterator i = _sounds_on_loan.begin();
338 for (; i != _sounds_on_loan.end(); ++i) {
339 (*i)->set_active(_active);
340 }
341
342 if ((!_active) && _hasMidiSounds) {
343 GlobalMilesManager::get_global_ptr()->force_midi_reset();
344 }
345 }
346}
347
348/**
349 *
350 */
351bool MilesAudioManager::
352get_active() const {
353 return _active;
354}
355
356/**
357 *
358 */
359void MilesAudioManager::
360set_concurrent_sound_limit(unsigned int limit) {
361 LightReMutexHolder holder(_lock);
362 _concurrent_sound_limit = limit;
363 do_reduce_sounds_playing_to(_concurrent_sound_limit);
364}
365
366/**
367 *
368 */
369unsigned int MilesAudioManager::
370get_concurrent_sound_limit() const {
371 return _concurrent_sound_limit;
372}
373
374/**
375 *
376 */
377void MilesAudioManager::
378reduce_sounds_playing_to(unsigned int count) {
379 LightReMutexHolder holder(_lock);
380 do_reduce_sounds_playing_to(count);
381}
382
383/**
384 * Stop playback on all sounds managed by this manager.
385 */
386void MilesAudioManager::
387stop_all_sounds() {
388 audio_debug("MilesAudioManager::stop_all_sounds()");
389 reduce_sounds_playing_to(0);
390}
391
392/**
393 * Set spatial attributes of the listener for 3D sounds. Note that Y and Z
394 * are switched to translate into Miles's coordinate system.
395 */
396void MilesAudioManager::audio_3d_set_listener_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz, PN_stdfloat vx, PN_stdfloat vy, PN_stdfloat vz, PN_stdfloat fx, PN_stdfloat fy, PN_stdfloat fz, PN_stdfloat ux, PN_stdfloat uy, PN_stdfloat uz) {
397 audio_debug("MilesAudioManager::audio_3d_set_listener_attributes()");
398
399 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
400 AIL_set_listener_3D_position(mgr->_digital_driver, px, pz, py);
401 AIL_set_listener_3D_velocity_vector(mgr->_digital_driver, vx, vz, vy);
402 AIL_set_listener_3D_orientation(mgr->_digital_driver, fx, fz, fy, ux, uz, uy);
403}
404
405/**
406 * Get spatial attributes of the listener for 3D sounds. Note that Y and Z
407 * are switched to translate from Miles's coordinate system.
408 */
409void MilesAudioManager::audio_3d_get_listener_attributes(PN_stdfloat *px, PN_stdfloat *py, PN_stdfloat *pz, PN_stdfloat *vx, PN_stdfloat *vy, PN_stdfloat *vz, PN_stdfloat *fx, PN_stdfloat *fy, PN_stdfloat *fz, PN_stdfloat *ux, PN_stdfloat *uy, PN_stdfloat *uz) {
410 audio_debug("MilesAudioManager::audio_3d_get_listener_attributes()");
411
412 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
413 float lpx, lpy, lpz, lvx, lvy, lvz, lfx, lfy, lfz, lux, luy, luz;
414 AIL_listener_3D_position(mgr->_digital_driver, &lpx, &lpz, &lpy);
415 AIL_listener_3D_velocity(mgr->_digital_driver, &lvx, &lvz, &lvy);
416 AIL_listener_3D_orientation(mgr->_digital_driver, &lfx, &lfz, &lfy, &lux, &luz, &luy);
417
418 *px = lpx;
419 *py = lpy;
420 *pz = lpz;
421 *vx = lvx;
422 *vy = lvy;
423 *vz = lvz;
424 *fx = lfx;
425 *fy = lfy;
426 *fz = lfz;
427 *ux = lux;
428 *uy = luy;
429 *uz = luz;
430}
431
432/**
433 * Set factor to allow user to easily work in a different scale. 1.0
434 * represents meters.
435 */
436void MilesAudioManager::audio_3d_set_distance_factor(PN_stdfloat factor) {
437 audio_debug("MilesAudioManager::audio_3d_set_distance_factor( factor= " << factor << ")");
438
439 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
440 AIL_set_3D_distance_factor(mgr->_digital_driver, factor);
441}
442
443/**
444 * Get factor controlling working units.
445 */
446PN_stdfloat MilesAudioManager::audio_3d_get_distance_factor() const {
447 audio_debug("MilesAudioManager::audio_3d_get_distance_factor()");
448
449 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
450 return AIL_3D_distance_factor(mgr->_digital_driver);
451}
452
453/**
454 * Exaggerates or diminishes the Doppler effect. Defaults to 1.0
455 */
456void MilesAudioManager::audio_3d_set_doppler_factor(PN_stdfloat factor) {
457 audio_debug("MilesAudioManager::audio_3d_set_doppler_factor(factor="<<factor<<")");
458
459 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
460 AIL_set_3D_doppler_factor(mgr->_digital_driver, factor);
461}
462
463/**
464 * Get the factor controlling the Doppler effect.
465 */
466PN_stdfloat MilesAudioManager::audio_3d_get_doppler_factor() const {
467 audio_debug("MilesAudioManager::audio_3d_get_doppler_factor()");
468
469 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
470 return AIL_3D_doppler_factor(mgr->_digital_driver);
471}
472
473/**
474 * Control the effect distance has on audability. Defaults to 1.0
475 */
476void MilesAudioManager::audio_3d_set_drop_off_factor(PN_stdfloat factor) {
477 audio_debug("MilesAudioManager::audio_3d_set_drop_off_factor("<<factor<<")");
478
479 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
480 AIL_set_3D_rolloff_factor(mgr->_digital_driver, factor);
481}
482
483/**
484 * Get the factor controlling how quickly sound falls off with distance.
485 */
486PN_stdfloat MilesAudioManager::audio_3d_get_drop_off_factor() const {
487 audio_debug("MilesAudioManager::audio_3d_get_drop_off_factor()");
488
489 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
490 return AIL_3D_rolloff_factor(mgr->_digital_driver);
491}
492
493/**
494 * Works similarly to MilesAudioSound::set_speaker_levels, but specifies the
495 * 3D positions of the speakers in space.
496 *
497 * Once a NULL value is found for a speaker position, no more speaker
498 * positions will be used.
499 *
500 * Note that Y and Z are switched to translate from Miles's coordinate system.
501 */
502void MilesAudioManager::
503set_speaker_configuration(LVecBase3 *speaker1, LVecBase3 *speaker2, LVecBase3 *speaker3, LVecBase3 *speaker4, LVecBase3 *speaker5, LVecBase3 *speaker6, LVecBase3 *speaker7, LVecBase3 *speaker8, LVecBase3 *speaker9) {
504 audio_debug("MilesAudioManager::set_speaker_configuration()");
505
506 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
507
508 MSSVECTOR3D speakers[9];
509
510 if(speaker1 != nullptr) {
511 speakers[0].x = speaker1->get_x();
512 speakers[0].y = speaker1->get_z();
513 speakers[0].z = speaker1->get_y();
514 }
515 if(speaker2 != nullptr) {
516 speakers[1].x = speaker2->get_x();
517 speakers[1].y = speaker2->get_z();
518 speakers[1].z = speaker2->get_y();
519 }
520 if(speaker3 != nullptr) {
521 speakers[2].x = speaker3->get_x();
522 speakers[2].y = speaker3->get_z();
523 speakers[2].z = speaker3->get_y();
524 }
525 if(speaker4 != nullptr) {
526 speakers[3].x = speaker4->get_x();
527 speakers[3].y = speaker4->get_z();
528 speakers[3].z = speaker4->get_y();
529 }
530 if(speaker5 != nullptr) {
531 speakers[4].x = speaker5->get_x();
532 speakers[4].y = speaker5->get_z();
533 speakers[4].z = speaker5->get_y();
534 }
535 if(speaker6 != nullptr) {
536 speakers[5].x = speaker6->get_x();
537 speakers[5].y = speaker6->get_z();
538 speakers[5].z = speaker6->get_y();
539 }
540 if(speaker7 != nullptr) {
541 speakers[6].x = speaker7->get_x();
542 speakers[6].y = speaker7->get_z();
543 speakers[6].z = speaker7->get_y();
544 }
545 if(speaker8 != nullptr) {
546 speakers[7].x = speaker8->get_x();
547 speakers[7].y = speaker8->get_z();
548 speakers[7].z = speaker8->get_y();
549 }
550 if(speaker9 != nullptr) {
551 speakers[8].x = speaker9->get_x();
552 speakers[8].y = speaker9->get_z();
553 speakers[8].z = speaker9->get_y();
554 }
555
556 if(speaker1 == nullptr) {
557 audio_error("No valid speaker positions specified in MilesAudioManager::set_speaker_configuration().");
558 } else if(speaker2 == nullptr) {
559 AIL_set_speaker_configuration(mgr->_digital_driver, speakers, 1, 1.0);
560 } else if(speaker3 == nullptr) {
561 AIL_set_speaker_configuration(mgr->_digital_driver, speakers, 2, 1.0);
562 } else if(speaker4 == nullptr) {
563 AIL_set_speaker_configuration(mgr->_digital_driver, speakers, 3, 1.0);
564 } else if(speaker5 == nullptr) {
565 AIL_set_speaker_configuration(mgr->_digital_driver, speakers, 4, 1.0);
566 } else if(speaker6 == nullptr) {
567 AIL_set_speaker_configuration(mgr->_digital_driver, speakers, 5, 1.0);
568 } else if(speaker7 == nullptr) {
569 AIL_set_speaker_configuration(mgr->_digital_driver, speakers, 6, 1.0);
570 } else if(speaker8 == nullptr) {
571 AIL_set_speaker_configuration(mgr->_digital_driver, speakers, 7, 1.0);
572 } else if(speaker9 == nullptr) {
573 AIL_set_speaker_configuration(mgr->_digital_driver, speakers, 8, 1.0);
574 } else {
575 AIL_set_speaker_configuration(mgr->_digital_driver, speakers, 9, 1.0);
576 }
577}
578
579/**
580 * Must be called every frame. Failure to call this every frame could cause
581 * problems for some audio managers.
582 */
583void MilesAudioManager::
584update() {
585 {
586 MutexHolder holder(_streams_lock);
587 if (_stream_thread.is_null() && !_streams.empty()) {
588 // If we don't have a sub-thread, we have to service the streams in the
589 // main thread.
590 do_service_streams();
591 }
592 }
593
594 if (_sounds_finished) {
595 _sounds_finished = false;
596
597 // If the _sounds_finished flag was set, we should scan our list of
598 // playing sounds and see if any of them have finished recently. We don't
599 // do this in the finished callback, because that might have been called
600 // in a sub-thread (and we may not have threading supported--and mutex
601 // protection--compiled in).
602
603 SoundsPlaying::iterator si = _sounds_playing.begin();
604 while (si != _sounds_playing.end()) {
605 MilesAudioSound *sound = (*si);
606 ++si;
607
608 if (sound->status() == AudioSound::READY) {
609 sound->stop();
610 }
611 }
612 }
613}
614
615/**
616 *
617 */
618void MilesAudioManager::
619release_sound(MilesAudioSound *audioSound) {
620 audio_debug("MilesAudioManager::release_sound(audioSound=\""
621 <<audioSound->get_name()<<"\"), this = " << (void *)this);
622 LightReMutexHolder holder(_lock);
623 AudioSet::iterator ai = _sounds_on_loan.find(audioSound);
624 if (ai != _sounds_on_loan.end()) {
625 _sounds_on_loan.erase(ai);
626 }
627
628 audio_debug("MilesAudioManager::release_sound() finished");
629}
630
631/**
632 * Shuts down the audio manager and releases any resources associated with it.
633 * Also cleans up all AudioSounds created via the manager.
634 */
635void MilesAudioManager::
636cleanup() {
637 audio_debug("MilesAudioManager::cleanup(), this = " << (void *)this
638 << ", _cleanup_required = " << _cleanup_required);
639 LightReMutexHolder holder(_lock);
640 if (!_cleanup_required) {
641 return;
642 }
643
644 // Be sure to cleanup associated sounds before cleaning up the manager:
645 AudioSet orig_sounds;
646 orig_sounds.swap(_sounds_on_loan);
647 AudioSet::iterator ai;
648 for (ai = orig_sounds.begin(); ai != orig_sounds.end(); ++ai) {
649 (*ai)->cleanup();
650 }
651
652 do_clear_cache();
653
654 // Now stop the thread, if it has been started.
655 if (!_stream_thread.is_null()) {
656 milesAudio_cat.info()
657 << "Stopping audio streaming thread.\n";
658 PT(StreamThread) old_thread;
659 {
660 MutexHolder holder(_streams_lock);
661 nassertv(!_stream_thread.is_null());
662 _stream_thread->_keep_running = false;
663 _streams_cvar.notify();
664 old_thread = _stream_thread;
665 _stream_thread.clear();
666 }
667 old_thread->join();
668 }
669
670 _cleanup_required = false;
671 audio_debug("MilesAudioManager::cleanup() finished");
672}
673
674/**
675 *
676 */
677void MilesAudioManager::
678output(std::ostream &out) const {
679 LightReMutexHolder holder(_lock);
680 out << get_type() << ": " << _sounds_playing.size()
681 << " / " << _sounds_on_loan.size() << " sounds playing / total";
682}
683
684/**
685 *
686 */
687void MilesAudioManager::
688write(std::ostream &out) const {
689 LightReMutexHolder holder(_lock);
690
691 out << (*this) << "\n";
692 AudioSet::const_iterator ai;
693 for (ai = _sounds_on_loan.begin(); ai != _sounds_on_loan.end(); ++ai) {
694 MilesAudioSound *sound = (*ai);
695 out << " " << *sound << "\n";
696 }
697
698 size_t total_preload = 0;
699 size_t num_preloaded = 0;
700 SoundMap::const_iterator si;
701 for (si = _sounds.begin(); si != _sounds.end(); ++si) {
702 if (!(*si).second->_raw_data.empty()) {
703 ++num_preloaded;
704 total_preload += (*si).second->_raw_data.size();
705 }
706 }
707 out << num_preloaded << " of " << _sounds.size() << " sounds preloaded, size used is " << (total_preload + 1023) / 1024 << "K\n";
708
709 {
710 MutexHolder holder(_streams_lock);
711 out << _streams.size() << " streams opened.\n";
712 if (!_stream_thread.is_null()) {
713 out << "(Audio streaming thread has been started.)\n";
714 }
715 }
716
717 GlobalMilesManager *mgr = GlobalMilesManager::get_global_ptr();
718
719 int num_samples = mgr->get_num_samples();
720 out << num_samples << " sample handles allocated globally.\n";
721
722 int num_sequences = mgr->get_num_sequences();
723 out << num_sequences << " sequence handles allocated globally.\n";
724}
725
726
727/**
728 * Implementation of is_valid(). Assumes the lock is already held.
729 */
730bool MilesAudioManager::
731do_is_valid() {
732 bool check = true;
733 if (_sounds.size() != _lru.size()) {
734 audio_debug("-- Error _sounds.size() != _lru.size() --");
735 check = false;
736
737 } else {
738 LRU::const_iterator i = _lru.begin();
739 for (; i != _lru.end(); ++i) {
740 SoundMap::const_iterator smi = _sounds.find(**i);
741 if (smi == _sounds.end()) {
742 audio_debug("-- "<<**i<<" in _lru and not in _sounds --");
743 check = false;
744 break;
745 }
746 }
747 }
748 return _is_valid && check;
749}
750
751/**
752 * Assumes the lock is already held.
753 */
754void MilesAudioManager::
755do_reduce_sounds_playing_to(unsigned int count) {
756 int limit = _sounds_playing.size() - count;
757 while (limit-- > 0) {
758 SoundsPlaying::iterator sound = _sounds_playing.begin();
759 assert(sound != _sounds_playing.end());
760 (**sound).stop();
761 }
762}
763
764/**
765 * Assumes the lock is already held.
766 */
767void MilesAudioManager::
768do_clear_cache() {
769 if (_is_valid) { nassertv(do_is_valid()); }
770 _sounds.clear();
771 _lru.clear();
772 if (_is_valid) { nassertv(do_is_valid()); }
773}
774
775/**
776 * Adds the indicated stream to the list of streams to be serviced by a Panda
777 * sub-thread. This is in lieu of Miles' auto-service-stream mechanism.
778 */
779void MilesAudioManager::
780start_service_stream(HSTREAM stream) {
781 MutexHolder holder(_streams_lock);
782 nassertv(find(_streams.begin(), _streams.end(), stream) == _streams.end());
783 _streams.push_back(stream);
784 _streams_cvar.notify();
785
786 if (_stream_thread.is_null() && Thread::is_threading_supported()) {
787 milesAudio_cat.info()
788 << "Starting audio streaming thread.\n";
789 _stream_thread = new StreamThread(this);
790 _stream_thread->start(TP_low, true);
791 }
792}
793
794/**
795 * Removes the indicated stream from the list of streams to be serviced by a
796 * Panda sub-thread.
797 */
798void MilesAudioManager::
799stop_service_stream(HSTREAM stream) {
800 MutexHolder holder(_streams_lock);
801 Streams::iterator si = find(_streams.begin(), _streams.end(), stream);
802 if (si != _streams.end()) {
803 _streams.erase(si);
804 }
805}
806
807
808/**
809 * Assumes the lock is already held.
810 */
811void MilesAudioManager::
812most_recently_used(const string &path) {
813 audio_debug("MilesAudioManager::most_recently_used(path=\""
814 <<path<<"\")");
815 LRU::iterator i=find(_lru.begin(), _lru.end(), &path);
816 if (i != _lru.end()) {
817 _lru.erase(i);
818 }
819 // At this point, path should not exist in the _lru:
820 assert(find(_lru.begin(), _lru.end(), &path) == _lru.end());
821 _lru.push_back(&path);
822 nassertv(do_is_valid());
823}
824
825/**
826 * Assumes the lock is already held.
827 */
828void MilesAudioManager::
829uncache_a_sound() {
830 audio_debug("MilesAudioManager::uncache_a_sound()");
831 nassertv(do_is_valid());
832 // uncache least recently used:
833 assert(_lru.size()>0);
834 LRU::reference path=_lru.front();
835 SoundMap::iterator i = _sounds.find(*path);
836 assert(i != _sounds.end());
837 _lru.pop_front();
838
839 if (i != _sounds.end()) {
840 audio_debug(" uncaching \""<<i->first<<"\"");
841 _sounds.erase(i);
842 }
843 nassertv(do_is_valid());
844}
845
846/**
847 * Inform the manager that a sound is about to play.
848 */
849void MilesAudioManager::
850starting_sound(MilesAudioSound *audio) {
851 LightReMutexHolder holder(_lock);
852 if (_concurrent_sound_limit) {
853 do_reduce_sounds_playing_to(_concurrent_sound_limit);
854 }
855 _sounds_playing.insert(audio);
856}
857
858/**
859 * Inform the manager that a sound is finished or someone called stop on the
860 * sound (this should not be called if a sound is only paused).
861 */
862void MilesAudioManager::
863stopping_sound(MilesAudioSound *audio) {
864 LightReMutexHolder holder(_lock);
865 _sounds_playing.erase(audio);
866 if (_hasMidiSounds && _sounds_playing.size() == 0) {
867 GlobalMilesManager::get_global_ptr()->force_midi_reset();
868 }
869}
870
871/**
872 * Reads a sound file and allocates a SoundData pointer for it. Returns NULL
873 * if the sound file cannot be loaded.
874 *
875 * Assumes the lock is already held.
876 */
877PT(MilesAudioManager::SoundData) MilesAudioManager::
878load(const Filename &file_name) {
879 PT(SoundData) sd = new SoundData;
880
882 PT(VirtualFile) file = vfs->get_file(file_name);
883 if (file == nullptr) {
884 milesAudio_cat.warning()
885 << "No such file: " << file_name << "\n";
886 return nullptr;
887 }
888
889 if (file->get_file_size() == 0) {
890 milesAudio_cat.warning()
891 << "File " << file_name << " is empty\n";
892 return nullptr;
893 }
894
895 sd->_basename = file_name.get_basename();
896
897 string extension = sd->_basename.get_extension();
898 if (extension == "pz" || extension == "gz") {
899 extension = Filename(sd->_basename.get_basename_wo_extension()).get_extension();
900 }
901
902 bool is_midi_file = (downcase(extension) == "mid");
903
904 if ((miles_audio_preload_threshold == -1 || file->get_file_size() < (std::streamsize)miles_audio_preload_threshold) ||
905 is_midi_file) {
906 // If the file is sufficiently small, we'll preload it into memory. MIDI
907 // files cannot be streamed, so we always preload them, regardless of
908 // size.
909
910 if (!file->read_file(sd->_raw_data, true)) {
911 milesAudio_cat.warning()
912 << "Unable to read " << file_name << "\n";
913 return nullptr;
914 }
915
916 sd->_file_type =
917 AIL_file_type(&sd->_raw_data[0], sd->_raw_data.size());
918
919 if (sd->_file_type == AILFILETYPE_MIDI) {
920 // A standard MIDI file. We have to convert this to XMIDI for Miles.
921 void *xmi;
922 U32 xmi_size;
923 if (AIL_MIDI_to_XMI(&sd->_raw_data[0], sd->_raw_data.size(),
924 &xmi, &xmi_size, 0)) {
925 audio_debug("converted " << sd->_basename << " from standard MIDI ("
926 << sd->_raw_data.size() << " bytes) to XMIDI ("
927 << xmi_size << " bytes)");
928
929 // Copy the data to our own buffer and free the Miles-allocated data.
930 sd->_raw_data.clear();
931 sd->_raw_data.insert(sd->_raw_data.end(),
932 (unsigned char *)xmi, (unsigned char *)xmi + xmi_size);
933 AIL_mem_free_lock(xmi);
934 sd->_file_type = AILFILETYPE_XMIDI;
935 } else {
936 milesAudio_cat.warning()
937 << "Could not convert " << sd->_basename << " to XMIDI.\n";
938 }
939 }
940
941 bool expand_to_wav = false;
942
943 if (sd->_file_type != AILFILETYPE_MPEG_L3_AUDIO) {
944 audio_debug(sd->_basename << " is not an mp3 file.");
945 } else if ((int)sd->_raw_data.size() >= miles_audio_expand_mp3_threshold) {
946 audio_debug(sd->_basename << " is too large to expand in-memory.");
947 } else {
948 audio_debug(sd->_basename << " will be expanded in-memory.");
949 expand_to_wav = true;
950 }
951
952 if (expand_to_wav) {
953 // Now convert the file to WAV format in-memory. This is useful to work
954 // around seek and length problems associated with variable bit-rate MP3
955 // encoding.
956 void *wav_data;
957 U32 wav_data_size;
958 if (AIL_decompress_ASI(&sd->_raw_data[0], sd->_raw_data.size(),
959 sd->_basename.c_str(), &wav_data, &wav_data_size,
960 nullptr)) {
961 audio_debug("expanded " << sd->_basename << " from " << sd->_raw_data.size()
962 << " bytes to " << wav_data_size << " bytes.");
963
964 if (wav_data_size != 0) {
965 // Now copy the memory into our own buffers, and free the Miles-
966 // allocated memory.
967 sd->_raw_data.clear();
968 sd->_raw_data.insert(sd->_raw_data.end(),
969 (unsigned char *)wav_data, (unsigned char *)wav_data + wav_data_size);
970 sd->_file_type = AILFILETYPE_PCM_WAV;
971 sd->_basename.set_extension("wav");
972 }
973 AIL_mem_free_lock(wav_data);
974
975 } else {
976 audio_debug("unable to expand " << sd->_basename);
977 }
978 }
979
980 } else {
981 // If the file is large, we'll stream it from disk instead of preloading
982 // it. This means we don't need to load any data at this point.
983 }
984
985 return sd;
986}
987
988/**
989 * Called to service the streaming audio channels currently playing on the
990 * audio manager.
991 */
992void MilesAudioManager::
993thread_main(volatile bool &keep_running) {
994 MutexHolder holder(_streams_lock);
995
996 while (keep_running) {
997 if (_streams.empty()) {
998 // If there are no streams to service, block on the condition variable.
999 _streams_cvar.wait();
1000 } else {
1001 do_service_streams();
1002 }
1003
1004 // Now yield to be polite to the main application.
1005 _streams_lock.release();
1007 _streams_lock.acquire();
1008 }
1009}
1010
1011/**
1012 * Internal function to service all the streams. Assumes _streams_lock is
1013 * already held.
1014 */
1015void MilesAudioManager::
1016do_service_streams() {
1017 size_t i = 0;
1018 while (i < _streams.size()) {
1019 HSTREAM stream = _streams[i];
1020
1021 // We must release the lock while we are servicing stream i.
1022 _streams_lock.release();
1023 AIL_service_stream(stream, 0);
1025 _streams_lock.acquire();
1026
1027 ++i;
1028 }
1029}
1030
1031/**
1032 *
1033 */
1034MilesAudioManager::StreamThread::
1035StreamThread(MilesAudioManager *mgr) :
1036 Thread("StreamThread", "StreamThread"),
1037 _mgr(mgr)
1038{
1039 _keep_running = true;
1040}
1041
1042/**
1043 *
1044 */
1045void MilesAudioManager::StreamThread::
1046thread_main() {
1047 _mgr->thread_main(_keep_running);
1048}
1049
1050/**
1051 *
1052 */
1053MilesAudioManager::SoundData::
1054SoundData() :
1055 _raw_data(MilesAudioManager::get_class_type()),
1056 _has_length(false),
1057 _length(0.0f)
1058{
1059}
1060
1061/**
1062 *
1063 */
1064MilesAudioManager::SoundData::
1065~SoundData() {
1066}
1067
1068/**
1069 *
1070 */
1071PN_stdfloat MilesAudioManager::SoundData::
1072get_length() {
1073 if (!_has_length) {
1074 // Time to determine the length of the file.
1075
1076 if (_raw_data.empty()) {
1077 _length = 0.0f;
1078 _has_length = true;
1079
1080 } else if (_file_type == AILFILETYPE_MPEG_L3_AUDIO) {
1081 // If it's an mp3 file, we have to calculate its length by walking
1082 // through all of its frames.
1083 audio_debug("Computing length of mp3 file " << _basename);
1084
1085 MP3_INFO info;
1086 AIL_inspect_MP3(&info, &_raw_data[0], _raw_data.size());
1087 _length = 0.0f;
1088 while (AIL_enumerate_MP3_frames(&info)) {
1089 _length += info.data_size * 8.0f / info.bit_rate;
1090 }
1091 _has_length = true;
1092
1093 } else if (_file_type == AILFILETYPE_PCM_WAV ||
1094 _file_type == AILFILETYPE_ADPCM_WAV ||
1095 _file_type == AILFILETYPE_OTHER_ASI_WAV) {
1096 audio_debug("Getting length of wav file " << _basename);
1097
1098 AILSOUNDINFO info;
1099 if (AIL_WAV_info(&_raw_data[0], &info)) {
1100 _length = (PN_stdfloat)info.samples / (PN_stdfloat)info.rate;
1101 audio_debug(info.samples << " samples at " << info.rate
1102 << "; length is " << _length << " seconds.");
1103 _has_length = true;
1104 }
1105 }
1106 }
1107
1108 nassertr(_has_length, 0.0f);
1109 return _length;
1110}
1111
1112/**
1113 * Records the sample length, as determined externally.
1114 */
1115void MilesAudioManager::SoundData::
1116set_length(PN_stdfloat length) {
1117 _length = length;
1118 _has_length = true;
1119}
1120
1121#endif //]
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
std::string get_basename() const
Returns the basename part of the filename.
Definition filename.I:367
std::string get_extension() const
Returns the file extension.
Definition filename.I:400
Similar to MutexHolder, but for a light reentrant mutex.
A MovieAudio is actually any source that provides a sequence of audio samples.
Definition movieAudio.h:44
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition mutexHolder.h:25
A thread; that is, a lightweight process.
Definition thread.h:46
is_threading_supported
Returns true if threading support has been compiled in and enabled, or false if no threading is avail...
Definition thread.h:112
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
Definition thread.I:212
static void force_yield()
Suspends the current thread for the rest of the current epoch.
Definition thread.I:201
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
A hierarchy of directories and files that appears to be one continuous file system,...
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const std::string &default_extension=std::string()) const
Searches the given search path for the filename.
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
The abstract base class for a file or directory within the VirtualFileSystem.
Definition virtualFile.h:35
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
string downcase(const string &s)
Returns the input string with all uppercase letters converted to lowercase.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.