Is there a script that can shift the whole project to the right, including tempo and parameter curves? I need to insert a bar at the start of the project. Alternatively can I set the start bar to 0 or -1?…. I am using version 1 not 2 as I need manual mode.
I have two possible scripts:-
‘shift.lua’ Copyright (c) 2021 hataori@protonmail.com
This moves by fractions or a number of whole bars but may slightly warp the parameter curves and leave remnants that need deleting.
copy_notes_and_parameters.lua Copyright 2022 RigoLigo
This seems to do a better job of copying parameter curves but is probably best used to copy to a new track and the curve start and end points need anchoring (add a pint to each end of the curve) or it will ramp from what ever the new start point is to your first specified point.
Neither copy vocal modes. Try them on a COPY of your project then you have a way to recover if it does go wrong!
Shift.lua
SCRIPT_TITLE = "RV Shift Notes & Params"
paramTypeNames = {
"pitchDelta", "vibratoEnv", "loudness", "tension", "breathiness", "voicing", "gender", "toneShift"
}
local inputForm = {
title = SV:T("Shift Notes & Params"),
message = SV:T("Workaround for the \"many notes with params shifting crash\"\nshifts everything between 1st and last note selected"),
buttons = "OkCancel",
widgets = {
{
name = "cbDirection", type = "ComboBox",
label = "Direction",
choices = {"Forward", "Backward"},
default = 0
},
{
name = "cbUnit", type = "ComboBox",
label = "Unit",
choices = {"Measure (at 1st note)", "Quarter", "Time (sec)"},
default = 0
},
{
name = "slAmount", type = "Slider",
label = "How much to shift",
format = "%1.0f",
minValue = 0,
maxValue = 16,
interval = 1,
default = 0
},
{
name = "tbAmount", type = "TextBox",
label = "How much to shift (can use floating point numbers)",
default = "0"
}
}
}
function getClientInfo()
return {
name = SV:T(SCRIPT_TITLE),
author = "Hataori@protonmail.com",
category = "Real Voice",
versionNumber = 2,
minEditorVersion = 65537
}
end
function process()
local timeAxis = SV:getProject():getTimeAxis()
local scope = SV:getMainEditor():getCurrentGroup()
local group = scope:getTarget()
-- determine start and end time
local minTime_b, maxTime_b = math.huge, 0
local noteCnt = group:getNumNotes()
if noteCnt == 0 then -- no notes in track
return
else
local selection = SV:getMainEditor():getSelection()
local selectedNotes = selection:getSelectedNotes()
if #selectedNotes == 0 then
SV:showMessageBox(SV:T("Nothing selected"), SV:T("Select notes to shift (only a start and end note is enough)"))
return
else
table.sort(selectedNotes, function(noteA, noteB)
return noteA:getOnset() < noteB:getOnset()
end)
for _, note in ipairs(selectedNotes) do
local onset_b, nend_b = note:getOnset(), note:getEnd()
if onset_b < minTime_b then
minTime_b = onset_b
end
if nend_b > maxTime_b then
maxTime_b = nend_b
end
end
end
end
assert(maxTime_b > minTime_b)
-- list of notes to shift
local notes = {}
for i = 1, group:getNumNotes() do
local note = group:getNote(i)
local onset_b, nend_b = note:getOnset(), note:getEnd()
if nend_b > minTime_b and onset_b < maxTime_b then
table.insert(notes, note)
end
end
inputForm.title = inputForm.title.." ("..#notes.." notes)"
-- show dialog
local dlgResult = SV:showCustomDialog(inputForm)
local amount = tonumber(dlgResult.answers.tbAmount)
local amountSlider = tonumber(dlgResult.answers.slAmount)
if not dlgResult.status or (amount + amountSlider) == 0 then return end -- cancel pressed or no shift
local direction = 1 - 2 * dlgResult.answers.cbDirection -- 1 forward, -1 backward
amount = (amount + amountSlider) * direction
local timeConvert, shift = false, 0
local unit = dlgResult.answers.cbUnit
local shift = 0
if unit == 0 then
local measure = timeAxis:getMeasureMarkAtBlick(minTime_b)
shift = measure.numerator / measure.denominator * 4 * SV.QUARTER * amount -- in blicks
elseif unit == 1 then
shift = SV.QUARTER * amount
else
timeConvert = true
end
-- do the shift
for _, note in ipairs(notes) do
local onset_b = note:getOnset()
local newOnset_b = onset_b
if timeConvert then
local onset = timeAxis:getSecondsFromBlick(onset_b)
newOnset_b = timeAxis:getBlickFromSeconds(onset + amount)
else
newOnset_b = onset_b + shift
end
if newOnset_b < 0 then
SV:showMessageBox("Error!", "You are about to move notes before zero time!\nFirst make a room by selecting all notes and shifting them forward.")
return
end
note:setOnset(newOnset_b)
end
-- correction of short pauses and overlap due to rounding
if timeConvert then
for _, note in ipairs(notes) do
local onset_b = note:getOnset()
local ni = note:getIndexInParent()
local nextNote = group:getNote(ni + 1)
if nextNote then
if math.abs(nextNote:getOnset() - note:getEnd()) <= 1 then
note:setDuration(nextNote:getOnset() - note:getOnset())
end
end
end
end
for _, par in ipairs(paramTypeNames) do
local am = group:getParameter(par) -- automation track
-- save points to list
local points = am:getPoints(minTime_b, maxTime_b)
-- add boundary points
local startval = am:get(minTime_b)
table.insert(points, 1, {minTime_b, startval})
local endval = am:get(maxTime_b)
table.insert(points, {maxTime_b, endval})
-- remove from track and add boundaries back
am:remove(minTime_b, maxTime_b)
am:add(minTime_b, startval)
am:add(maxTime_b, endval)
-- target times
local newStartTime_b, newEndTime_b
if timeConvert then
local minTime = timeAxis:getSecondsFromBlick(minTime_b)
newStartTime_b = timeAxis:getBlickFromSeconds(minTime + amount)
local maxTime = timeAxis:getSecondsFromBlick(maxTime_b)
newEndTime_b = timeAxis:getBlickFromSeconds(maxTime + amount)
else
newStartTime_b, newEndTime_b = minTime_b + shift, maxTime_b + shift
end
startval = am:get(newStartTime_b - 1)
endval = am:get(newEndTime_b + 1)
am:remove(newStartTime_b, newEndTime_b)
if amount < 0 or (amount > 0 and newStartTime_b > maxTime_b) then
am:add(newStartTime_b - 1, startval)
end
if amount > 0 or (amount < 0 and newEndTime_b < minTime_b) then
am:add(newEndTime_b + 1, endval)
end
-- place points
for _, pt in ipairs(points) do
if timeConvert then
local onset = timeAxis:getSecondsFromBlick(pt[1])
am:add(timeAxis:getBlickFromSeconds(onset + amount), pt[2])
else
am:add(pt[1] + shift, pt[2])
end
end
end
end
function main()
process()
SV:finish()
end
copy_notes_and_parameters.lua
--[[
Synthesizer V Studio Pro Script
Copy notes with parameters. MIT License.
The script copies a group of selected notes to the destination along with
corresponding parameters. It will wait for the user to navigate towards the
destination, and does not incorporate traditional copy-paste operations.
Copyright 2022 RigoLigo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
]]
function getClientInfo()
return {
name = SV:T("Copy Notes and Parameters"),
category = "Pitches",
author = "RigoLigo",
versionNumber = 2
}
end
function getTranslations(langCode)
if langCode == "zh-cn" then
return {
{"Copy Notes and Parameters", "带参数复制音符"},
{"No selection", "没有选择音符"},
{"You must select the notes to be copied.", "必须先选中要复制的音符。"},
{"Begin copying", "开始复制"},
{"Confirm copying", "确认复制"},
{"Timeout", "等待时间"},
{" secs", " 秒"},
{"Script will now wait for you to move your playhead and track selection to the desired destination.\\n"..
"Select a timeout, and click OK to continue. Click Cancel to abort.\\n\\n"..
"Note: Timeout of 0 will activte copying immediately.",
"脚本会等你把播放头和选定的轨道移动到粘贴位置。\\n请选择等待时长,单击“确定”继续。单击“取消”中止。\\n\\n"..
"注意:等待时间为0时,会立即触发复制操作。"},
{"Are you now ready for copying? Click Yes to confirm copying.\\n\\n"..
"Otherwise, select a timeout, and click No to continue waiting.\\n"..
"Click Cancel to abort.",
"准备好复制了吗?单击“是”开始复制。否则,可再选择一次等待时间,然后单击“否”继续等待。\\n单击“取消”中止。"}
}
end
end
function main()
if checkHasSelection() then
useSelectionBounds:exec()
else
SV:showMessageBox(SV:T("No selection"), SV:T("You must select the notes to be copied."))
end
end
function dbgInfo(v)
local x = "String form: <<" .. tostring(v) .. ">>\n"..
"Type: <<" .. type(v) .. ">>"
SV:showMessageBox("Debug", x);
end
function checkHasSelection()
return SV:getMainEditor():getSelection():hasSelectedContent()
end
function getPlayheadTimeBlick()
return SV:getProject():getTimeAxis():getBlickFromSeconds(SV:getPlayback():getPlayhead())
end
function safeOp2(a, b, op)
if a == nil then
if b == nil then
return 0
else
return b
end
elseif b == nil then
return a
else
return op(a, b)
end
end
useSelectionBounds = {
exec = function()
local sel = SV:getMainEditor():getSelection()
selNotes = sel:getSelectedNotes()
selGroups = sel:getSelectedGroups()
-- Get begin and end time of the section safely
local beginTime1 = (#selNotes ~= 0) and selNotes[1]:getOnset() or nil
local beginTime2 = (#selGroups ~= 0)and selGroups[1]:getOnset()or nil
beginTime = safeOp2(beginTime1, beginTime2, math.min)
local endTime1 = (#selNotes ~= 0) and selNotes[#selNotes]:getEnd() or nil
local endTime2 = (#selGroups ~= 0)and selGroups[#selGroups]:getEnd()or nil
endTime = safeOp2(endTime1, endTime2, math.max)
-- Store the group UUID
fromGroup = SV:getMainEditor():getCurrentGroup():getTarget()
groupId = fromGroup:getUUID()
useSelectionBounds:beginWaitCopy()
end,
beginWaitCopy = function()
local firstWaitForm = {
title = SV:T("Begin copying"),
message = SV:T("Script will now wait for you to move your playhead and track selection to the desired destination.\n"..
"Select a timeout, and click OK to continue. Click Cancel to abort.\n\n"..
"Note: Timeout of 0 will activte copying immediately."),
buttons = "OkCancel",
widgets = {
{
name = "timeout", type = "Slider", label = SV:T("Timeout"),
format = "%.1f" .. SV:T(" secs"), minValue = 0, maxValue = 40, interval = 0.5, default = 10
}
}
}
local result = SV:showCustomDialog(firstWaitForm)
if result.status == true then
if result.answers.timeout == 0 then
useSelectionBounds:doCopy()
else
SV:setTimeout(result.answers.timeout * 1000, useSelectionBounds.confirmCopy)
end
else
SV:finish()
end
end,
confirmCopy = function()
local confirmForm = {
title = SV:T("Confirm copying"),
message = SV:T("Are you now ready for copying? Click Yes to confirm copying.\n\n"..
"Otherwise, select a timeout, and click No to continue waiting.\n"..
"Click Cancel to abort."),
buttons = "YesNoCancel",
widgets = {
{
name = "timeout", type = "Slider", label = SV:T("Timeout"),
format = "%.1f" .. SV:T(" secs"), minValue = 3, maxValue = 40, interval = 0.5, default = 10
}
}
}
local result = SV:showCustomDialog(confirmForm)
if result.status == "No" then
SV:setTimeout(result.answers.timeout * 1000, useSelectionBounds.confirmCopy)
elseif result.status == "Yes" then
useSelectionBounds:doCopy();
else
SV:finish()
end
end,
doCopy = function()
toGroup = SV:getMainEditor():getCurrentGroup():getTarget()
-- Snap to nearest
destTime = SV:getMainEditor():getNavigation():snap(getPlayheadTimeBlick())
-- Remember to minus the current group onset
timeOffset = destTime - beginTime - SV:getMainEditor():getCurrentGroup():getOnset()
useSelectionBounds:copyNotes()
useSelectionBounds:copyGroups()
useSelectionBounds:copyParams()
SV:finish()
end,
copyNotes = function()
for _, i in pairs(selNotes) do
local n = i:clone()
n:setOnset(n:getOnset() + timeOffset)
toGroup:addNote(n)
end
end,
copyGroups = function()
-- FIXME BUG Synthesizer V doens't provide an NoteGroupReference:setOnset API which is crucial for the operation
-- local toGroup = SV:getMainEditor():getCurrentGroup():getTarget()
--
-- -- Don't copy a group into a group, SynthV editor doesn't support this
-- if #toGroup ~= 0 and not toGroup:isMain() then
-- return
-- end
--
-- local toTrack = SV:getMainEditor():getCurrentTrack()
-- local timeOffset = getPlayheadTimeBlick() - beginTime
-- for k, v in pairs(selGroups) do
-- local n = v:clone()
-- n:setOnset(n:getOnset() + timeOffset)
-- toTrack:add(n)
-- end
end,
copyParams = function()
params = {
pitchDelta = 0,
vibratoEnv = 1,
loudness = 0,
tension = 0,
breathiness = 0,
voicing = 1,
gender = 0
}
for i, def in pairs(params) do
local p = fromGroup:getParameter(i)
local pts = p:getPoints(beginTime, endTime) -- Get points in range
-- If no points found?
if #pts == 0 then
-- Check value right at the head and tail and add exact points.
pts[1] = {beginTime, p:get(beginTime)}
pts[2] = {beginTime, p:get(endTime)}
end
-- Make the point at beginning and ending the exact values
pts[1][2] = p:get(pts[1][1])
pts[#pts][2] = p:get(pts[#pts][1])
-- Clear the destination area, extends 100 blinks for safety
local q = toGroup:getParameter(i)
q:remove(destTime - 100, destTime + (endTime - beginTime) + 100)
-- Add each one into destination
for _, j in pairs(pts) do
q:add(j[1] + timeOffset, j[2])
end
::NextParam::
end
end
}
End of post
I created a script to move n measures to the left or right, while preserving the settings and tempo of the original track. See : MoveTime
This script clones all note groups and recreates them at the offset time position (preserving all vocal parameters). It moves all notes outside of any group (main group) and also shifts all tempo markers.
This version is for the Synthesizer V 1. Another one is also available for V2 in my github repo.
Hi @jfa - wow thanks for this, it’s nearly exactly what I’m looking for. How do I select the whole project, ie all tracks to be shifted? At the moment it is only shifting the selected track, so by the time I shift all 4 tracks, the tempo has shifted 4 times.
It is not possible with this first version. Get the new one!
There : MoveTime V1
Wow - yes that works, thank you so much! ![]()