Go Back   Cockos Incorporated Forums > REAPER Forums > ReaScript, JSFX, REAPER Plug-in Extensions, Developer Forum

Reply
 
Thread Tools
Old 05-23-2020, 05:22 PM   #1
tack
Human being with feelings
 
tack's Avatar
 
Join Date: Jan 2014
Location: Ontario, Canada
Posts: 1,638
Default Clarification on JSFX atomic_setifequal() semantics

This is probably only something Justin or Schwa can answer, but if anyone knows, I'd be grateful:
  1. Does atomic_setifequal() have any behavioral differences by platform? Can we expect it to work equally reliably on Mac, Windows, and Linux?
  2. Does atomic_setifequal() work on gmem buffers?
  3. Does atomic_setifequal() work on _global.* variables?

Specifically, I am trying to understand if this code is invalid.

A Reaticulate user reported an issue that could be explained by a race condition in JSFX initialization if atomic_setifequal() does not support gmem buffers.

Ultimately I need to be able to lock a critical section across arbitrarily many instances of a JSFX.

Thanks!

Last edited by tack; 05-23-2020 at 07:16 PM.
tack is offline   Reply With Quote
Old 05-24-2020, 08:12 AM   #2
tack
Human being with feelings
 
tack's Avatar
 
Join Date: Jan 2014
Location: Ontario, Canada
Posts: 1,638
Default

And to add, if atomic_setifequal() can't work across JSFX instances (gmem regions or _global.* variables), is anyone aware of some other pattern or recipe to implement some form of cross-instance locking?
tack is offline   Reply With Quote
Old 05-25-2020, 05:20 PM   #3
tack
Human being with feelings
 
tack's Avatar
 
Join Date: Jan 2014
Location: Ontario, Canada
Posts: 1,638
Default

I'm fairly well convinced by now that atomic_setifequal() doesn't work with gmem region or global variables.

I've implemented a horrifying workaround by having a central Lua script elect one of the JSFX instances to be a lock manager. But this is really tragic. I'm quite interested to know if there's a proper way to do synchronization of critical sections across JSFX instances.

By way of example, consider the scenario where a JSFX instance wants to allocate a unique instance id by counter. (This is what Reaticulate needs to do, as each JSFX instance must autonomously and safely carve out its own region in the shared gmem buffer.)

Here's the JSFX, which tries (and fails) to use atomic_setifequal() to implement a spinlock used to generate a synchronized counter to act as an instance id:

Code:
// RaceMaker.jsfx
desc:RaceMaker
options:gmem=racemaker

slider1:-1<-1,1000,1>instance counter

@init
MAGIC = 0xdeadbeef;
instance_id = -1;
slider1 = instance_id;


@block
while (instance_id == -1 && gmem[0] == MAGIC) (
    // Spin until lock is acquired (or we reach max allowed iterations)
    (atomic_setifequal(_global.racemaker_lock, 0, 1) == 0) ? (
        // Lock was acquired.
        instance_id = gmem[1];
        gmem[1] += 1;
        // Release lock
        _global.racemaker_lock = 0;
        slider1 = instance_id;
    );
);
The idea is that the instances will startup and lie dormant (not attempt to acquire the id) until the first slot in the gmem buffer is updated with a special value to indicate readiness by a Lua script. Once the Lua script writes this magic value, all instances spring to life with the usual parallelism for FX processing.

Here is a Lua script now which writes the magic value to the gmem buffer, and then scans all the instances to look for collisions of instance ids.

Code:
function scan()
    local seen = {}
    local collisions = 0
    for tidx = 0, reaper.CountTracks(0) - 1 do
        local track = reaper.GetTrack(0, tidx)
        local fx = reaper.TrackFX_GetByName(track, "RaceMaker", false)
        if fx and fx ~= -1 and
          reaper.GetMediaTrackInfo_Value(track, "I_FXEN") == 1 and
          reaper.TrackFX_GetEnabled(track, fx) then
            local instance_id, _, _ = reaper.TrackFX_GetParam(track, fx, 0)
            if seen[tostring(instance_id)] then
                reaper.ShowConsoleMsg("COLLISION: track " .. tostring(tidx + 1) ..
                                    " with " .. seen[tostring(instance_id)] .. ": " ..
                                    tostring(instance_id) .. "\n")
                collisions = collisions + 1
            else
                seen[tostring(instance_id)] = tostring(tidx + 1)
            end
        end
    end

    if collisions == 0 then
        reaper.ShowConsoleMsg("OK: no counter collisions\n")
    end
