Python Opcodes for Csound

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.

Table of Contents

Opcodes
To Do
Bibliography

Opcodes

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:

  1. Run a statement (run).

  2. Execute a script (exec).

  3. Invoke a callable and pass arguments (call).

  4. Evaluate an expression (eval).

  5. Change the value of a Python object, possibly creating a new Python object (assign).

Since you can do these things...

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

Orchestra Syntax

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 Opcode

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.

Run Opcodes

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.

Exec Opcodes

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.

Call Opcodes

                                        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.

Eval Opcodes

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.

Assign Opcodes

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.

To Do

  1. Debug memory leaks.

  2. Optimize for speed.

Bibliography

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.