Introducción a la programación en Python
clase 13

Python y Csound (I) - Python en Csound

Csound y Python tienen diversas formas posibles de interactuar entre sí. Una de ellas es embeber código de Python dentro de la orquesta de Csound, aprovechando que Csound dispone de un intérprete interno de Python.

IMPORTANTE: Csound interpreta código de Python 2, que tiene algunas diferencias de sintaxis con Python 3, que es la versión que hemos utilizado en este curso.

Python opcodes en Csound

Hay 6 operadores básicos de Python en Csound:

  1. pyinit: inicializa el intérprete de Python
  2. pyrun: ejecuta una instrucción
  3. pyexec: ejecuta un script
  4. pycall: invoca un elemento que puede sar llamado ("callable"), y pasa argumentos
  5. pyeval: evalúa una expresión
  6. pyassign: cambia el valor de un objeto, eventualmente creando un objeto nuevo

A su vez, cada uno de ellos puede tener distintas variantes, generando una colección de operadores.

El nombre de los operadores de Python en Csound sigue esta lógica:

"py" + [(opcional) prefijo de contexto] + [nombre de la acción] + [(opcional) sufijo de tiempo de ejecución]
  1. Los nombres de todos los operadores de Python comienzan con el prefijo py.
  2. El prefijo de contexto es opcional: por defecto, los operadores operan a nivel global, pero con el prefijo l operan a nivel local. Por ejemplo, pyrun y pylrun
  3. El nombre de la acción determina alguno de los 6 tipos vistos arriba
  4. El sufijo de tiempo de ejecución también es opcional: por defecto, los operadores operan a frecuencia k, pero con el sufijo i operan al momento de inicialización (init-time), y con el sufijo t operan disparados por un gatillo (trigger).

Ejemplos:

  • pyrun: ejecuta código Python a nivel global, a frecuencia k
  • pyruni: ejecuta código Python a nivel global, al momento de inicialización
  • pylrun: ejecuta código Python a nivel local, a frecuencia k
  • pylruni: ejecuta código Python a nivel local, al momento de inicialización
  • pyrunt: ejecuta código Python a nivel global, disparado por un gatillo (trigger)
  • pylrunt: ejecuta código Python a nivel local, disparado por un gatillo

pyinit

Cuando se quiera utilizar operadores de Python en Csound, debe incluirse siempre el operador pyinit en el encabezamiento de la orquesta, antes de invocar a cualquier otro operador Python.

El operador pyinit no tiene ninguna de las variantes explicadas arriba.

pyruni y pylruni

El operador pyrun y sus variantes simplemente ejecutan un bloque de código Python dentro de la orquesta de Csound.

Si el código es una instrucción que ocupa sólo una línea, puede encerrarse entre comillas (" ").
Si ocupa varias líneas, deberá delimitarse con doble llaves ({{ }}).

La siguiente orquesta de Csound utiliza el operador pyruni en el encabezamiento de la orquesta para ejecutar código Python al tiempo de inicialización de la orquesta.

Obsérvese el uso del operador pyinit al comienzo.

In [16]:
%load py01.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-ndm0
</CsOptions>
<CsInstruments>

; se inicializa el intérprete de Python
pyinit

; se ejecuta código Python a init-time
pyruni "print '*************************'"
pyruni "print '** Hola Csound Python! **'"
pyruni {{
print 'Se inicializa la orquesta'
}}
pyruni {{print '*************************' }}

; instrumento vacío
instr   1

endin

</CsInstruments>
<CsScore>
; se prenden notas para disparar el proceso
i 1 0 0
i 1 1 0
</CsScore>
</CsoundSynthesizer>
In []:
<pre>
<CsoundSynthesizer>
<CsOptions>
-ndm0
</CsOptions>
<CsInstruments>

; se inicializa el intérprete de Python
pyinit

; se ejecuta código Python a init-time
pyruni "print '*************************'"
pyruni "print '** Hola Csound Python! **'"
pyruni {{
print 'Se inicializa la orquesta'
}}
pyruni {{print '*************************' }}

; instrumento vacío
instr   1

endin

