1ardour { 2 ["type"] = "EditorAction", 3 name = "Swing It (Rubberband)", 4 license = "MIT", 5 author = "Ardour Team", 6description = [[ 7Create a 'swing feel' in selected regions. 8 9The beat position of selected audio regions is analyzed, 10then the audio is time-stretched, moving 8th notes back in 11time while keeping 1/4-note beats in place to produce 12a rhythmic swing style. 13 14(This script also servers as example for both VAMP 15analysis as well as Rubberband region stretching.) 16 17Kudos to Chris Cannam. 18]] 19} 20 21function factory () return function () 22 23 -- helper function -- 24 -- there is currently no direct way to find the track 25 -- corresponding to a [selected] region 26 function find_track_for_region (region_id) 27 for route in Session:get_tracks ():iter () do 28 local track = route:to_track () 29 local pl = track:playlist () 30 if not pl:region_by_id (region_id):isnil () then 31 return track 32 end 33 end 34 assert (0) -- can't happen, region must be in a playlist 35 end 36 37 -- get Editor selection 38 -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Editor 39 -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection 40 local sel = Editor:get_selection () 41 42 -- Instantiate the QM BarBeat Tracker 43 -- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:LuaAPI:Vamp 44 -- http://vamp-plugins.org/plugin-doc/qm-vamp-plugins.html#qm-barbeattracker 45 local vamp = ARDOUR.LuaAPI.Vamp ("libardourvampplugins:qm-barbeattracker", Session:nominal_sample_rate ()) 46 47 -- prepare undo operation 48 Session:begin_reversible_command ("Rubberband Regions") 49 local add_undo = false -- keep track if something has changed 50 51 -- for each selected region 52 -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:RegionSelection 53 for r in sel.regions:regionlist ():iter () do 54 -- "r" is-a http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Region 55 56 -- test if it's an audio region 57 local ar = r:to_audioregion () 58 if ar:isnil () then 59 goto next 60 end 61 62 -- create Rubberband stretcher 63 local rb = ARDOUR.LuaAPI.Rubberband (ar, false) 64 65 -- the rubberband-filter also implements the readable API. 66 -- https://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Readable 67 -- This allows to read from the master-source of the given audio-region. 68 -- Any prior time-stretch or pitch-shift are ignored when reading, however 69 -- processing retains the previous settings 70 local max_pos = rb:readable ():readable_length () 71 72 -- prepare table to hold analysis results 73 -- the beat-map is a table holding audio-sample positions: 74 -- [from] = to 75 local beat_map = {} 76 local prev_beat = 0 77 78 -- construct a progress-dialog with cancle button 79 local pdialog = LuaDialog.ProgressWindow ("Rubberband", true) 80 -- progress dialog callbacks 81 function vamp_callback (_, pos) 82 return pdialog:progress (pos / max_pos, "Analyzing") 83 end 84 function rb_progress (_, pos) 85 return pdialog:progress (pos / max_pos, "Stretching") 86 end 87 88 -- run VAMP plugin, analyze the first channel of the audio-region 89 vamp:analyze (rb:readable (), 0, vamp_callback) 90 91 -- getRemainingFeatures returns a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:FeatureSet 92 -- get the first output. here: Beats, estimated beat locations & beat-number 93 -- "fl" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:FeatureList 94 local fl = vamp:plugin ():getRemainingFeatures ():at (0) 95 local beatcount = 0 96 -- iterate over returned features 97 for f in fl:iter () do 98 -- "f" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:Feature 99 local fn = Vamp.RealTime.realTime2Frame (f.timestamp, Session:nominal_sample_rate ()) 100 beat_map[fn] = fn -- keep beats (1/4 notes) unchanged 101 if prev_beat > 0 then 102 -- move the half beats (1/8th) back 103 local diff = (fn - prev_beat) / 2 104 beat_map[fn - diff] = fn - diff + diff / 3 -- moderate swing 2:1 (triplet) 105 --beat_map[fn - diff] = fn - diff + diff / 2 -- hard swing 3:1 (dotted 8th) 106 beatcount = beatcount + 1 107 end 108 prev_beat = fn 109 end 110 -- reset the plugin state (prepare for next iteration) 111 vamp:reset () 112 113 if pdialog:canceled () then goto out end 114 115 -- skip regions shorter than a bar 116 if beatcount < 8 then 117 pdialog:done () 118 goto next 119 end 120 121 -- configure rubberband stretch tool 122 rb:set_strech_and_pitch (1, 1) -- no overall stretching, no pitch-shift 123 rb:set_mapping (beat_map) -- apply beat-map from/to 124 125 -- now stretch the region 126 local nar = rb:process (rb_progress) 127 128 if pdialog:canceled () then goto out end 129 130 -- hide modal progress dialog and destroy it 131 pdialog:done () 132 pdialog = nil 133 134 -- replace region 135 if not nar:isnil () then 136 print ("new audio region: ", nar:name (), nar:length ()) 137 local track = find_track_for_region (r:to_stateful ():id ()) 138 local playlist = track:playlist () 139 playlist:to_stateful ():clear_changes () -- prepare undo 140 playlist:remove_region (r) 141 playlist:add_region (nar, r:position (), 1, false, 0, 0, false) 142 -- create a diff of the performed work, add it to the session's undo stack 143 -- and check if it is not empty 144 if not Session:add_stateful_diff_command (playlist:to_statefuldestructible ()):empty () then 145 add_undo = true 146 end 147 end 148 149 ::next:: 150 end 151 152 ::out:: 153 154 -- all done, commit the combined Undo Operation 155 if add_undo then 156 -- the 'nil' Command here mean to use the collected diffs added above 157 Session:commit_reversible_command (nil) 158 else 159 Session:abort_reversible_command () 160 end 161end end 162 163 164function icon (params) return function (ctx, width, height, fg) 165 local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil(width * .7) .. "px") 166 txt:set_text ("\u{266b}\u{266a}") -- 8th note symbols 167 local tw, th = txt:get_pixel_size () 168 ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg)) 169 ctx:move_to (.5 * (width - tw), .5 * (height - th)) 170 txt:show_in_cairo_context (ctx) 171end end 172