JSFX Dojo

I got slider1 showing a UI menu with the different styles to pick
Not actually doing anything yet
But I’ll get there

Good stuff on the Multifreaq tuneup

That’s half the battle, storing/setting the styles can be done in a bunch of different ways. If you want you can post it and I’ll add the way I’d do it.

If I get stuck I will, cheers
I might learn more doing some trial and error here
and comparing with the reference and other examples
Prob have more time in the weekend to finish it

This is the working slider for changing the font:

slider1: 1<0,4,1{White,Grey,Orange,Red,Black}> UI Style

I’ll work on the if/else tomorrow

Good plan, the syntax is a bit minimal so it does take a bit of getting used to that as well. Feel free to give me a shout and remember your DRY (Do not Repeat Yourself) principle where if you find yourself writing the exact same code more than once (or twice(ish)), stick the logic in a function or the arithmetic in a variable and give it a name your future self will understand!!! :smiley:

@Snookoda
Ready for a general critique of my code and formatting
So I have stage one working (mostly) now,
adjustable font colour, background colour and font size, via visible sliders

Ready for hiding the sliders and using GUI buttons instead,
after an example nudge regarding the buttons :smiley:

TrackNotes_v1.jsfx (4.9 KB)

I still have a bug to solve in the background colour
Orange and Red appear as Aqua and Blue

You’d think I’d just put the wrong hex colour there right?
But they look legit,

0xFFA500 for Orange
0xFF0000 for Red

So, not sure what’s up there yet


Considering adding an individual slider to toggle the bold option, maybe
I think it looks better on all the time though

Current limitations are can’t cut/paste, and the bold etc options are global
Still, pretty useful for adding mixing notes on the actual track panel
I use it anyhoot :smiley:

Sweet, I’ll answer later, bit of a rush for next few hours.

Cheers Snooks, no rush here at all

Yes, that’s looking good. For the wrong colour thing, It looks like Cockos have made the low bits for that variable red, so it’s the other way around: 0xBBGGRR.

Couple of minor niggles:

Indentation not perfect

  fontcolor == 0 ? (
    gfx_set(1, 1, 1);                   //! White
    ) : (
       fontcolor == 1 ? (
        gfx_set(0.7, 0.7, 0.7);         //! Grey
       ) : (
       fontcolor == 2 ? (
        gfx_set(1.0, 0.5, 0.0);         //! Orange
       ) : (
       fontcolor == 3 ? (
        gfx_set(1.0, 0.0, 0.0);         //! Red
       ) : (
       fontcolor == 4 ? (
        gfx_set(0, 0, 0);               //! Black
       );););
    );
);

With cascading conditionals it looks nicer like this, and by now you’ve probably noticed that you start on column 3 but the last paren is on column 1 and that there’s also a mix of 3, 2 and 1 spaces indentation here. I sometimes notice these things around 5 seconds after posting something publicly. :slight_smile:

Sections
You have @slider first, which is fine but it’s usually @init. Again, no biggie and doesn’t matter for a minimal slider section, but @init does/should run first in a script and it would be a bit strange seeing a large slider section followed by crucial variable init next.

Also you have slipped:

gfx_ext_retina = ext_noinit = 1;

… into @serialize. The gfx_ stuff is usually called in @gfx but I don’t know if the variables are that important, and it’s working at the mo anyway but neither of those should ideally be in @serialize.

’Magic’ numbers
In much the same way as seeing slider1slider25 variables used in a script grinds my caretaking goat, so does style == 1 etc. For a sprinkle of numbers/sliders it doesn’t matter (much) but it’s a source of bugs so it’s good practice to try and give stuff names. Like GREY = 1; in @init (or kGrey/kgrey if you like less shouting, k being a standard prefix for a kconstant) and then use the variable name.

Cheers Snooks! Working now…is that a known bug they never fixed?

I copied the example you gave me earlier which does the same thing

@gfx 400 200
  style == 0 ? (
    gfx_clear = 0*141414; // better dealing in hex with one number colours stuff since each pair of `ff`s is r, g, b
    gfx_set(0, 0, 0, 0);
  ) : ( // <-- remember this is how elses are done
    style == 1 ? (
      // change colours
    ) : (
      style == 2 ? (
        //change colours
      );
   );
);