</CsInstruments>
<CsScore>
; se prenden notas para disparar el proceso
i 1 0 0
i 1 1 0
</CsScore>
</CsoundSynthesizer>
</pre>
In [25]:
!csound py01.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.03.2 (double samples) Nov  8 2014
libsndfile-1.0.25
UnifiedCSD:  py01.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
sorting score ...
	... done
--Csound version 6.03.2 (double samples) Nov  8 2014
displays suppressed
0dBFS level = 32768.0
*************************
** Hola Csound Python! **
Se inicializa la orquesta
*************************
orch now loaded
audio buffered in 256 sample-frame blocks
not writing to sound disk
SECTION 1:
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:      0.0
	   overall samples out of range:        0
0 errors in performance
no sound written to disk

La siguiente orquesta utiliza el operador pyruni en el encabezamiento y dentro de los instrumentos, en su variante global (pyruni) y local (pylruni).

In [2]:
%load py02.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-ndm0
</CsOptions>
<CsInstruments>

; se inicializa el intérprete de Python
pyinit

; se ejecuta código Python en el encabezamiento
pyruni {{
a = 1
b = 2
print "********************************"
print "en el encabezamiento: a + b = " + str(a+b)
print "********************************"
}}


; se ejecuta código Python dentro del instrumento 1
; el valor de las variables es global
instr   1
pyruni {{a = 3
b = 4
print "en el instrumento 1: a + b = " + str(a+b)}} 
endin

; se ejecuta código Python dentro del instrumento 2
; el valor de las variables es local
instr   2
pylruni {{a = 5
b = 6
print "en el instrumento 2: a + b = " + str(a+b)}} 
endin


; se ejecuta código Python dentro del instrumento 2
instr   3
pyruni {{print "en el instrumento 3: a + b = " + str(a+b)}}
endin


</CsInstruments>

<CsScore>

i1 0 0
i2 1 0
i3 2 0
i1 3 0

</CsScore>
</CsoundSynthesizer>
In [29]:
!csound py02.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.03.2 (double samples) Nov  8 2014
libsndfile-1.0.25
UnifiedCSD:  py02.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
sorting score ...
	... done
--Csound version 6.03.2 (double samples) Nov  8 2014
displays suppressed
0dBFS level = 32768.0
********************************
en el encabezamiento: a + b = 3
********************************
orch now loaded
audio buffered in 256 sample-frame blocks
not writing to sound disk
SECTION 1:
en el instrumento 1: a + b = 7
en el instrumento 2: a + b = 11
en el instrumento 3: a + b = 7
en el instrumento 1: a + b = 7
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:      0.0
	   overall samples out of range:        0
0 errors in performance
no sound written to disk

Se puede observar que:

  1. el código en el encabezamiento se ejecuta solamente al inicializar la orquesta
  2. el código dentro de los instrumentos se ejecuta al inicializar cada nota del instrumento respectivo
  3. por defecto, las variables adquieren valores globales
  4. cuando se usa la variante local (pylruni), las variables adquieren valores locales

pyrun

El operador pyrun, sin sufijo de tiempo de ejecución, opera a tiempo de frecuencia de control (k-rate).

In [1]:
%load py03.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-ndm0
</CsOptions>
<CsInstruments>

sr=44100
kr=100

; se inicializa el intérprete de Python
pyinit

; se ejecuta código Python a init-time
pyruni "print '*************************'"
pyruni "print '** Se inicializa a = 0 **'"
pyruni "print '*************************'"
pyruni "a = 0"

instr 1
    ; se incrementa la variable "a" a k-rate
    pyrun "a = a + 1" 
endin


instr 2
    ; imprime el valor de a
    pyruni {{print "a = " + str(a)}}
endin


</CsInstruments>
<CsScore>

i 1 0 1  ; incrementa a durante un segundo
i 2 1 0  ; imprime el valor de a

i 1 2 1  ; incrementa a durante otro segundo
i 2 2.5 0  ; imprime el valor de a

</CsScore>
</CsoundSynthesizer>
In [3]:
!csound py03.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.02.0 (double samples) Jan 25 2014
libsndfile-1.0.25
UnifiedCSD:  py03.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
sorting score ...
	... done
Csound version 6.02.0 (double samples) Jan 25 2014
displays suppressed
0dBFS level = 32768.0
*************************
** Se inicializa a = 0 **
*************************
orch now loaded
audio buffered in 256 sample-frame blocks
not writing to sound disk
SECTION 1:
a = 100
a = 150
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:      0.0
	   overall samples out of range:        0
