Old 01-30-2019, 11:50 PM   #1
Breeder
Human being with feelings
 
Breeder's Avatar
 
Join Date: Nov 2010
Posts: 2,436
Default Script request: delete empty AUDIO take from item

Why is this useful?

When you loop things or have record armed many tracks that are ready to capture things that don't necessarily have to be connected at the time of recording you may end up with lots of empty audio items. When I say empty I mean audio items whose samples (all of them) are 0.

If these are saved in *.wav format you can imagine the waste of space... (thank God for wavpack)

The script would work the same way SWS/S&M: Takes - Remove empty MIDI takes/items among selected items
works but with audio takes only.


I tried to find something similar to this on ReaPack and forums but no luck...


edit: solved - go to post #5 of this thread for a fully working lua script
__________________
REAPER ReWorked: An elegant and self-sufficient all-around REAPER configuration
Other stuff

Last edited by Breeder; 09-03-2020 at 03:18 PM.
Breeder is offline   Reply With Quote
Old 01-31-2019, 03:28 PM   #2
andyp24
Human being with feelings
 
andyp24's Avatar
 
Join Date: Mar 2016
Posts: 1,239
Default

But if it's an analogue input, even if nothing is connected, chances are the noise floor might cause non-zero samples surely.....?
andyp24 is offline   Reply With Quote
Old 01-31-2019, 03:32 PM   #3
Breeder
Human being with feelings
 
Breeder's Avatar
 
Join Date: Nov 2010
Posts: 2,436
Default

Quote:
Originally Posted by andyp24 View Post
But if it's an analogue input, even if nothing is connected, chances are the noise floor might cause non-zero samples surely.....?
In my case it is always 0 but in theory you can check for other values close to 0 that you judge to be acceptable in your user case.

I finished the script by myself in the end, just need to handle sample reading and then I'll post it here unless someone with ready solution pops up.
__________________
REAPER ReWorked: An elegant and self-sufficient all-around REAPER configuration
Other stuff

Last edited by Breeder; 09-03-2020 at 03:18 PM.
Breeder is offline   Reply With Quote
Old 01-31-2019, 04:30 PM   #4
nofish
Human being with feelings
 
nofish's Avatar
 
Join Date: Oct 2007
Location: home is where the heart is
Posts: 12,096
Default

Quote:
Originally Posted by Breeder View Post
just need to handle sample reading
These templates may come in handy:
https://github.com/ReaTeam/ReaScript...e/master/Audio

Nice script idea btw.
nofish is offline   Reply With Quote
Old 01-31-2019, 08:56 PM   #5
Breeder
Human being with feelings
 
Breeder's Avatar
 
Join Date: Nov 2010
Posts: 2,436
Default

Quote:
Originally Posted by nofish View Post
These templates may come in handy:
https://github.com/ReaTeam/ReaScript...e/master/Audio

Nice script idea btw.
Thanks!

This seems to work here for me, hope it helps someone else
Oh, and it needs SWS extension installed to make it work!
Code:
-- Globals -------------------------------------------------------------------------------------------------------------
local g_db_limit = nil -- Set to nil for "-inf" (g_db_limit = nil ...only samples with value 0.00000000001 which
                       -- is equal to -220db will be considered empty) or db value instead (g_db_limit = -80)

local g_delete_files_with_non_existent_source = true -- If this is true, if take's source is missing (indicated by the
                                                     -- text "offline" instead of audio peaks) it will also get deleted
                                                     -- Set to "false" to disable

-- Actions used --------------------------------------------------------------------------------------------------------
local g_cmd_delete_take        = reaper.NamedCommandLookup("_S&M_DELTAKEANDFILE4") -- SWS/S&M: Delete active take and source file in selected items (no undo)
local g_cmd_unselect_all_items = 40289                                             -- Item: Unselect all items