I did try cascading conditionals but I thought it looked more readable with the 2nd one on all lining up
My vs code extension was leading the indenting, and I followed
I indented the first closing ‘if’ paren accidentally for some reason, it should have lined up with fontcolor
I was cutting and pasting code around a lot so I might have messed it up :smiley:

Is there a style guide for EEL2?
Maybe my extension has a formatting feature
If you could take that fontcolor section above and post it here formatted exactly how you think it should look,
that would help

I was using zenoMOD VU Meter as a template,
and he has the slider section first before @init
I have only done a handful of LOSER’s jsfx previously,
and they all started with the slider section.
So, that’s a no-no?
I’ll move that @init line before the sliders then

Ahh, that’s meant to be 2 lines lower, after

@gfx 400 200

I’ll move that
What exactly does that line do anyway?

I’ll have a think about how to place that
Maybe an example?

Thanks heaps Snooks

He he, I must have typed that right into the forum, I’ll take my criticism on board! :slight_smile:

Here’s some button code with lots of comments! It’s a bit of a PITA with no early return statements etc in EEL2 and there are probably better ways of doing this, but I’ve handled mouse hover, mouse down, mouse click and mouse exit from a control before click. It was fun doing it, it’s been a while! :smiley:

desc:sandpit


 /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
@init
  // konstants, tidied away in our 'gui' "namespace pseudo-object". I find it
  // handy to whack things into namespace type things even just for autocomplete
  // goodness, which also avoids typo madness and other errors (magic number 
  // troubles, for example).
  gui.kNoMouse = 0.65;   // weird numbers because we reuse these konstants as 
  gui.kMouseDown = 0.3;  // alpha values for our momentary button. They
  gui.kMouseHover = 0.8; // only have to be different from each other.
  
  gui.kLeftButton = 1; // store the 'mouse_cap' state for left mouse button
                       // in a fairly sensibly named variable.
  
  
  // define our buttons here. what we declare here is entirely
  // dependent on how we implement our 'drawXXX' function for
  // the button.
  button.r = 0.8; button.g = 0.62; button.b = 0.1;
  button.label = "Normal";
  
  toggle.label = "Toggle";
  
  // depending on what we're doing, we could just do everything in the drawXXX
  // call and forget about doing anything here.


 /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
