Old 01-14-2018, 03:34 AM   #1
_Stevie_
Human being with feelings
 
_Stevie_'s Avatar
 
Join Date: Oct 2017
Location: Black Forest
Posts: 5,067
Default Replace FX with another one in same slot

Hey all,

I'm trying to figure how, if it's possible to replace a VST instrument (always placed in FX slot 1 here) with another one via ReaScript.
From what I could gather, there is no function to insert or move an FX to a specific FX slot. Is there a workaround?
__________________
My Reascripts forum thread | My Reascripts on GitHub
If you like or use my scripts, please support the Ukraine: Ukraine Crisis Relief Fund | DirectRelief | Save The Children | Razom
_Stevie_ is online now   Reply With Quote
Old 01-14-2018, 05:20 AM   #2
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,175
Default

Quote:
Originally Posted by _Stevie_ View Post
Hey all,

I'm trying to figure how, if it's possible to replace a VST instrument (always placed in FX slot 1 here) with another one via ReaScript.
From what I could gather, there is no function to insert or move an FX to a specific FX slot. Is there a workaround?
Yes it's possible.

There is an SWS API which allows for shifting an FX plugin up or down the order - SNM_MoveOrRemoveTrackFX (by one place at a time - so looping the API call would be required to move it the correct number of places). So you would use the TrackFX_AddByName API call to insert the new FX at the end, then using MoveOrRemoveTrackFX you can remove the first fx plugin and then calling the same MoveOrRemoveTrackFX API - (as many times as there are FX in the chain) - you can shift the plugin up the order to the top.

Or (perhaps more efficiently - although at the expense of more complex code) you can directly manipulate the Track chunk - cutting the string data for the last inserted FX plugin - then overwrite the first FX plugin data with that data - and write the new chunk string back in using TrackFX_SetTrackStateChunk API. Lua is ideal for this as it provides very useful string search and replace functions. Although I would say this is probably mid-level reascript coding - there are plenty of examples out there for manipulating the track chunk.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 01-14-2018, 05:22 AM   #3
_Stevie_
Human being with feelings
 
_Stevie_'s Avatar
 
Join Date: Oct 2017
Location: Black Forest
Posts: 5,067
Default

Awesome lb0!!! That's exactly the info that I needed, thank you very much!
I think I will go for the more complex solution, since it seems to be more flexible.
__________________
My Reascripts forum thread | My Reascripts on GitHub
If you like or use my scripts, please support the Ukraine: Ukraine Crisis Relief Fund | DirectRelief | Save The Children | Razom
_Stevie_ is online now   Reply With Quote
Old 01-14-2018, 05:41 AM   #4
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,175
Default

Quote:
Originally Posted by _Stevie_ View Post
Awesome lb0!!! That's exactly the info that I needed, thank you very much!
I think I will go for the more complex solution, since it seems to be more flexible.
No problem.

Just for an example (of the first case) - here's some code that moves the last fx plugin in the chain to the top for the selected track.

Code:
local track = reaper.GetSelectedTrack(0, 0)
if track then
  local fxcnt = reaper.TrackFX_GetCount(track)
  for i = fxcnt-1,1,-1 do
    local ret = reaper.SNM_MoveOrRemoveTrackFX(track, i, -1)
  end
end
But yeah - I would personally do it in the chunk. My stripper script has lots of chunk manipulation functions - but they are buried amongst a LOT of other code. If you need any further help - let me know.

EDIT: Also - when reading the track chunk - I would use the GetTrackChunk function from here:

https://forum.cockos.com/showthread.php?t=193686

This avoids the track chunk data being truncated if it exceeds a certain size (easily done if using Kontakt or similar instruments). The native reaper API for reading the track chunk seems to have a bug which causes data to go missing.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 01-14-2018, 07:06 AM   #5
_Stevie_
Human being with feelings
 
_Stevie_'s Avatar
 
Join Date: Oct 2017
Location: Black Forest
Posts: 5,067
Default

Okay the chunk solution seems indeed a bit more complex.
Will GetTrackChunk() retrieve an XML file?

What I plan to do is:

- check if there is a VST instrument in slot 1
- if so, replace it with Kontakt 5
- if there are insert FX in slot 2,3,4, etc... leave them untouched
__________________
My Reascripts forum thread | My Reascripts on GitHub
If you like or use my scripts, please support the Ukraine: Ukraine Crisis Relief Fund | DirectRelief | Save The Children | Razom
_Stevie_ is online now   Reply With Quote
Old 01-14-2018, 08:39 AM   #6
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,175
Default