end

reaper.gmem_attach("racemaker")
reaper.gmem_write(0, 0xdeadbeef)
-- Give instances a bit of time to allocate an id
reaper.defer(function()
    scan()
end)
I reproduce this by:
  1. Creating 200 tracks with the RaceMaker JSFX
  2. Executing the above Lua script

This outputs something like:

Code:
COLLISION: track 5 with 1: 163.0
COLLISION: track 16 with 13: 12.0
COLLISION: track 17 with 12: 18.0
COLLISION: track 22 with 12: 18.0
COLLISION: track 28 with 12: 18.0
COLLISION: track 42 with 36: 29.0
COLLISION: track 52 with 48: 38.0
[...]
Of course, as this is a race, the number and nature of the collisions varies. (Occasionally, as you'd expect, there are no collisions at all.)

So, with all that, unless I've done something silly with my spinlock implementation, I feel confident in saying atomic_setifequal() doesn't work with global variables.

Can this be implemented? Or is there another option to accomplish synchronization between JSFX instances?

Thanks!

Last edited by tack; 05-25-2020 at 05:37 PM.
tack is offline   Reply With Quote
Old 05-27-2020, 04:15 AM   #4
IXix
Human being with feelings
 
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,975
Default

Can't help regarding the atomics but I made a library ages ago to track instances of a JSFX. Here's the link if it's any use... https://stash.reaper.fm/v/25417/IX.Tracker.jsfx-inc.zip
IXix is online now   Reply With Quote
Old 05-27-2020, 07:29 AM   #5
tack
Human being with feelings
 
tack's Avatar
 
Join Date: Jan 2014
Location: Ontario, Canada
Posts: 1,638
Default

Quote:
Originally Posted by IXix View Post
Can't help regarding the atomics but I made a library ages ago to track instances of a JSFX. Here's the link if it's any use... https://stash.reaper.fm/v/25417/IX.Tracker.jsfx-inc.zip
Thanks for sharing that.

It looks like your code has the same problem mine does. In the case of your tracker library, you're making the assumption that Tracker.Register() can't execute concurrently across multiple JSFX instances. In my case, I was assuming that wasn't safe, but was making a different assumption that atomic_setifequal() worked on gmem buffers, which appears also to be false.

Concurrent execution of Tracker.Register() breaks the promise of unique instance ids.

Now, in practice, this race seems to be really rare. Personally, I have never run into it: I'd never observed multiple Reaticulate JSFX instances getting the same id. It works much like your code does: if the instance id isn't yet assigned in @block, then it goes and allocates one. This has been fine for me and until recently I'd not received user reports of any problems.

But the user in question can, for reasons that still aren't clear to me, very reliably reproduce the race with a few reloads of a project.

So to prove the unsafeness of the approach you and I have taken for allocating instance ids, I've made concurrency of the id allocation code much more likely by requiring a flag be set somewhere in gmem before the JSFX will try to allocate its id.

And this works for your Tracker library too:

Code:
@block
(gmem[20000] == 0xdeadbeef) ? (
    refresh_state = _global.Demo.Tracker.Update(self);
    my_id = _global.Demo.Tracker.GetID(self);
    instance_count = _global.Demo.Tracker.GetCount();
);
Then, once the project is loaded, I call a Lua script to write 0xdeadbeef at that (arbitrary) gmem slot, inducing the race. With this, I can get your library to generate the same id for multiple JSFX instances.

So ultimately the raciness is still there. Like I mentioned earlier, I've got a solution baking to basically assign one of the JSFX instances to act as a lock manager for the critical section (which assignment is done by a single-instance Lua script). But I'd really like there to be a proper way to solve this concurrency problem in JSFX.

(One may wonder, if I have a global Lua script running, why it doesn't take the role of assigning unique instance ids. There are reasons we can talk about if you want. But it's still only a workaround to the basic problem of memory-safe concurrency in JSFX.)
tack is offline   Reply With Quote
Old 05-27-2020, 07:47 AM   #6
IXix
Human being with feelings
 
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,975
Default

Oh, sorry it wasn't more helpful. I didn't think instances could be updated simultaneously but perhaps that's something to do with anticipative processing, multiple cores and all that voodoo. IIRC the atomic functions were to help manage interaction between the gfx and audio threads of a single jsfx.


I'm sure Justin would probably consider adding global variants if there's a valid use case but he's probably super busy with much bigger fish right now, so don't hold your breath.
IXix is online now   Reply With Quote
Old 05-27-2020, 09:11 AM   #7
tack
Human being with feelings
 
tack's Avatar
 
Join Date: Jan 2014
Location: Ontario, Canada
Posts: 1,638
Default

Quote:
Originally Posted by IXix View Post
IIRC the atomic functions were to help manage interaction between the gfx and audio threads of a single jsfx.
Yep, this is what I've now come to understand.


Quote:
Originally Posted by IXix View Post
I'm sure Justin would probably consider adding global variants if there's a valid use case but he's probably super busy with much bigger fish right now, so don't hold your breath.
Indeed. I've come to learn that one can't quite predict what things catch Justin and schwa's attention. I think it somehow relates to quantum indeterminacy.
tack is offline   Reply With Quote
Old 05-27-2020, 11:07 AM   #8
IXix
Human being with feelings
 
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,975
Default

Quote:
Originally Posted by tack View Post
one can't quite predict what things catch Justin and schwa's attention. I think it somehow relates to quantum indeterminacy.
Haha! I think their focus will definitely be elsewhere for the next couple of months but yes, you never know.
IXix is online now   Reply With Quote
Old 05-30-2020, 10:25 AM   #9
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 16,511
Default

sorry I've been weighing over this for a bit figuring out what to do.

atomic_*() work on gmem, but only atomically within a particular JSFX instance (so not across multiple instances).

Internally they are implemented using a mutex, so while I'd like to make it global, I want to make sure it doesn't cause any performance issues down the road. Expect some changes for this in a build soon.
Justin is offline   Reply With Quote
Old 05-30-2020, 10:36 AM   #10
tack
Human being with feelings
 
tack's Avatar
 
Join Date: Jan 2014
Location: Ontario, Canada
Posts: 1,638
Default

Quote:
Originally Posted by Justin View Post
sorry I've been weighing over this for a bit figuring out what to do.
Awesome! I'm really happy to know you're chewing on it.

Quote:
Originally Posted by Justin View Post
Internally they are implemented using a mutex, so while I'd like to make it global, I want to make sure it doesn't cause any performance issues down the road. Expect some changes for this in a build soon.
To mitigate risk of unintended consequences (performance hits or edge case deadlocks), I'd be quite happy with an explicit function call to acquire/release a mutex too. (Of course, the JSFX failing to release the lock would result in a deadlock too, but at least that wouldn't be an edge case. )

