first commit
This commit is contained in:
33
src/Sound/CMakeLists.txt
Normal file
33
src/Sound/CMakeLists.txt
Normal 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)
|
||||
135
src/Sound/VoiceSynthesizer.cxx
Normal file
135
src/Sound/VoiceSynthesizer.cxx
Normal 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);
|
||||
}
|
||||
|
||||
111
src/Sound/VoiceSynthesizer.hxx
Normal file
111
src/Sound/VoiceSynthesizer.hxx
Normal 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
166
src/Sound/audioident.cxx
Normal 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
70
src/Sound/audioident.hxx
Normal 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
163
src/Sound/beacon.cxx
Normal 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
123
src/Sound/beacon.hxx
Normal 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
237
src/Sound/fg_fx.cxx
Normal 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
86
src/Sound/fg_fx.hxx
Normal 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
88
src/Sound/flitevoice.cxx
Normal 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
51
src/Sound/flitevoice.hxx
Normal 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
256
src/Sound/morse.cxx
Normal 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
125
src/Sound/morse.hxx
Normal 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
102
src/Sound/sample_queue.cxx
Normal 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
|
||||
71
src/Sound/sample_queue.hxx
Normal file
71
src/Sound/sample_queue.hxx
Normal 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
|
||||
64
src/Sound/soundgenerator.cxx
Normal file
64
src/Sound/soundgenerator.cxx
Normal 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 ) ;
|
||||
}
|
||||
}
|
||||
67
src/Sound/soundgenerator.hxx
Normal file
67
src/Sound/soundgenerator.hxx
Normal 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
280
src/Sound/soundmanager.cxx
Normal 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}});
|
||||
90
src/Sound/soundmanager.hxx
Normal file
90
src/Sound/soundmanager.hxx
Normal 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
334
src/Sound/voice.cxx
Normal 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
118
src/Sound/voice.hxx
Normal 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
348
src/Sound/voiceplayer.cxx
Normal 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
328
src/Sound/voiceplayer.hxx
Normal 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
|
||||
Reference in New Issue
Block a user