Old 04-08-2020, 03:29 PM   #41
lalo
Human being with feelings
 
Join Date: Jun 2006
Posts: 187
Default

Hi all, great stuff in here. I'm looking for a way to do tabbed UI in a jsFX. Is this framework abel to do it? Is the "screen" paradigm a kind of tab UI system but limited to a max of 10 tabs?
thanks!
a.
lalo is offline   Reply With Quote
Old 01-14-2021, 04:42 PM   #42
Nostrap
Human being with feelings
 
Join Date: Dec 2017
Posts: 179
Default

Had hopefully a simple question. I have a UI built with a few sliders, and when I set the @gfx w, h it will only actually resize the plugin to be the height I request and not the width. Is there a way to override whatever is making the window larger than I request?
Nostrap is offline   Reply With Quote
Old 01-15-2021, 01:46 AM   #43
JPH
Human being with feelings
 
JPH's Avatar
 
Join Date: Sep 2020
Location: In the alps
Posts: 20
Default

You could fix the width by doing "gfx_w = value;"
This will however prevent the width from scaling when you drag the window larger.

Last edited by JPH; 01-18-2021 at 05:59 AM.
JPH is offline   Reply With Quote
Old 01-18-2021, 05:22 AM   #44
GameAudioRvlzzr
Human being with feelings
 
GameAudioRvlzzr's Avatar
 
Join Date: Apr 2016
Location: Stuttgart, Germany
Posts: 217
Default

Quote:
Originally Posted by geraintluff View Post
I've started to write a getting-started guide. I would appreciate any feedback.
Hey, i'm working through your guide atm and it's helping me a great deal, thank you!

However, the following part has a mistake, i think:
https://github.com/geraintluff/jsfx-...with-arguments

Shouldn't ui_screen_set really be ui_screen_arg? I think this is a typo.

Or am i misunderstanding something?
Micha

Last edited by GameAudioRvlzzr; 01-18-2021 at 05:38 AM.
GameAudioRvlzzr is offline   Reply With Quote
Old 01-18-2021, 05:44 AM   #45
JPH
Human being with feelings
 
JPH's Avatar
 
Join Date: Sep 2020
Location: In the alps
Posts: 20
Default

Quote:
Originally Posted by GameAudioRvlzzr View Post
Hey, i'm working through your guide atm and it's helping me a great deal, thank you!

However, the following part has a mistake, i think:
https://github.com/geraintluff/jsfx-...with-arguments

Shouldn't ui_screen_set really be ui_screen_arg? I think this is a typo.

Or am i misunderstanding something?
Micha
I think Geraint hasn't been on the forum for a while. But maybe I can help you somehow.

The function ui_screen_set uses ui_screen_arg:
Code:
function ui_screen_set(index, value) (
	ui_screen_arg(index, value);
);
Code:
function ui_screen_arg(index, value) local(screen, offset) (
	screen = (uix_screenstack + uix_screenstack_step*uix_screenstack_currentlayer);
	offset = index + 1;
	offset < uix_screenstack_step ? (
		screen[offset] = value;
	) : (
		ui_error("Screens only have 10 arguments");
	);
);
ui_screen_set is not in his API but only in the "tour" compat file.
JPH is offline   Reply With Quote
Old 01-18-2021, 06:19 AM   #46
GameAudioRvlzzr
Human being with feelings
 
GameAudioRvlzzr's Avatar
 
Join Date: Apr 2016
Location: Stuttgart, Germany
Posts: 217
Default

Thank you!

Quote:
Originally Posted by JPH View Post
The function ui_screen_set uses ui_screen_arg:
Code:
function ui_screen_set(index, value) (
	ui_screen_arg(index, value);
);
ui_screen_set is not in his API but only in the "tour" compat file.
Do i understand you correctly that the two are interchangeable?
Because if i use set, i get a compile error, while the same line, only difference being using arg, works.
I also get a compile error if i use the code example provided in the guide.
GameAudioRvlzzr is offline   Reply With Quote
Old 01-18-2021, 06:28 AM   #47
JPH
Human being with feelings
 
JPH's Avatar
 
Join Date: Sep 2020
Location: In the alps
Posts: 20
Default

Quote:
Originally Posted by GameAudioRvlzzr View Post
Thank you!



Do i understand you correctly that the two are interchangeable?
Because if i use set, i get a compile error, while the same line, only difference being using arg, works.
I also get a compile error if i use the code example provided in the guide.
Yes theoretically they are interchangeable. However only _arg is defined in ui-lib.jsfx-inc.
_set is defined in ui-lib-compat.jsfx-inc, so to use it, you'd have to import it:
Code:
import ui-lib-compat.jsfx-inc
somewhere before your @init section.
Would you like to share your code and the error you get, or did my explanation resolve it?

Last edited by JPH; 01-18-2021 at 06:34 AM.
JPH is offline   Reply With Quote
Old 01-18-2021, 09:09 AM   #48
GameAudioRvlzzr
Human being with feelings
 
GameAudioRvlzzr's Avatar
 
Join Date: Apr 2016
Location: Stuttgart, Germany
Posts: 217
Default

Oh i see.

Quote:
Originally Posted by JPH View Post
Would you like to share your code and the error you get, or did my explanation resolve it?
It's resolved, thank you very much.
Micha
GameAudioRvlzzr is offline   Reply With Quote
Old 01-18-2021, 02:14 PM   #49
GameAudioRvlzzr
Human being with feelings
 
GameAudioRvlzzr's Avatar
 
Join Date: Apr 2016
Location: Stuttgart, Germany
Posts: 217
Default

I have 2 more problems, and i've been trying for hours to solve them myself, but no luck -.-

- I try to send a SysEx message in the init section, hoping it gets send when i load the plugin, but it doesn't work. Strangely, it DOES work when, while the plugin is already running, i change something irrelevant (like change a comment) in the code and save.

- I can't get a button to send a midi note. The very same midi message works just fine when i send it from the block section... but not from the button in the gfx section.

Do you have any pointers for me?

Code:
desc: _mLaunchpad2Actions_v0.0.2_forForum

import ui-lib.jsfx-inc

in_pin:none
out_pin:none 
options:no_meter

// --- INIT ---------------------------------------------
@init

file = -1;
ext_noinit = 1.0;
ui_buffer = ui_setup(0);


midisend_str(0, "\xf0\x00\x20\x29\x02\x0d\x00\x7f\xf7");


// --- BLOCK ---------------------------------------------
@block

// --- GFX ---------------------------------------------
@gfx 
ui_start("main");

ui_screen() == "main" ? (
	control_button("Send Note 11 with random velocity") ? (
		midisend(0, $x90, 11, rand(128));
	);
);

Last edited by GameAudioRvlzzr; 01-18-2021 at 02:44 PM. Reason: clarification
GameAudioRvlzzr is offline   Reply With Quote
Old 01-19-2021, 02:57 AM   #50
JPH
Human being with feelings
 
JPH's Avatar
 
Join Date: Sep 2020
Location: In the alps
Posts: 20
Default

Quote:
Originally Posted by GameAudioRvlzzr View Post
- I can't get a button to send a midi note. The very same midi message works just fine when i send it from the block section... but not from the button in the gfx section.

Do you have any pointers for me?
So I didn't really write much MIDI related code, but maybe i can help.
This could be a solution. You declare x when hitting the button, which runs the code in the block section.

About the first question, I don't really understand what you want to do. You'd like to run the midisend command at startup?

Code:
desc: _mLaunchpad2Actions_v0.0.2_forForum

import ui-lib.jsfx-inc

in_pin:none
out_pin:none 
options:no_meter

// --- INIT ---------------------------------------------
@init

file = -1;
ext_noinit = 1.0;
ui_buffer = ui_setup(0);

x = 0;

// --- BLOCK ---------------------------------------------
@block
x == 1 ? (
midisend(0, $x90, 11, rand(128));
x = 0;
);
// --- GFX ---------------------------------------------
@gfx 
ui_start("main");

ui_screen() == "main" ? (
  control_button("Send Note 11 with random velocity") ? (
      x = 1;
  );
);
Another possibility is to wrap it in a function. The midisend() command can't however be executed from the @gfx section.

Code:
desc: _mLaunchpad2Actions_v0.0.2_forForum

