Skip to content

opcode

Defines the start of user-defined opcode block.

Defining opcodes

The opcode and endop statements allow defining a new opcode that can be used the same way as any of the built-in Csound opcodes, using the Csound language itself. There are two declaration forms for UDOs: classic and modern. Besides the different syntaxes, these forms may also imply different call semantics (see below).

A user-defined opcode definition or its protype declaration must precede the instrument (or other opcode) from which it is used. It is also possible to call the opcode from itself. Such recursive calls are limited by default to a depth of 1000, but this can be adjusted by the --recursion-depth= option (see options).

Input and output arguments may be passed by copy or by reference (see call semantics below). Some parameters are automatically copied at initialization:

  • all p-fields up to and including the highest-numbered one referenced in the calling instrument
  • extra time (see also xtratim, linsegr, and related opcodes). This may affect the operation of linsegr/expsegr/linenr/envlpxr in the user-defined opcode block.
  • MIDI parameters, if there are any.

The release flag (see the release opcode) is copied at performance time from the calling instrument.

Modifying the note duration in the opcode definition by assigning to p3, or using ihold, turnoff, xtratim, linsegr, or similar opcodes will also affect the caller instrument. Changes to MIDI controllers (for example with ctrlinit) will also apply to the instrument from which the opcode was called.

Use the setksmps opcode to set the local ksmps directly or alternative oversample and undersample to set local sampling/control rates, indirectly.

The xin and xout allow communication with the calling instrument. The former is only required in classic UDO syntax

ℹ Tip

You can create UDOs which take no inputs or outputs by using 0 instead of a string, or void instead of arguments/output types in the modern syntax for,

📝 Notes

  • xin and xout should be used only once, and xin should precede xout.
  • These opcodes actually run only at i-time to set up the connections to the caller. Performance time copying or referencing is done by the user opcode call. This means that skipping xin or xout at perf time (e.g. with kgoto) etc has no effect, while skipping at i-time (e.g. with igoto) affects both init and performance time operation.

Syntax

opcode name(arg:type, ...):(out-type,...) 
opcode name, outtypes, intypes   

Initialization

name -- name of the opcode. It may consist of any combination of letters, digits, and underscore but should not begin with a digit. Opcodes may be overloaded, i.e. use the same name with different arguments (numbers/types). Some reserved words (like instr and endin) cannot be redefined.

intypes -- list of input types, any combination of the characters: a, f, k, O, P, V, K, i, o, p, and j. A single 0 character can be used if there are no input arguments. Double quotes and delimiter characters (e.g. comma) are not needed.

arg:type -- comma-separated list of arguments with their types, void may be used in place of an empty list.

out-type -- comma-separated list of output types (as expected by xout), void is used for an empty list.

The meaning of the various intypes is shown in the following table:

Type Description Variable Types Allowed Updated At
a a-rate variable a-rate a-rate
f f-sig variable f-sig k-rate
i i-rate variable i-rate i-time
j optional i-time, defaults to -1 i-rate, constant i-time
k k-rate variable k- and i-rate, constant k-rate
O optional k-rate variable, defaults to 0 k- and i-rate, constant k-rate
P optional k-rate variable, defaults to 1 k- and i-rate, constant k-rate
V optional k-rate variable, defaults to 0.5 k- and i-rate, constant k-rate
J optional k-rate variable, defaults to -1 k- and i-rate, constant k-rate
K k-rate with initialization k- and i-rate, constant i-time and k-rate
o optional i-time, defaults to 0 i-rate, constant i-time
p optional i-time, defaults to 1 i-rate, constant i-time
S string variable k- and i-rate string, constant i-time and k-rate

outtypes -- list of output types. The format is the same as in the case of intypes.

Here are the available outtypes:

Type Description Variable Types Allowed Updated At
a a-rate variable a-rate a-rate
f f-sig variable f-sig k-rate
i i-rate variable i-rate i-time
k k-rate variable k-rate k-rate
K k-rate with initialization k-rate i-time and k-rate
S string variable k- and i-rate string i-time and k-rate

Arrays are set by the relevant builtin type followed by square brackets ([]). In addition to these, the second form of UDO declaration also allows for any other builtin or user-defined types to be employed for input and output types. These are not supported in the classic UDO form.

The maximum allowed number of output arguments is 256.

