|
|
@@ -25,6 +25,7 @@
|
|
|
#include "config_audio.h"
|
|
|
#include "fmodAudioSound.h"
|
|
|
#include "string_utils.h"
|
|
|
+#include "fileSystemInfo.h"
|
|
|
|
|
|
TypeHandle FmodAudioSound::_type_handle;
|
|
|
|
|
|
@@ -71,46 +72,119 @@ FmodAudioSound(AudioManager *manager, Filename file_name, bool positional) {
|
|
|
|
|
|
_channel = 0;
|
|
|
_file_name = file_name;
|
|
|
-
|
|
|
- FMOD_CREATESOUNDEXINFO *sound_info = NULL;
|
|
|
+ _file_name.set_binary();
|
|
|
|
|
|
//Get the Speaker Mode [Important for later on.]
|
|
|
result = _manager->_system->getSpeakerMode( &_speakermode );
|
|
|
fmod_audio_errcheck("_system->getSpeakerMode()", result);
|
|
|
|
|
|
- // Calculate the approximate uncompressed size of the sound.
|
|
|
- int size = file_name.get_file_size();
|
|
|
- string ext = downcase(file_name.get_extension());
|
|
|
- if (ext != "wav") size *= 10;
|
|
|
-
|
|
|
- int flag = positional ? FMOD_3D : FMOD_2D;
|
|
|
- int streamflag = (size > 250000) ? FMOD_CREATESTREAM : FMOD_CREATESAMPLE;
|
|
|
- if (ext == "mid") {
|
|
|
- streamflag = FMOD_CREATESTREAM;
|
|
|
- sound_info = &_manager->_midi_info;
|
|
|
+ VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
|
+ PT(VirtualFile) file = vfs->get_file(_file_name);
|
|
|
+ if (file == (VirtualFile *)NULL) {
|
|
|
+ // File not found. We will display the appropriate error message
|
|
|
+ // below.
|
|
|
+ result = FMOD_ERR_FILE_NOTFOUND;
|
|
|
+
|
|
|
+ } else {
|
|
|
+ bool preload = (fmod_audio_preload_threshold < 0) || (file->get_file_size() < fmod_audio_preload_threshold);
|
|
|
+ int flags = FMOD_SOFTWARE;
|
|
|
+ flags |= positional ? FMOD_3D : FMOD_2D;
|
|
|
+
|
|
|
+ FMOD_CREATESOUNDEXINFO sound_info;
|
|
|
+ memset(&sound_info, 0, sizeof(sound_info));
|
|
|
+ sound_info.cbsize = sizeof(sound_info);
|
|
|
|
|
|
- if (sound_info->dlsname != NULL) {
|
|
|
- audio_debug("Using DLS file " << sound_info->dlsname);
|
|
|
+ string ext = downcase(_file_name.get_extension());
|
|
|
+ if (ext == "mid") {
|
|
|
+ // Get the MIDI parameters.
|
|
|
+ memcpy(&sound_info, &_manager->_midi_info, sizeof(sound_info));
|
|
|
+ if (sound_info.dlsname != NULL) {
|
|
|
+ audio_debug("Using DLS file " << sound_info.dlsname);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
+
|
|
|
+ const char *name_or_data = _file_name.c_str();
|
|
|
+
|
|
|
+ pvector<unsigned char> mem_buffer;
|
|
|
+ FileSystemInfo info;
|
|
|
+ if (preload) {
|
|
|
+ // Pre-read the file right now, and pass it in as a memory
|
|
|
+ // buffer. This avoids threading issues completely, because all
|
|
|
+ // of the reading happens right here.
|
|
|
+ file->read_file(mem_buffer, true);
|
|
|
+ sound_info.length = mem_buffer.size();
|
|
|
+ if (mem_buffer.size() != 0) {
|
|
|
+ name_or_data = (const char *)&mem_buffer[0];
|
|
|
+ }
|
|
|
+ flags |= FMOD_OPENMEMORY;
|
|
|
+ if (fmodAudio_cat.is_debug()) {
|
|
|
+ fmodAudio_cat.debug()
|
|
|
+ << "Reading " << _file_name << " into memory (" << sound_info.length
|
|
|
+ << " bytes)\n";
|
|
|
+ }
|
|
|
|
|
|
- result = _manager->_system->createSound( file_name.c_str(), FMOD_SOFTWARE | streamflag | flag ,
|
|
|
- sound_info, &_sound);
|
|
|
- if (result != FMOD_OK) {
|
|
|
- audio_error("createSound(" << file_name << "): " << FMOD_ErrorString(result));
|
|
|
+ } else if (file->get_system_info(info)) {
|
|
|
+ // The file exists on disk (or it's part of a multifile that
|
|
|
+ // exists on disk), so we can have FMod read the file directly.
|
|
|
+ // This is also safe, because FMod uses its own I/O operations
|
|
|
+ // that don't involve Panda, so this can safely happen in an
|
|
|
+ // FMod thread.
|
|
|
+ name_or_data = info.get_os_file_name().c_str();
|
|
|
+ sound_info.fileoffset = (unsigned int)info.get_file_start();
|
|
|
+ sound_info.length = (unsigned int)info.get_file_size();
|
|
|
+ flags |= FMOD_CREATESTREAM;
|
|
|
+ if (fmodAudio_cat.is_debug()) {
|
|
|
+ fmodAudio_cat.debug()
|
|
|
+ << "Streaming " << _file_name << " from disk (" << name_or_data
|
|
|
+ << ", " << sound_info.fileoffset << ", " << sound_info.length << ")\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
|
|
+ // Otherwise, if the Panda threading system is compiled in, we
|
|
|
+ // can assign callbacks to read the file through the VFS.
|
|
|
+ name_or_data = (const char *)file.p();
|
|
|
+ sound_info.length = (unsigned int)info.get_file_size();
|
|
|
+ sound_info.useropen = open_callback;
|
|
|
+ sound_info.userclose = close_callback;
|
|
|
+ sound_info.userread = read_callback;
|
|
|
+ sound_info.userseek = seek_callback;
|
|
|
+ flags |= FMOD_CREATESTREAM;
|
|
|
+ if (fmodAudio_cat.is_debug()) {
|
|
|
+ fmodAudio_cat.debug()
|
|
|
+ << "Streaming " << _file_name << " from disk using callbacks\n";
|
|
|
+ }
|
|
|
+
|
|
|
+#else // HAVE_THREADS && !SIMPLE_THREADS
|
|
|
+ // Without threads, we can't safely read this file.
|
|
|
+ name_or_data = "";
|
|
|
|
|
|
+ fmodAudio_cat.warning()
|
|
|
+ << "Cannot stream " << _file_name << "; file is not literally on disk.\n";
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ result =
|
|
|
+ _manager->_system->createSound(name_or_data, flags, &sound_info, &_sound);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (result != FMOD_OK) {
|
|
|
+ audio_error("createSound(" << _file_name << "): " << FMOD_ErrorString(result));
|
|
|
+
|
|
|
// We couldn't load the sound file. Create a blank sound record
|
|
|
// instead.
|
|
|
+ FMOD_CREATESOUNDEXINFO sound_info;
|
|
|
+ memset(&sound_info, 0, sizeof(sound_info));
|
|
|
char blank_data[100];
|
|
|
- FMOD_CREATESOUNDEXINFO exinfo;
|
|
|
- memset(&exinfo, 0, sizeof(exinfo));
|
|
|
memset(blank_data, 0, sizeof(blank_data));
|
|
|
- exinfo.cbsize = sizeof(exinfo);
|
|
|
- exinfo.length = sizeof(blank_data);
|
|
|
- exinfo.numchannels = 1;
|
|
|
- exinfo.defaultfrequency = 8000;
|
|
|
- exinfo.format = FMOD_SOUND_FORMAT_PCM16;
|
|
|
- result = _manager->_system->createSound( blank_data, FMOD_SOFTWARE | flag | FMOD_OPENMEMORY | FMOD_OPENRAW, &exinfo, &_sound);
|
|
|
+ sound_info.cbsize = sizeof(sound_info);
|
|
|
+ sound_info.length = sizeof(blank_data);
|
|
|
+ sound_info.numchannels = 1;
|
|
|
+ sound_info.defaultfrequency = 8000;
|
|
|
+ sound_info.format = FMOD_SOUND_FORMAT_PCM16;
|
|
|
+ int flags = FMOD_SOFTWARE | FMOD_OPENMEMORY | FMOD_OPENRAW;
|
|
|
+
|
|
|
+ result = _manager->_system->createSound( blank_data, flags, &sound_info, &_sound);
|
|
|
fmod_audio_errcheck("createSound (blank)", result);
|
|
|
}
|
|
|
|
|
|
@@ -157,27 +231,6 @@ play() {
|
|
|
start_playing();
|
|
|
}
|
|
|
|
|
|
-////////////////////////////////////////////////////////////////////
|
|
|
-// Function: sound_end_callback
|
|
|
-// Access: Static
|
|
|
-// Description: When fmod finishes playing a sound, decrements the
|
|
|
-// reference count of the associated FmodAudioSound.
|
|
|
-////////////////////////////////////////////////////////////////////
|
|
|
-FMOD_RESULT F_CALLBACK sound_end_callback(FMOD_CHANNEL * channel,
|
|
|
- FMOD_CHANNEL_CALLBACKTYPE type,
|
|
|
- void *commanddata1,
|
|
|
- void *commanddata2) {
|
|
|
- if (type == FMOD_CHANNEL_CALLBACKTYPE_END) {
|
|
|
- FMOD::Channel *fc = (FMOD::Channel *)channel;
|
|
|
- void *userdata = NULL;
|
|
|
- FMOD_RESULT result = fc->getUserData(&userdata);
|
|
|
- fmod_audio_errcheck("channel->getUserData()", result);
|
|
|
- FmodAudioSound *fsound = (FmodAudioSound*)userdata;
|
|
|
- fsound->_self_ref = fsound;
|
|
|
- }
|
|
|
- return FMOD_OK;
|
|
|
-}
|
|
|
-
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
// Function: FmodAudioSound::stop
|
|
|
// Access: public
|
|
|
@@ -884,4 +937,147 @@ get_finished_event() const {
|
|
|
return _finished_event;
|
|
|
}
|
|
|
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FmodAudioSound::sound_end_callback
|
|
|
+// Access: Private, Static
|
|
|
+// Description: When fmod finishes playing a sound, decrements the
|
|
|
+// reference count of the associated FmodAudioSound.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+FMOD_RESULT F_CALLBACK FmodAudioSound::
|
|
|
+sound_end_callback(FMOD_CHANNEL * channel,
|
|
|
+ FMOD_CHANNEL_CALLBACKTYPE type,
|
|
|
+ void *commanddata1,
|
|
|
+ void *commanddata2) {
|
|
|
+ // Fortunately, this callback is made synchronously rather than
|
|
|
+ // asynchronously (it is triggered during System::update()), so we
|
|
|
+ // don't have to worry about thread-related issues here.
|
|
|
+ if (type == FMOD_CHANNEL_CALLBACKTYPE_END) {
|
|
|
+ FMOD::Channel *fc = (FMOD::Channel *)channel;
|
|
|
+ void *userdata = NULL;
|
|
|
+ FMOD_RESULT result = fc->getUserData(&userdata);
|
|
|
+ fmod_audio_errcheck("channel->getUserData()", result);
|
|
|
+ FmodAudioSound *fsound = (FmodAudioSound*)userdata;
|
|
|
+ fsound->_self_ref = fsound;
|
|
|
+ }
|
|
|
+ return FMOD_OK;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FmodAudioSound::open_callback
|
|
|
+// Access: Private, Static
|
|
|
+// Description: A hook into Panda's virtual file system.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+FMOD_RESULT F_CALLBACK FmodAudioSound::
|
|
|
+open_callback(const char *name, int, unsigned int *file_size,
|
|
|
+ void **handle, void **user_data) {
|
|
|
+ // We actually pass in the VirtualFile pointer as the "name".
|
|
|
+ VirtualFile *file = (VirtualFile *)name;
|
|
|
+ if (file == (VirtualFile *)NULL) {
|
|
|
+ return FMOD_ERR_FILE_NOTFOUND;
|
|
|
+ }
|
|
|
+ if (fmodAudio_cat.is_spam()) {
|
|
|
+ fmodAudio_cat.spam()
|
|
|
+ << "open_callback(" << *file << ")\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ istream *str = file->open_read_file(true);
|
|
|
+
|
|
|
+ (*file_size) = file->get_file_size(str);
|
|
|
+ (*handle) = (void *)str;
|
|
|
+ (*user_data) = (void *)file;
|
|
|
+
|
|
|
+ // Explicitly ref the VirtualFile since we're storing it in a void
|
|
|
+ // pointer instead of a PT(VirtualFile).
|
|
|
+ file->ref();
|
|
|
+
|
|
|
+ return FMOD_OK;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FmodAudioSound::close_callback
|
|
|
+// Access: Private, Static
|
|
|
+// Description: A hook into Panda's virtual file system.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+FMOD_RESULT F_CALLBACK FmodAudioSound::
|
|
|
+close_callback(void *handle, void *user_data) {
|
|
|
+ VirtualFile *file = (VirtualFile *)user_data;
|
|
|
+ if (fmodAudio_cat.is_spam()) {
|
|
|
+ fmodAudio_cat.spam()
|
|
|
+ << "close_callback(" << *file << ")\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
|
+
|
|
|
+ istream *str = (istream *)handle;
|
|
|
+ vfs->close_read_file(str);
|
|
|
+
|
|
|
+ // Explicitly unref the VirtualFile pointer.
|
|
|
+ unref_delete(file);
|
|
|
+
|
|
|
+ return FMOD_OK;
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FmodAudioSound::read_callback
|
|
|
+// Access: Private, Static
|
|
|
+// Description: A hook into Panda's virtual file system.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+FMOD_RESULT F_CALLBACK FmodAudioSound::
|
|
|
+read_callback(void *handle, void *buffer, unsigned int size_bytes,
|
|
|
+ unsigned int *bytes_read, void *user_data) {
|
|
|
+ VirtualFile *file = (VirtualFile *)user_data;
|
|
|
+ if (fmodAudio_cat.is_spam()) {
|
|
|
+ fmodAudio_cat.spam()
|
|
|
+ << "read_callback(" << *file << ", " << size_bytes << ")\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ istream *str = (istream *)handle;
|
|
|
+ str->read((char *)buffer, size_bytes);
|
|
|
+ (*bytes_read) = str->gcount();
|
|
|
+
|
|
|
+ // We can't yield here, since this callback is made within a
|
|
|
+ // sub-thread--an OS-level sub-thread spawned by FMod, not a Panda
|
|
|
+ // thread. But we will only execute this code in the true-threads
|
|
|
+ // case anyway.
|
|
|
+ //thread_consider_yield();
|
|
|
+
|
|
|
+ if (str->eof()) {
|
|
|
+ if ((*bytes_read) == 0) {
|
|
|
+ return FMOD_ERR_FILE_EOF;
|
|
|
+ } else {
|
|
|
+ // Report the EOF next time.
|
|
|
+ return FMOD_OK;
|
|
|
+ }
|
|
|
+ } if (str->fail()) {
|
|
|
+ return FMOD_ERR_FILE_BAD;
|
|
|
+ } else {
|
|
|
+ return FMOD_OK;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FmodAudioSound::seek_callback
|
|
|
+// Access: Private, Static
|
|
|
+// Description: A hook into Panda's virtual file system.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+FMOD_RESULT F_CALLBACK FmodAudioSound::
|
|
|
+seek_callback(void *handle, unsigned int pos, void *user_data) {
|
|
|
+ VirtualFile *file = (VirtualFile *)user_data;
|
|
|
+ if (fmodAudio_cat.is_spam()) {
|
|
|
+ fmodAudio_cat.spam()
|
|
|
+ << "seek_callback(" << *file << ", " << pos << ")\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ istream *str = (istream *)handle;
|
|
|
+ str->clear();
|
|
|
+ str->seekg(pos);
|
|
|
+
|
|
|
+ if (str->fail() && !str->eof()) {
|
|
|
+ return FMOD_ERR_FILE_COULDNOTSEEK;
|
|
|
+ } else {
|
|
|
+ return FMOD_OK;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
#endif //]
|