But if you're confident enough in making the atomic_() mutexes global would be safe, that'd definitely be nicest from a Just Works perspective.
tack is offline   Reply With Quote
Old 06-04-2020, 06:26 AM   #11
nofish
Human being with feelings
 
nofish's Avatar
 
Join Date: Oct 2007
Location: home is where the heart is
Posts: 12,417
Default

tack, you may want to check latest pre-release.
nofish is offline   Reply With Quote
Old 06-04-2020, 06:37 AM   #12
tack
Human being with feelings
 
tack's Avatar
 
Join Date: Jan 2014
Location: Ontario, Canada
Posts: 1,638
Default

Quote:
Originally Posted by nofish View Post
tack, you may want to check latest pre-release.
Awesome!
tack is offline   Reply With Quote
Old 09-07-2022, 05:16 AM   #13
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 17,432
Default

Quote:
Originally Posted by Justin View Post
atomic_*() work on gmem, but only atomically within a particular JSFX instance (so not across multiple instances).
...
Expect some changes for this in a build soon.


I just tried to build a queue with the ability to handle multiple senders, each of which will be a JSFX instance in different tracks, and a single consumer JSFX.

Hence the queue is in gmem.

Works fine when not using the atomic functions. But of course this is not safe. Hence I do need atomic to work across instances of JSFXes.

