Introduction

With the release of the Csound host API programmers now have a powerful toolkit for the development of sophisticated audio software at their disposal. The following article will explore some features of the Csound API by taking the users through the complete development process of a soundfile player complete with a file-open menu and playback scrubber. Users are advised to check out my "Introduction to using the Csound Host API" prior to proceeding with this article as it explains in much more depth some of Csound API functions which I will use in the examples that follow. It also describes how to create an import library so one can build Csound API applications with MinGW. Apart from the Csound API, users who wish to follow the examples in this article will also need to download and install wxWidgets, the GUI toolkit used to provide the graphical interface to our simple Csound host application. wxWidgets can be downloaded from http://www.wxwidgets.org/ . Details on how to install the library are included in the download. 

I. wxWidgets

wxWidgets is an Application Programming Interface(API) for writing cross-platform Graphical User Interface (GUI) applications. Apart from being a GUI toolkit wxWidgets also provides great tools for I/O streams, drag and drop, multithreading, image loading and saving, HTML viewing and printing, and much more. The following section we will explore some basic wxWidgets concepts such as the main application classes and event handling. From these basic concepts a basic 'hello world' application will be implemented. In section II we will see how easily a simple wxWidgets application can be modified to add support for the Csound API. As this article is more concerned with the Csound API it will not provide an extensive introduction to wxWidgets. For a more in-depth overview of the wxWidgets library please visit the wxWidgets homepage.

The wxWidgets application class

Every wxWidgets application defines an application class deriving from wxApp. It's only instantiated once and this one instance handles the running of the application. Every application you write will also need an OnInit() function that's called when wxWidgets is ready to start running your application. This OnInit() function is similar to a main() function in C. Here is a minimal application class declaration:

#include "wx/wx.h"

class MyApp: public wxApp { virtual bool OnInit(); };
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.

When OnInit() is called it will normally create at least one window, interpret any command-line arguments, set up data for the application, and perform any other initialisation tasks required for the application.

If the function returns true wxWidgets starts the event loop that processes user input and runs event handlers as necessary. If the function returns false wxWidgets will clean up its internal structures, and the application will terminate. You can see an example of a minimal OnInit() function later in this section.

The wxFrame class

While the main application is derived from the wxApp class, the main GUI window, is created by deriving a class from wxFrame. This main window will be resizeable and will hold all the GUI components such as buttons and sliders. We declare our wxFrame derived class like this:
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.

Event Handlers

Any class that needs to respond to an "event" must declare an event table using the DECLARE_EVENT_TABLE(). By event I mean things like mouse clicks, menu messages, scrollbar changes, etc. Applications respond to such events through "handlers", which are unique functions that react to certain events. In our example, we will create two menu buttons. As there are two menu commands we need to specify two handlers, OnQuit(...) and OnAbout(...). When these handlers are called they pass information about the control which triggered them to the wxCommandEvent data type. These unique event handlers will be member functions of the wxFrame derived class, MyFrame.
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.

The wxWidgets event table

In order for an application to react to a menu command, controls must be given a unique identifier such as a const or an enum. These unique identifiers are known as wxWindowID's and are an important aspect of how the event handling is processed in wxWidgets. We then need to write an event table in which the events are routed to their respective handler functions which are defined in the class MyFrame, i.e., OnQuit(...), and OnAbout(...).For example we will want the OnExit() function to be called whenever a user presses the Exit menu command.

There are predefined macros for routing all common events and it is important to specify the correct event for each control otherwise you will get errors when you build your application. Here is the event table for our simple application.
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.

The MyFrame class constructor

Before we begin to define our member functions we must first implement the MyFrame constructor. It is here in this constructor for the main window that we create our application menu which consists of two different items, "About" and "Exit".
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.

We've seen the wxWindowID type before. A wxWindowID is basically a const int and any GUI control that can trigger an event must have one so that the event table will know which function to call when it is triggered. wxWidgets has many reserved ID's but users can also specify their own unique ID's using the enum keyword like this:
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.