import ui-lib.jsfx-inc

in_pin:none
out_pin:none 
options:no_meter

// --- INIT ---------------------------------------------
@init

file = -1;
ext_noinit = 1.0;
ui_buffer = ui_setup(0);

function note11() (
midisend(0, $x90, 11, rand(128));
);
// --- BLOCK ---------------------------------------------
@block
x == 1 ? (
note11();
x = 0;
);

@gfx 
ui_start("main");

ui_screen() == "main" ? (
  control_button("Send Note 11 with random velocity") ? (
      x = 1;
  );
);

Last edited by JPH; 01-19-2021 at 03:04 AM.
JPH is offline   Reply With Quote
Old 01-19-2021, 04:16 AM   #51
GameAudioRvlzzr
Human being with feelings
 
GameAudioRvlzzr's Avatar
 
Join Date: Apr 2016
Location: Stuttgart, Germany
Posts: 217
Default

Thank you JPH!

Quote:
Originally Posted by JPH View Post
About the first question, I don't really understand what you want to do. You'd like to run the midisend command at startup?
Yes, i'd like to send a Sysex-string once, when the plugin starts (= when the user puts it onto a track's FX chain). In order to control the Launchpad, i need to initially put it into a certain mode, which happens by Sysex message.

Quote:
Originally Posted by JPH View Post
This could be a solution. You declare x when hitting the button, which runs the code in the block section.
Yes, i'll try this. I think i'll have to put extra code in there to make sure it gets executed only once per button press, but i'll think i can do it.

Quote:
Originally Posted by JPH View Post
The midisend() command can't however be executed from the @gfx section.
That's the thing, i'm not so much looking for a workaround, rather than understanding why the button can't execute the midisend command itself. Trying to learn eel and not sure if i'm missing a larger concept here.
GameAudioRvlzzr is offline   Reply With Quote
Old 01-19-2021, 04:34 AM   #52
JPH
Human being with feelings
 
JPH's Avatar
 
Join Date: Sep 2020
Location: In the alps
Posts: 20
Default

Quote:
Originally Posted by GameAudioRvlzzr View Post
Yes, i'd like to send a Sysex-string once, when the plugin starts (= when the user puts it onto a track's FX chain). In order to control the Launchpad, i need to initially put it into a certain mode, which happens by Sysex message.
I think you can do that the same way I did it in the code example above. You wrap it in a condition which gets executed only at startup (but from the @block section).

Quote:
Originally Posted by GameAudioRvlzzr View Post
Yes, i'll try this. I think i'll have to put extra code in there to make sure it gets executed only once per button press, but i'll think i can do it.
When I tested it, it only executed once on button press.

Quote:
Originally Posted by GameAudioRvlzzr View Post
That's the thing, i'm not so much looking for a workaround, rather than understanding why the button can't execute the midisend command itself. Trying to learn eel and not sure if i'm missing a larger concept here.
Well yeah for that I can only refer you to the documentation. Different sections do different things. I am no expert on this, but I think the sections are well explained in there.
JPH is offline   Reply With Quote
Old 01-19-2021, 10:13 AM   #53
GameAudioRvlzzr
Human being with feelings
 
GameAudioRvlzzr's Avatar
 
Join Date: Apr 2016
Location: Stuttgart, Germany
Posts: 217
Default

You're right.
Works like a charm, thank you!
GameAudioRvlzzr is offline   Reply With Quote
Old 02-28-2021, 06:49 AM   #54
Phazma
Human being with feelings
 
Join Date: Jun 2019
Posts: 2,875
Default

Could someone please help me to get this working?

Basically I have created the UI with the UI generator and so far it all looks nice.
However when mouse-tweaking the knobs, the parameters only update after I start/stop playback and not in realtime. I can not hear the sound changing when tweaking the knobs during playback. If controlled by envelopes however the parameters change correctly in realtime during playback.

I suspect this might be related to buffering settings as discussed by Geraint with another user previously. However I have no clue how to set it up correctly for my case. All of Geraint's suggested settings in this thread, on the UI generator description and in the guide don't work.

How can I find out the correct way to set this up for the jsfx I want to skin?

Here is the code. It is a mashup of various jsfx by various scripters with a few lines from myself (with next to no scripting knowledge) so it might be very messy, non-standard and eye-hurting. Please bear with me But the ui-lib related stuff should all be in the header and start of @init and in the bottom @gfx section anyway:

Code:
desc: Distoclip HQ

slider1:0<-24,24,0.1>-Input
slider2:dBGain=0<  0,76,0.01>-Drive
slider3:0<-0.5,0.5,0.01>-Symmetry
slider4:0<0,48,0.1>-Hardclip
slider5:0<-24,0,0.5>-Output
slider6:ovs=0<0,4,{1x,2x,4x,8x,16x}>-Quality
slider7:filter=1<0,3,{Relaxed,Normal,Heavy,Insane}>-Filter
slider8:dBCeil=0<-24, 0,0.5>-Output

in_pin:left input
in_pin:right input
out_pin:left output
out_pin:right output

import ui-lib.jsfx-inc

@init
  // Two buffers of length 1024
  buffer0 = 0;
  buffer1 = 1024;
  // Next free memory slot is 2048
  ui_setup(2048);   
  
  
  // Buffer for upsampled samples
  ups = 100000;

  orderUp = 1;
  orderDn = 1;
  
  // Decibel to gain factor conversion
  function dBToGain (decibels) (10.0 ^ (decibels / 20.0));
  
  function tanh (number) local (xa, x2, x3, x4, x7, res)
  (
    xa = abs(number);
    x2 = xa * xa;
    x3 = x2 * xa;
    x4 = x2 * x2;
    x7 = x4 * x3;
    res = (1.0 - 1.0 / (1.0 + xa + x2 + 0.58576695 * x3 + 0.55442112 * x4 + 0.057481508 * x7));
    
    sign(number) * res;
  );
  
  // Soft clipping function with ceiling
  function softclip ()
  (

    vCeiling = min(roof, 1.0);
    vUpscale = gain / vCeiling;
    
    this  = tanh(this * vUpscale);
    this *= vCeiling;
  );
  

  function hardclip ()
  (
    this = max(-1, min(1, this))
  );
  

  function dcBlocker () instance (stateIn, stateOut)
  (
    stateOut *= 0.99988487;
    stateOut += this - stateIn;
    stateIn = this;
    this = stateOut;
  );
  
  // Filter used for up- and downsampling
  function bwLP (Hz, SR, order, memOffset) instance (a, d1, d2, w0, w1, w2, stack) local (a1, a2, ro4, step, r, ar, ar2, s2, rs2)
  (
    a  = memOffset; d1 = a+order; d2 = d1+order; w0 = d2+order; w1 = w0+order; w2 = w1+order;
    stack = order; a1  = tan($PI * (Hz / SR)); a2  = sqr(a1); ro4 = 1.0 / (4.0 * order);
    step = 0; while (step < order)
    (
      r   = sin($PI * (2.0 * step + 1.0) * ro4); ar2 = 2.0 * a1 * r; s2  = a2 + ar2 + 1.0; rs2 = 1.0 / s2;
      a[step]  = a2 * rs2; d1[step] = 2.0 * (1.0 - a2) * rs2; d2[step] = -(a2 - ar2 + 1.0) * rs2; step += 1;
    );
  );
  
  function bwTick (sample) instance (a, d1, d2, w0, w1, w2, stack) local (output, step)
  (
    output = sample; step = 0;
    while (step < stack)
    (
      w0[step] = d1[step] * w1[step] + d2[step] * w2[step] + output;
      output = a[step] * (w0[step] + 2.0 * w1[step] + w2[step]);
      w2[step] = w1[step]; w1[step] = w0[step]; step += 1;
    );
    output;
  );

  function updateOversamplingX () local (newX, newUp, newDn)
  (
    // Calculate new values, "just in case" and to compare
    newX = pow(2, ovs); // 0,1,2,3 -> 1,2,4,8
    newUp = 2^(filter+1);
    newDn = 2^(filter+2);
    

    ((newX != ovsX) || (newUp != orderUp) || (newDn != orderDn)) ? 
    (

      ovsX = newX;
      orderUp = newUp;
      orderDn = newDn;
      

      upFilterL.bwLP(22050, srate*ovsX, orderUp, 200000);
      upFilterR.bwLP(22050, srate*ovsX, orderUp, 201000);
      
      dnFilterL.bwLP(22000, srate*ovsX, orderDn, 202000);
      dnFilterR.bwLP(22000, srate*ovsX, orderDn, 203000);
    );
  );
  
