User-Defined Opcodes

The opcodes included with Csound are written in the C programming language. While they’ll do lots and lots of things, you may not always be able to find one that does precisely what you need. If you’re fluent in C, you can write a new opcode using the guidelines in the manual, compile it, and place it in the plugins64 directory of your Csound program directory. Some users do precisely this.

If you’re not conversant with C, you may be delighted to find that there’s an easier way. You can create new opcodes using the Csound language itself and include them in your .csd file. This is done using a special syntax for creating user-defined opcodes (UDOs). In the next couple of pages, you’ll learn how to do this.

Technically, you can’t do anything by writing your own opcode that you couldn’t do just by writing the code for the new opcode as part of an instrument. (This is not 100% true, as we’ll see, but it’s a good approximation.) So why write a UDO? There are two main reasons: encapsulation and reusability.

Encapsulation makes your code easier to read and maintain. Instead of writing 30 lines of code in the instrument, you write a one-line call to a UDO. The UDO is in a different part of your source code file. Once you’ve debugged it, you don’t need to look at or worry about it. In addition, if five different instruments in your orchestra need to make use of the same complex procedure, you only need to write the code once. All five instruments can use the same UDO, exactly the way they would use any other opcode. On that basis, a UDO is a bit like a GOSUB statement in BASIC.

Once you’ve created a useful opcode, such as your own custom reverb or filter bank, you can save it to your hard drive as a separate file. Having done that, you don’t even need to open the file and copy the code into your next project. To reuse it, just use an #include directive, and the file will be included in your orchestra when the orchestra is compiled. After creating a file called my_opcodes.txt, you would put this line in the orchestra header:


  #include “my_opcodes.txt”

Csound will look for the included file first in the same directory as the .csd file and then in the directory you’ve specified using the INCDIR environment variable. (In CsoundQt, this directory can be set in the Environment tab of the Configuration window.)

User-defined opcodes will generally be less efficient at the code level than opcodes written in C. That is, they’ll use up more CPU time when running. Generally, the convenience factor outweighs the performance hit.

User-defined opcodes are created with the opcode opcode. The prototype of this opcode is a little different from other prototypes. It looks like this:


  opcode name, outtypes, intypes

What this says is that we need to give the opcode a name (in order to be able to call it from our instrument code). We also need to tell Csound what type(s) of input(s) and output(s) the opcode will receive and send. The name can consist of any combination of letters, digits, and underscore characters, but should not begin with a digit. It may be a good idea to start the name of your opcode with a capital letter, as this will make the fact that it’s a UDO more obvious when you’re reading your code. Names such as myfilter and my_filter are legal, but MyFilter works better visually.

The outtypes and intypes of the UDO are defined using the familiar letters i, k, a, and S, but with a couple of extra possibilities. Here, for instance, is an opcode that accepts three k-rate inputs and outputs two a-rate outputs:


  opcode MyOp, aa, kkk
  kdata1, kdata2, kdata3 xin
  ; processing code would go here, including lines that create two audio
  ; variables
  xout audio1, audio2
  endop

The first thing to note about this is that there is a comma after the name of the UDO. There won’t be a comma after the name when you use it in your instrument code, so omitting the comma here is an easy mistake to make. The inputs are assigned to local variables using the xin opcode, and the outputs are transmitted back to the calling instrument using the xout opcode. The number of arguments to xin and xout must always match the opcode header. The opcode ends with endop.

The line containing xin should be the first line of the code in the UDO, and the line containing xout should be the last line. It’s legal to put code before xin or after xout, but if you do this, your code may not run the way you expect it to. This is because xin and xout are actually init-time opcodes. They initialize the data connections between the UDO and the instrument that invokes it, but they’re ignored during k-rate operations. As a simple illustration, consider this rather silly opcode:


  opcode Mult, k, kk
  kval1, kval2 xin
  kresult = kval1* kval2
  kstorage = kresult
  kresult = 0
  xout kresult
  kresult = kstorage
  endop

Logically, you might expect it to output 0, since kresult is set to a value of 0 just before xout is called. But the UDO will output the product of the two inputs, which probably won’t be 0, because the last line restores kresult to its previous value before the output is sent to the calling instrument.

An opcode with neither inputs nor outputs is legal, though it’s not likely to be very useful. If you don’t need ins or outs, put 0’s in those arguments. For example:


  giSine ftgen 0, 0, 8192, 10, 1

  opcode MyTone, 0, 0
  asig oscil 0.5, 220, giSine
  outs asig, asig
  endop
  instr 11
  MyTone
  endin

This produces the same signal as if the calls to oscil and outs had been in instrument 11 itself. Even if your opcode needs no inputs, it would be better practice to pass the audio output(s) back to the calling instrument and use outs in the instrument.

As with many of Csound’s built-in opcodes, UDOs can be given default values for optional i-time input arguments. If an i-time argument is included among the inputs, it can be symbolized by j (in which case the default will be −1), o (which defaults to zero), or p (which defaults to 1). These are the only default values that can be used, but they cover a lot of territory.

For example, suppose the opcode is defined this way:


  opcode MyOp, aa, kjp

The k-rate input will always be required. The instrument calling the opcode can give a non-default value to the two i-time arguments, but it’s not required to do so. If it doesn’t, then following this:


  opcode MyOp, aa, kjp
  kdata1, idata2, idata3 xin

the variable idata2 will have a value of −1 and the variable idata3 will have a value of 1.

If you use a capital K in defining the opcode’s inputs, the value will be read from the calling instrument during the initialization pass and then in each k-period. If you use a lowercase k, the input will not be read during the initialization pass, which may result in unexpected behavior.

The one thing that makes a UDO different from ordinary code, which was alluded to earlier, is this: An opcode can be given a lower value of ksmps. This can sometimes provide better audio quality when the opcode is engaged in complex DSP—if you’re writing your own reverb, for example. The orchestra as a whole might have ksmps=10, yet the UDO could run with ksmps=1.

This is done using the setksmps opcode:


  setksmps 1

The new, temporary value of ksmps must always be a factor of the orchestra’s setting for this value. If the orchestra has ksmps=10, for example, then a value of 5, 2, or 1 could be used in the UDO.

As the manual notes, when ksmps is set to a lower value, global a-rate operations must not be used in the opcode. You can’t access ga- variables or use the a-rate zak opcodes, for instance.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.237.24