http://www.wxwidgets.org/ . Details on how to install the library are included in the download.
#include "wx/wx.h"OnInit()is always declared as virtual. The reason why virtual functions are used is to allow a derived class to override functions in classes it inherits from. For example a playSound() member function in a class SoundUtils may have to create a system sound such a beep. At the same time a class derived from SoundUtils called my_SoundUtils may have to play back a sound file instead of a beep. By declaring the member function as virtual I can create a new function to do this which uses the same name i.e., playSound(). The new playSound() function will therefore have a different functionality from the one in the class it's derived from.
class MyApp: public wxApp{ virtual bool OnInit();};
class MyFrame: public wxFrame{public:MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);}The constructor in this case is passed the text that appears in the main window's title bar, the window dimensions and the X/Y position of the main window. As with all the other wxWidgets classes wxFrame also has lots of member functions such as SetIcon() which lets you set the icon that appears in the top left hand side of the window and SetTransparent() which lets you adjust the transparency of the window. For more details consult the wxWidgets documentation.
class MyFrame: public wxFrame{public:MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);void OnQuit(wxCommandEvent& event);void OnAbout(wxCommandEvent& event);wxMenu *menu;wxMenuBar *menuBar;DECLARE_EVENT_TABLE()};You'll notice that we declared pointers to a wxMenu and wxMenuBar class. We will call the constructors for these classes in our main window constructor, ie MyFrame::MyFrame(). We could have declared these class in the MyFrame constructor but personally I prefer to keep these declarations in the main class definition.
BEGIN_EVENT_TABLE(MyFrame, wxFrame)EVT_MENU(ID_Quit, MyFrame::OnQuit)EVT_MENU(ID_About, MyFrame::OnAbout)END_EVENT_TABLE()BEGIN_EVENT_TABLE(...)is passed two parameters, a user-defined class and the base class from which the user-defined class is derived from. EVT_MENU(...) which is used to process menu commands also takes two arguments, a wxWindowID, and a member function of the class MyFrame. Every event table must end with the END_EVENT_TABLE() macro. As mentioned above each event has an associated predefined macro, EVT_MENU(...) is used for menu commands, EVT_BUTTON(...) is used for buttons clicks, EVT_SLIDER(...) for sliders and so on. For more details please consult the wxWidgets documentation.
MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size): wxFrame((wxFrame *)NULL, -1, title, pos, size){wxMenu *menuFile = new wxMenu;menuFile->Append( ID_About, "&About..." );menuFile->Append( ID_Quit, "E&xit" );wxMenuBar *menuBar = new wxMenuBar;menuBar->Append( menuFile, "&File" );SetMenuBar( menuBar );}In the above constructor we begin by creating an instance of the wxMenu class. We then call one of it's member functions, i.e., Append(...). The append function takes two arguments, one a unique wxWindowID and the other, a string that will appear in the menu.
enum{ID_Quit = 100,ID_About = 200,};Following the two calls to the Append(...) function we declare an instance of a wxMenuBar. We then call its Append function, but this time passing a wxMenu derived class and a string. Next we will call the SetMenuBar(...) function which is a member function of the base class wxFrame. For a full list of wxFrame's data members refer to the wxWidgets documentation.
bool MyApp::OnInit(){//declare an instance of MyFrame and pass arguments to constructorMyFrame *frame = new MyFrame( "Hello World", wxPoint(50,50), wxSize(450,340) );//display the frame by calling Show(TRUE)frame->Show(TRUE);//return true so that our program knows that everything was initialised okreturn TRUE;}wxPoint(...)is a useful function which when passed an X and Y argument returns a wxPoint type so you don't have to keep declaring wxPoint types all over your code. wxSize(...) is similar only it is used for specifying the size of a window.
void MyFrame::OnQuit(wxCommandEvent& event){Close(TRUE);}To display our 'About' information I call the wxMessageBox(..) function which display a little notification box which in time honoured fashion will contain the string "Hello World!". Message boxes are a great way to debug programs as they can easily be used to retrieve important information whenever you're having problems with your program. They can only be passed strings so if you want to print numbers you will need to convert them to strings first. Here is our OnAbout(..) handler:
void MyFrame::OnAbout(wxCommandEvent& event){wxMessageBox("Hello World!");}
IMPLEMENT_APP(MyApp)Now you are ready to build the full application. Here is the complete source code. If you have any compiler problems make sure that your include directories and import libraries are correct.
/***************************** helloworld.cpp *****************************/#include "helloworld.h"IMPLEMENT_APP(MyApp)bool MyApp::OnInit(){ MyFrame *frame = new MyFrame( "Hello World", wxPoint(50,50), wxSize(450,340)); frame->Show(TRUE); return TRUE;} BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(ID_Quit, MyFrame::OnQuit) EVT_MENU(ID_About, MyFrame::OnAbout)END_EVENT_TABLE()MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size): wxFrame((wxFrame *)NULL, -1, title, pos, size){ wxMenu *menuFile = new wxMenu; menuFile->Append( ID_About, "&About..." ); menuFile->Append( ID_Quit, "E&xit" ); wxMenuBar *menuBar = new wxMenuBar; menuBar->Append( menuFile, "&File" ); wxFrame::SetMenuBar( menuBar );}void MyFrame::OnQuit(wxCommandEvent& event){ Close(TRUE);}void MyFrame::OnAbout(wxCommandEvent& event){ wxMessageBox("Hello World!");}/*********************** helloworld.h **************************/#ifndef MYFRAME_H#define MYFRAME_H#include "wx/wx.h" enum{ ID_Quit = 100, ID_About = 200,};class MyApp: public wxApp{ virtual bool OnInit();};class MyFrame: public wxFrame{public: MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size); void OnQuit(wxCommandEvent& event); void OnAbout(wxCommandEvent& event); DECLARE_EVENT_TABLE()};#endif
#include "csound.h"The next step is to define a structure that will contain all the data needed by our performance thread. We also need to declare the performance thread.
struct userData{/*result of csoundCompile()*/int result; /*instance of csound*/CSOUND* csound; /*performance status*/bool PERF_STATUS; };//main csound performance thread.... uintptr_t csThread(void *clientData);
class MyFrame: public wxFrame{public: MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size); ~MyFrame(); void OnQuit(wxCommandEvent& event); void OnAbout(wxCommandEvent& event); void OnOpen(wxCommandEvent& event); void ScrollChange(wxScrollEvent &event); void OnPlay(wxCommandEvent &event); void OnTimer(wxTimerEvent& event); wxMenu *menu; wxMenuBar *menuBar; wxSlider* slider; wxButton* button; wxPanel* panel; userData* ud; wxTextCtrl* label; char** cmdl; void* threadID; protected: DECLARE_EVENT_TABLE()};You may notice that three new event functions have been declared, one for each new control. Take note that the OnScrollChange() function which will handle movements of the slider receives a wxScrollEvent rather than a wxCommandEvent which the others controls receive. Remember that each control must receive a particular type of event, more information on the different events can be found in the wxWidgets documentation.
MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size): wxFrame((wxFrame *)NULL, -1, title, pos, size){ menu = new wxMenu; menu->Append( ID_Open, "&Open" ); menu->Append( ID_About, "&About..." ); menu->Append( ID_Quit, "E&xit" ); menuBar = new wxMenuBar; menuBar->Append( menu, "&File" ); wxFrame::SetMenuBar( menuBar ); panel = new wxPanel(this, -1, wxPoint(0, 0), wxFrame::GetSize()); slider = new wxSlider(panel, ID_Scroll, 0, 0, 100, wxPoint(10, 10), wxSize(200, 30)); button = new wxButton(panel, ID_Play, "Play file", wxPoint(140, 40), wxSize(60, 20)); label = new wxTextCtrl(panel, -2, "0:00:00", wxPoint(10, 40), wxSize(50, 20), wxTE_RICH); ud = (userData *) malloc(sizeof(userData)); //initialise PERF_STATUS to 0 ud->PERF_STATUS = 0;}In order to use these new controls we need to update the event table so that our application knows how to respond to each event that may occur when a user manipulates one of the the GUI controls. This is how our new event table will look.
BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(ID_Open, MyFrame::OnOpen) EVT_MENU(ID_Quit, MyFrame::OnQuit) EVT_MENU(ID_About, MyFrame::OnAbout) EVT_BUTTON(ID_Play, MyFrame::OnPlay) EVT_COMMAND_SCROLL ( ID_Scroll, MyFrame::ScrollChange)END_EVENT_TABLE()Again take note of how each of the different controls uses a different event macro. In this case button uses the EVT_BUTTON macro while slider uses the EVT_COMMAND_SCROLL macro.
/*** soundfile.csd ***/<CsoundSynthesizer><CsInstruments> /*soundfile.csd*/ sr = 44100kr = 4410 ksmps = 10nchnls = 1instr 1Sname strget 1a1 soundin Snameout a1endin</CsInstruments><CsScore>f1 0 4096 10 1 i1 0 100e </CsScore></CsoundSynthesizer>One can start the instrument form the command line like this:
csound soundfile.csd --strset1=D:/MyDocuments/Old/voice.wavNow that we can easily pass a sound file to my Csound instrument we're ready to finish implementing the OnOpen handler. As you can see from the code below I am passing the full path and name of the selected sound file as a command line argument to my instance of Csound. I'm also using a wxString data type to handle my strings although any string library or even simple char arrays could also be used.
void MyFrame::OnOpen(wxCommandEvent& event){ /*only allow users to select file when performance thread has stopped*/ if(!ud->PERF_STATUS){ /*char array to hld command line flags*/ char** cmdl; /*allocate enough memory to hold three command line parameters*/ cmdl = (char **) malloc(sizeof(char*)*(3)); /*call wxFileDialog constructor*/ wxFileDialog *dlg = new wxFileDialog(this, "Open a text file", "", "", "All files(*.*)|*.*|Text Files(*.txt)|*.txt", wxOPEN, wxDefaultPosition); /* only enter test if user presses Ok if the user were to press cancel instead ShowModal() would return wxID_CANCEL*/ if ( dlg->ShowModal() == wxID_OK ) { /*get file name, GetPath retrieves name and path of selected file and append --strset to the start of it*/ wxString filestring = "--strset1="+dlg->GetPath(); /*create instance of Csound*/ ud->csound=csoundCreate(NULL); /*set command line flags*/ cmdl[0] = "csound"; cmdl[1] = "soundfile.csd"; cmdl[2] = (char*)filestring.ToAscii(); ud->result=csoundCompile(ud->csound,3,cmdl); } dlg->Destroy(); delete cmdl; } else wxMessageBox("Please stop the audio first");}
void MyFrame::OnPlay(wxCommandEvent& event){ if(ud->PERF_STATUS==0) { if(!ud->result) { ud->PERF_STATUS=1; threadID = csoundCreateThread(csThread, (void*)ud); button->SetLabel("Pause"); } }else{ ud->PERF_STATUS = 0; button->SetLabel("Play"); }}The csThread function called by csoundCreateThread() is shown in full at the end of the article. More details can be found in an "Introduction to using the Csound Host API".
void MyFrame::OnPlay(wxCommandEvent& event){MYFLT* pvalue; if(ud->PERF_STATUS==0) { if(!ud->result) { ud->PERF_STATUS=1; threadID = csoundCreateThread(csThread, (void*)ud); /*set range of slider*/ if(csoundGetChannelPtr(ud->csound, &pvalue, "length", CSOUND_OUTPUT_CHANNEL | CSOUND_CONTROL_CHANNEL)==0) { slider->SetRange(0, (int)*pvalue); } button->SetLabel("Pause"); } }else{ ud->PERF_STATUS = 0; button->SetLabel("Play"); }}The reason why it's important to get the length of the sound file is to ensure that when our slider is set to it's mid position our application will reflect this by starting playback from the middle of the sound file. In order to do this we need to rewrite our Csound code so that the play position of the the sound file can be changed while performing.
/*** soundfile.csd ***/<CsoundSynthesizer> <CsOptions>-odac -b10</CsOptions><CsInstruments>sr = 44100kr = 4410ksmps = 10nchnls = 2 ;//length of soundfilegiflen init 0;instr 1;//retrieve file string form command line Sname strget 1giflen filelen Sname /*send value of length to host on channel "lenght"*/chnset giflen, "length"/*start instr 2 with duration giflen*/ event_i "i", 2, 0, giflenendininstr 2ifirst = 0Sname strget 1/*retrieve number of audio channels in fileso that we can add support for mono and stereo*/ichnls filenchnls Sname/*retrieve slider position from host application*/ksearch chnget "skip"/*set position to start playback fromksearch chnget "skip"*/ktrig changed ksearch reset:k1 line 0, p3, p3 iskip = i(ksearch); if(ktrig==0) then goto continelse/*reinitialise instrument every time a user changes the slider*/reinit reset endifcontin: if(ichnls==2) then a1, a2 soundin Sname, iskip outs a1, a2 elseif(ichnls==1) then a1 soundin Sname, iskip outs a1, a1 endif rireturn endin</CsInstruments><CsScore>i1 0 1</CsScore></CsoundSynthesizer>The ScrollChange event handler will look like this:
void MyFrame::ScrollChange(wxScrollEvent& event){MYFLT* pvalue; /*send position of slider to Csound on channel "skip"*/ if(csoundGetChannelPtr(ud->csound, &pvalue, "skip", CSOUND_INPUT_CHANNEL | CSOUND_CONTROL_CHANNEL)==0) *pvalue = (MYFLT)slider->GetValue(); }
ktime = k1+iskip;/*output time to our host*/chnset ktime, "time"if(ktime>giflen) thenchnset 0, "time"event "e", 2, 0, 0endifThe above code will output the elapsed performance time to our host application a channel named "time". I have also added a simple test to turn off the instrument once ktime goes above giflen. We turn the instrument off using the event opcode which sends scores events from within a Csound instrument. More details of this and other the opcodes mentioned can be found in the Csound documentation.
EVT_TIMER(ID_Timer, MyFrame::OnTimer)In this case OnTimer is the event handler for our timer function. In the OnTimer handler we need to call csoundGetChannelPtr() to retrieve the value being transmitted on channel "time". We also need to arrange this data so that it appears in the standard hrs:min:sec format. A few simple if/else statement will cover this. To set the value of our text control we can use its SetValue() method.