/*************************************************************************** * Copyright (C) 2005 to 2007 by Jonathan Duddington * * email: jonsd@users.sourceforge.net * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write see: * * . * ***************************************************************************/ #include #include "wx/wx.h" #include #include #include #include #include "wx/filename.h" #include "wx/wfstream.h" #include "speak_lib.h" #include "main.h" #include "speech.h" #include "phoneme.h" #include "synthesize.h" #include "voice.h" #include "spect.h" #include "translate.h" #include "options.h" /* Read a file of vowel symbols and f1,f2 formants, and produce a vowel diagram */ extern wxString path_phsource; extern char *spects_data; extern int n_phoneme_tables; extern PHONEME_TAB_LIST phoneme_tab_list[N_PHONEME_TABS]; // size of the vowelchart png #define WIDTH 1580 #define HEIGHT 800 #define ROUND(x) ((int) ((x) + 0.5)) static int HslValue (double n1, double n2, double hue) {//=================================================== double value; if (hue > 255) hue -= 255; else if (hue < 0) hue += 255; if (hue < 42.5) value = n1 + (n2 - n1) * (hue / 42.5); else if (hue < 127.5) value = n2; else if (hue < 170) value = n1 + (n2 - n1) * ((170 - hue) / 42.5); else value = n1; return ROUND (value * 255.0); } /** * @hue: Hue channel, returns Red channel * @saturation: Saturation channel, returns Green channel * @lightness: Lightness channel, returns Blue channel * * The arguments are pointers to int, with the values pointed to in the * following ranges: H [0, 360], L [0, 255], S [0, 255]. * * The function changes the arguments to point to the RGB value * corresponding, with the returned values all in the range [0, 255]. **/ void HslToRgb (int *hue, int *saturation, int *lightness) {//====================================================== double h, s, l; h = (*hue * 256)/360; s = *saturation; l = *lightness; if (s == 0) { /* achromatic case */ *hue = (int)l; *lightness = (int)l; *saturation = (int)l; } else { double m1, m2; if (l < 128) m2 = (l * (255 + s)) / 65025.0; else m2 = (l + s - (l * s) / 255.0) / 255.0; m1 = (l / 127.5) - m2; /* chromatic case */ *hue = HslValue (m1, m2, h + 85); *saturation = HslValue (m1, m2, h); *lightness = HslValue (m1, m2, h - 85); } } static int vowel_posn[N_PHONEME_TAB]; static int vowel_posn_ix; static double log2a(double x) {//======================== // log2(x) = log(x) / log(2) return(log(x) / 0.693147); } static int VowelX(int f2) {//====================== return(WIDTH - int((log2a(f2) - 9.40)*WIDTH/1.9)); // return(WIDTH - int((log2a(f2) - 9.49)*WIDTH/1.8)); } static int VowelY(int f1) {//====================== return(int((log2a(f1) - 7.85)*HEIGHT/2.15)); } static int VowelZ(int f3) {//====================== int z; // range 2000-3000Hz, log2= 10.96 to 11.55 z = int((log2a(f3) - 11.05)*256/0.50); if(z < 0) z = 0; if(z > 255) z = 255; return(z); } static void DrawVowel(wxDC *dc, wxString name, int f1, int f2, int f3, int g1, int g2) {//================================================================================== int ix; int posn; int collisions; int x,y,z,x2,y2; int r,g,b; wxBrush brush; y = VowelY(f1); x = VowelX(f2); z = VowelZ(f3); if(y < 0) y = 0; if(y > (HEIGHT-4)) y= (HEIGHT-4); if(x < 0) x = 0; if(x > (WIDTH-12)) x = (WIDTH-12); r = z; g = 255; b = 100 + z/2; HslToRgb(&r,&g,&b); brush.SetColour(r,g,b); dc->SetBrush(brush); dc->DrawCircle(x,y,4); // check for a label already at this position collisions = 0; posn = (x/8)*WIDTH + (y/8); for(ix=0; ixDrawText(name,x+4,y+(collisions*10)); if(g2 != 0xffff) { y2 = VowelY(g1); x2 = VowelX(g2); dc->DrawLine(x,y,x2,y2); } } static int VowelChartDir(wxDC *dc, wxBitmap *bitmap) {//================================================= int ix; int nf; int count = 0; SpectSeq *spectseq; SpectFrame *frame1; SpectFrame *frame2=NULL; wxFileName filename; wxString dir = wxDirSelector(_T("Directory of vowel files"),path_phsource); if(dir.IsEmpty()) return(0); wxString path = wxFindFirstFile(dir+_T("/*"),wxFILE); while (!path.empty()) { if((spectseq = new SpectSeq) == NULL) break; filename = wxFileName(path); wxFileInputStream stream(path); if(stream.Ok() == FALSE) { path = wxFindNextFile(); continue; } spectseq->Load(stream); nf = 0; frame1 = NULL; if(spectseq->numframes > 0) { frame2 = spectseq->frames[0]; } for(ix=0; ixnumframes; ix++) { if(spectseq->frames[ix]->keyframe) { nf++; frame2 = spectseq->frames[ix]; if(frame2->markers & FRFLAG_VOWEL_CENTRE) frame1 = frame2; } } if((nf >= 3) && (frame1 != NULL)) { DrawVowel(dc,wxString(filename.GetName()), frame1->peaks[1].pkfreq, frame1->peaks[2].pkfreq, frame1->peaks[3].pkfreq, frame2->peaks[1].pkfreq, frame2->peaks[2].pkfreq); count++; } delete spectseq; path = wxFindNextFile(); } filename.SetPath(dir); filename.SetFullName(_T("vowelchart.png")); bitmap->SaveFile(filename.GetFullPath(),wxBITMAP_TYPE_PNG); return(count); } static int VowelChartList(wxDC *dc, wxBitmap *bitmap, char *fname) {//=============================================================== // Plot a graph of vowel formants. // y-axis is decreasing f1 (closeness) // x-axis is decreasing f2 (backness) FILE *f_in; int ix; int f1,f2,f3,g1,g2; int count=0; wxFileName filename; char name[40]; char buf[200]; wxString path; if(fname != NULL) { path = wxString(fname,wxConvLocal); } else { path = wxFileSelector(_T("Read file of vowel formants"),path_phsource, _T(""),_T(""),_T("*"),wxOPEN); } if(path.IsEmpty()) { return(0); } filename = wxFileName(path); strcpy(buf,path.mb_str(wxConvLocal)); f_in = fopen(buf,"r"); if(f_in == NULL) { wxLogError(_T("Can't read file: %s"),buf); return(0); } while(fgets(buf,sizeof(buf),f_in) != NULL) { g2 = 0xffff; ix = sscanf(buf,"%s %d %d %d %d %d",name,&f1,&f2,&f3,&g1,&g2); if(ix >= 3) { DrawVowel(dc,wxString(name,wxConvLocal), f1,f2,f3,g1,g2); count++; } } filename.SetExt(_T("png")); bitmap->SaveFile(filename.GetFullPath(),wxBITMAP_TYPE_PNG); return(count); } void VowelChart(int control, char *fname) {//====================================== // Plot a graph of vowel formants. // y-axis is decreasing f1 (closeness) // x-axis is decreasing f2 (backness) // control=1 from directory of lists // control=2 from single list // control=3 from directory of phoneme source data files int ix; int x,y; int count; wxFileName filename; wxBitmap bitmap(WIDTH,HEIGHT); // Create a memory DC wxMemoryDC dc; dc.SelectObject(bitmap); dc.SetBrush(*wxWHITE_BRUSH); dc.SetFont(*wxSWISS_FONT); dc.Clear(); // draw grid dc.SetPen(*wxLIGHT_GREY_PEN); for(ix=200; ix<=1000; ix+=50) { y = VowelY(ix); dc.DrawLine(0,y,WIDTH,y); if((ix % 100) == 0) dc.DrawText(wxString::Format(_T("%d"),ix),1,y); } for(ix=700; ix<=2400; ix+=100) { x = VowelX(ix); dc.DrawLine(x,0,x,HEIGHT); if((ix % 200)==0) dc.DrawText(wxString::Format(_T("%d"),ix),x+1,0); } dc.SetPen(*wxBLACK_PEN); vowel_posn_ix = 0; if(control==3) count = VowelChartDir(&dc, &bitmap); else count = VowelChartList(&dc, &bitmap, fname); if(control != 1) wxLogStatus(_T("Plotted %d vowels"),count); } void FindPhonemesUsed(void) {//======================== int hash; char *p; unsigned int *pw; char *start; char *next; unsigned char c; int count = 0; int ignore; char phonetic[N_WORD_PHONEMES]; // look through all the phoneme strings in the **_rules data // and mark these phoneme codes as used. p = translator->data_dictrules; while(*p != 0) { if(*p == RULE_CONDITION) p+=2; if(*p == RULE_LINENUM) p+=3; if(*p == RULE_GROUP_END) { p++; if(*p == 0) break; } if(*p == RULE_GROUP_START) { if(p[1] == RULE_REPLACEMENTS) { p++; pw = (unsigned int *)(((long)p+4) & ~3); // advance to next word boundary while(pw[0] != 0) { pw += 2; // find the end of the replacement list, each entry is 2 words. } p = (char *)(pw+1); continue; } if(p[1] == RULE_LETTERGP2) { while(*p != RULE_GROUP_END) p++; continue; } p += (strlen(p)+1); } while((c = *p) != 0) { if(c == RULE_CONDITION) p++; // next byte is the condition number, which may be 3 (= RULE_PHONEMES) if(c == RULE_PHONEMES) break; p++; } count++; if(c == RULE_PHONEMES) { ignore = 0; start = p; p++; while((c = *p) != 0) { if(c == phonSWITCH) ignore = 1; if(ignore == 0) phoneme_tab_flags[c] |= 2; p++; } } p++; } // NOTE, we should recognise langopts.textmode and ignore the *_list file (lang=zh) for(hash=0; hashdict_hashtab[hash]; if(p == NULL) continue; while(*p != 0) { next = p + p[0]; if((p[1] & 0x80) == 0) { p += ((p[1] & 0x3f) + 2); strcpy(phonetic,p); p += strlen(phonetic) +1; // examine flags ignore = 0; while(p < next) { if(*p == BITNUM_FLAG_TEXTMODE) { ignore = 1; break; } p++; } if(ignore == 0) { p = phonetic; while((c = *p) != 0) { if(c == phonSWITCH) break; phoneme_tab_flags[c] |= 2; p++; } } } p = next; } } } // end of FindPhonemesUsed void MakeVowelLists(void) {//====================== // For each phoneme table, make a list of its vowels and their // formant frequencies (f1,f2,f3) for use by VowelChart() int table; int ix; int phcode; PHONEME_TAB *ph; FILE *f; SPECT_SEQ *seq; SPECT_SEQK *seqk; frame_t *frame; int match_level; char dirname[sizeof(path_source)+20]; char fname[sizeof(dirname)+40]; progress = new wxProgressDialog(_T("Vowel charts"),_T(""),n_phoneme_tables); sprintf(dirname,"%s%s",path_source,"vowelcharts"); mkdir(dirname,S_IRWXU | S_IRGRP | S_IROTH); for(table=0; tableUpdate(table); // select the phoneme table by name // if(SetVoiceByName(phoneme_tab_list[table].name) != 0) continue; if(SelectPhonemeTableName(phoneme_tab_list[table].name) < 0) continue; FindPhonemesUsed(); // phoneme table is terminated by a phoneme with no name (=0) for(phcode=1; phcode < n_phoneme_tab; phcode++) { //if((phoneme_tab_flags[phcode] & 3) == 0) // continue; // inherited, and not used ph = phoneme_tab[phcode]; if(ph->type != phVOWEL) continue; if((ix = LookupSound(ph, phoneme_tab[phonPAUSE], 1, &match_level, 0)) == 0) continue; seq = (SPECT_SEQ *)(&spects_data[ix]); seqk = (SPECT_SEQK *)seq; if(seq->frame[0].frflags & FRFLAG_KLATT) frame = &seqk->frame[1]; else frame = (frame_t *)&seq->frame[1]; fprintf(f,"%s\t %3d %4d %4d",WordToString(ph->mnemonic), frame->ffreq[1],frame->ffreq[2],frame->ffreq[3]); if(seq->frame[0].frflags & FRFLAG_KLATT) frame = &seqk->frame[seqk->n_frames-1]; else frame = (frame_t *)&seq->frame[seq->n_frames-1]; fprintf(f," %3d %4d %4d\n",frame->ffreq[1],frame->ffreq[2],frame->ffreq[3]); } fclose(f); VowelChart(1,fname); // draw the vowel chart } LoadVoice(voice_name2,0); // reset the original phoneme table delete progress; } extern int n_envelopes; extern char envelope_paths[][80]; extern unsigned char envelope_dat[][128]; #define HT_ENV 140 #define WD_ENV 128*2 void DrawEnvelopes() {//================ int ix_env; int y_base; int x; FILE *f_txt=NULL; unsigned char *env; char name[200]; wxBitmap bitmap(WD_ENV,HT_ENV*n_envelopes); // Create a memory DC wxMemoryDC dc; dc.SelectObject(bitmap); dc.SetBrush(*wxWHITE_BRUSH); dc.SetFont(*wxSWISS_FONT); dc.Clear(); sprintf(name,"%s%s",path_source,"envelopes.txt"); // f_txt = fopen(name,"w"); for(ix_env=0; ix_env