|
|
|
11-09-2018, 03:55 PM
|
#1
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
How to use Defer?
I'm relatively new to Lua, so please bear with me.
I was trying to get my script to perform a series of tasks, then hand off control to an external program, and finally wait to be notified when to take control again. The hand-off part works great, but I hadn't been able to effectively wait for the signal to regain control without blocking the external script from using Reaper too.
Then someone mentioned Defer.
I hadn't used it before. And in trying to figure out how to use it now, either I haven't had enough coffee, or it's just been that long of a week, but either way I'm not understanding how to use it like I want to.
Lua doesn't really support a Timer that doesn't interfere with the normal operation of Reaper, and that's the part that befuddles me on how Defer can help.
How should I be going about the above scenario where I'm waiting for a signal to take control of Reaper again from an outside script that also controls Reaper?
|
|
|
11-09-2018, 04:17 PM
|
#2
|
Human being with feelings
Join Date: May 2015
Location: Québec, Canada
Posts: 4,937
|
Quote:
Originally Posted by tXShooter
Lua doesn't really support a Timer that doesn't interfere with the normal operation of Reaper, and that's the part that befuddles me on how Defer can help.
|
defer runs the given code a bit later (33 ms or so), giving back control to REAPER in the meantime.
Last edited by cfillion; 11-09-2018 at 04:29 PM.
|
|
|
11-09-2018, 08:02 PM
|
#3
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Quote:
Originally Posted by cfillion
defer runs the given code a bit later (33 ms or so), giving back control to REAPER in the meantime.
|
Ok... well... now my mind is about as slow as molasses in the winter.
Perhaps I'm not explaining my confusion. Here's what I have:
Code:
function Render()
--[[ Do
Stuff
Here
]]--
-- Send message to watchdog to take over at this point:
reaper.ShowConsoleMsg("Engage the Watchdog",0)
-- Wait for Watchdog to create a file that we will be looking for:
if file_exists("C:\\temp\\RDI.rdi") == true then do end end
MsgBox("Watchdog is relinquishing control back to Reaper.")
end
function file_exists(name)
local f=io.open(name,"r")
if f~=nil then io.close(f) return true else return false end
reaper.defer(file_exists)
end
----------------------------------------
function GUI_Functions_Goes_Here()
End
----------------------------------------
GUI.New (code goes here)
End
----------------------------------------
local function Main()
reaper.Main_OnCommand(40262) -- Enable Auto-View-Scroll while Recording
reaper.Main_OnCommand(41172) -- Dock/undock currently focused window
if GUI.resized then
-- If the window's size has been changed, reopen it
-- at the current position with the size we specified
local __,x,y,w,h = gfx.dock(-1,0,0,0,0)
gfx.quit()
gfx.init(GUI.name, GUI.w, GUI.h, 0, x, y)
GUI.redraw_z[0] = true
gui.settings.docker_id = 0
end
end
GUI.Init()
GUI.Main()
Immediately after the upper part is finished the "Engage the Watchdog" message appears without delay even though the Watchdog isn't done doing its thing.
Last edited by tXShooter; 11-10-2018 at 10:37 AM.
|
|
|
11-10-2018, 12:10 PM
|
#4
|
Human being with feelings
Join Date: Aug 2012
Location: Finland
Posts: 2,668
|
Here's a simple test:
Code:
local defer_count = 0
function wait_for_playback()
defer_count = defer_count+1
if reaper.GetPlayState() == 0 then
reaper.defer(wait_for_playback)
else
reaper.ShowConsoleMsg("Playing...\n")
return
end
reaper.ShowConsoleMsg(tostring(defer_count) .. " ")
end
reaper.ShowConsoleMsg("Press play...\n")
wait_for_playback()
|
|
|
11-10-2018, 04:37 PM
|
#5
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Quote:
Originally Posted by spk77
Here's a simple test:
Code:
local defer_count = 0
function wait_for_playback()
defer_count = defer_count+1
if reaper.GetPlayState() == 0 then
reaper.defer(wait_for_playback)
else
reaper.ShowConsoleMsg("Playing...\n")
return
end
reaper.ShowConsoleMsg(tostring(defer_count) .. " ")
end
reaper.ShowConsoleMsg("Press play...\n")
wait_for_playback()
|
So when reaper.GetPlayState() equals 0, the command reaper.defer(wait_for_playback) exits the if and also exits the function and re-executes itself, performing a sort of self-inflicted loop?
|
|
|
11-11-2018, 06:24 AM
|
#6
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by tXShooter
So when reaper.GetPlayState() equals 0, the command reaper.defer(wait_for_playback) exits the if and also exits the function and re-executes itself, performing a sort of self-inflicted loop?
|
Not exactly, defer schedules the given function to be executed later on the GUI/main thread. (So it is not recursion, which people sometimes mistakenly think it is.) The rest of the function after the defer call still executes. (Because defer is just a regular function, it doesn't do any magic which would enable cutting off from the current execution path.)
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
Last edited by Xenakios; 11-11-2018 at 06:29 AM.
|
|
|
11-11-2018, 12:13 PM
|
#7
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Quote:
Originally Posted by Xenakios
Not exactly, defer schedules the given function to be executed later on the GUI/main thread.
|
You threw me with the schedules part.
How does this schedule work? Once per cycle (if so, which cycle)? A set time? When GUI / Main threads are 'activated'?
I'm just not getting the principle of how Defer works, where to apply it, and when it does its job. Everything that I have come across seems to define Defer as something that gets 'deferred' until some other time, but very little explains what exactly gets deferred, or how or when.
I'm sorry to be so dense, but this just seems like its out of my reach at the moment.
I'm just simply trying to pause my own script until a specific file exists (or condition, if you will), and then it will carry on, but do so without hindering the rest of Reaper's operations. In other languages I would use something like 'While', or an if/loop with a timer. When I do that in Lua for Reaper, it pretty much locks up Reaper until that condition is met.
|
|
|
11-11-2018, 01:16 PM
|
#8
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by tXShooter
I'm just simply trying to pause my own script until a specific file exists
|
Your only choice for that is to use defer, there's no ability to "pause" or "sleep" in the script code that would work. You can't do things like while loops inside the ReaScript code to wait for things because the script code is run in the GUI thread, that is shared with everything else in Reaper that has to do with user interaction and graphics. That's why they came up with the defer mechanism to allow scripts that can run in the "background". The function you set to be called with defer is called at some time in the future with a timer in the GUI thread. The deferred function runs only once, but if you make repeated calls into defer from the deferred function itself, you have effectively made an infinite loop that runs at each timer tick. (You can end the loop by no longer calling defer from the function.)
The defer timer is an internal thing in Reaper, you can't change its speed or expect it to run at any particular exact time interval. It runs maybe a few dozen times per second.
What is not going to work and will cause the GUI in Reaper to freeze :
Code:
while MyCondition()==false do
-- Assuming Lua had a simple way to sleep, it doesn't. But sleeping here
-- wouldn't help anyway because the control isn't given back to the Reaper
-- main GUI event loop as long as this while loop is running
sleep(100)
end
How it could be done with defer :
Code:
function tick()
if MyCondition()==true then
-- do whatever should be done when condition true
return -- finishes the script, no more calls to "tick" are going to happen
end
reaper.defer(tick) -- condition wasn't true, execute tick again later
end
tick() -- kick off execution with normal function call
Things do get more complicated if you don't want to do your condition check too often (many dozen times a second) or if you want to do some kind of multistep algorithm which needs to wait for external things to happen at each step. The former can be done by calculating the difference in clock time compared to the previous "tick" call. For the latter you will need a global variable that keeps track of what step in the algorithm is going on.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
Last edited by Xenakios; 11-11-2018 at 02:56 PM.
|
|
|
11-11-2018, 02:34 PM
|
#9
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Let me see if I understand the mechanics of this correctly.
First, you define a function. function tick()
Second,you declare the 'loop' for the defer (looping back to itself, but through a defer call) as part of a condition (false in this case) within that function. reaper.defer(tick)
Third, you call the function. tick()
Does that about sum it up?
Assuming the condition does eventually become true, would you skip the 'final' iteration of tick() by using goto label, where labelis after the declaration of reaper.defer(tick)
|
|
|
11-11-2018, 02:55 PM
|
#10
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by tXShooter
Assuming the condition does eventually become true, would you skip the 'final' iteration of tick()
|
It is skipped by simply returning early from the "tick" function inside the if condition. That ends the defer cycling and the whole script.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
11-12-2018, 01:03 PM
|
#11
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
I'm still doing something wrong here, and I would appreciate it if someone can help me to figure out what.
Code:
function file_exists()
local f=io.open("C:/temp/RDI.RDI","r")
if f~=nil then
io.close(f)
os.remove("C:/temp/RDI.RDI")
return true
else
reaper.defer(file_exists)
return false
end
end
This bit is supposed to test to see if RDI.RDI exists, and when it does, delete it and return true, otherwise loop until the file does exist... or at least that's what I was aiming for. What am I doing wrong here? The code following this call executes immediately instead of waiting until this condition is met.
|
|
|
11-12-2018, 01:08 PM
|
#12
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by tXShooter
I'm still doing something wrong here, and I would appreciate it if someone can help me to figure out what.
Code:
function file_exists()
local f=io.open("C:/temp/RDI.RDI","r")
if f~=nil then
io.close(f)
os.remove("C:/temp/RDI.RDI")
return true
else
reaper.defer(file_exists)
return false
end
end
This bit is supposed to test to see if RDI.RDI exists, and when it does, delete it and return true, otherwise loop until the file does exist... or at least that's what I was aiming for. What am I doing wrong here? The code following this call executes immediately instead of waiting until this condition is met.
|
The deferred function must not return anything.
So maybe something like this instead :
Code:
function tick()
local f=io.open("C:/temp/RDI.RDI","r")
if f~=nil then
io.close(f)
os.remove("C:/temp/RDI.RDI")
return
end
reaper.defer(tick)
end
tick()
In other words, you won't be able to do a simple "file_exists" function that waits until the file exists without blocking Reaper's GUI. Your whole script needs to be designed around the deferred function. That obviously can complicate things quite a bit, but there unfortunately isn't any other way to do it.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
Last edited by Xenakios; 11-12-2018 at 01:43 PM.
|
|
|
11-12-2018, 02:07 PM
|
#13
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Quote:
Originally Posted by Xenakios
Your whole script needs to be designed around the deferred function. That obviously can complicate things quite a bit, but there unfortunately isn't any other way to do it.
|
(Insert shocked yet confused look here... This might could explain why I have been experiencing difficulties understanding defer.)
Annnnnnnnnnnnnnnd how would I go about doing that (designing my script around defer)?
|
|
|
11-12-2018, 02:09 PM
|
#14
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Better yet, let's look for a different approach here...
How would I signal to Reaper that an outside script is finished, but still allow Reaper to be used by the outside script?
|
|
|
11-12-2018, 02:15 PM
|
#15
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by tXShooter
Annnnnnnnnnnnnnnd how would I go about doing that (designing my script around defer)?
|
Here's a "simple" script I did that waits until a file exists and if it does, reads and prints out its contents if they are different than the last time and stops running after 30 seconds :
Code:
-- Need this state outside the deferred function so that it stays around for each call
local fn = "C:/MusicAudio/RDI.txt"
local f=nil
local startclocktime = reaper.time_precise()
local lastcontent = ""
function tick()
if f == nil then
f=io.open(fn,"r")
end
if f~=nil then
f:seek("set",0)
local content = f:read("*all")
if content~=nil and lastcontent~=content then
reaper.ShowConsoleMsg("")
reaper.ShowConsoleMsg(content)
lastcontent = content
end
end
-- Close the file and end the script after running for 30 seconds
if reaper.time_precise()-startclocktime>=30.0 then
reaper.ShowConsoleMsg("\nfinished\n")
if f~=nil then io.close(f) end
return
end
reaper.defer(tick)
end
tick()
It probably has bugs. So this isn't exactly nice and easy stuff to do.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
11-12-2018, 02:20 PM
|
#16
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
Probably worth mentioning that since you're using my GUI, there's already a defer step being run by the GUI itself. If you want, you can just give it your function to run on each "loop":
Code:
GUI.func = my_function -- Make sure you don't have ()s there
GUI.freq = 0 -- Time between calls to your function, in seconds IIRC
It's often easiest to have your own "main" function being run on each loop, as above, and then checking in there for various conditions that will prompt your script to do something.
Signalling Reaper: Checking if a temporary file exists is probably the easiest way. Maybe putting a key into Reaper's ExtState file (just a standard .ini), since you can use the Reaper API to check that rather than [i]io[/o].
Defer: As Xenakios described you're just telling Reaper "please do this on your next processing cycle".
Code:
function my_deferred_function()
if not condition_where_we_want_to_stop then
reaper.defer(my_deferred_function)
else
stuff_to_do_before_stopping()
end
end
my_deferred_function()
Last edited by Lokasenna; 11-12-2018 at 02:26 PM.
|
|
|
11-12-2018, 02:20 PM
|
#17
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by tXShooter
Better yet, let's look for a different approach here...
How would I signal to Reaper that an outside script is finished, but still allow Reaper to be used by the outside script?
|
I dunno really, maybe beyond.Reaper might help, that's a Python thing designed to allow writing scripts that run in a process outside Reaper, which can maybe alleviate some issues :
https://forum.cockos.com/showthread.php?t=129696
But I don't know anything particular about it, I haven't used it nor Python at all lately.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
11-12-2018, 04:46 PM
|
#18
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Quote:
Originally Posted by Lokasenna
Probably worth mentioning that since you're using my GUI, there's already a defer step being run by the GUI itself. If you want, you can just give it your function to run on each "loop":
Code:
GUI.func = my_function -- Make sure you don't have ()s there
GUI.freq = 0 -- Time between calls to your function, in seconds IIRC
Code:
function my_deferred_function()
if not condition_where_we_want_to_stop then
reaper.defer(my_deferred_function)
else
stuff_to_do_before_stopping()
end
end
my_deferred_function()
|
How would I execute the deferred call from a Gui button's function?
(Forest, meet tree. Quarry, meet rock. Ocean, meet droplet. Thought, meet............ ?)
|
|
|
11-12-2018, 05:11 PM
|
#19
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
The same as anything else:
GUI.elms.my_button.func = my_deferred_function
my_deferred_function is be deferring itself - that is, telling Reaper "hey bud, call me again on the next cycle plzkthx". You just need to call it the first time.
|
|
|
11-12-2018, 06:23 PM
|
#20
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Quote:
Originally Posted by Lokasenna
The same as anything else:
GUI.elms.my_button.func = my_deferred_function
my_deferred_function is be deferring itself - that is, telling Reaper "hey bud, call me again on the next cycle plzkthx". You just need to call it the first time.
|
Yeah, but how does GUI.freq work in this? Do you have some code as an example?
Side question, one that may need to be asked in its own thread:
1) When a project is loaded, what tells a Lua script that a new project has been loaded?
2) Secondary to that, how would it read in stuff like the markers and volume settings from the .rpp file?
|
|
|
11-12-2018, 08:20 PM
|
#21
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
GUI.freq is only for when you're providing a function for it to call with GUI.func. As soon as you put a function there and GUI.Main i called, it will automatically call your function at the interval specified (every loop, if you set freq to 0). You never need to call the function yourself in this case.
This is the easiest way to have your script monitor things and live-update itself ("Did the user select a different track? Get the info from it").
The other approach - having a function that defers itself, then starting it with a button or something - might make more sense in your situation where you only want the deferred function to be running at a specific point in the sequence of events you're performing.
|
|
|
11-14-2018, 08:03 PM
|
#22
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Quote:
Originally Posted by Lokasenna
GUI.freq is only for when you're providing a function for it to call with GUI.func. As soon as you put a function there and GUI.Main i called, it will automatically call your function at the interval specified (every loop, if you set freq to 0). You never need to call the function yourself in this case.
|
YES!!!!!
I finally got my 'monitor' to work with this GUI.Main feature. THANK YOU!!!!
I'm not totally out of the woods yet. I still need to learn 1) how to work with COM objects in Lua for emailing with Outlook, 2) figure out OSC scene changes from an X32 console, and 3) how to notify my script when projects load / change. Eventually I want to get to a point where everything I need is from within Reaper, but for now I can start using my script and be productive.
__________________
"But be ye doers of the word, and not hearers only, deceiving your own selves."
|
|
|
11-14-2018, 09:11 PM
|
#23
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
For #2, I'm skeptical that you'll find anything that works entirely in Lua. The only option I can find right now is LuaCOM and that uses a binary executable. You can execute programs from the Reaper API though, so once the Reaper side of things is finished you could certainly run an external script/app to keep going.
For #3, your script could look in Reaper's .ini files (I forget the command, search for "PrivateProfileString" in the API) for whatever key stores the recent projects. If the most recent project changes then you've clearly loaded a new one.
Last edited by Lokasenna; 11-14-2018 at 09:16 PM.
|
|
|
11-15-2018, 02:58 AM
|
#24
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by Lokasenna
The only option I can find right now is LuaCOM and that uses a binary executable.
|
In other words, ReaScript Lua can not use any 3rd party Lua library that isn't source code only. Libraries that depend on loading compiled dll files or such are not going to work.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
11-15-2018, 08:10 PM
|
#25
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Quote:
Originally Posted by Lokasenna
For #2, I'm skeptical that you'll find anything that works entirely in Lua. The only option I can find right now is LuaCOM and that uses a binary executable. You can execute programs from the Reaper API though, so once the Reaper side of things is finished you could certainly run an external script/app to keep going.
|
Quote:
Originally Posted by Xenakios
In other words, ReaScript Lua can not use any 3rd party Lua library that isn't source code only. Libraries that depend on loading compiled dll files or such are not going to work.
|
Yeah, after my brief research on LuaCOM (latest stable version of 1.4 from 2004), perhaps launching an AHK script to handle the COM calls... that might be a better way to go?
Side topic to this... what is LuaROCKS?
__________________
"But be ye doers of the word, and not hearers only, deceiving your own selves."
|
|
|
11-15-2018, 08:21 PM
|
#26
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by tXShooter
Side topic to this... what is LuaROCKS?
|
Library package manager for Lua, but pretty much nothing it can install can be used with ReaScript.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
11-16-2018, 05:11 AM
|
#27
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
OSC on the X32
I’ve read as much as I can find on this subject as it relates to Reaper, and I still am having trouble understanding how to program a script that even interfaces with, let alone control, the X32. Most of what I have found deals with midi, and nothing on getting scene information.
Where can I find examples and information on setting up connectivity to an X32 via OSC in a Lua script?
__________________
"But be ye doers of the word, and not hearers only, deceiving your own selves."
|
|
|
11-16-2018, 06:24 AM
|
#28
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by tXShooter
via OSC in a Lua script?[/b]
|
I don't think you can do anything meaningful with OSC with Lua in ReaScript. The API has only one function to deal with OSC messages and that is just for sending an OSC message into Reaper itself. (Yeah, don't ask, I have no idea what that would be useful for...) Even if you were prepared to write the OSC code yourself, the networking functionality isn't available with Lua in ReaScript.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
Last edited by Xenakios; 11-16-2018 at 06:29 AM.
|
|
|
11-16-2018, 06:50 AM
|
#29
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,621
|
The reaper_plugin_functions.h mentions many additional OSC-functions not exposed to ReaScript, like:
CSurf_OnOscControlMessage, CreateLocalOscHandler, DestroyLocalOscHandler, SendLocalOscMessage.
Would it be useful to expose them to ReaScript?
|
|
|
11-16-2018, 10:49 AM
|
#30
|
Human being with feelings
Join Date: Aug 2017
Posts: 336
|
Quote:
Originally Posted by mespotine
The reaper_plugin_functions.h mentions many additional OSC-functions not exposed to ReaScript, like:
CSurf_OnOscControlMessage, CreateLocalOscHandler, DestroyLocalOscHandler, SendLocalOscMessage.
Would it be useful to expose them to ReaScript?
|
What do these do? How do you expose them to ReaScript?
__________________
"But be ye doers of the word, and not hearers only, deceiving your own selves."
|
|
|
11-18-2018, 01:46 PM
|
#31
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,621
|
Quote:
Originally Posted by tXShooter
What do these do? How do you expose them to ReaScript?
|
Via an extension plugin like SWS or JS-plugin.
What they do, I have no idea. I'm not a CPP-coder so I can't check...
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 05:29 PM.
|