;

@slider
  
  // Simple "slider dB value to gain factor" conversions
  input =10^(slider1/20);
  roof = dBToGain(dBCeil); // ceiling
  gain = dBToGain(dBGain); // boost
  offset =slider3;
  clip = 10^(slider4/20);
  ceiling =10^(slider5/20);
  hdistr = min(slider4/48,.999);
  foo = 2*hdistr/(1-hdistr);
  
@sample
  
  // Before any processing starts, check if new parameter values were
  // set on the UI, and "import" them to their targets if so.
  updateOversamplingX();
  
  // Do the following only if oversampling is happening
  ovsX > 1 ?
  (

    spl0 = upFilterL.bwTick(spl0 * ovsX);
    spl1 = upFilterR.bwTick(spl1 * ovsX);

    counter = 0;
    while (counter < ovsX-1)
    (
      ups[counter]      = upFilterL.bwTick(0);
      ups[counter+ovsX] = upFilterR.bwTick(0);
      counter += 1;
    );
  );
  
  // Oversampled or not, this is the place where the magic happens,
  // i.e. this is where the signal is clipped.
  
  spl0*=input;
  spl1*=input;
  
  dBGain != 0 ?
  (
    // Run the DC blocker on each sample
    spl0.softclip();
    spl1.softclip();
  );
 
  //Offset
  spl0+=offset;
  spl1+=offset;
  
  //Clip
  spl0*=clip;
  spl1*=clip;
  
  // And yet another block of stuff that has to be processed when
  // oversamplign is activated
  ovsX > 1 ?
  (
    counter = 0;
    while (counter < ovsX-1)
    (
      orly1 = ups[counter];
      orly1.softclip();
      ups[counter] = orly1;
      
      orly2 = ups[counter+ovsX];
      orly2.softclip();
      ups[counter+ovsX] = orly2;
      
      counter += 1;
    );

    spl0 = dnFilterL.bwTick(spl0);
    spl1 = dnFilterR.bwTick(spl1);
    
    counter = 0;
    while (counter < ovsX-1)
    (
      ups[counter]      = dnFilterL.bwTick(ups[counter]);
      ups[counter+ovsX] = dnFilterR.bwTick(ups[counter+ovsX]);
      counter += 1;
    );
  );

// Hard limit in case input goes over 0dBFS:
spl0=max(min(spl0,1),-1);
spl1=max(min(spl1,1),-1);

  
// Bring down to ceiling volume:
spl0*=ceiling;
spl1*=ceiling;

// If DC blocking enabled

  slider3 != 0 ?
  (
    // Run the DC blocker on each sample
    spl0.dcBlocker();
    spl1.dcBlocker();
  );

// Hard limit in case input goes over 0dBFS:
spl0=max(min(spl0,ceiling),-ceiling);
spl1=max(min(spl1,ceiling),-ceiling);



@gfx 340 110

// 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);
);

function gfx_ui_layout_text(title, text) 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);
    ui_text(text);
  ui_pop();
);

function gfx_ui_layout_title(title) (
  gfx_ui_layout_text(title, "");
);

function gfx_ui_ovs_to_text(value) (
  (value < 3 ? (value < 2 ? (value === 0 ? "1x" : "2x") : "4x") : (value === 3 ? "8x" : "16x"));
);

control_start("main", "tron");

ui_screen() === "main" ? (
  ui_split_topratio(1/1); // single row
    ui_split_leftratio(6/6);
      // row 1, group 1
      ui_split_leftratio(1/6);
        gfx_ui_automate_slider(slider1, gfx_ui_layout_textnumber("Input", slider1, "%.1f"));
        gfx_ui_dial_rounded(slider1, -24, 24, 0, 0, 0.1);
      ui_split_next();
        gfx_ui_automate_slider(dBGain, gfx_ui_layout_textnumber("Drive", dBGain, "%.2f"));
        gfx_ui_dial_rounded(dBGain, 0, 76, 2, 0, 0.01);
      ui_split_next();
        gfx_ui_automate_slider(slider3, gfx_ui_layout_textnumber("Symmetry", slider3, "%.2f"));
        gfx_ui_dial_rounded(slider3, -0.5, 0.5, 0, 0, 0.01);
      ui_split_next();
        gfx_ui_automate_slider(slider4, gfx_ui_layout_textnumber("Hardclip", slider4, "%.1f"));
        gfx_ui_dial_rounded(slider4, 0, 48, 2, 0, 0.1);
      ui_split_next();
        gfx_ui_automate_slider(slider5, gfx_ui_layout_textnumber("Output", slider5, "%.1f"));
        gfx_ui_dial_rounded(slider5, -24, 0, -2, 0, 0.5);
      ui_split_next();
        gfx_ui_layout_title("Quality");
        gfx_ui_automate_slider(ovs, control_selector(ovs, gfx_ui_ovs_to_text(ovs), min(4, ovs + 1), max(0, ovs - 1)));
      ui_pop();
    ui_pop();
  ui_pop();
) : control_system();
// END OF AUTOGENERATED UI //
Phazma is offline   Reply With Quote
Old 03-02-2021, 04:18 AM   #55
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default

Quote:
Originally Posted by JPH View Post
I think Geraint hasn't been on the forum for a while. But maybe I can help you somehow.
Thank you, I really appreciate this.

I got a bit burned-out on open-source stuff a while ago and can't be here as much as I'd like, but seeing this made me feel a bit lighter.

Quote:
Originally Posted by Phazma View Post
Could someone please help me to get this working?
The key is in the "Slider changes" section of the generator page.

Basically, when you change the sliders in @gfx (instead of using the built-in sliders), @slider doesn't execute. The best solution I've found is to wrap up your slider changes in a function, and call that from @slider, and also from @block if a flag is set by @gfx saying "things have changed".

