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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
ardour {
["type"] = "EditorAction",
name = "Swing It (Rubberband)",
license = "MIT",
author = "Ardour Team",
description = [[
Create a 'swing feel' in selected regions.
Analyze beat-position from the selected audio regions,
then time-stretch the audio and move 8th notes back in
time while keeping 1/4 note beats in place.
(This script also servers as example for both VAMP
analysis as well as Rubberband region stretching.)
Kudos to Chris Cannam.
]]
}
function factory () return function ()
-- helper function --
-- there is currently no direct way to find the track
-- corresponding to a [selected] region
function find_track_for_region (region_id)
for route in Session:get_tracks ():iter () do
local track = route:to_track ()
local pl = track:playlist ()
if not pl:region_by_id (region_id):isnil () then
return track
end
end
assert (0) -- can't happen, region must be in a playlist
end
-- get Editor selection
-- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Editor
-- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
local sel = Editor:get_selection ()
-- Instantiate the QM BarBeat Tracker
-- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:LuaAPI:Vamp
-- http://vamp-plugins.org/plugin-doc/qm-vamp-plugins.html#qm-barbeattracker
local vamp = ARDOUR.LuaAPI.Vamp ("libardourvampplugins:qm-barbeattracker", Session:nominal_sample_rate ())
-- prepare undo operation
Session:begin_reversible_command ("Rubberband Regions")
local add_undo = false -- keep track if something has changed
-- for each selected region
-- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:RegionSelection
for r in sel.regions:regionlist ():iter () do
-- "r" is-a http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Region
-- test if it's an audio region
local ar = r:to_audioregion ()
if ar:isnil () then
goto next
end
-- create Rubberband stretcher
local rb = ARDOUR.LuaAPI.Rubberband (ar, false)
-- the rubberband-filter also implements the readable API to read from
-- the master-source of the given audio-region (ignoring any prior time-stretch
-- or pitch-shiting).
-- https://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Readable
local max_pos = rb:readable ():readable_length ()
-- prepare table to hold analysis results
-- the beat-map is a table holding audio-sample positions:
-- [from] = to
local beat_map = {}
local prev_beat = 0
local pdialog = LuaDialog.ProgressWindow ("Rubberband", true)
-- callback to handle Vamp-Plugin analysis results
function vamp_callback (_, pos)
return pdialog:progress (pos / max_pos, "Analyzing")
end
function rb_progress (_, pos)
return pdialog:progress (pos / max_pos, "Stretching")
end
-- run VAMP plugin, analyze the first channel of the audio-region
vamp:analyze (rb:readable (), 0, vamp_callback)
-- getRemainingFeatures returns a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:FeatureSet
-- get the first output. here: Beats, estimated beat locations & beat-number
-- "fl" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:FeatureList
local fl = vamp:plugin ():getRemainingFeatures ():at (0)
local beatcount = 0
-- iterate over returned features
for f in fl:iter () do
-- "f" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:Feature
local fn = Vamp.RealTime.realTime2Frame (f.timestamp, Session:nominal_sample_rate ())
beat_map[fn] = fn -- keep beats (1/4 notes) unchanged
if prev_beat > 0 then
-- move the half beats (1/8th) back
local diff = (fn - prev_beat) / 2
beat_map[fn - diff] = fn - diff + diff / 3 -- moderate swing 2:1 (triplet)
--beat_map[fn - diff] = fn - diff + diff / 2 -- hard swing 3:1 (dotted 8th)
beatcount = beatcount + 1
end
prev_beat = fn
end
-- reset the plugin (prepare for next iteration)
vamp:reset ()
if pdialog:canceled () then goto out end
-- skip regions shorter than a bar
if beatcount < 8 then
pdialog:done ()
goto next
end
-- now stretch the region
rb:set_strech_and_pitch (1, 1)
rb:set_mapping (beat_map)
local nar = rb:process (rb_progress)
if pdialog:canceled () then goto out end
-- hide modal progress dialog and destroy it
pdialog:done ()
pdialog = nil
-- replace region
if not nar:isnil () then
print ("new audio region: ", nar:name (), nar:length ())
local track = find_track_for_region (r:to_stateful ():id ())
local playlist = track:playlist ()
playlist:to_stateful ():clear_changes () -- prepare undo
playlist:remove_region (r)
playlist:add_region (nar, r:position (), 1, false, 0, 0, false)
-- create a diff of the performed work, add it to the session's undo stack
-- and check if it is not empty
if not Session:add_stateful_diff_command (playlist:to_statefuldestructible ()):empty () then
add_undo = true
end
end
::next::
end
::out::
-- all done, commit the combined Undo Operation
if add_undo then
-- the 'nil' Command here mean to use the collected diffs added above
Session:commit_reversible_command (nil)
else
Session:abort_reversible_command ()
end
vamp = nil
collectgarbage ()
end end
|