Luckily all wxWidgets controls are automatically destroyed when the top level window is destroyed. Therefore we don't have to write a deconstructor to free our controls as this is done automatically.

The OnInit() function

As mentioned earlier every wxWidget application must have an OnInit() function. The OnInit() function for our basic application looks like this:
bool MyApp::OnInit()
{
//declare an instance of MyFrame and pass arguments to constructor
MyFrame *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 ok
return 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.

The event handlers

In order for the file menu commands to work we need to write the event handlers, i.e., the functions that get called whenever a user triggers an event such as a button press or a file menu command. MyFrame::OnQuit() closes the main window by calling Close(), a member function of the wxWindow class, the base class for all GUI controls. The parameter TRUE indicates that other windows have no say in whether or not the window will stay open.

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!");
}

Putting it all together

There is only one thing left to do and that is to create an instance of wxApp. wxWidgets does this internally, but you still need to tell wxWidgets what kind of object to create. Therefore you need to add the IMPLEMENT_APP macro and pass it the user-defined class MyApp which I derived from the base class wxApp like this:

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

II. Adding Csound support

Now that the basic shell of the application is complete we are ready to add some Csound functionality to it. The first thing we need to do is to add the relevant include directories to our project and make sure that the linker knows to link to our import library. Details on how to do this can be found in my "Introduction to using the Csound Host API". Once your project settings are edited you will have to the following include directive to your header file:

#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);

Next we will need to modify the existing class definition by adding some controls which will be used to communicate with our Csound instrument. As we are going to to build a sound file player we will need a play button, a slider that can be moved to scrub through the audio playback, a text control to display the elapsed time in the performance and a panel which will hold all the controls. We also need to add a pointer to our data structure userData and a pointer to type void for our Thread ID. This is how our new class definition will look:
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.

In our main window class constructor we will add a new menu command, 'Open', which will allow users to browse for sound files on their computer. We also need to call the constructors for the button, panel, slider and text control classes as well as allocate memory for the userData variable. Details of the class constructors for each class can again 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.

The OnOpen handler

Next we need to define our event functions. We'll start with the 'OnOpen' event which will be called every time a user selects open from the file menu. This event will create a file browser dialog so that users can browse for a sound file on their computer. Once they have selected a file an instance of Csound will be created. Finally Csound will be compiled so that it is ready to start performing. Of course it will not start performing until the user presses the 'Play' button which we'll see later.

To let users browse for files we will use the wxFileDialog class. After calling the constructor for this class we need to call the ShowModal member method so that the browser dialog appears. To retrieve the name of the selected file we will use the GetPath() member function which returns the name and path of the selected file.

At this stage of the development process it's important that we understand how to pass strings to a Csound instrument, otherwise how will Csound know which sound file to play? In Csound the easiest way of passing a name of a sound file to an instrument is to pass it as a string argument from the command line. We can then retrieve the name of the file in our Csound instrument using the strget opcode. Here is a simple example of how to use this opcode to retrieve string values from the command line:

/*** soundfile.csd ***/

<CsoundSynthesizer>
<CsInstruments> 
/*soundfile.csd*/     
sr = 44100
kr = 4410     
ksmps = 10
nchnls = 1

instr 1
Sname strget 1
a1 soundin Sname
out a1
endin
</CsInstruments>

<CsScore>
f1 0 4096 10 1  
i1 0 100
e   
</CsScore>
</CsoundSynthesizer>
One can start the instrument form the command line like this:
csound soundfile.csd --strset1=D:/MyDocuments/Old/voice.wav 
Now 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");
}

The OnPlay handler

Next we'll implement our OnPlay event. This function will be called when a users hits the 'Play' button. From within this function we need to start our Csound performance thread. The OnPlay event can be defined as this:

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".

The 'Play' button will act as a pause when the user hits it while a file is playing. Although we don't have the slider event added yet this application should compile and build without any problems so long as

The ScrollChange handler

The ScrollChange event will be called every time a user changes the slider position. The first thing we need to do in this handler is to make sure that Csound is running. I can do this by checking the status of ud->PERF_STATUS at the start of the event. Following this we need to set the maximum range of our slider to the length of the sound file. To do this we are going to need to find out the length of the sound file. Luckily for us Csound can do this so we don't have to. Even still we will need to instruct Csound to send this information to the host application which we can do by using one of the channel opcodes.