Quote:
Originally Posted by _Stevie_ View Post
Okay the chunk solution seems indeed a bit more complex.
Will GetTrackChunk() retrieve an XML file?

What I plan to do is:

- check if there is a VST instrument in slot 1
- if so, replace it with Kontakt 5
- if there are insert FX in slot 2,3,4, etc... leave them untouched
The track chunk isn't XML - it has its own format.

A definition can be found here:

https://github.com/ReaTeam/ReaScript...%20Definitions

The following code will show the track chunk for the selected track:

Code:
 function GetTrackChunk(track, usefix)
    if not track then return end
    local track_chunk
    
    
    if usefix == true and reaper.APIExists('SNM_CreateFastString') == true then
      
      local fast_str = reaper.SNM_CreateFastString("")
      if reaper.SNM_GetSetObjectState(track, fast_str, false, false) then
        track_chunk = reaper.SNM_GetFastString(fast_str)
      end
      reaper.SNM_DeleteFastString(fast_str)  
    else
      _, track_chunk = reaper.GetTrackStateChunk(track,'',false)
    end
    return track_chunk
  end
  
  local track = reaper.GetSelectedTrack2(0, 0, true)
  local chunk = GetTrackChunk(track,true)
  if chunk then
    reaper.ShowConsoleMsg(chunk)
  end
So using this you can analyse different track setups to see how they look.

To check if there is a VSTi in the first slot - you'd need to identify the start of the FXCHAIN section of the track chunk, pull out the first plugin data

