Csound

THE CSOUND API

An application programming interface (API) is an interface provided by a computer system, library or application that allows users to access functions and routines for a particular task. It gives developers a way to harness the functionality of existing software within a host application. The Csound API can be used to control an instance of Csound through a series of different functions thus making it possible to harness all the power of Csound in one’s own applications. In other words, almost anything that can be done within Csound can be done with the API. The API is written in C, but there are interfaces to other languages as well, such as Python, C++ and Java.   

To use the Csound C API, you have to include csound.h in your source file and to link your code with libcsound. Here is an example of the csound command line application written using the Csound C API:

#include <csound/csound.h>

int main(int argc, char **argv)
{
  CSOUND *csound = csoundCreate(NULL);
  int result = csoundCompile(csound, argc, argv);
  if (result == 0) {
    result = csoundPerform(csound);
  }
  csoundDestroy(csound);
  return (result >= 0 ? 0 : result);
}

First we create an instance of Csound. To do this we call csoundCreate() which returns an opaque pointer that will be passed to most Csound API functions. Then we compile the orc/sco files or the csd file given as input arguments through the argv parameter of the main function. If the compilation is successful (result == 0), we call the csoundPerform() function. csoundPerform() will cause Csound to perform until the end of the score is reached. When this happens csoundPerform() returns a non-zero value and we destroy our instance before ending the program.

On a linux system, with libcsound named libcsound64 (double version of the csound library), supposing that all include and library paths are set correctly, we would build the above example with the following command (notice the use of the -DUSE_DOUBLE flag to signify that we compile against the 64 bit version of the csound library):

gcc -DUSE_DOUBLE -o csoundCommand csoundCommand.c -lcsound64

 The command for building with a 32 bit version of the library would be:

gcc -o csoundCommand csoundCommand.c -lcsound

Within the C or C++ examples of this chapter, we will use the MYFLT type for the audio samples. Doing so, the same source files can be used for both development (32 bit or 64 bit), the compiler knowing how to interpret MYFLT as double if the macro USE_DOUBLE is defined, or as float if the macro is not defined.

The C API has been wrapped in a C++ class for convenience. This gives the Csound basic C++ API. With this API, the above example would become:

#include <csound/csound.hpp>

int main(int argc, char **argv)
{
  Csound *cs = new Csound();
  int result = cs->Compile(argc, argv);
  if (result == 0) {
    result = cs->Perform();
  }
  return (result >= 0 ? 0 : result);
}

Here, we get a pointer to a Csound object instead of the csound opaque pointer. We call methods of this object instead of C functions, and we don't need to call csoundDestroy in the end of the program, because the C++ object destruction mechanism takes care of this. On our linux system, the example would be built with the following command:

g++ -DUSE_DOUBLE -o csoundCommandCpp csoundCommand.cpp -lcsound64

The Csound API has also been wrapped to other languages. The Csound Python API wraps the Csound API to the Python language. To use this API, you have to import the csnd module. The csnd module is normally installed in the site-packages or dist-packages directory of your python distribution as a csnd.py file. Our csound command example becomes:

import sys
import csnd

def csoundCommand(args):
    csound = csnd.Csound()
    arguments = csnd.CsoundArgVList()
    for s in args:
        arguments.Append(s)
    result = csound.Compile(arguments.argc(), arguments.argv())
    if result == 0:
        result = csound.Perform()
    return result

def main():
    csoundCommand(sys.argv)

if __name__ =='__main__':
    main()

We use a Csound object (remember Python has OOp features). Note the use of the CsoundArgVList helper class to wrap the program input arguments into a C++ manageable object. In fact, the Csound class has syntactic sugar (thanks to method  overloading) for the Compile method. If you have less than six string arguments to pass to this method, you can pass them directly. But here, as we don't know the number of arguments to our csound command, we use the more general mechanism of the CsoundArgVList helper class.

