Panda3D
globalMilesManager.cxx
1 // Filename: globalMilesManager.cxx
2 // Created by: drose (26Jul07)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "globalMilesManager.h"
16 
17 #ifdef HAVE_RAD_MSS //[
18 
19 #include "lightMutexHolder.h"
20 #include "milesAudioManager.h"
21 #include "milesAudioSample.h"
22 #include "milesAudioSequence.h"
23 
24 #ifdef WIN32
25 // For midiOutReset()
26 #include <windows.h>
27 #include <mmsystem.h>
28 #endif
29 
30 GlobalMilesManager *GlobalMilesManager::_global_ptr;
31 
32 ////////////////////////////////////////////////////////////////////
33 // Function: GlobalMilesManager::Constructor
34 // Access: Private
35 // Description:
36 ////////////////////////////////////////////////////////////////////
37 GlobalMilesManager::
38 GlobalMilesManager() :
39  _managers_lock("GlobalMilesManager::_managers_lock"),
40  _samples_lock("GlobalMilesManager::_samples_lock"),
41  _sequences_lock("GlobalMilesManager::_sequences_lock")
42 {
43  _digital_driver = 0;
44  _midi_driver = 0;
45  _dls_device = 0;
46  _dls_file = 0;
47  _is_open = false;
48 }
49 
50 ////////////////////////////////////////////////////////////////////
51 // Function: GlobalMilesManager::add_manager
52 // Access: Public
53 // Description: Records a new MilesAudioManager in the world. This
54 // will open the Miles API when the first audio manager
55 // is added.
56 ////////////////////////////////////////////////////////////////////
57 void GlobalMilesManager::
58 add_manager(MilesAudioManager *manager) {
59  LightMutexHolder holder(_managers_lock);
60  _managers.insert(manager);
61  if (!_is_open) {
62  open_api();
63  }
64 }
65 
66 ////////////////////////////////////////////////////////////////////
67 // Function: GlobalMilesManager::remove_manager
68 // Access: Public
69 // Description: Records that a MilesAudioManager is destructing.
70 // This will clsoe the Miles API when the last audio
71 // manager is removed.
72 ////////////////////////////////////////////////////////////////////
73 void GlobalMilesManager::
74 remove_manager(MilesAudioManager *manager) {
75  LightMutexHolder holder(_managers_lock);
76  _managers.erase(manager);
77  if (_managers.empty() && _is_open) {
78  close_api();
79  }
80 }
81 
82 ////////////////////////////////////////////////////////////////////
83 // Function: GlobalMilesManager::cleanup
84 // Access: Public
85 // Description: Calls cleanup() on all MilesAudioManagers, to cause a
86 // clean shutdown.
87 ////////////////////////////////////////////////////////////////////
88 void GlobalMilesManager::
89 cleanup() {
90  LightMutexHolder holder(_managers_lock);
91  Managers::iterator mi;
92  for (mi = _managers.begin(); mi != _managers.end(); ++mi) {
93  (*mi)->cleanup();
94  }
95 }
96 
97 ////////////////////////////////////////////////////////////////////
98 // Function: GlobalMilesManager::get_sample
99 // Access: Public
100 // Description: Gets a sample handle from the global pool for the
101 // digital output device, to be used with the indicated
102 // AudioSound.
103 //
104 // If successful, sets the sample handle and the index
105 // (which should later be used to release the sample)
106 // and returns true. If unsuccessful (because there are
107 // no more available handles), returns false.
108 //
109 // This is a very limited resource; you should only get
110 // a sample just before playing a sound.
111 ////////////////////////////////////////////////////////////////////
112 bool GlobalMilesManager::
113 get_sample(HSAMPLE &sample, size_t &index, MilesAudioSample *sound) {
114  LightMutexHolder holder(_samples_lock);
115 
116  for (size_t i = 0; i < _samples.size(); ++i) {
117  SampleData &smp = _samples[i];
118  if (AIL_sample_status(smp._sample) == SMP_DONE) {
119  if (smp._sound != NULL) {
120  // Tell the last sound that was using this sample that it's
121  // done now.
122  smp._sound->internal_stop();
123  }
124  smp._sound = sound;
125  sample = smp._sample;
126  index = i;
127  return true;
128  }
129  }
130 
131  // No more already-allocated samples; get a new one from the system.
132  sample = AIL_allocate_sample_handle(_digital_driver);
133  if (sample == 0) {
134  return false;
135  }
136 
137  AIL_init_sample(sample, DIG_F_STEREO_16, 0);
138  index = _samples.size();
139 
140  SampleData smp;
141  smp._sound = sound;
142  smp._sample = sample;
143  _samples.push_back(smp);
144  return true;
145 }
146 
147 ////////////////////////////////////////////////////////////////////
148 // Function: GlobalMilesManager::release_sample
149 // Access: Public
150 // Description: Indicates that the indicated AudioSound no longer
151 // needs this sample.
152 ////////////////////////////////////////////////////////////////////
153 void GlobalMilesManager::
154 release_sample(size_t index, MilesAudioSample *sound) {
155  LightMutexHolder holder(_samples_lock);
156  nassertv(index < _samples.size());
157 
158  SampleData &smp = _samples[index];
159  if (smp._sound == sound) {
160  smp._sound = NULL;
161  }
162 }
163 
164 ////////////////////////////////////////////////////////////////////
165 // Function: GlobalMilesManager::get_sequence
166 // Access: Public
167 // Description: Gets a sequence handle from the global pool for the
168 // digital output device, to be used with the indicated
169 // AudioSound.
170 //
171 // If successful, sets the sequence handle and the index
172 // (which should later be used to release the sequence)
173 // and returns true. If unsuccessful (because there are
174 // no more available handles), returns false.
175 //
176 // This is a very limited resource; you should only get
177 // a sequence just before playing a sound.
178 ////////////////////////////////////////////////////////////////////
179 bool GlobalMilesManager::
180 get_sequence(HSEQUENCE &sequence, size_t &index, MilesAudioSequence *sound) {
181  LightMutexHolder holder(_sequences_lock);
182 
183  for (size_t i = 0; i < _sequences.size(); ++i) {
184  SequenceData &seq = _sequences[i];
185  if (AIL_sequence_status(seq._sequence) == SEQ_DONE) {
186  if (seq._sound != NULL) {
187  // Tell the last sound that was using this sequence that it's
188  // done now.
189  seq._sound->internal_stop();
190  }
191  seq._sound = sound;
192  sequence = seq._sequence;
193  index = i;
194  return true;
195  }
196  }
197 
198  // No more already-allocated sequences; get a new one from the system.
199  sequence = AIL_allocate_sequence_handle(_midi_driver);
200  if (sequence == 0) {
201  return false;
202  }
203 
204  index = _sequences.size();
205 
206  SequenceData seq;
207  seq._sound = sound;
208  seq._sequence = sequence;
209  _sequences.push_back(seq);
210  return true;
211 }
212 
213 ////////////////////////////////////////////////////////////////////
214 // Function: GlobalMilesManager::release_sequence
215 // Access: Public
216 // Description: Indicates that the indicated AudioSound no longer
217 // needs this sequence.
218 ////////////////////////////////////////////////////////////////////
219 void GlobalMilesManager::
220 release_sequence(size_t index, MilesAudioSequence *sound) {
221  LightMutexHolder holder(_sequences_lock);
222  nassertv(index < _sequences.size());
223 
224  SequenceData &seq = _sequences[index];
225  if (seq._sound == sound) {
226  seq._sound = NULL;
227  }
228 }
229 
230 ////////////////////////////////////////////////////////////////////
231 // Function: GlobalMilesManager::force_midi_reset
232 // Access: Public
233 // Description: Sometimes Miles seems to leave midi notes hanging,
234 // even after stop is called, so call this method to
235 // perform an explicit reset using winMM.dll calls, just
236 // to ensure silence.
237 ////////////////////////////////////////////////////////////////////
238 void GlobalMilesManager::
239 force_midi_reset() {
240  if (!miles_audio_force_midi_reset) {
241  audio_debug("MilesAudioManager::skipping force_midi_reset");
242  return;
243  }
244  audio_debug("MilesAudioManager::force_midi_reset");
245 
246 #ifdef WIN32
247  if ((_midi_driver!=NULL) && (_midi_driver->deviceid != MIDI_NULL_DRIVER) && (_midi_driver->hMidiOut != NULL)) {
248  audio_debug("MilesAudioManager::calling midiOutReset");
249  midiOutReset(_midi_driver->hMidiOut);
250  }
251 #endif
252 }
253 
254 ////////////////////////////////////////////////////////////////////
255 // Function: GlobalMilesManager::get_global_ptr
256 // Access: Public, Static
257 // Description: Returns the pointer to the one GlobalMilesManager
258 // object.
259 ////////////////////////////////////////////////////////////////////
260 GlobalMilesManager *GlobalMilesManager::
261 get_global_ptr() {
262  if (_global_ptr == NULL) {
263  _global_ptr = new GlobalMilesManager;
264  }
265  return _global_ptr;
266 }
267 
268 ////////////////////////////////////////////////////////////////////
269 // Function: GlobalMilesManager::open_api
270 // Access: Private
271 // Description: Called internally to initialize the Miles API.
272 ////////////////////////////////////////////////////////////////////
273 void GlobalMilesManager::
274 open_api() {
275  audio_debug("GlobalMilesManager::open_api()")
276  nassertv(!_is_open);
277 
278  bool use_digital = (audio_play_wave || audio_play_mp3);
279  if (audio_play_midi && audio_software_midi) {
280  use_digital = true;
281  }
282 
283 #ifdef IS_OSX
284  audio_software_midi = true;
285 #endif
286 
287  audio_debug(" use_digital="<<use_digital);
288  audio_debug(" audio_play_midi="<<audio_play_midi);
289  audio_debug(" audio_software_midi="<<audio_software_midi);
290  audio_debug(" audio_output_rate="<<audio_output_rate);
291  audio_debug(" audio_output_bits="<<audio_output_bits);
292  audio_debug(" audio_output_channels="<<audio_output_channels);
293  audio_debug(" audio_software_midi="<<audio_software_midi);
294 
295 #if !defined(NDEBUG) && defined(AIL_MSS_version) //[
296  char version[8];
297  AIL_MSS_version(version, 8);
298  audio_debug(" Mss32.dll Version: "<<version);
299 #endif //]
300 
301  if (!AIL_startup()) {
302  milesAudio_cat.warning()
303  << "Miles Sound System already initialized!\n";
304  }
305 
306  AIL_set_file_callbacks(open_callback, close_callback,
307  seek_callback, read_callback);
308 
309  if (use_digital) {
310  _digital_driver =
311  AIL_open_digital_driver(audio_output_rate, audio_output_bits,
312  audio_output_channels, 0);
313  }
314 
315  if (audio_play_midi) {
316  if (audio_software_midi) {
317  _midi_driver = AIL_open_XMIDI_driver(AIL_OPEN_XMIDI_NULL_DRIVER);
318 
319  // Load the downloadable sounds file:
320  _dls_device = AIL_DLS_open(_midi_driver, _digital_driver, NULL, 0,
321  audio_output_rate, audio_output_bits,
322  audio_output_channels);
323 
324  Filename dls_pathname = AudioManager::get_dls_pathname();
325 
327  vfs->resolve_filename(dls_pathname, get_model_path());
328 
329  _dls_data.clear();
330  PT(VirtualFile) file = vfs->get_file(dls_pathname);
331  if (file == (VirtualFile *)NULL) {
332  milesAudio_cat.warning()
333  << "DLS file does not exist: " << dls_pathname << "\n";
334 
335  } else if (!file->read_file(_dls_data, true)) {
336  milesAudio_cat.warning()
337  << "Could not read DLS file: " << dls_pathname << "\n";
338 
339  } else if (_dls_data.empty()) {
340  milesAudio_cat.warning()
341  << "DLS file is empty: " << dls_pathname << "\n";
342 
343  } else {
344  _dls_file = AIL_DLS_load_memory(_dls_device, &_dls_data[0], 0);
345  }
346 
347  if (_dls_file == 0) {
348  audio_error(" Could not get DLS file, switching to hardware MIDI.");
349  AIL_DLS_close(_dls_device, 0);
350  _dls_device = 0;
351  AIL_close_XMIDI_driver(_midi_driver);
352  _midi_driver = AIL_open_XMIDI_driver(0);
353 
354  } else {
355  audio_info(" using Miles software midi");
356  }
357  } else {
358  _midi_driver = AIL_open_XMIDI_driver(0);
359  audio_info(" using Miles hardware midi");
360  }
361  }
362 
363  _is_open = true;
364 }
365 
366 ////////////////////////////////////////////////////////////////////
367 // Function: GlobalMilesManager::close_api
368 // Access: Private
369 // Description: Called internally to shut down the Miles API.
370 ////////////////////////////////////////////////////////////////////
371 void GlobalMilesManager::
372 close_api() {
373  audio_debug("GlobalMilesManager::close_api()")
374  nassertv(_is_open);
375 
376  Samples::iterator si;
377  for (si = _samples.begin(); si != _samples.end(); ++si) {
378  SampleData &smp = (*si);
379  AIL_release_sample_handle(smp._sample);
380  }
381  _samples.clear();
382 
383  Sequences::iterator qi;
384  for (qi = _sequences.begin(); qi != _sequences.end(); ++qi) {
385  SequenceData &smp = (*qi);
386  AIL_release_sequence_handle(smp._sequence);
387  }
388  _sequences.clear();
389 
390  if (_dls_file != 0) {
391  AIL_DLS_unload(_dls_device, _dls_file);
392  _dls_file = 0;
393  }
394 
395  if (_dls_device != 0) {
396  AIL_DLS_close(_dls_device, 0);
397  _dls_device = 0;
398  }
399 
400  if (_midi_driver != 0) {
401  AIL_close_XMIDI_driver(_midi_driver);
402  _midi_driver = 0;
403  }
404 
405  if (_digital_driver != 0) {
406  AIL_close_digital_driver(_digital_driver);
407  _digital_driver = 0;
408  }
409 
410  AIL_shutdown();
411 
412  _is_open = false;
413 }
414 
415 ////////////////////////////////////////////////////////////////////
416 // Function: GlobalMilesManager::open_callback
417 // Access: Private, Static
418 // Description: This callback function is given to Miles to handle
419 // file I/O via the Panda VFS. It's only used to
420 // implemented streaming audio files, since in all other
421 // cases we open files directly.
422 ////////////////////////////////////////////////////////////////////
423 U32 AILCALLBACK GlobalMilesManager::
424 open_callback(char const *filename, UINTa *file_handle) {
426  istream *strm = vfs->open_read_file(Filename::binary_filename(string(filename)), true);
427  if (strm == NULL) {
428  // Failure.
429  return 0;
430  }
431  // Success.
432  (*file_handle) = (UINTa)strm;
433  return 1;
434 }
435 
436 ////////////////////////////////////////////////////////////////////
437 // Function: GlobalMilesManager::close_callback
438 // Access: Private, Static
439 // Description: This callback function is given to Miles to handle
440 // file I/O via the Panda VFS.
441 ////////////////////////////////////////////////////////////////////
442 void AILCALLBACK GlobalMilesManager::
443 close_callback(UINTa file_handle) {
444  istream *strm = (istream *)file_handle;
446  vfs->close_read_file(strm);
447 }
448 
449 ////////////////////////////////////////////////////////////////////
450 // Function: GlobalMilesManager::seek_callback
451 // Access: Private, Static
452 // Description: This callback function is given to Miles to handle
453 // file I/O via the Panda VFS.
454 ////////////////////////////////////////////////////////////////////
455 S32 AILCALLBACK GlobalMilesManager::
456 seek_callback(UINTa file_handle, S32 offset, U32 type) {
457  istream *strm = (istream *)file_handle;
458  strm->clear();
459  switch (type) {
460  case AIL_FILE_SEEK_BEGIN:
461  strm->seekg(offset, ios::beg);
462  break;
463 
464  case AIL_FILE_SEEK_CURRENT:
465  strm->seekg(offset, ios::cur);
466  break;
467 
468  case AIL_FILE_SEEK_END:
469  strm->seekg(offset, ios::end);
470  break;
471  }
472 
473  return strm->tellg();
474 }
475 
476 ////////////////////////////////////////////////////////////////////
477 // Function: GlobalMilesManager::read_callback
478 // Access: Private, Static
479 // Description: This callback function is given to Miles to handle
480 // file I/O via the Panda VFS.
481 ////////////////////////////////////////////////////////////////////
482 U32 AILCALLBACK GlobalMilesManager::
483 read_callback(UINTa file_handle, void *buffer, U32 bytes) {
484  istream *strm = (istream *)file_handle;
485  strm->read((char *)buffer, bytes);
486  return strm->gcount();
487 }
488 
489 #endif //]
490 
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const string &default_extension=string()) const
Searches the given search path for the filename.
A hierarchy of directories and files that appears to be one continuous file system, even though the files may originate from several different sources that may not be related to the actual OS&#39;s file system.
istream * open_read_file(const Filename &filename, bool auto_unwrap) const
Convenience function; returns a newly allocated istream if the file exists and can be read...
static Filename get_dls_pathname()
Returns the full pathname to the DLS file, as specified by the Config.prc file, or the default for th...
The abstract base class for a file or directory within the VirtualFileSystem.
Definition: virtualFile.h:37
static void close_read_file(istream *stream)
Closes a file opened by a previous call to open_read_file().
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
Similar to MutexHolder, but for a light mutex.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.