The input parameters can be read with xin, and the output is written by xout opcode. Only one instance of these units is normally used. The number and type of arguments for xin and xout must be the same as in the declaration of the user-defined opcode block (see tables above). Accessing the engine main input and output buffers through the in and out (etc) opcodes is discouraged.

Performance

The syntax of a user-defined opcode block is as follows. Classic syntax:

opcode  name, outtypes, intypes
xinarg1 [, xinarg2] [, xinarg3] ... [xinargN]  xin
[setksmps  iksmps]
... the rest of the instrument's code.
xout  xoutarg1 [, xoutarg2] [, xoutarg3] ... [xoutargN]

Modern syntax:

opcode name(arg:type, ...):(out-type,...) 
// no need for xin - input arg variables  given at declaration
xout(outarg, ...)
endop

The new opcode can then be used like any other opcode in Csound:

[xoutarg1] [, xoutarg2] ... [xoutargN] name  [xinarg1] [, xinarg2] ... [xinargN]
[xoutarg1] [, xoutarg2] ... [xoutargN] = name([xinarg1] [, xinarg2] ... [xinargN])

📝 Note

The opcode call is always executed both at initialization and performance time, even if there are no a- or k-rate arguments. If there are many user opcode calls that are known to have no effect at performance time in an instrument, then it may save some CPU time to jump over groups of such opcodes with kgoto.

Call Semantics

The classic form of the UDO declaration defines an opcode with pass-by-copy call semantics. This means all inputs are copied into UDO local variables (through the xin opcode), and the outputs as given to xout are also copied to the caller. This differs from built-in (binary-compiled) opcodes where the call semantics is pass-by-reference. In this case, the reference to the variable is passed (its memory location), and no data is copied.

The new UDO form, defined by the function-style syntax, implements a similar pass-by-reference call semantics that aligns with how built-in opcodes work. The main implications are that not data is copied, therefore there are less overheads in the use of UDOs; the memory is shared, and so UDOs may modify external data directly.

Pass-by-copy is enforced on all cases where there are local ksmps or local sr/kr conditions (setksmps, oversample, undersample).

Examples

Here is an example of a user-defined opcode. It uses the file opcode.csd.

Example of a user-defined opcode.
<CsoundSynthesizer>
<CsOptions>
; Select audio/midi flags here according to platform
-odac    ;;;realtime audio out
;-iadc    ;;;uncomment -iadc if realtime audio input is needed too
; For Non-realtime ouput leave only the line below:
; -o opcode_example.wav -W ;;; for file output any platform
</CsOptions>
<CsInstruments>

sr = 44100
ksmps = 32
nchnls = 2
0dbfs  = 1

/* example opcode 1: simple oscillator */

        opcode Oscillator, a, kk

kamp, kcps      xin             ; read input parameters
a1      vco2 kamp, kcps         ; sawtooth oscillator
        xout a1                 ; write output

        endop

// modern syntax
opcode Oscillator2(amp:k, freq:k):a
  xout(vco2(amp,freq))
endop

/* example opcode 2: lowpass filter with local ksmps */

        opcode Lowpass, a, akk

        setksmps 1              ; need sr=kr
ain, ka1, ka2   xin             ; read input parameters
aout    init 0                  ; initialize output
aout    =  ain*ka1 + aout*ka2   ; simple tone-like filter
        xout aout               ; write output

        endop

// modern syntax 
opcode Lowpass2(x:a,c1:k,c2:k):a
 n:k = 0
 y:a init 0
 ynm1:k init 0
 while n < ksmps do
  y[n] = x[n]*c1 + ynm1*c2
  ynm1 = y[n]
  n+=1
 od
 xout(y)
endop



/* example opcode 3: recursive call */

        opcode RecursiveLowpass, a, akkpp

ain, ka1, ka2, idep, icnt       xin     ; read input parameters
        if (icnt >= idep) goto skip1    ; check if max depth reached
ain     RecursiveLowpass ain, ka1, ka2, idep, icnt + 1
skip1:
aout    Lowpass ain, ka1, ka2           ; call filter
        xout aout                       ; write output

        endop

// modern syntax
opcode RecursiveLowpass2(sig:a,c1:k,c2:k,dep:p,cnt:p):a
  if cnt < dep then
   sig = RecursiveLowpass2(sig,c1,c2,dep,cnt+1)
  endif
   xout(Lowpass2(sig,c1,c2))
endop

/* example opcode 4: de-click envelope */
        opcode DeClick, a, a

