JPatch - Module Descriptions v1.3
Introduction
Module Descriptions is a XML format which can be used to provide a comprehensive documentation of the modules of a synthesizer. The format is designed to make automated generation of modules and their user interface easy.
Tutorial
Introduction
This tutorial describes step by step how to write the module descriptions xml file. For this tutorial we describe the properties of the Waldorf Miniworks 4 Pole Filter. The documentation is available in the archives at http://www.waldorfmusic.de (direkt link).
First Step
We start with a new file containing ModuleDescriptions as the root element where the current version of the format version="1.3" is specified.
<?xml version="1.0" encoding="utf-8"?> <ModuleDescriptions version="1.3" xmlns="http://nmedit.sf.net/ns/ModuleDescriptions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> ... </ModuleDescriptions>
ModuleDescriptions - attribute overview
attribute | use | description |
---|---|---|
version | required | current format version: 1.3 |
Document Header
Then we declare the header where global properties are defined. The first line contains the vendor of the ModuleDescriptions document. The element optionally allows to specify the vendor's homepage using the url-attribute. The second line contains the device description. Containing the model-name, device-vendor and optionally some version-information.
At the end of the device- or header-element it is optionally possible to specify custom properties.
<?xml version="1.0" encoding="utf-8"?> <ModuleDescriptions version="1.3" xmlns="http://nmedit.sf.net/ns/ModuleDescriptions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <header> <vendor url="http://nmedit.sf.net">NMedit Project</vendor> <device> <model>miniWORKS 4pole</model> <vendor url="http://www.waldorfmusic.de/">Waldorf Music</vendor> <version type="os-version">1.0</version> </device> <property name="author">Christian Schneider</property> </header> ... </ModuleDescriptions>
vendor - attribute overview
attribute | use | description |
---|---|---|
url | optional | vendor url |
version - attribute overview
attribute | use | description |
---|---|---|
type | required | version |
property - attribute overview
attribute | use | description |
---|---|---|
name | required | name of the property |
Annotations
The annotation section allows to add comments, license / copyright information, a changelog or other useful information. The element comes after the header element. Here is some example:
<?xml version="1.0" encoding="utf-8"?> <ModuleDescriptions version="1.3" xmlns="http://nmedit.sf.net/ns/ModuleDescriptions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <header> ... </header> <annotation> <section> <title>License</title> <pre> <!-- here should be the license in preformattet text --> </pre> </section> <section> <title>Misc</title> This is some inline <code>code</code> that should be rendered in an appropriate font (monospace). A simple <link href="http://nmedit.sf.net">http://nmedit.sf.net</link>. A mail adress: <mail mailto="someone@somewhere.net">someone@somewhere.net</mail>. A simple list: <list> <item>one</item> <item>two</item> <item>three</item> </list> </section> </annotation> </ModuleDescriptions>
Defining custom types
After the header we have the optional defs-element where custom types can be defined. Custom types can be used
- as type in the attribute-element
- as formatter of a parameter
<?xml version="1.0" encoding="utf-8"?> <ModuleDescriptions version="1.3" xmlns="http://nmedit.sf.net/ns/ModuleDescriptions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <header> ... </header> <defs> <def-type name="tmLFOShape" > <enumeration key="0" value="sin"/> <enumeration key="1" value="tri"/> <enumeration key="2" value="saw"/> <enumeration key="3" value="pls"/> <enumeration key="4" value="S-H"/> </def-type> <def-type name="tmTriggerSource" > <enumeration key="0" value="Audio"/> <enumeration key="1" value="MIDI"/> <enumeration key="2" value="All"/> </def-type> <def-type name="tmTriggerMode" > <enumeration key="0" value="Multi"/> <enumeration key="1" value="Single"/> </def-type> <def-type name="tmModulationSource" > <enumeration key="0" value="off"/> <enumeration key="1" value="LFO"/> <enumeration key="2" value="LFO * ModW."/> <enumeration key="3" value="LFO * Aftertouch"/> <enumeration key="4" value="LFO * VCA Env"/> <enumeration key="5" value="VCF Env"/> <enumeration key="6" value="VCA Env"/> <enumeration key="7" value="Signal Env"/> <enumeration key="8" value="Vel * VCA Env"/> <enumeration key="9" value="Velocity"/> <enumeration key="10" value="Keytrack"/> <enumeration key="11" value="Pitch Bend"/> <enumeration key="12" value="Modwheel"/> <enumeration key="13" value="Aftertouch"/> <enumeration key="14" value="Breath Ctr."/> <enumeration key="15" value="Foot Ctr."/> </def-type> </defs> ... </ModuleDescriptions>
In this example we have defined the three types tmLFOShape, tmTriggerSource and tmTriggerMode. Each type contain several enumeration-elements with a key and a string representation of the key. The use of custom types will be described later.
def-type - attribute overview
attribute | use | description |
---|---|---|
name | required | name of the type |
enumeration attribute overview
attribute | use | description |
---|---|---|
key | required | a key that is associated with the value |
value | required | the value |
Signal types
Depending on the synthesizer some modules may have connectors. Such connectors can have different output signals for example audio, control or logic signals. For such cases it is possible to define custom signal types.
<?xml version="1.0" encoding="utf-8"?> <ModuleDescriptions version="1.3" xmlns="http://nmedit.sf.net/ns/ModuleDescriptions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <header> ... </header> <defs> <def-signal> <signal key="0" type="audio" /> <signal key="1" type="logic" /> <signal key="2" type="control" /> <signal key="3" type="master-slave" /> </def-signal> <!-- other defs ... --> ... </def-type> </defs> ... </ModuleDescriptions>
The example shows the definition of four signal types: audio, logic, control and master-slave. Each signal type is associated with a number (key-attribute).
signal - attribute overview
attribute | use | description |
---|---|---|
key | required | integer value associated with the signal type |
type | required | type/name of the signal |
Modules I
Inside the body-element it is possible to define an arbitrary number of modules. In the example we only have one module representing the miniWorks filter. The module-element contains a comment which describes the module. Comments are also allowed inside parameter- and connector-Elements.
<?xml version="1.0" encoding="utf-8"?> <ModuleDescriptions version="1.3" xmlns="http://nmedit.sf.net/ns/ModuleDescriptions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <header> ... </header> <defs> ... </defs> <body> <module name="miniWorks 4pole" > <comment> This module represents the Waldorf miniWorks 4pole filter. </comment> ... </module> </body> </ModuleDescriptions>
module - attribute overview
attribute | use | description |
---|---|---|
name | required | module name |
display-name | optional | provides a short version of the module name. If not specified the name-value is used as display name |
id | optional | element id |
key | optional | Identifies the current module. No other module can have the same key. If not specified the name-value is used as key |
category | optional | name of the category this module belongs to |
category-id | optional | id of the category this module belongs to |
class | optional | optional attribute that can be used to associate a module with a class of modules |
Parameters
Before we can define the module we have to take a look at the properties of the module. The column byte means the parameter-offset in midi messages, value is the valid parameter range and description contains the parameter name and optionally the description of a parameter value.
Byte # Value Description 6 0-127 VCF Envelope Attack 7 0-127 VCF Envelope Decay 8 0-127 VCF Envelope Sustain 9 0-127 VCF Envelope Release 10 0-127 VCA Envelope Attack 11 0-127 VCA Envelope Decay 12 0-127 VCA Envelope Sustain 13 0-127 VCA Envelope Release 14 0-127 VCF Envlope Cutoff Amount -64...+63 15 0-127 VCA Envlope Volume Amount -64...+63 16 0-127 LFO Speed 17 0-127 LFO Speed Mod. Amount -64...+63 18 0-4 LFO Shape 0:sin 1:tri 2:saw 3:pls 4:S-H 19 0-15 LFO Speed Modulation Source 20 0-127 Cutoff Modulation Amount -64...+63 21 0-127 Resonance Modulation Amount -64...+63 22 0-127 Volume Modulation Amount -64...+63 23 0-127 Panning Modulation Amount -64...+63 24 0-15 Cutoff Modulation Source 25 0-15 Resonance Modulation Source 26 0-15 Volume Modulation Source 27 0-15 Panning Modulation Source 28 0-127 Cutoff 29 0-127 Resonance 30 0-127 Volume 31 0-127 Panning 32 0-127 Gate Time 0.000 to 1.02 s 33 0-2 Trigger Source 0:Audio 1:MIDI 2:All 34 0-1 Trigger Mode 0:Multi 1:Single
Now we add the parameters to the module element. Each parameter has a key assigned to it (optional) as unique identifier of each parameter in the module. Since each parameter has also it's unique offset in the midi message it makes sense to use the offset also as identifier of the parameter.
... <module name="miniWORKS 4pole" > <parameter key="6" name="VCF Envelope Attack" /> <parameter key="7" name="VCF Envelope Decay" /> <parameter key="8" name="VCF Envelope Sustain" /> <parameter key="9" name="VCF Envelope Release" /> <parameter key="10" name="VCA Envelope Attack" /> <parameter key="11" name="VCA Envelope Decay" /> <parameter key="12" name="VCA Envelope Sustain" /> <parameter key="13" name="VCA Envelope Release" /> <parameter key="14" name="VCF Envlope Cutoff Amount" formatter="offset(-64)" /> <parameter key="15" name="VCA Envlope Volume Amount" formatter="offset(-64)" /> <parameter key="16" name="LFO Speed" /> <parameter key="17" name="LFO Speed Mod. Amount" formatter="offset(-64)" /> <parameter key="18" name="LFO Shape" maxValue="4" formatter="type('tmLFOShape')" /> <parameter key="19" name="LFO Speed Modulation Source" maxValue="15" formatter="type('tmModulationSource')"/> <parameter key="20" name="Cutoff Modulation Amount" formatter="offset(-64)" /> <parameter key="21" name="Resonance Modulation Amount" formatter="offset(-64)" /> <parameter key="22" name="Volume Modulation Amount" formatter="offset(-64)" /> <parameter key="23" name="Panning Modulation Amount" formatter="offset(-64)" /> <parameter key="24" name="Cutoff Modulation Source" maxValue="15" formatter="type('tmModulationSource')"/> <parameter key="25" name="Resonance Modulation Source" maxValue="15" formatter="type('tmModulationSource')"/> <parameter key="26" name="Volume Modulation Source" maxValue="15" formatter="type('tmModulationSource')" /> <parameter key="27" name="Panning Modulation Source" maxValue="15" formatter="type('tmModulationSource')" /> <parameter key="28" name="Cutoff" /> <parameter key="29" name="Resonance" /> <parameter key="30" name="Volume" /> <parameter key="31" name="Panning" /> <parameter key="32" name="Gate Time" format-id="GateTimeFormatter" /> <parameter key="33" name="Trigger Source" maxValue="2" formatter="type('tmTriggerSource')" /> <parameter key="34" name="Trigger Mode" maxValue="1" formatter="type('tmTriggerMode')" /> </module> ...
parameter - attribute overview
attribute | use | description |
---|---|---|
name | required | parameter name |
id | optional | element id |
key | optional | Identifies the current parameter. No other parameter in the module can have the same key. If not specified the name-value is used as key. |
class | optional | optional attribute that can be used to associate a parameter with a class of parameters |
minValue | optional, default: 0 | minimum value |
defaultValue | optional, default: 0 | default value |
maxValue | optional, default: 127 | maximum value |
format-id | see: attribute formatter | specifies the id of an external formatter. |
formatter |
formatter and format-id are both optional. Only one of them can be specified. | Uses one of the built in formatters. |
Modules II - Restructuring
Now we have defined the properties of the MiniWorks Filter. But in our case it would make sense to provide more information. One problem is that we have all parameters mixed (VCF, VCA, LFO, ...). We also have some kind of internal routing. Modulation sources and targets could be represented by connectors.
... <module category="External" name="External"> <connector type="output" key="7" name="Signal Env" /> <connector type="output" key="9" name="Velocity" /> <connector type="output" key="10" name="Keytrack" /> <connector type="output" key="11" name="Pitch Bend" /> <connector type="output" key="12" name="Modwheel" /> <connector type="output" key="13" name="Aftertouch" /> <connector type="output" key="14" name="Breath Ctr." /> <connector type="output" key="15" name="Foot Ctr." /> </module> <module category="LFO" name="LFO"> <parameter key="16" name="Speed" /> <parameter key="17" name="Speed Mod. Amount" formatter="offset(-64)" /> <parameter key="18" name="Shape" maxValue="4" formatter="type('tmLFOShape')" /> <parameter key="19" name="Speed Modulation Source" maxValue="15" formatter="type('tmModulationSource')"> <attribute type="boolean" name="has-replacement" value="yes" /> </parameter> <connector type="input" key="19" name="Speed Modulation Source"> <comment>Replacement for 'Speed Modulation Source' parameter</comment> </connector> <connector type="output" key="1" name="LFO" /> <connector type="output" key="2" name="LFO * ModW." /> <connector type="output" key="3" name="LFO * Aftertouch" /> <connector type="output" key="4" name="LFO * VCA Env" /> </module> <module category="Envelope" name="VCF Envelope"> <parameter key="6" name="Attack" /> <parameter key="7" name="Decay" /> <parameter key="8" name="Sustain" /> <parameter key="9" name="Release" /> <parameter key="14" name="VCF Envlope Cutoff Amount" formatter="offset(-64)" /> <connector type="output" key="5" name="VCF Env" /> </module> <module category="Envelope" name="VCA Envelope"> <parameter key="10" name="Attack" /> <parameter key="11" name="Decay" /> <parameter key="12" name="Sustain" /> <parameter key="13" name="Release" /> <parameter key="15" name="VCA Envlope Volume Amount" formatter="offset(-64)" /> <connector type="output" key="6" name="VCA Env" /> <connector type="output" key="8" name="Vel * VCA Env" /> </module> <module category="Filter" name="Filter"> <parameter key="20" name="Cutoff Modulation Amount" formatter="offset(-64)" /> <parameter key="21" name="Resonance Modulation Amount" formatter="offset(-64)" /> <parameter key="24" name="Cutoff Modulation Source" maxValue="15" formatter="type('tmModulationSource')"> <attribute type="boolean" name="has-replacement" value="yes" /> </parameter> <parameter key="25" name="Resonance Modulation Source" maxValue="15" formatter="type('tmModulationSource')"> <attribute type="boolean" name="has-replacement" value="yes" /> </parameter> <parameter key="28" name="Cutoff" /> <parameter key="29" name="Resonance" /> <connector type="input" key="24" name="Cutoff Modulation Source"> <comment>Replacement for parameter</comment> </connector> <connector type="input" key="25" name="Resonance Modulation Source"> <comment>Replacement for parameter</comment> </connector> </module> <module category="Audio" name="Amp" > <parameter key="22" name="Volume Modulation Amount" formatter="offset(-64)" /> <parameter key="26" name="Volume Modulation Source" maxValue="15" formatter="type('tmModulationSource')" > <attribute type="boolean" name="has-replacement" value="yes" /> </parameter> <parameter key="30" name="Volume" /> <parameter key="23" name="Panning Modulation Amount" formatter="offset(-64)" /> <parameter key="27" name="Panning Modulation Source" maxValue="15" formatter="type('tmModulationSource')" > <attribute type="boolean" name="has-replacement" value="yes" /> </parameter> <parameter key="31" name="Panning" /> <connector type="input" key="26" name="Volume Modulation Source"> <comment>Replacement for parameter</comment> </connector> <connector type="input" key="27" name="Panning Modulation Source"> <comment>Replacement for parameter</comment> </connector> </module> <module category="Settings" name="Settings" > <parameter key="32" name="Gate Time" format-id="GateTimeFormatter" /> <parameter key="33" name="Trigger Source" maxValue="2" formatter="type('tmTriggerSource')" /> <parameter key="34" name="Trigger Mode" maxValue="1" formatter="type('tmTriggerMode')" /> </module> ...
Here we have moved parameters which belong together into their own module. Additionally we have defined connectors which should later be used to connect modulation sources with modulation inputs.
Connectors
The connector-element is used to define in- and outputs of a module. Since the miniWorks has no connectors we put the example aside. The following example shows a module with one input and one output connector.
... <module id="someID" name="someName" > ... <connector name="audio-in" key="c1" signal="input" /> <connector name="audio-out" key="c2" signal="output" /> </module> ...
connector - attribute overview
attribute | use | description |
---|---|---|
name | required | connector name |
id | optional | element id |
type | required: "input" or "output" | connector type |
signal | optional | The signal type of the connector. The signal type has to be defined in the defs section. See Signal types. |
key | optional | Identifies the current connector. No other connector in the module can have the same key. If not specified the name-value is used as key. |
class | optional | optional attribute that can be used to associate a connector with a class of connectors |
Attributes
In some cases the default attributes for the module-, parameter- and connector-elements are not enough to store all information. Thus we provide the attribute- element which can be used inside these three elements to specify custom properties. Here are some usage example:
... <body> <module ...> <attribute name="dsp-load" type="double" value="0.23" /> <attribute name="another-one" value="this is a string" /> <connector ...> <attribute name="hint" type="boolean" value="yes" /> </connector> <parameter ...> <attribute name="a-number" type="integer" value="5" /> </parameter> </module> </body> ...
attribute - attribute overview
attribute | use | description |
---|---|---|
name | required | attribute name |
id | optional | attribute id |
type |
built in types:
boolean - value = "yes" or "no" float double integer string (used as default if attribute type is not specified) custom types: types defined in the defs-section. The value must be one of the enumeration values of the specified type. | type of the specified value |
value | required | a value according to the specified type |
Comments
The format allows to add a comment in module-, parameter- and connector elements. The comment has to be the first child element.
... <body> <module ...> <comment>comment about the module</comment> <connector ...> <comment>comment about the connector</comment> </connector> <parameter ...> <comment>comment about the parameter</comment> </parameter> </module> </body> ...
Formatter
In most cases the parameter value itself is the internal representation which alone has no meaning to the user. Then it is necessary to substitute the value with something more meaningful. As solution we provide built in formatters and the possibility to identify custom formatters.
Built in formatters
Built in formatters are used inside the formatter-attribute of the parameter-element. Like in this example:
<parameter name="LFO Speed Mod. Amount" formatter="offset(-64)" />
Built in formatters overview
formatter | arguments | description | example |
---|---|---|---|
offset | offset:integer | Adds the specified offset to the parameter value | offset(-64) formatted value: parameter-value + (-64) |
scale | factor:integer | Multiplies the specified offset with the parameter value | scale(2) formatted value: parameter-value * 2 |
scaled | factor:double (,base:integer)? | Multiplies the specified offset with
the parameter value.
The base value performs the additional
operation: if (base<0): result=round( scaledValue *(10^-base) ) *(10^base) else if (base>0): result=round( scaledValue *(10^base) ) *(10^-base); else: result=round(scaledValue); If the base value is not specified then a value of 0 (zero) is used. |
scaled(0.1234, -3) formatted value: The result of parameter-value*0.1234 is rounded up to the third digit behind the decimal point. |
scale | min:integer, max:integer | specifies a different range for the parameter | scale(2, 7) scale(min, max) formatted value: (((pvalue-pmin)/(pmax-pmin)) *(max-min))+min |
str | string:string | displays a constant string. Can only be used as first and/or last formatter in a sequence of formatters. | str('+') formatted value: + |
type | name:string | Uses the key-value pair of a custom type defined in the defs-section to substitute a value with a string. | type('tmLFOShape') formatted value: switch (parameter-value) { case type.0: return "sin"; case type.1: return "tri"; case type.2: return "saw"; case type.3: return "pls"; case type.4: return "S-H"; default: return string( parameter-value); } |
The built in formatters can be combined with each other if they are seperated using the comma character:
<parameter name="p" formatter="str('x'),offset(-64),scale(2),str('y')" />
Custom formatters
Some values require formatters that are more complex than the built in ones. In that case it is possible to add the format-id attribute to a parameter and link the formatter at runtime with the parameter.
<parameter name="Gate Time" format-id="GateTimeFormatter" />
Result
You can download the example file below.
The example file has a reference to a XSL stylesheet. If the ModuleDescriptions file is opened in a browser it should render as html. (tested only in Mozilla Firefox 2.0)
<?xml version="1.0" encoding="utf-8"?> <?xml-stylesheet type="text/xml" href="./ModuleDescriptions2html.xsl"?> <ModuleDescriptions version="1.3" ... > ... </ModuleDescriptions>
Todo
- annotation support
- annotations that describe the preferred control of a parameter (knob, slider, button(s), ...)
- annotations that describe envelopes (param1 = attack.time, param2 = decay.time, ...)
- defining virtual modules - these are child modules of a real module that create logic groups of parameters and connectors which belong together.
by Christian Schneider