Scripts already referenced from CLAUDE.md but never committed: deploy_searxng.sh (which stands up the SearXNG instance Devon's web_search depends on), post_milestone.py, generate_milestone_images.py, make_x_launch_assets.py, schedule_x_launch.py, download_music.py, and the Reaper bleep-selection script. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
160 lines
5.4 KiB
Lua
160 lines
5.4 KiB
Lua
-- Bleep Selection — censor a time range on the selected track(s)
|
|
--
|
|
-- Replaces the audio inside the time selection with a 1kHz tone rather than
|
|
-- muting it. Muted regions read as silence to strip_silence_dialog.lua
|
|
-- (SILENCE_DB -30, thresholds 5-6s), so a muted bleep over a slowly-read phone
|
|
-- number can get stripped and shift everything after it out of sync. A tone at
|
|
-- TONE_GAIN_DB is nowhere near -30, so the silence pass leaves it alone.
|
|
--
|
|
-- Usage: make a time selection over the digits, select the track, run.
|
|
|
|
---------------------------------------------------------------------------
|
|
-- SETTINGS
|
|
---------------------------------------------------------------------------
|
|
local TONE_GAIN_DB = 0.0 -- adjust bleep level; file is generated at -15 dBFS
|
|
local FADE_MS = 4.0 -- fade in/out on the tone, prevents clicks
|
|
local TONE_FILE = "bleep_1khz.wav" -- 60s 1kHz sine @48k, sits next to this script
|
|
|
|
---------------------------------------------------------------------------
|
|
|
|
local EPS = 1e-9
|
|
|
|
local function script_dir()
|
|
local src = debug.getinfo(1, "S").source
|
|
return src:match("@?(.*[/\\])") or ""
|
|
end
|
|
|
|
local function items_on(track)
|
|
local t = {}
|
|
for i = 0, reaper.CountTrackMediaItems(track) - 1 do
|
|
t[#t + 1] = reaper.GetTrackMediaItem(track, i)
|
|
end
|
|
return t
|
|
end
|
|
|
|
local function item_bounds(item)
|
|
local pos = reaper.GetMediaItemInfo_Value(item, "D_POSITION")
|
|
return pos, pos + reaper.GetMediaItemInfo_Value(item, "D_LENGTH")
|
|
end
|
|
|
|
-- Split every item crossing either edge so the range is cleanly separable
|
|
local function split_at_edges(track, sel_start, sel_end)
|
|
for _, item in ipairs(items_on(track)) do
|
|
local pos, fin = item_bounds(item)
|
|
if fin > sel_start + EPS and pos < sel_end - EPS then
|
|
local right = item
|
|
if pos < sel_start - EPS then
|
|
right = reaper.SplitMediaItem(item, sel_start)
|
|
end
|
|
if right then
|
|
local rpos, rfin = item_bounds(right)
|
|
if rfin > sel_end + EPS and rpos < sel_end - EPS then
|
|
reaper.SplitMediaItem(right, sel_end)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function delete_inside(track, sel_start, sel_end)
|
|
local removed = 0
|
|
for _, item in ipairs(items_on(track)) do
|
|
local pos, fin = item_bounds(item)
|
|
if pos >= sel_start - EPS and fin <= sel_end + EPS then
|
|
reaper.DeleteTrackMediaItem(track, item)
|
|
removed = removed + 1
|
|
end
|
|
end
|
|
return removed
|
|
end
|
|
|
|
-- Build the item directly rather than via InsertMedia(): InsertMedia behaves
|
|
-- like the user-facing "insert media file" action — it obeys ripple editing
|
|
-- (shifting other tracks), can spawn a new track, and moves the edit cursor.
|
|
-- AddMediaItemToTrack touches nothing but this track.
|
|
local function insert_tone(track, sel_start, sel_len, tone_source)
|
|
local item = reaper.AddMediaItemToTrack(track)
|
|
if not item then return nil end
|
|
local take = reaper.AddTakeToMediaItem(item)
|
|
if not take then return nil end
|
|
reaper.SetMediaItemTake_Source(take, tone_source)
|
|
|
|
reaper.SetMediaItemInfo_Value(item, "D_POSITION", sel_start)
|
|
reaper.SetMediaItemInfo_Value(item, "D_LENGTH", sel_len)
|
|
reaper.SetMediaItemInfo_Value(item, "B_LOOPSRC", 0)
|
|
reaper.SetMediaItemInfo_Value(item, "D_VOL", 10 ^ (TONE_GAIN_DB / 20))
|
|
|
|
local fade = math.min(FADE_MS / 1000, sel_len / 2)
|
|
reaper.SetMediaItemInfo_Value(item, "D_FADEINLEN", fade)
|
|
reaper.SetMediaItemInfo_Value(item, "D_FADEOUTLEN", fade)
|
|
return item
|
|
end
|
|
|
|
---------------------------------------------------------------------------
|
|
|
|
local function main()
|
|
local sel_start, sel_end = reaper.GetSet_LoopTimeRange(false, false, 0, 0, false)
|
|
local sel_len = sel_end - sel_start
|
|
if sel_len <= 0 then
|
|
reaper.ShowMessageBox("Make a time selection over the audio to bleep.", "Bleep Selection", 0)
|
|
return
|
|
end
|
|
|
|
local n_tracks = reaper.CountSelectedTracks(0)
|
|
if n_tracks == 0 then
|
|
reaper.ShowMessageBox("Select the track to bleep.", "Bleep Selection", 0)
|
|
return
|
|
end
|
|
|
|
local tone_path = script_dir() .. TONE_FILE
|
|
local f = io.open(tone_path, "rb")
|
|
if not f then
|
|
reaper.ShowMessageBox("Tone file not found:\n" .. tone_path, "Bleep Selection", 0)
|
|
return
|
|
end
|
|
f:close()
|
|
|
|
local tone_source = reaper.PCM_Source_CreateFromFile(tone_path)
|
|
if not tone_source then
|
|
reaper.ShowMessageBox("Could not load tone file:\n" .. tone_path, "Bleep Selection", 0)
|
|
return
|
|
end
|
|
|
|
local targets = {}
|
|
for i = 0, n_tracks - 1 do
|
|
targets[#targets + 1] = reaper.GetSelectedTrack(0, i)
|
|
end
|
|
|
|
reaper.Undo_BeginBlock()
|
|
reaper.PreventUIRefresh(1)
|
|
|
|
-- Ripple editing would shift unrelated items (and other tracks) when items
|
|
-- are removed. Force it off for the duration, restore the user's mode after.
|
|
local ripple_per_track = reaper.GetToggleCommandStateEx(0, 40310) == 1
|
|
local ripple_all = reaper.GetToggleCommandStateEx(0, 40311) == 1
|
|
if ripple_per_track or ripple_all then
|
|
reaper.Main_OnCommand(40309, 0) -- ripple editing off
|
|
end
|
|
|
|
local bleeped = 0
|
|
for _, track in ipairs(targets) do
|
|
split_at_edges(track, sel_start, sel_end)
|
|
delete_inside(track, sel_start, sel_end)
|
|
if insert_tone(track, sel_start, sel_len, tone_source) then
|
|
bleeped = bleeped + 1
|
|
end
|
|
end
|
|
|
|
if ripple_all then
|
|
reaper.Main_OnCommand(40311, 0)
|
|
elseif ripple_per_track then
|
|
reaper.Main_OnCommand(40310, 0)
|
|
end
|
|
|
|
reaper.PreventUIRefresh(-1)
|
|
reaper.UpdateArrange()
|
|
reaper.Undo_EndBlock(string.format("Bleep %.2fs on %d track(s)", sel_len, bleeped), -1)
|
|
end
|
|
|
|
main()
|