0 errors in performance
no sound written to disk

pyrunt

Con el operador pyrunt podemos controlar que la ejecución del código Python se realice disparada por un gatillo o trigger.

La sintaxis es:

pyrunt   ktrigger, "expresion"
In [4]:
%load py04.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-ndm0
</CsOptions>
<CsInstruments>

sr=44100
kr=100

; se inicializa el intérprete de Python
pyinit

instr 1
    ; se inicializa la variable "a"
    pyruni "print 'se inicializa a = 0'"
    pyruni "a = 0"
    ; se genera un gatillo con frecuencia p4
    ktrig   metro   p4
    ; se incrementa "a" controlado por el gatillo
    pyrunt ktrig, "a = a + 1" 
    ; y se imprime su valor
    pyrunt ktrig, "print a"
endin


</CsInstruments>
<CsScore>

i1 0 1 5 ; incrementa la variable 5 veces por segundo
i1 1 1 10 ; incrementa la variable 10 veces por segundo

</CsScore>
</CsoundSynthesizer>
In [33]:
!csound py04.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.03.2 (double samples) Nov  8 2014
libsndfile-1.0.25
UnifiedCSD:  py04.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
sorting score ...
	... done
--Csound version 6.03.2 (double samples) Nov  8 2014
displays suppressed
0dBFS level = 32768.0
orch now loaded
audio buffered in 256 sample-frame blocks
not writing to sound disk
SECTION 1:
se inicializa a = 0
1
2
3
4
5
se inicializa a = 0
1
2
3
4
5
6
7
8
9
10
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:      0.0
	   overall samples out of range:        0
0 errors in performance
no sound written to disk

pyeval

El operador pyeval y sus variantes sirven para evaluar una expresión de Python y asignar su valor a una variable de Csound.

Hay que cuidar que la expresión evalúe a un número de coma flotante.

In [17]:
%load py05.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-d
</CsOptions>
<CsInstruments>

sr=44100
ksmps=1
0dbfs = 1

; se inicializa el intérprete de Python
pyinit

; se importa en módulo random
pyruni  "import random"

instr 1
    ; se genera un gatillo con frecuencia p4
    ktrig   metro   p4
    ; el gatillo dispara la generación de un número aleatorio
    pyrunt ktrig, "a = float(random.randrange(200, 500))"
    pyrunt ktrig, "print a"
    ; la variable "a" se asigna a la variable "krand"
    krand  pyeval "a"
    kfreq  port   krand, 0.001
    asig   oscil   0.5, kfreq, 1
    kamp   linen   1, 0.05, p3, 0.1
           out     asig*kamp
endin


</CsInstruments>
<CsScore>

f1 0 16384 10 1
i1 0 1 4 ; incrementa la variable 5 veces por segundo
i1 1 1 16 ; incrementa la variable 10 veces por segundo

</CsScore>
</CsoundSynthesizer>
In [18]:
!csound py05.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.03.2 (double samples) Nov  8 2014
libsndfile-1.0.25
UnifiedCSD:  py05.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
Elapsed time at end of orchestra compile: real: 0.002s, CPU: 0.002s
sorting score ...
	... done
Elapsed time at end of score sort: real: 0.002s, CPU: 0.002s
--Csound version 6.03.2 (double samples) Nov  8 2014
displays suppressed
0dBFS level = 1.0
orch now loaded
audio buffered in 256 sample-frame blocks
writing 512-byte blks of shorts to test.wav (WAV)
SECTION 1:
ftable 1:
new alloc for instr 1:
209.0
485.0
311.0
206.0
B  0.000 ..  1.000 T  1.000 TT  1.000 M:  0.50000
262.0
205.0
267.0
383.0
430.0
277.0
207.0
321.0
446.0
296.0
357.0
200.0
244.0
350.0
408.0
469.0
B  1.000 ..  2.000 T  2.000 TT  2.000 M:  0.50000
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:  0.50000
	   overall samples out of range:        0
0 errors in performance
Elapsed time at end of performance: real: 0.925s, CPU: 0.833s
256 512 sample blks of shorts written to test.wav (WAV)

