-
Notifications
You must be signed in to change notification settings - Fork 10
/
randomizeOnsets.lua
121 lines (102 loc) · 3.44 KB
/
randomizeOnsets.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
SCRIPT_TITLE = "RV Randomize Onsets"
local MIN_NOTE_LENGTH_B = SV:quarter2Blick(1/32)
local inputForm = {
title = SV:T("Randomize Onsets"),
message = SV:T("Randomly shifts onsets of selected or all notes in current group"),
buttons = "OkCancel",
widgets = {
{
name = "sl", type = "Slider",
label = "Amount of randomness (standard deviation in ms)",
format = "%3.0f",
minValue = 0,
maxValue = 50,
interval = 1,
default = 25
},
{
name = "tb", type = "TextBox",
label = "Random Seed (to have repeatable results)",
default = "0"
}
}
}
function getClientInfo()
return {
name = SV:T(SCRIPT_TITLE),
author = "[email protected]",
category = "Real Voice",
versionNumber = 1,
minEditorVersion = 65537
}
end
local function gaussian(mean, variance)
return math.sqrt(-2 * variance * math.log(math.random())) *
math.cos(2 * math.pi * math.random()) + mean
end
local function randInRange(mean, stdev, range)
local var = stdev^2
local x
repeat
x = gaussian(mean, var)
until x >= - range and x <= range
return x
end
function process()
local dlgResult = SV:showCustomDialog(inputForm)
if not dlgResult.status then return end -- cancel pressed
local stdev = dlgResult.answers.sl
local seed = tonumber(dlgResult.answers.tb) or 0
local timeAxis = SV:getProject():getTimeAxis()
local scope = SV:getMainEditor():getCurrentGroup()
local group = scope:getTarget()
math.randomseed(seed)
local notes = {} -- notes indexes
local noteCnt = group:getNumNotes()
if noteCnt == 0 then -- no notes
return
else
local selection = SV:getMainEditor():getSelection()
local selectedNotes = selection:getSelectedNotes()
if #selectedNotes == 0 then
for i = 1, noteCnt do
table.insert(notes, i)
end
else
table.sort(selectedNotes, function(noteA, noteB)
return noteA:getOnset() < noteB:getOnset()
end)
for _, n in ipairs(selectedNotes) do
table.insert(notes, n:getIndexInParent())
end
end
end
for _, i in ipairs(notes) do
local note = group:getNote(i)
local onset_b, nend_b = note:getOnset(), note:getEnd()
local shift = randInRange(0, stdev, stdev) / 1000 -- ms -> sec
local onset = timeAxis:getSecondsFromBlick(onset_b)
local new_onset_b = timeAxis:getBlickFromSeconds(onset + shift)
-- positive shift correction
if new_onset_b > onset_b and (nend_b - new_onset_b) < MIN_NOTE_LENGTH_B then
new_onset_b = nend_b - MIN_NOTE_LENGTH_B
end
local ni = note:getIndexInParent()
if ni > 1 then -- not first note
local p_note = group:getNote(ni - 1) -- previous note
local p_onset_b, p_nend_b = p_note:getOnset(), p_note:getEnd()
-- negative shift correction
if new_onset_b < onset_b and (new_onset_b - p_onset_b) < MIN_NOTE_LENGTH_B then
new_onset_b = p_onset_b + MIN_NOTE_LENGTH_B
end
if math.abs(onset_b - p_nend_b) < MIN_NOTE_LENGTH_B / 4 then -- 2 notes connected, change duration of previous
p_note:setDuration(new_onset_b - p_onset_b)
end
end
note:setTimeRange(new_onset_b, nend_b - new_onset_b)
end
end
function main()
process()
SV:finish()
end