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 14KB

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