(I could probably make this slightly easier by adding a function to the UI library, but it's always going to be a bit subtle and annoying.)

Here's a working version - look for all mentions of "slider_update_function" and "needs_slider_update" to see what I've changed.

Code:
desc: Distoclip HQ

slider1:0<-24,24,0.1>-Input
slider2:dBGain=0<  0,76,0.01>-Drive
slider3:0<-0.5,0.5,0.01>-Symmetry
slider4:0<0,48,0.1>-Hardclip
slider5:0<-24,0,0.5>-Output
slider6:ovs=0<0,4,{1x,2x,4x,8x,16x}>-Quality
slider7:filter=1<0,3,{Relaxed,Normal,Heavy,Insane}>-Filter
slider8:dBCeil=0<-24, 0,0.5>-Output

in_pin:left input
in_pin:right input
out_pin:left output
out_pin:right output

import ui-lib.jsfx-inc

@init
  // Two buffers of length 1024
  buffer0 = 0;
  buffer1 = 1024;
  // Next free memory slot is 2048
  ui_setup(2048);   
  
  
  // Buffer for upsampled samples
  ups = 100000;

  orderUp = 1;
  orderDn = 1;
  
  // Decibel to gain factor conversion
  function dBToGain (decibels) (10.0 ^ (decibels / 20.0));
  
  function tanh (number) local (xa, x2, x3, x4, x7, res)
  (
    xa = abs(number);
    x2 = xa * xa;
    x3 = x2 * xa;
    x4 = x2 * x2;
    x7 = x4 * x3;
    res = (1.0 - 1.0 / (1.0 + xa + x2 + 0.58576695 * x3 + 0.55442112 * x4 + 0.057481508 * x7));
    
    sign(number) * res;
  );
  
  // Soft clipping function with ceiling
  function softclip ()
  (

    vCeiling = min(roof, 1.0);
    vUpscale = gain / vCeiling;
    
    this  = tanh(this * vUpscale);
    this *= vCeiling;
  );
  

  function hardclip ()
  (
    this = max(-1, min(1, this))
  );
  

  function dcBlocker () instance (stateIn, stateOut)
  (
    stateOut *= 0.99988487;
    stateOut += this - stateIn;
    stateIn = this;
    this = stateOut;
  );
  
  // Filter used for up- and downsampling
  function bwLP (Hz, SR, order, memOffset) instance (a, d1, d2, w0, w1, w2, stack) local (a1, a2, ro4, step, r, ar, ar2, s2, rs2)
  (
    a  = memOffset; d1 = a+order; d2 = d1+order; w0 = d2+order; w1 = w0+order; w2 = w1+order;
    stack = order; a1  = tan($PI * (Hz / SR)); a2  = sqr(a1); ro4 = 1.0 / (4.0 * order);
    step = 0; while (step < order)
    (
      r   = sin($PI * (2.0 * step + 1.0) * ro4); ar2 = 2.0 * a1 * r; s2  = a2 + ar2 + 1.0; rs2 = 1.0 / s2;
      a[step]  = a2 * rs2; d1[step] = 2.0 * (1.0 - a2) * rs2; d2[step] = -(a2 - ar2 + 1.0) * rs2; step += 1;
    );
  );
  
  function bwTick (sample) instance (a, d1, d2, w0, w1, w2, stack) local (output, step)
  (
    output = sample; step = 0;
    while (step < stack)
    (
      w0[step] = d1[step] * w1[step] + d2[step] * w2[step] + output;
      output = a[step] * (w0[step] + 2.0 * w1[step] + w2[step]);
      w2[step] = w1[step]; w1[step] = w0[step]; step += 1;
    );
    output;
  );

  function updateOversamplingX () local (newX, newUp, newDn)
  (
    // Calculate new values, "just in case" and to compare
    newX = pow(2, ovs); // 0,1,2,3 -> 1,2,4,8
    newUp = 2^(filter+1);
    newDn = 2^(filter+2);
    

    ((newX != ovsX) || (newUp != orderUp) || (newDn != orderDn)) ? 
    (

      ovsX = newX;
      orderUp = newUp;
      orderDn = newDn;
      

      upFilterL.bwLP(22050, srate*ovsX, orderUp, 200000);
      upFilterR.bwLP(22050, srate*ovsX, orderUp, 201000);
      
      dnFilterL.bwLP(22000, srate*ovsX, orderDn, 202000);
      dnFilterR.bwLP(22000, srate*ovsX, orderDn, 203000);
    );
  );
  
;

function slider_update_function() (
  needs_slider_update = 0;
  // Simple "slider dB value to gain factor" conversions
  input =10^(slider1/20);
  roof = dBToGain(dBCeil); // ceiling
  gain = dBToGain(dBGain); // boost
  offset =slider3;
  clip = 10^(slider4/20);
  ceiling =10^(slider5/20);
  hdistr = min(slider4/48,.999);
  foo = 2*hdistr/(1-hdistr);
);

@slider
slider_update_function();

@block
needs_slider_update ? slider_update_function();

@sample
  
  // Before any processing starts, check if new parameter values were
  // set on the UI, and "import" them to their targets if so.
  updateOversamplingX();
  
  // Do the following only if oversampling is happening
  ovsX > 1 ?
  (

    spl0 = upFilterL.bwTick(spl0 * ovsX);
    spl1 = upFilterR.bwTick(spl1 * ovsX);

    counter = 0;
    while (counter < ovsX-1)
    (
      ups[counter]      = upFilterL.bwTick(0);
      ups[counter+ovsX] = upFilterR.bwTick(0);
      counter += 1;
    );
  );
  
  // Oversampled or not, this is the place where the magic happens,
  // i.e. this is where the signal is clipped.
  
  spl0*=input;
  spl1*=input;
  
  dBGain != 0 ?
  (
    // Run the DC blocker on each sample
    spl0.softclip();
    spl1.softclip();
  );
 
  //Offset
  spl0+=offset;
  spl1+=offset;
  
  //Clip
  spl0*=clip;
  spl1*=clip;
  
  // And yet another block of stuff that has to be processed when
  // oversamplign is activated
  ovsX > 1 ?
  (
    counter = 0;
    while (counter < ovsX-1)
    (
      orly1 = ups[counter];
      orly1.softclip();
      ups[counter] = orly1;
      
      orly2 = ups[counter+ovsX];
      orly2.softclip();
      ups[counter+ovsX] = orly2;
      
      counter += 1;
    );

    spl0 = dnFilterL.bwTick(spl0);
    spl1 = dnFilterR.bwTick(spl1);
    
    counter = 0;
    while (counter < ovsX-1)
    (
      ups[counter]      = dnFilterL.bwTick(ups[counter]);
      ups[counter+ovsX] = dnFilterR.bwTick(ups[counter+ovsX]);
      counter += 1;
    );
  );

// Hard limit in case input goes over 0dBFS:
spl0=max(min(spl0,1),-1);
spl1=max(min(spl1,1),-1);

  
// Bring down to ceiling volume:
spl0*=ceiling;
spl1*=ceiling;

// If DC blocking enabled

  slider3 != 0 ?
  (
    // Run the DC blocker on each sample
    spl0.dcBlocker();
    spl1.dcBlocker();
  );

// Hard limit in case input goes over 0dBFS:
spl0=max(min(spl0,ceiling),-ceiling);
spl1=max(min(spl1,ceiling),-ceiling);



@gfx 340 110

// 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);
);

function gfx_ui_layout_text(title, text) 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);
    ui_text(text);
  ui_pop();
);

function gfx_ui_layout_title(title) (
  gfx_ui_layout_text(title, "");
);

function gfx_ui_ovs_to_text(value) (
  (value < 3 ? (value < 2 ? (value === 0 ? "1x" : "2x") : "4x") : (value === 3 ? "8x" : "16x"));
);

control_start("main", "tron");

ui_screen() === "main" ? (
  ui_split_topratio(1/1); // single row
    ui_split_leftratio(6/6);
      // row 1, group 1
      ui_split_leftratio(1/6);
        gfx_ui_automate_slider(slider1, gfx_ui_layout_textnumber("Input", slider1, "%.1f"));
        gfx_ui_dial_rounded(slider1, -24, 24, 0, 0, 0.1);
      ui_split_next();
        gfx_ui_automate_slider(dBGain, gfx_ui_layout_textnumber("Drive", dBGain, "%.2f"));
        gfx_ui_dial_rounded(dBGain, 0, 76, 2, 0, 0.01);
      ui_split_next();
        gfx_ui_automate_slider(slider3, gfx_ui_layout_textnumber("Symmetry", slider3, "%.2f"));
        gfx_ui_dial_rounded(slider3, -0.5, 0.5, 0, 0, 0.01);
      ui_split_next();
        gfx_ui_automate_slider(slider4, gfx_ui_layout_textnumber("Hardclip", slider4, "%.1f"));
        gfx_ui_dial_rounded(slider4, 0, 48, 2, 0, 0.1);
      ui_split_next();
        gfx_ui_automate_slider(slider5, gfx_ui_layout_textnumber("Output", slider5, "%.1f"));
        gfx_ui_dial_rounded(slider5, -24, 0, -2, 0, 0.5);
      ui_split_next();
        gfx_ui_layout_title("Quality");
        gfx_ui_automate_slider(ovs, control_selector(ovs, gfx_ui_ovs_to_text(ovs), min(4, ovs + 1), max(0, ovs - 1)));
      ui_pop();
    ui_pop();
  ui_pop();
) : control_system();
// END OF AUTOGENERATED UI //

// Custom slider code
ui_interacted() ? needs_slider_update = 1;
__________________
JSFX set | Bandcamp/SoundCloud/Spotify
geraintluff is offline   Reply With Quote
Old 03-02-2021, 04:53 AM   #56
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default

Quote:
Originally Posted by Bobo007 View Post
my knobs are working but.. when i hit stop and then play again I have to touch one of the knobs to hear anything.
You need to call slider_update_function() from both @block (which you are doing), and from @slider.

Try adding:

Code:
@slider
slider_update_function();
Slider-update code seems to be a common problem, I'll have a think if there's anything to make it simpler. It might just need to be emphasised/explained more in the documentation...
__________________
JSFX set | Bandcamp/SoundCloud/Spotify
geraintluff is offline   Reply With Quote
Old 03-02-2021, 05:16 AM   #57
Phazma
Human being with feelings
 
