Creating custom device GUIs for MSoundFactory and MXXX
MeldaProduction has built an engine which lets you change the appearance of your devices using various images for the background, knobs etc. This is the way that most plugins are designed, because it is quite easy to do, however it is rather difficult to maintain—adding more controls for example may become a tedious task, since you may need to move the existing controls as well, change a background etc. You are likely to edit the entire visual in Photoshop for example, then export images of the background and of each control. After that you just need to write a simple XML text file containing the XY coordinates of each control and some other properties.
This document will serve as a description of the current possibilities and it may change from time to time, when new features are added.
Let's assume that you want to create custom graphics for the “Superman Piano” device; this device will be stored (on Windows) in
First of all you need to create a folder with the same name, just no extension and a dot (
.) at the start, which
usually marks hidden folders. In our case the folder name is this:
Then put all your graphics files into that folder. You can use PNG and JPG files. To minimize compatibility issues following these rules for file names is recommended:
- All filenames should start with a letter.
- Only letters and numbers are allowed, no whitespaces, special characters etc.
- Names are case sensitive, so if you use Test.png and name it "test" in the definition, it will not work, because
- Make the names as short as possible, for performance reasons.
So for example
KnobDryWet.png is great, but
Knob-123Dry-Wet!&=.png not so much. If you create multiple devices
which share the images, you can change the name of the directory via the XML file that we will be creating below, so
these devices can share the resources.
Finally you need to create the definition XML file, which we will discuss below. In our case it would be:
When MSoundFactory or MXXX loads a device, it checks whether there's a XML file with the same name and if there is, it loads the XML file and does whatever is necessary with the information in it.
Screen Resolution and GUI Size
The display resolutions for modern screens vary a lot, from low resolutions used on MacBooks (2560x1600, the HDPI version of 1280x800) to high resolution laptops (3840x2160, the HDPI version of 1920x1080) and of course gigantic desktop displays having 5K resolution (5120x2880) or more at the time you are reading this.
And, according to https://en.wikipedia.org/wiki/Display_resolution#Common_display_resolutions and https://gs.statcounter.com/screen-resolution-stats/desktop/worldwide the two most popular desktop screen resolutions are 1366 x 768 and 1920 x 1080.
In any case, good GUI design needs to address the lowest resolutions used, otherwise your device will not be displayed completely on the screen. You can certainly change the width as well, but then you can expect the GUI to be resized.
Every device has a background image, which defines the total size of the GUI.
It is important to understand that the same GUI needs to work on both normal and HDPI/retina displays (which are basically 2x larger in both height and width). All images that you provide for your device are assumed to be designed for HDPI displays. For MSoundFactory devices the width of the background image should be 1400 pixels. The height is up to you, but the taller it is, the more the chance that it won't fit on the screen. For non-HDPI displays the images are halved in size, hence the default width for MSoundFactory devices is 700 pixels on non-HDPI screens. The engine actually lets the user manually resize the devices, so the images may be stretched to any size.
The coordinates and dimensions of all components in the GUI definition (described below) should be divisible by 2. That helps avoiding controls being placed incorrectly when rendered from HDPI to normal displays.
When designing the graphics it is essential to be aware that the change of resolution may occur. It is essential that you test your graphics and make sure you won't lose important details – for example it may be tempting to create very fine grained text and sharp lines in your GUI, however there's a danger that it may not be readable after lowering the image size. As an example, lines with a width of 1 pixel could disappear.
You may provide different graphics for lower resolutions and avoid this problem. The downside is that you will need to maintain multiple editions of your graphics, the size deployed on the user computers will be bigger and more files will need to be loaded to memory, which means higher memory consumption and longer loading times. If you still want to do that, simply add "2" postfixed to all 2x downsized image files. For example, if there is background.jpg (1400x1000), you may also add background2.jpg (700x500) for non-HDPI displays (or if a user reduces the GUI size intentionally). Please note that you don't need to create downsized versions for all images, just those you really need.
Definition XML file
The word XML may make you feel scared, but it's really just a text tile. Here's an example:
<device> <GUI> <tab name="Generator" background="piano"> <control target="1" pos="100,100" image="knob"/> <control target="3" pos="100,250" image="knob"/> </tab> </GUI> </device>
Let’s work through that example.
XML is an easy to edit text format, which is also pretty fast to parse, so it is ideal for our cause. It consists of a set of so-called "tags", each tag looks like this:
<tagname attribute1="value" attribute2="value" ...> subtags </tagname>
If there are no subtags, you can simplify it like this:
<tagname attribute1="value" attribute2="value" .../>
Notice the terminating "/" symbol. That's all that is needed there!
Our XML file has a global tag, named "device", which just needs to be there. Inside it there's a "GUI" tag, which marks the section describing the GUI of the device. And inside that one you create a custom GUI for each tab in your device. Each tab has a name, the same one as you used in the actual MSoundFactory /MXXXdevice and the background image name (without the file extension). Finally each "control" tag describes one control, such as a knob. In our case there are 2 identical knobs, both associated with the image knob.png (or jpg). The first one is associated with multiparameter 1 and painted at position 100,100 from the left top corner (0,0). The second one is associated with multiparameter 3 and painted at position 100,250. The control dimensions are determined from the image. Simple.
Each control tag can look like this (the attributes surrounded by [ ] are optional, and the ... indicates that there may be further optional attributes):
<control target="target multiparameter" pos="position, ideally divisible by 2" [type="type of the control, see below, defaults to knob/button/switch" ] [image="name of the image file without extension" ] [imagedisabled="name of the image file for disabled state" ] [height="height, ideally divisible by 2" ] [count="number of states for switch/button" ] ... />
Please note that if type is not specified, then the type defaults to knob/button/switch.
Comments will be useful when you come back to your design after a few weeks. A comment can be added by starting it
<!-- and ending it with
-->. (without the full stop)
Please remember that the controls are displayed in the order in which they appear in the XML file.
Knobs and basic controls...
Knobs and other basic controls are represented by an image file, which usually contains multiple images of the same control. For instance, if this is a knob, it is not a single image of a knob, but a (very) tall (vertical) image containing several knob images (“frames”) stacked vertically on top of each other. The top one represents the appearance for the minimum parameter value, and the bottom one represents the appearance for the maximum value. This is a traditional way for texturing knobs and other controls using knobman for example. Here's an example.
It is important to choose a good number of frames – each one represents a different "rotation" of the knob. We recommend 40 to 80 frames. Too low would make the knob hard to control, jumping from step to step. Too high a number of frames would require more memory and loading time. Image size should not exceed a value of 16,384, since that's the typical maximum texture size for most GPUs. If your knob requires a middle value (e.g. gain from -12dB to +12dB with a middle value 0dB), the number of frames should also be "odd", so that there's a dedicated frame for the middle value. So for example 40 frames may not be ideal and you should consider using 41 frames. Or, given that knobs often range from 0% to 100%, 51 could be the right number. As an example, 51 frames of a knob that is 160 pixels high would be 8160 pixels high.
Controls such as buttons or switches (for some particular number of values) use the same approach, except that the
number of frames is defined by the number of states. For example, a button has 2 states:
0 = not pushed,
pushed. A switch between 4 values (implemented using a multiparameter in banks mode for example) has 4 frames. So
defining graphics for these controls is actually the same as for knobs! The top frame represents the “not-pushed”
state for a button or the first state for a switch and the bottom frame represents the “pushed” state for a button
or the last state for a switch. You should specify the number of states using the count attribute though, e.g.:
<control target="1" pos="100,100" image="switch" count="4"/>
By default the engine assumes that the image of each frame is square, which is usually the case for knobs. So if
each knob is 32x32, the system can obviously deduce the height of it just from the width of the image. For other
controls (or if your knob is not square) you should specify the optional
<control target="1" pos="100,100" image="notsquareknob" height="112"/>
blend="1" makes the engine not only paint one image from the set of images, but interpolate
between 2 of them, providing finer granularity. This isn't really useful for knobs, where interpolating between 2
different images only produces some sort of blurred output, but may be useful for example for "led" indicators
changing colours, images of "tubes" being heated differently etc. For example:
<control target="1" pos="100,100" image="tube" blend="1"/>
Multiple controls may use the same image. This is advantageous because the engine will load the image only once and share it between all controls that use it. So for example if you have a knob with some units around it, you should draw the units on the background image, not on the knob image. This also makes the knob smaller and provides better compression due to higher predictability of pixels. But, make sure that the knob is in the right place; if you subsequently move it you will need to edit the background image too. Tip: it will be valuable to use an image editor that support layers to make this easier&mdadh;with the “real” background on one layer and the units characters on another.
From the user point of view the knobs are handled in exactly the same way as MeldaProduction knobs: vertical dragging changes the value, right clicking selects the default value, ctrl key is used for fine tuning, shift + click or double click provides a text edit.
Arc and Rotated knobs
Classic knobs created using a stack of image frames are a generalized solution, though quite resource demanding - they require a huge image to be loaded and used, hence bigger executables and slower loading. Additionally the number of frames is limited, so the knob is "jumping" from one frame to the next. To make the visual response smoother, the only solution is to increase the number of frames, hence again increasing the resource requirements.
For 2D flat design it is often possible to paint the knobs in a different way—by painting only an arc of the image or rotating a single image. You can also combine all 3 of these methods. The normal image would be the static background, and an arc (usually some kind of partial circle indicating the current value) would be painted over it, and finally an actual knob would be painted as a rotated image. The arc also lets you highlight the value's origin. For example:
<control target="1" pos="32,240" image="background" imagearc="arc" imagerotated="theknob" baseangle="30" />
Base angle defines the angle of the 0% value from the bottom centre in degrees. By default 40 degrees is used. A variant of this approach is to use a static background, and a simple bar as the imagerotated. Think of a clock face – 95% is static, only the hand(s) move.
Using knob controls as horizontal / vertical sliders
Sliders present another way for controlling the value and in fact these can work in exactly the same way as knobs, just displaying a different control. While you can use the traditional way and make a big image of many slider frames in various positions, there's an alternative approach similar to the use of arcs and rotated knobs.
You would provide a background image and a highlight image. Background would always be painted fully, but the highlight would be painted only partly—the engine automatically cuts the left/right or top/bottom parts from it depending on current value and orientation (determined by the image dimensions). For example:
<control target="1" pos="32,240" image="background" imagehighlight="hl" />
For a horizontal slider, the
imagehighlight is cut from the right-hand side and for a vertical slider the
imagehighlight is cut from the top.
Using knob controls as meters
A knob always needs a
target, typically a Multiparameter (“MP”). However if the MP's mode is Meter, it won't
really be possible to control the knob and instead the knob will display the current value of the MP. While having a
knob rotating on its own would be a bit obscure, you need to remember that it may actually not be a knob image, but
any kind of visualisation – led indicators, tubes, some sort of animation... All these controls work the same way.
In this case you need to set the MP in your plugin settings in some way. One possibility is to attach it to a metering parameter – these are available for each of MSoundFactory's (or MXXX's) modules and usually there are input and output level, gain reduction and level follower values.
Another useful option is to control the MP manually using a modulator. You can follow the input level. Or even use LFO/random modulation which may be useful to create some cool animations.
Meters are often painted as LED strips. In that case it would be quite inefficient to create the image with all
possible states separately as we used with knobs. Instead you can simply provide one single image with all LEDs on
and the engine can paint just part of the image. Disabled LEDs would be part of the background image. Then you
specify the number of LEDs using attribute
count="N" and enable the feature using attribute
autofillery="1", or, if
the image is not equally spaced (e.g. due to 3D projection or varying LED sizes) include attribute
fillery="y0,y1,y2...", where yn defines number of pixels to paint for each state.
<control target="1" pos="100,100" image="ledstrip" count="32" autofillery="1" />
The image ledstrip has 32 lit LEDs. Depending on the value of multiparameter 1, 1 to 32 of those LEDs are displayed (and, of course, each LED can be a different colour or intensity).
There's one more case in which the knobs, not really being knobs, is useful—for some sort of background images.
Imagine a module which would switch between multiple compressors and you want the background to change depending on
which compressor has been selected. There will be some MP which is selecting the compressor, so you can attach the
same MP to another knob, which will be just the background image. If there are say 3 compressors, then this special
knob would have 3 image frames. And to prevent it from actually working like a knob, hence letting user click on it
and change the MPs value, specify the attribute
<control target="1" pos="100,100" image="CompressorBackgrounds" height="320" static="1" />
Of course, this control would need to be in the XML file before the actual compressor knob controls.
Switches are painted the same way as knobs. Knobs are controlled by vertical dragging, which would be quite
cumbersome with switches between say 3 values. So there's a different control type called
switch—by default it
selects the next value on every click (right mouse button selects the previous value). Alternatively you can specify
clickswitch="0", in which case the image would be cut into equally sized regions for each value and
clicking will select the value on which the user clicked. By default this is assumed to be a horizontal set of
"radio buttons", to use vertical set just specify
You should use attribute count="..." to set the number of values. By default it is 2, which is useful for buttons, representing on/off.
<control target="2" type="switch" pos="100,100" image="SidechainEnableButton" count="2" /> <control target="1" type="switch" pos="100,100" image="CompressorSwitch" count="3" clickswitch="0" />
Additionally you may create a clickmap, if the switch isn't simply horizontal or vertical, by specifying a set of points. For example:
<control target="1" type="switch" pos="100,16" size="150,50" image="CompressorSwitch" count="3" > <item value="0" pos="0,0" size="50,50"/> <item value="1" pos="50,0" size="30,50"/> <item value="2" pos="80,0" size="70,50"/> </control>
This example will display a 3-way switch with frame widths of 50, 30 and 70 pixels respectively.
Parameter locks work in the same way as switches and their purpose is to lock current values when loading presets.
Useful for dry/wet in reverbs for example. The control has a
lock type and the image is assumed to have 2 states
(the top one is “unlocked”).
<control target="1" type="lock" pos="100,10" image="DryWetLock" />
Sliders mimic the classic faders from mixing consoles. Again you need to specify the image attribute, which selects the image of the fader handle. The background is assumed to be incorporated into the plugin background image. Again, we recommend using an image editor that supports layers, so that you can move the slider backgrounds around more easily. The slider is painted simply by rendering the handle image at a position depending on the current value. Sliders can be horizontal or vertical (default). For vertical sliders you need to specify the height, which controls the area in which the handle is moving. Width is assumed to be the same as the width of the handle. For example:
<control type="slider" target="21" pos="10,10" height="224" image="handle" />
For horizontal sliders it's the other way around—height is assumed to be the same as handle height, and you need to specify the width. Like this:
<control type="slider" target="21" pos="10,10" width="224" vertical="0" image="handle" />
For 3D HW looking interfaces you may run into the problem that a slider isn't really going straight up and down but
is rather following some diagonal line and even perspective may come into play. In that case you can specify pos and
size attributes defining the entire region and then provide coordinates
pos3, which define the
trajectory for the slider from pos1 (0%) through pos2 (50%) to pos3 (100%). You can also specify the attribute
centre, which defines coordinates of the centre position in the slider handle image, which is useful to align the
slider to the 3-point trajectory. For example:
<control type="slider" target="1" pos="10,10" size="120,538" pos0="128,714" pos1="136,487" pos2="144,270" center="46,60" image="handle" />
XY-pad is a cool way to control 2 multiparameters at once, one is referenced as the
target and the other as
image of the XY pad is simply the "handle", which is painted into the appropriate position. As before, the
background to the XY pad is assumed to be incorporated into the plugin background image. The
size need to be
specified and define the area in which the handle can move. By default the XY pad assumes that the handle is the
same size as its image (which limits its movement inside the XY pad), but if you for example include some sort of
outer glow or something, you don't really want to limit the movement based on the edge of the glow, so you need to
also add attribute
handlesize. For example:
<control type="xypad" target="11" target2="12" size="384,384" pos="1056,288" image="handleimage" handlesize="2" />
Image is simply an image overlay displayed over the current background with controls painted so far. It can be used
to provide some additional 3D realistic feeling. It doesn't need the
target attribute of course.
<control type="image" pos="100,100" image="FrontGlass"/>
A needle is quite common for painting oldschool hardware VU / level meters. This type of control doesn't have an
image file at all. Instead it has attributes
size specifying the size (which combined with
pos results in the
center as the place where the needle is attached (usually out of the crop rectangle),
the minimum value end point and
max as the maximum value end point. The engine will calculate the remaining points
automatically. It additionally needs
color attribute as RGBA and
<control type="needle" target="5" pos="550,200" size="200,100" center="650,400" min="560,280" max="740,280" color="00000030" linewidth="8" />
To paint a needle with a shadow you can simply use multiple needles.
Meter control provides the classic bar meter that we use in MeldaProduction plugins. It needs the attributes
color2 specified as hexadecimal RGBA,
size specifying its full size (width, height, the engine uses
it also to detect whether the meter is horizontal or vertical) and
linesize, which controls the size of each "led"
indicator in the meter. It isn't so useful for vintage hardware (unless it is in some sort of display), but is
definitely useful for more modern GUIs.
<control type="meter" target="6" pos="100,150" size="40,200" color1="00FF00FF" color2="FF0000FF" linesize="4" />
Units is a plain text field painted using the specified font, which displays the text representation of value of the
target multiparameter. It is useful for actual controls as well as meters. The text is centred by default, but you
can change that using attribute
fontlayout, currently supported values are
example (which shows the defaults for font selection):
<control type="units" target="6" pos="100,150" size="40,200" color="00FF00FF" fontname="roboto" fontheight="20" fontwidth="0" fontitalic="0" fontbold="0" fontlayout="center" />
You can use any fonts installed on the system. We would however recommend using Roboto, which is installed with Melda plugins. If your font is not found on the system, Roboto will be used.
Menu is similar to the Switcher control in MeldaProduction plugins. It displays the text representation of the
current value and clicking on it displays a menu with choices. It should be associated with a multiparameter in
Banks mode; the bank names are used for the menu choices. It is a single frame image. Optionally it can have arrows
on the side, which need to be part of its image and the size of both left and right arrows is represented by
arrowwidth. It can also be controlled using vertical arrows in which case there should be attribute
. You also need to specify font for the units the same way it is done for Units type.
<control target="1" type="menu" pos="100,16" image="Switch" color="00FF00FF" fontname="arial" fontheight="20" fontwidth="0" fontitalic="0" fontbold="0" fontlayout="right" />