Wow, it looks great !!
I'm sort of very lazy when it comes to graphic interface for my synths.
Basically, i need what jsfx sliders offer (lists and sliders) in a more compact way so that i can layout all the controls in a logical/easy to understand way.
The idea is to have about 40 sliders and 20 option lists.
With the built in sliders, i can't go beyond ~30 sliders because there is no layout option, no multi column... people complain because they can't use my plugins on their laptops
OK - vertical sliders and two types of dial added. The dials work by clicking and dragging vertically on the dial (not doing something with rotations).
Attached is some example code you might use to pack lots of dials into a screen, and a screenshot of what it looks like.
Quote:
Originally Posted by reapercurious
in my opinion it would be better than a knob or a slider to have a number entry box that is click+draggable.
Interesting - what would you use this for? That click+drag is how the dials work, so would a dial with text on it work for you, as in the example?
Just a note to say thanks for this! I've just used it to create control/edit panels for the Roland JU06, JP08, JX03 and TR8, which I shall upload to the stash later.
Just one observation - on the dials, if you let your finger linger on the mouse button, the value starts to drop quite rapidly even if you're not moving the mouse. This is only a minor issue for my purposes.
OK, I have rewritten the documentation from scratch, including tons of example code and screenshots, and even an interactive JSFX demo (which doubles as API documentation).
I've added Retina support (because I got a new monitor and all the JSFX suddenly looked a bit blocky).
You enable it by setting:
Code:
gfx_ext_retina = 1;
If all you're using is the built-in controls, that's all you need, and it should just work! It hides the difference for you, so even if your code uses exact pixel sizes (e.g. "ui_push_top(50)"), then it will still draw the same size on your screen.
If you are drawing your own UI components, using ui_left()/ui_height() etc. then you need a little bit extra, because the viewport functions like ui_left() will give their answer scaled down. This means that if you then draw to these positions using gfx_* functions, it won't be in the right place.
The simplest solution is: the function ui_retina() can either get the current pixel-scaling value, or set the pixel-scaling value. If you set the pixel-scaling value to 1, then it ui_left()/others will return the values you want, which match up to positions in the actual image buffer. (If you use any of the built-in elements at this point it will now draw them at the wrong size, though.)
Code:
ui_push()
ui_retina(1); // Remove all pixel-scaling
// You can now use ui_left() as actual pixel locations
// However, it is your responsibility to draw things twice the size if needed
gfx_x = ui_left();
gfx_y = ui_top();
...
ui_pop();
For example, these two lines are all it took to modify my Panalysis effect (which uses built-in controls but also has a custom graphics section): set gfx_ext_retina=1, and then call ui_retina(1) just before our custom drawing code.
It gets a bit fussier when dealing with the mouse though, but I hope to sort that out soon.
Shared on another thread, but makes sense to post here as well.
I've made an experimental UI generator. The idea is that you put a few simple annotations next to the sliders, to tell it how to organise the controls into rows and groups.
Here are some annotations added to the built-in "Auto Wideness" effect:
Could you post a feature list, e.g. what kind of controls can be generated and what options can be assigned to those (position, color, annotation, orientation, scale, ....) ?
Do you plan extensions such as "Level indicators" or similar ? (Checkboxes/Switches, and dropdown lists seem to be already in place ! )
Is there an easy way to combine such an automatically generated GUI with additional GUI elements such as graphics (e.g. I often do spectrum diagrams), or text ?
Thanks a lot !
Could you post a feature list, e.g. what kind of controls can be generated and what options can be assigned to those (position, color, annotation, orientation, scale, ....) ?
I chose to make an opinionated tool that did as much as possible automatically. So it makes some choices for you (e.g. selecting which controls to use). This means you can get a useful UI working using just the "// ui:row" and "// ui:group" annotations.
The screenshots in my previous post are literally just what is produced using the annotations in the code above them (plus a single extra annotation for the compact version).
You can override certain things, and supported parameters are mentioned in the how-to guide. My next comment explains a bit more, and the initial code (which appears when you open the tool) demonstrates their use.
The layout is pretty fixed: controls are listed in order, and are separated into rows, each row is separated into groups, and everything is evenly spaced. You only really get to specify how it's split into rows/groups, not individually place each control.
It can't do different colours, although you can provide a custom overall theme using a simple bitmap template.
Quote:
Originally Posted by mschnell
Do you plan extensions such as "Level indicators" or similar ? (Checkboxes/Switches, and dropdown lists seem to be already in place ! )
Possible - I mean, even in the example, the "Current Wideness" one is an output, and it draws as a dial (which is slightly inaccurate).
I don't want to get too specific, though - and also something like a level-meter might want to be a different shape to everything else, so can't be laid out in a basic grid. So yeah maybe, but at a certain point it might make more sense to use the generated code as a starting-point, and start wrestling with the layout code. :/ I'm not sure where the best balance is for that.
Quote:
Originally Posted by mschnell
Is there an easy way to combine such an automatically generated GUI with additional GUI elements such as graphics (e.g. I often do spectrum diagrams), or text ?
Graphics: absolutely! The "how-to" section in the tool explains how to do this (see "Including existing graphics code"). Basically you wrap a function declaration around your existing @gfx block, add an annotation with the function name, and you're done - your custom graphics are displayed underneath the generated controls.
Text: not currently (assuming you mean "insert fixed text into certain points in the layout"). The only place custom text appears is group labels and control titles.
So, I put a bit of effort into writing the guide (included in the tool itself), and I think all of the annotations and parameters are covered in there. If something's not clear, I should change that guide.
However, here's a bit more detail as requested:
Controls
The controls are chosen based on the slider definition, and the options are:
switch - for a slider with two named options
up/down selector - for a slider with a handful of named options
an "edit" button which opens a popup full of radio-selectors - for a slider with many named options
plain dial - for a slider with no named options.
Annotations
"// ui:row" - starts a new row. No parameters.
"// ui:group" - starts a new group within a row. One parameter: name (optional)
"// ui:gfx" - configures custom graphics. Parameters are function, height, buffer (optional) and finish (optional) - see the "Including existing graphics code" section for details.
"// ui:layout" - configures overall layout. Parameters are compact (optional) and simple-bitmap (optional) - see "Compact layout" and "Custom Theme/Colours" sections.
Parameters
All parameters must be comma-separated.
Any comment line that doesn't start with one of those is assumed to be a set of control parameters, which are:
"title=" - for any control, it overrides the displayed title
"format=" - for dials, it's an sprintf-style format for display
"bias=" - for dials, changes how angle maps to value. 0 is linear, positive means more detail on the lower values, negative the opposite.
"inverted=" - for switches/selectors, 0 means in the order they are defined, 1 means reversed/flipped
"columns=" - for up/down selectors and popups. 0 forces it to be an up/down selector, positive integers make it a popup split into that number of columns
If your slider title ends with a single word in brackets (e.g. "Attack (ms)" ), that's taken to be a unit, and displayed alongside the value instead of in the title. This can be overridden by explicitly providing "format=" and "title=".
This is great! (Range slider would be a nice addition )
Don't know how usable this would be, but gfx_getdropfile seems to work in JSFX too:
Code:
EEL: gfx_getdropfile(idx[,#str])
Enumerates any drag/dropped files. call gfx_dropfile(-1) to clear the list when finished. Returns 1 if idx is valid, 0 if idx is out of range.
So, I put a bit of effort into writing the guide (included in the tool itself), and I think all of the annotations and parameters are covered in there. If something's not clear, I should change that guide.
Thanks a lot for this post !
I think such an overview is very helpful.
Thanks again,
-Michael
So far, I am able to import ui-lib, copy freemem @init, generate and paste @gfx code in the end, and hide JS sliders.
The GUI shows up, but the knob in the GUI would not respond to any click. Instead, moving the original JS slider will turn the GUI into a blank white screen with a message "Unknown screen: (65.000000)".
I suspect this can be solved by a "needs_slider_update" definition. However, I haven't figured out how to implement the function (in @init) yet.
Can you show us a before & after example in how to do this?
I am hoping it will work with sysex editors like below and with CCenv by Panu Aaltio and P.M. Crockett.
A generator like this for us less technical users is a godsend.
So far, I am able to import ui-lib, copy freemem @init, generate and paste @gfx code in the end, and hide JS sliders.
The GUI shows up, but the knob in the GUI would not respond to any click. Instead, moving the original JS slider will turn the GUI into a blank white screen with a message "Unknown screen: (65.000000)".
So, the idea of "freemem" in my example is to make sure you don't have a clash, where two bits of code are trying to use the same section of memory buffer for different things.
However, in your code, you aren't initialising "msgbuf", so it has the default value 0 - which is the same section of memory that you're passing into ui_setup().
The fix is to initialise msgbuf, using freemem so it is guaranteed to come after the memory that the UI system's using:
Code:
@init
freemem = 0;
// Reserve some memory buffer for the UI
freemem = ui_setup(freemem);
// Reserve some memory buffer for msgbuf
msgbuf = freemem;
freemem += 14; // msgbuf has 14 items
Does that make sense?
With that fix, it draws two dials as intended.
Quote:
Originally Posted by tksense
I suspect this can be solved by a "needs_slider_update" definition. However, I haven't figured out how to implement the function (in @init) yet.
You don't need this with the code you posted. Your @slider section is just doing rounding, but the dials produce rounded outputs anyway, just like the built-in sliders do.
Now the only thing issue left with this sysex JS is how GUI's knobs do not speak automation to the DAW, whereas I'd have to still either grab the original JS sliders or go to "envelopes menu" to manually tick the checkbox to activate and use the slider in the track envelope lane.
Would this be an easy fix?
The next endeavor is to view all parameters in CCenv - this JS works along with another port-opening JS "CCenv input", which together enables Reaper to receive incoming CC and draw automation on the DAW at the same time.
The only issue is, of course, the 64 sliders. Is this one any more complicated? The below code returns all kinds of jitter like flashing knobs and sending CC117-127, but only when GUI is viewed (stops jitters when GUI is not on), is this related to "needs_slider_update"?
As I have no idea where to begin besides asking, so here's the code
Frankly with this one, I'd be happy with a two-column view at 32 parameters each, just to view all parameters. I'm curious, can this be as simple as adding a few lines of code? This can be good for a quick fix sometimes.
Thanks for your help, I'm learning bit by bit, one step at a time!
Now the only thing issue left with this sysex JS is how GUI's knobs do not speak automation to the DAW
Done - updated the UI generator to add automation support. It now updates "last touched" in the Param menu, and the controls can also be used to record automation ("write" and "latch" mode work fine, I'm still working on "touch").
Quote:
Originally Posted by tksense
The next endeavor is to view all parameters in CCenv
[...]
The only issue is, of course, the 64 sliders. Is this one any more complicated? The below code returns all kinds of jitter like flashing knobs and sending CC117-127, but only when GUI is viewed (stops jitters when GUI is not on), is this related to "needs_slider_update"?
Nope - again, you have a clash with memory locations. It picks some memory locations in this code:
Frankly with this one, I'd be happy with a two-column view at 32 parameters each, just to view all parameters.
I tried it out, and I think it works nicely with 4 columns.
To do this (once you've made the above fix), just put a row annotation every four sliders. You also probably want the compact-mode annotation, like this:
The GUI basically works now, I'm very happy already!
As for minor details, I am wondering if the GUI can display midi values 0-127 in a -63, 0 , +64 format?
A formula 0<-63,64,1> did not work because the incoming MIDI value 0 would be interpreted as a zero in the middle. So I will have to stick with a 64<0,127,1> in the JS.
As for the ON/OFF switch, is it possible to assign midi values other than 0, 1 by replacing a few lines or numbers inside the generated gfx code? For example, I'd like a switch to choose between value 16 and 48. Something like this in theory: 16<16,48,32{Off,on}>. Now I'd get around this by enumerating
"off,off,off...on" until the 49th integer. It works.
I've spent some more time trying to optimise the GUI behavior.
-For non-incremental values, I've decided to name the options in the parameter as it would probably be too much hassle to alter the code. For example in the picture below, 0=norm, 64=Punch, 127=Snap.
-The Cyan GUI comes from Geraint's original preset "theme-cyan". I think it looks very nice!
-For Novation A-Station, I changed the theme and knob colors and gave it a white tick, starting from the GUI template provided by Geraint.
I am wondering if the GUI can display midi values 0-127 in a -63, 0 , +64 format?
Quote:
Originally Posted by bezusheist
Easy...just subtract 63 from the (0-127) value.
Yep. There isn't a way to do this using the code generator, so this is the point where you have to make manual changes to the code.
If I understand correctly, you want to display values as -63-64, but leave the actual values as 0-127. There should be a line that looks a bit like this in your generated code:
You'll need to subtract 63 from the value before the text is calculated. You also need to add +63 back on afterwards - this is because the user can right-click and enter exact new values (now in the -63-64 range), so you need to translate those back into 0-127:
I have been working for some weeks with the very interesting UI library developed by Geraint Luff, and I found it provided many features I was looking for. Some of you may be interested in some additions I made to it.
Very shortly, these additions address the following goals :
- Check for the necessity of executing, or not, the graphic interface of the plugin. For me, this is useful to reduce CPU usage, since I work on installations using slow CPU cards.
- Provide a support for 24bit packed RGB colors ;
- Provide a control for native gfx menus.
1) Optimization of the execution of the graphic section
When you don't need real time display, you can choose to execute the graphic section only once every “n” frames, or every “n” seconds, or when the mouse is clicked inside the plugin window. Here are some examples :
Code:
// In the “@init” section, use a combination of :
ui_Xframe_period = 6; // execute every “6” graphic frames
ui_Xtime_period = 2.5; // execute every 2.5 seconds
ui_XFlags = ui_XonLMC | ui_XonKeyb; // execute when left mouse button is clicked or released, or when a char is typed
// and anywhere in your code :
ui_GFXdoNow = 1; // to execute once the "@gfx" section as soon as possible
// Then, in the @gfx section, use :
must_do_gfx() ? (
// do here the graphic stuff, like :
control_start("main", "tron");
…
);
2) Support of 24bit packed colors.
Actually of little use for now, except for the “menu” extension. It is expected to be more usable when new controls types are added.
3) Control of menus
A menu is defined by an array of values. This is an example of a 3 items menu
Code:
size_menu = nxtmem; nxtmem += 32; // array for the menu description
size_menu[ui_jmenu_typ] = ui_jmenu_typ_sel | ui_jmenu_opt_rndrect;
size_menu[ui_jmenu_itc] = 3; // item count
size_menu[ui_jmenu_res] = 0; // last item selected
size_menu[ui_jmenu_ticks] = 1; // first item is “ticked”
size_menu[ui_jmenu_disf] = 0; // no item is disabled
size_menu[ui_jmenu_title] = "Size"; // the title
size_menu[ui_jmenu_items+0] = "resizable"; // item 1
size_menu[ui_jmenu_items+1] = "fixed 1"; // item 2
size_menu[ui_jmenu_items+2] = "fixed 2"; // item 3
// You install the menu like any other graphic component, and control
// it with the “ui_menu_control” function :
ui_split_toptext(-1); // This is for the menu bar...
ui_split_leftratio(1/4); // up to 4 menus, 1 used...
// This menu is associated with a slider : size_sel
zz8 = ui_menu_control(size_menu, size_sel);
(zz8 != 0) ? ( ...do something... );
ui_pop();
ui_pop();
There is actually 3 types of menus : “action” menus, which use no tick mark, “selection” menus, where just one item can have a mark, and “option” menus, where every item can be marked. Also, every item can be enabled or disabled.
Installing the extension
After deleting the ".txt" in the attached files, put the file “ui-lib-JJ.jsfx-inc” in the same directory as your plug-in, and include it after “ui-lib.jsfx-inc”. You need of course to have the original “ui-lib.jsfx-inc” file in this directory.
The file “chkui01.jsfx” is an example of use. You may want to add in the plug-in directory a subdirectory named “themes”, with the various “theme-xxx.png” provided by Geraint Luff as examples, or parts of its own plug-ins.
The documentation is mainly in the codes of the library and the provided example. Of course, all questions, requests and bug reports are welcomed...
I used your Generator page to get some code back for the "8 to 1 stereo"
so... im not to sure atm if this would effect different jsfx differntly but..
Long story short my knobs are working but.. when i hit stop and then play again I have to touch one of the knobs to hear anything.
I tried putting the update slider bits in different sections of the code and changing their values but have no idea what i am doing.
Any chance you could help with this?
also i dont know if this s helpful at all ... but one variation (moving the update slider bits around in the code below) had it so i could set the knob/sliders and there was no funny business about having to touch a knob to hear something.... but i could not adjust the knobs/sliders in real time...i had to stop/play to hear the change
Code:
/*******************************************************************************
* Copyright 2007 - 2011, Philip S. Considine *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License (http://www.gnu.org/licenses/)for more details. *
*******************************************************************************/
desc:8x Stereo to 1x Stereo Mixer
//desc:8x Stereo to 1x Stereo Mixer [IXix]
desc:Drum Levels for DrumFX
import ui-lib.jsfx-inc
//tags: mixer gain
//author: IXix
slider1:0<-60,30,0.1>Sub
slider2:0<-60,30,0.1>Kick
slider3:0<-60,30,0.1>snare
slider4:0<-60,30,0.1>CL-HH
slider5:0<-60,30,0.1>OP-HH
slider6:0<-60,30,0.1>Level 11+12 RimShot)(dB)
slider7:0<-60,30,0.1>Level 13+14 (Rando1) (dB)
slider8:0<-60,30,0.1>Level 15+16 (Rando2) (dB)
slider9:0<-60,30,0.1>Level 17+18 (Rando3) (dB)
in_pin:input 1 L
in_pin:input 1 R
in_pin:input 2 L
in_pin:input 2 R
in_pin:input 3 L
in_pin:input 3 R
in_pin:input 4 L
in_pin:input 4 R
in_pin:input 5 L
in_pin:input 5 R
in_pin:input 6 L
in_pin:input 6 R
in_pin:input 7 L
in_pin:input 7 R
in_pin:input 8 L
in_pin:input 8 R
in_pin:input 9 L
in_pin:input 9 R
out_pin:output L
out_pin:output R
///////////////////////////////////////////////////////////////////////////////
@init
freemem = 0; // some empty section of the memory buffer
freemem = ui_setup(freemem); // returns the first index it's not using
gainMin = -60;
gainMax = 30;
///////////////////////////////////////////////////////////////////////////////
function slider_update_function() (
needs_slider_update = 0;
// previous @slider code
// @slider
slider1 = min(max(slider1, gainMin), gainMax);
slider2 = min(max(slider2, gainMin), gainMax);
slider3 = min(max(slider3, gainMin), gainMax);
slider4 = min(max(slider4, gainMin), gainMax);
slider5 = min(max(slider5, gainMin), gainMax);
slider6 = min(max(slider6, gainMin), gainMax);
slider7 = min(max(slider7, gainMin), gainMax);
slider8 = min(max(slider8, gainMin), gainMax);
slider9 = min(max(slider9, gainMin), gainMax);
levelA = 2 ^ (slider1 / 6);
levelB = 2 ^ (slider2 / 6);
levelC = 2 ^ (slider3 / 6);
levelD = 2 ^ (slider4 / 6);
levelE = 2 ^ (slider5 / 6);
levelF = 2 ^ (slider6 / 6);
levelG = 2 ^ (slider7 / 6);
levelH = 2 ^ (slider8 / 6);
levelI = 2 ^ (slider9 / 6);
);
///////////////////////////////////////////////////////////////////////////////
@sample
// Do the left mix
spl0 = (spl0 * levelA) +
(spl2 * levelB) +
(spl4 * levelC) +
(spl6 * levelD) +
(spl8 * levelE) +
(spl10 * levelF)+
(spl12 * levelG)+
(spl14 * levelH)+
(spl16 * levelI);
// Do the right mix
spl1 = (spl1 * levelA) +
(spl3 * levelB) +
(spl5 * levelC) +
(spl7 * levelD) +
(spl9 * levelE) +
(spl11 * levelF)+
(spl13 * levelG)+
(spl15 * levelH)+
(spl17 * levelI);
@gfx 810 130
// AUTOGENERATED UI //
// generated from slider section: https://github.com/geraintluff/jsfx-ui-lib
function gfx_ui_automate_slider(slidervar*, new_value) (
slidervar !== new_value ? (
slidervar = new_value;
slider_automate(slidervar);
);
new_value;
);
function gfx_ui_layout_textnumber(title, value, format) local(h) (
h = max((ui_height() - 60)/2, ui_height()*0.2);
ui_split_top(h);
ui_text(title);
ui_pop();
ui_split_bottom(h);
value = control_hidden_textnumber(value, value*1.00000001, format);
ui_pop();
value;
);
function gfx_ui_dial_rounded(slidervar*, low, high, bias, default, step) (
slidervar != floor(slidervar._ui_gen_float/step + 0.5)*step ? slidervar._ui_gen_float = slidervar;
slidervar._ui_gen_float = control_dial(slidervar._ui_gen_float, low, high, bias, default);
gfx_ui_automate_slider(slidervar, floor(slidervar._ui_gen_float/step + 0.5)*step);
);
control_start("main", "default");
ui_screen() === "main" ? (
ui_split_topratio(1/1); // single row
ui_split_leftratio(9/9);
// row 1, group 1
ui_split_leftratio(1/9);
gfx_ui_automate_slider(slider1, gfx_ui_layout_textnumber("Sub", slider1, "%.1f"));
gfx_ui_dial_rounded(slider1, -60, 30, 0, 0, 0.1);
ui_split_next();
gfx_ui_automate_slider(slider2, gfx_ui_layout_textnumber("Kick", slider2, "%.1f"));
gfx_ui_dial_rounded(slider2, -60, 30, 0, 0, 0.1);
ui_split_next();
gfx_ui_automate_slider(slider3, gfx_ui_layout_textnumber("snare", slider3, "%.1f"));
gfx_ui_dial_rounded(slider3, -60, 30, 0, 0, 0.1);
ui_split_next();
gfx_ui_automate_slider(slider4, gfx_ui_layout_textnumber("CL-HH", slider4, "%.1f"));
gfx_ui_dial_rounded(slider4, -60, 30, 0, 0, 0.1);
ui_split_next();
gfx_ui_automate_slider(slider5, gfx_ui_layout_textnumber("OP-HH", slider5, "%.1f"));
gfx_ui_dial_rounded(slider5, -60, 30, 0, 0, 0.1);
ui_split_next();
gfx_ui_automate_slider(slider6, gfx_ui_layout_textnumber("Level 11+12 RimShot)", slider6, "%.1f dB"));
gfx_ui_dial_rounded(slider6, -60, 30, 0, 0, 0.1);
ui_split_next();
gfx_ui_automate_slider(slider7, gfx_ui_layout_textnumber("Level 13+14 (Rando1)", slider7, "%.1f dB"));
gfx_ui_dial_rounded(slider7, -60, 30, 0, 0, 0.1);
ui_split_next();
gfx_ui_automate_slider(slider8, gfx_ui_layout_textnumber("Level 15+16 (Rando2)", slider8, "%.1f dB"));
gfx_ui_dial_rounded(slider8, -60, 30, 0, 0, 0.1);
ui_split_next();
gfx_ui_automate_slider(slider9, gfx_ui_layout_textnumber("Level 17+18 (Rando3)", slider9, "%.1f dB"));
gfx_ui_dial_rounded(slider9, -60, 30, 0, 0, 0.1);
ui_pop();
ui_pop();
ui_pop();
) : control_system();
ui_interacted() ? needs_slider_update = 1;
// END OF AUTOGENERATED UI //
@block
needs_slider_update ? slider_update_function();