So I tried atomic_setifequal(), checking the result of that function and spinning if the result is not as expected (== previous value ).

Works with a single instance, but I do need the multiple instance stuff. Is this already working ?

Thanks,
-Michael

Code:
desc:midi to partner send

options:gmem=MIDI_TRANSFER

in_pin:none
out_pin:none

@init
  gmem[0] = 4; // write pointer for writers (needs atomic)
  gmem[1] = 4; // write pointer for readers
  gmem[2] = 4; // read pointer

@block
  while (midirecv(mpos, msg1, msg2, msg3) ) (         
    midisend(mpos, msg1, msg2, msg3);  // pass through
    ptr_if = 1;
    while (ptr_if)  (
      ptr = atomic_get(gmem[0]);
      ptr_old = ptr;
      ptr_4 =  ptr+4;
      ptr_4 >= 4+4*10000 ? ptr_4 = 4;
      ptr_cmp =  atomic_setifequal(gmem[0], ptr_old, ptr_4);
      ptr_cmp == ptr_old ? (  // qmwm[0] not changend by another sending instance
        gmem[ptr] = mpos; // we can do this, as the other only reads the prevoisly stored values
        ptr +=1;
        gmem[ptr] = msg1;
        ptr +=1;
        gmem[ptr] = msg2;
        ptr +=1;
        gmem[ptr] = msg3;
        ptr +=1;
        gmem[1] = ptr_4; // allow reading 
        ptr_if = 0;  // successfully queued
      );
    ); 
  );

Last edited by mschnell; 09-07-2022 at 06:08 AM.
mschnell is offline   Reply With Quote
Old 09-07-2022, 11:33 AM   #14
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 17,432
Default

Quote:
Originally Posted by Justin View Post
Internally they are implemented using a mutex,

AFAIU both X86 and ARM architectures do feature instructions that allow to implement e.g. atomic_setifequal() without the help of the OS. That would automatically be global and supposedly the fastest way to do it.

BTW.: what is atomic_get() and atomic_set() supposed to do ? AFAIU these actions don't need atomicness.

-Michael

Last edited by mschnell; 09-08-2022 at 02:59 AM.
mschnell is offline   Reply With Quote
Old 09-07-2022, 01:09 PM   #15
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 17,432
Default

Two instances of these

Code:
desc:midi to partner send

options:gmem=MIDI_TRANSFER

in_pin:none
out_pin:none

@init
  gmem[0] = 4; // write pointer for writers (needs atomic)
  gmem[1] = 4; // write pointer for readers
  gmem[2] = 4; // read pointer

@block
  while (midirecv(mpos, msg1, msg2, msg3) ) (         
    midisend(mpos, msg1, msg2, msg3);  // pass through
    ptr_if = 1;
    while (ptr_if)  (
      ptr = atomic_get(gmem[0]);
      ptr_4 =  ptr+4;
      ptr_4 >= 4+4*10000 ? ptr_4 = 4;
      ptr ==  atomic_setifequal(gmem[0], ptr, ptr_4)  ? (  // qmwm[0] not changend by another sending instance
        gmem[ptr] = mpos; // we can do this, as the other only reads the prevoisly stored values
        ptr +=1;
        gmem[ptr] = msg1;
        ptr +=1;
        gmem[ptr] = msg2;
        ptr +=1;
        gmem[ptr] = msg3;
        ptr +=1;
        gmem[1] = ptr_4; // allow reading 
        ptr_if = 0;  // successfully queued
       ):(
        _1 += 1; 
      );
    ); 
  );
indeed _1 slowly counts up, if multiple instances are active with receiving many Midi messages.


Hence the atomicness across instances seems to work now.

-Michael

Last edited by mschnell; 09-08-2022 at 03:01 AM.
mschnell is offline   Reply With Quote
Old 09-08-2022, 03:05 AM   #16
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 17,432
Default