@gfx 399 399
  gfx_setfont(1, "Verdana", 15, 'b'); // because we're worth it
  
  /*
    the following functions assume that we are using "namespace pseudo-objects". See:
        https://www.reaper.fm/sdk/js/userfunc.php#js_userfunc
        
    We can use every variable as an object AS WELL AS a variable:
        button = 3;
        button.x = 20;
        // button == 3 is true
        // button.x == 20 is true
     
     We can't pass the variable/object to functions as arguments, a function parameter 
     receives the actual variable value. But we can pass 'object.whatever' etc by value
     
     The interesting part is when we call:
         button.myFunction()
         
     Now, in 'myFunction', the 'this' variable then gives us all of the "subvariables"
     we've attached to 'button'. So we'll soon see that the following functions are
     written as methods for the pseudo object(s) we create. In this case a button.
  */
  
  
  // standard function to check whether x and y fall within the rectangle defined in 
  // our object via x, y, w(idth) and h(eight) member variables.
  //   e.g.:
  //        control.x = 100; control.y = 50; control.w = 100; control.h = 50;
  //        control.isInBounds(120, 40) is true
  //
  function isInBounds(x, y)
  (
    x > this.x && x < this.x + this.w && y > this.y && y < this.y + this.h; 
  );
  
  // we detect hover state, depressed state and clicked state here. The (long) list
  // of globals is actually pretty handy. We are telling the compiler that these
  // are the only globals we will be touching, so we'd get a nice error with a typo 
  // or when we are trying to access another global variable not in the list.
  //
  // It would be nice if we could pass just the whole 'gui' thing, maybe we can
  // and I don't know about it.
  function isClicked()
    local(mouse_in_bounds mouse_down_was_in_bounds)
    globals(gui.mouse_up gui.mouse_down mouse_x mouse_y gui.kNoMouse
      gui.mouse_down.x gui.mouse_down.y gui.kMouseDown gui.kMouseHover)
  (
    // we need these values more than once, plus it's always good to
    // name things
    mouse_in_bounds = this.isInBounds(mouse_x, mouse_y);
    mouse_down_was_in_bounds = this.isInBounds(gui.mouse_down.x, gui.mouse_down.y);
    
    // logic for an actual click on our control
    gui.mouse_up ? (
      mouse_in_bounds && mouse_down_was_in_bounds ? (
        this.state = gui.kNoMouse;
        1;
      );
    ) : (
      gui.mouse_down ? (
        mouse_down_was_in_bounds ? (
          mouse_in_bounds ? (
            this.state = gui.kMouseDown;
          ) : (
            // here the cursor was clicked on our control, but has wandered
            // off, so this should cancel any click if it drifts back into
            // our control and the left button is released
            this.state = gui.kNoMouse;
            // make any "was in bounds" checks fail
            gui.mouse_down.x = gui.mouse_down.y = -1;
          );
          
          // remember that EEL2 functions return the value of the last expression
          // that was evaluated. Here we are being explicit in returning a zerp 
          // value in case one of the previous statements here evaluated to
          // non-zero, which would be 'true'.
          //
          // It's not necessary if the last statement was a failed conditional like:
          //       1 == 2 ? ( return_value; ); // 0 returned on fail
          // ... but if we have something like:
          //       important_thing = 1;
          // ... as the last statement then we have returned 'true'.
          //
          // Here, the last expressions are both potentially (and actually) non-zero, 
          // so we return zero.
          0;
        )
      ) : (
        // cursor is just hovering with no relevant mouse button action
        mouse_in_bounds ? (
          this.state = gui.kMouseHover;
        ) : (
          this.state = gui.kNoMouse;
        );
        0;
      )
    )
  );
  
  // this should be called near the beginning of our gui scripting since it updates
  // our internal mouse state and is needed by isClicked
  function processMouse(x, y)
    globals(mouse_cap gui.mouse_cap_prev gui.mouse_down gui.mouse_down.x gui.mouse_down.y
      gui.mouse_up gui.mouse_up.x gui.mouse_up.y gui.kLeftButton)
  (
    // mouse_cap is a number with different bits set for different mouse button/wheel
    // states. We're just using the left button here.
    mouse_cap == gui.kLeftButton && gui.mouse_cap_prev == 0 ? ( 
      gui.mouse_down.x = x; 
      gui.mouse_down.y = y;
      gui.mouse_down = 1;
      gui.mouse_up = 0;
    ) : (
      mouse_cap == 0 && gui.mouse_cap_prev == gui.kLeftButton ? (
        gui.mouse_up.x = x;
        gui.mouse_up.y = y;
        gui.mouse_down = 0;
        gui.mouse_up = 1;
      );
    );
  );
  
  // call at end of script in case there are any queries of mouse
  // state up until then
  function resetMouse()
    globals(gui.mouse_up mouse_cap gui.mouse_cap_prev)
  (
    // mouse_up is a temporary state that does not persist beyond handling
    // it in this frame
    gui.mouse_up = 0;
    // updating so that in the next frame we can query changes to the state
    // of the (left) mouse button(s)
    gui.mouse_cap_prev = mouse_cap;
  );
  
  function drawCenteredLabel()
    local(w h)
    globals(gfx_x gfx_y)
  (
    // center our text by measuring the label and positioning
    // relative to the rect position
    gfx_measurestr(this.label, w, h);
    gfx_x = this.x + this.w / 2 - w / 2; 
    gfx_y = this.y + this.h / 2 - h / 2;
    gfx_drawstr(this.label);
  );
  
  // now we only need to implement a draw function for our button. Mouse
  // logic is handled so it's just drawing here, depending on state.
  function drawMomentaryButton(x, y, w, h)
    local(red)
    globals(gfx_x gfx_y gui.kMouseHover)
  (
    this.x = x; this.y = y; this.w = w; this.h = h;
    this.state == gui.kMouseHover ? red = 1 : red = this.red;
    gfx_set(red, this.g, this.b, this.state);
    gfx_roundrect(this.x, this.y, this.w, this.h, 15, 1);
    this.drawCenteredLabel();
  );
  
  // we can implement different buttony things by sticking on extra member variables
  // and doing stuff depending on what they are. 
  // 
  // In this case we add an 'is_on' member of our pseudo-object so it can have a fully
  // on or off state to draw and to query.
  function drawToggleButton(x, y, w, h)
    local(blue)
  (
    this.x = x; this.y = y; this.w = w; this.h = h;
    blue = this.state == gui.kMouseHover ? 0.8 : 0.0;
    this.is_on ? gfx_set(0, .8, blue, 1) : gfx_set(.7, 0, blue, 1);
    gfx_rect(this.x, this.y, this.w, this.h, 1);
    gfx_set(0, 0, 0, 1);
    this.drawCenteredLabel();
    1;
 );
  
  
 /*-- -- -- -- -- -- -- -- -- -- -- -- -- --*/ 
  
  // note that we didn't init our mouse variables to zero. we are taking advantage 
  // of EEL2 initialising everything to zero and praying a little too.
  
  processMouse(mouse_x, mouse_y); // janitorial essential, before we do stuff
  
  
  
  button.drawMomentaryButton(100, 100, 100, 40); 
  button.isClicked() ? (
    click_count += 1;
  );
  
  gfx_x = 100; gfx_y = 200;
  gfx_set(.8, .8, .8);
  gfx_printf("Clicks: %d", click_count);
  
  
  toggle.drawToggleButton(250, 100, 100, 40);
  // if we don't want to bother our CPUs with constant calls here to handle
  // mouse hovering, we can choose to only check for mouse button changes
  // and then only check for clicks once in a blue moon.
  // 
  // Delete/uncomment this check to get mouse hovering back for the control
  gui.mouse_cap_prev != mouse_cap ? (
    toggle.isClicked() ? (
      toggle.is_on = !toggle.is_on;
      1;
    );
  );
  
  gfx_x = 250; gfx_y = 200;
  gfx_set(.8, .8, .8);
  gfx_printf("Toggle button is: %s", (toggle.is_on ? "On" : "Off"));
  
  
 
 
  resetMouse(); // caretaking necessity, after we do stuff
  

