]> git.mxchange.org Git - flightgear.git/blob - src/ATCDCL/ATCVoice.cxx
46ab81df79f133ee861f76dccaa83ffd7e02e209
[flightgear.git] / src / ATCDCL / ATCVoice.cxx
1 // FGATCVoice.cxx - a class to encapsulate an ATC voice
2 //
3 // Written by David Luff, started November 2002.
4 //
5 // Copyright (C) 2002  David C Luff - david.luff@nottingham.ac.uk
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25
26 #include "ATCVoice.hxx"
27
28 #include <stdlib.h>
29 #include <ctype.h>
30 #include <fstream>
31 #include <list>
32 #include <vector>
33
34 #include <boost/shared_array.hpp>
35
36 #include <simgear/misc/sg_path.hxx>
37 #include <simgear/debug/logstream.hxx>
38 #include <simgear/misc/sgstream.hxx>
39 #include <simgear/math/sg_random.h>
40 #include <simgear/sound/sample_openal.hxx>
41
42 #include <Main/globals.hxx>
43
44 #include <stdio.h>
45
46 #ifdef _MSC_VER
47 #define strtok_r strtok_s
48 #endif
49
50 using namespace std;
51
52 FGATCVoice::FGATCVoice() {
53   SoundData = 0;
54   rawSoundData = 0;
55 }
56
57 FGATCVoice::~FGATCVoice() {
58     if (rawSoundData)
59         free( rawSoundData );
60     delete SoundData;
61 }
62
63 // Load the two voice files - one containing the raw sound data (.wav) and one containing the word positions (.vce).
64 // Return true if successful.
65 bool FGATCVoice::LoadVoice(const string& voice) {
66     // FIXME CLO: disabled to try to see if this is causing problemcs
67     // return false;
68
69         std::ifstream fin;
70
71         SGPath path = globals->get_fg_root();
72         path.append( "ATC" );
73
74         string file = voice + ".wav";
75         
76         SGSoundSample SoundData;
77         rawSoundData = (char *)SoundData.load_file(path.c_str(), file.c_str());
78         rawDataSize = SoundData.get_size();
79 #ifdef VOICE_TEST
80         ALenum fmt = SoundData.get_format();
81         cout << "ATCVoice:  format: " << fmt 
82                         << "  size: " << rawDataSize << endl;
83 #endif  
84         path = globals->get_fg_root();
85         string wordPath = "ATC/" + voice + ".vce";
86         path.append(wordPath);
87         
88         // Now load the word data
89         fin.open(path.c_str(), ios::in);
90         if(!fin) {
91                 SG_LOG(SG_ATC, SG_ALERT, "Unable to open input file " << path.c_str());
92                 return(false);
93         }
94         SG_LOG(SG_ATC, SG_INFO, "Opened word data file " << wordPath << " OK...");
95         char numwds[10];
96         char wrd[100];
97         string wrdstr;
98         char wrdOffsetStr[20];
99         char wrdLengthStr[20];
100         unsigned int wrdOffset;         // Offset into the raw sound data that the word sample begins
101         unsigned int wrdLength;         // Length of the word sample in bytes
102         WordData wd;
103         fin >> numwds;
104         unsigned int numwords = atoi(numwds);
105         //cout << numwords << '\n';
106         for(unsigned int i=0; i < numwords; ++i) {
107                 fin >> wrd;
108                 wrdstr = wrd;
109                 fin >> wrdOffsetStr;
110                 fin >> wrdLengthStr;
111                 wrdOffset = atoi(wrdOffsetStr);
112                 wrdLength = atoi(wrdLengthStr);
113                 wd.offset = wrdOffset;
114                 wd.length = wrdLength;
115                 wordMap[wrdstr] = wd;
116                 string ws2 = wrdstr;
117                 for(string::iterator p = ws2.begin(); p != ws2.end(); p++){
118                   *p = tolower(*p);
119                   if (*p == '-') *p = '_';
120                 }
121                 if (wrdstr != ws2) wordMap[ws2] = wd;
122
123                 //cout << wrd << "\t\t" << wrdOffset << "\t\t" << wrdLength << '\n';
124                 //cout << i << '\n';
125         }
126         
127         fin.close();
128         return(true);
129 }
130
131
132 typedef list < string > tokenList_type;
133 typedef tokenList_type::iterator tokenList_iterator;
134
135 // Given a desired message, return a string containing the
136 // sound-sample data
137 string FGATCVoice::WriteMessage(const char* message, bool& dataOK) {
138         
139         // What should we do here?
140         // First - parse the message into a list of tokens.
141         // Sort the tokens into those we understand and those we don't.
142         // Add all the raw lengths of the token sound data, allocate enough space, and fill it with the rqd data.
143         tokenList_type tokenList;
144         tokenList_iterator tokenListItr;
145
146         // TODO - at the moment we're effectively taking 3 passes through the data.
147         // There is no need for this - 2 should be sufficient - we can probably ditch the tokenList.
148         size_t n1 = 1+strlen(message);
149         boost::shared_array<char> msg(new char[n1]);
150         strncpy(msg.get(), message, n1); // strtok requires a non-const char*
151         char* token;
152         int numWords = 0;
153         const char delimiters[] = " \t.,;:\"\n";
154         char* context;
155         token = strtok_r(msg.get(), delimiters, &context);
156         while(token != NULL) {
157                 for (char *t = token; *t; t++) {
158                   *t = tolower(*t);     // canonicalize the case, to
159                   if (*t == '-') *t = '_';   // match what's in the index
160                 }
161                 tokenList.push_back(token);
162                 ++numWords;
163                 SG_LOG(SG_ATC, SG_DEBUG, "voice synth: token: '"
164                     << token << "'");
165                 token = strtok_r(NULL, delimiters, &context);
166         }
167
168         vector<WordData> wdptr;
169         wdptr.reserve(numWords);
170         unsigned int cumLength = 0;
171
172         tokenListItr = tokenList.begin();
173         while(tokenListItr != tokenList.end()) {
174                 if(wordMap.find(*tokenListItr) == wordMap.end()) {
175                 // Oh dear - the token isn't in the sound file
176                   SG_LOG(SG_ATC, SG_ALERT, "voice synth: word '"
177                       << *tokenListItr << "' not found");
178                 } else {
179                     wdptr.push_back(wordMap[*tokenListItr]);
180                     cumLength += wdptr.back().length;
181                 }
182                 ++tokenListItr;
183         }
184         const size_t word = wdptr.size();
185         
186         // Check for no tokens found else slScheduler can be crashed
187         if(!word) {
188                 dataOK = false;
189                 return "";
190         }
191         boost::shared_array<char> tmpbuf(new char[cumLength]);
192         unsigned int bufpos = 0;
193         for(int i=0; i<word; ++i) {
194                 /*
195                 *  Sanity check for corrupt/mismatched sound data input - avoids a seg fault
196                 *  (As long as the calling function checks the return value!!)
197                 *  This check should be left in even when the default Flightgear files are known
198                 *  to be OK since it checks for mis-indexing of voice files by 3rd party developers.
199                 */
200                 if((wdptr[i].offset + wdptr[i].length) > rawDataSize) {
201                         SG_LOG(SG_ATC, SG_ALERT, "ERROR - mismatch between ATC .wav and .vce file in ATCVoice.cxx\n");
202                         SG_LOG(SG_ATC, SG_ALERT, "Offset + length: " << wdptr[i].offset + wdptr[i].length
203                              << " exceeds rawdata size: " << rawDataSize << endl);
204
205                         dataOK = false;
206                         return "";
207                 }
208                 memcpy(tmpbuf.get() + bufpos, rawSoundData + wdptr[i].offset, wdptr[i].length);
209                 bufpos += wdptr[i].length;
210         }
211         
212         // tmpbuf now contains the message starting at the beginning - but we want it to start at a random position.
213         unsigned int offsetIn = (int)(cumLength * sg_random());
214         if(offsetIn > cumLength) offsetIn = cumLength;
215
216         string front(tmpbuf.get(), offsetIn);
217         string back(tmpbuf.get() + offsetIn, cumLength - offsetIn);
218
219         dataOK = true;  
220         return back + front;
221 }