first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

33
src/Sound/CMakeLists.txt Normal file
View File

@@ -0,0 +1,33 @@
include(FlightGearComponent)
set(SOURCES
audioident.cxx
soundgenerator.cxx
beacon.cxx
fg_fx.cxx
morse.cxx
sample_queue.cxx
voice.cxx
voiceplayer.cxx
soundmanager.cxx
)
set(HEADERS
audioident.hxx
soundgenerator.hxx
beacon.hxx
fg_fx.hxx
morse.hxx
sample_queue.hxx
voice.hxx
voiceplayer.hxx
soundmanager.hxx
VoiceSynthesizer.hxx
flitevoice.hxx
)
flightgear_component(Sound "${SOURCES}" "${HEADERS}")
add_library(fgvoicesynth STATIC VoiceSynthesizer.cxx flitevoice.cxx)
target_link_libraries(fgvoicesynth PRIVATE flightgear_flite_hts SimGearScene)

View File

@@ -0,0 +1,135 @@
/*
* VoiceSynthesizer.cxx - wraps flite+hts_engine
* Copyright (C) 2014 Torsten Dreyer - torsten (at) t3r (dot) de
*
* 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 "VoiceSynthesizer.hxx"
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/threads/SGThread.hxx>
#include <flite_hts_engine.h>
using std::string;
static const char * VOICE_FILES[] = {
"cmu_us_arctic_slt.htsvoice",
"cstr_uk_female-1.0.htsvoice"
};
class FLITEVoiceSynthesizer::WorkerThread : public SGThread
{
public:
WorkerThread(FLITEVoiceSynthesizer * synthesizer)
: _synthesizer(synthesizer)
{
}
virtual void run();
private:
FLITEVoiceSynthesizer * _synthesizer;
};
void FLITEVoiceSynthesizer::WorkerThread::run()
{
for (;;) {
SynthesizeRequest request = _synthesizer->_requests.pop();
// marker value indicating termination requested
if ((request.speed < 0.0) && (request.volume < 0.0)) {
SG_LOG(SG_SOUND, SG_DEBUG, "FLITE synthesis thread exiting");
return;
}
if ( NULL != request.listener) {
SGSharedPtr<SGSoundSample> sample = _synthesizer->synthesize(request.text, request.volume, request.speed, request.pitch);
request.listener->SoundSampleReady( sample );
}
}
}
SynthesizeRequest SynthesizeRequest::cancelThreadRequest()
{
SynthesizeRequest marker;
marker.volume = -999.0;
marker.speed = -999.0;
return marker;
}
string FLITEVoiceSynthesizer::getVoicePath( voice_t voice )
{
if( voice < 0 || voice >= VOICE_UNKNOWN ) return string("");
SGPath voicePath = globals->get_fg_root() / "ATC" / VOICE_FILES[voice];
return voicePath.utf8Str();
}
string FLITEVoiceSynthesizer::getVoicePath( const string & voice )
{
if( voice == "cmu_us_arctic_slt" ) return getVoicePath(CMU_US_ARCTIC_SLT);
if( voice == "cstr_uk_female" ) return getVoicePath(CSTR_UK_FEMALE);
return getVoicePath(VOICE_UNKNOWN);
}
void FLITEVoiceSynthesizer::synthesize( SynthesizeRequest & request)
{
_requests.push(request);
}
FLITEVoiceSynthesizer::FLITEVoiceSynthesizer(const std::string & voice)
// REVIEW: Memory Leak - 1,696 bytes in 4 blocks are definitely lost in loss record 6,145 of 6,440
: _engine(new Flite_HTS_Engine), _worker(new FLITEVoiceSynthesizer::WorkerThread(this)), _volume(6.0)
{
_volume = fgGetDouble("/sim/sound/voice-synthesizer/volume", _volume );
Flite_HTS_Engine_initialize(_engine);
Flite_HTS_Engine_load(_engine, voice.c_str());
_worker->start();
}
FLITEVoiceSynthesizer::~FLITEVoiceSynthesizer()
{
// push the special marker value
_requests.push(SynthesizeRequest::cancelThreadRequest());
_worker->join();
Flite_HTS_Engine_clear(_engine);
}
SGSoundSample * FLITEVoiceSynthesizer::synthesize(const std::string & text, double volume, double speed, double pitch )
{
SG_CLAMP_RANGE( volume, 0.0, 1.0 );
SG_CLAMP_RANGE( speed, 0.0, 10.0 );
SG_CLAMP_RANGE( pitch, 0.0, 10.0 );
HTS_Engine_set_volume( &_engine->engine, _volume );
HTS_Engine_set_speed( &_engine->engine, 0.8 + 0.4 * speed );
HTS_Engine_add_half_tone(&_engine->engine, -4.0 + 8.0 * pitch );
void* data;
int rate, count;
if ( FALSE == Flite_HTS_Engine_synthesize_samples_mono16(_engine, text.c_str(), &data, &count, &rate)) return NULL;
auto buf = std::unique_ptr<unsigned char, decltype(free)*>{
reinterpret_cast<unsigned char*>( data ),
free
};
return new SGSoundSample(buf,
count * sizeof(short),
rate,
SG_SAMPLE_MONO16);
}

View File

@@ -0,0 +1,111 @@
/*
* VoiceSynthesizer.hxx - wraps flite+hts_engine
* Copyright (C) 2014 Torsten Dreyer - torsten (at) t3r (dot) de
*
* 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.
*/
#ifndef VOICESYNTHESIZER_HXX_
#define VOICESYNTHESIZER_HXX_
#include <simgear/sound/sample.hxx>
#include <simgear/threads/SGQueue.hxx>
#include <string>
struct _Flite_HTS_Engine;
/**
* A Voice Synthesizer Interface
*/
class VoiceSynthesizer {
public:
virtual ~VoiceSynthesizer() {};
virtual SGSoundSample * synthesize( const std::string & text, double volume, double speed, double pitch ) = 0;
};
class SoundSampleReadyListener {
public:
virtual ~SoundSampleReadyListener() {}
virtual void SoundSampleReady( SGSharedPtr<SGSoundSample> ) = 0;
};
struct SynthesizeRequest {
SynthesizeRequest() {
speed = 0.5;
volume = 1.0;
pitch = 0.5;
listener = NULL;
}
SynthesizeRequest( const SynthesizeRequest & other ) {
text = other.text;
speed = other.speed;
volume = other.volume;
pitch = other.pitch;
listener = other.listener;
}
SynthesizeRequest & operator = ( const SynthesizeRequest & other ) {
text = other.text;
speed = other.speed;
volume = other.volume;
pitch = other.pitch;
listener = other.listener;
return *this;
}
// return a special marker request used to indicate the synthesis thread
// should be exited.
static SynthesizeRequest cancelThreadRequest();
std::string text;
double speed;
double volume;
double pitch;
SoundSampleReadyListener * listener;
};
/**
* A Voice Synthesizer using FLITE+HTS
*/
class FLITEVoiceSynthesizer : public VoiceSynthesizer {
public:
typedef enum {
CMU_US_ARCTIC_SLT = 0,
CSTR_UK_FEMALE,
VOICE_UNKNOWN // keep this at the end
} voice_t;
static std::string getVoicePath( voice_t voice );
static std::string getVoicePath( const std::string & voice );
FLITEVoiceSynthesizer( const std::string & voice );
~FLITEVoiceSynthesizer();
virtual SGSoundSample * synthesize( const std::string & text, double volume, double speed, double pitch );
virtual void synthesize( SynthesizeRequest & request );
private:
struct _Flite_HTS_Engine * _engine;
class WorkerThread;
WorkerThread * _worker;
typedef SGBlockingQueue<SynthesizeRequest> SynthesizeRequestList;
SynthesizeRequestList _requests;
double _volume;
};
#endif /* VOICESYNTHESIZER_HXX_ */

166
src/Sound/audioident.cxx Normal file
View File

@@ -0,0 +1,166 @@
// audioident.cxx -- audible station identifiers
//
// Written by Torsten Dreyer, September 2011
//
// 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 "audioident.hxx"
#include <simgear/sg_inlines.h>
#include <simgear/sound/sample_group.hxx>
#include <Main/globals.hxx>
#include <Sound/morse.hxx>
AudioIdent::AudioIdent( const std::string & fx_name, const double interval_secs, const int frequency_hz ) :
_fx_name(fx_name),
_frequency(frequency_hz),
_timer(0.0),
_interval(interval_secs),
_running(false)
{
}
void AudioIdent::init()
{
auto soundManager = globals->get_subsystem<SGSoundMgr>();
if (!soundManager)
return; // sound disabled
_timer = 0.0;
_ident = "";
_running = false;
_sgr = soundManager->find("avionics", true);
_sgr->tie_to_listener();
}
void AudioIdent::stop()
{
if (_sgr && _sgr->exists( _fx_name ) )
_sgr->stop( _fx_name );
_running = false;
}
void AudioIdent::start()
{
if (!_sgr)
return;
_timer = _interval;
_sgr->play_once(_fx_name);
_running = true;
}
void AudioIdent::setVolumeNorm( double volumeNorm )
{
if (!_sgr)
return;
SG_CLAMP_RANGE(volumeNorm, 0.0, 1.0);
SGSoundSample *sound = _sgr->find( _fx_name );
if ( sound != NULL ) {
sound->set_volume( volumeNorm );
}
}
void AudioIdent::setIdent( const std::string & ident, double volumeNorm )
{
if (!_sgr)
return;
// Signal may flicker very frequently (due to our realistic newnavradio...).
// Avoid recreating identical sound samples all the time, instead turn off
// volume when signal is lost, and save the most recent sample.
if (ident.empty())
volumeNorm = 0;
// don't bother with sounds when volume is OFF anyway...
if ((_ident == ident ) || (volumeNorm == 0))
{
if (!_ident.empty())
setVolumeNorm(volumeNorm);
return;
}
try {
stop();
if ( _sgr->exists( _fx_name ) )
_sgr->remove( _fx_name );
if (!ident.empty()) {
SGSoundSample* sound = FGMorse::instance()->make_ident(ident, _frequency );
sound->set_volume( volumeNorm );
if (!_sgr->add( sound, _fx_name )) {
SG_LOG(SG_SOUND, SG_WARN, "Failed to add sound '" << _fx_name << "' for ident '" << ident << "'" );
delete sound;
return;
}
start();
}
_ident = ident;
} catch (sg_io_exception& e) {
SG_LOG(SG_SOUND, SG_ALERT, e.getFormattedMessage());
}
}
void AudioIdent::update( double dt )
{
// single-shot
if( !_running || _interval < SGLimitsd::min() )
return;
_timer -= dt;
if( _timer < SGLimitsd::min() ) {
_timer = _interval;
stop();
start();
}
}
// FIXME: shall transmit at least 6 wpm (ICAO Annex 10 - 3.5.3.6.3)
DMEAudioIdent::DMEAudioIdent( const std::string & fx_name )
: AudioIdent( fx_name, 40, FGMorse::HI_FREQUENCY )
{
}
//FIXME: for co-located VOR/DME or ILS/DME, assign four time-slots
// 3xVOR/ILS ident, 1xDME ident
// FIXME: shall transmit at approx. 7 wpm (ICAO Annex 10 - 3.3.6.5.1)
VORAudioIdent::VORAudioIdent( const std::string & fx_name )
: AudioIdent( fx_name, 10, FGMorse::LO_FREQUENCY )
{
}
//FIXME: LOCAudioIdent at approx 7wpm (ICAO Annex 10 - 3.1.3.9.4)
// not less than six times per minute at approx equal intervals
// frequency 1020+/-50Hz (3.1.3.9.2)
LOCAudioIdent::LOCAudioIdent( const std::string & fx_name )
: AudioIdent( fx_name, 10, FGMorse::LO_FREQUENCY )
{
}
// FIXME: NDBAudioIdent at approx 7 wpm (ICAO ANNEX 10 - 3.4.5.1)
// at least once every 10s (3.4.5.2.1)
// frequency 1020+/-50Hz or 400+/-25Hz (3.4.5.4)

70
src/Sound/audioident.hxx Normal file
View File

@@ -0,0 +1,70 @@
// audioident.hxx -- audible station identifiers
//
// Written by Torsten Dreyer, September 2011
//
// 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.
//
#ifndef _FGAUDIOIDENT_HXX
#define _FGAUDIOIDENT_HXX
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string>
#include <simgear/sound/soundmgr.hxx>
class AudioIdent {
public:
AudioIdent( const std::string & fx_name, const double interval_secs, const int frequency );
void init();
void setVolumeNorm( double volumeNorm );
void setIdent( const std::string & ident, double volumeNorm );
void update( double dt );
private:
void stop();
void start();
SGSharedPtr<SGSampleGroup> _sgr;
std::string _fx_name;
const int _frequency;
std::string _ident;
double _timer;
double _interval;
bool _running;
};
class DMEAudioIdent : public AudioIdent {
public:
DMEAudioIdent( const std::string & fx_name );
};
class VORAudioIdent : public AudioIdent {
public:
VORAudioIdent( const std::string & fx_name );
};
class LOCAudioIdent : public AudioIdent {
public:
LOCAudioIdent( const std::string & fx_name );
};
#endif // _FGAUDIOIDENT_HXX

163
src/Sound/beacon.cxx Normal file
View File

@@ -0,0 +1,163 @@
// beacon.cxx -- Morse code generation class
//
// Written by Curtis Olson, started March 2001.
//
// 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 <stdlib.h>
#include <cstring>
#include "beacon.hxx"
#include <simgear/sound/sample.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/debug/logstream.hxx>
// constructor
FGBeacon::FGBeacon()
{
}
// destructor
FGBeacon::~FGBeacon() {
}
// allocate and initialize sound samples
bool FGBeacon::init() {
unsigned char *ptr;
size_t i, len;
auto inner_buf = std::unique_ptr<unsigned char, decltype(free)*>{
reinterpret_cast<unsigned char*>( malloc( INNER_SIZE ) ),
free
};
auto middle_buf = std::unique_ptr<unsigned char, decltype(free)*>{
reinterpret_cast<unsigned char*>( malloc( MIDDLE_SIZE ) ),
free
};
auto outer_buf = std::unique_ptr<unsigned char, decltype(free)*>{
reinterpret_cast<unsigned char*>( malloc( OUTER_SIZE ) ),
free
};
// Make inner marker beacon sound
len= (int)(INNER_DIT_LEN / 2.0 );
unsigned char inner_dit[INNER_DIT_LEN];
make_tone( inner_dit, INNER_FREQ, len, INNER_DIT_LEN, TRANSITION_BYTES );
ptr = inner_buf.get();
for ( i = 0; i < 6; ++i ) {
memcpy( ptr, inner_dit, INNER_DIT_LEN );
ptr += INNER_DIT_LEN;
}
try {
inner = new SGSoundSample( inner_buf, INNER_SIZE, BYTES_PER_SECOND );
inner->set_reference_dist( 10.0 );
inner->set_max_dist( 20.0 );
// Make middle marker beacon sound
len= (int)(MIDDLE_DIT_LEN / 2.0 );
unsigned char middle_dit[MIDDLE_DIT_LEN];
make_tone( middle_dit, MIDDLE_FREQ, len, MIDDLE_DIT_LEN,
TRANSITION_BYTES );
len= (int)(MIDDLE_DAH_LEN * 3 / 4.0 );
unsigned char middle_dah[MIDDLE_DAH_LEN];
make_tone( middle_dah, MIDDLE_FREQ, len, MIDDLE_DAH_LEN,
TRANSITION_BYTES );
ptr = (unsigned char*)middle_buf.get();
memcpy( ptr, middle_dit, MIDDLE_DIT_LEN );
ptr += MIDDLE_DIT_LEN;
memcpy( ptr, middle_dah, MIDDLE_DAH_LEN );
middle = new SGSoundSample( middle_buf, MIDDLE_SIZE, BYTES_PER_SECOND);
middle->set_reference_dist( 10.0 );
middle->set_max_dist( 20.0 );
// Make outer marker beacon sound
len= (int)(OUTER_DAH_LEN * 3.0 / 4.0 );
unsigned char outer_dah[OUTER_DAH_LEN];
make_tone( outer_dah, OUTER_FREQ, len, OUTER_DAH_LEN,
TRANSITION_BYTES );
ptr = (unsigned char*)outer_buf.get();
memcpy( ptr, outer_dah, OUTER_DAH_LEN );
ptr += OUTER_DAH_LEN;
memcpy( ptr, outer_dah, OUTER_DAH_LEN );
outer = new SGSoundSample( outer_buf, OUTER_SIZE, BYTES_PER_SECOND );
outer->set_reference_dist( 10.0 );
outer->set_max_dist( 20.0 );
} catch ( sg_io_exception &e ) {
SG_LOG(SG_SOUND, SG_ALERT, e.getFormattedMessage());
}
return true;
}
FGBeacon * FGBeacon::_instance = NULL;
FGBeacon * FGBeacon::instance()
{
if( _instance == NULL ) {
_instance = new FGBeacon();
_instance->init();
}
return _instance;
}
static const uint64_t sizeToUSec = 1000000UL / FGBeacon::BYTES_PER_SECOND;
FGBeacon::BeaconTiming FGBeacon::getTimingForInner() const
{
BeaconTiming r;
const uint64_t ditLen = INNER_DIT_LEN * sizeToUSec;
r.durationUSec = ditLen;
r.periodsUSec[0] = ditLen / 2;
r.periodsUSec[1] = ditLen / 2;
return r;
}
FGBeacon::BeaconTiming FGBeacon::getTimingForMiddle() const
{
BeaconTiming r;
const uint64_t ditLen = MIDDLE_DIT_LEN * sizeToUSec;
const uint64_t dahLen = MIDDLE_DAH_LEN * sizeToUSec;
r.durationUSec = MIDDLE_SIZE * sizeToUSec;
r.periodsUSec[0] = ditLen;
r.periodsUSec[1] = ditLen;
r.periodsUSec[2] = (dahLen * 3) / 4;
r.periodsUSec[3] = dahLen - r.periodsUSec[2];
return r;
}
FGBeacon::BeaconTiming FGBeacon::getTimingForOuter() const
{
BeaconTiming r;
const uint64_t dahLen = OUTER_DAH_LEN * sizeToUSec;
r.durationUSec = dahLen;
r.periodsUSec[0] = (dahLen * 3) / 4;
r.periodsUSec[1] = dahLen - r.periodsUSec[0];
return r;
}

123
src/Sound/beacon.hxx Normal file
View File

