ATCVoice: support multiple voice files
so we can split ATIS voice files into separate files, i.e. for airport names and phraseology, so we don't need to regenerate airport names when extending/changing phraseology. Also allows to add custom airport names. Enable switching voice files at run-time (different airports could have different voices...).
This commit is contained in:
parent
9bb9a78249
commit
395c317627
8 changed files with 152 additions and 45 deletions
|
@ -41,7 +41,6 @@ FGATC::FGATC() :
|
||||||
range(0),
|
range(0),
|
||||||
_voice(true),
|
_voice(true),
|
||||||
_playing(false),
|
_playing(false),
|
||||||
_vPtr(NULL),
|
|
||||||
_sgr(NULL),
|
_sgr(NULL),
|
||||||
_type(INVALID),
|
_type(INVALID),
|
||||||
_display(false)
|
_display(false)
|
||||||
|
@ -167,10 +166,9 @@ void FGATC::Render(std::string& msg, const float volume,
|
||||||
_currentMsg = msg;
|
_currentMsg = msg;
|
||||||
size_t len;
|
size_t len;
|
||||||
void* buf = NULL;
|
void* buf = NULL;
|
||||||
if (!_vPtr)
|
FGATCVoice* vPtr = GetVoicePointer();
|
||||||
_vPtr = GetVoicePointer();
|
if (vPtr)
|
||||||
if (_vPtr)
|
buf = vPtr->WriteMessage((char*)msg.c_str(), &len);
|
||||||
buf = _vPtr->WriteMessage((char*)msg.c_str(), &len);
|
|
||||||
NoRender(refname);
|
NoRender(refname);
|
||||||
if(buf) {
|
if(buf) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -155,7 +155,7 @@ protected:
|
||||||
// The refname is a string to identify this sample to the sound manager
|
// The refname is a string to identify this sample to the sound manager
|
||||||
// The repeating flag indicates whether the message should be repeated continuously or played once.
|
// The repeating flag indicates whether the message should be repeated continuously or played once.
|
||||||
void Render(std::string& msg, const float volume = 1.0,
|
void Render(std::string& msg, const float volume = 1.0,
|
||||||
const std::string& refname = "", bool repeating = false);
|
const std::string& refname = "", bool repeating = false);
|
||||||
|
|
||||||
// Cease rendering all transmission from this station.
|
// Cease rendering all transmission from this station.
|
||||||
// Requires the sound manager refname if audio, else "".
|
// Requires the sound manager refname if audio, else "".
|
||||||
|
@ -176,7 +176,6 @@ protected:
|
||||||
// Rendering related stuff
|
// Rendering related stuff
|
||||||
bool _voice; // Flag - true if we are using voice
|
bool _voice; // Flag - true if we are using voice
|
||||||
bool _playing; // Indicates a message in progress
|
bool _playing; // Indicates a message in progress
|
||||||
FGATCVoice* _vPtr;
|
|
||||||
|
|
||||||
SGSharedPtr<SGSampleGroup> _sgr; // default sample group;
|
SGSharedPtr<SGSampleGroup> _sgr; // default sample group;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// ATCProjection.cxx - A convienience projection class for the ATC/AI system.
|
// ATCProjection.cxx - A convenience projection class for the ATC/AI system.
|
||||||
//
|
//
|
||||||
// Written by David Luff, started 2002.
|
// Written by David Luff, started 2002.
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// ATCProjection.hxx - A convienience projection class for the ATC/AI system.
|
// ATCProjection.hxx - A convenience projection class for the ATC/AI system.
|
||||||
//
|
//
|
||||||
// Written by David Luff, started 2002.
|
// Written by David Luff, started 2002.
|
||||||
//
|
//
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "ATCVoice.hxx"
|
#include "ATCVoice.hxx"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
|
|
||||||
#include <simgear/sound/soundmgr_openal.hxx>
|
#include <simgear/sound/soundmgr_openal.hxx>
|
||||||
#include <simgear/sound/sample_openal.hxx>
|
#include <simgear/sound/sample_openal.hxx>
|
||||||
|
#include <simgear/misc/sg_dir.hxx>
|
||||||
|
|
||||||
#include <simgear/misc/sg_path.hxx>
|
#include <simgear/misc/sg_path.hxx>
|
||||||
#include <simgear/debug/logstream.hxx>
|
#include <simgear/debug/logstream.hxx>
|
||||||
|
@ -43,49 +45,124 @@
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
FGATCVoice::FGATCVoice() {
|
FGATCVoice::FGATCVoice() :
|
||||||
SoundData = 0;
|
rawSoundData(0),
|
||||||
rawSoundData = 0;
|
rawDataSize(0),
|
||||||
|
SoundData(0)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
FGATCVoice::~FGATCVoice() {
|
FGATCVoice::~FGATCVoice() {
|
||||||
if (rawSoundData)
|
if (rawSoundData)
|
||||||
free( rawSoundData );
|
free( rawSoundData );
|
||||||
delete SoundData;
|
delete SoundData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the two voice files - one containing the raw sound data (.wav) and one containing the word positions (.vce).
|
// Load all data for the requested voice.
|
||||||
// Return true if successful.
|
// Return true if successful.
|
||||||
bool FGATCVoice::LoadVoice(const string& voice) {
|
bool FGATCVoice::LoadVoice(const string& voicename)
|
||||||
std::ifstream fin;
|
{
|
||||||
|
rawDataSize = 0;
|
||||||
|
if (rawSoundData)
|
||||||
|
free(rawSoundData);
|
||||||
|
rawSoundData = NULL;
|
||||||
|
|
||||||
|
// determine voice directory
|
||||||
|
SGPath voicepath = globals->get_fg_root();
|
||||||
|
voicepath.append( "ATC" );
|
||||||
|
voicepath.append( "voices" );
|
||||||
|
voicepath.append( voicename );
|
||||||
|
|
||||||
|
simgear::Dir d(voicepath);
|
||||||
|
if (!d.exists())
|
||||||
|
{
|
||||||
|
SG_LOG(SG_ATC, SG_ALERT, "Unable to load ATIS voice. No such directory: " << voicepath.str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load all files from the voice's directory
|
||||||
|
simgear::PathList paths = d.children(simgear::Dir::TYPE_FILE);
|
||||||
|
bool Ok = false;
|
||||||
|
for (unsigned int i=0; i<paths.size(); ++i)
|
||||||
|
{
|
||||||
|
if (paths[i].lower_extension() == "vce")
|
||||||
|
Ok |= AppendVoiceFile(voicepath, paths[i].file_base());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Ok)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_ATC, SG_ALERT, "Unable to load ATIS voice. Files are invalid or no files in directory: " << voicepath.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok when at least some files loaded fine
|
||||||
|
return Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load a voice file and append it to the current word database
|
||||||
|
bool FGATCVoice::AppendVoiceFile(const SGPath& basepath, const string& file)
|
||||||
|
{
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
|
SG_LOG(SG_ATC, SG_INFO, "Loading ATIS voice file: " << file);
|
||||||
|
|
||||||
|
// path to compressed voice file
|
||||||
|
SGPath path(basepath);
|
||||||
|
path.append(file + ".wav.gz");
|
||||||
|
|
||||||
|
// load wave data
|
||||||
|
SGSoundMgr *smgr = globals->get_soundmgr();
|
||||||
|
int format, freq;
|
||||||
|
void *data;
|
||||||
|
size_t size;
|
||||||
|
if (!smgr->load(path.str(), &data, &format, &size, &freq))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// append to existing data
|
||||||
|
if (!rawSoundData)
|
||||||
|
rawSoundData = (char*)data;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rawSoundData = (char*) realloc(rawSoundData, rawDataSize + size);
|
||||||
|
// new data starts behind existing sound data
|
||||||
|
offset = rawDataSize;
|
||||||
|
if (!rawSoundData)
|
||||||
|
{
|
||||||
|
SG_LOG(SG_ATC, SG_ALERT, "Out of memory. Cannot load file " << path.str());
|
||||||
|
rawDataSize = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// append to existing sound data
|
||||||
|
memcpy(rawSoundData+offset, data, size);
|
||||||
|
free(data);
|
||||||
|
data = NULL;
|
||||||
|
}
|
||||||
|
rawDataSize += size;
|
||||||
|
|
||||||
SGPath path = globals->get_fg_root();
|
|
||||||
string file = voice + ".wav";
|
|
||||||
path.append( "ATC" );
|
|
||||||
path.append( file );
|
|
||||||
|
|
||||||
string full_path = path.str();
|
|
||||||
int format, freq;
|
|
||||||
SGSoundMgr *smgr = globals->get_soundmgr();
|
|
||||||
void *data;
|
|
||||||
if (!smgr->load(full_path, &data, &format, &rawDataSize, &freq))
|
|
||||||
return false;
|
|
||||||
rawSoundData = (char*)data;
|
|
||||||
#ifdef VOICE_TEST
|
#ifdef VOICE_TEST
|
||||||
cout << "ATCVoice: format: " << format
|
cout << "ATCVoice: format: " << format
|
||||||
<< " size: " << rawDataSize << endl;
|
<< " size: " << rawDataSize << endl;
|
||||||
#endif
|
#endif
|
||||||
path = globals->get_fg_root();
|
|
||||||
string wordPath = "ATC/" + voice + ".vce";
|
// load and parse index file (.vce)
|
||||||
path.append(wordPath);
|
return ParseVoiceIndex(basepath, file, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load and parse a voice index file (.vce)
|
||||||
|
bool FGATCVoice::ParseVoiceIndex(const SGPath& basepath, const string& file, size_t globaloffset)
|
||||||
|
{
|
||||||
|
// path to voice index file
|
||||||
|
SGPath path(basepath);
|
||||||
|
path.append(file + ".vce");
|
||||||
|
|
||||||
// Now load the word data
|
// Now load the word data
|
||||||
|
std::ifstream fin;
|
||||||
fin.open(path.c_str(), ios::in);
|
fin.open(path.c_str(), ios::in);
|
||||||
if(!fin) {
|
if(!fin) {
|
||||||
SG_LOG(SG_ATC, SG_ALERT, "Unable to open input file " << path.c_str());
|
SG_LOG(SG_ATC, SG_ALERT, "Unable to open input file " << path.c_str());
|
||||||
return(false);
|
return(false);
|
||||||
}
|
}
|
||||||
SG_LOG(SG_ATC, SG_INFO, "Opened word data file " << wordPath << " OK...");
|
SG_LOG(SG_ATC, SG_INFO, "Opened word data file " << path.c_str() << " OK...");
|
||||||
|
|
||||||
char numwds[10];
|
char numwds[10];
|
||||||
char wrd[100];
|
char wrd[100];
|
||||||
string wrdstr;
|
string wrdstr;
|
||||||
|
@ -94,30 +171,44 @@ bool FGATCVoice::LoadVoice(const string& voice) {
|
||||||
unsigned int wrdOffset; // Offset into the raw sound data that the word sample begins
|
unsigned int wrdOffset; // Offset into the raw sound data that the word sample begins
|
||||||
unsigned int wrdLength; // Length of the word sample in bytes
|
unsigned int wrdLength; // Length of the word sample in bytes
|
||||||
WordData wd;
|
WordData wd;
|
||||||
|
|
||||||
|
// first entry: number of words in the index
|
||||||
fin >> numwds;
|
fin >> numwds;
|
||||||
unsigned int numwords = atoi(numwds);
|
unsigned int numwords = atoi(numwds);
|
||||||
//cout << numwords << '\n';
|
//cout << numwords << '\n';
|
||||||
|
|
||||||
|
// now load each word, its file offset and length
|
||||||
for(unsigned int i=0; i < numwords; ++i) {
|
for(unsigned int i=0; i < numwords; ++i) {
|
||||||
|
// read data
|
||||||
fin >> wrd;
|
fin >> wrd;
|
||||||
wrdstr = wrd;
|
|
||||||
fin >> wrdOffsetStr;
|
fin >> wrdOffsetStr;
|
||||||
fin >> wrdLengthStr;
|
fin >> wrdLengthStr;
|
||||||
|
|
||||||
|
wrdstr = wrd;
|
||||||
wrdOffset = atoi(wrdOffsetStr);
|
wrdOffset = atoi(wrdOffsetStr);
|
||||||
wrdLength = atoi(wrdLengthStr);
|
wrdLength = atoi(wrdLengthStr);
|
||||||
wd.offset = wrdOffset;
|
|
||||||
|
// store word in map
|
||||||
|
wd.offset = wrdOffset + globaloffset;
|
||||||
wd.length = wrdLength;
|
wd.length = wrdLength;
|
||||||
wordMap[wrdstr] = wd;
|
wordMap[wrdstr] = wd;
|
||||||
string ws2 = wrdstr;
|
|
||||||
for(string::iterator p = ws2.begin(); p != ws2.end(); p++){
|
// post-process words
|
||||||
*p = tolower(*p);
|
string ws2 = wrdstr;
|
||||||
if (*p == '-') *p = '_';
|
for(string::iterator p = ws2.begin(); p != ws2.end(); p++){
|
||||||
}
|
*p = tolower(*p);
|
||||||
if (wrdstr != ws2) wordMap[ws2] = wd;
|
if (*p == '-')
|
||||||
|
*p = '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
// store alternative version of word (lowercase/no hyphen)
|
||||||
|
if (wrdstr != ws2)
|
||||||
|
wordMap[ws2] = wd;
|
||||||
|
|
||||||
//cout << wrd << "\t\t" << wrdOffset << "\t\t" << wrdLength << '\n';
|
//cout << wrd << "\t\t" << wrdOffset << "\t\t" << wrdLength << '\n';
|
||||||
//cout << i << '\n';
|
//cout << i << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
fin.close();
|
fin.close();
|
||||||
return(true);
|
return(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class SGSoundSample;
|
class SGSoundSample;
|
||||||
|
class SGPath;
|
||||||
|
|
||||||
struct WordData {
|
struct WordData {
|
||||||
unsigned int offset; // Offset of beginning of word sample into raw sound sample
|
unsigned int offset; // Offset of beginning of word sample into raw sound sample
|
||||||
|
@ -47,13 +48,15 @@ public:
|
||||||
|
|
||||||
// Load the two voice files - one containing the raw sound data (.wav) and one containing the word positions (.vce).
|
// Load the two voice files - one containing the raw sound data (.wav) and one containing the word positions (.vce).
|
||||||
// Return true if successful.
|
// Return true if successful.
|
||||||
bool LoadVoice(const std::string& voice);
|
bool LoadVoice(const std::string& voicename);
|
||||||
|
|
||||||
// Given a desired message, return a pointer to the data buffer and write the buffer length into len.
|
// Given a desired message, return a pointer to the data buffer and write the buffer length into len.
|
||||||
// Sets len to something other than 0 if the returned buffer is valid.
|
// Sets len to something other than 0 if the returned buffer is valid.
|
||||||
void* WriteMessage(const std::string& message, size_t *len);
|
void* WriteMessage(const std::string& message, size_t *len);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool AppendVoiceFile(const SGPath& basepath, const std::string& file);
|
||||||
|
bool ParseVoiceIndex(const SGPath& basepath, const std::string& file, size_t globaloffset);
|
||||||
|
|
||||||
// the sound and word position data
|
// the sound and word position data
|
||||||
char* rawSoundData;
|
char* rawSoundData;
|
||||||
|
|
|
@ -69,6 +69,19 @@ void FGATISMgr::init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FGATISMgr::reinit()
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_AUDIO_SUPPORT
|
||||||
|
if ((voiceName != "")&&
|
||||||
|
(voiceName != fgGetString("/sim/atis/voice", "default")))
|
||||||
|
{
|
||||||
|
voiceName = fgGetString("/sim/atis/voice", "default");
|
||||||
|
delete voice;
|
||||||
|
voice = NULL;
|
||||||
|
useVoice = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void FGATISMgr::update(double dt)
|
void FGATISMgr::update(double dt)
|
||||||
{
|
{
|
||||||
|
@ -108,10 +121,11 @@ FGATCVoice* FGATISMgr::GetVoicePointer(const atc_type& type)
|
||||||
*/
|
*/
|
||||||
if (!voice && fgGetBool("/sim/sound/working")) {
|
if (!voice && fgGetBool("/sim/sound/working")) {
|
||||||
voice = new FGATCVoice;
|
voice = new FGATCVoice;
|
||||||
|
voiceName = fgGetString("/sim/atis/voice", "default");
|
||||||
try {
|
try {
|
||||||
useVoice = voice->LoadVoice("default");
|
useVoice = voice->LoadVoice(voiceName);
|
||||||
} catch ( sg_io_exception & e) {
|
} catch ( sg_io_exception & e) {
|
||||||
SG_LOG(SG_ATC, SG_ALERT, "Unable to load default voice : "
|
SG_LOG(SG_ATC, SG_ALERT, "Unable to load voice '" << voiceName << "': "
|
||||||
<< e.getFormattedMessage().c_str());
|
<< e.getFormattedMessage().c_str());
|
||||||
useVoice = false;
|
useVoice = false;
|
||||||
delete voice;
|
delete voice;
|
||||||
|
|
|
@ -40,6 +40,7 @@ private:
|
||||||
#ifdef ENABLE_AUDIO_SUPPORT
|
#ifdef ENABLE_AUDIO_SUPPORT
|
||||||
bool useVoice; // Flag - true if we are using voice
|
bool useVoice; // Flag - true if we are using voice
|
||||||
FGATCVoice* voice;
|
FGATCVoice* voice;
|
||||||
|
std::string voiceName; // currently loaded voice name
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -47,6 +48,7 @@ public:
|
||||||
~FGATISMgr();
|
~FGATISMgr();
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
void reinit();
|
||||||
void update(double dt);
|
void update(double dt);
|
||||||
|
|
||||||
// Return a pointer to an appropriate voice for a given type of ATC
|
// Return a pointer to an appropriate voice for a given type of ATC
|
||||||
|
|
Loading…
Reference in a new issue