// fg_fx.cxx -- Sound effect management class implementation // // Started by David Megginson, October 2001 // (Reuses some code from main.cxx, probably by Curtis Olson) // // Copyright (C) 2001 Curtis L. Olson - curt@flightgear.org // // 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., 675 Mass Ave, Cambridge, MA 02139, USA. // // $Id$ #include // FIXME: remove direct dependencies #include
#include "fg_fx.hxx" static const char * engine_names[FGFX::MAX_ENGINES] = { "engine0", "engine1" }; static const char * crank_names[FGFX::MAX_ENGINES] = { "crank0", "crank1" }; FGFX::FGFX () : _old_flap_position(0), _old_gear_position(0), _wind(0), _stall(0), _rumble(0), _flaps(0), _gear_up(0), _gear_dn(0), _squeal(0), _click(0), _stall_warning_prop(0), _flaps_prop(0), _gear_prop(0) { for (int i = 0; i < MAX_ENGINES; i++) { _engine[i] = 0; _crank[i] = 0; _engine_running_prop[i] = 0; _engine_cranking_prop[i] = 0; } } FGFX::~FGFX () { // FIXME: is this right, or does the // sound manager assume pointer ownership? for (int i = 0; i < MAX_ENGINES; i++) { delete _engine[i]; delete _crank[i]; } delete _wind; delete _stall; delete _rumble; delete _flaps; delete _gear_up; delete _gear_dn; delete _squeal; delete _click; } void FGFX::init () { FGSoundMgr * mgr = globals->get_soundmgr(); int i; // // Create and add engine-related sounds. // for (i = 0; i < MAX_ENGINES; i++) { // Engine _engine[i] = new FGSimpleSound(fgGetString("/sim/sounds/engine/path", "Sounds/wasp.wav")); _engine[i]->set_volume(fgGetFloat("/sim/sounds/engine/volume", 1.0)); _engine[i]->set_pitch(fgGetFloat("/sim/sounds/engine/pitch", 1.0)); mgr->add(_engine[i], engine_names[i]); // Starter _crank[i] = new FGSimpleSound(fgGetString("/sim/sounds/cranking/path", "Sounds/cranking.wav")); _crank[i]->set_volume(fgGetFloat("/sim/sounds/cranking/volume", 0.5)); _crank[i]->set_pitch(fgGetFloat("/sim/sounds/cranking/pitch", 1.00)); mgr->add(_crank[i], crank_names[i]); } // // Create and add the wind noise. // _wind = new FGSimpleSound(fgGetString("/sim/sounds/wind/path", "Sounds/wind.wav")); _wind->set_volume(fgGetFloat("/sim/sounds/wind/volume", 1.0)); _wind->set_pitch(fgGetFloat("/sim/sounds/wind/pitch", 1.0)); mgr->add(_wind, "wind"); // // Create and add the stall noise. // _stall = new FGSimpleSound(fgGetString("/sim/sounds/stall/path", "Sounds/stall.wav")); _stall->set_volume(fgGetFloat("/sim/sounds/stall/volume", 1.0)); _stall->set_pitch(fgGetFloat("/sim/sounds/stall/pitch", 1.0)); mgr->add(_stall, "stall"); // // Create and add the rumble noise. // _rumble = new FGSimpleSound(fgGetString("/sim/sounds/rumble/path", "Sounds/rumble.wav")); _rumble->set_volume(fgGetFloat("/sim/sounds/rumble/volume", 1.0)); _rumble->set_pitch(fgGetFloat("/sim/sounds/rumble/pitch", 1.0)); mgr->add(_rumble, "rumble"); // // Create and add the flaps noise // _flaps = new FGSimpleSound(fgGetString("/sim/sounds/flaps/path", "Sounds/flaps.wav")); _flaps->set_volume(fgGetFloat("/sim/sounds/flaps/volume", 0.25)); _flaps->set_pitch(fgGetFloat("/sim/sounds/flaps/pitch", 1.0)); mgr->add(_flaps, "flaps"); // // Create and add the gear noises. // _gear_up = new FGSimpleSound(fgGetString("/sim/sounds/gear-up/path", "Sounds/gear-up.wav")); _gear_dn = new FGSimpleSound(fgGetString("/sim/sounds/gear-down/path", "Sounds/gear-dn.wav")); _gear_up->set_volume(fgGetFloat("/sim/sounds/gear-up/volume", 1.0)); _gear_dn->set_volume(fgGetFloat("/sim/sounds/gear-down/volume", 1.0)); _gear_up->set_pitch(fgGetFloat("/sim/sounds/gear-up/pitch", 1.0)); _gear_dn->set_pitch(fgGetFloat("/sim/sounds/gear-down/pitch", 1.0)); mgr->add(_gear_up, "gear-up"); mgr->add(_gear_dn, "gear-down"); // // Create and add the squeal noise. // _squeal = new FGSimpleSound(fgGetString("/sim/sounds/squeal/path", "Sounds/squeal.wav")); _squeal->set_volume(fgGetFloat("/sim/sounds/squeal/volume", 1.0)); _squeal->set_pitch(fgGetFloat("/sim/sounds/squeal/pitch", 1.0)); mgr->add(_squeal, "squeal"); // // Simplistic wheel spin model for audio effect purposes only. We // don't want to play a full squeel, if the wheel has only departed // from the ground for a split second. // for (i = 0; i < MAX_GEAR; i++) { _wheel_spin[i] = 0.0; } // // Create and add the click noise. _click = new FGSimpleSound(fgGetString("/sim/sounds/click/path", "Sounds/click.wav")); _flaps->set_volume(fgGetFloat("/sim/sounds/click/volume", 1.0)); _flaps->set_pitch(fgGetFloat("/sim/sounds/click/pitch", 1.0)); mgr->add(_click, "click"); //////////////////////////////////////////////////////////////////// // Grab some properties. //////////////////////////////////////////////////////////////////// for (i = 0; i < MAX_ENGINES; i++) { SGPropertyNode * node = fgGetNode("engines/engine", i, true); _engine_running_prop[i] = node->getChild("running", 0, true); _engine_cranking_prop[i] = node->getChild("cranking", 0, true); } _stall_warning_prop = fgGetNode("/sim/aero/alarms/stall-warning", true); _vc_prop = fgGetNode("/velocities/airspeed-kt", true); _flaps_prop = fgGetNode("/controls/flaps", true); _gear_prop = fgGetNode("/controls/gear-down", true); } void FGFX::bind () { } void FGFX::unbind () { } void FGFX::update (int dt) { FGSoundMgr * mgr = globals->get_soundmgr(); int i; //////////////////////////////////////////////////////////////////// // Update the engine sound. //////////////////////////////////////////////////////////////////// for (i = 0; i < MAX_ENGINES; i++) { SGPropertyNode * node = fgGetNode("engines/engine", i, true); if (_engine_running_prop[i]->getBoolValue()) { // pitch corresponds to rpm // volume corresponds to manifold pressure double rpm_factor; rpm_factor = node->getDoubleValue("rpm") / 2500.0; double pitch = 0.3 + rpm_factor * 3.0; // don't run at absurdly slow rates -- not realistic // and sounds bad to boot. :-) if (pitch < 0.7) pitch = 0.7; if (pitch > 5.0) pitch = 5.0; double mp_factor; mp_factor = node->getDoubleValue("mp-osi") / 100; double volume = 0.15 + mp_factor / 2.0; if (volume < 0.15) volume = 0.15; if (volume > 0.5) volume = 0.5; _engine[i]->set_pitch( pitch ); _engine[i]->set_volume( volume ); set_playing(engine_names[i], true); } else { set_playing(engine_names[i], false); } // FIXME set_playing(crank_names[i], _engine_cranking_prop[i]->getBoolValue()); } //////////////////////////////////////////////////////////////////// // Update the wind noise. //////////////////////////////////////////////////////////////////// // The wind sound is airspeed and altitude // dependent. The wind noise should become // silent at about 65000 feet, and is based // on a logarithmic scale. float rel_wind = cur_fdm_state->get_V_rel_wind(); // FPS float alt_vol = 1-log(cur_fdm_state->get_Altitude()/65000.0)/4.81; if ((rel_wind > 2.0) && (alt_vol > 0.2)) { float volume = rel_wind/937; double pitch = 1.0+(rel_wind/53.0); _wind->set_volume(volume*alt_vol/2); _wind->set_pitch(pitch); set_playing("wind", true); } else { set_playing("wind", false); } //////////////////////////////////////////////////////////////////// // Update the stall horn. //////////////////////////////////////////////////////////////////// double stall = _stall_warning_prop->getDoubleValue(); double vc = _vc_prop->getDoubleValue(); if (stall > 0.0 && vc > 30.0) { _stall->set_volume(stall); set_playing("stall", true); } else { set_playing("stall", false); } //////////////////////////////////////////////////////////////////// // Update the rumble. //////////////////////////////////////////////////////////////////// float gearOnGround = 0; // Calculate whether a squeal is // required, and set the volume. // Currently, the squeal volume is the // current local down velocity in feet // per second divided by 10.0, and // will not be played if under 0.1. // FIXME: take rotational velocities // into account as well. for (i = 0; i < MAX_GEAR; i++) { SGPropertyNode * node = fgGetNode("gear/gear", i, true); // cout << "air speed = " << cur_fdm_state->get_V_equiv_kts(); // cout << " wheel " << i << " speed = " << _wheel_spin[i]; if (node->getBoolValue("wow")) { gearOnGround++; if (!_gear_on_ground[i]) { // wheel just touched down // 3 parts horizontal velocity + 1 part vertical velocity double squeal_volume = 0.75 * cur_fdm_state->get_V_equiv_kts() / 90.0 + 0.25 * cur_fdm_state->get_V_down() / 5.0; // scale volume by difference between wheel spin speed and // ground speed double diff = fabs( cur_fdm_state->get_V_equiv_kts() - _wheel_spin[i] ); // cout << " speed diff = " << diff; double scale_factor = 0.0; if ( diff > 10 ) { scale_factor = ( diff - 10.0 ) / 30.0f; if ( scale_factor > 1.0 ) { scale_factor = 1.0; } } // cout << " scale_factor = " << scale_factor; squeal_volume *= scale_factor; if (squeal_volume > 0.1) { _squeal->set_volume(squeal_volume); _squeal->set_pitch(1.25); mgr->play_once("squeal"); } _gear_on_ground[i] = true; } // cout << " wow"; _wheel_spin[i] = cur_fdm_state->get_V_equiv_kts(); } else { // cout << " airborn"; _gear_on_ground[i] = false; /* fix me: wheel spindown is currently frame rate dependent which it shouldn't be */ _wheel_spin[i] -= 0.2; if ( _wheel_spin[i] < 0.0 ) { _wheel_spin[i] = 0.0; } } // cout << endl; } // Now, if any of the gear is in // contact with the ground play the // rumble sound. The volume is a // logarthmic scale of the absolute // velocity in knots divided by 12.0. // No rumble will be played if the // velocity is under ~0.25 kt. double speed = cur_fdm_state->get_V_equiv_kts(); if (gearOnGround > 0 && speed > 0.5) { double volume = 2.0 * (gearOnGround/MAX_GEAR) * log(speed)/12; //(speed/60.0); _rumble->set_volume(volume); set_playing("rumble", true); } else { set_playing("rumble", false); } //////////////////////////////////////////////////////////////////// // Check for flap movement. //////////////////////////////////////////////////////////////////// double flap_position = _flaps_prop->getDoubleValue(); if (fabs(flap_position - _old_flap_position) > 0.1) { mgr->play_once("flaps"); _old_flap_position = flap_position; } //////////////////////////////////////////////////////////////////// // Check for gear movement. //////////////////////////////////////////////////////////////////// double gear_position = _gear_prop->getDoubleValue(); if (gear_position != _old_gear_position) { if (gear_position < _old_gear_position) { mgr->play_once("gear-up"); } else { mgr->play_once("gear-down"); } _old_gear_position = gear_position; } // TODO: click } void FGFX::set_playing (const char * soundName, bool state) { FGSoundMgr * mgr = globals->get_soundmgr(); bool playing = mgr->is_playing(soundName); if (state && !playing) mgr->play_looped(soundName); else if (!state && playing) mgr->stop(soundName); } // end of fg_fx.cxx