eSpeak NG is an open source speech synthesizer that supports more than hundred languages and accents.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

prosodydisplay.cpp 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. /***************************************************************************
  2. * Copyright (C) 2005 to 2014 by Jonathan Duddington *
  3. * email: [email protected] *
  4. * Copyright (C) 2015 by Reece H. Dunn *
  5. * *
  6. * This program is free software; you can redistribute it and/or modify *
  7. * it under the terms of the GNU General Public License as published by *
  8. * the Free Software Foundation; either version 3 of the License, or *
  9. * (at your option) any later version. *
  10. * *
  11. * This program is distributed in the hope that it will be useful, *
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  14. * GNU General Public License for more details. *
  15. * *
  16. * You should have received a copy of the GNU General Public License *
  17. * along with this program; if not, write see: *
  18. * <http://www.gnu.org/licenses/>. *
  19. ***************************************************************************/
  20. #include "wx/wx.h"
  21. #include "wx/numdlg.h"
  22. #include "speak_lib.h"
  23. #include "main.h"
  24. #include "speech.h"
  25. #include "phoneme.h"
  26. #include "synthesize.h"
  27. #include "prosodydisplay.h"
  28. #include "translate.h"
  29. extern MyFrame *myframe;
  30. extern ProsodyDisplay *prosodycanvas;
  31. wxMenu *menu_prosody;
  32. BEGIN_EVENT_TABLE(ProsodyDisplay, wxScrolledWindow)
  33. EVT_LEFT_DOWN(ProsodyDisplay::OnMouse)
  34. EVT_RIGHT_DOWN(ProsodyDisplay::OnMouse)
  35. EVT_CHAR(ProsodyDisplay::OnKey)
  36. EVT_MENU(-1, ProsodyDisplay::OnMenu)
  37. END_EVENT_TABLE()
  38. static wxPen PEN_PITCHENV(wxColour(0,0,255),1,wxSOLID);
  39. static wxPen PEN_SAMPLED(wxColour(0,200,200),2,wxSOLID);
  40. static wxPen PEN_PHSELECTED(wxColour(255,0,0),2,wxSOLID);
  41. static wxPen PEN_PHSTRESSED(wxColour(80,80,196),3,wxSOLID);
  42. static wxPen PEN_PHSTRESSED2(wxColour(160,160,255),2,wxSOLID);
  43. typedef struct {
  44. unsigned int value;
  45. char *name;
  46. } NAMETAB;
  47. NAMETAB *manifest = NULL;
  48. int n_manifest;
  49. const char *LookupManifest(unsigned int addr)
  50. {//=============================================
  51. int ix;
  52. if(manifest != NULL)
  53. {
  54. for(ix=0; ix < n_manifest; ix++)
  55. {
  56. if(manifest[ix].value == addr)
  57. return(manifest[ix].name);
  58. if(manifest[ix].value > addr)
  59. break;
  60. }
  61. }
  62. return("");
  63. }
  64. void ReadPhondataManifest()
  65. {//=========================
  66. // Read the phondata-manifest file
  67. FILE *f;
  68. int n_lines=0;
  69. int ix;
  70. char *p;
  71. unsigned int value;
  72. char buf[sizeof(path_home)+40];
  73. char name[120];
  74. sprintf(buf,"%s%c%s",path_home,PATHSEP,"phondata-manifest");
  75. if((f = fopen(buf, "r")) == NULL)
  76. return;
  77. while(fgets(buf, sizeof(buf), f) != NULL)
  78. n_lines++;
  79. rewind(f);
  80. if(manifest != NULL)
  81. {
  82. for(ix=0; ix < n_manifest; ix++)
  83. free(manifest[ix].name);
  84. }
  85. if((manifest = (NAMETAB *)realloc(manifest, n_lines * sizeof(NAMETAB))) == NULL)
  86. {
  87. fclose(f);
  88. return;
  89. }
  90. n_manifest = 0;
  91. while(fgets(buf, sizeof(buf), f) != NULL)
  92. {
  93. if(!isalpha(buf[0]))
  94. continue;
  95. if(sscanf(&buf[2], "%x %s", &value, name) == 2)
  96. {
  97. if((p = (char *)malloc(strlen(name)+1)) != NULL)
  98. {
  99. strcpy(p, name);
  100. manifest[n_manifest].value = value;
  101. manifest[n_manifest].name = p;
  102. n_manifest++;
  103. }
  104. }
  105. }
  106. fclose(f);
  107. }
  108. ProsodyDisplay::ProsodyDisplay(wxWindow *parent, const wxPoint& pos, const wxSize& size)
  109. : wxScrolledWindow(parent, -1, pos, size,
  110. wxSUNKEN_BORDER | wxNO_FULL_REPAINT_ON_RESIZE)
  111. {//=====================================================================
  112. linewidth = size.GetWidth();
  113. scalex = 0.5;
  114. scaley = double(LINESEP*6)/150.0;
  115. selected_ph = -1;
  116. SetBackgroundColour(wxColour(245,245,245));
  117. } // end of ProsodyDisplay::ProsodyDisplay
  118. ProsodyDisplay::~ProsodyDisplay()
  119. {//==========================
  120. prosodycanvas = NULL;
  121. }
  122. extern MNEM_TAB envelope_names[];
  123. void InitProsodyDisplay()
  124. {//======================
  125. wxMenu *menu_envelopes;
  126. int ix;
  127. wxString string;
  128. ReadPhondataManifest();
  129. menu_envelopes = new wxMenu;
  130. // entries match those in envelope_data[] in intonation.cpp
  131. for(ix=0; envelope_names[ix].mnem != NULL; ix++)
  132. {
  133. string = wxString(envelope_names[ix].mnem, wxConvLocal);
  134. menu_envelopes->Append(0x100+envelope_names[ix].value, string);
  135. }
  136. #ifdef deleted
  137. menu_envelopes->Append(0x100,_T("fall"));
  138. menu_envelopes->Append(0x102,_T("rise"));
  139. menu_envelopes->Append(0x104,_T("fall-rise"));
  140. // menu_envelopes->Append(0x105,_T("fall-rise (R)"));
  141. menu_envelopes->Append(0x106,_T("fall-rise 2"));
  142. // menu_envelopes->Append(0x107,_T("fall-rise 2(R)"));
  143. menu_envelopes->Append(0x108,_T("rise-fall"));
  144. menu_envelopes->Append(0x10a,_T("fall-rise 3"));
  145. menu_envelopes->Append(0x10c,_T("fall-rise 4"));
  146. menu_envelopes->Append(0x10e,_T("fall 2"));
  147. menu_envelopes->Append(0x110,_T("rise 2"));
  148. menu_envelopes->Append(0x112,_T("rise-fall-rise"));
  149. #endif
  150. menu_prosody = new wxMenu;
  151. menu_prosody->Append(1,_T("Pitch envelope"),menu_envelopes);
  152. menu_prosody->Append(2,_T("Amplitude"));
  153. menu_prosody->Append(3,_T("Length"));
  154. menu_prosody->Append(4,_T("Play F2"));
  155. }
  156. void ProsodyDisplay::RefreshLine(int line)
  157. {//=====================================
  158. int x,y;
  159. CalcScrolledPosition(0,line*FRAMEHEIGHT,&x,&y);
  160. RefreshRect(wxRect(0,y,linewidth,FRAMEHEIGHT));
  161. }
  162. int ProsodyDisplay::GetWidth(PHONEME_LIST *p)
  163. {//========================================
  164. int w;
  165. if(p->ph == NULL)
  166. return(0);
  167. w = (p->ph->std_length * 2);
  168. if(w == 0) w = 60;
  169. if(p->length != 0)
  170. w = (w * p->length) / 256;
  171. return(int((w + p->prepause)* scalex) + 1);
  172. }
  173. void ProsodyDisplay::SelectPh(int index)
  174. {//=====================================
  175. // A phoneme has been selected
  176. PHONEME_LIST *p;
  177. const char *emphasized;
  178. int y1, y2;
  179. int ix;
  180. const char *name = "?";
  181. char buf[120];
  182. char len_string[20];
  183. char param_string[20];
  184. if(index < 0) return;
  185. p = &phlist[index];
  186. if((p == NULL) || (p->ph == NULL)) return;
  187. emphasized = "";
  188. if(p->stresslevel & 8)
  189. emphasized = "*";
  190. for(ix=0; envelope_names[ix].mnem != NULL; ix++)
  191. {
  192. if(envelope_names[ix].value == (p->env & 0xfe))
  193. {
  194. name = envelope_names[ix].mnem;
  195. break;
  196. }
  197. }
  198. y1 = p->pitch1;
  199. y2 = p->pitch2;
  200. len_string[0] = 0;
  201. param_string[0] = 0;
  202. if(p->std_length > 0)
  203. sprintf(len_string," Length %d", p->std_length*2);
  204. if(p->sound_param != 0)
  205. sprintf(param_string,", %d", p->sound_param);
  206. sprintf(buf,"Stress %s%d Amp %2d LengthMod %2d Pitch %3d %3d %s PhFlags %.2x (%s%s)%s",
  207. emphasized,p->stresslevel&0x7,p->amp, p->length,y1,y2,name,p->ph->phflags, LookupManifest(p->phontab_addr), param_string, len_string);
  208. wxLogStatus(wxString(buf,wxConvLocal));
  209. }
  210. void ProsodyDisplay::ChangePh(int pitch1, int pitch2)
  211. {//================================================
  212. PHONEME_LIST *p;
  213. int sign1;
  214. int sign2;
  215. if(selected_ph < 0)
  216. return;
  217. p = &phlist[selected_ph];
  218. sign1 = p->pitch1 - p->pitch2;
  219. p->pitch1 += pitch1;
  220. p->pitch2 += (pitch1 + pitch2);
  221. sign2 = p->pitch1 - p->pitch2;
  222. if((sign1 != 0) && ((sign1 * sign2) <= 0))
  223. {
  224. // change of sign, change rise to fall
  225. if(p->env == 1)
  226. p->env = 0;
  227. else
  228. if(p->env == 0)
  229. p->env = 1;
  230. }
  231. }
  232. void ProsodyDisplay::OnMenu(wxCommandEvent& event)
  233. {//===============================================
  234. int id;
  235. int value;
  236. PHONEME_LIST *p;
  237. id = event.GetId();
  238. p = &phlist[selected_ph];
  239. if((id & 0xf00) == 0x100)
  240. {
  241. // selected a pitch envelope
  242. p->env = id - 0x100;
  243. }
  244. switch(id)
  245. {
  246. case 2:
  247. value = wxGetNumberFromUser(_T(""),_T("Amplitude"),_T(""),p->amp,0,40);
  248. if(value >= 0)
  249. p->amp = value;
  250. break;
  251. case 3:
  252. value = wxGetNumberFromUser(_T(""),_T("Length"),_T(""),p->length,1,500);
  253. if(value >= 0)
  254. p->length = value;
  255. break;
  256. case 4:
  257. MakeWave2(phlist,numph);
  258. break;
  259. }
  260. SelectPh(selected_ph);
  261. Refresh();
  262. }
  263. void ProsodyDisplay::OnMouse(wxMouseEvent& event)
  264. {//============================================
  265. int line;
  266. int ix;
  267. int xpos=0;
  268. wxClientDC dc(this);
  269. PrepareDC(dc);
  270. wxPoint pt(event.GetLogicalPosition(dc));
  271. if(selected_ph >= 0)
  272. {
  273. // find line for previously selected phoneme
  274. for(line=0; line<num_lines; line++)
  275. if(linetab[line+1] > selected_ph) break;
  276. RefreshLine(line);
  277. selected_ph = -1;
  278. }
  279. line = pt.y / FRAMEHEIGHT;
  280. if(line < num_lines)
  281. {
  282. // find which phoneme is selected on this line
  283. for(ix=linetab[line]; (ix<linetab[line+1]) && (ix<numph); ix++)
  284. {
  285. xpos += GetWidth(&phlist[ix]);
  286. if(xpos > pt.x)
  287. {
  288. selected_ph = ix;
  289. SelectPh(selected_ph);
  290. break;
  291. }
  292. }
  293. RefreshLine(line);
  294. }
  295. if(event.RightDown())
  296. {
  297. PopupMenu(menu_prosody);
  298. }
  299. } // end of ProsodyDisplay::OnMouse
  300. void ProsodyDisplay::OnKey(wxKeyEvent& event)
  301. {//========================================
  302. PHONEME_LIST *p;
  303. int display=1;
  304. if(selected_ph < 0)
  305. selected_ph = 0;
  306. p = &phlist[selected_ph];
  307. switch(event.GetKeyCode())
  308. {
  309. case WXK_F2:
  310. // make and play from this clause
  311. MakeWave2(phlist,numph);
  312. break;
  313. case WXK_LEFT:
  314. if(selected_ph > 1)
  315. selected_ph--;
  316. break;
  317. case WXK_RIGHT:
  318. if(selected_ph < (numph-2))
  319. selected_ph++;
  320. break;
  321. case WXK_UP:
  322. if(event.ControlDown())
  323. ChangePh(-1,2);
  324. else
  325. ChangePh(1,0);
  326. display = 1;
  327. break;
  328. case WXK_DOWN:
  329. if(event.ControlDown())
  330. ChangePh(1,-2);
  331. else
  332. ChangePh(-1,0);
  333. break;
  334. case ',':
  335. case '<':
  336. if(p->length > 0)
  337. p->length--;
  338. break;
  339. case '.':
  340. case '>':
  341. p->length++;
  342. break;
  343. case WXK_TAB:
  344. display = 0;
  345. event.Skip();
  346. transldlg->SetFocus();
  347. break;
  348. default:
  349. display = 0;
  350. event.Skip();
  351. break;
  352. }
  353. if(display)
  354. {
  355. Refresh();
  356. SelectPh(selected_ph);
  357. }
  358. } // end of ProsodyDisplay::OnKey
  359. void ProsodyDisplay::DrawEnv(wxDC& dc, int x1, int y1, int width, PHONEME_LIST *ph)
  360. {//==============================================================================
  361. int pitchr;
  362. int pitch;
  363. int p1;
  364. int ix;
  365. int x,y;
  366. int y2=0;
  367. unsigned char *env;
  368. PHONEME_DATA phdata_tone;
  369. if(width <= 0) return;
  370. if((pitchr = ph->pitch2 - ph->pitch1) < 0)
  371. {
  372. pitchr = -pitchr;
  373. p1 = ph->pitch2;
  374. }
  375. else
  376. {
  377. p1 = ph->pitch1;
  378. }
  379. if(p1 == 255) return;
  380. dc.SetPen(PEN_PITCHENV);
  381. env = envelope_data[ph->env];
  382. if((ph->type == phVOWEL) && (ph->tone_ph != 0))
  383. {
  384. // the envelope is given by a Tone phoneme acting on this vowel
  385. InterpretPhoneme2(ph->tone_ph, &phdata_tone);
  386. env = GetEnvelope(phdata_tone.pitch_env);
  387. }
  388. if(env == NULL)
  389. return;
  390. for(ix=0; ix<=width; ix+=4)
  391. {
  392. x = int((ix * 127.9)/width);
  393. pitch = p1 + (pitchr * env[x])/256;
  394. y = y1-int(pitch * scaley);
  395. if(ix > 0)
  396. dc.DrawLine(x1+ix-4,y2,x1+ix,y);
  397. y2 = y;
  398. }
  399. } // end of DrawEnv
  400. static void GetPhonemeName(PHONEME_TAB *ph, wxString& string)
  401. {//==========================================================
  402. string = wxString(WordToString(ph->mnemonic),wxConvLocal);
  403. }
  404. void ProsodyDisplay::DrawPitchline(wxDC& dc, int line, int x1, int x2)
  405. {//=================================================================
  406. int ix;
  407. int endix;
  408. int y;
  409. int offy;
  410. int xpos;
  411. int width; // total width, including pre-pause
  412. int width2; // width without pre-pause
  413. int width_env;
  414. int textwidth, textheight;
  415. wxString string;
  416. PHONEME_LIST *p;
  417. if(linetab[line] >= numph) return;
  418. offy = (line+1) * FRAMEHEIGHT - 1;
  419. y = LINEBASE+LINESEP;
  420. dc.SetPen(*wxLIGHT_GREY_PEN);
  421. // dc.SetPen(*wxCYAN_PEN);
  422. for(ix=0; ix<5; ix++)
  423. {
  424. dc.DrawLine(0,offy-y,linewidth,offy-y);
  425. y += LINESEP;
  426. }
  427. endix = linetab[line+1];
  428. xpos = 0;
  429. for(ix=linetab[line]; ix<endix; ix++)
  430. {
  431. if(ix < 0 || ix > numph-2) break;
  432. if(xpos > x2) break; // past the redraw region
  433. p = &phlist[ix];
  434. width = GetWidth(p);
  435. width2 = width - int(p->prepause * scalex) - 1;
  436. if(xpos+width < x1)
  437. {
  438. xpos += width;
  439. continue; // before the redraw region
  440. }
  441. // is this a stressed vowel ?
  442. if((p->type == phVOWEL) && (p->stresslevel >= 2))
  443. {
  444. if(p->stresslevel >= 4)
  445. dc.SetPen(PEN_PHSTRESSED);
  446. else
  447. dc.SetPen(PEN_PHSTRESSED2);
  448. dc.DrawLine(xpos,offy-LINEBASE-1,xpos+width,offy-LINEBASE-1);
  449. }
  450. // is this phoneme selected ?
  451. if(ix == selected_ph)
  452. {
  453. dc.SetPen(PEN_PHSELECTED);
  454. dc.DrawLine(xpos,offy-LINEBASE,xpos+width,offy-LINEBASE);
  455. }
  456. // draw separator bar
  457. if(p->newword)
  458. dc.SetPen(*wxBLACK_PEN); // word boundary
  459. else
  460. dc.SetPen(*wxLIGHT_GREY_PEN);
  461. dc.DrawLine(xpos,offy-LINEBASE,xpos,offy-LINEBASE-LINESEP*6);
  462. // draw pitch envelope
  463. if(((p->ph->phflags & phWAVE) == 0) && (p->ph->type != phPAUSE))
  464. {
  465. if(!(p->synthflags & SFLAG_SEQCONTINUE))
  466. {
  467. width_env = width2;
  468. if(phlist[ix+1].synthflags & SFLAG_SEQCONTINUE)
  469. width_env += GetWidth(&phlist[ix+1]);
  470. DrawEnv(dc,xpos+1+(width-width2),offy-LINEBASE,width_env,p);
  471. }
  472. }
  473. else
  474. if(p->type != phPAUSE)
  475. {
  476. // sampled sound, draw a flat line
  477. dc.SetPen(PEN_SAMPLED);
  478. dc.DrawLine(xpos+1+(width-width2),offy-LINEBASE-LINESEP,
  479. xpos+1+width,offy-LINEBASE-LINESEP);
  480. }
  481. // show phoneme name from the PHONEME_TAB
  482. GetPhonemeName(p->ph,string);
  483. dc.GetTextExtent(string,&textwidth,&textheight);
  484. dc.DrawText(string,xpos+(width-textwidth/2)/2, offy-LINEBASE);
  485. xpos += width;
  486. }
  487. // draw the separator bar at the end of the line
  488. if(ix==endix && ix<numph && phlist[ix].newword)
  489. dc.SetPen(*wxLIGHT_GREY_PEN);
  490. else
  491. dc.SetPen(*wxBLACK_PEN); // word boundary or end of list
  492. dc.DrawLine(xpos,offy-LINEBASE,xpos,offy-LINEBASE-LINESEP*6);
  493. } // end of ProsodyDisplay::DrawPitchline
  494. void ProsodyDisplay::OnDraw(wxDC& dc)
  495. {//================================
  496. int x1,y1;
  497. int vX,vY,vW,vH; // Dimensions of client area in pixels
  498. int line, start, end;
  499. GetClientSize(&x1, &y1);
  500. if(x1 != linewidth)
  501. {
  502. LayoutData(NULL, 0);
  503. }
  504. wxRegionIterator upd(GetUpdateRegion()); // get the update rect list
  505. while (upd)
  506. {
  507. vX = upd.GetX();
  508. vY = upd.GetY();
  509. vW = upd.GetW();
  510. vH = upd.GetH();
  511. CalcUnscrolledPosition(vX,vY,&x1,&y1);
  512. // Repaint this rectangle, find which lines to redraw
  513. start = y1/FRAMEHEIGHT;
  514. end = (y1+vH)/FRAMEHEIGHT;
  515. for(line=start; line<=end && line<num_lines; line++)
  516. DrawPitchline(dc,line,x1,x1+vW);
  517. upd ++ ;
  518. }
  519. } // end of ProsodyDisplay::OnDraw
  520. void ProsodyDisplay::LayoutData(PHONEME_LIST *ph_list, int n_ph)
  521. {//===========================================================
  522. // divide the phoneme list into lines for display
  523. int xpos;
  524. int w;
  525. int ix;
  526. int height;
  527. PHONEME_LIST *p;
  528. if(ph_list != NULL)
  529. {
  530. numph = n_ph;
  531. phlist = ph_list;
  532. }
  533. num_lines = 0;
  534. linetab[0] = 1;
  535. GetClientSize(&linewidth, &height);
  536. xpos = linewidth;
  537. // could improve this to do 'wordwrap' - only split on word boundary
  538. // or unvoiced phonemes
  539. for(ix=1; ix<numph-2; ix++)
  540. {
  541. p = &phlist[ix];
  542. w = GetWidth(p);
  543. if(w + xpos >= linewidth)
  544. {
  545. linetab[num_lines++] = ix;
  546. xpos = 0;
  547. }
  548. xpos += w;
  549. }
  550. linetab[num_lines]=numph-2;
  551. SetScrollbars(SCROLLUNITS,SCROLLUNITS,linewidth/SCROLLUNITS,
  552. (num_lines*FRAMEHEIGHT)/SCROLLUNITS+1);
  553. Refresh();
  554. } // end of ProsodyDisplay::LayoutData
  555. extern int adding_page;
  556. void MyFrame::OnProsody(wxCommandEvent& WXUNUSED(event))
  557. {//=====================================================
  558. // Open the Prosody display window
  559. int width, height;
  560. int ix, npages;
  561. if(prosodycanvas != NULL)
  562. {
  563. // The Prosody window is already open
  564. // ?? select the prosody page ??
  565. npages = screenpages->GetPageCount();
  566. for(ix=0; ix<npages; ix++)
  567. {
  568. if(screenpages->GetPage(ix) == (wxWindow*)prosodycanvas)
  569. {
  570. screenpages->ChangeSelection(ix);
  571. break;
  572. }
  573. }
  574. return;
  575. }
  576. screenpages->GetClientSize(&width, &height);
  577. prosodycanvas = new ProsodyDisplay(screenpages, wxPoint(0, 50), wxSize(width-10, height));
  578. adding_page = 2; // work around for wxNotebook bug (version 2.8.7)
  579. screenpages->AddPage(prosodycanvas, _T("Prosody"), true);
  580. }