-
Notifications
You must be signed in to change notification settings - Fork 11
Making a new module
This is a work in progress, the article is not finished yet.
Note (2018-03-04, issue #7): We have recently added the ability to parse module modes, which is not reflected in this tutorial yet. The instrument code will differ a little: it will include a column with mode values after parameters but before inlets. UDO developers should treat these values as typical integer arguments.
Also, option
-m
can help you a lot in the module implementations. [Example].
The pch2csd
tool parses the Nord Modular G2 binary patch file with .pch2
extension and converts it into the .csd
file of Csound. G2's modules are
represented as user-defined opcodes, or UDOs for short. And although the
pch2csd compiler is generally working, the tool's ability to convert your
patches depends on the opcodes available. There are more than 200 modules in
NMG2 and not all of them are implemented by this project at the moment. Another
aspect is how close the UDOs simulate the Nord modules. Some of them can be
just rough approximations made only to pass compilation, others can be more
focused on the original sound. In this tutorial we briefly explain how to write
your own UDO, compatible with the converter.
The tool uses Csound in a rather unconventional way. Typically people define variables to hold sound data and write instructions to generate or process it (1):
instr 1
kEnv madsr iAtt, iDec, iSus, iRel
aVco vco2 iAmp, iFreq
aLp moogladder aVco, iCutoff*kEnv, iRes
out aLp*kEnv
endin
We see how envelope and waveforms are passed to the variables kEnv
and
aVco
, then the former is used to control the cutoff of the moogladder
.
The filter outputs variable aLp
, which is then enveloped and sent to
the output by passing the signal to the opcode out
. Our tool is built in a
such way that it doesn't require any variable to be connected to our UDOs.
Instead, it uses the zak patching system to route signals between them.
The instrument generated by the converter may look like this:
instr 1
MySaw iFreq, iAmp 1
MyFilter iCutoff, 1, 2
MyOut 2
endin
MySaw
, MyFilter
and MyOut
are the custom user-defined opcodes. The named
parameters represent static values we set in the interface. The value of 1 in
the MySaw
opcode tells it to route the oscillator output to the 1st bus of
the zak-space. The MyFilter
then reads the value from this bus and passes the
filtered signal to the bus 2. Finally, the MyOut
UDO listens to the bus 2 and
outputs the filtered signal to the system out.
Note, that the UDOs don't output anything, they just accept parameters and
number of zak-space busses, all processing happens inside the opcodes. This
approach separates the implementation and the composition of sound processing
blocks. Basically, the purpose of the pch2csd
is to connect the (user)
provided user-defined opcodes together in the zak-space.
To make new modules you need to access the source code. You can either download
it directly from the GitHub's web interface or use git
from a terminal. The
UDOs are located in the directory pch2csd/resources/modules
. The file name
consists of number representing the type ID of the Nord module and the .txt
extension, e.g. 194.txt
. Assuming you have Python 3.5 and the project
dependencies installed, you can run the tool directly from the sources by
invoking the pch2csd/app.py
script, like this:
python3 pch2csd/app.py -h
For the sake of simplicity we will implement a trivial module, the level amplifier, or LevAmp:
This module has two parameters, an amplification knob and linear / logarithmic scale switch, one input and one output. The module multiplies the input signal by the value set by the knob. The blue color of the input and output pins means that the module works with control rate signals. But this module, as many other ones in G2, can be polymorphous, i.e. can change the pin types, from the control to the audio rate (blue to red) depending on the type of connected cables:
Other examples of such modules include, for example, EnvADSR and all mixers:
Note, that all pins in the mixer change their colors, but only two are changed
in the envelope. Last but not least, when connected to non-convertable pin, the
rate of the signal passing through a cable is converted on the fly. This
conversion is handled by the pch2csd
tool automatically.
First we make a simple patch consisting of a single LevAmp module and checking its type id:
$ pch2csd -p LevAmp.pch2
...
Name ID Type Parameters Area
------ ---- ------ ------------ ------
LevAmp 1 81 [64, 0] VOICE
...
So, the LevAmp integer type is 81. This module makes it simple to understand, that the first parameter is a knob, and the second is the button. Other modules may experimentation to understand the mappings between parameters and the knobs.
Now let's check is there is already an UDO template for this module:
$ pch2csd --check-template 81
Checking UDO for the type ID 81
Found module of this type: LevAmp
ERROR UdoTemplate(LevAmp, 81.txt): no opcode 'args' annotations were found in the template
The error message tells us that the .txt
file with the UDO exists, but it
hasn't been properly implemented yet. This message in particular informs us
that no special opcode annotations were found. The args
annotations declare
the number of UI parameters, as well as the number and types (audio or control
rates) of inlets and outlets (ports for connecting virtual cables).
We have many unfinished templates in the pch2csd/resources/modules
directory
to get you started quite quickly. You can put your new module into the user
directory ~/pch2csd/modules
. When looking up the module template— the
converter first checks the local directory, and if it didn't find the template
there, it falls back to the default directory. This makes it convenient to
test new module implementation without running local version of the tool.
The UDO for the module LevAmp
module can look like this:
;@ map LVLamp CLAEXP
;@ map BUT002
;@ ins k
;@ outs k
opcode LevAmp,0,kkkk
kValue, kScale, kzIn, kzOut xin
kIn zkr kzIn
if kScale != 1 goto LinScale
kMult table kValue, giCLAEXP
goto Out
LinScale:
kMult table kValue, giLVLamp
Out:
zkw kIn * kMult, kzOut
endop
;@ ins a
;@ outs a
opcode LevAmp,0,kkkk
kValue, kScale, kzIn, kzOut xin
aIn zar kzIn
if kScale != 1 goto LinScale
kMult table kValue, giCLAEXP
goto Out
LinScale:
kMult table kValue, giLVLamp
Out:
zkw aIn * kMult, kzOut
endop
We introduced several special annotations to help the tool in the conversion process. They are all put in the comments like this: `;@ <...>', and currently can be of the following types:
-
;@ ins <list of input types>
;@ outs <list of out types>
— these annotations should be added just before the[opcode]
definition. Their function is to declare the number of input and output buses, as well the buses' signal types. They are needed for the tool 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. In the example above, theins
annotation of the default UDO variant declare one input ask
, and also one output of the same type. The two UDO variants differ by the types of inputs and outputs: in the first UDO inputs and outputs are of the k-rate type, and in the second UDO they are of the a-rate type. The converter chooses only one UDO for the module depending on what types of cables are coming into the module. -
;@ map <args>
. The NM stores module parameter values as a 7-bit MIDI values (0..127), which need to be mapped to UDO dependent values — like the attack time in milliseconds or the compressor threshold in dB. We copied (most of) all tables and put them in the value_maps.json file, which is generated from the value_maps.ods LibreOffice spreadsheet. If you notice that some table is missing, then you can manually add it there. Check the value_maps.ods spreadsheet in thedocs
directory to check the available mapping tables.
Once you have created your UDO template and saved it as a file 81.txt
in the
~/pch2csd/modules
directory, you can run the pch2csd
tool from the command
line to convert the patch with the new UDO:
pch2csd LevAmp.pch2
This action will create the Csound source file LevAmp.pch2.csd
in the same
directory:
<CsoundSynthesizer>
<CsOptions>
</CsOptions>
<CsInstruments>
sr = 96000
kr = 24000
nchnls = 2
0dbfs = 1
zakinit 2, 2
; Function tables for rectifier
giHalfPos ftgen 10, 0, 32768, 7, 0, 16384, 0, 16384, 1
giHalfNeg ftgen 11, 0, 32768, 7, -1, 16384, 0
giFullPos ftgen 12, 0, 32768, 7, 1, 16384, 0, 16384, 1
giFullNeg ftgen 13, 0, 32768, 7, -1, 16384, 0, 16384, -1
;---------------------------------
; Function tables for sequencer
giSeqTab1 ftgen 20, 0, 16, -2, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0
giSeqTab2 ftgen 20, 0, 16, -2, 1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0
;----------------------------------
; Function table generation block
giSin ftgen 1, 0, 16384, 10, 1
giTri ftgen 2, 0, 16384, 7, 0, 4096, 1, 8192, -1, 4096, 0
giSaw ftgen 3, 0, 16384, 7, 1, 16384, -1
giSqr50 ftgen 4, 0, 16384, 7, 1, 8192, 1, 0, -1
giSqr25 ftgen 5, 0, 16384, 7, 1, 4096, 1, 0, -0.25
giSqr10 ftgen 6, 0, 16384, 7, 1, 1024, 1, 0, -0.0625
;---------------------------------
giCLAEXP ftgen 100, 0, 128, 2, 0.0, 0.008, 0.016, 0.023, 0.031, 0.039, 0.047, 0.055, 0.063, 0.07, 0.078, 0.086, 0.094, 0.102, 0.109, 0.117, 0.125, 0.133, 0.141, 0.148, 0.156, 0.164, 0.172, 0.18, 0.188, 0.195, 0.203, 0.211, 0.219, 0.227, 0.234, 0.242, 0.25, 0.258, 0.266, 0.273, 0.281, 0.289, 0.297, 0.305, 0.313, 0.32, 0.328, 0.336, 0.344, 0.352, 0.359, 0.367, 0.375, 0.383, 0.391, 0.398, 0.406, 0.414, 0.422, 0.43, 0.438, 0.445, 0.453, 0.461, 0.469, 0.477, 0.484, 0.492, 0.5, 0.508, 0.516, 0.523, 0.531, 0.539, 0.547, 0.555, 0.563, 0.57, 0.578, 0.586, 0.594, 0.602, 0.609, 0.617, 0.625, 0.633, 0.641, 0.648, 0.656, 0.664, 0.672, 0.68, 0.688, 0.695, 0.703, 0.711, 0.719, 0.727, 0.734, 0.742, 0.75, 0.758, 0.766, 0.773, 0.781, 0.789, 0.797, 0.805, 0.813, 0.82, 0.828, 0.836, 0.844, 0.852, 0.859, 0.867, 0.875, 0.883, 0.891, 0.898, 0.906, 0.914, 0.922, 0.93, 0.938, 0.945, 0.953, 0.961, 0.969, 0.977, 0.984, 1.0
giLVLamp ftgen 100, 0, 128, 2, 0.0, 0.008, 0.016, 0.023, 0.031, 0.039, 0.047, 0.055, 0.063, 0.07, 0.078, 0.086, 0.094, 0.102, 0.109, 0.117, 0.125, 0.133, 0.141, 0.148, 0.156, 0.164, 0.172, 0.18, 0.188, 0.195, 0.203, 0.211, 0.219, 0.227, 0.234, 0.242, 0.25, 0.258, 0.266, 0.273, 0.281, 0.289, 0.297, 0.305, 0.313, 0.32, 0.328, 0.336, 0.344, 0.352, 0.359, 0.367, 0.375, 0.383, 0.391, 0.398, 0.406, 0.414, 0.422, 0.43, 0.438, 0.445, 0.453, 0.461, 0.469, 0.477, 0.484, 0.492, 0.5, 0.508, 0.516, 0.523, 0.531, 0.539, 0.547, 0.555, 0.563, 0.57, 0.578, 0.586, 0.594, 0.602, 0.609, 0.617, 0.625, 0.633, 0.641, 0.648, 0.656, 0.664, 0.672, 0.68, 0.688, 0.695, 0.703, 0.711, 0.719, 0.727, 0.734, 0.742, 0.75, 0.758, 0.766, 0.773, 0.781, 0.789, 0.797, 0.805, 0.813, 0.82, 0.828, 0.836, 0.844, 0.852, 0.859, 0.867, 0.875, 0.883, 0.891, 0.898, 0.906, 0.914, 0.922, 0.93, 0.938, 0.945, 0.953, 0.961, 0.969, 0.977, 0.984, 1.0
opcode LevAmp,0,kkkk
kValue, kScale, kzIn, kzOut xin
kIn zkr kzIn
if kScale != 1 goto LinScale
kMult table kValue, giCLAEXP
goto Out
LinScale:
kMult table kValue, giLVLamp
Out:
zkw kIn * kMult, kzOut
endop
; --------------------
; VOICE AREA
instr 1
; Module Parameters Inlets Outlets
LevAmp_v0 64,0, 1, 0
endin
; --------------------
; FX AREA
instr 2
; Module Parameters Inlets Outlets
endin
</CsInstruments>
<CsScore>
i1 0 [60*60*24*7]
i2 0 [60*60*24*7]
</CsScore>
</CsoundSynthesizer>
Please note, that by default inlet of LevAmp_v0 opcode is connected to the bus
1
and the outlet to the bus 0
in the zak-space. These are the special
buses: bus 0
is used as the “trash” bus, where we simply send audio and never
read from it, and bus 1
is a bus which contains only zeros. By default, all
unused outlets are passing signals to the bus 0
, and all unused inlets are
receiving zero signal from the bus 1
. The _v0
suffix of the opcode name
means that the first UDO version, the k-rate one, was used.
To run this file in Csound, you can use the command line interface or, for example, CsoundQt to make things even more simplier.
The converter code can be considered to be stable, but the tool itself lacks of many essential modules implemented as UDOs. We are slowly implementing them, but if you wish to help, it will be greatly appreciated. The list of modules and their implementations status can be found on the “Module implementation status” wiki page. You can submit your implementations either through pull requests to the main repo or just file up the issue with your code if you're not proficient with Git, and we will upload it ourselves. The code contribution are warmly appreciated as well!