1
0
Fork 0

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:
ThorstenB 2012-10-13 14:37:47 +02:00
parent 9bb9a78249
commit 395c317627
8 changed files with 152 additions and 45 deletions

View file

@ -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 {

View file

@ -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;

View file

@ -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.
// //

View file

@ -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.
// //

View file

@ -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,9 +45,11 @@
using namespace std; using namespace std;
FGATCVoice::FGATCVoice() { FGATCVoice::FGATCVoice() :
SoundData = 0; rawSoundData(0),
rawSoundData = 0; rawDataSize(0),
SoundData(0)
{
} }
FGATCVoice::~FGATCVoice() { FGATCVoice::~FGATCVoice() {
@ -54,38 +58,111 @@ FGATCVoice::~FGATCVoice() {
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;
SGPath path = globals->get_fg_root(); // determine voice directory
string file = voice + ".wav"; SGPath voicepath = globals->get_fg_root();
path.append( "ATC" ); voicepath.append( "ATC" );
path.append( file ); voicepath.append( "voices" );
voicepath.append( voicename );
string full_path = path.str(); simgear::Dir d(voicepath);
int format, freq; if (!d.exists())
SGSoundMgr *smgr = globals->get_soundmgr(); {
void *data; SG_LOG(SG_ATC, SG_ALERT, "Unable to load ATIS voice. No such directory: " << voicepath.str());
if (!smgr->load(full_path, &data, &format, &rawDataSize, &freq))
return false; 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; 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;
#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,25 +171,39 @@ 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;
// post-process words
string ws2 = wrdstr; string ws2 = wrdstr;
for(string::iterator p = ws2.begin(); p != ws2.end(); p++){ for(string::iterator p = ws2.begin(); p != ws2.end(); p++){
*p = tolower(*p); *p = tolower(*p);
if (*p == '-') *p = '_'; if (*p == '-')
*p = '_';
} }
if (wrdstr != ws2) wordMap[ws2] = wd;
// 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';

View file

@ -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;

View file

@ -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;

View file

@ -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