There aren’t really any no-nos, it’s just more logical putting them in order of execution to me and I’ve never seen them out of order back in the day IIRC. EEL2 is flexible though in that we don’t need to forward declare variables or anything, but it seems untidy to me to put sections out of order.

I don’t know if you know, but the JSFX bible is here: REAPER | JSFX Programming Reference - Special Variables

I think I got carried away a bit with the button example. A typical button is surprisingly fully featured though. An unnatural button that instantly reacts to a mouse down and doesn’t do hover or anything would be simpler.

gfx_ext_retina = ext_noinit = 1;

I hadn’t seen that exact page Snooks, cheers for that, very helpful

ext_noinit
Context: @init only
Set this variable to 1.0 in your @init section if you do not wish for @init to be called (and variables/RAM to be possibly cleared) on every transport start.


So @init doesn’t work before the slider section, I put it back after it again
I get a syntax error
Has that behaviour changed on you, or is something else required?

init before sliders syntax error


EDIT:

I checked some of Tale’s JSFX examples
He has sliders, then imports, then @init
eg

desc:Mono/poly synth

// Copyright (C) 2015-2017 Theo Niessink <theo@taletn.com>

// This work is free. You can redistribute it and/or modify it under the

// terms of the Do What The Fuck You Want To Public License, Version 2,

// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.

slider1:1<0,1,1{Mono,Poly}>Mode

import Tale/array.jsfx-inc

import Tale/midi_queue.jsfx-inc

import Tale/poly_blep.jsfx-inc

@init

voice.array_init(0, 128, 3);

@slider

mode = slider1 >= 0.5;

@sample