Join Date: Jun 2019
Posts: 2,875
Default

Hey Geraint,

I meanwhile figured it out myself but still thanks a lot for chiming in and for your wonderful work!
There is only 1 issue remaining, maybe you can help me out with that as well?

Basically the dials all work correctly now, except when I drag a dial and start playback (via spacebar, if that matters) while still dragging it, the dial locks at the position it was when I started playback. I have to release the mouse and initiate the drag again to further tweak it. Can that be fixed?

Btw I tidied up the code a lot, so here is the updated version (if neccessary):

Code:
desc: DistoClip04

slider1:inputlevelslider=0<-24,24,0.5> -Input 
slider2:algorithmslider=4<0,4,{Soft Dist,Hard Dist,Soft Clip,Med Clip,Hard Clip}>
slider3:outputlevelslider=0<-24,24,0.5>-Output
slider4:driveslider=0<0,76,0.5> -Drive
slider5:algorithmslider=4<0,4,1>-Style
slider6:symmetryslider=0<-50,50,1>-Symmetry

in_pin:left input
in_pin:right input
out_pin:left output
out_pin:right output

import ui-lib.jsfx-inc



///////////////////////
///////////////////////
///////////////////////
//////////INIT/////////
///////////////////////
///////////////////////
///////////////////////


@init

//BUFFER FOR UI
freemem = 0; 
freemem = ui_setup(freemem); 

//SMOOTH SLIDERS  
last_drive=10^(driveslider/20);
last_inputlevel=10^(inputlevelslider/20);
last_symmetry=symmetryslider/100;
last_outputlevel=10^(outputlevelslider/20);

//DB TO GAIN
function dBToGain (decibels) (10.0 ^ (decibels / 20.0));


//ALGORITHMS//
//SOFT DISTORTION = ARCTAN   
function arctan(x) 
(
  2/3.14159265359*atan((3.14159265359/2)*x);
); 
//HARD DISTORTION = ?   
function hard(x) 
(
  x / (1.0 + abs(x))*1.5;
);  
//SOFT CLIPPING = TANH   
function tanh (number) local (xa, x2, x3, x4, x7, res)
(
  xa = abs(number);
  x2 = xa * xa;
  x3 = x2 * xa;
  x4 = x2 * x2;
  x7 = x4 * x3;
  res = (1.0 - 1.0 / (1.0 + xa + x2 + 0.58576695 * x3 + 0.55442112 * x4 + 0.057481508 * x7));
  sign(number) * res;
);
//MEDIUM CLIPPING = SIN
function sine(x) 
(
  (abs(x)>1.4142135623) ? sign(x)*0.9428090416 : (x-(x*x*x)/6);
);


//SCALED ALGORITHMIC FUNCTIONS//
//SOFT DISTORTION
function arctandist ()
(
  vCeiling = min(roof, 1.0);
  vUpscale = gain / vCeiling;
  this  = arctan(this * vUpscale);
  this *= vCeiling;
);
//HARD DISTORTION
function harddist ()
(
  vCeiling = min(roof, 1.0);
  vUpscale = gain / vCeiling;
  this  = hard(this * vUpscale);
  this *= vCeiling;
);
//SOFT CLIPPING
function tanhclip ()
(
  vCeiling = min(roof, 1.0);
  vUpscale = gain / vCeiling;
  this  = tanh(this * vUpscale);
  this *= vCeiling;
);
//MEDIUM CLIPPING
function sinclip ()
(
  vCeiling = min(roof, 1.0);
  vUpscale = gain / vCeiling;
  this  = sine(this *  vUpscale);
  this *= vCeiling*1.0606;
);


//DC BLOCKER   
function dcBlocker () instance (stateIn, stateOut)
(
  stateOut *= 0.99988487;
  stateOut += this - stateIn;
  stateIn = this;
  this = stateOut;
);

//UPDATE SLIDERS  
function slider_update_function() 
(
  inputlevel =10^(inputlevelslider/20);
  drive =10^(driveslider/20); 
  symmetry =symmetryslider/100;
  outputlevel =10^(outputlevelslider/20);
  roof = dBToGain(0); 
  gain = dBToGain(dBGain);
  //SMOOTH SLIDERS
  nextdrive = 10^(driveslider/20);
  nextinputlevel = 10^(inputlevelslider/20);
  nextsymmetry = symmetryslider/100;
  nextoutputlevel = 10^(outputlevelslider/20);
);

  
  
///////////////////////
///////////////////////
///////////////////////
/////////SLIDER////////
///////////////////////
///////////////////////
///////////////////////
  
  
  
@slider
  
slider_update_function();



///////////////////////
///////////////////////
///////////////////////
/////////BLOCK/////////
///////////////////////
///////////////////////
///////////////////////



@block

needs_slider_update ? slider_update_function();
//SMOOTH SLIDERS
d_drive = (nextdrive - last_drive)/samplesblock;
d_inputlevel = (nextinputlevel - last_inputlevel)/samplesblock;
d_symmetry = (nextsymmetry - last_symmetry)/samplesblock;
d_outputlevel = (nextoutputlevel - last_outputlevel)/samplesblock;



///////////////////////
///////////////////////
///////////////////////
/////////SAMPLE////////
///////////////////////
///////////////////////
///////////////////////



@sample

//ADJUST INPUT LEVEL
spl0 *= last_inputlevel;
spl1 *= last_inputlevel;
last_inputlevel +=d_inputlevel;

//ADJUST DISTORTION LEVEL  
spl0 *= last_drive;
spl1 *= last_drive;  
last_drive +=d_drive;
  
//ADJUST SYMMETRY
spl0 += last_symmetry;
spl1 += last_symmetry;
last_symmetry +=d_symmetry;
  
//CHOOSE DISTORTION STYLE
algorithmslider == 0 ?
(
  spl0.arctandist();
  spl1.arctandist();
);
algorithmslider == 1 ?
(
  spl0.harddist();
  spl1.harddist();
);
algorithmslider == 2 ?
(
  spl0.tanhclip();
  spl1.tanhclip();
);
algorithmslider == 3 ?
(
  spl0.sinclip();
  spl1.sinclip();
);
  
//HARD CLIP
spl0=max(min(spl0,1),-1);
spl1=max(min(spl1,1),-1);

//ADJUST OUTPUT LEVEL
spl0 *= last_outputlevel;
spl1 *= last_outputlevel;
last_outputlevel +=d_outputlevel;

//REMOVE DC OFFSET

symmetryslider != 0 ?
(
  spl0.dcBlocker();
  spl1.dcBlocker();
);

// FINAL CLIPPER
spl0=max(min(spl0,last_outputlevel),-last_outputlevel);
spl1=max(min(spl1,last_outputlevel),-last_outputlevel);



///////////////////////
///////////////////////
///////////////////////
//////////GFX//////////
///////////////////////
///////////////////////
///////////////////////



@gfx 10 300

// 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);
);

function gfx_ui_layout_text(title, text) 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);
    ui_text(text);
  ui_pop();
);

function gfx_ui_layout_title(title) (
  gfx_ui_layout_text(title, "");
);

function gfx_ui_algorithmslider_to_text(value) (
  (value < 3 ? (value < 2 ? (value === 0 ? "Soft Dist" : "Hard Dist") : "Soft Clip") : (value === 3 ? "Med Clip" : "Hard Clip"));
);

control_start("main", "tron");

