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),
|
||||
_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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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;
|
||||
|
||||
SGPath path = globals->get_fg_root();
|
||||
string file = voice + ".wav";
|
||||
path.append( "ATC" );
|
||||
path.append( file );
|
||||
// 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;
|
||||
|
||||
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);
|
||||
|
||||
// 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,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 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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue