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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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. extern MyFrame *myframe;
  28. extern ChildFrProsody *prosodyframe;
  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. ProsodyDisplay::ProsodyDisplay(wxWindow *parent, const wxPoint& pos, const wxSize& size)
  41. : wxScrolledWindow(parent, -1, pos, size,
  42. wxSUNKEN_BORDER | wxNO_FULL_REPAINT_ON_RESIZE)
  43. {//=====================================================================
  44. linewidth = size.GetWidth();
  45. scalex = 0.5;
  46. scaley = double(LINESEP*6)/80.0;
  47. selected_ph = -1;
  48. // SetBackgroundColour(* wxWHITE);
  49. LayoutData(phoneme_list,n_phoneme_list);
  50. } // end of ProsodyDisplay::ProsodyDisplay
  51. ProsodyDisplay::~ProsodyDisplay()
  52. {//==========================
  53. prosodycanvas = NULL;
  54. }
  55. void InitProsodyDisplay()
  56. {//======================
  57. wxMenu *menu_envelopes;
  58. menu_envelopes = new wxMenu;
  59. // entries match those in envelope_data[] in intonation.cpp
  60. menu_envelopes->Append(0x100,_T("Fall"));
  61. menu_envelopes->Append(0x101,_T("Rise"));
  62. menu_envelopes->Append(0x102,_T("Fall-rise"));
  63. menu_envelopes->Append(0x103,_T("Fall-rise (R)"));
  64. menu_envelopes->Append(0x104,_T("Fall-2"));
  65. menu_envelopes->Append(0x105,_T("Fall-3"));
  66. menu_prosody = new wxMenu;
  67. menu_prosody->Append(1,_T("Pitch envelope"),menu_envelopes);
  68. menu_prosody->Append(2,_T("Amplitude"));
  69. menu_prosody->Append(3,_T("Length"));
  70. menu_prosody->Append(4,_T("Play F2"));
  71. }
  72. void ProsodyDisplay::RefreshLine(int line)
  73. {//=====================================
  74. int x,y;
  75. CalcScrolledPosition(0,line*FRAMEHEIGHT,&x,&y);
  76. RefreshRect(wxRect(0,y,linewidth,FRAMEHEIGHT));
  77. }
  78. int ProsodyDisplay::GetWidth(PHONEME_LIST *p)
  79. {//========================================
  80. int w;
  81. if(p->ph == NULL)
  82. return(0);
  83. w = p->ph->std_length;
  84. if(w == 0) w = 60;
  85. if(p->length != 0)
  86. w = (w * p->length) / 256;
  87. return(int((w + p->prepause)* scalex) + 1);
  88. }
  89. void ProsodyDisplay::SelectPh(int index)
  90. {//=====================================
  91. // A phoneme has been selected
  92. PHONEME_LIST *p;
  93. char buf[120];
  94. if(index < 0) return;
  95. p = &phlist[index];
  96. if((p == NULL) || (p->ph == NULL)) return;
  97. sprintf(buf,"Stress %d Amp %2d LenMod %2d Pitch %3d %3d [env=%d] Flags %.2x ",
  98. p->tone,p->amp,p->length,p->pitch1,p->pitch2,p->env,p->ph->phflags);
  99. wxLogStatus(wxString(buf,wxConvLocal));
  100. }
  101. void ProsodyDisplay::ChangePh(int pitch1, int pitch2)
  102. {//================================================
  103. PHONEME_LIST *p;
  104. int sign1;
  105. int sign2;
  106. if(selected_ph < 0)
  107. return;
  108. p = &phlist[selected_ph];
  109. sign1 = p->pitch1 - p->pitch2;
  110. p->pitch1 += pitch1;
  111. p->pitch2 += (pitch1 + pitch2);
  112. sign2 = p->pitch1 - p->pitch2;
  113. if((sign1 != 0) && ((sign1 * sign2) <= 0))
  114. {
  115. // change of sign, change rise to fall
  116. if(p->env == 1)
  117. p->env = 0;
  118. else
  119. if(p->env == 0)
  120. p->env = 1;
  121. }
  122. }
  123. void ProsodyDisplay::OnMenu(wxCommandEvent& event)
  124. {//===============================================
  125. int id;
  126. int value;
  127. PHONEME_LIST *p;
  128. id = event.GetId();
  129. p = &phlist[selected_ph];
  130. if((id & 0xf00) == 0x100)
  131. {
  132. // selected a pitch envelope
  133. p->env = id - 0x100;
  134. }
  135. switch(id)
  136. {
  137. case 2:
  138. value = wxGetNumberFromUser(_T(""),_T("Amplitude"),_T(""),p->amp,0,40);
  139. if(value >= 0)
  140. p->amp = value;
  141. break;
  142. case 3:
  143. value = wxGetNumberFromUser(_T(""),_T("Length"),_T(""),p->length,1,500);
  144. if(value >= 0)
  145. p->length = value;
  146. break;
  147. case 4:
  148. MakeWave2(phlist,numph);
  149. break;
  150. }
  151. SelectPh(selected_ph);
  152. Refresh();
  153. }
  154. void ProsodyDisplay::OnMouse(wxMouseEvent& event)
  155. {//============================================
  156. int line;
  157. int ix;
  158. int xpos=0;
  159. wxClientDC dc(this);
  160. PrepareDC(dc);
  161. wxPoint pt(event.GetLogicalPosition(dc));
  162. if(selected_ph >= 0)
  163. {
  164. // find line for previously selected phoneme
  165. for(line=0; line<num_lines; line++)
  166. if(linetab[line+1] > selected_ph) break;
  167. RefreshLine(line);
  168. selected_ph = -1;
  169. }
  170. line = pt.y / FRAMEHEIGHT;
  171. // find which phoneme is selected on this line
  172. for(ix=linetab[line]; (ix<linetab[line+1]) && (ix<numph); ix++)
  173. {
  174. xpos += GetWidth(&phlist[ix]);
  175. if(xpos > pt.x)
  176. {
  177. selected_ph = ix;
  178. SelectPh(selected_ph);
  179. break;
  180. }
  181. }
  182. RefreshLine(line);
  183. if(event.RightDown())
  184. {
  185. PopupMenu(menu_prosody);
  186. }
  187. } // end of ProsodyDisplay::OnMouse
  188. void ProsodyDisplay::OnKey(wxKeyEvent& event)
  189. {//========================================
  190. PHONEME_LIST *p;
  191. int display=1;
  192. if(selected_ph < 0)
  193. selected_ph = 0;
  194. p = &phlist[selected_ph];
  195. switch(event.GetKeyCode())
  196. {
  197. case WXK_F2:
  198. // make and play from this clause
  199. MakeWave2(phlist,numph);
  200. break;
  201. case WXK_LEFT:
  202. if(selected_ph > 1)
  203. selected_ph--;
  204. break;
  205. case WXK_RIGHT:
  206. if(selected_ph < (numph-2))
  207. selected_ph++;
  208. break;
  209. case WXK_UP:
  210. if(event.ControlDown())
  211. ChangePh(-1,2);
  212. else
  213. ChangePh(1,0);
  214. display = 1;
  215. break;
  216. case WXK_DOWN:
  217. if(event.ControlDown())
  218. ChangePh(1,-2);
  219. else
  220. ChangePh(-1,0);
  221. break;
  222. case ',':
  223. case '<':
  224. if(p->length > 0)
  225. p->length--;
  226. break;
  227. case '.':
  228. case '>':
  229. p->length++;
  230. break;
  231. case WXK_TAB:
  232. display = 0;
  233. event.Skip();
  234. transldlg->SetFocus();
  235. break;
  236. default:
  237. display = 0;
  238. event.Skip();
  239. break;
  240. }
  241. if(display)
  242. {
  243. Refresh();
  244. SelectPh(selected_ph);
  245. }
  246. } // end of ProsodyDisplay::OnKey
  247. void ProsodyDisplay::DrawEnv(wxDC& dc, int x1, int y1, int width, PHONEME_LIST *ph)
  248. {//==============================================================================
  249. int pitchr;
  250. int pitch;
  251. int p1;
  252. int ix;
  253. int x,y;
  254. int y2=0;
  255. unsigned char *env;
  256. if(width <= 0) return;
  257. if((pitchr = ph->pitch2 - ph->pitch1) < 0)
  258. {
  259. pitchr = -pitchr;
  260. p1 = ph->pitch2;
  261. }
  262. else
  263. {
  264. p1 = ph->pitch1;
  265. }
  266. dc.SetPen(PEN_PITCHENV);
  267. env = envelope_data[ph->env];
  268. if((ph->type == phVOWEL) && (ph->tone_ph != 0))
  269. {
  270. // the envelope is given by a Tone phoneme acting on this vowel
  271. env = LookupEnvelope(phoneme_tab[ph->tone_ph]->spect);
  272. }
  273. for(ix=0; ix<=width; ix+=4)
  274. {
  275. x = int((ix * 127.9)/width);
  276. pitch = ph->pitch1 + (pitchr * env[x])/256;
  277. y = y1-int(pitch * scaley);
  278. if(ix > 0)
  279. dc.DrawLine(x1+ix-4,y2,x1+ix,y);
  280. y2 = y;
  281. }
  282. } // end of DrawEnv
  283. static void GetPhonemeName(PHONEME_TAB *ph, wxString& string)
  284. {//==========================================================
  285. string = wxString(WordToString(ph->mnemonic),wxConvLocal);
  286. }
  287. void ProsodyDisplay::DrawPitchline(wxDC& dc, int line, int x1, int x2)
  288. {//=================================================================
  289. int ix;
  290. int endix;
  291. int y;
  292. int offy;
  293. int xpos;
  294. int width; // total width, including pre-pause
  295. int width2; // width without pre-pause
  296. int width_env;
  297. int textwidth, textheight;
  298. wxString string;
  299. PHONEME_LIST *p;
  300. if(linetab[line] >= numph) return;
  301. offy = (line+1) * FRAMEHEIGHT - 1;
  302. y = LINEBASE+LINESEP;
  303. dc.SetPen(*wxLIGHT_GREY_PEN);
  304. for(ix=0; ix<5; ix++)
  305. {
  306. dc.DrawLine(0,offy-y,linewidth,offy-y);
  307. y += LINESEP;
  308. }
  309. endix = linetab[line+1];
  310. xpos = 0;
  311. for(ix=linetab[line]; ix<endix; ix++)
  312. {
  313. if(ix < 0 || ix > numph-2) break;
  314. if(xpos > x2) break; // past the redraw region
  315. p = &phlist[ix];
  316. width = GetWidth(p);
  317. width2 = width - int(p->prepause * scalex) - 1;
  318. if(xpos+width < x1)
  319. {
  320. xpos += width;
  321. continue; // before the redraw region
  322. }
  323. // is this phoneme selected ?
  324. if(ix == selected_ph)
  325. {
  326. dc.SetPen(PEN_PHSELECTED);
  327. dc.DrawLine(xpos,offy-LINEBASE,xpos+width,offy-LINEBASE);
  328. }
  329. // draw separator bar
  330. if(p->newword)
  331. dc.SetPen(*wxBLACK_PEN); // word boundary
  332. else
  333. dc.SetPen(*wxLIGHT_GREY_PEN);
  334. dc.DrawLine(xpos,offy-LINEBASE,xpos,offy-LINEBASE-LINESEP*6);
  335. // draw pitch envelope
  336. if(((p->ph->phflags & phWAVE) == 0) && (p->ph->type != phPAUSE))
  337. {
  338. if(!(p->synthflags & SFLAG_SEQCONTINUE))
  339. {
  340. width_env = width2;
  341. if(phlist[ix+1].synthflags & SFLAG_SEQCONTINUE)
  342. width_env += GetWidth(&phlist[ix+1]);
  343. DrawEnv(dc,xpos+1+(width-width2),offy-LINEBASE,width_env,p);
  344. }
  345. }
  346. else
  347. if(p->type != phPAUSE)
  348. {
  349. // sampled sound, draw a flat line
  350. dc.SetPen(PEN_SAMPLED);
  351. dc.DrawLine(xpos+1+(width-width2),offy-LINEBASE-LINESEP,
  352. xpos+1+width,offy-LINEBASE-LINESEP);
  353. }
  354. // show phoneme name from the PHONEME_TAB
  355. GetPhonemeName(p->ph,string);
  356. dc.GetTextExtent(string,&textwidth,&textheight);
  357. dc.DrawText(string,xpos+(width-textwidth/2)/2, offy-LINEBASE);
  358. xpos += width;
  359. }
  360. // draw the separator bar at the end of the line
  361. if(ix==endix && ix<numph && phlist[ix].newword)
  362. dc.SetPen(*wxLIGHT_GREY_PEN);
  363. else
  364. dc.SetPen(*wxBLACK_PEN); // word boundary or end of list
  365. dc.DrawLine(xpos,offy-LINEBASE,xpos,offy-LINEBASE-LINESEP*6);
  366. } // end of ProsodyDisplay::DrawPitchline
  367. void ProsodyDisplay::OnDraw(wxDC& dc)
  368. {//================================
  369. int x1,y1;
  370. int vX,vY,vW,vH; // Dimensions of client area in pixels
  371. int line, start, end;
  372. wxRegionIterator upd(GetUpdateRegion()); // get the update rect list
  373. while (upd)
  374. {
  375. vX = upd.GetX();
  376. vY = upd.GetY();
  377. vW = upd.GetW();
  378. vH = upd.GetH();
  379. CalcUnscrolledPosition(vX,vY,&x1,&y1);
  380. // Repaint this rectangle, find which lines to redraw
  381. start = y1/FRAMEHEIGHT;
  382. end = (y1+vH)/FRAMEHEIGHT;
  383. for(line=start; line<=end && line<num_lines; line++)
  384. DrawPitchline(dc,line,x1,x1+vW);
  385. upd ++ ;
  386. }
  387. } // end of ProsodyDisplay::OnDraw
  388. void ProsodyDisplay::LayoutData(PHONEME_LIST *ph_list, int n_ph)
  389. {//===========================================================
  390. // divide the phoneme list into lines for display
  391. int xpos;
  392. int w;
  393. int ix;
  394. PHONEME_LIST *p;
  395. numph = n_ph;
  396. phlist = ph_list;
  397. num_lines = 0;
  398. linetab[0] = 1;
  399. xpos = linewidth;
  400. // could improve this to do 'wordwrap' - only split on word boundary
  401. // or unvoiced phonemes
  402. for(ix=1; ix<numph-2; ix++)
  403. {
  404. p = &phlist[ix];
  405. w = GetWidth(p);
  406. if(w + xpos >= linewidth)
  407. {
  408. linetab[num_lines++] = ix;
  409. xpos = 0;
  410. }
  411. xpos += w;
  412. }
  413. linetab[num_lines]=numph-2;
  414. SetScrollbars(SCROLLUNITS,SCROLLUNITS,linewidth/SCROLLUNITS,
  415. (num_lines*FRAMEHEIGHT)/SCROLLUNITS+1);
  416. Refresh();
  417. } // end of ProsodyDisplay::LayoutData
  418. void MyFrame::OnProsody(wxCommandEvent& WXUNUSED(event))
  419. {//=====================================================
  420. // Open the Prosody display window
  421. // Make another frame, containing a canvas
  422. if(prosodyframe != NULL)
  423. {
  424. // The Prosody window is already open
  425. prosodyframe->Activate();
  426. return;
  427. }
  428. prosodyframe = new ChildFrProsody(myframe, _T(""),
  429. wxPoint(10, 200), wxSize(1000, 300),
  430. wxDEFAULT_FRAME_STYLE |
  431. wxNO_FULL_REPAINT_ON_RESIZE);
  432. prosodyframe->SetTitle(_T("Prosody"));
  433. // Give it a status line
  434. prosodyframe->CreateStatusBar();
  435. int width, height;
  436. wxMDIClientWindow *clientwin = this->GetClientWindow();
  437. clientwin->GetClientSize(&width, &height);
  438. #ifdef deleted
  439. wxPanel *panel = new wxPanel(prosodyframe,-1,wxPoint(0,0), wxSize(width,50));
  440. ProsodyDisplay *canvas = new ProsodyDisplay(prosodyframe, wxPoint(0, 50), wxSize(width-2, height-50));
  441. #else
  442. ProsodyDisplay *canvas = new ProsodyDisplay(prosodyframe, wxPoint(0, 50), wxSize(width-10, height));
  443. #endif
  444. prosodycanvas = canvas;
  445. // Associate the menu bar with the frame
  446. prosodyframe->SetMenuBar(MakeMenu(2));
  447. prosodyframe->prosodycanvas = canvas;
  448. prosodyframe->Show(TRUE);
  449. }
  450. BEGIN_EVENT_TABLE(ChildFrProsody, wxMDIChildFrame)
  451. EVT_MENU(SPECTSEQ_CLOSE, ChildFrProsody::OnQuit)
  452. // EVT_ACTIVATE(ChildFrProsody::OnActivate)
  453. END_EVENT_TABLE()
  454. extern wxList my_children;
  455. ChildFrProsody::ChildFrProsody(wxMDIParentFrame *parent, const wxString& title, const wxPoint& pos, const wxSize& size,
  456. const long style):
  457. wxMDIChildFrame(parent, -1, title, pos, size, style)
  458. {
  459. my_children.Append(this);
  460. }
  461. ChildFrProsody::~ChildFrProsody(void)
  462. {
  463. wxWindow *w;
  464. my_children.DeleteObject(this);
  465. prosodycanvas = NULL;
  466. prosodyframe = NULL;
  467. #ifndef PLATFORM_WINDOWS
  468. // bug in wxMDIChildFrame, we need to explicitly remove the ChildFrame from the ClientWindow
  469. w = myframe->GetClientWindow();
  470. w->RemoveChild(this);
  471. #endif
  472. }
  473. void ChildFrProsody::OnQuit(wxCommandEvent& WXUNUSED(event))
  474. {
  475. Close(TRUE);
  476. }