ain     xin
aenv    linseg 0, 0.02, 1, p3 - 0.05, 1, 0.02, 0, 0.01, 0
        xout ain * aenv         ; apply envelope and write output

        endop

// modern syntax
opcode DeClick2(sig:a):a
 xout(sig*linseg(0, 0.02, 1, p3 - 0.05, 1, 0.02, 0, 0.01, 0))
endop


/* instr 1 uses the example opcodes - classic UDO, pass by copy */

        instr 1
kamp    =  .7                ; amplitude
kcps    expon 50, p3, 500       ; pitch
a1      Oscillator kamp, kcps                   ; call oscillator
kflt    linseg 0.4, 1.5, 0.4, 1, 0.8, 1.5, 0.8  ; filter envelope
a2      RecursiveLowpass a1, kflt, 1 - kflt, 10 ; 10th order lowpass
a1      DeClick a2
        out a1, a1
        endin


// instr 2 uses the modern form, pass by reference */
        instr 2
kamp    =  .7                //  amplitude
kcps = expon(50, p3, 500)    // pitch
a1 =  Oscillator2(kamp, kcps)  // call oscillator
kflt = linseg(0.4, 1.5, 0.4, 1, 0.8, 1.5, 0.8)  // filter envelope
a1  = RecursiveLowpass2(a1, kflt, 1 - kflt, 10) // 10th order lowpass
a1  = DeClick2(a1)
        out(a1, a1)

         endin



</CsInstruments>
<CsScore>

i 1 0 4
i 2 4 4
e9              ;extra second before quitting

</CsScore>
</CsoundSynthesizer>

Here is another example of a user-defined opcode. It uses the file opcode_f.csd.

Second example of a user-defined opcode.
<CsoundSynthesizer>
<CsOptions>
; Select audio/midi flags here according to platform
-odac    ;;;realtime audio out
;-iadc    ;;;uncomment -iadc if realtime audio input is needed too
; For Non-realtime ouput leave only the line below:
; -o opcode_f.wav -W ;;; for file output any platform
</CsOptions>
<CsInstruments>

sr = 44100
ksmps = 32
0dbfs =  1
nchnls = 2

; example by Luis Salgueiro and joachim heintz

opcode Transpose, a, fk    ; transpose a monophonic input signal through FFT.
  fsig, kInterval xin
  fScaled pvscale fsig, semitone(kInterval)
  aTransposed pvsynth fScaled
  xout aTransposed
endop

// modern syntax
opcode Transpose2(sig:f,interval:k):a
  scaled:f = pvscale(sig,semitone(interval))
  xout(pvsynth(scaled))
endop

opcode Stretch,f,ik
  iTab, kSpeed xin
  fStretch pvstanal kSpeed, 1, 1, iTab
  xout fStretch
endop

// modern syntax
opcode Stretch2(tab:i,speed:k):f
 xout(pvstanal(speed,1,1,tab))
endop


giFox ftgen 0, 0, 0, 1, "fox.wav", 0, 0, 0

instr 1
  aSnd diskin2 "fox.wav"
  fSnd pvsanal aSnd, 1024, 256, 1024, 1
  aTransposed Transpose fSnd, p4   ; p4 = transposition in semitones
  outall aTransposed/2
endin

instr 2
  fStretch Stretch giFox, transeg:k(1,p3,-3,0)
  aStretch pvsynth fStretch
  outall aStretch
endin


instr 3
  aSnd diskin2 "fox.wav"
  fSnd pvsanal aSnd, 1024, 256, 1024, 1
  aTransposed Transpose2 fSnd, p4   ; p4 = transposition in semitones
  outall aTransposed/2
endin

instr 4
  fStretch Stretch2 giFox, transeg:k(1,p3,-3,0)
  aStretch pvsynth fStretch
  outall aStretch
endin

</CsInstruments>
<CsScore>
i1 0 3 0
i1 . . 7
i1 . . 12
i2 4 8
i3 12 3 0
i3 . . 7
i3 . . 12
i4 16 8
e
</CsScore>
</CsoundSynthesizer>

See Also

User Defined Opcodes (UDO)

Credits

Author: originally introduced by Istvan Varga, 2002, based on code by Matt J. Ingalls; extended/improved by V Lazzarini, Steven Yi. New UDO syntax/pass-by-reference by Steven Yi.

New in version 4.22 New UDO syntax in version 7.