pyassign

El operador pyassign y sus variantes asignan un valor a una variable de Python, creándola en caso de que no exista previamente.

In [20]:
%load py06.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-nd
</CsOptions>
<CsInstruments>

pyinit

instr 1 ; asigna p4 a la variable cociente
pyassigni "cociente", p4
endin

instr 2 ; calcula la distancia en cents
pyruni {{
from math import log
cents = log(cociente,2)*1200
print "cociente: " + str(cociente)
print "cents: " + str(cents)
}}
endin

</CsInstruments>
<CsScore>
i1 0 0 1.5
i2 0 0

i1 1 0 2
i2 1 0
</CsScore>
</CsoundSynthesizer>
In [21]:
!csound py06.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.03.2 (double samples) Nov  8 2014
libsndfile-1.0.25
UnifiedCSD:  py06.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
Elapsed time at end of orchestra compile: real: 0.001s, CPU: 0.001s
sorting score ...
	... done
Elapsed time at end of score sort: real: 0.001s, CPU: 0.001s
--Csound version 6.03.2 (double samples) Nov  8 2014
displays suppressed
0dBFS level = 32768.0
orch now loaded
audio buffered in 256 sample-frame blocks
not writing to sound disk
SECTION 1:
new alloc for instr 1:
new alloc for instr 2:
cociente: 1.5
cents: 701.955000865
B  0.000 ..  1.000 T  1.000 TT  1.000 M:      0.0
cociente: 2.0
cents: 1200.0
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:      0.0
	   overall samples out of range:        0
0 errors in performance
Elapsed time at end of performance: real: 0.012s, CPU: 0.012s
no sound written to disk

pycall

El operador pycall y sus variantes pueden llamar a un objeto llamable (callable oject) de Python, por ejemplo, una función.

Si el objeto recibe argumentos, éstos pueden pasarse como parámetros adicionales del operador, según la siguiente sintaxis:

pycall   "objeto" [, val1] [, val2] [, ...]

El siguiente ejemplo muestra una orquesta con una función que recibe un argumento e imprime su valor.
La función no devuelve ningún valor (objeto None).

In [22]:
%load py07.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-dnm0
</CsOptions>
<CsInstruments>

pyinit

pyruni {{
def instr_num(arg):
    '''función que imprime el parámetro recibido'''
    print "se prende una nota en el instrumento " + str(arg)
    return None
}}

instr 1
; llama a la función con el parámetro p1, número de instrumento
pycalli "instr_num", p1
endin

</CsInstruments>
<CsScore>
i1.1 0 1
i1.2 1 1
</CsScore>
</CsoundSynthesizer>
In [23]:
!csound py07.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.03.2 (double samples) Nov  8 2014
libsndfile-1.0.25
UnifiedCSD:  py07.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
sorting score ...
	... done
--Csound version 6.03.2 (double samples) Nov  8 2014
displays suppressed
0dBFS level = 32768.0
orch now loaded
audio buffered in 256 sample-frame blocks
not writing to sound disk
SECTION 1:
se prende una nota en el instrumento 1.1
se prende una nota en el instrumento 1.2
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:      0.0
	   overall samples out of range:        0
0 errors in performance
no sound written to disk

El objeto llamado puede devolver uno o más valores, hasta un máximo de 8. En este caso deberá usarse respectivamente alguno de los operadores pycall1 a pycall8 (o sus variantes), para poder asignar los valores que devuelve el objeto a variables de Csound.

Las sintaxis respectivas siguen el modelo de estos ejemplos:

ires           pycall1i    "funcion" [, iarg1, .....]
kr1, kr2       pycall2     "funcion" [, karg1, .....]
kr1, kr2, kr3  pycall3t    ktrigger, "funcion" [, karg1, .....]

Etcétera.

En el siguiente ejemplo se define la función "promedio", que recibe dos argumentos y devuelve un valor.
Se invoca con pycall1i.

In [25]:
%load py08.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-dnm0
</CsOptions>
<CsInstruments>

pyinit

pyruni {{
def promedio(a, b):
    avrg = (a+b)/2
    return avrg
}}

instr 1
iavrg  pycall1i "promedio", p4, p5
print  iavrg
endin

