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),
_voice(true),
_playing(false),
_vPtr(NULL),
_sgr(NULL),
_type(INVALID),
_display(false)
@ -167,10 +166,9 @@ void FGATC::Render(std::string& msg, const float volume,
_currentMsg = msg;
size_t len;
void* buf = NULL;
if (!_vPtr)
_vPtr = GetVoicePointer();
if (_vPtr)
buf = _vPtr->WriteMessage((char*)msg.c_str(), &len);
FGATCVoice* vPtr = GetVoicePointer();
if (vPtr)
buf = vPtr->WriteMessage((char*)msg.c_str(), &len);
NoRender(refname);
if(buf) {
try {

View file

@ -155,7 +155,7 @@ protected:
// 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.
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.
// Requires the sound manager refname if audio, else "".
@ -176,7 +176,6 @@ protected:
// Rendering related stuff
bool _voice; // Flag - true if we are using voice
bool _playing; // Indicates a message in progress
FGATCVoice* _vPtr;
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.
//

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

View file

@ -26,6 +26,7 @@
#include "ATCVoice.hxx"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fstream>
#include <vector>
@ -33,6 +34,7 @@
#include <simgear/sound/soundmgr_openal.hxx>
#include <simgear/sound/sample_openal.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/debug/logstream.hxx>
@ -43,49 +45,124 @@
using namespace std;
FGATCVoice::FGATCVoice() {
SoundData = 0;
rawSoundData = 0;
FGATCVoice::FGATCVoice() :
rawSoundData(0),
rawDataSize(0),
SoundData(0)
{
}
FGATCVoice::~FGATCVoice() {
if (rawSoundData)
free( rawSoundData );
free( rawSoundData );
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.
bool FGATCVoice::LoadVoice(const string& voice) {
std::ifstream fin;
bool FGATCVoice::LoadVoice(const string& voicename)
{
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
cout << "ATCVoice: format: " << format
<< " size: " << rawDataSize << endl;
#endif
path = globals->get_fg_root();
string wordPath = "ATC/" + voice + ".vce";
path.append(wordPath);
#endif
// load and parse index file (.vce)
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
std::ifstream fin;
fin.open(path.c_str(), ios::in);
if(!fin) {
SG_LOG(SG_ATC, SG_ALERT, "Unable to open input file " << path.c_str());
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 wrd[100];
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 wrdLength; // Length of the word sample in bytes
WordData wd;
// first entry: number of words in the index
fin >> numwds;
unsigned int numwords = atoi(numwds);
//cout << numwords << '\n';
// now load each word, its file offset and length
for(unsigned int i=0; i < numwords; ++i) {
// read data
fin >> wrd;
wrdstr = wrd;
fin >> wrdOffsetStr;
fin >> wrdLengthStr;
wrdstr = wrd;
wrdOffset = atoi(wrdOffsetStr);
wrdLength = atoi(wrdLengthStr);
wd.offset = wrdOffset;
// store word in map
wd.offset = wrdOffset + globaloffset;
wd.length = wrdLength;
wordMap[wrdstr] = wd;
string ws2 = wrdstr;
for(string::iterator p = ws2.begin(); p != ws2.end(); p++){
*p = tolower(*p);
if (*p == '-') *p = '_';
}
if (wrdstr != ws2) wordMap[ws2] = wd;
// post-process words
string ws2 = wrdstr;
for(string::iterator p = ws2.begin(); p != ws2.end(); p++){
*p = tolower(*p);
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 << i << '\n';
}
fin.close();
return(true);
}

View file

@ -28,6 +28,7 @@
#include <string>
class SGSoundSample;
class SGPath;
struct WordData {
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).
// 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.
// Sets len to something other than 0 if the returned buffer is valid.
void* WriteMessage(const std::string& message, size_t *len);
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
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)
{
@ -108,10 +121,11 @@ FGATCVoice* FGATISMgr::GetVoicePointer(const atc_type& type)
*/
if (!voice && fgGetBool("/sim/sound/working")) {
voice = new FGATCVoice;
voiceName = fgGetString("/sim/atis/voice", "default");
try {
useVoice = voice->LoadVoice("default");
useVoice = voice->LoadVoice(voiceName);
} 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());
useVoice = false;
delete voice;

View file

@ -40,6 +40,7 @@ private:
#ifdef ENABLE_AUDIO_SUPPORT
bool useVoice; // Flag - true if we are using voice
FGATCVoice* voice;
std::string voiceName; // currently loaded voice name
#endif
public:
@ -47,6 +48,7 @@ public:
~FGATISMgr();
void init();
void reinit();
void update(double dt);
// Return a pointer to an appropriate voice for a given type of ATC