Go Back   Cockos Incorporated Forums > REAPER Forums > REAPER General Discussion Forum

Reply
 
Thread Tools Display Modes
Old 12-10-2019, 10:59 AM   #1
n997
Human being with feelings
 
Join Date: Dec 2018
Posts: 85
Default 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
n997 is offline   Reply With Quote
Old 12-11-2019, 12:19 AM   #2
fred garvin
Human being with feelings
 
Join Date: May 2018
Posts: 488
Default

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.
fred garvin is offline   Reply With Quote
Old 12-11-2019, 04:56 AM   #3
n997
Human being with feelings
 
Join Date: Dec 2018
Posts: 85
Default

Quote:
Originally Posted by fred garvin View Post
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.
n997 is offline   Reply With Quote
Old 12-11-2019, 05:27 AM   #4
Vagelis
Human being with feelings
 
Join Date: Oct 2017
Location: Larisa, Greece
Posts: 516
Default

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.
Vagelis is online now   Reply With Quote
Old 12-11-2019, 05:33 AM   #5
X-Raym
Human being with feelings
 
X-Raym's Avatar
 
Join Date: Apr 2013
Location: France
Posts: 6,319
Default

Scripts like that are very common. Simple conditions check is the essence of scripting. :P There is no reason to doubt about it.
X-Raym is offline   Reply With Quote
Old 12-11-2019, 06:14 AM   #6
n997
Human being with feelings
 
Join Date: Dec 2018
Posts: 85
Default

Quote:
Originally Posted by fred garvin View Post
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.
Attached Images
File Type: png Conditions in Custom Actions.png (23.6 KB, 267 views)
n997 is offline   Reply With Quote
Old 12-11-2019, 06:50 AM   #7
Vagelis
Human being with feelings
 
Join Date: Oct 2017
Location: Larisa, Greece
Posts: 516
Default

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.
Vagelis is online now   Reply With Quote
Old 12-11-2019, 10:12 AM   #8
X-Raym
Human being with feelings
 
X-Raym's Avatar
 
Join Date: Apr 2013
Location: France
Posts: 6,319
Default

@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.
X-Raym is offline   Reply With Quote
Old 12-11-2019, 10:19 AM   #9
Breeder
Human being with feelings
 
Breeder's Avatar
 
Join Date: Nov 2010
Location: Croatia
Posts: 2,284
Default

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
Breeder is offline   Reply With Quote
Old 12-11-2019, 11:17 AM   #10
n997
Human being with feelings
 
Join Date: Dec 2018
Posts: 85
Default

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.
n997 is offline   Reply With Quote
Old 12-12-2019, 02:50 AM   #11
X-Raym
Human being with feelings
 
X-Raym's Avatar
 
Join Date: Apr 2013
Location: France
Posts: 6,319
Default

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
X-Raym is offline   Reply With Quote
Old 12-12-2019, 04:57 AM   #12
n997
Human being with feelings
 
Join Date: Dec 2018
Posts: 85
Default

Quote:
Originally Posted by X-Raym View Post
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.
n997 is offline   Reply With Quote
Old 12-12-2019, 08:42 PM   #13
n997
Human being with feelings
 
Join Date: Dec 2018
Posts: 85
Default

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!
n997 is offline   Reply With Quote
Old 12-12-2019, 09:59 PM   #14
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,550
Default

Code:
reaper.Main_OnCommand((envSel and 40286 or 41180), 0)
Lokasenna is offline   Reply With Quote
Old 12-13-2019, 03:52 AM   #15
X-Raym
Human being with feelings
 
X-Raym's Avatar
 
Join Date: Apr 2013
Location: France
Posts: 6,319
Default

@loksenna
Clever ! This is as concized as it can be ^^
X-Raym is offline   Reply With Quote
Old 12-13-2019, 06:41 AM   #16
n997
Human being with feelings
 
Join Date: Dec 2018
Posts: 85
Default

Quote:
Originally Posted by Lokasenna View Post
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()
n997 is offline   Reply With Quote
Old 12-13-2019, 08:18 AM   #17
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,550
Default

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.
Lokasenna is offline   Reply With Quote
Old 12-13-2019, 11:31 AM   #18
Zapatero
Human being with feelings
 
Join Date: Oct 2018
Posts: 7
Default

Quote:
Originally Posted by Lokasenna View Post
(...)

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.
Zapatero is online now   Reply With Quote
Old 12-13-2019, 11:44 AM   #19
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,550
Default

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.
Lokasenna is offline   Reply With Quote
Old 12-13-2019, 04:41 PM   #20
n997
Human being with feelings
 
Join Date: Dec 2018
Posts: 85
Default

Quote:
Originally Posted by Lokasenna View Post
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
n997 is offline   Reply With Quote
Old 12-13-2019, 05:21 PM   #21
n997
Human being with feelings
 
Join Date: Dec 2018
Posts: 85
Default

Quote:
Originally Posted by Zapatero View Post
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
n997 is offline   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 04:31 PM.


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