@@ -0,0 +1,123 @@
/**
* \file beacon.hxx -- Provides marker beacon audio generation.
*/
// Written by Curtis Olson, started March 2001.
//
// 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.
//
#ifndef _BEACON_HXX
#define _BEACON_HXX
#include <array>
#include "soundgenerator.hxx"
#include <simgear/compiler.h>
#include <simgear/sound/soundmgr.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
// Quoting from http://www.smartregs.com/data/sa326.htm
// Smart REGS Glossary - marker beacon
//
// An electronic navigation facility transmitting a 75 MHz vertical fan
// or boneshaped radiation pattern. Marker beacons are identified by
// their modulation frequency and keying code, and when received by
// compatible airborne equipment, indicate to the pilot, both aurally
// and visually, that he is passing over the facility.
// (See outer marker middle marker inner marker.)
//
// Smart REGS Glossary - outer marker
//
// A marker beacon at or near the glideslope intercept altitude of an
// ILS approach. It is keyed to transmit two dashes per second on a
// 400 Hz tone, which is received aurally and visually by compatible
// airborne equipment. The OM is normally located four to seven miles from
// the runway threshold on the extended centerline of the runway.
//
// Smart REGS Glossary - middle marker
//
// A marker beacon that defines a point along the glideslope of an
// ILS normally located at or near the point of decision height
// (ILS Category I). It is keyed to transmit alternate dots and dashes,
// with the alternate dots and dashes keyed at the rate of 95 dot/dash
// combinations per minute on a 1300 Hz tone, which is received
// aurally and visually by compatible airborne equipment.
//
// Smart REGS Glossary - inner marker
//
// A marker beacon used with an ILS (CAT II) precision approach located
// between the middle marker and the end of the ILS runway,
// transmitting a radiation pattern keyed at six dots per second and
// indicating to the pilot, both aurally and visually, that he is at
// the designated decision height (DH), normally 100 feet above the
// touchdown zone elevation, on the ILS CAT II approach. It also marks
// progress during a CAT III approach.
// (See instrument landing system) (Refer to AIM.)
// manages everything we need to know for an individual sound sample
class FGBeacon : public FGSoundGenerator {
private:
static const int INNER_FREQ = 3000;
static const int MIDDLE_FREQ = 1300;
static const int OUTER_FREQ = 400;
static const int INNER_SIZE = BYTES_PER_SECOND;
static const int MIDDLE_SIZE = (int)(BYTES_PER_SECOND * 60 / 95 );
static const int OUTER_SIZE = BYTES_PER_SECOND;
static const int INNER_DIT_LEN = (int)(BYTES_PER_SECOND / 6);
static const int MIDDLE_DIT_LEN = (int)(MIDDLE_SIZE / 3);
static const int MIDDLE_DAH_LEN = (int)(MIDDLE_SIZE * 2 / 3);
static const int OUTER_DAH_LEN = (int)(BYTES_PER_SECOND / 2);
SGSharedPtr<SGSoundSample> inner;
SGSharedPtr<SGSoundSample> middle;
SGSharedPtr<SGSoundSample> outer;
// allocate and initialize sound samples
bool init();
static FGBeacon * _instance;
public:
FGBeacon();
~FGBeacon();
static FGBeacon * instance();
SGSoundSample *get_inner() { return inner; }
SGSoundSample *get_middle() { return middle; }
SGSoundSample *get_outer() { return outer; }
struct BeaconTiming {
uint64_t durationUSec;
std::array<uint64_t, 4> periodsUSec;
};
BeaconTiming getTimingForInner() const;
BeaconTiming getTimingForMiddle() const;
BeaconTiming getTimingForOuter() const;
};
#endif // _BEACON_HXX

237
src/Sound/fg_fx.cxx Normal file
View File

@@ -0,0 +1,237 @@
// 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 - 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.
//
// $Id$
#ifdef _MSC_VER
#pragma warning (disable: 4786)
#endif
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "fg_fx.hxx"
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/sentryIntegration.hxx>
#include <Sound/soundmanager.hxx>
#include <algorithm>
#include <simgear/debug/ErrorReportingCallback.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/sound/xmlsound.hxx>
FGFX::FGFX ( const std::string &refname, SGPropertyNode *props ) :
_props( props )
{
if (!props) {
_is_aimodel = false;
_props = globals->get_props();
_enabled = fgGetNode("/sim/sound/effects/enabled", true);
_volume = fgGetNode("/sim/sound/effects/volume", true);
} else {
_is_aimodel = true;
_enabled = _props->getNode("/sim/sound/aimodels/enabled", true);
_enabled->setBoolValue(fgGetBool("/sim/sound/effects/enabled"));
_volume = _props->getNode("/sim/sound/aimodels/volume", true);
_volume->setFloatValue(fgGetFloat("/sim/sound/effects/volume"));
}
_avionics_enabled = _props->getNode("sim/sound/avionics/enabled", true);
_avionics_volume = _props->getNode("sim/sound/avionics/volume", true);
_avionics_ext = _props->getNode("sim/sound/avionics/external-view", true);
_internal = _props->getNode("sim/current-view/internal", true);
_atc_enabled = _props->getNode("sim/sound/atc/enabled", true);
_atc_volume = _props->getNode("sim/sound/atc/volume", true);
_atc_ext = _props->getNode("sim/sound/atc/external-view", true);
_machwave_active = _props->getNode("sim/sound/machwave/active", true);
_machwave_volume = _props->getNode("sim/sound/machwave/offset-m", true);
_smgr = globals->get_subsystem<FGSoundManager>();
if (!_smgr) {
return;
}
_active = _smgr->is_active();
_refname = refname;
_smgr->add(this, refname);
if (!_is_aimodel) // only for the main aircraft
{
_avionics = _smgr->find("avionics", true);
_avionics->tie_to_listener();
_atc = _smgr->find("atc", true);
_atc->tie_to_listener();
}
}
void FGFX::unbind()
{
if (_smgr)
{
_smgr->remove(_refname);
}
// because SGXmlSound has an owning ref back to us, we need to
// clear these here, or we will never get destroyed
std::for_each(_sound.begin(), _sound.end(), [](const SGXmlSound* snd) { delete snd; });
_sound.clear();
}
FGFX::~FGFX ()
{
}
void
FGFX::init()
{
if (!_smgr) {
return;
}
SGPropertyNode *node = _props->getNode("sim/sound", true);
std::string path_str = node->getStringValue("path");
if (path_str.empty()) {
SG_LOG(SG_SOUND, SG_ALERT, "No path in sim/sound/path");
return;
}
SGPath path = globals->resolve_aircraft_path(path_str);
if (path.isNull())
{
simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::AudioFX,
"Failed to find FX XML file:" + path_str, sg_location{path_str});
SG_LOG(SG_SOUND, SG_ALERT,
"File not found: '" << path_str);
return;
}
SG_LOG(SG_SOUND, SG_INFO, "Reading sound " << node->getNameString()
<< " from " << path);
SGPropertyNode root;
try {
readProperties(path, &root);
} catch (const sg_exception& e) {
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::AudioFX,
"Failure loading FX XML:" + e.getFormattedMessage(), e.getLocation());
return;
}
node = root.getNode("fx");
if(node) {
for (int i = 0; i < node->nChildren(); ++i) {
std::unique_ptr<SGXmlSound> soundfx{new SGXmlSound};
try {
bool ok = soundfx->init( _props, node->getChild(i), this, _avionics,
path.dir() );
if (ok) {
// take the pointer out of the unique ptr so it's not deleted
_sound.push_back( soundfx.release() );
}
} catch ( sg_exception &e ) {
SG_LOG(SG_SOUND, SG_ALERT, e.getFormattedMessage());
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::AudioFX,
"Failure creating Audio FX:" + e.getFormattedMessage(), path);
}
}
}
}
void
FGFX::reinit()
{
SGSampleGroup::stop();
std::for_each(_sound.begin(), _sound.end(), [](const SGXmlSound* snd) { delete snd; });
_sound.clear();
init();
SGSampleGroup::resume();
}
void
FGFX::update (double dt)
{
if (!_smgr) {
return;
}
if (!_active && _smgr->is_active())
{
_active = true;
for ( unsigned int i = 0; i < _sound.size(); i++ ) {
_sound[i]->start();
}
}
if ( _enabled->getBoolValue() ) {
if ( _avionics)
{
const bool e = _avionics_enabled->getBoolValue();
if (e && (_avionics_ext->getBoolValue() || _internal->getBoolValue())) {
// avionics sound is enabled
_avionics->resume(); // no-op if already in resumed state
_avionics->set_volume( _avionics_volume->getFloatValue() );
}
else
_avionics->suspend();
}
if ( _atc)
{
const bool e = _atc_enabled->getBoolValue();
if (e && (_atc_ext->getBoolValue() || _internal->getBoolValue())) {
// ATC sound is enabled
_atc->resume(); // no-op if already in resumed state
_atc->set_volume( _atc_volume->getFloatValue() );
}
else
_atc->suspend();
}
_machwave_active->setBoolValue(_mInCone);
_machwave_volume->setFloatValue(_mOffset_m);
set_volume( _volume->getDoubleValue() );
resume();
// update sound effects if not paused
for ( unsigned int i = 0; i < _sound.size(); i++ ) {
_sound[i]->update(dt);
}
SGSampleGroup::update(dt);
}
else
suspend();
}
// end of fg_fx.cxx

86
src/Sound/fg_fx.hxx Normal file
View File

@@ -0,0 +1,86 @@
// fg_fx.hxx -- Sound effect management class
//
// Started by David Megginson, October 2001
// (Reuses some code from main.cxx, probably by Curtis Olson)
//
// 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.
//
// $Id$
#ifndef __FGFX_HXX
#define __FGFX_HXX 1
#include <simgear/compiler.h>
#include <vector>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
#include <simgear/sound/sample_group.hxx>
class SGXmlSound;
/**
* Generator for FlightGear model sound effects.
*
* This module uses a FGSampleGroup class to generate sound effects based
* on current flight conditions. The sound manager must be initialized
* before this object is.
*
* This module will load and play a set of sound effects defined in an
* xml file and tie them to various property states.
*/
class FGFX : public SGSampleGroup
{
public:
FGFX ( const std::string &refname, SGPropertyNode *props = 0 );
virtual ~FGFX ();
void init ();
void reinit ();
void update (double dt) override;
void unbind();
private:
bool _active;
bool _is_aimodel;
SGSharedPtr<SGSampleGroup> _avionics;
SGSharedPtr<SGSampleGroup> _atc;
std::vector<SGXmlSound *> _sound;
SGPropertyNode_ptr _props;
SGPropertyNode_ptr _enabled;
SGPropertyNode_ptr _volume;
SGPropertyNode_ptr _avionics_enabled;
SGPropertyNode_ptr _avionics_volume;
SGPropertyNode_ptr _avionics_ext;
SGPropertyNode_ptr _internal;
SGPropertyNode_ptr _atc_enabled;
SGPropertyNode_ptr _atc_volume;
SGPropertyNode_ptr _atc_ext;
SGPropertyNode_ptr _machwave_active;
SGPropertyNode_ptr _machwave_volume;
};
#endif
// end of fg_fx.hxx

88
src/Sound/flitevoice.cxx Normal file
View File

@@ -0,0 +1,88 @@
// speech synthesis interface subsystem
//
// Written by Torsten Dreyer, started April 2014
//
// Copyright (C) 2014 Torsten Dreyer - torsten (at) t3r (dot) de
//
// 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.
//
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "flitevoice.hxx"
#include <Main/fg_props.hxx>
#include <simgear/sound/sample_group.hxx>
#include <flite_hts_engine.h>
using std::string;
#include "VoiceSynthesizer.hxx"
FGFLITEVoice::FGFLITEVoice(FGVoiceMgr * mgr, const SGPropertyNode_ptr node, const char * sampleGroupRefName)
: FGVoice(mgr), _synthesizer( NULL), _seconds_to_run(0.0)
{
_sampleName = node->getStringValue("desc", node->getPath().c_str());
SGPath voice = globals->get_fg_root() / "ATC" /
node->getStringValue("htsvoice", "cmu_us_arctic_slt.htsvoice");
_synthesizer = new FLITEVoiceSynthesizer(voice.utf8Str());
SGSoundMgr *smgr = globals->get_subsystem<SGSoundMgr>();
_sgr = smgr->find(sampleGroupRefName, true);
_sgr->tie_to_listener();
node->getNode("text", true)->addChangeListener(this);
SG_LOG(SG_SOUND, SG_DEBUG, "FLITEVoice initialized for sample-group '" << sampleGroupRefName
<< "'. Samples will be named '" << _sampleName << "' "
<< "voice is '" << voice << "'");
}
FGFLITEVoice::~FGFLITEVoice()
{
delete _synthesizer;
}
void FGFLITEVoice::speak(const string & msg)
{
// this is called from voice.cxx:FGVoiceMgr::FGVoiceThread::run
string s = simgear::strutils::strip(msg);
if (!s.empty()) {
_sampleQueue.push(_synthesizer->synthesize(msg, 1.0, 0.5, 0.5));
}
}
void FGFLITEVoice::update(double dt)
{
_seconds_to_run -= dt;
if (_seconds_to_run < 0.0) {
SGSharedPtr<SGSoundSample> sample = _sampleQueue.pop();
if (sample.valid()) {
_sgr->remove(_sampleName);
_sgr->add(sample, _sampleName);
_sgr->resume();
_sgr->play(_sampleName, false);
// Don't play any further TTS until we've finished playing this sample,
// allowing 500ms for a gap between transmissions. Good radio comms!
_seconds_to_run = 0.5 + ((float) sample->get_no_samples()) / ((float) sample->get_frequency());
}
}
}

51
src/Sound/flitevoice.hxx Normal file
View File

@@ -0,0 +1,51 @@
// speech synthesis interface subsystem
//
// Written by Torsten Dreyer, started April 2014
//
// Copyright (C) 2014 Torsten Dreyer - torsten (at) t3r (dot) de
//
// 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.
//
// $Id$
#ifndef _FLITEVOICE_HXX
#define _FLITEVOICE_HXX
#include "voice.hxx"
#include <simgear/sound/soundmgr.hxx>
#include <simgear/sound/sample.hxx>
#include <simgear/threads/SGQueue.hxx>
class VoiceSynthesizer;
class FGFLITEVoice: public FGVoiceMgr::FGVoice {
public:
FGFLITEVoice(FGVoiceMgr *, const SGPropertyNode_ptr, const char * sampleGroupRefName = "flite-voice");
virtual ~FGFLITEVoice();
virtual void speak(const std::string & msg);
virtual void update(double dt);
private:
FGFLITEVoice(const FGFLITEVoice & other);
FGFLITEVoice & operator =(const FGFLITEVoice & other);
SGSharedPtr<SGSampleGroup> _sgr;
VoiceSynthesizer * _synthesizer;
SGLockedQueue<SGSharedPtr<SGSoundSample> > _sampleQueue;
std::string _sampleName;
double _seconds_to_run;
};
#endif // _FLITEVOICE_HXX

256
src/Sound/morse.cxx Normal file
View File

@@ -0,0 +1,256 @@
// morse.cxx -- Morse code generation class
//
// Written by Curtis Olson, started March 2001.
//
// 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 <simgear/constants.h>
#include "morse.hxx"
#include <simgear/sound/sample.hxx>
#include <cstring>
#include <stdlib.h>
static const char DI = '1';
static const char DIT = '1';
static const char DA = '2';
static const char DAH = '2';
static const char END = '0';
static const char alphabet[26][4] = {
{ DI, DAH, END, END }, /* A */
{ DA, DI, DI, DIT }, /* B */
{ DA, DI, DA, DIT }, /* C */
{ DA, DI, DIT, END }, /* D */
{ DIT, END, END, END }, /* E */
{ DI, DI, DA, DIT }, /* F */
{ DA, DA, DIT, END }, /* G */
{ DI, DI, DI, DIT }, /* H */
{ DI, DIT, END, END }, /* I */
{ DI, DA, DA, DAH }, /* J */
{ DA, DI, DAH, END }, /* K */
{ DI, DA, DI, DIT }, /* L */
{ DA, DAH, END, END }, /* M */
{ DA, DIT, END, END }, /* N */
{ DA, DA, DAH, END }, /* O */
{ DI, DA, DA, DIT }, /* P */
{ DA, DA, DI, DAH }, /* Q */
{ DI, DA, DIT, END }, /* R */
{ DI, DI, DIT, END }, /* S */
{ DAH, END, END, END }, /* T */
{ DI, DI, DAH, END }, /* U */
{ DI, DI, DI, DAH }, /* V */
{ DI, DA, DAH, END }, /* W */
{ DA, DI, DI, DAH }, /* X */
{ DA, DI, DA, DAH }, /* Y */
{ DA, DA, DI, DIT } /* Z */
};
static const char numerals[10][5] = {
{ DA, DA, DA, DA, DAH }, // 0
{ DI, DA, DA, DA, DAH }, // 1
{ DI, DI, DA, DA, DAH }, // 2
{ DI, DI, DI, DA, DAH }, // 3
{ DI, DI, DI, DI, DAH }, // 4
{ DI, DI, DI, DI, DIT }, // 5
{ DA, DI, DI, DI, DIT }, // 6
{ DA, DA, DI, DI, DIT }, // 7
{ DA, DA, DA, DI, DIT }, // 8
{ DA, DA, DA, DA, DIT } // 9
};
// constructor
FGMorse::FGMorse() {
}
// destructor
FGMorse::~FGMorse() {
}
// allocate and initialize sound samples
bool FGMorse::init() {
// Make Low DIT
make_tone( lo_dit, LO_FREQUENCY, DIT_SIZE - COUNT_SIZE, DIT_SIZE,
TRANSITION_BYTES );
// Make High DIT
make_tone( hi_dit, HI_FREQUENCY, DIT_SIZE - COUNT_SIZE, DIT_SIZE,
TRANSITION_BYTES );
// Make Low DAH
make_tone( lo_dah, LO_FREQUENCY, DAH_SIZE - COUNT_SIZE, DAH_SIZE,
TRANSITION_BYTES );
// Make High DAH
make_tone( hi_dah, HI_FREQUENCY, DAH_SIZE - COUNT_SIZE, DAH_SIZE,
TRANSITION_BYTES );
// Make SPACE
int i;
for ( i = 0; i < SPACE_SIZE; ++i ) {
space[ i ] = (unsigned char) ( 0.5 * 255 ) ;
}
return true;
}
// allocate and initialize sound samples
bool FGMorse::cust_init(const int freq ) {
int i;
// Make DIT
make_tone( cust_dit, freq, DIT_SIZE - COUNT_SIZE, DIT_SIZE,
TRANSITION_BYTES );
// Make DAH
make_tone( cust_dah, freq, DAH_SIZE - COUNT_SIZE, DAH_SIZE,
TRANSITION_BYTES );
// Make SPACE
for ( i = 0; i < SPACE_SIZE; ++i ) {
space[ i ] = (unsigned char) ( 0.5 * 255 ) ;
}
return true;
}
// make a SGSoundSample morse code transmission for the specified string
SGSoundSample *FGMorse::make_ident( const std::string& id, const int freq ) {
char *idptr = (char *)id.c_str();
int length = 0;
int i, j;
// 0. Select the frequency. If custom frequency, generate the
// sound fragments we need on the fly.
unsigned char *dit_ptr, *dah_ptr;
if ( freq == LO_FREQUENCY ) {
dit_ptr = lo_dit;
dah_ptr = lo_dah;
} else if ( freq == HI_FREQUENCY ) {
dit_ptr = hi_dit;
dah_ptr = hi_dah;
} else {
cust_init( freq );
dit_ptr = cust_dit;
dah_ptr = cust_dah;
}
// 1. Determine byte length of message
for ( i = 0; i < (int)id.length(); ++i ) {
if ( idptr[i] >= 'A' && idptr[i] <= 'Z' ) {
int c = (int)(idptr[i] - 'A');
for ( j = 0; j < 4 && alphabet[c][j] != END; ++j ) {
if ( alphabet[c][j] == DIT ) {
length += DIT_SIZE;
} else if ( alphabet[c][j] == DAH ) {
length += DAH_SIZE;
}
}
length += SPACE_SIZE;
} else if ( idptr[i] >= '0' && idptr[i] <= '9' ) {
int c = (int)(idptr[i] - '0');
for ( j = 0; j < 5; ++j) {
if ( numerals[c][j] == DIT ) {
length += DIT_SIZE;
} else if ( numerals[c][j] == DAH ) {
length += DAH_SIZE;
}
}
length += SPACE_SIZE;
} else {
// skip unknown character
}
}
// add 2x more space to the end of the string
length += 2 * SPACE_SIZE;
// 2. Allocate space for the message
auto buffer = std::unique_ptr<unsigned char, decltype(free)*>{
reinterpret_cast<unsigned char*>( malloc( length ) ),
free
};
// 3. Assemble the message;
unsigned char *buf_ptr = buffer.get();
for ( i = 0; i < (int)id.length(); ++i ) {
if ( idptr[i] >= 'A' && idptr[i] <= 'Z' ) {
int c = (int)(idptr[i] - 'A');
for ( j = 0; j < 4 && alphabet[c][j] != END; ++j ) {
if ( alphabet[c][j] == DIT ) {
memcpy( buf_ptr, dit_ptr, DIT_SIZE );
buf_ptr += DIT_SIZE;
} else if ( alphabet[c][j] == DAH ) {
memcpy( buf_ptr, dah_ptr, DAH_SIZE );
buf_ptr += DAH_SIZE;
}
}
memcpy( buf_ptr, space, SPACE_SIZE );
buf_ptr += SPACE_SIZE;
} else if ( idptr[i] >= '0' && idptr[i] <= '9' ) {
int c = (int)(idptr[i] - '0');
for ( j = 0; j < 5; ++j ) {
if ( numerals[c][j] == DIT ) {
memcpy( buf_ptr, dit_ptr, DIT_SIZE );
buf_ptr += DIT_SIZE;
} else if ( numerals[c][j] == DAH ) {
memcpy( buf_ptr, dah_ptr, DAH_SIZE );
buf_ptr += DAH_SIZE;
}
}
memcpy( buf_ptr, space, SPACE_SIZE );
buf_ptr += SPACE_SIZE;
} else {
// skip unknown character
}
}
memcpy( buf_ptr, space, SPACE_SIZE );
buf_ptr += SPACE_SIZE;
memcpy( buf_ptr, space, SPACE_SIZE );
buf_ptr += SPACE_SIZE;
// 4. create the simple sound and return
SGSoundSample *sample = new SGSoundSample( buffer, length,
BYTES_PER_SECOND );
sample->set_reference_dist( 10.0 );
sample->set_max_dist( 20.0 );
return sample;
}
FGMorse * FGMorse::_instance = NULL;
FGMorse * FGMorse::instance()
{
if( _instance == NULL ) {
_instance = new FGMorse();
_instance->init();
}
return _instance;
}

