PyCsound Manual [DRAFT]

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

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

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