// soundmanager.cxx -- Wraps the SimGear OpenAl sound manager class // // Copyright (C) 2001 Curtis L. Olson - http://www.flightgear.org/~curt // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // #include #include #include #include "VoiceSynthesizer.hxx" #include "sample_queue.hxx" #include "soundmanager.hxx" #include
#include
#include #include #include #include #ifdef ENABLE_AUDIO_SUPPORT /** * Listener class that monitors the sim state. */ class Listener : public SGPropertyChangeListener { public: Listener(FGSoundManager *wrapper) : _wrapper(wrapper) {} virtual void valueChanged (SGPropertyNode * node); private: FGSoundManager* _wrapper; }; void Listener::valueChanged(SGPropertyNode * node) { _wrapper->activate(node->getBoolValue()); } FGSoundManager::FGSoundManager() : _active_dt(0.0), _is_initialized(false), _enabled(false), _listener(new Listener(this)) { } FGSoundManager::~FGSoundManager() { } void FGSoundManager::init() { _sound_working = fgGetNode("/sim/sound/working"); _sound_enabled = fgGetNode("/sim/sound/enabled"); _volume = fgGetNode("/sim/sound/volume"); _device_name = fgGetNode("/sim/sound/device-name"); _velocityNorthFPS = fgGetNode("velocities/speed-north-fps", true); _velocityEastFPS = fgGetNode("velocities/speed-east-fps", true); _velocityDownFPS = fgGetNode("velocities/speed-down-fps", true); _frozen = fgGetNode("sim/freeze/master"); SGPropertyNode_ptr scenery_loaded = fgGetNode("sim/sceneryloaded", true); scenery_loaded->addChangeListener(_listener.get()); globals->get_commands()->addCommand("play-audio-sample", this, &FGSoundManager::playAudioSampleCommand); reinit(); } void FGSoundManager::shutdown() { SGPropertyNode_ptr scenery_loaded = fgGetNode("sim/sceneryloaded", true); scenery_loaded->removeChangeListener(_listener.get()); stop(); _queue.clear(); globals->get_commands()->removeCommand("play-audio-sample"); SGSoundMgr::shutdown(); } void FGSoundManager::reinit() { _is_initialized = false; if (_is_initialized && !_sound_working->getBoolValue()) { // shutdown sound support stop(); return; } if (!_sound_working->getBoolValue()) { return; } update_device_list(); select_device(_device_name->getStringValue().c_str()); SGSoundMgr::reinit(); _is_initialized = true; activate(fgGetBool("sim/sceneryloaded", true)); } void FGSoundManager::activate(bool State) { if (_is_initialized) { if (State) { SGSoundMgr::activate(); } } } void FGSoundManager::update_device_list() { std::vector devices = get_available_devices(); for (unsigned int i=0; isetStringValue(devices[i].c_str()); } devices.clear(); } bool FGSoundManager::stationaryView() const { // this is an ugly hack to decide if the *viewer* is stationary, // since we don't model the viewer velocity directly. flightgear::View* _view = globals->get_current_view(); return (_view->getXOffset_m () == 0.0 && _view->getYOffset_m () == 0.0 && _view->getZOffset_m () == 0.0); } // Update sound manager and propagate property values, // since the sound manager doesn't read any properties itself. // Actual sound update is triggered by the subsystem manager. void FGSoundManager::update(double dt) { if (is_working() && _is_initialized && _sound_working->getBoolValue()) { bool enabled = _sound_enabled->getBoolValue() && !_frozen->getBoolValue(); if (enabled != _enabled) { if (enabled) resume(); else suspend(); _enabled = enabled; } if (enabled) { flightgear::View* _view = globals->get_current_view(); set_position( _view->getViewPosition(), _view->getPosition() ); set_orientation( _view->getViewOrientation() ); SGVec3d velocity(SGVec3d::zeros()); if (!stationaryView()) { velocity = SGVec3d(_velocityNorthFPS->getDoubleValue(), _velocityEastFPS->getDoubleValue(), _velocityDownFPS->getDoubleValue() ); } set_velocity( velocity ); float vf = 1.0f; if (_active_dt < 5.0) { _active_dt += dt; vf = std::min(std::pow(_active_dt*0.2, 5.0), 1.0); } set_volume(vf*_volume->getFloatValue()); SGSoundMgr::update(dt); } } } /** * Built-in command: play an audio message (i.e. a wav file) This is * fire and forget. Call this once per message and it will get dumped * into a queue. Except for the special 'instant' queue, messages within * a given queue are played sequentially so they do not overlap. */ bool FGSoundManager::playAudioSampleCommand(const SGPropertyNode * arg, SGPropertyNode * root) { string qname = arg->getStringValue("queue", ""); string name = !qname.empty() ? qname : "chatter"; string path = arg->getStringValue("path"); string file = arg->getStringValue("file"); float volume = arg->getFloatValue("volume"); const auto fullPath = SGPath(path) / file; const auto foundPath = globals->resolve_maybe_aircraft_path( fullPath.utf8Str()); if (!foundPath.exists()) { SG_LOG(SG_GENERAL, SG_ALERT, "play-audio-sample: no such file: '" << fullPath.utf8Str() << "'"); return false; } // SG_LOG(SG_GENERAL, SG_ALERT, "Playing '" << foundPath.utf8Str() << "'"); try { SGSoundSample *msg = new SGSoundSample(foundPath); msg->set_volume( volume ); if (name == "instant") { static const char *r = "0123456789abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; string rstr = "NASAL: "; for (int i=0; i<10; i++) { rstr.push_back( r[rand() % strlen(r)] ); } // Add a special queue-name 'instant' which does not put samples // into a sample queue but plays them instantly. SGSampleGroup* sgr = find("NASAL instant queue", true); sgr->tie_to_listener(); sgr->add(msg, rstr); sgr->play_once(rstr); } else { if ( !_queue[name] ) { _queue[name] = new FGSampleQueue(this, name); _queue[name]->tie_to_listener(); } _queue[name]->add( msg ); } return true; } catch (const sg_io_exception&) { SG_LOG(SG_GENERAL, SG_ALERT, "play-audio-sample: " "failed to load '" << foundPath.utf8Str() << "'"); return false; } } VoiceSynthesizer * FGSoundManager::getSynthesizer( const std::string & voice ) { std::map::iterator it = _synthesizers.find(voice); if( it == _synthesizers.end() ) { VoiceSynthesizer * synthesizer = new FLITEVoiceSynthesizer( voice ); _synthesizers[voice] = synthesizer; return synthesizer; } return it->second; } #endif // ENABLE_AUDIO_SUPPORT // Register the subsystem. SGSubsystemMgr::Registrant registrantFGSoundManager( SGSubsystemMgr::SOUND, {{"SGSoundMgr", SGSubsystemMgr::Dependency::HARD}});