125
src/Sound/morse.hxx Normal file
View File

@@ -0,0 +1,125 @@
// morse.hxx -- Morse code generation class
//
// Written by Curtis Olson, started March 2001.
//
// 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.
//
#ifndef _MORSE_HXX
#define _MORSE_HXX
#include "soundgenerator.hxx"
#include <simgear/compiler.h>
#include <simgear/sound/soundmgr.hxx>
// Quoting from http://www.kluft.com/~ikluft/ham/morse-intro.html by
// Ian Kluft KO6YQ <ikluft@kluft.com>
//
// [begin quote]
//
// What is the Standard for Measuring Morse Code Speed?
//
// [This was adapted from the Ham Radio FAQ which used to be posted on UseNet.]
//
// The word PARIS was chosen as the standard length for CW code
// speed. Each dit counts for one count, each dah counts for three
// counts, intra-character spacing is one count, inter-character
// spacing is three counts and inter-word spacing is seven counts, so
// the word PARIS is exactly 50 counts:
//
// PPPPPPPPPPPPPP AAAAAA RRRRRRRRRR IIIIII SSSSSSSSSS
// di da da di di da di da di di di di di di
// 1 1 3 1 3 1 1 3 1 1 3 3 1 1 3 1 1 3 1 1 1 3 1 1 1 1 1 7 = 50
// ^ ^ ^
// ^Intra-character ^Inter-character Inter-word^
//
// So 5 words-per-minute = 250 counts-per-minute / 50 counts-per-word
// or one count every 240 milliseconds. 13 words-per-minute is one
// count every ~92.3 milliseconds. This method of sending code is
// sometimes called "Slow Code", because at 5 wpm it sounds VERY SLOW.
//
// The "Farnsworth" method is accomplished by sending the dits and
// dahs and intra-character spacing at a higher speed, then increasing
// the inter-character and inter-word spacing to slow the sending
// speed down to the desired speed. For example, to send at 5 wpm with
// 13 wpm characters in Farnsworth method, the dits and
// intra-character spacing would be 92.3 milliseconds, the dah would
// be 276.9 milliseconds, the inter-character spacing would be 1.443
// seconds and inter-word spacing would be 3.367 seconds.
//
// [end quote]
// Ok, back to Curt
// My formulation is based dit = 1 count, dah = 3 counts, 1 count for
// intRA-character space, 3 counts for intER-character space. Target
// is 5 wpm which by the above means 1 count = 240 milliseconds.
//
// AIM 1-1-7 (f) states that the frequency of the tone should be 1020
// Hz for the VOR ident.
// manages everything we need to know for an individual sound sample
class FGMorse : public FGSoundGenerator {
private:
unsigned char hi_dit[ DIT_SIZE ] ;
unsigned char lo_dit[ DIT_SIZE ] ;
unsigned char hi_dah[ DAH_SIZE ] ;
unsigned char lo_dah[ DAH_SIZE ] ;
unsigned char space[ SPACE_SIZE ] ;
unsigned char cust_dit[ DIT_SIZE ] ;
unsigned char cust_dah[ DAH_SIZE ] ;
static FGMorse * _instance;
bool cust_init( const int freq );
// allocate and initialize sound samples
bool init();
public:
static const int BYTES_PER_SECOND = 22050;
// static const int BEAT_LENGTH = 240; // milleseconds (5 wpm)
static const int BEAT_LENGTH = 92; // milleseconds (13 wpm)
static const int TRANSITION_BYTES = BYTES_PER_SECOND/200; // aka (int)(0.005 * BYTES_PER_SECOND);
static const int COUNT_SIZE = BYTES_PER_SECOND * BEAT_LENGTH / 1000;
static const int DIT_SIZE = 2 * COUNT_SIZE; // 2 counts
static const int DAH_SIZE = 4 * COUNT_SIZE; // 4 counts
static const int SPACE_SIZE = 3 * COUNT_SIZE; // 3 counts
static const int LO_FREQUENCY = 1020; // AIM 1-1-7 (f) specified in Hz
static const int HI_FREQUENCY = 1350; // AIM 1-1-7 (f) specified in Hz
FGMorse();
~FGMorse();
static FGMorse * instance();
// make a SimpleSound morse code transmission for the specified string
SGSoundSample *make_ident( const std::string& id,
const int freq = LO_FREQUENCY );
};
#endif // _MORSE_HXX

102
src/Sound/sample_queue.cxx Normal file
View File

@@ -0,0 +1,102 @@
// _samplequeue.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 - 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.
//
// $Id$
#ifdef _MSC_VER
#pragma warning (disable: 4786)
#endif
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "sample_queue.hxx"
#include <Main/fg_props.hxx>
#include <simgear/sound/soundmgr.hxx>
#include <simgear/sound/sample.hxx>
FGSampleQueue::FGSampleQueue ( SGSoundMgr *smgr, const std::string &refname ) :
last_enabled( true ),
last_volume( 0.0 ),
_enabled( fgGetNode("/sim/sound/"+refname+"/enabled", true) ),
_volume( fgGetNode("/sim/sound/"+refname+"/volume", true) )
{
SGSampleGroup::_smgr = smgr;
SGSampleGroup::_smgr->add(this, refname);
SGSampleGroup::_refname = refname;
}
FGSampleQueue::~FGSampleQueue ()
{
}
void
FGSampleQueue::update (double dt)
{
// command sound manger
bool new_enabled = _enabled->getBoolValue();
if ( new_enabled != last_enabled ) {
if ( new_enabled ) {
resume();
} else {
suspend();
}
last_enabled = new_enabled;
}
if ( new_enabled ) {
double volume = _volume->getDoubleValue();
if ( volume != last_volume ) {
set_volume( volume );
last_volume = volume;
}
// process message queue
const std::string msgid = "Sequential Audio Message";
bool now_playing = false;
if ( exists( msgid ) ) {
now_playing = is_playing( msgid );
if ( !now_playing ) {
// current message finished, stop and remove
stop( msgid ); // removes source
remove( msgid ); // removes buffer
}
}
if ( !now_playing ) {
// message queue idle, add next sound if we have one
if ( ! _messages.empty() ) {
SGSampleGroup::add( _messages.front(), msgid );
_messages.pop();
play_once( msgid );
}
}
SGSampleGroup::update(dt);
}
}
// end of _samplequeue.cxx

View File

@@ -0,0 +1,71 @@
// sample_queue.hxx -- sample queue management class
//
// Started by David Megginson, October 2001
// (Reuses some code from main.cxx, probably by Curtis Olson)
//
// 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.
//
// $Id$
#ifndef __FGSAMPLE_QUEUE_HXX
#define __FGSAMPLE_QUEUE_HXX 1
#include <simgear/compiler.h>
#include <queue>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
#include <simgear/sound/sample_group.hxx>
class SGSoundSample;
/**
* FlightGear sample queue class
*
* This modules maintains a queue of 'message' audio files. These
* are played sequentially with no overlap until the queue is finished.
* This second mechanisms is useful for things like tutorial messages or
* background ATC chatter.
*/
class FGSampleQueue : public SGSampleGroup
{
public:
FGSampleQueue ( SGSoundMgr *smgr, const std::string &refname );
virtual ~FGSampleQueue ();
virtual void update (double dt);
inline void add (SGSharedPtr<SGSoundSample> msg) { _messages.push(msg); }
private:
std::queue< SGSharedPtr<SGSoundSample> > _messages;
bool last_enabled;
double last_volume;
SGPropertyNode_ptr _enabled;
SGPropertyNode_ptr _volume;
};
#endif
// end of fg_fx.hxx

View File

