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