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.

espeakedit.cpp 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  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, see: *
  17. * <http://www.gnu.org/licenses/>. *
  18. ***************************************************************************/
  19. #include "wx/wx.h"
  20. #include "wx/wfstream.h"
  21. #include "wx/notebook.h"
  22. #include "wx/image.h"
  23. #include "wx/filename.h"
  24. #include "wx/numdlg.h"
  25. #include "wx/mdi.h"
  26. #include "wx/laywin.h"
  27. #include "wx/sashwin.h"
  28. #include <locale.h>
  29. #include "speak_lib.h"
  30. #include "main.h"
  31. #include "speech.h"
  32. #include "options.h"
  33. #include "phoneme.h"
  34. #include "synthesize.h"
  35. #include "voice.h"
  36. #include "spect.h"
  37. #include "translate.h"
  38. #include "prosodydisplay.h"
  39. static const char *about_string = "espeakedit: %s\nAuthor: Jonathan Duddington (c) 2007\n\n"
  40. "Licensed under GNU General Public License version 3\n"
  41. "http://espeak.sourceforge.net/";
  42. const char *path_data = "/home/jsd1/speechdata/phsource";
  43. extern void TestTest(int control);
  44. extern void CompareLexicon(int);
  45. extern void ConvertToUtf8();
  46. extern void init_z();
  47. extern void CompileInit(void);
  48. extern void CompileMbrola();
  49. extern void InitSpectrumDisplay();
  50. extern void InitProsodyDisplay();
  51. extern void InitWaveDisplay();
  52. extern void VowelChart(int control, char *fname);
  53. extern void MakeVowelLists(void);
  54. extern void MakeWordFreqList();
  55. extern const char *dict_name;
  56. MyFrame *myframe = NULL;
  57. SpectDisplay *currentcanvas = NULL;
  58. ChildFrProsody *prosodyframe = NULL;
  59. ProsodyDisplay *prosodycanvas = NULL;
  60. wxNotebook *notebook = NULL;
  61. wxProgressDialog *progress;
  62. int progress_max;
  63. int gui_flag = 0;
  64. wxList my_children;
  65. wxFont FONT_SMALL(8,wxSWISS,wxNORMAL,wxNORMAL);
  66. wxFont FONT_MEDIUM(9,wxSWISS,wxNORMAL,wxNORMAL);
  67. wxFont FONT_NORMAL(10,wxSWISS,wxNORMAL,wxNORMAL);
  68. IMPLEMENT_APP(MyApp)
  69. wxString AppName = _T("espeakedit");
  70. int MyApp::OnExit()
  71. {//================
  72. ConfigSave(1);
  73. return(0);
  74. }
  75. static const char *help_text =
  76. "\n\nespeakedit\n"
  77. "\tRun with GUI\n"
  78. "espeakedit --compile\n"
  79. "\tCompile phoneme data in espeak-data/phsource\n"
  80. "\tand dictionary data in espeak-data/dictsource\n";
  81. // Initialise this in OnInit, not statically
  82. bool MyApp::OnInit(void)
  83. {//=====================
  84. int j;
  85. wxChar *p;
  86. char param[80];
  87. if(argc > 1)
  88. {
  89. extern void VoiceReset(int control);
  90. p = argv[1];
  91. j = 0;
  92. while((param[j] = p[j]) != 0) j++;
  93. if((strcmp(param,"--help")==0) || (strcmp(param,"-h")==0))
  94. {
  95. printf(about_string,espeak_Info(NULL));
  96. printf(help_text);
  97. exit(0);
  98. }
  99. ConfigInit();
  100. VoiceReset(0);
  101. WavegenSetVoice(voice);
  102. WavegenInitSound();
  103. if(strcmp(param,"--compile")==0)
  104. {
  105. CompileInit();
  106. exit(0);
  107. }
  108. }
  109. gui_flag = 1;
  110. // It seems that the wctype functions don't work until the locale has been set
  111. // to something other than the default "C". Then, not only Latin1 but also the
  112. // other characters give the correct results with iswalpha() etc.
  113. if(setlocale(LC_CTYPE,"en_US.UTF-8") == NULL)
  114. {
  115. if(setlocale(LC_CTYPE,"UTF-8") == NULL)
  116. setlocale(LC_CTYPE,"");
  117. }
  118. // Create the main frame window
  119. myframe = new MyFrame(NULL, -1, AppName, wxPoint(0, 0), wxSize(1024, 768),
  120. wxDEFAULT_FRAME_STYLE |
  121. wxNO_FULL_REPAINT_ON_RESIZE |
  122. wxHSCROLL | wxVSCROLL);
  123. // Make a menubar
  124. myframe->SetMenuBar(MakeMenu(0));
  125. myframe->CreateStatusBar();
  126. myframe->Show(TRUE);
  127. SetTopWindow(myframe);
  128. wxInitAllImageHandlers();
  129. // wxImage::AddHandler(wxPNGHandler);
  130. return TRUE;
  131. }
  132. BEGIN_EVENT_TABLE(MyFrame, wxMDIParentFrame)
  133. EVT_CHAR(MyFrame::OnKey)
  134. EVT_MENU(MENU_ABOUT, MyFrame::OnAbout)
  135. EVT_MENU(MENU_SPECTRUM, MyFrame::OnNewWindow)
  136. EVT_MENU(MENU_SPECTRUM2, MyFrame::OnNewWindow)
  137. EVT_MENU(MENU_PROSODY, MyFrame::OnProsody)
  138. EVT_MENU(MENU_PARAMS, MyFrame::OnOptions)
  139. EVT_MENU(MENU_PATH0, MyFrame::OnOptions)
  140. EVT_MENU(MENU_PATH1, MyFrame::OnOptions)
  141. EVT_MENU(MENU_PATH2, MyFrame::OnOptions)
  142. EVT_MENU(MENU_PATH3, MyFrame::OnOptions)
  143. EVT_MENU(MENU_PATH4, MyFrame::OnOptions)
  144. EVT_MENU(MENU_COMPILE_PH, MyFrame::OnTools)
  145. EVT_MENU(MENU_COMPILE_DICT, MyFrame::OnTools)
  146. EVT_MENU(MENU_COMPILE_DICT_DEBUG, MyFrame::OnTools)
  147. EVT_MENU(MENU_COMPILE_MBROLA, MyFrame::OnTools)
  148. EVT_MENU(MENU_CLOSE_ALL, MyFrame::OnQuit)
  149. EVT_MENU(MENU_QUIT, MyFrame::OnQuit)
  150. EVT_MENU(MENU_SPEAK_TRANSLATE, MyFrame::OnSpeak)
  151. EVT_MENU(MENU_SPEAK_RULES, MyFrame::OnSpeak)
  152. EVT_MENU(MENU_SPEAK_TEXT, MyFrame::OnSpeak)
  153. EVT_MENU(MENU_SPEAK_FILE, MyFrame::OnSpeak)
  154. EVT_MENU(MENU_SPEAK_STOP, MyFrame::OnSpeak)
  155. EVT_MENU(MENU_SPEAK_PAUSE, MyFrame::OnSpeak)
  156. EVT_MENU(MENU_SPEAK_VOICE, MyFrame::OnSpeak)
  157. EVT_MENU(MENU_SPEAK_VOICE_VARIANT, MyFrame::OnSpeak)
  158. EVT_MENU(MENU_LOAD_WAV, MyFrame::OnTools)
  159. EVT_MENU(MENU_VOWELCHART1, MyFrame::OnTools)
  160. EVT_MENU(MENU_VOWELCHART2, MyFrame::OnTools)
  161. EVT_MENU(MENU_VOWELCHART3, MyFrame::OnTools)
  162. EVT_MENU(MENU_LEXICON_RU, MyFrame::OnTools)
  163. EVT_MENU(MENU_LEXICON_DE, MyFrame::OnTools)
  164. EVT_MENU(MENU_TO_UTF8, MyFrame::OnTools)
  165. EVT_MENU(MENU_COUNT_WORDS, MyFrame::OnTools)
  166. EVT_MENU(MENU_TEST, MyFrame::OnTools)
  167. EVT_MENU(MENU_TEST2, MyFrame::OnTools)
  168. EVT_TIMER(1, MyFrame::OnTimer)
  169. EVT_SIZE(MyFrame::OnSize)
  170. EVT_SASH_DRAGGED_RANGE(ID_WINDOW_TOP, ID_WINDOW_BOTTOM, MyFrame::OnSashDrag)
  171. END_EVENT_TABLE()
  172. MyFrame::MyFrame(wxWindow *parent, const wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size,
  173. const long style):
  174. wxMDIParentFrame(parent, id, title, pos, size, style)
  175. {//===================================================================================================================
  176. // Main Frame constructor
  177. int error_flag = 0;
  178. int result;
  179. int param;
  180. // Create some dummy layout windows
  181. wxSashLayoutWindow *win;
  182. // Another window to the left of the client window
  183. win = new wxSashLayoutWindow(this, ID_WINDOW_LEFT2,
  184. wxDefaultPosition, wxSize(200, 30),
  185. wxNO_BORDER | wxSW_3D | wxCLIP_CHILDREN);
  186. win->SetDefaultSize(wxSize(208, 1000));
  187. win->SetOrientation(wxLAYOUT_VERTICAL);
  188. win->SetAlignment(wxLAYOUT_LEFT);
  189. // win->SetBackgroundColour(wxColour(0, 255, 255));
  190. win->SetSashVisible(wxSASH_RIGHT, TRUE);
  191. m_leftWindow2 = win;
  192. notebook = new wxNotebook(m_leftWindow2,-1);
  193. // notebook->AddPage(voicedlg,_T("Voice"),FALSE);
  194. formantdlg = new FormantDlg(notebook);
  195. notebook->AddPage(formantdlg,_T(" Spect"),FALSE);
  196. voicedlg = new VoiceDlg(notebook);
  197. transldlg = new TranslDlg(notebook);
  198. notebook->AddPage(transldlg,_T("Text"),TRUE);
  199. ConfigInit();
  200. WavegenInitSound();
  201. if((result = LoadPhData()) != 1)
  202. {
  203. if(result == -1)
  204. wxLogError(_T("Failed to load phoneme data,\nneeds espeak-data/phontab,phondata,phonindex\nPath = ")+wxString(path_home,wxConvLocal));
  205. else
  206. wxLogError(_T("Wrong version of espeak-data: 0x%x (expects 0x%x)"),result,version_phdata);
  207. error_flag = 1;
  208. }
  209. f_trans = stdout;
  210. option_ssml = 1;
  211. // if(LoadVoice(voice_name,0) == NULL)
  212. if(SetVoiceByName(voice_name2) != EE_OK)
  213. {
  214. if(error_flag==0)
  215. wxLogError(_T("Failed to load voice data"));
  216. strcpy(dictionary_name,"en");
  217. }
  218. else
  219. {
  220. SetVoiceTitle(voice_name2);
  221. }
  222. WavegenSetVoice(voice);
  223. for(param=0; param<N_SPEECH_PARAM; param++)
  224. param_stack[0].parameter[param] = param_defaults[param];
  225. SetParameter(espeakRATE,option_speed,0);
  226. SetSpeed(3);
  227. SynthesizeInit();
  228. InitSpectrumDisplay();
  229. InitProsodyDisplay();
  230. // InitWaveDisplay();
  231. espeak_ListVoices(NULL);
  232. m_timer.SetOwner(this,1);
  233. m_timer.Start(500); /* 0.5 timer */
  234. } // end of MyFrame::MyFrame
  235. void MyFrame::SetVoiceTitle(char *voice_name)
  236. {//==========================================
  237. SetTitle(AppName + _T(" - ") + wxString(voice_name,wxConvLocal) + _T(" voice"));
  238. }
  239. void MyFrame::OnKey(wxKeyEvent& event)
  240. {
  241. int key;
  242. key = event.GetKeyCode();
  243. if((currentcanvas != NULL) && (currentcanvas != FindFocus()))
  244. {
  245. if((key == WXK_F1) || (key == WXK_F2))
  246. {
  247. currentcanvas->OnKey(event);
  248. currentcanvas->SetFocus();
  249. return;
  250. }
  251. }
  252. event.Skip();
  253. }
  254. void MyFrame::OnTimer(wxTimerEvent &event)
  255. //****************************************
  256. {
  257. SynthOnTimer();
  258. }
  259. void MyFrame::OnQuit(wxCommandEvent& event)
  260. {
  261. switch(event.GetId())
  262. {
  263. case MENU_QUIT:
  264. Close(TRUE);
  265. break;
  266. case MENU_CLOSE_ALL:
  267. break;
  268. }
  269. }
  270. void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
  271. {//===================================================
  272. char buf[300];
  273. sprintf(buf,about_string,espeak_Info(NULL));
  274. (void)wxMessageBox(wxString(buf,wxConvLocal), _T("About eSpeak Editor"));
  275. }
  276. void OnOptions2(int event_id)
  277. {//==========================
  278. wxString string;
  279. int value;
  280. switch(event_id)
  281. {
  282. case MENU_PARAMS:
  283. value = wxGetNumberFromUser(_T(""),_T(""),_T("Speed"),option_speed,80,390);
  284. if(value > 0)
  285. {
  286. option_speed = value;
  287. SetParameter(espeakRATE,option_speed,0);
  288. SetSpeed(3);
  289. }
  290. break;
  291. case MENU_PATH0:
  292. string = wxFileSelector(_T("Master phonemes file"),wxFileName(path_phfile).GetPath(),
  293. _T(""),_T(""),_T("*"),wxOPEN);
  294. if(!string.IsEmpty())
  295. {
  296. path_phfile = string;
  297. }
  298. break;
  299. case MENU_PATH1:
  300. string = wxDirSelector(_T("Phoneme source directory"),path_phsource);
  301. if(!string.IsEmpty())
  302. {
  303. path_phsource = string;
  304. }
  305. break;
  306. case MENU_PATH2:
  307. string = wxDirSelector(_T("Dictionary source directory"),path_dictsource);
  308. if(!string.IsEmpty())
  309. {
  310. path_dictsource = string;
  311. }
  312. break;
  313. case MENU_PATH3:
  314. string = wxFileSelector(_T("Sound output file"),wxFileName(path_speech).GetPath(),
  315. _T(""),_T("WAV"),_T("*"),wxSAVE);
  316. if(!string.IsEmpty())
  317. {
  318. path_speech = string;
  319. }
  320. break;
  321. case MENU_PATH4:
  322. string = wxFileSelector(_T("Voice file to modify formant peaks"),wxFileName(path_speech).GetPath(),
  323. _T(""),_T(""),_T("*"),wxOPEN);
  324. if(!string.IsEmpty())
  325. {
  326. path_modifiervoice = string;
  327. }
  328. break;
  329. }
  330. ConfigSetPaths();
  331. }
  332. void MyFrame::OnOptions(wxCommandEvent& event)
  333. {//===========================================
  334. OnOptions2(event.GetId());
  335. }
  336. void MyFrame::OnTools(wxCommandEvent& event)
  337. {//=========================================
  338. int err;
  339. FILE *log;
  340. int debug_flag=0;
  341. char fname_log[sizeof(path_dsource)+12];
  342. char err_fname[sizeof(path_home)+15];
  343. switch(event.GetId())
  344. {
  345. case MENU_TEST:
  346. TestTest(0);
  347. break;
  348. case MENU_TEST2:
  349. TestTest(2);
  350. break;
  351. case MENU_TO_UTF8:
  352. ConvertToUtf8();
  353. break;
  354. case MENU_COUNT_WORDS:
  355. MakeWordFreqList();
  356. break;
  357. case MENU_LEXICON_RU:
  358. case MENU_LEXICON_DE:
  359. CompareLexicon(event.GetId()); // Compare a lexicon with _rules translation
  360. break;
  361. case MENU_COMPILE_PH:
  362. CompileInit();
  363. break;
  364. case MENU_COMPILE_MBROLA:
  365. CompileMbrola();
  366. break;
  367. case MENU_COMPILE_DICT_DEBUG:
  368. debug_flag =1; // and drop through to next case
  369. case MENU_COMPILE_DICT:
  370. sprintf(fname_log,"%s%s",path_dsource,"dict_log");
  371. log = fopen(fname_log,"w");
  372. if((err = CompileDictionary(path_dsource,dictionary_name,log,err_fname,debug_flag)) < 0)
  373. {
  374. wxLogError(_T("Can't access file:\n")+wxString(err_fname,wxConvLocal));
  375. wxString dir = wxDirSelector(_T("Directory containing dictionary files"),path_dictsource);
  376. if(!dir.IsEmpty())
  377. {
  378. path_dictsource = dir;
  379. strncpy0(path_dsource,path_dictsource.mb_str(wxConvLocal),sizeof(path_dsource)-1);
  380. strcat(path_dsource,"/");
  381. }
  382. break;
  383. }
  384. wxLogStatus(_T("Compiled '")+wxString(dictionary_name,wxConvLocal)+_T("', %d errors"),err);
  385. if(log != NULL)
  386. fclose(log);
  387. break;
  388. case MENU_VOWELCHART1:
  389. MakeVowelLists();
  390. break;
  391. case MENU_VOWELCHART2:
  392. VowelChart(2,NULL);
  393. break;
  394. case MENU_VOWELCHART3:
  395. VowelChart(3,NULL);
  396. break;
  397. case MENU_LOAD_WAV:
  398. // LoadWavFile();
  399. break;
  400. }
  401. }
  402. extern wxMenu *speak_menu;
  403. void MyFrame::OnSpeak(wxCommandEvent& event)
  404. {//=========================================
  405. switch(event.GetId())
  406. {
  407. case MENU_SPEAK_TRANSLATE:
  408. case MENU_SPEAK_RULES:
  409. case MENU_SPEAK_TEXT:
  410. transldlg->OnCommand(event);
  411. break;
  412. case MENU_SPEAK_FILE:
  413. transldlg->SpeakFile();
  414. break;
  415. case MENU_SPEAK_STOP:
  416. SpeakNextClause(NULL,NULL,2);
  417. break;
  418. case MENU_SPEAK_PAUSE:
  419. SpeakNextClause(NULL,NULL,3);
  420. if(SynthStatus() & 2)
  421. speak_menu->SetLabel(MENU_SPEAK_PAUSE,_T("&Resume"));
  422. else
  423. speak_menu->SetLabel(MENU_SPEAK_PAUSE,_T("&Pause"));
  424. break;
  425. case MENU_SPEAK_VOICE:
  426. transldlg->ReadVoice(0);
  427. SetVoiceTitle(voice_name2);
  428. break;
  429. case MENU_SPEAK_VOICE_VARIANT:
  430. transldlg->ReadVoice(1);
  431. SetVoiceTitle(voice_name2);
  432. break;
  433. }
  434. }
  435. void MyFrame::OnSashDrag(wxSashEvent& event)
  436. {
  437. if (event.GetDragStatus() == wxSASH_STATUS_OUT_OF_RANGE)
  438. return;
  439. switch (event.GetId())
  440. {
  441. case ID_WINDOW_TOP:
  442. {
  443. m_topWindow->SetDefaultSize(wxSize(1000, event.GetDragRect().height));
  444. break;
  445. }
  446. case ID_WINDOW_LEFT1:
  447. {
  448. m_leftWindow1->SetDefaultSize(wxSize(event.GetDragRect().width, 1000));
  449. break;
  450. }
  451. case ID_WINDOW_LEFT2:
  452. {
  453. m_leftWindow2->SetDefaultSize(wxSize(event.GetDragRect().width, 1000));
  454. break;
  455. }
  456. case ID_WINDOW_BOTTOM:
  457. {
  458. m_bottomWindow->SetDefaultSize(wxSize(1000, event.GetDragRect().height));
  459. break;
  460. }
  461. }
  462. wxLayoutAlgorithm layout;
  463. layout.LayoutMDIFrame(this);
  464. // Leaves bits of itself behind sometimes
  465. GetClientWindow()->Refresh();
  466. }
  467. void MyFrame::OnSize(wxSizeEvent& WXUNUSED(event))
  468. {
  469. wxLayoutAlgorithm layout;
  470. layout.LayoutMDIFrame(this);
  471. }
  472. // Note that SASHTEST_NEW_WINDOW and SASHTEST_ABOUT commands get passed
  473. // to the parent window for processing, so no need to
  474. // duplicate event handlers here.
  475. BEGIN_EVENT_TABLE(MyChild, wxMDIChildFrame)
  476. EVT_MENU(SPECTSEQ_CLOSE, MyChild::OnQuit)
  477. EVT_MENU(SPECTSEQ_SAVE, MyChild::OnSave)
  478. EVT_MENU(SPECTSEQ_SAVEAS, MyChild::OnSaveAs)
  479. EVT_MENU(SPECTSEQ_SAVESELECT, MyChild::OnSaveSelect)
  480. EVT_MENU(SPECTSEQ_SAVEPITCH, MyChild::OnSavePitchenv)
  481. EVT_ACTIVATE(MyChild::OnActivate)
  482. END_EVENT_TABLE()
  483. MyChild::MyChild(wxMDIParentFrame *parent, const wxString& title, const wxPoint& pos, const wxSize& size,
  484. const long style):
  485. wxMDIChildFrame(parent, -1, title, pos, size, style)
  486. {
  487. canvas = NULL;
  488. my_children.Append(this);
  489. }
  490. MyChild::~MyChild(void)
  491. {
  492. wxWindow *w;
  493. canvas = NULL;
  494. #ifndef PLATFORM_WINDOWS
  495. // bug in wxMDIChildFrame, we need to explicitly remove the ChildFrame from the ClientWindow
  496. w = myframe->GetClientWindow();
  497. w->RemoveChild(this);
  498. #endif
  499. my_children.DeleteObject(this);
  500. }
  501. //extern void CloseCanvas();
  502. void MyChild::OnQuit(wxCommandEvent& WXUNUSED(event))
  503. {
  504. Destroy();
  505. }
  506. void MyChild::OnSave(wxCommandEvent& WXUNUSED(event))
  507. {
  508. canvas->Save(canvas->savepath);
  509. }
  510. void MyChild::OnSaveAs(wxCommandEvent& WXUNUSED(event))
  511. {
  512. canvas->Save();
  513. }
  514. void MyChild::OnSaveSelect(wxCommandEvent& WXUNUSED(event))
  515. {
  516. canvas->Save(_T(""),1);
  517. }
  518. void MyChild::OnSavePitchenv(wxCommandEvent& WXUNUSED(event))
  519. {
  520. canvas->SavePitchenv(canvas->spectseq->pitchenv);
  521. }
  522. void MyChild::OnActivate(wxActivateEvent& event)
  523. {
  524. if(canvas)
  525. canvas->OnActivate(event.GetActive());
  526. }