@@ -0,0 +1,64 @@
// soundgenerator.cxx -- simple sound generation
//
// Written by Curtis Olson, started March 2001.
//
// 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 "soundgenerator.hxx"
#include <simgear/constants.h>
FGSoundGenerator::~FGSoundGenerator()
{
}
// Make a tone of specified freq and total_len with trans_len ramp in
// and out and only the first len bytes with sound, the rest with
// silence
void FGSoundGenerator::make_tone( unsigned char *buf, int freq,
int len, int total_len, int trans_len )
{
int i, j;
for ( i = 0; i < trans_len; ++i ) {
float level = ( sin( (double) i * SGD_2PI / (BYTES_PER_SECOND / freq) ) )
* ((double)i / trans_len) / 2.0 + 0.5;
/* Convert to unsigned byte */
buf[ i ] = (unsigned char) ( level * 255.0 ) ;
}
for ( i = trans_len; i < len - trans_len; ++i ) {
float level = ( sin( (double) i * SGD_2PI / (BYTES_PER_SECOND / freq) ) )
/ 2.0 + 0.5;
/* Convert to unsigned byte */
buf[ i ] = (unsigned char) ( level * 255.0 ) ;
}
j = trans_len;
for ( i = len - trans_len; i < len; ++i ) {
float level = ( sin( (double) i * SGD_2PI / (BYTES_PER_SECOND / freq) ) )
* ((double)j / trans_len) / 2.0 + 0.5;
--j;
/* Convert to unsigned byte */
buf[ i ] = (unsigned char) ( level * 255.0 ) ;
}
for ( i = len; i < total_len; ++i ) {
buf[ i ] = (unsigned char) ( 0.5 * 255.0 ) ;
}
}

View File

@@ -0,0 +1,67 @@
// soundgenerator.hxx -- simple sound generation
//
// Written by Curtis Olson, started March 2001.
//
// 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.
//
#ifndef _FGSOUNDGENERATOR_HXX
#define _FGSOUNDGENERATOR_HXX
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
class FGSoundGenerator {
public:
static const int BYTES_PER_SECOND = 22050;
// static const int BEAT_LENGTH = 240; // milleseconds (5 wpm)
static const int BEAT_LENGTH = 92; // milleseconds (13 wpm)
static const int TRANSITION_BYTES = BYTES_PER_SECOND/200; // aka (int)(0.005 * BYTES_PER_SECOND);
static const int COUNT_SIZE = BYTES_PER_SECOND * BEAT_LENGTH / 1000;
static const int DIT_SIZE = 2 * COUNT_SIZE; // 2 counts
static const int DAH_SIZE = 4 * COUNT_SIZE; // 4 counts
static const int SPACE_SIZE = 3 * COUNT_SIZE; // 3 counts
static const int LO_FREQUENCY = 1020; // AIM 1-1-7 (f) specified in Hz
static const int HI_FREQUENCY = 1350; // AIM 1-1-7 (f) specified in Hz
protected:
/**
* \relates FGMorse
* Make a tone of specified freq and total_len with trans_len ramp in
* and out and only the first len bytes with sound, the rest with
* silence.
* @param buf unsigned char pointer to sound buffer
* @param freq desired frequency of tone
* @param len length of tone within sound
* @param total_len total length of sound (anything more than len is padded
* with silence.
* @param trans_len length of ramp up and ramp down to avoid audio "pop"
*/
static void make_tone( unsigned char *buf, int freq,
int len, int total_len, int trans_len );
public:
virtual ~FGSoundGenerator();
};
#endif // _FGSOUNDGENERATOR_HXX

280
src/Sound/soundmanager.cxx Normal file
View File