I am going to need to use following opcodes in my Csound instrument so that we can communicate with the host application:


The Csound API function that I will use to retrieve data on a named channel is csoundGetChannelPtr(), details on this function can be found in an "Introduction to using the Csound Host API".

Once the host application has retrieved the length of the file we can call SetRange(), a member method of wxSlider that can dynamically set the sliders range according to the length of the sound file. We will do this just after we start the performance thread in our OnPlay event like this:
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 = 44100
kr = 4410
ksmps = 10
nchnls = 2 

;//length of soundfile
giflen init 0;

instr 1
;//retrieve file string form command line 
Sname strget 1
giflen  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, giflen
endin

instr 2
ifirst = 0
Sname  strget  1

/*retrieve number of audio channels in file
so 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 from
ksearch chnget "skip"*/
ktrig changed ksearch 

reset:
k1 line 0, p3, p3  
iskip = i(ksearch);             
if(ktrig==0) then 
goto contin
else

/*reinitialise instrument every time 
a user changes the slider*/
reinit reset 

endif
contin: 
        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(); 
} 

The finishing touches

The only remaining issue to be tackled is how to display the elapsed performance time in our text control. In order to do this we can implement a timer function that will retrieve the elapsed time from our Csound instrument every N milliseconds. This means that we will need to output the elapsed time from our Csound instrument and send it to our host application on a channel named "time". To this we can add the following line just after my contin: label.
ktime = k1+iskip;
/*output time to our host*/
chnset ktime, "time"
if(ktime>giflen) then
chnset 0, "time"
event "e", 2, 0, 0
endif
The 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.

wxWidgets comes with its own timer class, wxTimer which can be used to monitor the time data being sent on channel "time" from our Csound instrument. This class is similar to other GUI classes in that it too needs to be added to the event table, the syntax looks like this:

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.

Any instance of a wxTimer class must be started and stopped manually, it does not start automatically. To do this we use the Start() and Stop() member methods of the wxTimer class. Start() accepts a value which will determine how frequently the OnTimer handler will be called. For example passing 500 will indicate that OnTimer should be called every 500 milliseconds.

Just as we did in the ScrollChange handler, we will add a test at the start of the OnTime handler to see if Csound is performing. If Csound is performing we will proceed with displaying the current time. If Csound has finished playing we will reset Csound and recompile so that the score returns to the beginning.

The full source code and DevC++ project file for the sample application can be found here. There is also a basic makefile included but note that both the Dev-C++ project file and basic makefile will need to be modified to so that all the paths are correct. The finished application will look something like this:

Conclusion

From my limited exploration of the Csound API I have started to appreciate the power of not only the host API but the actual Csound language itself. When first starting to develop host applications I tried all manners and means of controlling my Csound instruments from the host applications themselves. After a few weeks of this however and with the help of the people on the Csound mailing list I started to see that a lot of the program control could be handled directly by Csound, thus illuminating the need to call API functions when not absolutely necessary. In fact the more host applications I write the less I find myself knee high in C++ code but knee high in Csound code instead. I now find myself editing my Csound code in order to change the behaviour of my host applications. As a result my modus operandi has completely changed. It used to be hours spent coding in C/C++, now its time spent working on the more important musical aspects of my Csound code, knowing that I can quickly test my instruments at any time.

Essentially what all this means is that any host application is only as powerful as the underlying Csound code which it runs. Encouraging news to the hundreds of Csound users who only possess a rudimentary knowledge of programming languages, for even with these basic skills they can now start to develop their own high-end cross-platform audio software.

Acknowledgements

I would like to thank all the developers who helped to create the Csound host API, without which this article would not exist. I would also like to express a sincere thanks to everyone on the Csound mailing lists in particular Victor Lazzarini, Istvan Varga, John ffitch and Michael Gogins who have been most patient and understanding in dealing with my constant deluge of API related questions.