The Csound Java API wraps the Csound API to the Java language. To use this API, you have to import the csnd package. The csnd package is located in the csnd.jar archive which has to be known from your Java path. Our csound command example becomes:

import csnd.*;

public class CsoundCommand
{
  private Csound csound = null;
  private CsoundArgVList arguments = null;

  public CsoundCommand(String[] args) {
    csound = new Csound();
    arguments = new CsoundArgVList();
    arguments.Append("dummy");
    for (int i = 0; i < args.length; i++) {
      arguments.Append(args[i]);
    }
    int result = csound.Compile(arguments.argc(), arguments.argv());
    if (result == 0) {
      result = csound.Perform();
    }
    System.out.println(result);
  }


  public static void main(String[] args) {
    CsoundCommand csCmd = new CsoundCommand(args);
  }
}

Note the "dummy" string as first argument in the arguments list. C, C++ and Python expect that the first argument in a program argv input array is implicitly the name of the calling program. This is not the case in Java: the first location in the program argv input array contains the first command line argument if any.  So we have to had this "dummy" string value in the first location of the arguments array so that the C API function called by our csound.Compile method is happy.

This illustrates a fundamental point about the Csound API. Whichever API wrapper is used (C++, Python, Java, etc), it is the C API which is working under the hood. So a thorough knowledge of the Csound C API is highly recommended if you plan to use the Csound API in any of its different flavours. The main source of information about the Csound C API is the csound.h header file which is fully commented.

On our linux system, with csnd.jar located in /usr/local/lib/csound/java, our Java Program would be compiled and run with the following commands:

javac -cp /usr/local/lib/csound/java/csnd.jar CsoundCommand.java
java -cp /usr/local/lib/csound/java/csnd.jar:. CsoundCommand

There also exists an extended Csound C++ API, which adds to the Csound C++ API a CsoundFile class, the CsoundAC C++ API, which provides a class hierarchy for doing algorithmic composition using Michael Gogins' concept of music graphs, and API wrappers for the LISP, LUA and HASKELL languages.

For now, in this chapter we will focus on the basic C/C++ API, and the Python and Java API.

Threading

Before we begin to look at how to control Csound in real time we need to look at threads. Threads are used so that a program can split itself into two or more simultaneously running tasks. Multiple threads can be executed in parallel on many computer systems. The advantage of running threads is that you do not have to wait for one part of your software to finish executing before you start another.

In order to control aspects of your instruments in real time your will need to employ the use of threads. If you run the first example found on this page you will see that the host will run for as long as csoundPerform() returns 0. As soon as it returns non-zero it will exit the loop and cause the application to quit. Once called, csoundPerform() will cause the program to hang until it is finished. In order to interact with Csound while it is performing you will need to call csoundPerform() in a separate unique thread. 

When implementing threads using the Csound API, we must define a special performance function thread. We then pass the name of this performance function to csoundCreateThread(), thus registering our performance-thread function with Csound. When defining a Csound performance-thread routine you must declare it to have a return type uintptr_t, hence it will need to return a value when called. The thread function will take only one parameter, a pointer to void. This pointer to void is quite important as it allows us to pass important data from the main thread to the performance thread. As several variables are needed in our thread function the best approach is to create a user defined data structure that will hold all the information your performance thread will need. For example:

typedef struct { 
  int result;        /* result of csoundCompile() */ 
  CSOUND *csound;    /* instance of csound */
  bool PERF_STATUS;  /* performance status */ 
} userData; 

Below is a basic performance-thread routine. *data is cast as a userData data type so that we can access its members. 

uintptr_t csThread(void *data)
{
  userData *udata = (userData *)data;
  if (!udata->result) {
    while ((csoundPerformKsmps(udata->csound) == 0) &&
           (udata->PERF_STATUS == 1));
    csoundDestroy(udata->csound);
  }
  udata->PERF_STATUS = 0;
  return 1;
}          

In order to start this thread we must call the csoundCreateThread() API function which is declared in csound.h as:  