ui_screen() === "main" ? (
  ui_split_topratio(1/2); // 2 rows
    ui_split_leftratio(3/3);
      // row 1, group 1
      ui_split_leftratio(1/3);
        gfx_ui_automate_slider(inputlevelslider, gfx_ui_layout_textnumber("Input", inputlevelslider, "%.1f dB"));
        gfx_ui_dial_rounded(inputlevelslider, -24, 24, 0, 0, 0.5);
      ui_split_next();
        gfx_ui_layout_title("");
        gfx_ui_automate_slider(algorithmslider, control_selector(algorithmslider, gfx_ui_algorithmslider_to_text(algorithmslider), min(4, algorithmslider + 1), max(0, algorithmslider - 1)));
      ui_split_next();
        gfx_ui_automate_slider(outputlevelslider, gfx_ui_layout_textnumber("Output", outputlevelslider, "%.1f dB"));
        gfx_ui_dial_rounded(outputlevelslider, -24, 24, 0, 0, 0.5);
      ui_pop();
    ui_pop();
  ui_split_next();
    ui_split_leftratio(3/3);
      // row 2, group 1
      control_group("DistoClip");
      ui_split_leftratio(1/3);
        gfx_ui_automate_slider(driveslider, gfx_ui_layout_textnumber("Drive", driveslider, "%.1f dB"));
        gfx_ui_dial_rounded(driveslider, 0, 76, 2, 0, 0.5);
      ui_split_next();
        gfx_ui_automate_slider(algorithmslider, gfx_ui_layout_textnumber("Style", algorithmslider, "%i"));
        gfx_ui_dial_rounded(algorithmslider, 0, 4, 0, 4, 1);
      ui_split_next();
        gfx_ui_automate_slider(symmetryslider, gfx_ui_layout_textnumber("Symmetry", symmetryslider, "%i %%"));
        gfx_ui_dial_rounded(symmetryslider, -50, 50, 0, 0, 1);
      ui_pop();
    ui_pop();
  ui_pop();
) : control_system();
// END OF AUTOGENERATED UI //
ui_interacted() ? needs_slider_update = 1;
And one last question: is it possible (with not too much work) to have a popup selector open a small window similar to how jsfx natively works? I found the clicking through options a bit cumbersome and by using columns it became distracting as a totally different page opens.
If that however is not possible or implies a lot of work it doesn't matter. As a workaround I have created a dial to switch between options and a selector that I use as a display of what is selected.
But a small popup window to directly choose an option instead of with the dial would be still handy if possible.
Phazma is offline   Reply With Quote
Old 03-02-2021, 08:23 AM   #58
JPH
Human being with feelings
 
JPH's Avatar
 
Join Date: Sep 2020
Location: In the alps
Posts: 20
Default

Quote:
Originally Posted by geraintluff View Post
Thank you, I really appreciate this.

I got a bit burned-out on open-source stuff a while ago and can't be here as much as I'd like, but seeing this made me feel a bit lighter.
I get it. You're welcome. I experimented enough with your library to answer basic questions, so I can chime in from time to time
Thanks for your work by the way!
JPH is offline   Reply With Quote
Old 03-02-2021, 11:33 AM   #59
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default

Quote:
Originally Posted by Phazma View Post
Basically the dials all work correctly now, except when I drag a dial and start playback (via spacebar, if that matters) while still dragging it, the dial locks at the position it was when I started playback. I have to release the mouse and initiate the drag again to further tweak it. Can that be fixed?
Hm - on my machine, Reaper just ignores the key-press and I can't even start playback like that.

I suspect that (on your machine) Reaper is sending the mouse-up signal when the keypress event happens. If you want to verify that, you can see what happens to "mouse_cap" in the debugger when you start playback, like this:



If it returns to 0, it means Reaper's lifting the mouse for us, and there's nothing we can do.

Quote:
Originally Posted by Phazma View Post
is it possible (with not too much work) to have a popup selector open a small window similar to how jsfx natively works?
Selectors which appear over other controls would be tricky - but a whole new screen (as a popup/dialog) is much easier. In fact, it already does this automatically - if there are six or more options, so you were just under the limit. ��

Put "// columns=1" before the slider and the generator will produce some code for a popup, which you can copy out and add to your own. In fact, try replacing your header with this and see what the generator does:

Code:
desc: DistoClip04

// ui:layout theme=tron
// format=%.1f dB
slider1:inputlevelslider=0<-24,24,0.5> -Input 
// columns=1
slider2:algorithmslider=4<0,4,{Soft Dist,Hard Dist,Soft Clip,Med Clip,Hard Clip}>
slider3:outputlevelslider=0<-24,24,0.5>-Output
// ui:row
// ui:group name=DistoClip
// format=%.1f dB
slider4:driveslider=0<0,76,0.5> -Drive
slider5:algorithmslider=4<0,4,1>-Style
// format=%i %%
slider6:symmetryslider=0<-50,50,1>-Symmetry
I didn't document it properly, so you couldn't have known how to use "// columns=N" - sorry about that.

Geraint

EDIT - I just added a "theme=tron" option to the generator, and the code above, just for kicks.
Attached Images
File Type: jpg screenshot.jpg (60.8 KB, 942 views)
__________________
JSFX set | Bandcamp/SoundCloud/Spotify

Last edited by geraintluff; 03-02-2021 at 11:54 AM.
geraintluff is offline   Reply With Quote
Old 03-02-2021, 12:12 PM   #60
Phazma
Human being with feelings
 
Join Date: Jun 2019
Posts: 2,875
Default

Quote:
Originally Posted by geraintluff View Post
Hm - on my machine, Reaper just ignores the key-press and I can't even start playback like that.

I suspect that (on your machine) Reaper is sending the mouse-up signal when the keypress event happens. If you want to verify that, you can see what happens to "mouse_cap" in the debugger when you start playback.

If it returns to 0, it means Reaper's lifting the mouse for us, and there's nothing we can do.
It shows 1 for as long as I keep the mouse pressed, doesn't matter if I press spacebar.

Quote:
Originally Posted by geraintluff View Post
Selectors which appear over other controls would be tricky - but a whole new screen (as a popup/dialog) is much easier. In fact, it already does this automatically - if there are six or more options, so you were just under the limit. ��

Put "// columns=1" before the slider and the generator will produce some code for a popup, which you can copy out and add to your own.

I didn't document it properly, so you couldn't have known how to use "// columns=N" - sorry about that.
Actually I had already tried columns=1 when I was writing the post but as said I am not really a fan of having another window open instead of seeing the options below (or on top of) the selector box or in a decoupled popup window that auto-closes when a selection is made. And I find the box that contains the parameter, as if it was the value display of a hardware unit, more stylish than a plain "edit" button. I suspect if I dive through your extended guide (which some day I surely will) I could find a way to do this or some equally nice alternative but for now the UI generator is sufficient and this is really no biggie, my workaround with the dial under the display is good enough.

Quote:
Originally Posted by geraintluff View Post
EDIT - I just added a "theme=tron" option to the generator, and the code above, just for kicks.
This is great! Was the only thing I needed to change each time within the code when I tried a new layout. This way the entire code is generated as desired from the start. I am sure others appreciate too.
Phazma is offline   Reply With Quote
Old 03-02-2021, 01:19 PM   #61
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default

Quote:
Originally Posted by Phazma View Post
I am not really a fan of having another window open instead of seeing the options below (or on top of) the selector box or in a decoupled popup window that auto-closes when a selection is made.
Ah right. Yeah, having multiple layers in a UI is difficult.

It's not (currently) supported in the generator, but how would you feel about replacing that control with the radio buttons which would otherwise be in the popup?



It does look a bit more cramped/busy, but it was just a thought. It sounds like you're happy with how it looks currently anyway, so I shouldn't try to mess with success.
Attached Images
File Type: png Screenshot 2021-03-02 at 20.15.23.png (35.5 KB, 897 views)
__________________
JSFX set | Bandcamp/SoundCloud/Spotify
geraintluff is offline   Reply With Quote
Old 03-02-2021, 02:14 PM   #62
Phazma
Human being with feelings
 
Join Date: Jun 2019
Posts: 2,875
Default

Hmm.. this is a hard choice. I feel like my original version indeed looks better but this would be more user-friendly. However I don't know if I maybe add further options in future and it might become problematic.

I am thinking of a fitting position for such a radio menu.
First off all the radio buttons seem off centered. Could they be moved a bit more to the right?
Perhaps the best position would be where now the "Style" dial is and move that one up. I might also edit the code to show the display rather than this dial which only makes sense if there is no single-click way to select an option.
Another idea would be to put it where currently the group name is shown. And perhaps apply that group name to the first row instead.

But I would have to try a few layouts to see what I end up liking the most. Anyway if you can provide this way of displaying radio buttons directly on the UI as an option for me to try out it would be appreciated. Providing this doesn't take you all too much time/effort to do, given that I might end up not using it (but perhaps other users also find it useful).
Phazma is offline   Reply With Quote
Old 03-13-2021, 10:45 AM   #63
Phazma
Human being with feelings
 
