Introduction

The Csound Python API provides classes and functions to use the Csound API fromPython. An interesting issue with Python is that we can use an enhanced versionof the Python shell, IPython [1], for interactive sessions.In this paper, a basic class encapsulating commands to run a Csound session ispresented, and examples are given of a real-time session with score events. Thereader is expected to be comfortable with the Python programming language[2] and to have a basic knowledge of Csound [3].


I. IPython

For this article we will need the following installed: Csound, Python, the interactive Python shell IPython,and the Python modules numpy, scipy and matplotlib. On mostLinux distributions there are prebuilt packages of all these components. On Windows,the simplest way to setup these is to install binaries. For example with Python 2.6, you can getan msi file here,the Csound installer from theCsound Page at SourceForge,an IPython binary from the IPython site,numpy and scipy binaries from SciPy.org,and matplotlib from its SourceForgepage (the download link on the right side). Windows users also need to install thepyreadline module to havea console with colors.

The IPython shell offers some goodies for an interactive use of Python:TAB-completion, exploring your objects, the %run command, input and output cache,%hist command, and a lot of other facilities. When launched with the pylabflag and the scipy profile (ipython -pylab -p scipy), IPython offers aready-to-go interactive environment similar to MATLAB® but with the scriptingpower of Python. Finally if we add to this environment access to Csoundvia its API, we get a very powerful system for interactive experimentation.

Note: All of the examples presented in this article have been tested on Linux and on Windows XPwith Csound 5.12 and Python 2.6. While I could not test these examples on Mac OS X, they should be able to work. One can find instructions to install SciPy for Mac OS Xhere.Note that they strongly recommend installing theofficalPython distribution instead of using the one shipped by Apple.


II. CsoundSession

In this article, a Csound session consists of a running Csound engine with a Python interpreterlistening for user input. Our basic class for running a session is called CsoundSession. Itinherits from the Csound class which is an interface to the Csound API. We willalso use the CsoundPerformanceThread class to perform a CSD file [4]in a separate thread.Initially it might make sense to make our class inherit from both the Csound and theCsoundPerformanceThread classes as Python allows multiple inheritance. However, doing sowould put both classes on the same level. In fact, the Csound class is tied to awhole session while the CsoundPerformanceThread class corresponds to a singleperformance within a session. We want to be able to perform successive CSD files during a singlesession. Therefore our CsoundSession class is designed to have an attribute of type CsoundPerformanceThread.

Figure 1. The CsoundSession class.

Launching a session

To start a session, open up a terminal (Command Prompt on Windows), change directories to the directory inwhich there is a copy of the csoundSession.py file,and then launch an IPython shell:

~$ cd 'our working directory'~$ ipython -pylab -p scipyPython 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) Type "copyright", "credits" or "license" for more information.IPython 0.10 -- An enhanced Interactive Python.?         -> Introduction and overview of IPython's features.%quickref -> Quick reference.help      -> Python's own help system.object?   -> Details about 'object'. ?object also works, ?? prints more.IPython profile: scipy  Welcome to pylab, a matplotlib-based Python environment.  For more information, type 'help(pylab)'.In [1]: 
(In the IPython shell, the default prompt is In [n]: where n isthe number of the command line typed by the user. The answer to the user command isdisplayed with an Out[n]: prompt).

To begin, first import everything from the csoundSession module and then create aCsoundSession object. Please note that the first letter of the module name is alowercase 'c' while the first letter of the class name is an uppercase 'C'.