- usually between BYPASS ..> WAK 0 (BYPASS normally signifies the start of a plugin , and WAK 0 the end. There may be rare occasions when this is not entirely correct.

you can check that the "<VST" string has "VSTi:" as part of its identifier (to identify it apart from other types of plugin).

EDIT: So long as you don't change any other plugin data (apart from the 1st) within the chunk - then they should remain as they are.

Note - an empty track that's never had any plugins on it - doesn't contain an FXCHAIN section. But a track that has had plugins on it (even if they've been removed) - will have at least a partial FXCHAIN section. So there are several cases you'd need to treat differently.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 01-14-2018, 08:55 AM   #7
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,175
Default

Here are a bunch of my FX chunk routines from my Stripper script:

Some might need altering to work in another script - but they hopefully will give you an idea on how to parse the chunk data. I'm not going to pretend they're perfect, nor that neatly written (I work pretty fast and quite often in suboptimal conditions - ie. kids running around my ankles). But they work for me.

And apologies for not commenting them well - again - I write them for my own use - so comments are generally not required for most of the code (where I consider it straightforward).


Code:
  function Chunk_GetFXChainSection(chunk)
  
    -- If FXChain - return section
    -- If none - return char after MAIN SEND \n
  
    local s1 = string.find(chunk, '<FXCHAIN.-\n')
    if s1 then
      local s = s1
      local indent, op, cl = 1
      while indent > 0 do
        op = string.find(chunk, '\n<', s+1, true)
        cl = string.find(chunk, '\n>\n', s+1, true) + 1
        if op == nil and cl == nil then break end
        if op ~= nil then
          op = op + 1
          if op <= cl then
            indent = indent + 1
            s = op
          else
            indent = indent - 1
            s = cl
          end
        else
          indent = indent - 1
          s = cl        
        end
      end
    
      local retch = string.sub(chunk,s1,cl)
      return retch, s1, cl
    else
      local s1, e1 = string.find(chunk, 'MAINSEND.-\n')
      return nil, s1, e1
    end
  end
  
  --returns new track chunk, new fxguid, old fxguid
  function Chunk_InsertFXChunkAtEndOfFXChain(trn, trchunk, insfxchunk, keepid)
  
    guids = {}
    local ofxid, nfxid = nil, nil
    local rchunk = nil
    local rchunk2 = nil
  
    if keepid == nil then
      --prepare insert chunk    
      if insfxchunk then
        insfxchunk = string.gsub(insfxchunk,
                                'FXID ({%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x})',
                                function(d) if guids[d] == nil then guids[d]=reaper.genGuid('') end return 'FXID '..guids[d] end)
      end
      
      --should be just one
      for i, v in pairs(guids) do
        ofxid = i
        nfxid = v 
      end
    end
    
    --condition insfxchunk - Thanks Eugen
    --PROGRAMENV
    local insfxchunk2 = string.gsub(insfxchunk,"<PROGRAMENV.->\n","")
          
    local chunk, chs, che = Chunk_GetFXChainSection(trchunk)
    if chunk then
      --insert before final character
      rchunk = string.sub(trchunk,0,che-1) .. insfxchunk .. string.sub(trchunk,che-1)
      if insfxchunk2 ~= insfxchunk then
        rchunk2 = string.sub(trchunk,0,che-1) .. insfxchunk2 .. string.sub(trchunk,che-1)      
      end
    else
      if trn == -1 then
        --master track
        local ms, me = string.find(trchunk,'.+>')
        if me then  
          --insert at very end
          rchunk = string.sub(trchunk,0,me-1).. '<FXCHAIN\nSHOW 0\nLASTSEL 0\nDOCKED 0\n'.. insfxchunk ..'\n>\n'..string.sub(trchunk,me)  
          if insfxchunk2 ~= insfxchunk then
            rchunk2 = string.sub(trchunk,0,me-1).. '<FXCHAIN\nSHOW 0\nLASTSEL 0\nDOCKED 0\n'.. insfxchunk2 ..'\n>\n'..string.sub(trchunk,me)  
          end
        end
      else
        --normal track -- insert after MAINSEND
        rchunk = string.sub(trchunk,0,che)..'<FXCHAIN\nSHOW 0\nLASTSEL 0\nDOCKED 0\n'.. insfxchunk ..'\n>\n'..string.sub(trchunk,che+1)
        if insfxchunk2 ~= insfxchunk then
          rchunk2 = string.sub(trchunk,0,che)..'<FXCHAIN\nSHOW 0\nLASTSEL 0\nDOCKED 0\n'.. insfxchunk2 ..'\n>\n'..string.sub(trchunk,che+1)
        end
      end    
    end
    return rchunk, nfxid, ofxid, rchunk2
    
  end
  
  
  function MoveFXChunk(srcfxnum, dstfxnum)
  
    local writechunk = false
    local trn = tracks[track_select].tracknum
    local tr = GetTrack(trn)
    local fxcnt = reaper.TrackFX_GetCount(tr)
    local chunk = GetTrackChunk(tr, settings_usetrackchunkfix)
    
    local nchunk = MoveFXChunk2(chunk, trn, srcfxnum, dstfxnum, fxcnt)
    if nchunk then
      SetTrackChunk(tr, nchunk, false)      
    end
    
  end
  
  function MoveFXChunk2(chunk, trn, srcfxnum, dstfxnum, fxcnt)

    local _, nchunk, movechunk = RemoveFXChunkFromTrackChunk(chunk, srcfxnum)
    
    if nchunk then
      if dstfxnum == fxcnt then
        --insert at end
        nchunk = Chunk_InsertFXChunkAtEndOfFXChain(trn, nchunk, movechunk, true)
        if nchunk then
          writechunk = true
        end
      else
    
        local fnd, _, s, e = GetFXChunkFromTrackChunk(nchunk, dstfxnum)
        if fnd then
          nchunk = string.sub(nchunk,0,s-1)..movechunk..string.sub(nchunk,s)
          writechunk = true
        end
      end
    end
  
    if writechunk then
      return nchunk
    end
  
  end
    
  --returns success, fxchunk, start loc, end loc
  function GetFXChunkFromTrackChunk(trchunk, fxn)
  
    local s,e, fnd = 0,0,nil
    for i = 1,fxn do
      s, e = string.find(trchunk,'(BYPASS.-WAK %d %d)',s)
      if s and e then
        fxchunk = string.sub(trchunk,s,e)

        if i == fxn then fnd = true break end
        s=e+1
      else
        fxchunk = nil
        fndn = nil
        break
      end
    end
    return fnd, fxchunk, s, e  
  
  end

  function GetFXChunks(tracknum)
  
    local tr = GetTrack(tracknum)
    local fxn = reaper.TrackFX_GetCount(tr) 
    local fxtbl = {}
    local guididx = {}
    local chunk = GetTrackChunk(tr, settings_usetrackchunkfix)
    local s,e, fnd = 0,0,nil
    local trchunk_beg, trchunk_end
    for i = 1,fxn do
      s, e = string.find(chunk,'(BYPASS.-WAK %d %d)',s)
      if i==1 then
        trchunk_beg = string.sub(chunk,0,s-2)
      elseif i == fxn then
        trchunk_end = string.sub(chunk,e+1)
      end
      if s and e then
        fxtbl[i] = {chunk = string.sub(chunk,s,e)}
        fxtbl[i].guid = string.match(fxtbl[i].chunk, 'FXID ({%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x})')
        guididx[fxtbl[i].guid] = i
        s=e+1
      else
        break
      end
    end
    return fxtbl, guididx, trchunk_beg, trchunk_end  
  
  end

  function RemoveFXChunkFromTrackChunk(trchunk, fxn)
  
    local s,e, fnd = 0,0,nil
    local nchunk
    for i = 1,fxn do
      s, e = string.find(trchunk,'(BYPASS.-WAK %d %d)',s)
      if s and e then
        fxchunk = string.sub(trchunk,s,e+1)

        if i == fxn then 
          fnd = true 
          nchunk = string.sub(trchunk,0,s-2)..string.sub(trchunk,e+1)
          break 
        end
        s=e+1
      else
        fxchunk = nil
        fndn = nil
        break
      end
    end
    return fnd, nchunk, fxchunk  
  
  end
Please also note - some of these functions contain extra code to do things like replace the FXID (as I reuse bits of plugin chunk data in different chunks - so FXID would need to change to prevent possibility of duplicate IDs in a chunk) - so overall they're probably good just as an example...

EDIT 15_03_2021: Updated to include fix for the introduction of a second number after the WAK line (thanks Vitalker).
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website

Last edited by lb0; 03-15-2021 at 02:10 AM.
lb0 is offline   Reply With Quote
Old 01-14-2018, 09:20 AM   #8
_Stevie_
Human being with feelings
 
_Stevie_'s Avatar
 
Join Date: Oct 2017
Location: Black Forest
Posts: 5,067
Default

Wow thanks ib0, that's really a pile of good information. Will try digging into it later today!
__________________
My Reascripts forum thread | My Reascripts on GitHub
If you like or use my scripts, please support the Ukraine: Ukraine Crisis Relief Fund | DirectRelief | Save The Children | Razom
_Stevie_ is online now   Reply With Quote
Old 01-14-2018, 09:42 PM   #9
_Stevie_
Human being with feelings
 
_Stevie_'s Avatar
 
Join Date: Oct 2017
Location: Black Forest
Posts: 5,067
Default

Hey Ib0!

I just finished the scripts. Although, I planned to use the chunk method,
I finally settled with the SWS function, since it gave me quicker results.
Here's what it looks like:

(first post, last GIF)
https://forums.cockos.com/showthread.php?t=200614
__________________
My Reascripts forum thread | My Reascripts on GitHub
If you like or use my scripts, please support the Ukraine: Ukraine Crisis Relief Fund | DirectRelief | Save The Children | Razom
_Stevie_ is online now   Reply With Quote
Old 01-15-2018, 02:54 AM   #10
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,175
Default

Quote:
Originally Posted by _Stevie_ View Post
Hey Ib0!

I just finished the scripts. Although, I planned to use the chunk method,
I finally settled with the SWS function, since it gave me quicker results.
Here's what it looks like:

(first post, last GIF)
https://forums.cockos.com/showthread.php?t=200614
Nice work! Nice neat code and functional - I could learn a thing or two there

The SWS API calls are great for producing nice compact code. I think they should be part of the native API as you simply cannot do some things without them (or without diving into the chunk - and without a nice neat chunk library that just works - this can be quite painful at times).
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 01-15-2018, 06:16 PM   #11
_Stevie_
Human being with feelings
 
_Stevie_'s Avatar
 
Join Date: Oct 2017
Location: Black Forest
Posts: 5,067
Default

Hah, thanks man! Much appreciated!

Absolutely, the SWS stuff is streamlined, it should really be part of the API.
They literally fill the gap.
__________________
My Reascripts forum thread | My Reascripts on GitHub
If you like or use my scripts, please support the Ukraine: Ukraine Crisis Relief Fund | DirectRelief | Save The Children | Razom
_Stevie_ is online now   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 08:07 AM.


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