/*************************************************************************** * 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 "wx/wx.h" #include "wx/numdlg.h" #include "speak_lib.h" #include "main.h" #include "speech.h" #include "phoneme.h" #include "synthesize.h" #include "prosodydisplay.h" #include "translate.h" extern MyFrame *myframe; extern ChildFrProsody *prosodyframe; extern ProsodyDisplay *prosodycanvas; wxMenu *menu_prosody; BEGIN_EVENT_TABLE(ProsodyDisplay, wxScrolledWindow) EVT_LEFT_DOWN(ProsodyDisplay::OnMouse) EVT_RIGHT_DOWN(ProsodyDisplay::OnMouse) EVT_CHAR(ProsodyDisplay::OnKey) EVT_MENU(-1, ProsodyDisplay::OnMenu) END_EVENT_TABLE() static wxPen PEN_PITCHENV(wxColour(0,0,255),1,wxSOLID); static wxPen PEN_SAMPLED(wxColour(0,200,200),2,wxSOLID); static wxPen PEN_PHSELECTED(wxColour(255,0,0),2,wxSOLID); static wxPen PEN_PHSTRESSED(wxColour(80,80,196),3,wxSOLID); static wxPen PEN_PHSTRESSED2(wxColour(160,160,255),2,wxSOLID); ProsodyDisplay::ProsodyDisplay(wxWindow *parent, const wxPoint& pos, const wxSize& size) : wxScrolledWindow(parent, -1, pos, size, wxSUNKEN_BORDER | wxNO_FULL_REPAINT_ON_RESIZE) {//===================================================================== linewidth = size.GetWidth(); scalex = 0.5; scaley = double(LINESEP*6)/150.0; selected_ph = -1; } // end of ProsodyDisplay::ProsodyDisplay ProsodyDisplay::~ProsodyDisplay() {//========================== prosodycanvas = NULL; } extern MNEM_TAB envelope_names[]; void InitProsodyDisplay() {//====================== wxMenu *menu_envelopes; int ix; wxString string; menu_envelopes = new wxMenu; // entries match those in envelope_data[] in intonation.cpp for(ix=0; envelope_names[ix].mnem != NULL; ix++) { string = wxString(envelope_names[ix].mnem, wxConvLocal); menu_envelopes->Append(0x100+envelope_names[ix].value, string); } #ifdef deleted menu_envelopes->Append(0x100,_T("fall")); menu_envelopes->Append(0x102,_T("rise")); menu_envelopes->Append(0x104,_T("fall-rise")); // menu_envelopes->Append(0x105,_T("fall-rise (R)")); menu_envelopes->Append(0x106,_T("fall-rise 2")); // menu_envelopes->Append(0x107,_T("fall-rise 2(R)")); menu_envelopes->Append(0x108,_T("rise-fall")); menu_envelopes->Append(0x10a,_T("fall-rise 3")); menu_envelopes->Append(0x10c,_T("fall-rise 4")); menu_envelopes->Append(0x10e,_T("fall 2")); menu_envelopes->Append(0x110,_T("rise 2")); menu_envelopes->Append(0x112,_T("rise-fall-rise")); #endif menu_prosody = new wxMenu; menu_prosody->Append(1,_T("Pitch envelope"),menu_envelopes); menu_prosody->Append(2,_T("Amplitude")); menu_prosody->Append(3,_T("Length")); menu_prosody->Append(4,_T("Play F2")); } void ProsodyDisplay::RefreshLine(int line) {//===================================== int x,y; CalcScrolledPosition(0,line*FRAMEHEIGHT,&x,&y); RefreshRect(wxRect(0,y,linewidth,FRAMEHEIGHT)); } int ProsodyDisplay::GetWidth(PHONEME_LIST *p) {//======================================== int w; if(p->ph == NULL) return(0); w = (p->ph->std_length * 2); if(w == 0) w = 60; if(p->length != 0) w = (w * p->length) / 256; return(int((w + p->prepause)* scalex) + 1); } void ProsodyDisplay::SelectPh(int index) {//===================================== // A phoneme has been selected PHONEME_LIST *p; const char *emphasized; int y1, y2; int ix; const char *name = "?"; char buf[120]; if(index < 0) return; p = &phlist[index]; if((p == NULL) || (p->ph == NULL)) return; emphasized = ""; if(p->stresslevel & 8) emphasized = "*"; for(ix=0; envelope_names[ix].mnem != NULL; ix++) { if(envelope_names[ix].value == (p->env & 0xfe)) { name = envelope_names[ix].mnem; break; } } y1 = p->pitch1; y2 = p->pitch2; sprintf(buf,"Stress %s%d Amp %2d LenMod %2d Pitch %3d %3d %s PhFlags %.2x ", emphasized,p->stresslevel&0x7,p->amp,p->length,y1,y2,name,p->ph->phflags); wxLogStatus(wxString(buf,wxConvLocal)); } void ProsodyDisplay::ChangePh(int pitch1, int pitch2) {//================================================ PHONEME_LIST *p; int sign1; int sign2; if(selected_ph < 0) return; p = &phlist[selected_ph]; sign1 = p->pitch1 - p->pitch2; p->pitch1 += pitch1; p->pitch2 += (pitch1 + pitch2); sign2 = p->pitch1 - p->pitch2; if((sign1 != 0) && ((sign1 * sign2) <= 0)) { // change of sign, change rise to fall if(p->env == 1) p->env = 0; else if(p->env == 0) p->env = 1; } } void ProsodyDisplay::OnMenu(wxCommandEvent& event) {//=============================================== int id; int value; PHONEME_LIST *p; id = event.GetId(); p = &phlist[selected_ph]; if((id & 0xf00) == 0x100) { // selected a pitch envelope p->env = id - 0x100; } switch(id) { case 2: value = wxGetNumberFromUser(_T(""),_T("Amplitude"),_T(""),p->amp,0,40); if(value >= 0) p->amp = value; break; case 3: value = wxGetNumberFromUser(_T(""),_T("Length"),_T(""),p->length,1,500); if(value >= 0) p->length = value; break; case 4: MakeWave2(phlist,numph); break; } SelectPh(selected_ph); Refresh(); } void ProsodyDisplay::OnMouse(wxMouseEvent& event) {//============================================ int line; int ix; int xpos=0; wxClientDC dc(this); PrepareDC(dc); wxPoint pt(event.GetLogicalPosition(dc)); if(selected_ph >= 0) { // find line for previously selected phoneme for(line=0; line selected_ph) break; RefreshLine(line); selected_ph = -1; } line = pt.y / FRAMEHEIGHT; // find which phoneme is selected on this line for(ix=linetab[line]; (ix pt.x) { selected_ph = ix; SelectPh(selected_ph); break; } } RefreshLine(line); if(event.RightDown()) { PopupMenu(menu_prosody); } } // end of ProsodyDisplay::OnMouse void ProsodyDisplay::OnKey(wxKeyEvent& event) {//======================================== PHONEME_LIST *p; int display=1; if(selected_ph < 0) selected_ph = 0; p = &phlist[selected_ph]; switch(event.GetKeyCode()) { case WXK_F2: // make and play from this clause MakeWave2(phlist,numph); break; case WXK_LEFT: if(selected_ph > 1) selected_ph--; break; case WXK_RIGHT: if(selected_ph < (numph-2)) selected_ph++; break; case WXK_UP: if(event.ControlDown()) ChangePh(-1,2); else ChangePh(1,0); display = 1; break; case WXK_DOWN: if(event.ControlDown()) ChangePh(1,-2); else ChangePh(-1,0); break; case ',': case '<': if(p->length > 0) p->length--; break; case '.': case '>': p->length++; break; case WXK_TAB: display = 0; event.Skip(); transldlg->SetFocus(); break; default: display = 0; event.Skip(); break; } if(display) { Refresh(); SelectPh(selected_ph); } } // end of ProsodyDisplay::OnKey void ProsodyDisplay::DrawEnv(wxDC& dc, int x1, int y1, int width, PHONEME_LIST *ph) {//============================================================================== int pitchr; int pitch; int p1; int ix; int x,y; int y2=0; unsigned char *env; PHONEME_DATA phdata_tone; if(width <= 0) return; if((pitchr = ph->pitch2 - ph->pitch1) < 0) { pitchr = -pitchr; p1 = ph->pitch2; } else { p1 = ph->pitch1; } if(p1 == 255) return; dc.SetPen(PEN_PITCHENV); env = envelope_data[ph->env]; if((ph->type == phVOWEL) && (ph->tone_ph != 0)) { // the envelope is given by a Tone phoneme acting on this vowel InterpretPhoneme2(ph->tone_ph, &phdata_tone); env = GetEnvelope(phdata_tone.pitch_env); } if(env == NULL) return; for(ix=0; ix<=width; ix+=4) { x = int((ix * 127.9)/width); pitch = p1 + (pitchr * env[x])/256; y = y1-int(pitch * scaley); if(ix > 0) dc.DrawLine(x1+ix-4,y2,x1+ix,y); y2 = y; } } // end of DrawEnv static void GetPhonemeName(PHONEME_TAB *ph, wxString& string) {//========================================================== string = wxString(WordToString(ph->mnemonic),wxConvLocal); } void ProsodyDisplay::DrawPitchline(wxDC& dc, int line, int x1, int x2) {//================================================================= int ix; int endix; int y; int offy; int xpos; int width; // total width, including pre-pause int width2; // width without pre-pause int width_env; int textwidth, textheight; wxString string; PHONEME_LIST *p; if(linetab[line] >= numph) return; offy = (line+1) * FRAMEHEIGHT - 1; y = LINEBASE+LINESEP; dc.SetPen(*wxLIGHT_GREY_PEN); for(ix=0; ix<5; ix++) { dc.DrawLine(0,offy-y,linewidth,offy-y); y += LINESEP; } endix = linetab[line+1]; xpos = 0; for(ix=linetab[line]; ix numph-2) break; if(xpos > x2) break; // past the redraw region p = &phlist[ix]; width = GetWidth(p); width2 = width - int(p->prepause * scalex) - 1; if(xpos+width < x1) { xpos += width; continue; // before the redraw region } // is this a stressed vowel ? if((p->type == phVOWEL) && (p->stresslevel >= 2)) { if(p->stresslevel >= 4) dc.SetPen(PEN_PHSTRESSED); else dc.SetPen(PEN_PHSTRESSED2); dc.DrawLine(xpos,offy-LINEBASE-1,xpos+width,offy-LINEBASE-1); } // is this phoneme selected ? if(ix == selected_ph) { dc.SetPen(PEN_PHSELECTED); dc.DrawLine(xpos,offy-LINEBASE,xpos+width,offy-LINEBASE); } // draw separator bar if(p->newword) dc.SetPen(*wxBLACK_PEN); // word boundary else dc.SetPen(*wxLIGHT_GREY_PEN); dc.DrawLine(xpos,offy-LINEBASE,xpos,offy-LINEBASE-LINESEP*6); // draw pitch envelope if(((p->ph->phflags & phWAVE) == 0) && (p->ph->type != phPAUSE)) { if(!(p->synthflags & SFLAG_SEQCONTINUE)) { width_env = width2; if(phlist[ix+1].synthflags & SFLAG_SEQCONTINUE) width_env += GetWidth(&phlist[ix+1]); DrawEnv(dc,xpos+1+(width-width2),offy-LINEBASE,width_env,p); } } else if(p->type != phPAUSE) { // sampled sound, draw a flat line dc.SetPen(PEN_SAMPLED); dc.DrawLine(xpos+1+(width-width2),offy-LINEBASE-LINESEP, xpos+1+width,offy-LINEBASE-LINESEP); } // show phoneme name from the PHONEME_TAB GetPhonemeName(p->ph,string); dc.GetTextExtent(string,&textwidth,&textheight); dc.DrawText(string,xpos+(width-textwidth/2)/2, offy-LINEBASE); xpos += width; } // draw the separator bar at the end of the line if(ix==endix && ix= linewidth) { linetab[num_lines++] = ix; xpos = 0; } xpos += w; } linetab[num_lines]=numph-2; SetScrollbars(SCROLLUNITS,SCROLLUNITS,linewidth/SCROLLUNITS, (num_lines*FRAMEHEIGHT)/SCROLLUNITS+1); Refresh(); } // end of ProsodyDisplay::LayoutData void MyFrame::OnProsody(wxCommandEvent& WXUNUSED(event)) {//===================================================== // Open the Prosody display window // Make another frame, containing a canvas if(prosodyframe != NULL) { // The Prosody window is already open prosodyframe->Activate(); return; } prosodyframe = new ChildFrProsody(myframe, _T(""), wxPoint(10, 200), wxSize(1000, 300), wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE); prosodyframe->SetTitle(_T("Prosody")); // Give it a status line prosodyframe->CreateStatusBar(); int width, height; wxMDIClientWindow *clientwin = this->GetClientWindow(); clientwin->GetClientSize(&width, &height); #ifdef deleted wxPanel *panel = new wxPanel(prosodyframe,-1,wxPoint(0,0), wxSize(width,50)); ProsodyDisplay *canvas = new ProsodyDisplay(prosodyframe, wxPoint(0, 50), wxSize(width-2, height-50)); #else ProsodyDisplay *canvas = new ProsodyDisplay(prosodyframe, wxPoint(0, 50), wxSize(width-10, height)); #endif prosodycanvas = canvas; // Associate the menu bar with the frame prosodyframe->SetMenuBar(MakeMenu(2)); prosodyframe->prosodycanvas = canvas; prosodyframe->Show(TRUE); } BEGIN_EVENT_TABLE(ChildFrProsody, wxMDIChildFrame) EVT_MENU(SPECTSEQ_CLOSE, ChildFrProsody::OnQuit) // EVT_ACTIVATE(ChildFrProsody::OnActivate) END_EVENT_TABLE() extern wxList my_children; ChildFrProsody::ChildFrProsody(wxMDIParentFrame *parent, const wxString& title, const wxPoint& pos, const wxSize& size, const long style): wxMDIChildFrame(parent, -1, title, pos, size, style) { my_children.Append(this); } ChildFrProsody::~ChildFrProsody(void) { wxWindow *w; my_children.DeleteObject(this); prosodycanvas = NULL; prosodyframe = NULL; #ifndef PLATFORM_WINDOWS // bug in wxMDIChildFrame, we need to explicitly remove the ChildFrame from the ClientWindow w = myframe->GetClientWindow(); w->RemoveChild(this); #endif } void ChildFrProsody::OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(TRUE); }