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
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. | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
Here is another example of a user-defined opcode. It uses the file opcode_f.csd.
See Also
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.