In [1]: from csoundSession import *In [2]: cs = CsoundSession()In [3]: 
Next, we can display the methods and attributes of our object with the dir() command. We can also print some information about Csound using those API functions which work even ifno CSD file is loaded.
In [3]: dir(cs)Out[3]: ['AddSpinSample', 'AppendOpcode', 'ChanIASet', 'ChanIASetSample', 'ChanIKSet', 'ChanIKSetValue', 'ChanOAGet', 'ChanOAGetSample', 'ChanOKGet', 'ChanOKGetValue', 'Cleanup', 'Compile', 'CreateConfigurationVariable', 'CreateGlobalVariable', 'DeleteChannelList', 'DeleteConfigurationVariable', 'DeleteUtilityList', 'DestroyGlobalVariable', 'DestroyMessageBuffer', 'DisposeOpcodeList', 'EnableMessageBuffer', 'Get0dBFS', 'GetChannel', 'GetChannelPtr', 'GetControlChannelParams', 'GetCsound', 'GetDebug', 'GetEnv', 'GetFirstMessage', 'GetFirstMessageAttr', 'GetInputBuffer', 'GetInputBufferSize', 'GetKr', 'GetKsmps', 'GetMessageCnt', 'GetMessageLevel', 'GetNchnls', 'GetOutputBuffer', 'GetOutputBufferSize', 'GetOutputFileName', 'GetRtPlayUserData', 'GetRtRecordUserData', 'GetSampleFormat', 'GetSampleSize', 'GetScoreOffsetSeconds', 'GetScoreTime', 'GetSpin', 'GetSpout', 'GetSpoutSample', 'GetSr', 'GetStrVarMaxLen', 'GetTable', 'GetUtilityDescription', 'InitializeCscore', 'InputMessage', 'IsScorePending', 'KeyPressed', 'ListChannels', 'ListConfigurationVariables', 'ListUtilities', 'Message', 'MessageS', 'NewOpcodeList', 'ParseConfigurationVariable', 'Perform', 'PerformBuffer', 'PerformKsmps', 'PerformKsmpsAbsolute', 'PopFirstMessage', 'PreCompile', 'PvsinSet', 'PvsoutGet', 'QueryConfigurationVariable', 'QueryGlobalVariable', 'QueryGlobalVariableNoCheck', 'Reset', 'RewindScore', 'RunUtility', 'ScoreEvent', 'ScoreExtract', 'ScoreSort', 'SetChannel', 'SetChannelIOCallback', 'SetConfigurationVariable', 'SetControlChannelParams', 'SetDebug', 'SetExternalMidiInCloseCallback', 'SetExternalMidiInOpenCallback', 'SetExternalMidiReadCallback', 'SetHostData', 'SetHostImplementedAudioIO', 'SetInputValueCallback', 'SetMessageCallback', 'SetMessageLevel', 'SetOutputValueCallback', 'SetScoreOffsetSeconds', 'SetScorePending', 'Stop', 'TableGet', 'TableLength', 'TableSet', '__class__', '__del__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattr__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__swig_getmethods__', '__swig_setmethods__', '__weakref__', 'CSD', 'getCsdFileName', 'note', 'pt', 'pydata', 'resetSession', 'scoreEvent', 'startThread', 'stopPerformance', 'this']In [4]: cs.GetSr()Out[4]: 44100.0In [5]: cs.GetKr()Out[5]: 4410.0In [6]: cs.GetNchnls()Out[6]: 1In [7]: cs.GetScoreTime()Out[7]: 0.0In [8]: cs.getCsdFileName()In [9]: 
The dir(cs) command returns a list of strings that are the method names of theCsound class (the parent class of our CsoundSession class), followed by theattribute and method names of the object class that was inherited by the Csoundclass (from '__class__' to '__subclasshook__'), then some method names providedby the SWIG wrapper and finally our own attributes and method names. Note thatthe Csound class method names begin with an uppercase letter while our own methodnames begin with a lowercase letter. Therefore our scoreEvent method is differentfrom its parent class ScoreEvent method.

Performing a CSD file

To produce some music, Csound needs an orchestra and a score. We load both of themfrom a CSD file named simple.csd.It contains a single intrument (instr 1) and a score including a single f0 event with of duration of14400 seconds (four hours). The -d -m0 flags are used to minimize the amount ofmessages displayed by Csound. (Note: If your sound card does not support a sampling rate of 96000 Hz,try with a lower sr)