Join Date: Jun 2019
Posts: 2,875
Default

Quote:
Originally Posted by Phazma View Post
Quote:
Originally Posted by geraintluff View Post
Hm - on my machine, Reaper just ignores the key-press and I can't even start playback like that.

I suspect that (on your machine) Reaper is sending the mouse-up signal when the keypress event happens. If you want to verify that, you can see what happens to "mouse_cap" in the debugger when you start playback.

If it returns to 0, it means Reaper's lifting the mouse for us, and there's nothing we can do.
It shows 1 for as long as I keep the mouse pressed, doesn't matter if I press spacebar.
So nothing that can be done about this?
I love the sleek look of your UI library but this interferes too much with my way of working, up to the point that I will probably have to consider searching for other solutions if I can not get it to work.
It does not only affect my JSFX but any other that is made with your library.

Btw it only happens when the spacebar is assigned to Transport: Play/stop (in all scopes).
If the spacebar is unassigned it does not influence turning the dial and also if any other key is assigned to Play/stop it doesn't happen either. It is noteworthy that no other assigned key shortcut other than the spacebar is able to trigger playback when the FX is in focus.

So maybe it is something on REAPER level that gives the space bar as playback controller a special priority that doesn't work with your UI library for some reason. However it is neither limited to your library, nor affects all GUIs. Some jsfx with custom GUIs allow start/stop of playback during tweaking while others don't.

If it is not something that can be fixed on your or my side, maybe you have some idea where I could start to troubleshoot? If the actual culprit can be found and is caused by REAPER itself, maybe it is an easy fix for Cockos if communicated clearly to them..
Phazma is offline   Reply With Quote
Old 03-20-2021, 02:05 AM   #64
geraintluff
Human being with feelings
 
geraintluff's Avatar
 
Join Date: Nov 2009
Location: mostly inside my own head
Posts: 346
Default

Quote:
Originally Posted by Phazma View Post
Some jsfx with custom GUIs allow start/stop of playback during tweaking while others don't.
Could you give some examples? If I can see a JSFX where it does work, I can try and see what they're doing differently.
__________________
JSFX set | Bandcamp/SoundCloud/Spotify
geraintluff is offline   Reply With Quote
Old 03-20-2021, 05:03 AM   #65
Phazma
Human being with feelings
 
Join Date: Jun 2019
Posts: 2,875
Default

Quote:
Originally Posted by geraintluff View Post
Could you give some examples? If I can see a JSFX where it does work, I can try and see what they're doing differently.
2 effects that work are for example Saike FM Filter 2 or ReEQ.

Here are the links:

ReEQ: https://forum.cockos.com/attachment....2&d=1593872276
FM Filter 2: https://github.com/JoepVanlier/JSFX/...FMFilter2.jsfx

If you need more examples let me know, I will see if I can find something.

EDIT: edited to provide direct links.

Last edited by Phazma; 03-20-2021 at 05:31 AM.
Phazma is offline   Reply With Quote
Old 05-06-2021, 06:36 PM   #66
MusoBob
Human being with feelings
 
MusoBob's Avatar
 
Join Date: Sep 2014
Posts: 2,643
Default

Will this still work with the ReaPlugs reajs.dll in other audio apps ?
I want to transfer the ReaTrak Instant Traks and Chord sheet over to JSFX so it will run in any DAW (Win DAW as ReaPlugs are only Win else it would be used in Mac Reaper).
https://forums.cockos.com/showthread.php?t=253184

__________________
ReaTrakStudio Chord Track for Reaper forum
www.reatrak.com
STASH Downloads https://stash.reaper.fm/u/ReaTrak
MusoBob is offline   Reply With Quote
Old 05-07-2021, 02:07 PM   #67
MusoBob
Human being with feelings
 
MusoBob's Avatar
 
Join Date: Sep 2014
Posts: 2,643
Default

Yes it all seems to work independent of Reaper.

You need to put a reajs.ini in the ..VSTplugins64\ReaPlugs
so it will use the local folder and not look for REAPER resource folder and srcipt in ..VSTplugins64\ReaPlugs\JS\Effects:
Quote:
[ReaJS]
rootpath= /JS
; can be absolute path, relative path (relative to reajs.dll), or
; begin with a \ to be relative to current drive -- note this
; isnt the path to effects directly, but a dir that should have
; data\ and effects\ subdirectories... the default is simply "JS"
; (if reaper isnt installed)

appendname=/blah ; makes effect called "ReaJS/blah"

; vstid=602358 ; default is CCONST('rejs') but if your host demands
; it you can pick your own (good luck not colliding)

defaulteffect=someeffectname ; loads this effect (without path!) by default

; defaulteffect=!someeffectname ; loads this effect, and doesnt allow user to
; switch to other effects!

vstcat=1 ; 1 = effect, 2= "synth", others, see vst sdk

preventedit=0 ; set to 1 to disable editing ui

inputs=2 ; number of audio inputs (0-64)

outputs=2 ; number of audio outputs (0-64)

midiflags=3 ; 0 = no midi send/recv, 1=recv midi from host,
; 2=send midi to host, 3=both
__________________
ReaTrakStudio Chord Track for Reaper forum
www.reatrak.com
STASH Downloads https://stash.reaper.fm/u/ReaTrak
MusoBob is offline   Reply With Quote
Old 02-25-2022, 06:27 AM   #68
mawi
Human being with feelings
 
Join Date: Apr 2011
Location: Germany
Posts: 1,186
Default

Hello, thank you very much for your UI-lib! I don't have any programming knowledge and I'm just trying to get into it. I have developed a JSFX and now I want to develop a GUI for it using your UI-lib. I have used your UI generator and have now run into the problem that the pots move the JSFX sliders but the slider values do not change. Only when I move the JSFX slider myself the value is taken from the slider. You write about this:

Quote:
Slider changes:

Because the sliders are no longer being handled by the built-in sliders, we might miss out on @slider updates when the user changes them. If this is a problem, you can avoid this by:

Define a flag, e.g. needs_slider_update.
Move any existing @slider code to a function (in @init) with no arguments, which resets this flag:

function slider_update_function() (
needs_slider_update = 0;

// previous @slider code
);

In @gfx (right at the end), set this flag if the user has interacted at all:

ui_interacted() ? needs_slider_update = 1;

Check this flag in @block, and call your function if needed:

@block
needs_slider_update ? slider_update_function();

Note: we shouldn't call slider_update_function() directly from @gfx, because @gfx runs in a different thread to every other block.

The @slider block will still be called for automation - this is only for when the user changes things using this interface.
Unfortunately I don't understand how to implement this, because I'm not familiar with functions yet. Can you please show me a simple example with maybe 5 sliders? Just so that I understand this better as a beginner.

Thanks a lot!
mawi is offline   Reply With Quote
Old 02-26-2022, 05:20 AM   #69
mawi
Human being with feelings
 
Join Date: Apr 2011
Location: Germany
Posts: 1,186
Default

I have now succeeded in implementing your instruction, it now works. now the fun can go on.
mawi is offline   Reply With Quote
Old 02-28-2022, 11:23 AM   #70
mawi
Human being with feelings
 
Join Date: Apr 2011
Location: Germany
Posts: 1,186
Default

Hello, I have more questions:

1. When I load my JSFX with the new GUI, I have no signal anymore. Only when I move a slider or dials in the GUI I get a signal. What do I have to do so that I have a signal immediately after loading the JSFX?

2. In the output group I have a large vertical slider, unfortunately I don't get the label "Volume" placed above the slider and the "Value in db" placed below the slider. Is there a special function for this?

