Copyright © 2002 by Maurizio Umberto Puxeddu. All rights reserved.
Portions copyright © 2004 by Michael Gogins
This document has been updated Sunday 25 July 2004 by Michael Gogins.
This section describes the Csound opcodes related to Python.
Using the Python opcode family, you can interact with a Python interpreter embedded in Csound in five ways:
Run a statement (run).
Execute a script (exec).
Invoke a callable and pass arguments (call).
Evaluate an expression (eval).
Change the value of a Python object, possibly creating a new Python object (assign).
Since you can do these things...
At i-time or at k-time,
In the global Python namespace, or in a namespace specific to an individual instance of a Csound instrument,
And can you can retrieve from 0 to 8 return values from callables that accept N parameters...
...this means that there are many Python-related opcodes. But all of these opcodes share the same py prefix, and have a regular naming scheme:
"py" + [optional context prefix] + [action name] + [optional x-time prefix] |
Blocks of Python code, and indeed entire scripts, can be embedded in Csound orchestras using the {{ and }} directives to enclose the script, as follows:
sr=44100 kr=4410 ksmps=10 nchnls=1 pyinit giSinusoid ftgen 0, 0, 8192, 10, 1 pyruni {{ import random pool = [(1 + i/10.0) ** 1.2 for i in range(100)] def get_number_from_pool(n, p): if random.random() < p: i = int(random.random() * len(pool)) pool[i] = n return random.choice(pool) }} instr 1 k1 oscil 1, 3, giSinusoid k2 pycall1 "get_number_from_pool", k1 + 2, p4 printk 0.01, k2 endin |
pyinit |
In the command-line version of Csound, you must first invoke the pyinit opcode in the orchestra header to initialize the Python interpreter, before using any of the other Python opcodes.
But if you use the Python opcodes in the CsoundVST version of Csound, you should not invoke pyinit, because CsoundVST automatically initializes the Python interpreter for you. In addition, CsoundVST automatically creates a Python interface to the Csound API, in the form a global instance of the CsoundVST.CppSound class named csound. Therefore, Python code written in the Csound orchestra has access to the global csound object.
pyrun "statement" pyruni "statement" pylrun "statememt" pylruni "statement" |
Execute the specified Python statement at k-time (pyrun and pylrun) or i-time (pyruni and pylruni).
The statement is executed in the global environment for pyrun and pyruni or the local environment for pylrun and pylruni..
These opcodes perform no message passing. However, since the statement have access to the main namespace and the private namespace, it can interact with objects previously created in that environment.
The "local" version of the "run" opcodes are useful when the code ran by different instances of an instrument should not interact.
Example 1. Example of the RUN opcode group
Orchestra:
sr=44100 kr=4410 ksmps=10 nchnls=1 pyruni "import random" instr 1 ; This message is stored in the main namespace ; and is the same for every instance pyruni "message = 'a global random number: %f' % random.random()" pyrun "print message" ; This message is stored in the private namespace ; and is different for different instances pylruni "message = 'a private random number: %f' % random.random()" pylrun "print message" endin |
Score:
i1 0 0.1 |
Running this score you should get intermixed pairs of messages from the two instances of instrument 1.
The first message of each pair is stored into the main namespace and so the second instance overwrites the message of the first instance. The result is that first message will be the same for both instances.
The second message is different for the two instances, being stored in the private namespace.
pyexec "filename" pyexeci "filename" pylexec "filename" pylexeci "filename" pyexect ktrigger, "filename" pylexec ktrigger, "filename" |
Execute a script from a file at k-time or i-time (i suffix).
This is not the same as calling the script with the system() call, since the code is executed by the embedded interpreter.
The code contained in the specified file is executed in the global environment for opcodes pyexec and pyexeci and in the private environment for the opcodes pylexec and pylexeci.
These opcodes perform no message passing. However, since the statement have access to the main namespace and the private namespace, it can interact with objects previously created in that environment.
The "local" version of the "exec" opcodes are useful when the code ran by different instances of an instrument should not interact.
Example 2. Example of the EXEC opcode group
Orchestra (pyexec.orc):
sr=44100 kr=4410 ksmps=10 nchnls=1 pyruni "import random" pyexeci "pyexec1.py" instr 1 pyexec "pyexec2.py" pylexeci "pyexec3.py" pylexec "pyexec4.py" endin |
Score (pyexec.sco):
i1 0 0.01 i1 0 0.01 |
The pyexec1.py script:
import time, os print print "Welcome to Csound!" try: s = ', %s?' % os.getenv('USER') except: s = '?' print 'What sound do you want to hear today%s' % s answer = raw_input() |
The pyexec2.py script:
print 'your answer is "%s"' % answer |
The pyexec3.py script:
message = 'a private random number: %f' % random.random() |
The pyexec4.py script:
print message |
If I run this example on my machine I get something like:
Using ../../csound.xmg Csound Version 4.19 (Mar 23 2002) Embedded Python interpreter version 2.2 orchname: pyexec.orc scorename: pyexec.sco sorting score ... ... done orch compiler: 11 lines read instr 1 Csound Version 4.19 (Mar 23 2002) displays suppressed Welcome to Csound! What sound do you want to hear today, maurizio? |
then I answer
damn you |
then Csound continues with the normal performance
your answer is "damn you" a private random number: 0.884006 new alloc for instr 1: your answer is "damn you" a private random number: 0.884006 your answer is "damn you" a private random number: 0.889868 your answer is "damn you" a private random number: 0.884006 your answer is "damn you" a private random number: 0.889868 your answer is "damn you" a private random number: 0.884006 your answer is "damn you" ... |
Embarassing.
In the same instrument a message is created in the private namespace and printed, appearing different for each instance.
pycall "callable", karg1, ... kresult pycall1 "callable", karg1, ... kresult1, kresult2 pycall2 "callable", karg1, ... kr1, kr2, kr3 pycall3 "callable", karg1, ... kr1, kr2, kr3, kr4 pycall4 "callable", karg1, ... kr1, kr2, kr3, kr4, kr5 pycall5 "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6 pycall6 "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6, kr7 pycall7 "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6, kr7, kr8 pycall8 "callable", karg1, ... pycallt ktrigger, "callable", karg1, ... kresult pycall1t ktrigger, "callable", karg1, ... kresult1, kresult2 pycall2t ktrigger, "callable", karg1, ... kr1, kr2, kr3 pycall3t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4 pycall4t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4, kr5 pycall5t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6 pycall6t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6, kr7 pycall7t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6, kr7, kr8 pycall8t ktrigger, "callable", karg1, ... pycalli "callable", karg1, ... iresult pycall1i "callable", iarg1, ... iresult1, iresult2 pycall2i "callable", iarg1, ... ir1, ir2, ir3 pycall3i "callable", iarg1, ... ir1, ir2, ir3, ir4 pycall4i "callable", iarg1, ... ir1, ir2, ir3, ir4, ir5 pycall5i "callable", iarg1, ... ir1, ir2, ir3, ir4, ir5, ir6 pycall6i "callable", iarg1, ... ir1, ir2, ir3, ir4, ir5, ir6, ir7 pycall7i "callable", iarg1, ... ir1, ir2, ir3, ir4, ir5, ir6, ir7, ir8 pycall8i "callable", iarg1, ... pycalln "callable", nresults, kresult1, ..., kresultn, karg1, ... pycallni "callable", nresults, iresult1, ..., iresultn, iarg1, ... pylcall "callable", karg1, ... kresult pylcall1 "callable", karg1, ... kresult1, kresult2 pylcall2 "callable", karg1, ... kr1, kr2, kr3 pylcall3 "callable", karg1, ... kr1, kr2, kr3, kr4 pylcall4 "callable", karg1, ... kr1, kr2, kr3, kr4, kr5 pylcall5 "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6 pylcall6 "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6, kr7 pylcall7 "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6, kr7, kr8 pylcall8 "callable", karg1, ... pylcallt ktrigger, "callable", karg1, ... kresult pylcall1t ktrigger, "callable", karg1, ... kresult1, kresult2 pylcall2t ktrigger, "callable", karg1, ... kr1, kr2, kr3 pylcall3t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4 pylcall4t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4, kr5 pylcall5t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6 pylcall6t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6, kr7 pylcall7t ktrigger, "callable", karg1, ... kr1, kr2, kr3, kr4, kr5, kr6, kr7, kr8 pylcall8t ktrigger, "callable", karg1, ... pylcalli "callable", karg1, ... iresult pylcall1i "callable", iarg1, ... iresult1, iresult2 pylcall2i "callable", iarg1, ... ir1, ir2, ir3 pylcall3i "callable", iarg1, ... ir1, ir2, ir3, ir4 pylcall4i "callable", iarg1, ... ir1, ir2, ir3, ir4, ir5 pylcall5i "callable", iarg1, ... ir1, ir2, ir3, ir4, ir5, ir6 pylcall6i "callable", iarg1, ... ir1, ir2, ir3, ir4, ir5, ir6, ir7 pylcall7i "callable", iarg1, ... ir1, ir2, ir3, ir4, ir5, ir6, ir7, ir8 pylcall8i "callable", iarg1, ... pylcalln "callable", nresults, kresult1, ..., kresultn, karg1, ... pylcallni "callable", nresults, iresult1, ..., iresultn, iarg1, ... |
This family of opcodes call the specified Python callable at k-time and i-time (i suffix), passing the given arguments. The call is perfomed in the global environment and the result (the returning value) is copied into the Csound output variables specified.
They pass any number of parameters which are cast to float inside the Python interpreter.
The pycall/pycalli, pycall1/pycall1i ... pycall8/pycall8i opcodes can accomodate for a number of results ranging from 0 to 8 according to their numerical prefix (0 is omitted).
The pycalln/pycallni opcodes can accomodate for any number of results: the callable name is followed by the number of output arguments, then come the list of Csound output variable and the list of parameters to be passed.
The returning value of the callable must be None for pycall or pycalli, a float for pycall1i or pycall1i and a tuple (with proper size) of floats for the pycall2/pycall2i ... pycall8/pycall8i and pycalln/pycallni opcodes.
Example 3. Calling a C or Python function
Supposing we have previously defined or imported a function named effe as
from random import random, choice # a pool of 100 numbers pool = [i ** 1.3 for i in range(100)] def get_number_from_pool(n, p): # substitute an old number with the new number? if random() < p: i = choice(range(len(pool))) pool[i] = n # return a random number from the pool return choice(pool) |
then the following orchestra code
k2 pycall1 "get_number_from_pool", k1, p6 |
would set k2 randomly from a pool of numbers changing in time. You can pass new pools elements and control the change rate from the orchestra.
Example 4. Calling a function-object
A more generic implementation of the previous example makes use of a simple function object.
from random import random, choice class GetNumberFromPool: def __init__(self, e, begin=0, end=100, step=1): self.pool = [i ** e for i in range(begin, end, step)] def __call__(self, n, p): # substitute an old number with the new number? if random() < p: i = choice(range(len(pool))) pool[i] = n # return a random number from the pool return choice(pool) get_number_from_pool1 = GetNumberFromPool(1.3) get_number_from_pool2 = GetNumberFromPool(1.5, 50, 250, 2) |
then the following orchestra code
k2 pycall1 "get_number_from_pool1", k1, p6 k4 pycall1 "get_number_from_pool2", k3, p7 |
would set k2 and k3 randomly from a pool of numbers changing in time. You can pass new pools elements (here k1 and k3) and control the change rate (here p6 and p7) from the orchestra.
As you can see in the first snippet, you can customize the initialization of the pool as well as create several pool.
kresult pyeval "expression" iresult pyevali "expression" kresult pyleval "expression" iresult pylevali "expression" kresult pyevalt ktrigger, "expression" kresult pylevalt ktrigger, "expression" |
These opcodes evaluate a generic Python expression and store the result in a Csound variable at k-time or i-time (i suffix).
The expression must evaluate in a float or an object that can be cast to a float.
They can be used effectively to trasfer data from a Python object into a Csound variable.
Example 5. Retrieving a Python variable value
The code
k1 pyeval "v1" |
will copy the content of the Python variable v1 into the Csound variable k1 at each k-time.
pyassign "variable", kvalue pyassigni "variable", ivalue pylassign "variable", kvalue pylassigni "variable", ivalue pyassignt ktrigger, "variable", kvalue pylassignt ktrigger, "variable", kvalue |
Assign the value of the given Csound variable to a Python variable possibly destroying its previous content.
The resulting Python object will be a float.
Debug memory leaks.
Optimize for speed.
Guido van Rossum, Python Tutorial.
Guido van Rossum, Python Library Reference.
Guido van Rossum, Extending and Embedding the Python Interpreter.
Guido van Rossum, Python/C API Reference Manual.