BTW.: the list of library functions used for "atomic" by the C++ compiler (and hence might be considered useful) can be found here: -> https://en.cppreference.com/w/cpp/atomic
-Michael

Last edited by mschnell; 09-09-2022 at 03:23 AM.
mschnell is offline   Reply With Quote
Old 09-30-2022, 10:10 AM   #17
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 17,432
Default

Why do we need atomic_set() and atomic_get(). Is it really possible that a thread is interrupted and a floating point value is saved only partly ?

-Michael

Last edited by mschnell; 09-30-2022 at 10:58 PM.
mschnell is offline   Reply With Quote
Old 09-30-2022, 10:58 PM   #18
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 17,432
Default

...
Or is this related to some multicore cache issues - which seems unlikely as the code in GHitGub does not use "memory boundary" ASM instructions but a Mutex ?
mschnell is offline   Reply With Quote
Old 10-01-2022, 05:58 AM   #19
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 17,432
Default

I posted a FR regarding this issues.

-Michael
mschnell is offline   Reply With Quote
Old 03-04-2025, 12:02 AM   #20
Solstice
Human being with feelings
 
Join Date: Oct 2015
Posts: 82
Default

Quote:
Originally Posted by tack View Post
This is probably only something Justin or Schwa can answer, but if anyone knows, I'd be grateful:
  1. Does atomic_setifequal() have any behavioral differences by platform? Can we expect it to work equally reliably on Mac, Windows, and Linux?
  2. Does atomic_setifequal() work on gmem buffers?
  3. Does atomic_setifequal() work on _global.* variables?

Specifically, I am trying to understand if this code is invalid.

A Reaticulate user reported an issue that could be explained by a race condition in JSFX initialization if atomic_setifequal() does not support gmem buffers.

Ultimately I need to be able to lock a critical section across arbitrarily many instances of a JSFX.

Thanks!
I don't understand why it is so difficult to add such simple features that get out of dead ends. Each FX instance should be given it's own unique ID(could be a uuid) which one could query and all that...

You could try something like the follow. It is not much different from your code but does a little more work to try to keep things straight.

The main linchpin here is that a random value is used as a unique ID for separating out the race condition:

atomic_setifequal(gmem[mcfx_base + mcfx_idx_sync], 0, uidr);
(gmem[mcfx_base + mcfx_idx_sync] == uidr) ? (

Assuming the atomic set works and uidr is unique per instance then it should work.
One could potentially check for any uidr collisions by also storing them and generating new ones if they already exist.

I'm not sure about the serialization. It only seems to serialize one value in the rpp but it is remembering both.

There is also unique FXID instance guids and that seems like it is what one actually wants to get but I doubt there is a way to do it.



Code:
desc:new effect
options:gmem=LuaCommTest
/*
  reaper.gmem_attach("LuaCommTest")
  reaper.gmem_read(idx)
  
  The following example tries to enable setting unique fx instance ID's to enable cross communication
*/



@serialize 
file_string(0, #uid);  
 

@init
function rc() ( (rand(36) > 25) ? floor('0' + rand(9)) : floor('A' + rand(26)); );

max_instances = 1000; // Max number of instances
mcfx_init = -1; // If this instance is mc initialized  
mcfx_i = -1; // the instance idx into the list of instances 
mcfx_base = 0; // Offset where multiple instances of this JS are configured for cross-communication
mcfx_idx_zero = 0; // If gmem is zero then no instance has been synced. Clears the count and other pre-initialization.
mcfx_idx_sync = 1; // the synchronization lock idx
mcfx_idx_num = 2; // the FX instance number idx
mcfx_idx_ids = 3; // the FX instance id's (each of length 13)

file_string(0, #uid); 
(strlen(#uid) == 0) ? ( 
  #uid = ""; // uid for FX
  sprintf(#uid, "%c%c%c%c-%c%c%c%c%c%c%c%c", rc(),rc(),rc(),rc(), rc(),rc(),rc(),rc(),rc(),rc(),rc(),rc());
);

uidr = rand(0); // real uid used to avoid collisions(if this is not unique then there may be race conditions)
uids = 14; // Size of uid string(including null)

// Force complete pre-initalization
//i = 0; while(i < 2000) ( gmem[mcfx_base + i] = 0; i = i + 1; );

// pre-initialize (singleton) 
(gmem[mcfx_base + mcfx_idx_zero] == 0) ? (
  gmem[mcfx_base + mcfx_idx_sync] = 0;
  gmem[mcfx_base + mcfx_idx_num] = 0;
); 

// prevents any further pre-initialization
gmem[mcfx_base + mcfx_idx_zero] = 1;



function gmRStr(index, output) local(offset, buf) (
  offset = 0;
  buf = #;
  while (
    gmem[index] ? (
      str_setchar(buf, offset, gmem[index]);
      offset += 1;
      index  += 1;
    ) : 0;
  );
  
  strcpy_substr(output, buf, 0, offset); // truncates the output
);


function gmWStr(index, str) local(offset, buf, ch) (
  while (offset < strlen(str)) (
    ch = str_getchar(str, offset);
    ch ? (
      gmem[index + offset] = ch;
      offset += 1;
    ) : 0;
  );
 gmem[index+offset] = 0;
);

function gmRStrB(index, output) ( gmRStr(index*1000, output); );
function gmWStrB(index, str) ( gmWStr(index*1000, str); );

@slider


@block

// gmem vars for display
numFX = gmem[mcfx_base + mcfx_idx_num];
gmRStr(mcfx_base + mcfx_idx_ids + 0*uids, #uidFX1);
gmRStr(mcfx_base + mcfx_idx_ids + 1*uids, #uidFX2);
gmRStr(mcfx_base + mcfx_idx_ids + 2*uids, #uidFX3);
gmRStr(mcfx_base + mcfx_idx_ids + 3*uids, #uidFX4);
gmRStr(mcfx_base + mcfx_idx_ids + 4*uids, #uidFX5);



(mcfx_init == -1) ? (

  // Scan for uid in list, if found then do not instantiate
  lmax = max_instances; 
  lcnt = 0;
  while (lcnt < lmax) (
    gmRStr(mcfx_base + mcfx_idx_ids + lcnt*uids, #tmpstr);
    (strcmp(#tmpstr, #uid) == 0) ?  
    (
      lcnt = lcnt + lmax + 10;
    );
    lcnt = lcnt + 1;
  );

  // uid was found in list from previous initialization so ignore
  (lcnt >= lmax + 10) ? (
    mcfx_i = lcnt - lmax - 10 - 1;
    lcnt = 1000100;
 ) : ( lcnt = 0; );
  
  
  // Initalize instance
  while (lcnt < 100) (
    lcnt = lcnt + 1;
    atomic_setifequal(gmem[mcfx_base + mcfx_idx_sync], 0, uidr);
    (gmem[mcfx_base + mcfx_idx_sync] == uidr) ? (
      mcfx_init = 1;
      lcnt = 1000000;
      mcfx_i = atomic_add(gmem[mcfx_base + mcfx_idx_num], 1) - 1;
      gmWStr(mcfx_base + mcfx_idx_ids + mcfx_i*uids, #uid);
      gmem[mcfx_base + mcfx_idx_sync] = 0;
    );
  );
)
Solstice is offline   Reply With Quote
Old 03-04-2025, 02:16 AM   #21
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 17,432
Default

Quote:
Originally Posted by Solstice View Post
I don't understand why it is so difficult to add such simple features that get out of dead ends.
What exactly do you claim is lacking ?

I did check that the atomic* functionality on gmem does work with multiple instances on I64 arch. Code see above. (I don't have Reaper on ARM, even though I do own a RasPI.)

Very long ago I did code that assigns an ID to each instance of a JSFX (using gmem). I never saw a problem, but its many years ago and I can't claim I did it right . I remember I counted up a gmem cell in @init (not doing any random stuff) and saved the ID in a slider to retrieve it with the next project loading.

Last edited by mschnell; 03-04-2025 at 02:31 AM.
mschnell is offline   Reply With Quote
Old 03-04-2025, 10:35 AM   #22
Solstice
Human being with feelings
 
Join Date: Oct 2015
Posts: 82
Default

Quote:
Originally Posted by mschnell View Post
What exactly do you claim is lacking ?

I did check that the atomic* functionality on gmem does work with multiple instances on I64 arch. Code see above. (I don't have Reaper on ARM, even though I do own a RasPI.)

Very long ago I did code that assigns an ID to each instance of a JSFX (using gmem). I never saw a problem, but its many years ago and I can't claim I did it right . I remember I counted up a gmem cell in @init (not doing any random stuff) and saved the ID in a slider to retrieve it with the next project loading.

JSFX instances should be able to get their uid's. Seems to have them by looking at the rpp so why not have some js fx function that can retrieve them.

Slider that allows text input. [I requested this long ago] E.g., say I had some AI based FX that generates a unique sound based on a prompt. No way to do this without creating or using some gfx ui code. No simple "slider" that allows for text input. My use case is that I want to be able to enter filenames. I had to create my own gfx code to do it and it doesn't support copy and paste so this makes it a real pain(I end up editing the source code and change the variable value manually so I can get the copy and paste).

The ability to query variables and their values from a script. E.g., reaper obviously knows of the variables and their values since it displays them(maybe this only works in the editor though). But if a script could get and set a JSFX variable then it would make it much easier to have the two interact.

In fact, JSFX likely should be able to interact with scripts without issue. In fact, that is what I'm trying to achieve using gmem and some message passing which is why I need unique id's to avoid collisions.

E.g., one can call scripts relatively easily by simply passing a string, as a message, to a script listener that then can execute that string. Passing data back and forth is a problem. Strings and numbers could be passed relatively easily but userdata(tracks, items, etc) might be more difficult. But a translation table could be done where uid's are passed instead.



What I'm trying to achieve is to write a master FX that controls many other JS FX instances so that I can have an easier interface into those instances. E.g., say I have 100 FX A instances. Currently I have to manually go and mess with each one(and in my case edit the code to change variables and each one is an FX on an item(not track FX)). FX B then is a master FX that is one interface into all those 100 allowing configuring them from one window or do other things.

If I could, in JSFX, query other FX and get all the FX of a certain type or id and their unique id along with their variables and to be able to change the playback position to them then it would be a relatively simple thing to do.

E.g., think of it like writing a custom "Track Manager", "Routing Matrix", "FX Browser", or whatever. Some window that lets one message with a bunch of other things(in this case a bunch of FX instances). That is what I'm trying to write because I'm tired of messing with the FX individually or manually doing certain tasks which can be more easily automated or drastically simplified with a nice single interface. The interface can be done by a script which is what I'm doing but I need some way to get at those FX instances and information they contain. But this is not an easy task because there is no sane way for scripts and JSFX to interact. gmem allows it to be hacked but does not seem to be safe and requires a lot of extra code(that pollutes the script but I guess one could put it in an include).


E.g., just to give an example of what could be done: Suppose one has a bunch of logger FX that compute various audio and/or midi statistics and has them strewn throughout the project(tracks and items). Then one could have a master logger(script or possibly another JSFX) which consolidates all that information and presents it in one interface.

First and foremost having a way to uniquely identify the JSFX instances is key. Second, some type of messaging or data passing system is beneficial. This can be done with gmem but, again, is a lot of extra work(which I'm currently implementing for my specific use case) and would be better to have it built in.
Solstice is offline   Reply With Quote
Old 03-04-2025, 11:27 AM   #23
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 17,432
Default

Scripts and JSFXes in different instances live in different threads. Hence the communication needs to be done via FiFos (this is exactly what the "Midi Transfer" Code above does). Neither JSFXes not Scripts are allowed to wait on something. Hence communication is bound to be somewhat complex and not straight forward...and not at all "realtime".

OK, as any plugin obviously does have an ID, so it might be possible that it could be provided in a variable.

Last edited by mschnell; 03-04-2025 at 11:41 AM.
mschnell is offline   Reply With Quote
Old 03-04-2025, 12:01 PM   #24
Solstice
Human being with feelings
 
Join Date: Oct 2015
Posts: 82
Default

Quote:
Originally Posted by mschnell View Post
Scripts and JSFXes in different instances live in different threads. Hence the communication needs to be done via FiFos (this is exactly what the "Midi Transfer" Code above does). Neither JSFXes not Scripts are allowed to wait on something. Hence communication is bound to be somewhat complex and not straight forward...and not at all "realtime".

OK, as any plugin obviously does have an ID, so it might be possible that it could be provided in a variable.
Well, this is why one can do it using messaging. It doesn't have to be "real time" with messaging. Basically you are doing messaging with the midi. It's just ugly and could be, say, wrapped up in a nicer interface.

What would be nice is not just to get the ID but be able to get other plugin information. Some way to distinguish both the JSFX and the JSFX instance.

e.g., maybe be able to get the filename and the id. This way one can check if the JSFX instance is an instance of a certain FX.

This would make it easier to have multiple FX running multiple sub-instances and being able to disambiguate them all.

E.g.,

JSFX_GetInstanceID() returns the uuid as a string or possibly a unique handle or whatever.

JSFX_GetInstanceFilename(id) returns the filename backing the script.

JSFX_PostMessage(str) posts a message on a global messaging system that can be read by scripts and vice versa(JSFX_GetMessage()).

These behave, of course, like typical messaging systems. One gets any message with it's id or if it is broadcast. One posts a message and it's ID is attached.

This would allow for basic communications between scripts and fx. This is what I'm basically implementing now. I'm generating my own uids to enable the messaging(which is done using a chunk of gmem partitioned in to message blocks).

Making such a feature available in general would make it more useful and give some basic messaging features and it could be optimized and less error prone. e.g., timing info(timestamp) could be easily added to the messages to help deal with old or dead messages.

This would also make it easy for various script types to interact such as lua and python. As long as enough disambiguating info is added it should be easy to avoid conflicts.
Solstice is offline   Reply With Quote
Old 03-04-2025, 01:33 PM   #25
Solstice
Human being with feelings
 
Join Date: Oct 2015
Posts: 82
Default

Also, to be clear. What is important for the uid is so that one can associate item FX's in such a way that the scripts and interact in a meaningful way with the instances.

Right now there, AFAIK, no way for an item FX instance to in any way be distinguished from any other. A instance uid helps solve that. More so, a script needs to be able to also get a takefx's uid.

In this way, by having the JSFX itself be able to get it's own uid and a script to be able to get the an FX's id then they can synchronize themselves in an unambiguous way.

E.g. I have a need to get know the specific positions of take FX. I can get an item and then it's FX instances but I have no way of getting any uid's for them. More so, the JSFX themselves have data I want to use but there is no way to coordinate these 3 pieces of info(item, it's FX instances, and the FX instance itself) because there is no way to get uid's in of them or at least not all of them which is required.

I think simply having a way to get an instance UID in a script and a JSFX will go a long way to solving these types of issues.

e.g., we can get

fx_type : type string
fx_ident : type-specific identifier
fx_name : name of FX (also supported as original_name)

so I suggest adding an 'fx_uid' to get the unique id.

Then in JSFX a function that also allows the instance to get it's own instance uid.

Then gmem can be used to synchronize between everything.

If it is not too difficult then something like 'fx_var.Name' to get the instances variable value.

E.g., fx_var.srate can get the srate or fx_var.myvar will get whatever myvar is set to. If a string then returns that string value). Also possibly being able to set such variables.
Solstice is offline   Reply With Quote
Old 03-04-2025, 03:43 PM   #26
Solstice
Human being with feelings
 
Join Date: Oct 2015
Posts: 82
Default

A bigger issue I am having is that item take FX are never initialized in any way unless the are active(being played).

This prevents me from initializing or sending any setup information from them on project load and the code only works when I have played the item(and I guess the active take fx) or the playback occurs after the item's end but before any other(seems reaper calls only the previous item's active take fx's @init section).

This makes it impossible to do anything in regards to consolidating take fx's into a single interface since they will never be active in any way until they are "used"(which is when the item they are used on is being played).

Ideally there would be some way to have the @init run on project load to initialize the interactions with scripts. Possibly another block like @projectinit could be created to enable global initialization on project load.
Solstice is offline   Reply With Quote
Reply

Thread Tools

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 02:40 PM.


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