-- Main ----------------------------------------------------------------------------------------------------------------
function Main ()

	-- Convert g_db_limit to sample value
	local dbValLimit = 0
	if g_db_limit ~= nil then
		dbValLimit = math.exp((g_db_limit)*0.11512925464970228420089957273422)
	else
		dbValLimit = 0 + 0.00000000001
	end

	-- Save selected items and their takes - also find empty audio takes
	local emptyTakesFound = false
	local selectedItems = {}
	for i = 0, reaper.CountSelectedMediaItems(0)-1 do
		local item = reaper.GetSelectedMediaItem(0, i)

		selectedItems[i+1]           = {}
		selectedItems[i+1].item      = item
		selectedItems[i+1].allTakes  = {}
		for j = 0, reaper.CountTakes(item)-1 do
			local take = reaper.GetTake(item, j)
			selectedItems[i+1].allTakes[j+1]          = {}
			selectedItems[i+1].allTakes[j+1].isEmpty  = true
			selectedItems[i+1].allTakes[j+1].takeId   = j

			-- Read samples from current take and mark it as empty if no samples over g_db_limit found
			if take == nil or reaper.TakeIsMIDI(take) == true then
				selectedItems[i+1].allTakes[j+1].isEmpty = false
			else
				local src        = reaper.GetMediaItemTake_Source(take)
				local samplerate = reaper.GetMediaSourceSampleRate(src)
				local channelCount = reaper.GetMediaSourceNumChannels(src)
				local srcType    = reaper.GetMediaSourceType(src, "")
				local srcFile    = reaper.GetMediaSourceFileName(src, "")

				if g_delete_files_with_non_existent_source == true and srcFile ~= "" and srcFile ~= nil and reaper.file_exists(srcFile) == false then
					selectedItems[i+1].allTakes[j+1].isEmpty = true
				elseif samplerate == 0 or channelCount == 0 or srcType == "VIDEOEFFECT" or srcType == "LTC" or srcType == "CLICK" or srcType == "RPP_PROJECT" or srcType == "VIDEO" then
					selectedItems[i+1].allTakes[j+1].isEmpty = false
				else
					local accessor     = reaper.CreateTakeAudioAccessor(take)

					local startTime    = reaper.GetAudioAccessorStartTime(accessor)
					local endTime      = reaper.GetAudioAccessorEndTime(accessor)
					local window       = 1.0 -- 1 second

					local breakNow = 0
					while startTime < endTime and breakNow ~= 2 and selectedItems[i+1].allTakes[j+1].isEmpty == true do
						local samplesPerChannel = math.ceil(window * samplerate)
						local bufferSize        = samplesPerChannel*channelCount
						local buffer            = reaper.new_array(samplesPerChannel*channelCount)

						reaper.GetAudioAccessorSamples(accessor, samplerate, channelCount, startTime, samplesPerChannel, buffer)

						-- Check all samples for values over dbValLimit and break as soon as they are found
						for k = 1, bufferSize do
							if math.abs(buffer[k]) > dbValLimit then
								selectedItems[i+1].allTakes[j+1].isEmpty = false
								break
							end
						end

						startTime = startTime + (window - (window / 100)) -- small overlap so there are no some kind of rounding mistakes or whatever

						-- If startTime passes over end time, make sure to get only the samples from the end (increase window so
						-- we don't lose anything due to roding errors for the same reasons in previous line) end then break out
						if startTime >= endTime then
							window = window + 1
							startTime = endTime - window
							if startTime < 0 then
								startTime = 0
							end
							window = endTime - startTime
							breakNow = 1
						end
						if breakNow == 1 then
							breakNow = 2
						end
					end
					reaper.DestroyAudioAccessor(accessor)
				end
			end

			if selectedItems[i+1].allTakes[j+1].isEmpty == true then
				emptyTakesFound = true
			end
		end
	end

	-- Delete empty takes
	if emptyTakesFound == true then
		reaper.PreventUIRefresh(1)
		reaper.Undo_BeginBlock2(0)

		reaper.Main_OnCommandEx(g_cmd_unselect_all_items, 0, 0)

		-- Go through all the items, deleting empty takes
		for _, currentItemData in ipairs(selectedItems) do
			local lastActiveTakeId = nil
			for i = 0, reaper.CountTakes(currentItemData.item)-1 do
				if reaper.GetActiveTake(currentItemData.item) == reaper.GetTake(currentItemData.item, i) then
					lastActiveTakeId = i
					break
				end
			end
			reaper.SetMediaItemInfo_Value(currentItemData.item, "B_UISEL", 1)

			-- Go through all the takes in current item and delete empty take
			for _, currentTakeData in ipairs (currentItemData.allTakes) do
				local currentTake = reaper.GetTake(currentItemData.item, currentTakeData.takeId)

				if currentTakeData.isEmpty == true and currentTake ~= nil then

					-- Delete this empty take
					reaper.SetActiveTake(currentTake)
					reaper.Main_OnCommandEx(g_cmd_delete_take, 0, 0)
					reaper.UpdateArrange()

					-- Update take ids (tried using take pointers and take guids but it seems action for deleting takes
					-- changes those so it's best to use ids which are always the same but require updating due to deleting)
					for _, takeData in ipairs (currentItemData.allTakes) do
						if takeData.takeId > currentTakeData.takeId then
							takeData.takeId = takeData.takeId - 1
						end
					end
					if lastActiveTakeId ~= nil and lastActiveTakeId > currentTakeData.takeId then
						lastActiveTakeId = lastActiveTakeId - 1
					end

					-- Check if active take was deleted
					if currentTakeData.takeId == lastActiveTakeId then
						lastActiveTakeId = nil -- if active take is the one we're
					end                         -- deleting, no need to restore it
				end
			end

			-- Restore active take and unselect all items
			if lastActiveTakeId ~= nil then
				reaper.SetActiveTake(reaper.GetTake(currentItemData.item, lastActiveTakeId))
			end
			reaper.Main_OnCommandEx(g_cmd_unselect_all_items, 0, 0)
			reaper.UpdateArrange()
		end

		-- Restore item selection
		for _, currentItemData in ipairs(selectedItems) do
			if currentItemData.item ~= nil and reaper.ValidatePtr(currentItemData.item, "MediaItem*") then
				reaper.SetMediaItemInfo_Value(currentItemData.item, "B_UISEL", 1)
			end
		end

		reaper.PreventUIRefresh(-1)
		reaper.UpdateArrange()
		reaper.Undo_EndBlock2(0, "Remove empty audio takes/items among selected items and delete their source files", -1)
	end
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
Some effort has been invested in this. If you seriously use it, please consider supporting the developer. Thank you!

I don't mind if someone puts this script in ReaPack, but please include my donation link, forum nickname and link to this thread in the description if you do so.

__________________
REAPER ReWorked: An elegant and self-sufficient all-around REAPER configuration
Other stuff

Last edited by Breeder; 09-03-2020 at 03:18 PM.
Breeder is offline   Reply With Quote
Old 02-22-2019, 11:12 PM   #6
Breeder
Human being with feelings
 
Breeder's Avatar
 
Join Date: Nov 2010
Posts: 2,436
Default

Fixed the script in my last post. Reupdate if you're using it


Bugs were:

- When deleting multiple takes in the same item, it didn't work because the action for deleting takes was changing take pointers (and take GUID it seems so take id is to be used)

- Sometimes REAPER doesn't output 0 but really small number so if using -g_db_limit = nil, it actually deletes anything below -220db

- Added the ability to delete audio files whose source is missing. This is enabled by default. Set g_delete_files_with_non_existent_source to false to disable
__________________
REAPER ReWorked: An elegant and self-sufficient all-around REAPER configuration
Other stuff

Last edited by Breeder; 09-03-2020 at 03:18 PM.
Breeder 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 12:14 PM.


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