Skip to content

UDO annotations

Eugene Cherny edited this page Mar 8, 2018 · 4 revisions

NB! This is a work in progress specification of a new format of UDO annotations. It may not have been implemented yet.

To properly map parameters of the modules in a Nord Modular patch to Csound code, the converter requires a few annotations to be added to the user defined opcodes (UDOs) implementing those modules. The annotations describe:

  • How to map a module parameter from MIDI (as it’s stored in the patch file) to the actual value that is meaningful in the context of a module (pitch, milliseconds, amplitude in different scales, etc.).
  • Order and type (control- or audio-rate) of the inlets and outlets in a module.
  • Human readable parameter names to assist the module implementation process.
  • TODO information.

This page documents what type of annotations the converter supports, as well as how to use them.

Quick and dirty cheat-sheet

Annotations for the LevAmp module (taken from the tutorial)

;@ map Amp          s LinLogSwitch LVLamp CLAEXP 
;@ map LinLogSwitch d              BUT002

;@ ins k:In
;@ outs k:Out
opcode LevAmp,0,iiii
  iKnob, iScale, izIn, izOut xin
  iMult = iKnob  ; TODO properly implement logarithmic mapping
  kIn zkr izIn
  zkw kIn*iMult
endop

;@ ins a:In
;@ outs a:Out
opcode LevAmp,0,iiii
  iKnob, iScale, izIn, izOut xin
  iMult = iKnob
  aIn zar izIn
  zaw aIn*iMult
endop

Understanding annotations

Annotations are non-block Csound comments that conform to a special format. In general, an annotation looks like this:

;@ annotationType argument1 argument2 ...

It starts with a semicolon immediately followed by the ‘at’ symbol (;@). Csound will treat such line as comment, but the ‘pch2csd’ tool recognizes it as special and will try to process it. The next token (annotationType) identifies the type of information this line contains followed by comma-separated arguments, which represent the data used by the converter to generate code.

There aren’t many types of annotations, but understanding them is a must for those who wish to implement modules (and less of a must for hackers).

map annotation

Nord Modular’s patch file stores module parameters as raw MIDI values (i.e. integers in range 0–127). By themselves they are not very useful for controlling real things like oscillators or filters, so the MIDI values should be mapped to something more sensible like Hertz, milliseconds, etc. Unfortunately the mapping information is not present in the patch file, so it must be provided in each UDO that implements a module.

We have a number of tables to assist the mapping of raw MIDI values to different value ranges (see value_maps.ods spreadsheet). They were created by observing the on-screen controls in the Nord’s editor while changing at the same time and writing down the displayed values in the whole MIDI range (a lot of work!).

The map annotations connects the module parameters to the appropriate mapping table. They are typically placed in the beginning of the txt template file. There are two types of mappings:

  • d, or “direct”, that maps a MIDI value to a value in some table.
  • s, or “switch”, that maps a MIDI value to a value in one of a few enumerated tables; which one — depends on a value in another parameter. It’s common in the Nord’s modules to have special buttons that change behavior of a knob: switching between uni-polar and bi-polar LFO, linear and logarithmic volume scaling, and so on.

The syntax differs slightly for these mapping types. Let’s see an example (square brackets mean optional parameter):

;@ map [MapName] d TableName
;@ map [MapName] s otherMapNameOrIndex TableName1 TableName2 ... TableName3

The first mapping is a direct one (d) — it maps a parameter to the table TableName. The second parameter is a switch (s) — it enumerates a number of tables (TableName1 TableName2 ... TableName3) and refers to another parameter which controls which table is used (otherMapNameOrIndex). This reference can be either a number of parameter (starting with 1) or a name provided in the optional argument (MapName). It’s fine for the converter to use integer parameter indices instead of human-readable names, but we typically advice humans against using it for obvious reasons.

The map annotations order should follow the parameter order in a patch. E.g. the first annotation maps the first parameter, the second annotations maps the second one, and so on.

To wrap up, here is how the mapping annotation for the LevAmp UDO implementation can look like (taken from the tutorial):

;@ map s 2 LVLamp CLAEXP 
;@ map d BUT002
opcode ...
endop
;@ map Amp s 2 LVLamp CLAEXP 
;@ map LinLog d BUT002
opcode ...
...
...
endop

mode annotation

Weirdly enough, some on-screen controls in the patch editor are not parameters, but “modes”. These include oscillator shapes, … , and others. We don’t know for sure why they are stored differently in the patch than parameters, so we treat them as different things. Luckily for you, they are much easier to annotate than parameters. The syntax is as follows:

;@ mode ModeName [Name1 Name2 ...]

As you can see, the annotation consists of mode name with an optional mode interpretation list. E.g. for the oscillator you may have the following mode

;@ mode OscShape Sine Triangle Saw Square ...

The interpretations (as well as human-readable parameter names) make your development more convenient. With -m command line-argument you can print all these values to console to debug the UDO easier.

ins and outs annotations

These annotations describe the types of inlets and outlets of a module. Their function is to declare the number of input and output buses, as well as their signal types. They are needed for the converter to properly establish connections between the modules in the zak-space, as well as to properly convert between cable types and to choose a proper UDO variant in the case of polymorphous modules. These annotations should be placed just before the opcode definition.

Syntax:

;@ ins k k a a ...
;@ ins k:ControlIn1 k:ControlIn2 a:AudioIn1 a:AudioIn1 ...

;@ outs k k a a ...
;@ outs k:ControlIn1 k:ControlIn2 a:AudioIn1 a:AudioIn1 ...

Each annotation contains a white-space-separated list of signal types (k for control-rate, a for audio-rate). You can provide human readable names for the inlets and outlets with a semicolon.