while(midi.midiq_recv()) (

  midi.msg1 &= 0xF0;

Same with LOSER’s jsfx
Sliders, then in/out pins, then @init

So I’m posting the background code here twice Snooks
Can you do me a huge favour and format the 2nd one exactly how you like it?
Just so I’m not missing or assuming anything
Cheers


Bevo:


@gfx 400 200
  gfx_ext_retina = ext_noinit = 1;

//---------- SET BACKGROUND via backcolor UI (slider2)


  backcolor == 0 ? (
    gfx_clear = 0x000000;               //! Black 
  ) : (
       backcolor == 1 ? (
        gfx_clear = 0x222222;           //! Grey
       ) : (
       backcolor == 2 ? (
        gfx_clear = 0x00A5FF;           //! Orange
       ) : (
       backcolor == 3 ? (
        gfx_clear = 0x0000FF;           //! Red
       ) : (
       backcolor == 4 ? (
        gfx_clear = 0xFFFFFF;           //! White
       );););
    );
);


Snooks:



@gfx 400 200
  gfx_ext_retina = ext_noinit = 1;

//---------- SET BACKGROUND via backcolor UI (slider2)


  backcolor == 0 ? (
    gfx_clear = 0x000000;               //! Black 
  ) : (
       backcolor == 1 ? (
        gfx_clear = 0x222222;           //! Grey
       ) : (
       backcolor == 2 ? (
        gfx_clear = 0x00A5FF;           //! Orange
       ) : (
       backcolor == 3 ? (
        gfx_clear = 0x0000FF;           //! Red
       ) : (
       backcolor == 4 ? (
        gfx_clear = 0xFFFFFF;           //! White
       );););
    );
);

There’s the slider part where the sliders are defined. That needs to go first. Then there’s the @slider section of code, which is what I mean. Tale has @init then @slider in the order I mean.

For the indentation, it just needs to be consistent so I’ll put that in an editor to make sure it is:

  backcolor == 0 ? (
    gfx_clear = 0x000000;            //! Black 
  ) : (
    backcolor == 1 ? (
      gfx_clear = 0x222222;          //! Grey
  ) : (
    backcolor == 2 ? (
      gfx_clear = 0x00A5FF;          //! Orange
  ) : (
    backcolor == 3 ? (
      gfx_clear = 0x0000FF;          //! Red
  ) : (
    backcolor == 4 ? (
      gfx_clear = 0xFFFFFF;          //! White
  ););););

… or …

  backcolor == 0 ? (
    gfx_clear = 0x000000;            //! Black 
  ) : (
    backcolor == 1 ? (
      gfx_clear = 0x222222;          //! Grey
    ) : (
    backcolor == 2 ? (
      gfx_clear = 0x00A5FF;          //! Orange
    ) : (
    backcolor == 3 ? (
      gfx_clear = 0x0000FF;          //! Red
    ) : (
    backcolor == 4 ? (
      gfx_clear = 0xFFFFFF;          //! White
    );););
  );

Personally I’d like to keep the colours in an array though so adding colours just means adding one to the slider and ond to the data. Say we have 20 different colour schemes, then we wouldn’t want to be running 38 if statements to select foreground/background colours. Having theme files is just a stone’s throw away from that too.

So we’d select the colour in @slider and the code in @gfx would use the array positions for foreground/background/highlight/whatever set once in global variables in @slider.

edit: ha, both of my indentation examples are one paren short, which is a problem with cocking a snook at the indentation gods. With cascading it would have been obvious:

  backcolor == 0 ? (
    gfx_clear = 0x000000;            //! Black 
  ) : (
    backcolor == 1 ? (
      gfx_clear = 0x222222;          //! Grey
    ) : (
      backcolor == 2 ? (
        gfx_clear = 0x00A5FF;          //! Orange
      ) : (
        backcolor == 3 ? (
          gfx_clear = 0x0000FF;          //! Red
        ) : (
          backcolor == 4 ? (
            gfx_clear = 0xFFFFFF;          //! White
          );
        );
      ); 
    );
  <-- one short

edit2: and if you think that maybe we could have had an extra paren somewhere down the line so everything matched, we’d have had code that could have compiled okay, but did weird things/bugs.

1 Like

Ahh crossed wires there
It was originally after the @init section,
but I moved it after I misread this line by you to mean the sliders definition area

Much clearer in hindsight LOL

Alright, changed and still working :smiley:

so now it goes:

desc:
options:
sliders
@init
@serialize
@slider
@gfx

Thanks again Snooks

So you converted me to cascading conditionals with that missing paren example

Have a final look sometime to see if I missed anything else

TrackNotes_v2.jsfx (4.9 KB)

Fixed now and still working:

Yeah, the cascading thing can save lives. Plus too much indentation is often a sign that a bit of refactoring is required, which is nice.

That’s mostly alright now! But you are still mooshing these together:

gfx_ext_retina = ext_noinit = 1;

… in @gfx when the ext_noinit is for @init only. You also have different amounts of blank lines coming before @ sections, for which I am currently rapidly breathing into a brown paper bag. :smiley:

I often only spot random stuff like that after uploading to Gitlab/hub or Gogs. The different formatting and read only or whatever seems to make stuff jump out.

LOL I told you earlier where I was moving it to

Breathe Snooky
I had 2 blank lines before and after comment-section lines
But I’ve added comment-section lines before @serialize and @sliders
and moved gfx_ext_retina to the end of @init

Must be getting close now…

TrackNotes_v2.jsfx (5.0 KB)