void *csoundCreateThread(uintptr_t (*threadRoutine (void *),
                         void *userdata);  

If you are building a command line program you will need to use some kind of mechanism to prevent int main() from returning until after the performance has taken place. A simple while loop will suffice. 

The first example presented above can now be rewritten to include a unique performance thread:


#include <stdio.h> 
#include <csound/csound.h> 

uintptr_t csThread(void *clientData); 

typedef struct { 
  int result; 
  CSOUND *csound; 
  int PERF_STATUS; 
} userData; 

int main(int argc, char *argv[]) 
{
  int finish;
  void *ThreadID; 
  userData *ud; 
  ud = (userData *)malloc(sizeof(userData));  
  MYFLT *pvalue; 
  csoundInitialize(&argc, &argv, 0);  
  ud->csound = csoundCreate(NULL);  
  ud->result = csoundCompile(ud->csound, argc, argv); 

  if (!ud->result) {  
    ud->PERF_STATUS = 1; 
    ThreadID = csoundCreateThread(csThread, (void *)ud); 
  } 
  else { 
    return 1; 
  }  

  /* keep performing until user types a number and presses enter */
  scanf("%d", &finish);
  ud->PERF_STATUS = 0; 
  csoundDestroy(ud->csound); 
  free(ud);  
  return 0; 
} 

/* performance thread function */
uintptr_t csThread(void *data) 
{ 
  userData *udata = (userData *)data; 
  if (!udata->result) {
    while ((csoundPerformKsmps(udata->csound) == 0) &&
           (udata->PERF_STATUS == 1));
    csoundDestroy(udata->csound); 
  }        
  udata->PERF_STATUS = 0;    
  return 1; 
}  
The application above might not appear all that interesting. In fact it's almost the exact same as the first example presented except that users can now stop Csound by hitting 'enter'.  The real worth of threads can only be appreciated when you start to control your instrument in real time.

Channel I/O

The big advantage to using the API is that it allows a host to control your Csound instruments in real time. There are several mechanisms provided by the API that allow us to do this. The simplest mechanism makes use of a 'software bus'.

The term bus is usually used to describe a means of communication between hardware components. Buses are used in mixing consoles to route signals out of the mixing desk into external devices. Signals get sent through the sends and are taken back into the console through the returns. The same thing happens in a software bus, only instead of sending analog signals to different hardware devices we send data to and from different software. 

Using one of the software bus opcodes in Csound we can provide an interface for communication with a host application. An example of one such opcode is chnget. The chnget opcode reads data that is being sent from a host Csound API application on a particular named channel, and assigns it to an output variable. In the following example instrument 1 retrieves any data the host may be sending on a channel named "pitch":

instr 1 
kfreq chnget "pitch" 
asig  oscil  10000, kfreq, 1 
      out    asig
endin 

One way in which data can be sent from a host application to an instance of Csound is through the use of the csoundGetChannelPtr() API function which is defined in csound.h as:   

int csoundGetChannelPtr(CSOUND *, MYFLT **p, const char *name,  int type);

CsoundGetChannelPtr() stores a pointer to the specified channel of the bus in p. The channel pointer p is of type MYFLT *. The argument name is the name of the channel and the argument type is a bitwise OR of exactly one of the following values:  

CSOUND_CONTROL_CHANNEL - control data (one MYFLT value) 
CSOUND_AUDIO_CHANNEL - audio data (ksmps MYFLT values) 
CSOUND_STRING_CHANNEL - string data (MYFLT values with enough space to store csoundGetStrVarMaxLen(CSOUND*) characters, including the NULL character at the end of the string)   

and at least one of these:  

CSOUND_INPUT_CHANNEL - when you need Csound to accept incoming values from a host
CSOUND_OUTPUT_CHANNEL - when you need Csound to send outgoing values to a host 

If the call to csoundGetChannelPtr() is successful the function will return zero. If not, it will return a negative error code. We can now modify our previous code in order to send data from our application on a named software bus to an instance of Csound using csoundGetChannelPtr().

#include <stdio.h> 
#include <csound/csound.h>

/* performance thread function prototype */
uintptr_t csThread(void *clientData);

/* userData structure declaration */
typedef struct {
  int result;
  CSOUND *csound;
  int PERF_STATUS;
} userData;

/*-----------------------------------------------------------
 * main function
 *-----------------------------------------------------------*/
int main(int argc, char *argv[])
{
  int userInput = 200;
  void *ThreadID;
  userData *ud;
  ud = (userData *)malloc(sizeof(userData));
  MYFLT *pvalue;
  csoundInitialize(&argc, &argv, 0);
  ud->csound = csoundCreate(NULL);
  ud->result = csoundCompile(ud->csound, argc, argv);
  if (csoundGetChannelPtr(ud->csound, &pvalue, "pitch",
          CSOUND_INPUT_CHANNEL | CSOUND_CONTROL_CHANNEL) != 0) {
    printf("csoundGetChannelPtr could not get the \"pitch\" channel");
    return 1;
  }
  if (!ud->result) {
    ud->PERF_STATUS = 1;
    ThreadID = csoundCreateThread(csThread, (void*)ud);
  }
  else {
    printf("csoundCompiled returned an error");
    return 1;
  }
  printf("\nEnter a pitch in Hz(0 to Exit) and type return\n");
  while (userInput != 0) {
    *pvalue = (MYFLT)userInput;
    scanf("%d", &userInput);
  }
  ud->PERF_STATUS = 0;
  csoundDestroy(ud->csound);
  free(ud);
  return 0;
}

/*-----------------------------------------------------------
 * definition of our performance thread function
 *-----------------------------------------------------------*/
uintptr_t csThread(void *data)
{
  userData *udata = (userData *)data;
  if (!udata->result) {
    while ((csoundPerformKsmps(udata->csound) == 0) &&
           (udata->PERF_STATUS == 1));
    csoundDestroy(udata->csound);
  }
  udata->PERF_STATUS = 0;
  return 1;
} 

Score Events 

Adding score events to the csound instance is easy to do. It requires that csound has its threading done, see the paragraph above on threading. To enter a score event into csound, one calls the following function:

void myInputMessageFunction(void *data, const char *message)
{
  userData *udata = (userData *)data;
  csoundInputMessage(udata->csound, message );
}

Now we can call that function to insert Score events into a running csound instance. The formatting of the message should be the same as one would normally have in the Score part of the .csd file. The example shows the format for the message. Note that if you're allowing csound to print its error messages, if you send a malformed message, it will warn you. Good for debugging. There's an example with the csound source code that allows you to type in a message, and then it will send it.

/*                     instrNum  start  duration   p4   p5   p6  ... pN */
const char *message = "i1        0      1          0.5  0.3  0.1";
myInputMessageFunction((void*)udata, message);


Callbacks

Coming in a future release of this manual...

Conclusion

This chapter discribed the Csound5 API. For the next Csound6 version, Andrés Cabrera is refactoring the API, breaking the backward compatibility of the API. The future versions of this manual will show the differences.

References & Links

Michael Gogins 2006, "Csound and CsoundVST API Reference Manual", http://csound.sourceforge.net/refman.pdf

Rory Walsh 2006, "Developing standalone applications using the Csound Host API and wxWidgets", Csound Journal Volume 1 Issue 4 - Summer 2006, http://www.csounds.com/journal/2006summer/wxCsound.html

Rory Walsh 2010, "Developing Audio Software with the Csound Host API",  The Audio Programming Book, DVD Chapter 35, The MIT Press

François Pinot 2011, "Real-time Coding Using the Python API: Score Events", Csound Journal Issue 14 - Winter 2011, http://www.csounds.com/journal/issue14/realtimeCsoundPython.html



your comment:
name :
comment :

If you can't read the word, click here
word :