-- @description Set FX routing (per-pin control)
-- @version 7.2
-- @author Andernik
--
-- ============================================================================
-- ОПИСАНИЕ
-- ============================================================================
-- Скрипт позволяет гибко настраивать маршрутизацию пинов (pin mapping)
-- для плагинов на выбранных дорожках. Вы можете задать:
-- • какие эффекты обрабатывать (первый, последний или все);
-- • количество каналов плагина (VST3 bus size);
-- • количество каналов самой дорожки;
-- • для каждого пина – на какой канал трека он должен быть направлен
-- (можно указывать комбинации каналов через "+" или отключать пин через "-").
-- • Ограничение каналов до 64 (Так как REAPER API поддерживает только 64 канала для pin mapping.)
-- Скрипт удобно использовать, создавая несколько копий с разными настройками
-- и запуская их по необходимости.
--
-- ============================================================================
-- КАК ПОЛЬЗОВАТЬСЯ
-- ============================================================================
-- 1. Выделите одну или несколько дорожек в REAPER.
-- 2. Отредактируйте настройки в блоке ниже под нужный сценарий.
-- 3. Запустите скрипт.
--
-- ============================================================================
-- ПРИМЕРЫ НАСТРОЕК
-- ============================================================================
-- Пример 1. Моно из первого канала, стерео выход (классический mono-режим):
-- mode = 2
-- VST3_BUS_SIZE = 1
-- TRACK_CHANNELS = 0
-- INPUT_PINS = "1=1"
-- OUTPUT_PINS = "1=1, 2=2"
--
-- Пример 2. Левый канал на вход, выход дублируется на каналы 1 и 2:
-- INPUT_PINS = "1=1"
-- OUTPUT_PINS = "1=1+2"
--
-- Пример 3. Sidechain на каналах 3 и 4, основные входы не трогаем:
-- HARD_RESET_PINS = 0
-- SIDECHAIN_PINS = "2=3, 3=4"
-- OUTPUT_PINS = "1=1, 2=2"
--
-- Пример 4. 6-канальный плагин: входы 1-6, выходы 1-6 (жёсткий сброс):
-- HARD_RESET_PINS = 1
-- VST3_BUS_SIZE = 6
-- TRACK_CHANNELS = 6
-- INPUT_PINS = "1=1, 2=2, 3=3, 4=4, 5=5, 6=6"
-- OUTPUT_PINS = "1=1, 2=2, 3=3, 4=4, 5=5, 6=6"
--
-- Пример 5. Все эффекты, каждый принимает моно из первого канала,
-- и выводит на первый канал:
-- mode = 3
-- INPUT_PINS = "1=1"
-- OUTPUT_PINS = "1=1"
--
-- ============================================================================
-- ============================================================
-- НАСТРОЙКИ (изменяйте здесь под каждую копию скрипта)
-- ============================================================
-- Режим выбора эффектов:
-- 1 = только первый эффект на дорожке
-- 2 = только последний эффект на дорожке
-- 3 = все эффекты на дорожке
local mode = 2
-- VST3 bus size (количество каналов плагина):
-- 0 = не менять, 1 = моно, 2 = стерео, и т.д. до 128
local VST3_BUS_SIZE = 1
-- Количество каналов дорожки (0 = не менять)
local TRACK_CHANNELS = 2
-- Жёсткий сброс всех пинов перед применением:
-- 0 = не сбрасывать (будут изменены только явно указанные пины)
-- 1 (или любое ненулевое) = сбросить все входные и выходные пины в 0 перед настройкой
local HARD_RESET_PINS = 1
-- Основные входные пины: "пин=канал, пин=канал1+канал2, пин=-"
-- Примеры:
-- "1=1" -- пин1 на канал1
-- "1 = 1+2, 2 = 1+2" -- пин1 на сумму 1+2, пин2 на сумму 1+2 (пробелы допустимы)
-- "1=1, 2=-" -- пин1 на канал1, пин2 отключён
-- Если строка пустая или "0", пины не трогаются.
local INPUT_PINS = "1=1"
-- Sidechain входные пины: абсолютные номера пинов (как в окне pin connector)
-- Формат такой же, как у INPUT_PINS. Пример: "3=3,4=4" ( "0" без Sidechain)
local SIDECHAIN_PINS = "0"
-- Выходные пины: абсолютные номера пинов (начиная с 1)
-- Примеры:
-- "1=1+2" -- пин1 на каналы 1 и 2
-- "1=1, 2=2" -- пин1 на канал1, пин2 на канал2
local OUTPUT_PINS = "1=1+2"
-- ============================================================
-- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ (не изменять)
-- ============================================================
local function trim(s)
return s:match("^%s*(.-)%s*$")
end
local function parseChannelMask(str)
local mask = 0
for token in str:gmatch("[^+]+") do
local ch = tonumber(trim(token))
if ch and ch >= 1 and ch <= 64 then -- REAPER поддерживает только каналы 1-64
mask = mask | (1 << (ch - 1))
end
end
return mask
end
local function parsePinMap(str)
if str == nil or str == "" then return nil end
local trimmed = trim(str)
if trimmed == "0" then return nil end -- явное "0" означает "не трогать"
local map = {}
for pair in trimmed:gmatch("[^,]+") do
local pair_trimmed = trim(pair)
if pair_trimmed == "" then goto continue end
local pin_str, value_str = pair_trimmed:match("^(%d+)%s*=%s*(.+)$")
if pin_str and value_str then
local pin = tonumber(pin_str)
if pin and pin >= 1 and pin <= 128 then
local val = trim(value_str)
if val == "-" then
map[pin] = 0
else
local mask = parseChannelMask(val)
if mask ~= 0 then
map[pin] = mask
end
end
end
end
::continue::
end
return next(map) and map or nil
end
local function resetAllPins(track, fxidx)
for pin = 1, 128 do
reaper.TrackFX_SetPinMappings(track, fxidx, 0, pin - 1, 0, 0)
reaper.TrackFX_SetPinMappings(track, fxidx, 1, pin - 1, 0, 0)
end
end
local function applyInputPins(track, fxidx, map)
if not map then return end
for pin, mask in pairs(map) do
-- Сначала отключаем пин, потом, если маска не ноль, устанавливаем новое значение
reaper.TrackFX_SetPinMappings(track, fxidx, 0, pin - 1, 0, 0)
if mask ~= 0 then
reaper.TrackFX_SetPinMappings(track, fxidx, 0, pin - 1, mask, 0)
end
end
end
local function applyOutputPins(track, fxidx, map)
if not map then return end
for pin, mask in pairs(map) do
reaper.TrackFX_SetPinMappings(track, fxidx, 1, pin - 1, 0, 0)
if mask ~= 0 then
reaper.TrackFX_SetPinMappings(track, fxidx, 1, pin - 1, mask, 0)
end
end
end
-- ============================================================
-- ОСНОВНАЯ ФУНКЦИЯ (обёрнута в pcall для безопасности)
-- ============================================================
local function main()
local inputMap = parsePinMap(INPUT_PINS)
local sidechainMap = parsePinMap(SIDECHAIN_PINS)
local outputMap = parsePinMap(OUTPUT_PINS)
local sel_tracks = reaper.CountSelectedTracks(0)
if sel_tracks == 0 then return end
reaper.Undo_BeginBlock2(0)
local changed = false
for i = 0, sel_tracks - 1 do
local track = reaper.GetSelectedTrack(0, i)
if TRACK_CHANNELS > 0 then
reaper.SetMediaTrackInfo_Value(track, "I_NCHAN", TRACK_CHANNELS)
changed = true
end
local fx_cnt = reaper.TrackFX_GetCount(track)
if fx_cnt == 0 then
goto continue -- нет эффектов на дорожке
end
local start_idx, end_idx, step
if mode == 1 then
start_idx, end_idx, step = 0, 0, 1
elseif mode == 2 then
start_idx, end_idx, step = fx_cnt - 1, fx_cnt - 1, 1
elseif mode == 3 then
start_idx, end_idx, step = 0, fx_cnt - 1, 1
else
reaper.ShowMessageBox("Ошибка: mode должен быть 1, 2 или 3.", "Set FX routing", 0)
reaper.Undo_EndBlock2(0, "", -1)
return
end
for fxidx = start_idx, end_idx, step do
if HARD_RESET_PINS ~= 0 then
resetAllPins(track, fxidx)
end
if VST3_BUS_SIZE > 0 then
reaper.TrackFX_SetNamedConfigParm(track, fxidx, 'channel_config', tostring(VST3_BUS_SIZE))
end
applyInputPins(track, fxidx, inputMap)
applyInputPins(track, fxidx, sidechainMap)
applyOutputPins(track, fxidx, outputMap)
changed = true
end
::continue::
end
if changed then
reaper.Undo_EndBlock2(0, "Set FX routing (per-pin)", -1)
else
reaper.Undo_EndBlock2(0, "", -1)
end
end
-- Защищённый запуск с гарантированным закрытием Undo блока
local ok, err = pcall(main)
if not ok then
reaper.Undo_EndBlock2(0, "", -1) -- закрываем блок, если он был открыт
reaper.ShowMessageBox("Ошибка выполнения: " .. tostring(err), "Set FX routing", 0)
end