</CsInstruments>
<CsScore>
i 1 0 1  100  200
i 1 1 1  1000 2000
</CsScore>
</CsoundSynthesizer>
In [26]:
!csound py08.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.03.2 (double samples) Nov  8 2014
libsndfile-1.0.25
UnifiedCSD:  py08.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
sorting score ...
	... done
--Csound version 6.03.2 (double samples) Nov  8 2014
displays suppressed
0dBFS level = 32768.0
orch now loaded
audio buffered in 256 sample-frame blocks
not writing to sound disk
SECTION 1:
instr 1:  iavrg = 150.000
instr 1:  iavrg = 1500.000
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:      0.0
	   overall samples out of range:        0
0 errors in performance
no sound written to disk

En el siguiente ejemplo la función recibe un argumento y devuelve dos valores
Es invocada con pycall2i.

In [27]:
%load py09.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-dnm0
</CsOptions>
<CsInstruments>

pyinit

pyruni {{
def oct_pc(nota_midi):
    '''devuelve la octava y la clase de altura de una nota midi'''
    oct = (nota_midi//12)-1
    pc = nota_midi%12
    return oct, pc
}}

instr 1
ioct, ipc   pycall2i "oct_pc", p4
print  ioct, ipc
endin

</CsInstruments>
<CsScore>
i 1 0 1  60
i 1 1 1  66
</CsScore>
</CsoundSynthesizer>
In [28]:
!csound py09.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.03.2 (double samples) Nov  8 2014
libsndfile-1.0.25
UnifiedCSD:  py09.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
sorting score ...
	... done
--Csound version 6.03.2 (double samples) Nov  8 2014
displays suppressed
0dBFS level = 32768.0
orch now loaded
audio buffered in 256 sample-frame blocks
not writing to sound disk
SECTION 1:
instr 1:  ioct = 4.000  ipc = 0.000
instr 1:  ioct = 4.000  ipc = 6.000
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:      0.0
	   overall samples out of range:        0
0 errors in performance
no sound written to disk

pyexec

El operador pyexec y sus derivados pueden ejecutar un script de Python guardado como un archivo en el disco duro.
Recibe como argumento el nombre del archivo, como ruta relativa o absoluta.

La siguiente orquesta de Csound ejecuta el script fecha.py, que imprime la fecha y hora al momento de ser ejecutado.

In [14]:
%load fecha.py
In []:
# imprime la fecha y hora local al momento de ejecutarse
from datetime import datetime
fecha = datetime.now().strftime('%Y-%m-%d, %H:%M:%S')
print "*****************************************"
print "fecha y hora actual: " + str(fecha)
print "*****************************************"
In [29]:
%load py10.csd
In []:
<CsoundSynthesizer>
<CsOptions>
-dnm0
</CsOptions>
<CsInstruments>

pyinit

; se ejecuta el script Python "fecha.py"
pyexeci "fecha.py"

instr 1
endin

</CsInstruments>
<CsScore>
i1 0 0
</CsScore>
</CsoundSynthesizer>
In [30]:
!csound py10.csd
virtual_keyboard real time MIDI plugin for Csound
0dBFS level = 32768.0
Csound version 6.03.2 (double samples) Nov  8 2014
libsndfile-1.0.25
UnifiedCSD:  py10.csd
STARTING FILE
Creating options
Creating orchestra
Creating score
rtaudio: ALSA module enabled
rtmidi: ALSA Raw MIDI module enabled
sorting score ...
	... done
--Csound version 6.03.2 (double samples) Nov  8 2014
displays suppressed
0dBFS level = 32768.0
*****************************************
fecha y hora actual: 2014-11-20, 12:38:25
*****************************************
orch now loaded
audio buffered in 256 sample-frame blocks
not writing to sound disk
SECTION 1:
Score finished in csoundPerform().
inactive allocs returned to freespace
end of score.		   overall amps:      0.0
	   overall samples out of range:        0
0 errors in performance
no sound written to disk

Otros recursos

Andrés Cabrera: Using Python Inside Csound - An introduction to the Python opcodes
Csound Journal - Issue 6, 2007
http://csounds.com/journal/issue6/pythonOpcodes.html

Csound FLOSS Manual: Python inside Csound
http://en.flossmanuals.net/csound/using-python-inside-csound/