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 //]
A hierarchy of directories and files that appears to be one continuous 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,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const std::string &default_extension=std::string()) const
Searches the given search path for the filename.
The abstract base class for a file or directory within the VirtualFileSystem.
Definition: virtualFile.h:35
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:176
static void close_read_file(std::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:39
Similar to MutexHolder, but for a light mutex.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.