|
|
|
12-10-2019, 10:59 AM
|
#1
|
Human being with feelings
Join Date: Dec 2018
Posts: 503
|
Do you use conditional/smart scripts for basic commands?
Sorry if this has been discussed already. Also apologies if you think this thread belongs to scripting section - I intentionally started it in General forum because it might be useful for people who aren't - yet - into scripting.
As I was customizing my REAPER setup, there was a need for basic editing commands to be more context-sensitive: same shortcut/context menu command initiating different actions depending on whether time selection was active, or item was selected, and so on.
I've now got many elementary actions such as COPY, CUT, DELETE, SPLIT, LOOP etc. linked to very basic conditional scripts, for example like this one for looping item or time selection:
Code:
reaper.PreventUIRefresh(1)
local starttime, endtime = reaper.GetSet_LoopTimeRange2(0, false, false, 0, 0, false)
-----------------------
-- if time NOT selected
-----------------------
if starttime == endtime
then
item = reaper.GetSelectedMediaItem(0,0)
if item ~= nil
then
reaper.Main_OnCommand(41039, 0) -- "Loop points: Set loop points to items"
reaper.Main_OnCommand(40632, 0) -- "Go to start of loop"
reaper.GetSetRepeat(1)
end
end
-----------------------
-- if time IS selected
-----------------------
if starttime ~= endtime
then
reaper.Main_OnCommand(40622, 0) -- "Time selection: Copy time selection to loop points"
reaper.Main_OnCommand(40632, 0) -- "Go to start of loop"
reaper.GetSetRepeat(1)
end
reaper.PreventUIRefresh(-1)
reaper.UpdateTimeline()
My questions for more experienced REAPER scripters is - do you use anything like this in your personal setups?
And is there perhaps already a better way to do things like this?
Generally, I was thinking that basic scripting like this might be part of the solution for users who are dissatisfied with REAPER's default user experience, or want it to resemble another DAW. At least, this possibility is something that newcomers should be made more aware of, as it does a lot to tailor the UX to suit one's needs.
But then again, I might just be reinventing the wheel here, so do tell if you have an opinion or insight on this subject
Last edited by n997; 12-11-2019 at 04:58 AM.
Reason: told better what the script does
|
|
|
12-11-2019, 12:19 AM
|
#2
|
Human being with feelings
Join Date: May 2018
Posts: 791
|
Looks cool. I don't know enough about Reaper to know what your variables are referencing. So that script changes your right click menu?
Maybe a super cool advanced Reaper scripting project would be a script where you could change stuff like that without writing a script.
|
|
|
12-11-2019, 04:56 AM
|
#3
|
Human being with feelings
Join Date: Dec 2018
Posts: 503
|
Quote:
Originally Posted by fred garvin
So that script changes your right click menu?
|
Nope - it simply does different things depending on what is selected:
if item selected, then set loop to that item and activate repeat
if time selected, then set loop to that time and activate repeat
Usually in REAPER those would be two (or more) separate commands, requiring separate shortcuts and several clicks. I have that script linked to Ctrl+L (and in context menus of Media items and Arrange view), so after selecting an item or time with mouse, it's always just one or two clicks to loop it.
I was used to this behaviour in Ableton Live (which has many similar optimizations in UX) and wanted to have the same command in REAPER.
|
|
|
12-11-2019, 05:27 AM
|
#4
|
Human being with feelings
Join Date: Oct 2017
Location: Larisa, Greece
Posts: 3,797
|
You could add this action to the media item left click in order to loop the source fast each time you click an item, but then if an item is already looping and you click it, it removes the looping source and set the new loop length to the whole item. That was my only problem of using it for the media item left click default mod.
If the action could loop the source only of an item that is not loopin, it would be great. Heading over to report it
Edit: but your talking about loop points and not loop source, sorry ididn’t See it at first.
*about these actions no need to script, is also possible by combining custom actions , or cycle actions if you want to change the behaviour more precise with a modifier with toggle state.
|
|
|
12-11-2019, 05:33 AM
|
#5
|
Human being with feelings
Join Date: Apr 2013
Location: France
Posts: 9,875
|
Scripts like that are very common. Simple conditions check is the essence of scripting. :P There is no reason to doubt about it.
|
|
|
12-11-2019, 06:14 AM
|
#6
|
Human being with feelings
Join Date: Dec 2018
Posts: 503
|
Quote:
Originally Posted by fred garvin
Maybe a super cool advanced Reaper scripting project would be a script where you could change stuff like that without writing a script.
|
Indeed - for non-scripter users, I'd imagine it'd be useful if conditionality was possible in Custom Action editor. There could be a list of conditions which user could insert, and then put actions inside them.
Granted, that is very close to basic scripting anyway, but since the Custom Action editor (Actions > Show action list... > Custom actions > New) already exists, the decision towards making things easier for non-scripters has already been made.
I think I'll make a feature suggestion of it, at least to log the idea.
|
|
|
12-11-2019, 06:50 AM
|
#7
|
Human being with feelings
Join Date: Oct 2017
Location: Larisa, Greece
Posts: 3,797
|
In order this to happen, the actions should report a toggle state, else how you could specify when an action is on? Same thing happens with cycle action editor.
|
|
|
12-11-2019, 10:12 AM
|
#8
|
Human being with feelings
Join Date: Apr 2013
Location: France
Posts: 9,875
|
@n997
This is so close to scripting... Better to script :P
Also scripts will be more optimum cause you can choose when to refresh GUI etc, not with custom actions.
I really rarely make custom actions anymore because of that.
|
|
|
12-11-2019, 10:19 AM
|
#9
|
Human being with feelings
Join Date: Nov 2010
Posts: 2,436
|
Of course, for example, all envelope actions I use always work on time selection if it exists...I got loads of scripts like that.
This is one of my favorite scripts (included in REAPER ReWorked )
Code:
-- Item - Set time selection to selected
items, automation items and
selected points in active envelope.lua
-- Main ----------------------------------------------------------------------------------------------------------------
function Main ()
reaper.PreventUIRefresh(1)
-- Get start and end time of item selection
local itemCount = reaper.CountSelectedMediaItems(0)
local itemStart = -1
local itemEnd = -1
for i = 0, itemCount-1 do
-- Get start and end position of current item
local item = reaper.GetSelectedMediaItem(0, i)
local currentStart = reaper.GetMediaItemInfo_Value(item, "D_POSITION")
local currentEnd = currentStart + reaper.GetMediaItemInfo_Value(item, "D_LENGTH")
-- Make sure itemStart and itemEnd are properly initiated for further comparison
if itemStart == -1 then
itemStart = currentStart
end
if itemEnd == -1 then
itemEnd = currentEnd
end
-- Check if item exceeds currently detected bounds
if currentStart < itemStart then
itemStart = currentStart
end
if currentEnd > itemEnd then
itemEnd = currentEnd
end
end
-- Get start and end time of automation item selection
local autoCount = 0
local autoStart = -1
local autoEnd = -1
for i = 0, reaper.CountTracks(0)-1 do
local track = reaper.GetTrack(0, i)
for j = 0, reaper.CountTrackEnvelopes(track)-1 do
-- First make sure envelope is visible before going through its automation items
local envelope = reaper.GetTrackEnvelope(track, j)
local brEnv = reaper.BR_EnvAlloc(envelope, false)
local _ , visible = reaper.BR_EnvGetProperties(brEnv, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
reaper.BR_EnvFree(brEnv, false)
if visible == true then
for h = 0, reaper.CountAutomationItems(envelope)-1 do
if reaper.GetSetAutomationItemInfo(envelope, h, "D_UISEL", 0, 0) > 0 then
-- Get start and end position of current item
autoCount = autoCount + 1
local currentStart = reaper.GetSetAutomationItemInfo(envelope, h, "D_POSITION", 0, 0)
local currentEnd = currentStart + reaper.GetSetAutomationItemInfo(envelope, h, "D_LENGTH", 0, 0)
-- Make sure autoStart and autoEnd are properly initiated for further comparison
if autoStart == -1 then
autoStart = currentStart
end
if autoEnd == -1 then
autoEnd = currentEnd
end
-- Check if item exceeds currently detected bounds
if currentStart < autoStart then
autoStart = currentStart
end
if currentEnd > autoEnd then
autoEnd = currentEnd
end
end
end
end
end
end
-- Get start and end time of envelope point selection in active envelope
local envCount = 0
local envEnd = -1
local envStart = -1
if reaper.GetSelectedEnvelope(0) ~= nil then
-- Check envelope is visible
local envelope = reaper.GetSelectedEnvelope(0)
local brEnv = reaper.BR_EnvAlloc(envelope, false)
local _ , visible = reaper.BR_EnvGetProperties(brEnv, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
reaper.BR_EnvFree(brEnv, false)
-- Go through selected envelope points
if visible == true then
for i = 0, reaper.CountEnvelopePointsEx(envelope, -1)-1 do
local _, position, _, _, _, selected = reaper.GetEnvelopePoint(envelope, i)
if selected == true then
if envEnd == -1 then
envEnd = position
end
if envStart == -1 then
envStart = position
end
if position > envEnd then
envEnd = position
end
if position < envStart then
envStart = position
end
envCount = envCount + 1
end
end
end
if itemCount <= 0 and autoCount <= 0 and envCount == 1 then
envCount = 0
envEnd = -1
endStart = -1
end
end
-- If selected items found, proceed to setting target time selection
if itemCount > 0 or autoCount > 0 or envCount then
-- Find target time selection values
local targetStart = -1
local targetEnd = -1
if itemCount > 0 and autoCount == 0 then
targetStart = itemStart
targetEnd = itemEnd
elseif itemCount == 0 and autoCount > 0 then
targetStart = autoStart
targetEnd = autoEnd
else
if itemStart < autoStart then
targetStart = itemStart
else
targetStart = autoStart
end
if itemEnd > autoEnd then
targetEnd = itemEnd
else
targetEnd = autoEnd
end
end
-- Compare results with envelope points
if envCount > 0 then
if envEnd > targetEnd then
targetEnd = envEnd
end
if envStart < targetStart or targetStart == -1 then
targetStart = envStart
end
end
-- Finally apply time selection change if new time selection is different
local timeStart, timeEnd = reaper.GetSet_LoopTimeRange(false, false, 0.0, 0.0, false)
if timeStart ~= targetStart or timeEnd ~= targetEnd then
reaper.GetSet_LoopTimeRange2(0, true, false, targetStart, targetEnd, false)
reaper.UpdateArrange()
reaper.Undo_OnStateChangeEx2(0, "Set time selection to items and automation items", 8, -1);
end
end
reaper.PreventUIRefresh(-1)
end
Main()
function NoUndoPoint () end -- Makes sure there is no unnecessary undo point created, see more
reaper.defer(NoUndoPoint) -- here: http://forum.cockos.com/showpost.php?p=1523953&postcount=67
|
|
|
12-11-2019, 11:17 AM
|
#10
|
Human being with feelings
Join Date: Dec 2018
Posts: 503
|
Thanks Breeder and X-Raym!
Interesting to see that this way of using scripts indeed seems to be the norm, at least for power users.
Having read some newcomers' complaints about REAPER's user experience, I feel that possibilities like these should perhaps be emphasized and mentioned more often. I suspect that many newcomers are so conditioned into not being able to modify their DAW UX themselves, that they wouldn't even think of something like this - despite it being the answer for many "I need REAPER UX to be like my other DAW" requests.
At least, I couldn't conceive of customizing basic editing in this way, until just a few months ago when I started to seriously look into REAPER customization and scripting. Now REAPER has become the go-to DAW for my projects.
|
|
|
12-12-2019, 02:50 AM
|
#11
|
Human being with feelings
Join Date: Apr 2013
Location: France
Posts: 9,875
|
Quote:
Having read some newcomers' complaints about REAPER's user experience, I feel that possibilities like these should perhaps be emphasized and mentioned more often.
|
From experience, I can told you this : don't make new comers coding a single line of code (if they are not used to this). You will just scare them haha
|
|
|
12-12-2019, 04:57 AM
|
#12
|
Human being with feelings
Join Date: Dec 2018
Posts: 503
|
Quote:
Originally Posted by X-Raym
From experience, I can told you this : don't make new comers coding a single line of code (if they are not used to this). You will just scare them haha
|
Very true But then again, maybe it's because there's not enough general encouragement that programming is a tool for everyone, not just "computer wizards".
The fact is that utilizing basic if/then statements, for loops etc. to make small scripts isn't really hard, especially if names of variables etc. are written in plain language or close to it. In my experience (with no formal training in programming) it's easier than some areas of elementary school math.
Thankfully, newer generations are usually taught some kind of introductory programming in schools. I think that programming will eventually be somewhat like cooking: everyone will have some elementary skills in it.
|
|
|
12-12-2019, 08:42 PM
|
#13
|
Human being with feelings
Join Date: Dec 2018
Posts: 503
|
As a newcomer to REAPER scripting, I just can't keep quiet about my happiness with the possibilities, also in this area
Today, I needed some vertical adjustment of envelope points, preferring to do it with a keyboard. My first reflex was to select the points and press UP and DOWN ARROWs on keyboard (muscle memory developed from moving vector points etc. in Photoshop).
On my setup, UP and DOWN ARROWs were instead linked to going to previous or next track, due to using same shortcuts in Live. I could have used actions for moving envelope points through other shortcuts (like NUM keys) but instead I really wanted to keep utilizing my existing muscle memories.
So conditional scripts were required, and I made these:
Code:
reaper.PreventUIRefresh(1)
local envSel = reaper.GetSelectedTrackEnvelope(0)
----------------------------------------------------
-- if envelope NOT selected
----------------------------------------------------
if envSel == nil
then
reaper.Main_OnCommand(40286, 0) -- "Track: Go to previous track"
end
-------------------------------------------------------------------------------
-- if envelope IS selected (ONLY FOR SELECTED ENVELOPE)
-------------------------------------------------------------------------------
if envSel ~= nil
then
reaper.Main_OnCommand(41180, 0) -- "Envelopes: Move selected points up a little bit"
end
reaper.PreventUIRefresh(-1)
reaper.UpdateTimeline()
Code:
reaper.PreventUIRefresh(1)
local envSel = reaper.GetSelectedTrackEnvelope(0)
----------------------------------------------------
-- if envelope NOT selected
----------------------------------------------------
if envSel == nil
then
reaper.Main_OnCommand(40285, 0) -- "Track: Go to next track"
end
-------------------------------------------------------------------------------
-- if envelope IS selected (ONLY FOR SELECTED ENVELOPE)
-------------------------------------------------------------------------------
if envSel ~= nil
then
reaper.Main_OnCommand(41181, 0) -- "Envelopes: Move selected points down a little bit"
end
reaper.PreventUIRefresh(-1)
reaper.UpdateTimeline()
After 20 years of using other DAWs, the flexibility to do this as a user is just amazing - including the fact that actions to move points already existed, so I didn't need to write any code for actually moving the points.
And of course, to suit another Photoshop reflex, I then made scripts for moving envelope points up or down in larger amounts and linked them to SHIFT + UP and DOWN ARROWs:
Code:
reaper.PreventUIRefresh(1)
local envSel = reaper.GetSelectedTrackEnvelope(0)
-------------------------------------------------------------------------------
-- if envelope IS selected (ONLY FOR SELECTED ENVELOPE)
-------------------------------------------------------------------------------
if envSel ~= nil
then
for i = 0, 5, 1
do
reaper.Main_OnCommand(41180, 0) -- "Envelopes: Move selected points up a little bit"
end
end
reaper.PreventUIRefresh(-1)
reaper.UpdateTimeline()
Code:
reaper.PreventUIRefresh(1)
local envSel = reaper.GetSelectedTrackEnvelope(0)
-------------------------------------------------------------------------------
-- if envelope IS selected (ONLY FOR SELECTED ENVELOPE)
-------------------------------------------------------------------------------
if envSel ~= nil
then
for i = 0, 5, 1
do
reaper.Main_OnCommand(41181, 0) -- "Envelopes: Move selected points down a little bit"
end
end
reaper.PreventUIRefresh(-1)
reaper.UpdateTimeline()
With all due respect to developers of other DAWs, this kind of possibility to customize even the basic user experience is what every DAW should allow. None of that would have been possible with other DAWs I use - in them, I settle for less convenience in workflows.
I understand that this kind of user empowerment in REAPER is possible only because other people have done the work to enable it. So again, I say: thank you developers, writers of extensions and scripts, and the community!
|
|
|
12-12-2019, 09:59 PM
|
#14
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
Code:
reaper.Main_OnCommand((envSel and 40286 or 41180), 0)
|
|
|
12-13-2019, 03:52 AM
|
#15
|
Human being with feelings
Join Date: Apr 2013
Location: France
Posts: 9,875
|
@loksenna
Clever ! This is as concized as it can be ^^
|
|
|
12-13-2019, 06:41 AM
|
#16
|
Human being with feelings
Join Date: Dec 2018
Posts: 503
|
Quote:
Originally Posted by Lokasenna
Code:
reaper.Main_OnCommand((envSel and 40286 or 41180), 0)
|
Interesting!
I can't get it to work though - should this be enough as the whole script for ARROW UP?
Code:
reaper.PreventUIRefresh(1)
local envSel = reaper.GetSelectedTrackEnvelope(0)
reaper.Main_OnCommand((envSel and 40286 or 41180), 0)
reaper.PreventUIRefresh(-1)
reaper.UpdateTimeline()
|
|
|
12-13-2019, 08:18 AM
|
#17
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
I misread your original code and got the action IDs reversed - it should be envSel and 41180 or 40286. But yes, that should work - it does here.
Explanation:
- In Lua, and most other languages, all values and types are considered either truthy or falsy. Lua specifically says that only false and nil are falsy; everything else - numbers, strings, tables, functions, etc - are truthy.
- The logical operators ( if X, X and Y, not(X or Y), etc.) look at truthiness rather than needing something that explicitly says true or false.
- Logical conditions are evaluated from left to right, respecting parentheses the same as in mathematics. If it's going through a condition and gets to a point where it can say with certainty what the result will be it short-circuits and returns the last value it passed without even looking at the rest. For example:
Code:
envSel and 41180 or 40286
If envSel is truthy, and all numbers are truthy, then " envSel and 41180" must be true. That makes the whole condition (truthy thing) or 40286, so Lua knows that it can stop evaluating. It then spits out the last value it checked - 41180 into the surrounding function call.
If envSel is falsy, falsy and 41180 can never be true so it keeps going. (falsy) or 40286 works because one of the values is truthy, so it spits out the first truthy value there - 40286.
Last edited by Lokasenna; 12-13-2019 at 08:49 AM.
|
|
|
12-13-2019, 11:31 AM
|
#18
|
Human being with feelings
Join Date: Oct 2018
Posts: 26
|
Quote:
Originally Posted by Lokasenna
(...)
Code:
envSel and 41180 or 40286
If envSel is truthy, and all numbers are truthy, then " envSel and 41180" must be true. That makes the whole condition (truthy thing) or 40286, so Lua knows that it can stop evaluating. It then spits out the last value it checked - 41180 into the surrounding function call.
If envSel is falsy, falsy and 41180 can never be true so it keeps going. (falsy) or 40286 works because one of the values is truthy, so it spits out the first truthy value there - 40286.
|
@Lokasenna: Wow! Thank you, that's the perfect explanation to a scripting-newbie like me! Trying to understand logical operations in some works of the "prominent" scripters around here regularly gave me brain knots.
|
|
|
12-13-2019, 11:44 AM
|
#19
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
That's where it becomes very helpful to format the code so it's readable:
Code:
local mouse_val = self.dir == "h"
and (GUI.mouse.x - self.x) / self.w
or (GUI.mouse.y - self.y) / self.h
I'm sure some of my scripts are still pretty hard to decipher though.
|
|
|
12-13-2019, 04:41 PM
|
#20
|
Human being with feelings
Join Date: Dec 2018
Posts: 503
|
Quote:
Originally Posted by Lokasenna
I misread your original code and got the action IDs reversed - it should be envSel and 41180 or 40286. But yes, that should work - it does here.
|
Yep - now it works!
And thanks for explanation - you pre-emptively answered some questions I was about to ask
|
|
|
12-13-2019, 05:21 PM
|
#21
|
Human being with feelings
Join Date: Dec 2018
Posts: 503
|
Quote:
Originally Posted by Zapatero
Trying to understand logical operations in some works of the "prominent" scripters around here regularly gave me brain knots.
|
Same here - I usually rewrite code into near-plain-language when learning from other people's scripts. And then my own scripts also tend to remain that way.
Admittedly, it does often help with readability and making modifications in the future, after forgetting most of the script. But it also tends to set self-imposed limits on how complex my code can be and what it can do.
It's somewhat comparable to musical composition. If one uses only basic scales, easily classifiable chord progressions etc., it is good for many things - but for writing truly masterful works, one will need to understand and have good command of more complex possibilities, too. I'm slowly working on that latter part, both in music and in coding
|
|
|
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 08:30 AM.
|