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.

vowelchart.cpp 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. /***************************************************************************
  2. * Copyright (C) 2005 to 2013 by Jonathan Duddington *
  3. * email: [email protected] *
  4. * Copyright (C) 2013-2015 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 <math.h>
  21. #include "wx/wx.h"
  22. #include <wx/dcmemory.h>
  23. #include <wx/dc.h>
  24. #include <wx/bitmap.h>
  25. #include <wx/dirdlg.h>
  26. #include "wx/filename.h"
  27. #include "wx/wfstream.h"
  28. #include "speak_lib.h"
  29. #include "main.h"
  30. #include "speech.h"
  31. #include "phoneme.h"
  32. #include "synthesize.h"
  33. #include "voice.h"
  34. #include "spect.h"
  35. #include "translate.h"
  36. #include "options.h"
  37. /* Read a file of vowel symbols and f1,f2 formants, and produce a vowel diagram
  38. */
  39. extern wxString path_phsource;
  40. extern char *phondata_ptr;
  41. extern USHORT *phoneme_index;
  42. extern int n_phoneme_tables;
  43. // size of the vowelchart png
  44. #define WIDTH 1580
  45. #define HEIGHT 800
  46. #define ROUND(x) ((int) ((x) + 0.5))
  47. static int HslValue (double n1, double n2, double hue)
  48. {//===================================================
  49. double value;
  50. if (hue > 255)
  51. hue -= 255;
  52. else if (hue < 0)
  53. hue += 255;
  54. if (hue < 42.5)
  55. value = n1 + (n2 - n1) * (hue / 42.5);
  56. else if (hue < 127.5)
  57. value = n2;
  58. else if (hue < 170)
  59. value = n1 + (n2 - n1) * ((170 - hue) / 42.5);
  60. else
  61. value = n1;
  62. return ROUND (value * 255.0);
  63. }
  64. /**
  65. * @hue: Hue channel, returns Red channel
  66. * @saturation: Saturation channel, returns Green channel
  67. * @lightness: Lightness channel, returns Blue channel
  68. *
  69. * The arguments are pointers to int, with the values pointed to in the
  70. * following ranges: H [0, 360], L [0, 255], S [0, 255].
  71. *
  72. * The function changes the arguments to point to the RGB value
  73. * corresponding, with the returned values all in the range [0, 255].
  74. **/
  75. void HslToRgb (int *hue, int *saturation, int *lightness)
  76. {//======================================================
  77. double h, s, l;
  78. h = (*hue * 256)/360;
  79. s = *saturation;
  80. l = *lightness;
  81. if (s == 0)
  82. {
  83. /* achromatic case */
  84. *hue = (int)l;
  85. *lightness = (int)l;
  86. *saturation = (int)l;
  87. }
  88. else
  89. {
  90. double m1, m2;
  91. if (l < 128)
  92. m2 = (l * (255 + s)) / 65025.0;
  93. else
  94. m2 = (l + s - (l * s) / 255.0) / 255.0;
  95. m1 = (l / 127.5) - m2;
  96. /* chromatic case */
  97. *hue = HslValue (m1, m2, h + 85);
  98. *saturation = HslValue (m1, m2, h);
  99. *lightness = HslValue (m1, m2, h - 85);
  100. }
  101. }
  102. static int vowel_posn[N_PHONEME_TAB];
  103. static int vowel_posn_ix;
  104. static double log2a(double x)
  105. {//========================
  106. // log2(x) = log(x) / log(2)
  107. return(log(x) / 0.693147);
  108. }
  109. static int VowelX(int f2)
  110. {//======================
  111. return(WIDTH - int((log2a(f2) - 9.40)*WIDTH/1.9));
  112. // return(WIDTH - int((log2a(f2) - 9.49)*WIDTH/1.8));
  113. }
  114. static int VowelY(int f1)
  115. {//======================
  116. return(int((log2a(f1) - 7.85)*HEIGHT/2.15));
  117. }
  118. static int VowelZ(int f3)
  119. {//======================
  120. int z;
  121. // range 2000-3000Hz, log2= 10.96 to 11.55
  122. z = int((log2a(f3) - 11.05)*256/0.50);
  123. if(z < 0) z = 0;
  124. if(z > 255) z = 255;
  125. return(z);
  126. }
  127. static void DrawVowel(wxDC *dc, wxString name, int f1, int f2, int f3, int g1, int g2)
  128. {//==================================================================================
  129. int ix;
  130. int posn;
  131. int collisions;
  132. int x,y,z,x2,y2;
  133. int r,g,b;
  134. wxBrush brush;
  135. y = VowelY(f1);
  136. x = VowelX(f2);
  137. z = VowelZ(f3);
  138. if(y < 0) y = 0;
  139. if(y > (HEIGHT-4)) y= (HEIGHT-4);
  140. if(x < 0) x = 0;
  141. if(x > (WIDTH-12)) x = (WIDTH-12);
  142. r = z;
  143. g = 255;
  144. b = 100 + z/2;
  145. HslToRgb(&r,&g,&b);
  146. brush.SetColour(r,g,b);
  147. dc->SetBrush(brush);
  148. dc->DrawCircle(x,y,4);
  149. // check for a label already at this position
  150. collisions = 0;
  151. posn = (x/8)*WIDTH + (y/8);
  152. for(ix=0; ix<vowel_posn_ix; ix++)
  153. {
  154. if(posn == vowel_posn[ix])
  155. collisions++;
  156. }
  157. vowel_posn[vowel_posn_ix++] = posn;
  158. dc->DrawText(name,x+4,y+(collisions*10));
  159. if(g2 != 0xffff)
  160. {
  161. y2 = VowelY(g1);
  162. x2 = VowelX(g2);
  163. dc->DrawLine(x,y,x2,y2);
  164. }
  165. }
  166. static int VowelChartDir(wxDC *dc, wxBitmap *bitmap)
  167. {//=================================================
  168. int ix;
  169. int nf;
  170. int count = 0;
  171. SpectSeq *spectseq;
  172. SpectFrame *frame1;
  173. SpectFrame *frame2=NULL;
  174. wxFileName filename;
  175. wxString dir = wxDirSelector(_T("Directory of vowel files"),path_phsource);
  176. if(dir.IsEmpty()) return(0);
  177. wxString path = wxFindFirstFile(dir+_T("/*"),wxFILE);
  178. while (!path.empty())
  179. {
  180. if((spectseq = new SpectSeq) == NULL) break;
  181. filename = wxFileName(path);
  182. wxFileInputStream stream(path);
  183. if(stream.Ok() == FALSE)
  184. {
  185. path = wxFindNextFile();
  186. delete spectseq;
  187. continue;
  188. }
  189. spectseq->Load(stream);
  190. nf = 0;
  191. frame1 = NULL;
  192. if(spectseq->numframes > 0)
  193. {
  194. frame2 = spectseq->frames[0];
  195. }
  196. for(ix=0; ix<spectseq->numframes; ix++)
  197. {
  198. if(spectseq->frames[ix]->keyframe)
  199. {
  200. nf++;
  201. frame2 = spectseq->frames[ix];
  202. if(frame2->markers & FRFLAG_VOWEL_CENTRE)
  203. frame1 = frame2;
  204. }
  205. }
  206. if((nf >= 3) && (frame1 != NULL))
  207. {
  208. DrawVowel(dc,wxString(filename.GetName()),
  209. frame1->peaks[1].pkfreq, frame1->peaks[2].pkfreq, frame1->peaks[3].pkfreq,
  210. frame2->peaks[1].pkfreq, frame2->peaks[2].pkfreq);
  211. count++;
  212. }
  213. delete spectseq;
  214. path = wxFindNextFile();
  215. }
  216. filename.SetPath(dir);
  217. filename.SetFullName(_T("vowelchart.png"));
  218. bitmap->SaveFile(filename.GetFullPath(),wxBITMAP_TYPE_PNG);
  219. return(count);
  220. }
  221. static int VowelChartList(wxDC *dc, wxBitmap *bitmap, char *fname)
  222. {//===============================================================
  223. // Plot a graph of vowel formants.
  224. // y-axis is decreasing f1 (closeness)
  225. // x-axis is decreasing f2 (backness)
  226. FILE *f_in;
  227. int ix;
  228. int f1,f2,f3,g1,g2;
  229. int colour;
  230. int count=0;
  231. wxFileName filename;
  232. char name[40];
  233. char buf[200];
  234. wxString path;
  235. if(fname != NULL)
  236. {
  237. path = wxString(fname,wxConvLocal);
  238. }
  239. else
  240. {
  241. path = wxFileSelector(_T("Read file of vowel formants"),path_phsource,
  242. _T(""),_T(""),_T("*"),wxOPEN);
  243. }
  244. if(path.IsEmpty())
  245. {
  246. return(0);
  247. }
  248. filename = wxFileName(path);
  249. strcpy(buf,path.mb_str(wxConvLocal));
  250. f_in = fopen(buf,"r");
  251. if(f_in == NULL)
  252. {
  253. wxLogError(_T("Can't read file: %s"),buf);
  254. return(0);
  255. }
  256. while(fgets(buf,sizeof(buf),f_in) != NULL)
  257. {
  258. g2 = 0xffff;
  259. ix = sscanf(buf,"%s %d %d %d %d %d %d",name,&colour,&f1,&f2,&f3,&g1,&g2);
  260. if(ix >= 4)
  261. {
  262. if(colour == 1)
  263. {
  264. dc->SetPen(*wxMEDIUM_GREY_PEN);
  265. dc->SetTextForeground(wxColour(100,100,128));
  266. }
  267. else
  268. {
  269. dc->SetPen(*wxBLACK_PEN);
  270. dc->SetTextForeground(*wxBLACK);
  271. }
  272. DrawVowel(dc,wxString(name,wxConvLocal),
  273. f1,f2,f3,g1,g2);
  274. count++;
  275. }
  276. }
  277. filename.SetExt(_T("png"));
  278. bitmap->SaveFile(filename.GetFullPath(),wxBITMAP_TYPE_PNG);
  279. fclose(f_in);
  280. return(count);
  281. }
  282. void VowelChart(int control, char *fname)
  283. {//======================================
  284. // Plot a graph of vowel formants.
  285. // y-axis is decreasing f1 (closeness)
  286. // x-axis is decreasing f2 (backness)
  287. // control=1 from directory of lists
  288. // control=2 from single list
  289. // control=3 from directory of phoneme source data files
  290. int ix;
  291. int x,y;
  292. int count;
  293. wxFileName filename;
  294. wxBitmap bitmap(WIDTH,HEIGHT);
  295. // Create a memory DC
  296. wxMemoryDC dc;
  297. dc.SelectObject(bitmap);
  298. dc.SetBrush(*wxWHITE_BRUSH);
  299. dc.SetFont(*wxSWISS_FONT);
  300. dc.Clear();
  301. // draw grid
  302. dc.SetPen(*wxLIGHT_GREY_PEN);
  303. for(ix=200; ix<=1000; ix+=50)
  304. {
  305. y = VowelY(ix);
  306. dc.DrawLine(0,y,WIDTH,y);
  307. if((ix % 100) == 0)
  308. dc.DrawText(wxString::Format(_T("%d"),ix),1,y);
  309. }
  310. for(ix=700; ix<=2400; ix+=100)
  311. {
  312. x = VowelX(ix);
  313. dc.DrawLine(x,0,x,HEIGHT);
  314. if((ix % 200)==0)
  315. dc.DrawText(wxString::Format(_T("%d"),ix),x+1,0);
  316. }
  317. dc.SetPen(*wxBLACK_PEN);
  318. vowel_posn_ix = 0;
  319. if(control==3)
  320. count = VowelChartDir(&dc, &bitmap);
  321. else
  322. count = VowelChartList(&dc, &bitmap, fname);
  323. dc.SetTextForeground(*wxBLACK);
  324. if(control != 1)
  325. wxLogStatus(_T("Plotted %d vowels"),count);
  326. }
  327. void FindPhonemesUsed(void)
  328. {//========================
  329. int hash;
  330. char *p;
  331. unsigned int *pw;
  332. char *next;
  333. unsigned char c;
  334. int count = 0;
  335. int ignore;
  336. char phonetic[N_WORD_PHONEMES];
  337. // look through all the phoneme strings in the **_rules data
  338. // and mark these phoneme codes as used.
  339. p = translator->data_dictrules;
  340. while(*p != 0)
  341. {
  342. if(*p == RULE_CONDITION)
  343. p+=2;
  344. if(*p == RULE_LINENUM)
  345. p+=3;
  346. if(*p == RULE_GROUP_END)
  347. {
  348. p++;
  349. if(*p == 0) break;
  350. }
  351. if(*p == RULE_GROUP_START)
  352. {
  353. if(p[1] == RULE_REPLACEMENTS)
  354. {
  355. p++;
  356. pw = (unsigned int *)(((long)p+4) & ~3); // advance to next word boundary
  357. while(pw[0] != 0)
  358. {
  359. pw += 2; // find the end of the replacement list, each entry is 2 words.
  360. }
  361. p = (char *)(pw+1);
  362. continue;
  363. }
  364. if(p[1] == RULE_LETTERGP2)
  365. {
  366. while(*p != RULE_GROUP_END) p++;
  367. continue;
  368. }
  369. p += (strlen(p)+1);
  370. }
  371. while((c = *p) != 0)
  372. {
  373. if(c == RULE_CONDITION)
  374. p++; // next byte is the condition number, which may be 3 (= RULE_PHONEMES)
  375. if(c == RULE_PHONEMES)
  376. break;
  377. p++;
  378. }
  379. count++;
  380. if(c == RULE_PHONEMES)
  381. {
  382. ignore = 0;
  383. p++;
  384. while((c = *p) != 0)
  385. {
  386. if(c == phonSWITCH)
  387. ignore = 1;
  388. if(ignore == 0)
  389. phoneme_tab_flags[c] |= 2;
  390. p++;
  391. }
  392. }
  393. p++;
  394. }
  395. // NOTE, we should recognise langopts.textmode and ignore the *_list file (lang=zh)
  396. for(hash=0; hash<N_HASH_DICT; hash++)
  397. {
  398. p = translator->dict_hashtab[hash];
  399. if(p == NULL)
  400. continue;
  401. while(*p != 0)
  402. {
  403. next = p + p[0];
  404. if((p[1] & 0x80) == 0)
  405. {
  406. p += ((p[1] & 0x3f) + 2);
  407. strcpy(phonetic,p);
  408. p += strlen(phonetic) +1;
  409. // examine flags
  410. ignore = 0;
  411. while(p < next)
  412. {
  413. if(*p == BITNUM_FLAG_TEXTMODE)
  414. {
  415. ignore = 1;
  416. break;
  417. }
  418. p++;
  419. }
  420. if(ignore == 0)
  421. {
  422. p = phonetic;
  423. while((c = *p) != 0)
  424. {
  425. if(c == phonSWITCH)
  426. break;
  427. phoneme_tab_flags[c] |= 2;
  428. p++;
  429. }
  430. }
  431. }
  432. p = next;
  433. }
  434. }
  435. } // end of FindPhonemesUsed
  436. #define N_VOWELFMT_ADDR 20
  437. int n_vowelfmt_addr;
  438. int vowelfmt_addr[N_VOWELFMT_ADDR]; // FMT() statements found in a phoneme definition
  439. static void FindVowelFmt(int prog_start, int length)
  440. {//=================================================
  441. USHORT *prog;
  442. USHORT instn;
  443. int prog_end;
  444. prog_end = prog_start + length;
  445. n_vowelfmt_addr = 0;
  446. for(prog = &phoneme_index[prog_start]; prog < &phoneme_index[prog_end]; prog += NumInstnWords(prog))
  447. {
  448. instn = *prog;
  449. if((instn >> 12) == 11)
  450. {
  451. // FMT instruction
  452. if(n_vowelfmt_addr < N_VOWELFMT_ADDR)
  453. {
  454. vowelfmt_addr[n_vowelfmt_addr++] = ((instn & 0xf) << 18) + (prog[1] << 2);
  455. }
  456. }
  457. }
  458. } // end of FindVowelFmt
  459. static int prog_log_sorter(PHONEME_PROG_LOG *p1, PHONEME_PROG_LOG *p2)
  460. {//===================================================================
  461. return(p1->addr - p2->addr);
  462. }
  463. void MakeVowelLists(void)
  464. {//======================
  465. // For each phoneme table, make a list of its vowels and their
  466. // formant frequencies (f1,f2,f3) for use by VowelChart()
  467. int table;
  468. int ix;
  469. int phcode;
  470. PHONEME_TAB *ph;
  471. FILE *f;
  472. FILE *f_prog_log;
  473. SPECT_SEQ *seq;
  474. SPECT_SEQK *seqk;
  475. frame_t *frame;
  476. int n_prog_log;
  477. int vowelfmt_ix;
  478. int colour;
  479. int voice_found;
  480. PHONEME_PROG_LOG *prog_log_table;
  481. PHONEME_PROG_LOG *found_prog;
  482. PHONEME_PROG_LOG this_prog;
  483. char dirname[sizeof(path_source)+20];
  484. char fname[sizeof(dirname)+40];
  485. char save_voice_name[80];
  486. strcpy(save_voice_name,voice_name2);
  487. sprintf(fname,"%s%s",path_source,"compile_prog_log");
  488. if((f_prog_log = fopen(fname,"rb")) == NULL)
  489. {
  490. wxLogError(_T("Can't read 'compile_prog_log;"));
  491. return;
  492. }
  493. ix = GetFileLength(fname);
  494. prog_log_table = (PHONEME_PROG_LOG *)malloc(ix);
  495. if(prog_log_table == NULL)
  496. {
  497. fclose(f_prog_log);
  498. return;
  499. }
  500. ix = fread(prog_log_table, 1, ix, f_prog_log);
  501. fclose(f_prog_log);
  502. n_prog_log = ix / sizeof(PHONEME_PROG_LOG);
  503. progress = new wxProgressDialog(_T("Vowel charts"),_T(""),n_phoneme_tables);
  504. sprintf(dirname,"%s%s",path_source,"vowelcharts");
  505. mkdir(dirname,S_IRWXU | S_IRGRP | S_IROTH);
  506. sprintf(fname,"%s/vowel_log",dirname);
  507. for(table=0; table<n_phoneme_tables; table++)
  508. {
  509. sprintf(fname,"%s/%s",dirname,phoneme_tab_list[table].name);
  510. if((f = fopen(fname,"w"))==NULL) continue;
  511. progress->Update(table);
  512. // select the phoneme table by name
  513. // if(SetVoiceByName(phoneme_tab_list[table].name) != 0) continue;
  514. if(SelectPhonemeTableName(phoneme_tab_list[table].name) < 0)
  515. {
  516. fclose(f);
  517. continue;
  518. }
  519. voice_found = 0;
  520. if((LoadVoice(phoneme_tab_list[table].name, 0) != NULL) && (translator->data_dictrules != NULL))
  521. {
  522. voice_found = 1;
  523. FindPhonemesUsed();
  524. }
  525. // phoneme table is terminated by a phoneme with no name (=0)
  526. for(phcode=1; phcode < n_phoneme_tab; phcode++)
  527. {
  528. ph = phoneme_tab[phcode];
  529. if((ph==NULL) || (ph->type != phVOWEL) || (ph->program == 0))
  530. continue;
  531. if(voice_found && (phoneme_tab_flags[phcode] & 3) == 0)
  532. {
  533. continue; // inherited, and not used
  534. }
  535. // find the size of this program
  536. this_prog.addr = ph->program;
  537. found_prog = (PHONEME_PROG_LOG *)bsearch((void *)&this_prog, (void *)prog_log_table, n_prog_log, sizeof(PHONEME_PROG_LOG), (int(*)(const void *,const void *))prog_log_sorter);
  538. FindVowelFmt(ph->program, found_prog->length);
  539. for(vowelfmt_ix=0; vowelfmt_ix < n_vowelfmt_addr; vowelfmt_ix++)
  540. {
  541. ix = vowelfmt_addr[vowelfmt_ix];
  542. seq = (SPECT_SEQ *)(&phondata_ptr[ix]);
  543. seqk = (SPECT_SEQK *)seq;
  544. if(seq->frame[0].frflags & FRFLAG_KLATT)
  545. frame = &seqk->frame[1];
  546. else
  547. frame = (frame_t *)&seq->frame[1];
  548. if((n_vowelfmt_addr - vowelfmt_ix) == 1)
  549. colour = 0;
  550. else
  551. colour = 1;
  552. fprintf(f,"%s\t %d %3d %4d %4d",WordToString(ph->mnemonic), colour,
  553. frame->ffreq[1],frame->ffreq[2],frame->ffreq[3]);
  554. if(seq->frame[0].frflags & FRFLAG_KLATT)
  555. frame = &seqk->frame[seqk->n_frames-1];
  556. else
  557. frame = (frame_t *)&seq->frame[seq->n_frames-1];
  558. fprintf(f," %3d %4d %4d\n",frame->ffreq[1],frame->ffreq[2],frame->ffreq[3]);
  559. }
  560. }
  561. fclose(f);
  562. VowelChart(1,fname); // draw the vowel chart
  563. }
  564. free(prog_log_table);
  565. LoadVoice(voice_name2,0); // reset the original phoneme table
  566. delete progress;
  567. LoadVoiceVariant(save_voice_name,0);
  568. }
  569. #define N_ENVELOPES 30
  570. extern int n_envelopes;
  571. extern char envelope_paths[N_ENVELOPES][80];
  572. extern unsigned char envelope_dat[N_ENVELOPES][ENV_LEN];
  573. #define HT_ENV 140
  574. #define WD_ENV 128*2
  575. void DrawEnvelopes()
  576. {//================
  577. int ix_env;
  578. int y_base;
  579. int x;
  580. FILE *f_txt=NULL;
  581. unsigned char *env;
  582. char name[200];
  583. wxBitmap bitmap(WD_ENV,HT_ENV*n_envelopes);
  584. // Create a memory DC
  585. wxMemoryDC dc;
  586. dc.SelectObject(bitmap);
  587. dc.SetBrush(*wxWHITE_BRUSH);
  588. dc.SetFont(*wxSWISS_FONT);
  589. dc.Clear();
  590. sprintf(name,"%s%s",path_source,"envelopes.txt");
  591. // f_txt = fopen(name,"w");
  592. for(ix_env=0; ix_env<n_envelopes; ix_env++)
  593. {
  594. y_base = HT_ENV * ix_env;
  595. dc.SetPen(*wxLIGHT_GREY_PEN);
  596. dc.DrawLine(0,y_base+0,256,y_base+0);
  597. dc.DrawLine(0,y_base+64,256,y_base+64);
  598. dc.DrawLine(0,y_base+128,256,y_base+128);
  599. dc.DrawLine(128,y_base+0,128,y_base+128);
  600. dc.SetPen(*wxBLACK_PEN);
  601. strncpy0(name,envelope_paths[ix_env],sizeof(name));
  602. dc.DrawText(wxString(name,wxConvLocal),1,y_base);
  603. env = envelope_dat[ix_env];
  604. y_base = y_base+128;
  605. for(x=0; x<127; x++)
  606. {
  607. dc.DrawLine(x*2, y_base-env[x]/2, (x+1)*2, y_base-env[x+1]/2);
  608. }
  609. if(f_txt != NULL)
  610. {
  611. fprintf(f_txt,"%s\n",name);
  612. for(x=0; x<128; x++)
  613. {
  614. fprintf(f_txt," 0x%.2x,",env[x]);
  615. if((x & 0xf) == 0xf)
  616. fputc('\n',f_txt);
  617. }
  618. fputc('\n',f_txt);
  619. }
  620. }
  621. bitmap.SaveFile(path_phsource+_T("/envelopes.png"),wxBITMAP_TYPE_PNG);
  622. if(f_txt != NULL)
  623. fclose(f_txt);
  624. }