Code:
...
  ui_split_next(); // column 2 Output
    ui_split_topratio(1/1);
      // row 1, group 1
      control_group("Output");
      ui_split_topratio(.9);
        ui_push_height(5000);
          gfx_ui_automate_slider(OUT_g, gfx_ui_layout_textnumber("Volume", OUT_g, "%.2f dB"));
          /*control_readout("Volume");*/
        ui_pop();
        ui_push_heightratio(.8);
          OUT_g=control_slider_y(OUT_g, -12, 12, 0, 0);
        ui_pop();
      ui_split_next();
        gfx_ui_layout_text("Phase", PHS_inv ? "Inverted" : "Normal");
        ui_pad(-1, 0);
        gfx_ui_automate_slider(PHS_inv, control_switch(PHS_inv));
      ui_pop();
    ui_pop();
  ui_pop();
) : (
...


Here is my feedback on the GUI generator and documentation, what I have noticed so far. The GUI generator shows an error message if you use a "+" sign with the slider values. E.g.: "slider1:OUT_g=0<-12,+12,0.01>-output (dB)".

In the documentation for sliders and dials, "control_vslider" did not work for me. "control_slider_y" worked for me.

The GUI generator is really helpful to get first results quickly. Unfortunately I think there are still some functions missing, like creating a right vertical column with groups or creating horizontal and vertical sliders (like the output column in my JSFX). Changing the code afterwards is difficult because a different code is used. The GUI is unfortunately very CPU intensive.

Will there be any updates with improvements coming?

Thanks for your great GUI lib, generator and documentation!
mawi is offline   Reply With Quote
Old 03-02-2022, 01:42 PM   #71
mawi
Human being with feelings
 
Join Date: Apr 2011
Location: Germany
Posts: 1,186
Default

Hello, I have been able to solve my questions. Here I have published my first JSFX:
https://forum.cockos.com/showthread.php?t=263782
mawi is offline   Reply With Quote
Old 03-03-2022, 03:45 AM   #72
X-Raym
Human being with feelings
 
X-Raym's Avatar
 
Join Date: Apr 2013
Location: France
Posts: 9,900
Default

@Mawi
Nice use of geraintluff framework!
I hope he will see that 🙂
X-Raym is offline   Reply With Quote
Old 03-03-2022, 02:24 PM   #73
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 3,984
Default

With no doubt, for now greatest library for JSFX I`ve ever saw.

Now, here is a concept I imagine:
1) have a reascript to set up common view of knobs (png or whatever), background etc
2) just like https://geraintluff.github.io/jsfx-u...nerate-ui.html generate GUI for ALL JSFX inside /effects folder except JSFX which already have gfx. stuff, using setting set up previously with step1.

...so all JSFX under the same skin.
Is there somewhere code-generator code so I can just port it to lua script?
mpl is offline   Reply With Quote
Old 03-08-2022, 08:29 AM   #74
Batman1965
Human being with feelings
 
Batman1965's Avatar
 
Join Date: Mar 2019
Location: Halifax, Canada
Posts: 4
Default

Firstly, thank you for this library!

I am very new to JSFX coding and have two questions for the very basic volume trim plugin below.

How do you call the grey theme?
How do you reset a knob to zero with a double click?

Code:
desc:Simple Volume Adjust with GUI
desc:Volume Trim

slider1:trim_db=0 < -60 , 12 , 1 >-Trim [dB]

import ui-lib.jsfx-inc

@init

freemem = ui_setup(0);

function labels(label, value, number_format) local(h) (
  h = max((ui_height() - 60)/2, ui_height()*0.2);
  ui_split_top(h);
    ui_align(0.5, 1);
    ui_text(label);
  ui_pop();
 
  ui_split_bottom(h);
    ui_align(0.5, 0);
    number_format >= 0 ? (
      ui_textnumber(value*1.0000001, number_format);
    );
  ui_pop();
  h;
);

@gfx
 ui_start();

 control_navbar("Trim");
  ui_split_leftratio(1/1);
  control_group("Trim");
  ui_push_width(50);
  labels("Trim [dB]", trim_db, "%.1f db");
  ui_automate (trim_db, control_dial(trim_db, -18, 18, 1));
 ui_pop();
 

@slider
trim = 10 ^ ( trim_db / 20 );

@block
trim = 10 ^ ( trim_db / 20 );

@sample
spl0 = spl0 * trim;
spl1 = spl1 * trim;
Batman1965 is offline   Reply With Quote
Old 03-09-2022, 01:55 PM   #75
Batman1965
Human being with feelings
 
Batman1965's Avatar
 
Join Date: Mar 2019
Location: Halifax, Canada
Posts: 4
Default

I've solved the two questions I asked in my previous post by using the online gfx generator.

Does anyone know if you can control two sliders with a single knob or slider?

I built a GUI for the Event Horizon Clipper/Limiter (Stillwell) and would like to add the feature from the Waves L1 limiter where the Threshold and Out Ceiling can be moved simultaneously with "Link"

My preference would be either a separate slider that controls both Threshold and Out Ceiling or a toggle that allows Threshold to also control Out Ceiling.
Batman1965 is offline   Reply With Quote
Old 03-10-2022, 05:00 AM   #76
mawi
Human being with feelings
 
Join Date: Apr 2011
Location: Germany
Posts: 1,186
Default

a peak meter, rms meter and gain reduction meter in the UI generator would be great. For my compressor I need a gain reduction meter, just don't know how to do that. seems to be a bigger task as a newbie.
mawi is offline   Reply With Quote
Old 03-10-2022, 07:47 AM   #77
witti
Human being with feelings
 
witti's Avatar
 
Join Date: May 2012
Posts: 1,216
Default

Hi mawi !

Here's a quick and dirty implemention of a Gain Reduction Meter.
I've also changed the behavior of the input gain knob.
Now it only affects the signal which is fed into the compressor...
Seems more logical to me... Maybe you want to have a look ?

Greetings
witti

(I can remove the attached file later if you want...)
witti is offline   Reply With Quote
Old 03-10-2022, 03:30 PM   #78
mawi
Human being with feelings
 
Join Date: Apr 2011
Location: Germany
Posts: 1,186
Default

Quote:
Originally Posted by witti View Post
Hi mawi !

Here's a quick and dirty implemention of a Gain Reduction Meter.
I've also changed the behavior of the input gain knob.
Now it only affects the signal which is fed into the compressor...
Seems more logical to me... Maybe you want to have a look ?

Greetings
witti

(I can remove the attached file later if you want...)
Hello witti, thanks for your GR meter, that helps me a lot!

The Gain Input should drive into the threshold with my compressor, like for example with the 1176, only that with my compressor the threshold is adjustable. I.e. the compressor should be able to be roughly adjusted mainly with the Input Gain and Output Volume. You have now modified the compressor so that the input gain moves the threshold and the signal gets quieter as you move the input gain into the threshold. That's not really what I had in mind. I've also been racking my brains a lot about the sidechain implementation, mainly because the input gain makes the signal much louder when there is no sidechain signal and sidechain mode is enabled or delta mode is enabled. I have not found a solution yet.

Thanks for your support, your JSFX are a great inspiration for me, I learned a lot from you, keep up the good work.
mawi is offline   Reply With Quote
Old 03-13-2022, 10:35 AM   #79
toddkc
Human being with feelings
 
Join Date: Dec 2011
Posts: 11
Default

Hey all. I made a wonderful MIDI plugin with this that looks very clean and does exactly what I need. The thing that I can't figure out is how to incorporate preset saving/loading. When I first add the plugin to a track all the knobs/switches are at 0 as I would want/expect, but then when I try to change them and save a preset and then reload the preset nothing happens.

I looked through some of the other MIDI plugins that don't use this library and they all load presets fine with no preset-specific code, so clearly I'm missing something that needs to be done here. I looked through the repo and couldn't find any doc or tutorial either. If anyone could point me in a direction or ELI5 how to do this I'd appreciate it!

edit - figured it out

Last edited by toddkc; 03-14-2022 at 08:09 AM.
toddkc is offline   Reply With Quote
Old 07-31-2022, 10:29 AM   #80
Rockum
Human being with feelings
 
Join Date: Apr 2009
Location: Nashville
Posts: 186
Default

I am having problems with text-entry when using the library. This occurs even in the interactive API documentation script.

At first I could not modify any of the text input fields. Deselecting "Send all keyboard input to plugin" in Reaper allowed me to to enter some text. However, generally when I enter a text field there is flickering on a single character. I can sometimes replace or add a character or two, but then editing stops working. Sometimes I can make no changes.

For some reason, the API scripts has one section that always allows flawless input -

The Basics: Setup, Screens, and Errors: The "control prompt" screen: Open text prompt:

Here there is no flicker and I can delete, modify, and add input text correctly.
Rockum is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 09:07 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.