<CsoundSynthesizer><CsOptions>  -d -o dac -m0</CsOptions><CsInstruments>sr     = 96000ksmps  = 100nchnls = 20dbfs  = 1          instr 1idur      =         p3iamp      =         p4icps      =         cpsoct(p5)ifn       =         p6irise     =         p7idec      =         p8ipan      =         p9kenv      linen     iamp, irise, idur, idecasig      oscili    kenv, icps, ifna1, a2    pan2      asig, ipan          outs      a1, a2          endin</CsInstruments><CsScore>f 0 14400</CsScore></CsoundSynthesizer>
The file is loaded and started with the resetSessionmethod which takes the file name as argument:
In [9]: cs.resetSession("simple.csd")PortAudio real-time audio module for CsoundPortMIDI real time MIDI plugin for Csoundvirtual_keyboard real time MIDI plugin for Csound0dBFS level = 32768.0Csound version 5.12 (double samples) Jul 29 2010libsndfile-1.0.21Reading options from $HOME/.csoundrcUnifiedCSD:  simple.csdSTARTING FILECreating optionsCreating orchestraCreating scoreorchname:  /tmp/csound-16QorA.orcscorename: /tmp/csound-Xdi5rI.scoRAWWAVE_PATH: /usr/local/share/csound/rawwaves/rtmidi: PortMIDI module enabledrtaudio: ALSA module enabledorch compiler:	instr	1	sorting score ...	... doneCsound version 5.12 (double samples) Jul 29 2010displays suppressed0dBFS level = 1.0orch now loadedaudio buffered in 1024 sample-frame blockswriting 4096-byte blks of shorts to dacSECTION 1:In [10]: 
This method compiles the orchestra and the score and starts the performance in aseparate thread using a CsoundPerformanceThread object. Note that thelines displayed by Csound (from 'Portaudio... to 'SECTION 1:') are not precededby an Out[10]: prompt because those lines are not a valuereturned by the resetSession method, but a direct output from Csound. As the performance is running in a separate thread, we still can enter commands from the top level:
In [10]: cs.GetSr(), cs.GetKr(), cs.GetNchnls()Out[10]: (96000.0, 960.0, 2)In [11]: cs.GetScoreTime()Out[11]: 9.6229166666666668In [12]: cs.getCsdFileName()Out[12]: 'simple.csd'In [13]:
Note that the three values returned in the tuple are from after loading the CSD file, and that these aredifferent from the default values we received when the CSD file was not loaded.

We could also have started the session and the performance in a single operation, givingthe CSD file name as argument to our object constructor. In the following display, we stopthe performance and leave the interactive shell. We then launch a shell and start a new Csound session:

In [13]: cs.stopPerformance()inactive allocs returned to freespaceend of score.		   overall amps:  0.00000	   overall samples out of range:        00 errors in performance181552 2048-byte soundblks of shorts written to dacIn [14]: quit()Do you really want to exit ([y]/n)? y~$ipython -pylab -p scipy...In [1]: from csoundSession import *In [2]: cs = CsoundSession("simple.csd")PortAudio real-time audio module for Csound...SECTION 1:In [3]: 
Now that a CSD file is being performed, we can send score events to Csound usingthe scoreEvent and note methods of our cs object .


III. Sending Score Events

The scoreEvent method takes two or three arguments:

'a' Statement

The 'a' score statement is used to advance the score time by a specified amount:

In [3]: cs.GetScoreTime()Out[3]: 6.5708333333333337In [4]: cs.scoreEvent('a', (0, 0, 6000))In [5]: time advanced 6000.000 beats by score request#return key hit to get the right promptIn [6]: cs.GetScoreTime()Out[6]: 6025.260416666667In [7]: 

In [3]: we display the current score time in beats, which means seconds herebecause the default tempo is 60 beats per second.
In [4]: the score time is advanced 6000 beats. The 3 values in the tuple are respectivelyp1 (no meaning here), p2 (start time), p3 (number of beats to advance). A start time of 0means executing the 'a' statement as soon as it is received.
In [5]: the message returned by Csound is displayed mixed with the prompt fromIPython for the next command. We hit the <return> key to get an empty prompt line.
In [6]: we verify that the current score time has been advanced.

In [7]: cs.scoreEvent('a', (0, 6100, 500), 1)In [8]: cs.GetScoreTime()Out[8]: 6057.0249999999996In [9]: time advanced 500.000 beats by score request#return key hit to get the right promptIn [10]: cs.GetScoreTime()Out[10]: 6629.1750000000002In [11]: 

In [7]: we ask Csound to advance the score time 500 beats when it will have reached theabsolute score time of 6100 beats (the absp2mode argument is different from 0).
In [8]: we verify that the score is still running and that it has not yet reachedthe advance point.
In [9]: after a few seconds, Csound tells us it has advanced the score time (at beat 6100).
In [10]: the score time is now more than 6600 beats.

In [11]: cs.RewindScore()end of section 1	 sect peak amps:  0.00000SECTION 1:In [12]: cs.GetScoreTime()Out[12]: 8.0416666666666661In [13]: 

In [11]: we rewind the score time to zero.

'e' Statement

The 'e' statement is used to mark the end of the score:

In [13]: cs.GetScoreTime()Out[13]: 376.04166666666669In [14]: cs.scoreEvent('e', ())In [15]: Score finished in csoundPerformKsmps().inactive allocs returned to freespaceend of score.		   overall amps:  0.00000	   overall samples out of range:        00 errors in performance24694 2048-byte soundblks of shorts written to dac#return key hit to get the right promptIn [16]: 

In [14]: we send an 'e' statement to Csound. As there is no pfield (empty tuple),the effect is immediate.
In [15]: Csound displays messages denoting the end of the performance.

In [16]: cs.resetSession()PortAudio real-time audio module for Csound...SECTION 1:In [17]: cs.GetScoreTime()Out[17]: 7.4249999999999998In [18]: cs.scoreEvent('e', (0, 15))In [19]: cs.GetScoreTime()Out[19]: 25.729166666666668In [20]: Score finished in csoundPerformKsmps().inactive allocs returned to freespaceend of score.		   overall amps:  0.00000	   overall samples out of range:        00 errors in performance1340 2048-byte soundblks of shorts written to dac#return key hit to get the right promptIn [21]: 

In [16]: we start a performance of the last loaded CSD file ('simple.csd').
In [17]: the performance is running.
In [18]: we ask Csound to end the performance in 15 beats from now.
In [19]: the performance is still running.
In [20]: Csound tells us that it has finished the performance.

In [21]: cs.resetSession()PortAudio real-time audio module for Csound...SECTION 1:In [22]: cs.GetScoreTime()Out[22]: 2.6666666666666665In [23]: cs.scoreEvent('e', (0, 20), 1)In [24]: cs.GetScoreTime()Out[24]: 18.987500000000001In [25]: Score finished in csoundPerformKsmps().inactive allocs returned to freespaceend of score.		   overall amps:  0.00000	   overall samples out of range:        00 errors in performance938 2048-byte soundblks of shorts written to dac#return key hit to get the right promptIn [26]: cs.resetSession()PortAudio real-time audio module for Csound...SECTION 1:In [27]: 

In [21]: new performance...
In [23]: we ask Csound to end the performance when it will have reached theabsolute time 20 beats.
In [24]: still running...
In [25]: score finished.
In [26]: ready for new adventures!

'f' Statement

We can use the function table statement to generate function tables.

In [27]: cs.scoreEvent('f', [1, 0, 4096, 10, 1])In [28]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [29]: cs.TableLength(1)Out[29]: 4096In [30]: cs.TableLength(2)Out[30]: -1In [31]: cs.TableGet(1, 1024)Out[31]: 1.0In [32]: cs.TableSet(1, 1024, -1.0)In [33]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [34]: cs.TableSet(1, 1024, 1.0)In [35]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [36]:

In [27]: ftable 1 is generated with a single sine wave. The second argumentis a list containing the pfields for the 'f' statement: p1=1, ftable number.p2 = 0, generate the ftable right now. p3 = 4096, length of the ftable in samples.p4 = 10, use GEN10. p5 = 1, only the first partial.
In [28]: we play a one second 440 Hz note using ftable 1. The note method ofthe CsoundSession class will be discussed in the next section.
In [29]: we can get the table length from the API.
In [30]: if the ftable does not exist, TableLength returns an error value (-1).
In [31]: we can read a single value from within an ftable. Here we get the value ofthe 1025th sample of ftable 1. Indexes of an ftable are in the range [0, length[.
In [32]: we write the value -1.0 to the 1025th location of ftable 1. This causes adiscontinuity in the sine wave.
In [33]: we can ear clicks in the note due to the discontinuity.
In [34]: we write back the original value to the 1025th location of ftable1.
In [35]: no more discontinuity, pure sine wave...

In the next examples, we will see how to use directly the data from an ftablethrough a numpy array. A numpy array is a data structure that can supportindexing and slicing operations. If the data in the array are contiguous as in a Carray, we should be able to create a numpy array with its buffer pointing to thedata of the ftable. Thus we can manipulate the ftable data from their Csoundbuffer without copying them,thanks to the addons of numpy arrays.

First we must obtain a pointer to the ftable data. This is done with theCsoundMYFLTArray class of the API. This class wraps a Csound MYFLT array. Ithas a GetPtr method. When called without argument, this method returns apointer to a pointer to a MYFLT. Creating a CsoundMYFLTArray object andpassing its GetPtr method without argument to the GetTable API function,fills the internal pointer of the CsoundMYFLTArray object with a MYFLT pointerto the ftable data:

In [36]: tbl = csnd.CsoundMYFLTArray()In [37]: tblSize = cs.GetTable(tbl.GetPtr(), 1)In [38]: tblSizeOut[38]: 4096In [39]: tbl.GetValue(1024)Out[39]: 1.0In [40]: tbl.SetValue(1024, -1.0)In [41]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [42]: tbl.SetValue(1024, 1.0)In [43]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [44]: 

In [36]: we create an empty CsoundMYFLTArray object.
In [37]: we make our object point to ftable 1 data, and we get ftable 1 length asa return value.
In [39] .. In [43]: click test to verify that we have a direct access to ftable 1 data.

Once our CsoundMYFLTArray object is pointing to the ftable data, we canget a pointer to any value inside the ftable by using its GetPtr methodwith an index within the ftable range as argument. Thus calling GetPtr(0)returns a pointer to the ftable buffer. With this pointer, we can initialize a floator a double array using the appropriate Csound Python API function. Assuming thatwe're using a double version of Csound we would have:

In [44]: tblDoubleArray = csnd.doubleArray_frompointer(tbl.GetPtr(0))# If the float version of Csound is used,# the following line should be typed instead:# tblArray = csnd.floatArray_frompointer(tbl.GetPtr(0))In [45]: tblDoubleArray[1024]Out[45]: 1.0In [46]: tblDoubleArray[1024] = -1.0In [47]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [48]: tblDoubleArray[1024] = 1.0In [49]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [50]: tblDoubleArray[1022:1027]---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)/home/pinot/Articles/console-python/<ipython console> in <module>()/usr/local/lib/python2.6/dist-packages/csnd.py in __getitem__(self, *args)    211     __swig_destroy__ = _csnd.delete_doubleArray    212     __del__ = lambda self : None;--> 213     def __getitem__(self, *args): return _csnd.doubleArray___getitem__(self, *args)    214     def __setitem__(self, *args): return _csnd.doubleArray___setitem__(self, *args)    215     def cast(self): return _csnd.doubleArray_cast(self)TypeError: in method 'doubleArray___getitem__', argument 2 of type 'size_t'In [51]: len(tblDoubleArray)---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last)/home/pinot/Articles/console-python/<ipython console> in <module>()TypeError: object of type 'doubleArray' has no len()In [52]: 

In [44]: we create a double array pointing to ftable 1 data. (If the floatversion of Csound is used, the line in the comment should be typed instead)
In [45] .. In [49]: our click test is successful. Moreover, we can now access theftable samples by indexing the array instead of using get or set functions.
In [50]: slicing does not work.
In [51]: neither do the len function. A bit more work is needed to get full Python functionalities.

Fortunately, Python provides a "swiss knife" for accessing C sharedlibraries from pure Python code: the ctypes module.

In [52]: import ctypesIn [53]: bufAdr = tbl.GetPtr(0).__long__()In [54]: tblBuffer = ctypes.ARRAY(ctypes.c_double, tblSize).from_address(bufAdr)# If the float version of Csound is used,# the following line should be typed instead:# tblBuffer = ctypes.ARRAY(ctypes.c_float, tblSize).from_address(bufAdr)In [55]: tblBuffer[1024]Out[55]: 1.0In [56]: tblBuffer[1024] = -1In [57]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [58]: tblBuffer[1024] = 1In [59]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [60]: tblBuffer[1022:1027]Out[60]: [0.99999529380957619, 0.99999882345170188, 1.0, 0.99999882345170188, 0.99999529380957619]In [61]: len(tblBuffer)Out[61]: 4096In [62]: 

In [53] : we get the pointer to the ftable data as a long integer, which is theusual format for pointers in C.
In [54] : we create a ctypes array with elements of type c_double, length equalto tblSize, and buffer pointing to ftable 1 data. This array presents thebuffer interface which allows slicing operations. (If the floatversion of Csound is used, the line in the comment should be typed instead)
In [55] .. In [59]: our click test is OK.
In [60]: slicing now works...
In [61]: and the len function as well.

The preceding example (from In [27]: to In [61]: ) has been presented for pedagogicalpurposes. Our CsoundSession class has a method called getTableBufferthat does the same job: it takes an ftable number as argument and it returns a ctypesarray with the appropriate c_float or c_double type depending on theversion of Csound that is used (float or double). Thus we can replace our last exampleswith the two following commands:

In [62]: cs.scoreEvent('f', [1, 0, 4096, 10, 1])In [63]: tblBuffer = cs.getTableBuffer(1)In [64]: 

All the material presented until now was core Python stuff. CsoundSessionitself is written to be used with core Python. But remember, we use an environmentwith numpy and matplotlib. It's time now to take advantage of thosetools.

In [64]: tblArray = numpy.frombuffer(tblBuffer)# If the float version of Csound is used,# the following line should be typed instead:# tblArray = numpy.frombuffer(tblBuffer, float32)In [65]: plot(tblArray)Out[65]: [<matplotlib.lines.Line2D object at 0xa69bb6c>]In [66]: axis([0, tblArray.size, -1.1, 1.1])Out[66]: [0, 4096, -1.1000000000000001, 1.1000000000000001]In [67]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [68]: 

In [64]: we turn our buffer into an numpy array. Note, because IPython wasstarted with the scipy profile, we could have also simply written tblArray = frombuffer(tblBuffer), without mentioning the numpynamespace. (If the floatversion of Csound is used, the line in the comment should be typed instead)
In [65]: a simple command lets us plot our data.
In [66]: we define some extra vertical space for a nicer drawing (see below).
In [67]: listen to our familiar sine note.

Figure 2. Sine wave plotted from ftable 1.

We can now alter ftable 1 in a more interesting way than putting a singlediscontinuity: we'll insert a notch into the first half of the sine wave.

In [68]: tblArray[512]Out[68]: 0.70710678118654746In [69]: m = (-0.5 - tblArray[512]) / (1024.0 - 512.0)In [70]: b = tblArray[512] - 512.0 * mIn [71]: x = arange(512,1025)In [72]: tblArray[512:1025] = m*x + bIn [73]: tblArray[1024]Out[73]: -0.5In [74]: tblArray[1025]Out[74]: 0.98825700000000005In [75]: tblArray[1025:1537] = tblArray[1023:511:-1]In [76]: plot(tblArray)Out[76]: [<matplotlib.lines.Line2D object at 0x95e0ccc>]In [77]: axis([0, tblArray.size, -1.1, 1.1])Out[77]: [0, 4096, -1.1000000000000001, 1.1000000000000001]In [78]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [79]: 

In [69], In [70]: we define the slope m and the y-intercept b ofthe equation of a straight-line going down from point (512, tblArray[512]) topoint (1024, -0.5).
In [71]: we define an array of abscissa values.
In [72]: we fill the ordinate values in the ftable 1 data by applying our straightline formula to the abcissa values array.
In [75]: positions 1025 to 1536 of the array are filled with the same valuesthan positions 1023 downto 512 (symetric pattern around a vertical axis at position1024)
In [76], In [77]: we plot the altered sine wave (green lines) over the originalone (blue lines) (see below).
In [78]: listen to the new sound.

Figure 3. Modified sine wave plotted from ftable 1.

In [79]: cs.scoreEvent('f', [1, 0, 4096, 10, 1])In [80]: tblArray = frombuffer(cs.getTableBuffer(1))# If the float version of Csound is used,# the following line should be typed instead:# tblArray = frombuffer(cs.getTableBuffer(1), float32)In [81]: cs.note([1, 0, 1, 0.5, 8.75, 1, 0.05, 0.3, 0.5])In [82]: 

In [79]: back to our sine wave.
In [80]: make sure that tblArray points to the right data.

For the next section, we generate three ftables containing classical waveforms.ftable 101 is a band limited triangle wave, ftable 111 is aband limited square wave, and ftable 121 is a band limited impulse. The latterone is built with sine waves so that it starts with a zero value. We wrotesome functions in a python script called sessionUtils.py,so that we can play with them by changing their arguments. The script is loadedwith the IPython magic command %run. Each time we modify the script, we canreload the modified script with the %run command. The script includescomments about some traps to avoid. If you fixed the orchestra sampling rate toa lower value than 96000 Hz you'll have to change the last argument of the blTriangle,blSquare, and blImpulse functions to a lower number of harmonics to avoidfoldover:

In [82]: %run sessionUtils.pyIn [83]: blTriangle(cs, 101, 4096, 25)In [84]: blSquare(cs, 111, 4096, 25)In [85]: blImpulse(cs, 121, 4096, 25)In [86]: clf(); drawTable(cs, 101); drawTable(cs, 111); drawTable(cs, 121)# If the float version of Csound is used,# the following line should be typed instead:# clf(); drawTable(cs, 101, float32); drawTable(cs, 111, float32); drawTable(cs, 121, float32)In [87]: cs.note([1, 0, 1, 0.5, 6.75, 101, 0.05, 0.3, 0.5])In [88]: cs.note([1, 0, 1, 0.5, 6.75, 111, 0.05, 0.3, 0.5])In [89]: cs.note([1, 0, 1, 0.5, 6.75, 121, 0.05, 0.3, 0.5])In [90]: 

Figure 4. ftable 101 (blue), 111 (green), and 121 (red).

Then we generate interpolated ftables between ftable 101 and ftable 111, andbetween ftable 111 and ftable 121. We have now 21 ftables to play with:

In [90]: interpolateTables(cs, 101, 111)# If the float version of Csound is used,# the following line should be typed instead:# interpolateTables(cs, 101, 111, float32)In [91]: interpolateTables(cs, 111, 121)# If the float version of Csound is used,# the following line should be typed instead:# interpolateTables(cs, 111, 121, float32)In [92]: 

Figure 5. ftables 102 to 110.

Figure 6. ftables 112 to 120.

We can listen to two series of 110 Hz notes using successively our ftables:

In [92]: for i in range(101, 112):    cs.note([1, i-101, 0.7, 0.4, 6.75, i, 0.05, 0.3, 0.5])   ....:        ....:     In [93]: for i in range(111, 122):    cs.note([1, i-111, 0.7, 0.4, 6.75, i, 0.05, 0.3, 0.5])   ....:        ....:In [94]: 

'i' Statement

The 'i' score statement sends a note to the orchestra to be played by aninstrument. Because this statement is used very often during a session, theCsoundSession class provides a note method which is a syntacticsugar for a scoreEvent('i', ...) call. We already used it in the examplesof the last section. It has an argument of type tuple, list or array, representingthe pfields for the note to be played.

To illustrate the use of the note method, we will build a musical structurebased on a ruled surface: a hyperbolic paraboloid. This idea was introduced byIannis Xenakis in 1954 in his first orchestra piece Metastasis. He used stringglissandi as interlaced straight lines to obtain sonic spaces of continuousevolution [5].

To draw a hyperbolic paraboloid in a 3D space, we use two non-coplanar lines (the bluelines in figure 7), and we join them by straight lines intersecting the two baselines at equally spaced points on them (the red lines in figure 7).Those lines run along a ruled surface called a hyperbolic paraboloid:

In [94]: from mpl_toolkits.mplot3d import Axes3DIn [95]: fig = figure()In [96]: ax = Axes3D(fig)In [97]: nlines = 41In [98]: l1 = array([linspace(1, -1, nlines), ones(nlines)*(-1),                      linspace(-1, 1, nlines)])In [99]: l2 = array([linspace(1, -1, nlines), ones(nlines),                      linspace(1, -1, nlines)])In [100]: sl = skewlines(l1, l2, 100, ax)In [101]: 

In [94] to In [97]: we initialize a 3D figure in matplotlib.
In [97]: We will generate 41 skew lines.
In [98], In [99]: our two base lines are the non parallel diagonals of two opposite faces ofa cube with length 2 sides.
In [100]: the ruled surface is drawn by the skewlines function of oursessionUtils.py script. The figure can be rotated by dragging the mouse on it(in the figure window, not on the fixed picture of this article). Due to a bug inthe show routine, Windows user will have to close the figure window beforeentering further commands. So, each call to skewline from Windows has tobe preceded by the fig = figure() and ax = Axes3D(fig) commands.Linux users do not need to do this.

Figure 7. Hyperbolic paraboloid.

For our example, we use 17 skew lines on a hyperbolic paraboloid (figure 8), butinstead of playing glissandi, we play 400 overlapping notes (grains) along each line.The values along the 'x' axis are interpreted as pan values, the 'y' axis representstime, and the values along the 'z' axis are pitches. Each grain uses one of the ftables(101 to 121) defined in the last section. All the work is done by the playHPfunction of our sessionUtils.py script. The whole example has a duration oftwo minutes, playing 400 * 17 = 8600 notes:

In [101]: nlines = 17In [102]: l1 = array([linspace(1, -1, nlines), ones(nlines)*(-1),                       linspace(-1, 1, nlines)])In [103]: l2 = array([linspace(-1, 1, nlines), linspace(1, -0.25, nlines),                       zeros(nlines)])In [104]: sl = skewlines(l1, l2, 400, ax)In [105]: playHP(cs, sl, 4.00, 8.75, 120, 101, 121)In [106]: 

Figure 8. The hyperbolic paraboloid used for our example.

The above call to playHP should produce sounds like this.

'q' Statement

The 'q' score statement is used to quiet an instrument:

In [106]: cs.note([1, 0, 30, 0.5, 6.875, 101, 0.05, 0.3, 0.5])In [107]: cs.scoreEvent('q', (1, 0, 0))In [108]: Setting instrument 1 off#return key hit to get the right promptIn [109]: cs.note([1, 0, 10, 0.5, 7.875, 101, 0.05, 0.3, 0.5])In [110]: cs.scoreEvent('q', (1, 0, 1))In [111]: Setting instrument 1 on#return key hit to get the right promptIn [112]: cs.note([1, 0, 10, 0.5, 7.875, 101, 0.05, 0.3, 0.5])In [113]: 

In [106]: we start a long 110 Hz note.
In [107]: we ask Csound to quiet instrument 1: p1 = 1 (instrument number),p2 = 0 (immediatly), p3 = 0 (mute).
In [108]: Csound tells us that instrument 1 is off. Note that this does notaffect the note actually played.
In [109]: we try to play a 220 Hz note. It is not heard.
In [110]: we ask Csound to unmute instrument 1.
In [112]: we try again to play a 220 Hz note and this time we hear it.

Coda

Csound, Python, and the SciPy suite form together an incrediblypowerful set of tools to experiment with. This paper has only surveyed a small part of what is possible. Thesetools may seem complex (and they surely are!) but one can find a lot ofdocumentation available. High level mathematics languages like Matlab, Octave or Scilab can giveexamples and ideas. Some ideas: modify the orchestra in a text editor while it isperformed by a CsoundSession object, and then load it into another CsoundSessionobject so that there is no sound interrruption. Modify the CsoundSession classso that the scoreEvent method can store into a text file a line in score formatfor each event it sends to Csound. Thus when the session ends, we can usethis text file to copy and paste events into a <CsScore> section of a CSDfile.


IV. Conclusion

This article presents some mechanisms of the Csound API: launching a Csound session,performing a CSD file, getting information from Csound, using multithreading, accessingCsound internal buffers, and playing with score events. The Csound API offers many morepossibilities such channel I/O, callback functions, and MIDI control. Using Python and the Csound API allows themusician-programmer to explore these new territories in realtime.

References

[1] Fernando Perez, Brian E. Granger, "IPython: A System for Interactive Scientific Computing," Computing in Science and Engineering, vol. 9, no. 3, pp. 21-29, May/June 2007.

[2] Python Software Foundation, The Python Tutorial, Release 2.7, December 28, 2010.

[3] Richard Boulanger, An Instrument Design TOOTorial, Boston, Massachusetts, March, 1991.

[4] Barry Vercoe et Al. 2005. The Canonical Csound Reference Manual. http://www.csounds.com/manual/html/index.html. [Accessed Dec. 27, 2010].

[5] Iannis Xenakis, FORMALIZED MUSIC: Thought and Mathematics inMusic, Pendragon Revised Edition, Pendragon Press, 1992.

Links

IPython

The Python Tutorial

An Instrument Design TOOTorial

Writing your own .csd files