@@ -0,0 +1,280 @@
// 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 <config.h>
#include <simgear/sound/soundmgr.hxx>
#include <simgear/structure/commands.hxx>
#include "VoiceSynthesizer.hxx"
#include "sample_queue.hxx"
#include "soundmanager.hxx"
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Viewer/view.hxx>
#include <stdio.h>
#include <vector>
#include <string>
#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 <std::string>devices = get_available_devices();
for (unsigned int i=0; i<devices.size(); i++) {
SGPropertyNode *p = fgGetNode("/sim/sound/devices/device", i, true);
p->setStringValue(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<std::string,VoiceSynthesizer*>::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<FGSoundManager> registrantFGSoundManager(
SGSubsystemMgr::SOUND,
{{"SGSoundMgr", SGSubsystemMgr::Dependency::HARD}});

View File

@@ -0,0 +1,90 @@
// soundmanager.hxx -- 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.
//
#ifndef __FG_SOUNDMGR_HXX
#define __FG_SOUNDMGR_HXX 1
#include <memory>
#include <map>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/sound/soundmgr.hxx>
class FGSampleQueue;
class SGSoundMgr;
class Listener;
class VoiceSynthesizer;
#ifdef ENABLE_AUDIO_SUPPORT
class FGSoundManager : public SGSoundMgr
{
public:
FGSoundManager();
virtual ~FGSoundManager();
// Subsystem API.
void init() override;
void reinit() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "sound"; }
void activate(bool State);
void update_device_list();
VoiceSynthesizer * getSynthesizer( const std::string & voice );
private:
bool stationaryView() const;
bool playAudioSampleCommand(const SGPropertyNode * arg, SGPropertyNode * root);
std::map<std::string,SGSharedPtr<FGSampleQueue>> _queue;
double _active_dt;
bool _is_initialized, _enabled;
SGPropertyNode_ptr _sound_working, _sound_enabled, _volume, _device_name;
SGPropertyNode_ptr _velocityNorthFPS, _velocityEastFPS, _velocityDownFPS;
SGPropertyNode_ptr _frozen;
std::unique_ptr<Listener> _listener;
std::map<std::string,VoiceSynthesizer*> _synthesizers;
};
#else
#include "Main/fg_props.hxx"
// provide a dummy sound class
class FGSoundManager : public SGSubsystem
{
public:
FGSoundManager() { fgSetBool("/sim/sound/working", false);}
~FGSoundManager() {}
// Subsystem API.
void update(double dt) {} override
// Subsystem identification.
static const char* staticSubsystemClassId() { return "sound"; }
};
#endif // ENABLE_AUDIO_SUPPORT
#endif // __FG_SOUNDMGR_HXX

334
src/Sound/voice.cxx Normal file
View File

@@ -0,0 +1,334 @@
// speech synthesis interface subsystem
//
// Written by Melchior FRANZ, started February 2006.
//
// Copyright (C) 2006 Melchior FRANZ - mfranz@aon.at
//
// 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.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <Main/globals.hxx>
#include <sstream>
#include <simgear/compiler.h>
#include <Main/fg_props.hxx>
#include "voice.hxx"
#include "flitevoice.hxx"
#define VOICE "/sim/sound/voices"
using std::string;
class FGFestivalVoice : public FGVoiceMgr::FGVoice {
public:
FGFestivalVoice(FGVoiceMgr *, const SGPropertyNode_ptr);
virtual ~FGFestivalVoice();
virtual void speak( const string & msg );
virtual void update(double);
void setVolume(double);
void setPitch(double);
void setSpeed(double);
private:
SGSocket *_sock;
double _volume;
double _pitch;
double _speed;
SGPropertyNode_ptr _volumeNode;
SGPropertyNode_ptr _pitchNode;
SGPropertyNode_ptr _speedNode;
};
/// MANAGER ///
FGVoiceMgr::FGVoiceMgr() :
#if defined(ENABLE_THREADS)
_thread(NULL),
#endif
_host(fgGetString(VOICE "/host", "localhost")),
_port(fgGetString(VOICE "/port", "1314")),
_enabled(fgGetBool(VOICE "/enabled", false)),
_pausedNode(fgGetNode("/sim/sound/working", true)),
_paused(false)
{
}
FGVoiceMgr::~FGVoiceMgr()
{
}
void FGVoiceMgr::init()
{
if (!_enabled)
return;
#if defined(ENABLE_THREADS)
_thread = new FGVoiceThread(this);
#endif
SGPropertyNode *base = fgGetNode(VOICE, true);
vector<SGPropertyNode_ptr> voices = base->getChildren("voice");
for (unsigned int i = 0; i < voices.size(); i++) {
SGPropertyNode_ptr voice = voices[i];
if( voice->getBoolValue("festival", false ) ) {
try {
_voices.push_back(new FGFestivalVoice(this, voice));
continue;
} catch (const std::string& ) {
SG_LOG(SG_SOUND, SG_WARN, "failed to create festival voice, falling back to flite voice" );
}
}
_voices.push_back(new FGFLITEVoice(this, voice));
}
#if defined(ENABLE_THREADS)
_thread->setProcessorAffinity(1);
_thread->start();
#endif
}
void FGVoiceMgr::shutdown()
{
#if defined(ENABLE_THREADS)
if( _thread ) {
_thread->cancel();
_thread->join();
delete _thread;
_thread = NULL;
}
#endif
for( std::vector<FGVoice*>::iterator it = _voices.begin(); it != _voices.end(); ++it )
delete *it;
}
void FGVoiceMgr::update(double dt)
{
if (!_enabled)
return;
_paused = !_pausedNode->getBoolValue();
for (unsigned int i = 0; i < _voices.size(); i++) {
_voices[i]->update(dt);
#if !defined(ENABLE_THREADS)
_voices[i]->speak();
#endif
}
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGVoiceMgr> registrantFGVoiceMgr(
SGSubsystemMgr::DISPLAY);
/// VOICE ///
FGFestivalVoice::FGFestivalVoice(FGVoiceMgr *mgr, const SGPropertyNode_ptr node) :
FGVoice(mgr),
_volumeNode(node->getNode("volume", true)),
_pitchNode(node->getNode("pitch", true)),
_speedNode(node->getNode("speed", true))
{
SG_LOG(SG_SOUND, SG_INFO, "VOICE: adding `" << node->getStringValue("desc", "<unnamed>")
<< "' voice");
const string &host = _mgr->_host;
const string &port = _mgr->_port;
_sock = new SGSocket(host, port, "tcp");
_sock->set_timeout(6000);
if (!_sock->open(SG_IO_OUT))
throw string("no connection to `") + host + ':' + port + '\'';
{
_sock->writestring("(+ 1 2)\015\012");
char buf[4];
int len = _sock->read(buf, 3);
if (len != 3 || buf[0] != 'L' || buf[1] != 'P')
throw string("unexpected or no response from `") + host + ':' + port
+ "'. Either it's not\n Festival listening,"
" or Festival couldn't open a sound device.";
SG_LOG(SG_SOUND, SG_INFO, "VOICE: connection to Festival server on `"
<< host << ':' << port << "' established");
}
const string preamble = node->getStringValue("preamble", "");
if (!preamble.empty()) {
pushMessage(preamble);
}
// set these after sending any preamble, since it may reset them
setVolume(_volume = _volumeNode->getDoubleValue());
setPitch(_pitch = _pitchNode->getDoubleValue());
setSpeed(_speed = _speedNode->getDoubleValue());
node->getNode("text", true)->addChangeListener(this);
}
FGFestivalVoice::~FGFestivalVoice()
{
_sock->close();
delete _sock;
}
void FGVoiceMgr::FGVoice::pushMessage( const string & m)
{
_msg.push(m);
#if defined(ENABLE_THREADS)
_mgr->_thread->wake_up();
#endif
}
bool FGVoiceMgr::FGVoice::speak(void)
{
if (_msg.empty())
return false;
const string s = _msg.front();
_msg.pop();
speak(s);
return !_msg.empty();
}
void FGFestivalVoice::speak( const string & msg )
{
if( msg.empty() )
return;
string s;
if( msg[0] == '(' ) {
s = msg;
} else {
s.append("(SayText \"");
s.append(msg).append("\")");
}
s.append("\015\012");
_sock->writestring(s.c_str());
}
void FGFestivalVoice::update(double dt)
{
double d;
d = _volumeNode->getDoubleValue();
if (d != _volume)
setVolume(_volume = d);
d = _pitchNode->getDoubleValue();
if (d != _pitch)
setPitch(_pitch = d);
d = _speedNode->getDoubleValue();
if (d != _speed)
setSpeed(_speed = d);
}
void FGFestivalVoice::setVolume(double d)
{
std::ostringstream s;
s << "(set! default_after_synth_hooks (list (lambda (utt)"
"(utt.wave.rescale utt " << d << " t))))";
pushMessage(s.str());
}
void FGFestivalVoice::setPitch(double d)
{
std::ostringstream s;
s << "(set! int_lr_params '((target_f0_mean " << d <<
")(target_f0_std 14)(model_f0_mean 170)"
"(model_f0_std 34)))";
pushMessage(s.str());
}
void FGFestivalVoice::setSpeed(double d)
{
std::ostringstream s;
s << "(Parameter.set 'Duration_Stretch " << d << ')';
pushMessage(s.str());
}
/// THREAD ///
#if defined(ENABLE_THREADS)
void FGVoiceMgr::FGVoiceThread::run(void)
{
while (1) {
bool busy = false;
for (unsigned int i = 0; i < _mgr->_voices.size(); i++)
busy |= _mgr->_voices[i]->speak();
if (!busy)
wait_for_jobs();
}
}
#endif
/// LISTENER ///
void FGVoiceMgr::FGVoice::valueChanged(SGPropertyNode *node)
{
if (_mgr->_paused)
return;
const string s = node->getStringValue();
//cerr << "\033[31;1mBEFORE [" << s << "]\033[m" << endl;
string m;
for (unsigned int i = 0; i < s.size(); i++) {
char c = s[i];
if (c == '\n' || c == '\r' || c == '\t')
m += ' ';
else if (!isprint(c))
continue;
else if (c == '\\' || c == '"')
m += '\\', m += c;
else if (c == '|' || c == '_')
m += ' '; // don't say "vertical bar" or "underscore"
else if (c == '&')
m += " and ";
else if (c == '>' || c == '<')
m += ' '; // don't say "greater than" or "less than" either
else if (c == '{') {
while (i < s.size())
if (s[++i] == '|')
break;
} else if (c == '}')
;
else
m += c;
}
//cerr << "\033[31;1mAFTER [" << m << "]\033[m" << endl;
pushMessage(m);
}

118
src/Sound/voice.hxx Normal file
View File

@@ -0,0 +1,118 @@
// speech synthesis interface subsystem
//
// Written by Melchior FRANZ, started February 2006.
//
// Copyright (C) 2006 Melchior FRANZ - mfranz@aon.at
//
// 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.
//
// $Id$
#pragma once
#include <vector>
#include <simgear/compiler.h>
#include <simgear/props/props.hxx>
#include <simgear/io/sg_socket.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <Main/fg_props.hxx>
#if defined(ENABLE_THREADS)
# include <OpenThreads/Thread>
# include <OpenThreads/Mutex>
# include <OpenThreads/ScopedLock>
# include <OpenThreads/Condition>
# include <simgear/threads/SGQueue.hxx>
#else
# include <queue>
#endif // ENABLE_THREADS
class FGVoiceMgr : public SGSubsystem
{
public:
FGVoiceMgr();
~FGVoiceMgr();
// Subsystem API.
void init() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "voice"; }
class FGVoice;
protected:
friend class FGFestivalVoice;
#if defined(ENABLE_THREADS)
class FGVoiceThread;
FGVoiceThread *_thread;
#endif
std::string _host;
std::string _port;
bool _enabled;
SGPropertyNode_ptr _pausedNode;
bool _paused;
std::vector<FGVoice *> _voices;
};
#if defined(ENABLE_THREADS)
class FGVoiceMgr::FGVoiceThread : public OpenThreads::Thread
{
public:
FGVoiceThread(FGVoiceMgr *mgr) : _mgr(mgr) {}
void run();
void wake_up() { _jobs.signal(); }
private:
void wait_for_jobs() { OpenThreads::ScopedLock<OpenThreads::Mutex> g(_mutex); _jobs.wait(&_mutex); }
OpenThreads::Condition _jobs;
OpenThreads::Mutex _mutex;
protected:
FGVoiceMgr *_mgr;
};
#endif
class FGVoiceMgr::FGVoice : public SGPropertyChangeListener
{
public:
FGVoice(FGVoiceMgr * mgr ) : _mgr(mgr) {}
virtual ~FGVoice() {}
virtual void speak( const std::string & msg ) = 0;
virtual void update(double dt) = 0;
void pushMessage( const std::string & m);
bool speak();
protected:
void valueChanged(SGPropertyNode *node);
FGVoiceMgr *_mgr;
#if defined(ENABLE_THREADS)
SGLockedQueue<std::string> _msg;
#else
std::queue<std::string> _msg;
#endif
};

348
src/Sound/voiceplayer.cxx Normal file
View File

@@ -0,0 +1,348 @@
// voiceplayer.cxx -- voice/sound sample player
//
// Written by Jean-Yves Lefort, started September 2005.
//
// Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
///////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma warning( disable: 4355 )
#endif
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "voiceplayer.hxx"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <cmath>
#include <string>
#include <sstream>
#include <simgear/debug/logstream.hxx>
#include <simgear/sound/soundmgr.hxx>
#include <simgear/sound/sample_group.hxx>
#include <simgear/structure/exception.hxx>
using std::string;
using std::map;
using std::vector;
///////////////////////////////////////////////////////////////////////////////
// constants //////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// helpers ////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#define ADD_VOICE(Var,Sample,Twice) \
{ make_voice(&Var);append(Var,Sample);\
if (Twice) append(Var,Sample); }
#define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
/////////////////////////////////////////////////////////////////////////
// FGVoicePlayer::Voice::SampleElement ///////////////////////////
/////////////////////////////////////////////////////////////////////////
FGVoicePlayer::Voice::SampleElement::SampleElement (SGSharedPtr<SGSoundSample> sample, float volume)
: _sample(sample), _volume(volume)
{
silence = false;
}
void FGVoicePlayer::Voice::SampleElement::play (float volume)
{
if (_sample && (volume > 0.05)) { set_volume(volume); _sample->play_once(); }
}
void FGVoicePlayer::Voice::SampleElement::stop ()
{
if (_sample) _sample->stop();
}
bool FGVoicePlayer::Voice::SampleElement::is_playing ()
{
return _sample ? _sample->is_playing() : false;
}
void FGVoicePlayer::Voice::SampleElement::set_volume (float volume)
{
if (_sample) _sample->set_volume(volume * _volume);
}
///////////////////////////////////////////////////////////////////////////////
// FGVoicePlayer //////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void
FGVoicePlayer::Speaker::bind (SGPropertyNode *node)
{
// uses xmlsound property names
tie(node, "volume", &volume);
tie(node, "pitch", &pitch);
}
void
FGVoicePlayer::Speaker::update_configuration ()
{
map< string, SGSharedPtr<SGSoundSample> >::iterator iter;
for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
{
SGSoundSample *sample = (*iter).second;
sample->set_pitch(pitch);
}
if (player->voice)
player->voice->volume_changed();
}
FGVoicePlayer::Voice::~Voice ()
{
for (iter = elements.begin(); iter != elements.end(); iter++)
delete *iter; // we owned the element
elements.clear();
}
void
FGVoicePlayer::Voice::play ()
{
iter = elements.begin();
element = *iter;
element->play(get_volume());
}
void
FGVoicePlayer::Voice::stop (bool now)
{
if (element)
{
if (now || element->silence)
{
element->stop();
element = NULL;
}
else
iter = elements.end() - 1; // stop after the current element finishes
}
}
void
FGVoicePlayer::Voice::set_volume (float _volume)
{
volume = _volume;
volume_changed();
}
void
FGVoicePlayer::Voice::volume_changed ()
{
if (element)
element->set_volume(get_volume());
}
void
FGVoicePlayer::Voice::update ()
{
if (element)
{
if (! element->is_playing())
{
if (++iter == elements.end())
element = NULL;
else
{
element = *iter;
element->play(get_volume());
}
}
}
}
FGVoicePlayer::FGVoicePlayer (PropertiesHandler* properties_handler, string _dev_name)
: volume(1.0), voice(NULL), next_voice(NULL), paused(false),
dev_name(_dev_name), dir_prefix(""),
speaker(this,properties_handler)
{
_sgr = NULL;
}
FGVoicePlayer::~FGVoicePlayer ()
{
vector<Voice *>::iterator iter1;
for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
delete *iter1;
_voices.clear();
samples.clear();
}
void
FGVoicePlayer::bind (SGPropertyNode *node, const char* default_dir_prefix)
{
dir_prefix = node->getStringValue("voice/file-prefix", default_dir_prefix);
speaker.bind(node);
}
void
FGVoicePlayer::init ()
{
SGSoundMgr *smgr = globals->get_subsystem<SGSoundMgr>();
_sgr = smgr->find("avionics", true);
_sgr->tie_to_listener();
speaker.update_configuration();
}
void
FGVoicePlayer::pause()
{
if (paused)
return;
paused = true;
if (voice)
{
voice->stop(true);
}
}
void
FGVoicePlayer::resume()
{
if (!paused)
return;
paused = false;
if (voice)
{
voice->play();
}
}
SGSoundSample *
FGVoicePlayer::get_sample (const char *name)
{
string refname;
refname = dev_name + "/" + dir_prefix + name;
SGSoundSample *sample = _sgr->find(refname);
if (! sample)
{
string filename = dir_prefix + string(name) + ".wav";
sample = new SGSoundSample(filename.c_str(), SGPath());
_sgr->add(sample, refname);
samples[refname] = sample;
}
return sample;
}
void
FGVoicePlayer::play (Voice *_voice, unsigned int flags)
{
if (!_voice)
return;
if (test_bits(flags, PLAY_NOW) || ! voice ||
(voice->element && voice->element->silence))
{
if (voice)
voice->stop(true);
voice = _voice;
looped = test_bits(flags, PLAY_LOOPED);
next_voice = NULL;
next_looped = false;
if (!paused)
voice->play();
}
else
{
next_voice = _voice;
next_looped = test_bits(flags, PLAY_LOOPED);
}
}
void
FGVoicePlayer::stop (unsigned int flags)
{
if (voice)
{
voice->stop(test_bits(flags, STOP_NOW));
if (voice->element)
looped = false;
else
voice = NULL;
next_voice = NULL;
}
}
void
FGVoicePlayer::set_volume (float _volume)
{
volume = _volume;
if (voice)
voice->volume_changed();
}
void
FGVoicePlayer::update ()
{
if (voice)
{
voice->update();
if (next_voice)
{
if (! voice->element || voice->element->silence)
{
voice = next_voice;
looped = next_looped;
next_voice = NULL;
next_looped = false;
voice->play();
}
}
else
{
if (! voice->element)
{
if (looped)
voice->play();
else
voice = NULL;
}
}
}
}
void
FGVoicePlayer::append (Voice *voice, const char *sample_name)
{
voice->append(new Voice::SampleElement(get_sample(sample_name)));
}

328
src/Sound/voiceplayer.hxx Normal file
View File

@@ -0,0 +1,328 @@
// voiceplayer.hxx -- voice/sound sample player
//
// Written by Jean-Yves Lefort, started September 2005.
//
// Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef __SOUND_VOICEPLAYER_HXX
#define __SOUND_VOICEPLAYER_HXX
#include <assert.h>
#include <vector>
#include <deque>
#include <map>
#include <simgear/props/props.hxx>
#include <simgear/props/tiedpropertylist.hxx>
class SGSampleGroup;
class SGSoundSample;
#include <Main/globals.hxx>
#ifdef _MSC_VER
# pragma warning( push )
# pragma warning( disable: 4355 )
#endif
/////////////////////////////////////////////////////////////////////////////
// FGVoicePlayer /////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
class FGVoicePlayer
{
public:
/////////////////////////////////////////////////////////////////////////////
// MK::RawValueMethodsData /////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
template <class C, class VT, class DT>
class RawValueMethodsData : public SGRawValue<VT>
{
public:
typedef VT (C::*getter_t) (DT) const;
typedef void (C::*setter_t) (DT, VT);
RawValueMethodsData (C &obj, DT data, getter_t getter = 0, setter_t setter = 0)
: _obj(obj), _data(data), _getter(getter), _setter(setter) {}
virtual VT getValue () const
{
if (_getter)
return (_obj.*_getter)(_data);
else
return SGRawValue<VT>::DefaultValue();
}
virtual bool setValue (VT value)
{
if (_setter)
{
(_obj.*_setter)(_data, value);
return true;
}
else
return false;
}
virtual SGRawValue<VT> *clone () const
{
return new RawValueMethodsData<C,VT,DT>(_obj, _data, _getter, _setter);
}
private:
C &_obj;
DT _data;
getter_t _getter;
setter_t _setter;
};
class PropertiesHandler : public simgear::TiedPropertyList
{
public:
template <class T>
inline void tie (SGPropertyNode *node, const SGRawValue<T> &raw_value)
{
Tie(node,raw_value);
}
template <class T>
inline void tie (SGPropertyNode *node,
const char *relative_path,
const SGRawValue<T> &raw_value)
{
Tie(node->getNode(relative_path, true),raw_value);
}
PropertiesHandler() {};
void unbind () {Untie();}
};
///////////////////////////////////////////////////////////////////////////
// FGVoicePlayer::Voice ////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
class Voice
{
public:
/////////////////////////////////////////////////////////////////////////
// FGVoicePlayer::Voice::Element ////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
class Element
{
public:
bool silence;
virtual ~Element() {}
virtual inline void play (float volume) {}
virtual inline void stop () {}
virtual bool is_playing () = 0;
virtual inline void set_volume (float volume) {}
};
/////////////////////////////////////////////////////////////////////////
// FGVoicePlayer::Voice::SampleElement ///////////////////////////
/////////////////////////////////////////////////////////////////////////
class SampleElement : public Element
{
SGSharedPtr<SGSoundSample> _sample;
float _volume;
public:
SampleElement (SGSharedPtr<SGSoundSample> sample, float volume = 1.0);
virtual void play (float volume);
virtual void stop ();
virtual bool is_playing ();
virtual void set_volume (float volume);
};
/////////////////////////////////////////////////////////////////////////
// FGVoicePlayer::Voice::SilenceElement //////////////////////////
/////////////////////////////////////////////////////////////////////////
class SilenceElement : public Element
{
double _duration;
double start_time;
public:
inline SilenceElement (double duration)
: _duration(duration) { silence = true; }
virtual inline void play (float volume) { start_time = globals->get_sim_time_sec(); }
virtual inline bool is_playing () { return globals->get_sim_time_sec() - start_time < _duration; }
};
/////////////////////////////////////////////////////////////////////////
// FGVoicePlayer::Voice (continued) //////////////////////////////
/////////////////////////////////////////////////////////////////////////
Element *element;
inline Voice (FGVoicePlayer *_player)
: element(NULL), player(_player), volume(1.0) {}
virtual ~Voice ();
inline void append (Element *_element) { elements.push_back(_element); }
void play ();
void stop (bool now);
void set_volume (float _volume);
void volume_changed ();
void update ();
private:
FGVoicePlayer *player;
float volume;
std::vector<Element *> elements;
std::vector<Element *>::iterator iter;
inline float get_volume () const { return player->volume * player->speaker.volume * volume; }
};
///////////////////////////////////////////////////////////////////////////
// FGVoicePlayer (continued) ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
struct
{
float volume;
} conf;
float volume;
Voice *voice;
Voice *next_voice;
bool paused;
std::string dev_name;
std::string dir_prefix;
FGVoicePlayer (PropertiesHandler* properties_handler, std::string _dev_name);
virtual ~FGVoicePlayer ();
void init ();
void pause();
void resume();
bool is_playing() { return (voice!=NULL);}
enum
{
PLAY_NOW = 1 << 0,
PLAY_LOOPED = 1 << 1
};
void play (Voice *_voice, unsigned int flags = 0);
enum
{
STOP_NOW = 1 << 0
};
void stop (unsigned int flags = 0);
void set_volume (float _volume);
void update ();
void bind (SGPropertyNode *node, const char* default_dir_prefix);
public:
///////////////////////////////////////////////////////////////////////////
// FGVoicePlayer::Speaker //////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
class Speaker
{
FGVoicePlayer *player;
PropertiesHandler* properties_handler;
double pitch;
template <class T>
inline void tie (SGPropertyNode *node, const char *name, T *ptr)
{
properties_handler->tie
(node, (std::string("speaker/") + name).c_str(),
RawValueMethodsData<FGVoicePlayer::Speaker,T,T*>
(*this, ptr,
&FGVoicePlayer::Speaker::get_property,
&FGVoicePlayer::Speaker::set_property));
}
public:
template <class T>
inline void set_property (T *ptr, T value) { *ptr = value; update_configuration(); }
template <class T>
inline T get_property (T *ptr) const { return *ptr; }
float volume;
inline Speaker (FGVoicePlayer *_player,PropertiesHandler* _properties_handler)
: player(_player),
properties_handler(_properties_handler),
pitch(1),
volume(1)
{
}
void bind (SGPropertyNode *node);
void update_configuration ();
};
protected:
///////////////////////////////////////////////////////////////////////////
// FGVoicePlayer (continued) ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
SGSharedPtr<SGSampleGroup> _sgr;
Speaker speaker;
std::map< std::string, SGSharedPtr<SGSoundSample> > samples;
std::vector<Voice *> _voices;
bool looped;
bool next_looped;
SGSoundSample *get_sample (const char *name);
inline void append (Voice *voice, Voice::Element *element) { voice->append(element); }
void append (Voice *voice, const char *sample_name);
inline void append (Voice *voice, double silence) { voice->append(new Voice::SilenceElement(silence)); }
inline void make_voice (Voice **voice) { *voice = new Voice(this); _voices.push_back(*voice); }
template <class T1>
inline void make_voice (Voice **voice, T1 e1) { make_voice(voice); append(*voice, e1); }
template <class T1, class T2>
inline void make_voice (Voice **voice, T1 e1, T2 e2) { make_voice(voice, e1); append(*voice, e2); }
template <class T1, class T2, class T3>
inline void make_voice (Voice **voice, T1 e1, T2 e2, T3 e3) { make_voice(voice, e1, e2); append(*voice, e3); }
template <class T1, class T2, class T3, class T4>
inline void make_voice (Voice **voice, T1 e1, T2 e2, T3 e3, T4 e4) { make_voice(voice, e1, e2, e3); append(*voice, e4); }
};
#endif // __SOUND_VOICEPLAYER_HXX