summaryrefslogtreecommitdiff
path: root/share
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2020-02-23 20:48:02 +0100
committerRobin Gareus <robin@gareus.org>2020-02-23 20:48:02 +0100
commit180843f9bd28b191c7404245ba0a121107992511 (patch)
treec60312dc09f76c2f55ba2383245c427e15c38dea /share
parentbf649cd68ad46c34a656700aa6cb89416d648c64 (diff)
Also move Lua scripts to share subfolder
Diffstat (limited to 'share')
-rw-r--r--share/scripts/HiAndLowPass.lua396
-rw-r--r--share/scripts/README29
-rw-r--r--share/scripts/__convolv.lua59
-rw-r--r--share/scripts/__fluidsynth1.lua51
-rw-r--r--share/scripts/__plugin_modulation.lua46
-rw-r--r--share/scripts/__readable.lua47
-rw-r--r--share/scripts/_amp1.lua47
-rw-r--r--share/scripts/_amp2.lua51
-rw-r--r--share/scripts/_amp3.lua44
-rw-r--r--share/scripts/_biquad_filter.lua280
-rw-r--r--share/scripts/_calc_dsp_statistics.lua27
-rw-r--r--share/scripts/_cron.lua37
-rw-r--r--share/scripts/_dialog_test.lua95
-rw-r--r--share/scripts/_dsp_plugin_communication.lua78
-rw-r--r--share/scripts/_dump_latency.lua77
-rw-r--r--share/scripts/_dump_midiregion.lua20
-rw-r--r--share/scripts/_dump_playlists.lua31
-rw-r--r--share/scripts/_export_plugins_on_save.lua44
-rw-r--r--share/scripts/_export_tracks.lua10
-rw-r--r--share/scripts/_fan_out_instrument.lua69
-rw-r--r--share/scripts/_find_nonzero_sample.lua74
-rw-r--r--share/scripts/_fir.lua36
-rw-r--r--share/scripts/_hook_test.lua41
-rw-r--r--share/scripts/_insert_region_gaps.lua62
-rw-r--r--share/scripts/_midi_lfo.lua74
-rw-r--r--share/scripts/_midi_rec_start.lua37
-rw-r--r--share/scripts/_midi_rewrite.lua34
-rw-r--r--share/scripts/_midifilter.lua39
-rw-r--r--share/scripts/_midigenerator.lua48
-rw-r--r--share/scripts/_midigenerator2.lua34
-rw-r--r--share/scripts/_osc_hook_example.lua52
-rw-r--r--share/scripts/_plot_graph.lua24
-rw-r--r--share/scripts/_pong.lua260
-rw-r--r--share/scripts/_rawmidi.lua110
-rw-r--r--share/scripts/_region_transients.lua16
-rw-r--r--share/scripts/_remember_file.lua37
-rw-r--r--share/scripts/_rewind.lua12
-rw-r--r--share/scripts/_rgh_midi_track_trick.lua81
-rw-r--r--share/scripts/_route_template_generic_audio.lua119
-rw-r--r--share/scripts/_route_template_generic_midi.lua78
-rw-r--r--share/scripts/_rubberband_swing.lua175
-rw-r--r--share/scripts/_session_load_hook.lua32
-rw-r--r--share/scripts/_session_test.lua34
-rw-r--r--share/scripts/_smash.lua31
-rw-r--r--share/scripts/_sort_tracks_by_name.lua36
-rw-r--r--share/scripts/_spike_synth.lua37
-rw-r--r--share/scripts/_split_benchmark.lua58
-rw-r--r--share/scripts/_stereo_to_mono.lua57
-rw-r--r--share/scripts/_system_exec.lua20
-rw-r--r--share/scripts/_tempo_map_dump.lua14
-rw-r--r--share/scripts/_toggle_monitor_section.lua10
-rw-r--r--share/scripts/_tx_raw_midi_from_file.lua128
-rw-r--r--share/scripts/_vamp_example.lua63
-rw-r--r--share/scripts/_vamp_note_example.lua68
-rw-r--r--share/scripts/_vamp_onset_example.lua83
-rw-r--r--share/scripts/_vamp_tempomap_example.lua85
-rw-r--r--share/scripts/_varispeed_callback.lua32
-rw-r--r--share/scripts/_vca_slave_assign.lua73
-rw-r--r--share/scripts/a_slow_mute.lua66
-rw-r--r--share/scripts/access_action.lua37
-rw-r--r--share/scripts/add_filters.lua54
-rw-r--r--share/scripts/addscopes.lua75
-rw-r--r--share/scripts/amp4.lua119
-rw-r--r--share/scripts/bounce_replace.lua101
-rw-r--r--share/scripts/bypass_all_plugins.lua22
-rw-r--r--share/scripts/create_drum_tracks.lua73
-rw-r--r--share/scripts/delete_xrun_markers.lua40
-rw-r--r--share/scripts/export_mp4chaps.lua78
-rw-r--r--share/scripts/faders_to_trims.lua72
-rw-r--r--share/scripts/jump_to_marker.lua67
-rw-r--r--share/scripts/lfo_automation.lua189
-rw-r--r--share/scripts/list_plugins.lua50
-rw-r--r--share/scripts/ltc_reader.lua82
-rw-r--r--share/scripts/meter_tap.lua42
-rw-r--r--share/scripts/midi_cc_to_automation.lua130
-rw-r--r--share/scripts/midi_remap.lua97
-rw-r--r--share/scripts/midimon.lua213
-rw-r--r--share/scripts/mixer_screenshot.lua46
-rw-r--r--share/scripts/mixer_settings_recall.lua470
-rw-r--r--share/scripts/mixer_settings_store.lua383
-rw-r--r--share/scripts/mute_all_tracks.lua21
-rw-r--r--share/scripts/new_playlist.lua17
-rw-r--r--share/scripts/noisegen.lua111
-rw-r--r--share/scripts/normalize_all_tracks.lua58
-rw-r--r--share/scripts/notch_bank.lua125
-rw-r--r--share/scripts/periodic_backup.lua47
-rw-r--r--share/scripts/post_export_save_hook.lua26
-rw-r--r--share/scripts/preare_record_example.lua82
-rw-r--r--share/scripts/remove_unknown_procs.lua44
-rw-r--r--share/scripts/reset_mixer.lua249
-rw-r--r--share/scripts/s_chanmap.lua34
-rw-r--r--share/scripts/s_fader_automation.lua58
-rw-r--r--share/scripts/s_foreach_track.lua10
-rw-r--r--share/scripts/s_group_color.lua11
-rw-r--r--share/scripts/s_import_files.lua14
-rw-r--r--share/scripts/s_plugin_automation.lua43
-rw-r--r--share/scripts/s_plugin_reorder.lua23
-rw-r--r--share/scripts/s_pluginutils.lua57
-rw-r--r--share/scripts/s_portengine.lua35
-rw-r--r--share/scripts/s_region_gain.lua82
-rw-r--r--share/scripts/s_region_gain2.lua63
-rw-r--r--share/scripts/s_replaceplugin.lua11
-rw-r--r--share/scripts/s_selection.lua60
-rw-r--r--share/scripts/s_showhide_track.lua23
-rw-r--r--share/scripts/s_thin_automation.lua47
-rw-r--r--share/scripts/s_timecode.lua21
-rw-r--r--share/scripts/s_track_props.lua48
-rw-r--r--share/scripts/s_vamp_plugin_index.lua45
-rw-r--r--share/scripts/s_whoami.lua22
-rw-r--r--share/scripts/scope.lua229
-rw-r--r--share/scripts/select_every_2nd_region.lua61
-rw-r--r--share/scripts/send_to_bus.lua41
-rw-r--r--share/scripts/session_template_advanced.lua59
-rw-r--r--share/scripts/session_template_record.lua60
-rw-r--r--share/scripts/set_automation_mode.lua83
-rw-r--r--share/scripts/singen.lua94
-rw-r--r--share/scripts/spectrogram.lua363
-rw-r--r--share/scripts/split_all_markers.lua122
-rw-r--r--share/scripts/stop_at_marker.lua49
-rw-r--r--share/scripts/store_recall_mixer.lua575
-rw-r--r--share/scripts/synth1.lua100
-rw-r--r--share/scripts/tomsloop.lua315
-rw-r--r--share/scripts/track_organizer.lua100
-rw-r--r--share/scripts/transparent_regions.lua28
-rw-r--r--share/scripts/vamp_audio_to_midi.lua112
-rw-r--r--share/scripts/voice_activate.lua53
-rw-r--r--share/scripts/wscript16
127 files changed, 10295 insertions, 0 deletions
diff --git a/share/scripts/HiAndLowPass.lua b/share/scripts/HiAndLowPass.lua
new file mode 100644
index 0000000000..75704d9701
--- /dev/null
+++ b/share/scripts/HiAndLowPass.lua
@@ -0,0 +1,396 @@
+ardour {
+ ["type"] = "dsp",
+ name = "a-High/Low Pass Filter",
+ category = "Filter",
+ license = "GPLv2",
+ author = "Ardour Team",
+ description = [[High and Low Pass Filter with de-zipped controls, written in Ardour-Lua]]
+}
+
+function dsp_ioconfig ()
+ return
+ {
+ -- allow any number of I/O as long as port-count matches
+ { audio_in = -1, audio_out = -1},
+ }
+end
+
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "High Pass Steepness", min = 0, max = 4, default = 1, enum = true, scalepoints =
+ {
+ ["Off"] = 0,
+ ["12dB/oct"] = 1,
+ ["24dB/oct"] = 2,
+ ["36dB/oct"] = 3,
+ ["48dB/oct"] = 4,
+ }
+ },
+ { ["type"] = "input", name = "High Pass Cut off frequency", min = 5, max = 20000, default = 100, unit="Hz", logarithmic = true },
+ { ["type"] = "input", name = "High Pass Resonance", min = 0.1, max = 6, default = .707, logarithmic = true },
+
+ { ["type"] = "input", name = "Low Pass Steepness", min = 0, max = 4, default = 1, enum = true, scalepoints =
+ {
+ ["Off"] = 0,
+ ["12dB/oct"] = 1,
+ ["24dB/oct"] = 2,
+ ["36dB/oct"] = 3,
+ ["48dB/oct"] = 4,
+ }
+ },
+ { ["type"] = "input", name = "Low Pass Cut off frequency", min = 20, max = 20000, default = 18000, unit="Hz", logarithmic = true },
+ { ["type"] = "input", name = "Low Pass Resonance", min = 0.1, max = 6, default = .707, logarithmic = true },
+ }
+end
+
+-- these globals are *not* shared between DSP and UI
+local hp = {} -- the biquad high-pass filter instances (DSP)
+local lp = {} -- the biquad high-pass filter instances (DSP)
+local filt = nil -- the biquad filter instance (GUI, response)
+local cur = {0, 0, 0, 0, 0, 0} -- current parameters
+local lpf = 0.03 -- parameter low-pass filter time-constant
+local chn = 0 -- channel/filter count
+local lpf_chunk = 0 -- chunk size for audio processing when interpolating parameters
+local max_freq = 20000
+
+local mem = nil -- memory x-fade buffer
+
+function dsp_init (rate)
+ -- allocate some mix-buffer
+ mem = ARDOUR.DSP.DspShm (8192)
+
+ -- max allowed cut-off frequency
+ max_freq = .499 * rate
+
+ -- create a table of objects to share with the GUI
+ local tbl = {}
+ tbl['samplerate'] = rate
+ tbl['max_freq'] = max_freq
+ self:table ():set (tbl)
+
+
+ -- Parameter smoothing: we want to filter out parameter changes that are
+ -- faster than 15Hz, and interpolate between parameter values.
+ -- For performance reasons, we want to ensure that two consecutive values
+ -- of the interpolated "steepness" are less that 1 apart. By choosing the
+ -- interpolation chunk size to be 64 in most cases, but 32 if the rate is
+ -- strictly less than 22kHz (there's only 8kHz in standard rates), we can
+ -- ensure that steepness interpolation will never change the parameter by
+ -- more than ~0.86.
+ lpf_chunk = 64
+ if rate < 22000 then lpf_chunk = 32 end
+ -- We apply a discrete version of the standard RC low-pass, with a cutoff
+ -- frequency of 15Hz. For more information about the underlying math, see
+ -- https://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization
+ -- (here Δt is lpf_chunk / rate)
+ local R = 2 * math.pi * lpf_chunk * 15 -- Hz
+ lpf = R / (R + rate)
+end
+
+function dsp_configure (ins, outs)
+ assert (ins:n_audio () == outs:n_audio ())
+ local tbl = self:table ():get () -- get shared memory table
+
+ chn = ins:n_audio ()
+ cur = {0, 0, 0, 0, 0, 0}
+
+ hp = {}
+ lp = {}
+
+ collectgarbage ()
+
+ for c = 1, chn do
+ hp[c] = {}
+ lp[c] = {}
+ -- initialize filters
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP:Biquad
+
+ -- A different Biquad is needed for each pass and channel because they
+ -- remember the last two samples seen during the last call of Biquad:run().
+ -- For continuity these have to come from the previous audio chunk of the
+ -- same channel and pass and would be clobbered if the same Biquad was
+ -- called several times by cycle.
+ for k = 1,4 do
+ hp[c][k] = ARDOUR.DSP.Biquad (tbl['samplerate'])
+ lp[c][k] = ARDOUR.DSP.Biquad (tbl['samplerate'])
+ end
+ end
+end
+
+function santize_params (ctrl)
+ -- don't allow manual cross-fades. enforce enums
+ ctrl[1] = math.floor(ctrl[1] + .5)
+ ctrl[4] = math.floor(ctrl[4] + .5)
+
+ -- high pass, clamp range
+ ctrl[2] = math.min (max_freq, math.max (5, ctrl[2]))
+ ctrl[3] = math.min (6, math.max (0.1, ctrl[3]))
+
+ -- low pass, clamp range
+ ctrl[5] = math.min (max_freq, math.max (20, ctrl[5]))
+ ctrl[6] = math.min (6, math.max (0.1, ctrl[6]))
+ return ctrl
+end
+
+-- helper functions for parameter interpolation
+function param_changed (ctrl)
+ for p = 1,6 do
+ if ctrl[p] ~= cur[p] then
+ return true
+ end
+ end
+ return false
+end
+
+function low_pass_filter_param (old, new, limit)
+ if math.abs (old - new) < limit then
+ return new
+ else
+ return old + lpf * (new - old)
+ end
+end
+
+-- apply parameters, re-compute filter coefficients if needed
+function apply_params (ctrl)
+ if not param_changed (ctrl) then
+ return
+ end
+
+ -- low-pass filter ctrl parameter values, smooth transition
+ cur[1] = low_pass_filter_param (cur[1], ctrl[1], 0.05) -- HP order x-fade
+ cur[2] = low_pass_filter_param (cur[2], ctrl[2], 1.0) -- HP freq/Hz
+ cur[3] = low_pass_filter_param (cur[3], ctrl[3], 0.01) -- HP quality
+ cur[4] = low_pass_filter_param (cur[4], ctrl[4], 0.05) -- LP order x-fade
+ cur[5] = low_pass_filter_param (cur[5], ctrl[5], 1.0) -- LP freq/Hz
+ cur[6] = low_pass_filter_param (cur[6], ctrl[6], 0.01) -- LP quality
+
+ for c = 1, chn do
+ for k = 1,4 do
+ hp[c][k]:compute (ARDOUR.DSP.BiquadType.HighPass, cur[2], cur[3], 0)
+ lp[c][k]:compute (ARDOUR.DSP.BiquadType.LowPass, cur[5], cur[6], 0)
+ end
+ end
+end
+
+
+-- the actual DSP callback
+function dsp_run (ins, outs, n_samples)
+ assert (n_samples <= 8192)
+ assert (#ins == chn)
+ local ctrl = santize_params (CtrlPorts:array ())
+
+ local changed = false
+ local siz = n_samples
+ local off = 0
+
+ -- if a parameter was changed, process at most lpf_chunk samples
+ -- at a time and interpolate parameters until the current settings
+ -- match the target values
+ if param_changed (ctrl) then
+ changed = true
+ siz = lpf_chunk
+ end
+
+ while n_samples > 0 do
+ if changed then apply_params (ctrl) end
+ if siz > n_samples then siz = n_samples end
+
+ local ho = math.floor(cur[1])
+ local lo = math.floor(cur[4])
+
+ -- process all channels
+ for c = 1, #ins do
+
+ -- High Pass
+ local xfade = cur[1] - ho
+
+ -- prepare scratch memory
+ ARDOUR.DSP.copy_vector (mem:to_float (off), ins[c]:offset (off), siz)
+
+ -- run at least |ho| biquads...
+ for k = 1,ho do
+ hp[c][k]:run (mem:to_float (off), siz)
+ end
+ ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
+
+ -- mix the output of |ho| biquads (with weight |1-xfade|)
+ -- with the output of |ho+1| biquads (with weight |xfade|)
+ if xfade > 0 then
+ ARDOUR.DSP.apply_gain_to_buffer (outs[c]:offset (off), siz, 1 - xfade)
+ hp[c][ho+1]:run (mem:to_float (off), siz)
+ ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
+ -- also run the next biquad because it needs to have the correct state
+ -- in case it start affecting the next chunck of output. Higher order
+ -- ones are guaranteed not to be needed for the next run because the
+ -- interpolated order won't increase more than 0.86 in one step thanks
+ -- to the choice of the value of |lpf|.
+ if ho + 2 <= 4 then hp[c][ho+2]:run (mem:to_float (off), siz) end
+ elseif ho + 1 <= 4 then
+ -- run the next biquad in case it is used next chunk
+ hp[c][ho+1]:run (mem:to_float (off), siz)
+ end
+
+ -- Low Pass
+ xfade = cur[4] - lo
+
+ -- prepare scratch memory (from high pass output)
+ ARDOUR.DSP.copy_vector (mem:to_float (off), outs[c]:offset (off), siz)
+
+ -- run at least |lo| biquads...
+ for k = 1,lo do
+ lp[c][k]:run (mem:to_float (off), siz)
+ end
+ ARDOUR.DSP.copy_vector (outs[c]:offset (off), mem:to_float (off), siz)
+
+ -- mix the output of |lo| biquads (with weight |1-xfade|)
+ -- with the output of |lo+1| biquads (with weight |xfade|)
+ if xfade > 0 then
+ ARDOUR.DSP.apply_gain_to_buffer (outs[c]:offset (off), siz, 1 - xfade)
+ lp[c][lo+1]:run (mem:to_float (off), siz)
+ ARDOUR.DSP.mix_buffers_with_gain (outs[c]:offset (off), mem:to_float (off), siz, xfade)
+ -- also run the next biquad in case it start affecting the next
+ -- chunck of output.
+ if lo + 2 <= 4 then lp[c][lo+2]:run (mem:to_float (off), siz) end
+ elseif lo + 1 <= 4 then
+ -- run the next biquad in case it is used next chunk
+ lp[c][lo+1]:run (mem:to_float (off), siz)
+ end
+
+ end
+
+ n_samples = n_samples - siz
+ off = off + siz
+ end
+
+ if changed then
+ -- notify display
+ self:queue_draw ()
+ end
+end
+
+
+-------------------------------------------------------------------------------
+--- inline display
+
+function round (n)
+ return math.floor (n + .5)
+end
+
+function freq_at_x (x, w)
+ -- frequency in Hz at given x-axis pixel
+ return 20 * 1000 ^ (x / w)
+end
+
+function x_at_freq (f, w)
+ -- x-axis pixel for given frequency, power-scale
+ return w * math.log (f / 20.0) / math.log (1000.0)
+end
+
+function db_to_y (db, h)
+ -- y-axis gain mapping
+ if db < -60 then db = -60 end
+ if db > 12 then db = 12 end
+ return -.5 + round (0.2 * h) - h * db / 60
+end
+
+function grid_db (ctx, w, h, db)
+ -- draw horizontal grid line
+ -- note that a cairo pixel at Y spans [Y - 0.5 to Y + 0.5]
+ local y = -.5 + round (db_to_y (db, h))
+ ctx:move_to (0, y)
+ ctx:line_to (w, y)
+ ctx:stroke ()
+end
+
+function grid_freq (ctx, w, h, f)
+ -- draw vertical grid line
+ local x = -.5 + round (x_at_freq (f, w))
+ ctx:move_to (x, 0)
+ ctx:line_to (x, h)
+ ctx:stroke ()
+end
+
+function response (ho, lo, f)
+ -- calculate transfer function response for given
+ -- hi/po pass order at given frequency [Hz]
+ local db = ho * filt['hp']:dB_at_freq (f)
+ return db + lo * filt['lp']:dB_at_freq (f)
+end
+
+function render_inline (ctx, w, max_h)
+ if not filt then
+ local tbl = self:table ():get () -- get shared memory table
+ -- instantiate filter (to calculate the transfer function's response)
+ filt = {}
+ filt['hp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
+ filt['lp'] = ARDOUR.DSP.Biquad (tbl['samplerate'])
+ max_freq = tbl['max_freq']
+ end
+
+ local ctrl = santize_params (CtrlPorts:array ())
+ -- set filter coefficients if they have changed
+ if param_changed (ctrl) then
+ for k = 1,6 do cur[k] = ctrl[k] end
+ filt['hp']:compute (ARDOUR.DSP.BiquadType.HighPass, cur[2], cur[3], 0)
+ filt['lp']:compute (ARDOUR.DSP.BiquadType.LowPass, cur[5], cur[6], 0)
+ end
+
+ -- calc height of inline display
+ local h = 1 | math.ceil (w * 9 / 16) -- use 16:9 aspect, odd number of y pixels
+ if (h > max_h) then h = max_h end -- but at most max-height
+
+ -- ctx is a http://cairographics.org/ context
+ -- http://manual.ardour.org/lua-scripting/class_reference/#Cairo:Context
+
+ -- clear background
+ ctx:rectangle (0, 0, w, h)
+ ctx:set_source_rgba (.2, .2, .2, 1.0)
+ ctx:fill ()
+ ctx:rectangle (0, 0, w, h)
+ ctx:clip ()
+
+ -- set line width: 1px
+ ctx:set_line_width (1.0)
+
+ -- draw grid
+ local dash3 = C.DoubleVector ()
+ local dash2 = C.DoubleVector ()
+ dash2:add ({1, 2})
+ dash3:add ({1, 3})
+ ctx:set_dash (dash2, 2) -- dotted line: 1 pixel 2 space
+ ctx:set_source_rgba (.5, .5, .5, .8)
+ grid_db (ctx, w, h, 0)
+ ctx:set_dash (dash3, 2) -- dashed line: 1 pixel 3 space
+ ctx:set_source_rgba (.5, .5, .5, .5)
+ grid_db (ctx, w, h, -12)
+ grid_db (ctx, w, h, -24)
+ grid_db (ctx, w, h, -36)
+ grid_freq (ctx, w, h, 100)
+ grid_freq (ctx, w, h, 1000)
+ grid_freq (ctx, w, h, 10000)
+ ctx:unset_dash ()
+
+ -- draw transfer function line
+ local ho = math.floor(cur[1])
+ local lo = math.floor(cur[4])
+
+ ctx:set_source_rgba (.8, .8, .8, 1.0)
+ ctx:move_to (-.5, db_to_y (response(ho, lo, freq_at_x (0, w)), h))
+ for x = 1,w do
+ local db = response(ho, lo, freq_at_x (x, w))
+ ctx:line_to (-.5 + x, db_to_y (db, h))
+ end
+ -- stoke a line, keep the path
+ ctx:stroke_preserve ()
+
+ -- fill area to zero under the curve
+ ctx:line_to (w, -.5 + round (db_to_y (0, h)))
+ ctx:line_to (0, -.5 + round (db_to_y (0, h)))
+ ctx:close_path ()
+ ctx:set_source_rgba (.5, .5, .5, .5)
+ ctx:fill ()
+
+ return {w, h}
+end
diff --git a/share/scripts/README b/share/scripts/README
new file mode 100644
index 0000000000..2527498ee3
--- /dev/null
+++ b/share/scripts/README
@@ -0,0 +1,29 @@
+Ardour Lua Scripts
+==================
+
+https://manual.ardour.org/lua-scripting/
+
+For upstream script addition, please file a pull-request at
+https://github.com/Ardour/ardour/tree/master/scripts
+
+Script Naming conventions:
+
+^_
+ A script filename with a leading underscore indicates an example script.
+ These scripts are only available from ardour's git repository and not
+ installed nor included with binary bundles.
+
+^__
+ Scripts with a filename starting with two underscores are excluded from
+ unit-tests. This is currently the case for convolver, fluidsynth and
+ plugin-modulation.
+ They depend on external files (soundfont, impulse-response) or a specific
+ session-setup (plugin-modulation needs an automatable plugin).
+
+^s_
+ A filename beginning with "s_" indicates a code snippet.
+ These scripts can only be used in the interactive interpreter
+ (Window > Scripting). They may be useful by themselves or handy for copy/edit
+ to create EditorActions.
+ The filename prefix is only for convenience, "type" = "Snippet" is used when
+ scripts are listed at runtime.
diff --git a/share/scripts/__convolv.lua b/share/scripts/__convolv.lua
new file mode 100644
index 0000000000..23ad9034ed
--- /dev/null
+++ b/share/scripts/__convolv.lua
@@ -0,0 +1,59 @@
+ardour { ["type"] = "dsp", name = "Lua Convolver", license = "MIT", author = "Ardour Lua Task Force", description = [[Another simple DSP example]] }
+
+function dsp_ioconfig () return
+ {
+ { audio_in = 1, audio_out = 1},
+ { audio_in = 1, audio_out = 2},
+ { audio_in = 2, audio_out = 2},
+ }
+end
+
+local conv, mode, ir_file
+
+ir_file = "/tmp/reverbs/St Nicolaes Church.wav"
+ir_file = "/tmp/reverbs/Large Wide Echo Hall.wav"
+
+function dsp_configure (ins, outs)
+ if outs:n_audio() == 1 then
+ assert (ins:n_audio() == 1)
+ mode = ARDOUR.DSP.IRChannelConfig.Mono
+ elseif ins:n_audio() == 1 then
+ assert (outs:n_audio() == 2)
+ mode = ARDOUR.DSP.IRChannelConfig.MonoToStereo
+ else
+ assert (ins:n_audio() == 2)
+ assert (outs:n_audio() == 2)
+ mode = ARDOUR.DSP.IRChannelConfig.Stereo
+ end
+
+ local irs = ARDOUR.DSP.IRSettings()
+ irs.gain = 0.5
+ conv = ARDOUR.DSP.Convolver (Session, ir_file, mode, irs)
+ collectgarbage ()
+end
+
+function dsp_latency ()
+ if conv then
+ return conv:latency()
+ else
+ return 0
+ end
+end
+
+-- the DSP callback function to process audio audio
+-- "ins" and "outs" are http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+function dsp_run (ins, outs, n_samples)
+ assert (#ins <= #outs)
+
+ for c = 1, #ins do
+ if ins[c] ~= outs[c] then -- if processing is not in-place..
+ ARDOUR.DSP.copy_vector (outs[c], ins[c], n_samples) -- ..copy data from input to output.
+ end
+ end
+
+ if #outs == 1 then
+ conv:run_mono (outs[1], n_samples)
+ else
+ conv:run_stereo (outs[1], outs[2], n_samples)
+ end
+end
diff --git a/share/scripts/__fluidsynth1.lua b/share/scripts/__fluidsynth1.lua
new file mode 100644
index 0000000000..f2e0ea2b52
--- /dev/null
+++ b/share/scripts/__fluidsynth1.lua
@@ -0,0 +1,51 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Lua Fluid Synth",
+ category = "Instrument",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An Example Synth for Prototyping.]]
+}
+
+function dsp_ioconfig ()
+ return
+ {
+ { midi_in = 1, audio_in = 0, audio_out = 2},
+ }
+end
+
+fluidsynth = nil
+
+function dsp_init (rate)
+ fluidsynth = ARDOUR.FluidSynth (rate, 32)
+ assert (fluidsynth:load_sf2 ("/usr/share/sounds/sf2/FluidR3_GM.sf2"))
+end
+
+function dsp_run (ins, outs, n_samples)
+ local tme = 1
+ assert (#outs == 2)
+
+ -- parse midi messages
+ assert (type(midiin) == "table") -- global table of midi events (for now)
+ for _, e in pairs (midiin) do
+ local t = e["time"] -- t = [ 1 .. n_samples ]
+
+ -- synth sound until event
+ if t > tme then
+ local off = tme - 1
+ local len = t - tme
+ fluidsynth:synth (outs[1]:offset (off), outs[2]:offset (off), len)
+ end
+
+ tme = t + 1
+
+ fluidsynth:midi_event (e["bytes"], e["size"]) -- parse midi event
+ end
+
+ -- synth rest of cycle
+ if tme <= n_samples then
+ local off = tme - 1
+ local len = 1 + n_samples - tme
+ fluidsynth:synth (outs[1]:offset (off), outs[2]:offset (off), len)
+ end
+end
diff --git a/share/scripts/__plugin_modulation.lua b/share/scripts/__plugin_modulation.lua
new file mode 100644
index 0000000000..bb94b64a00
--- /dev/null
+++ b/share/scripts/__plugin_modulation.lua
@@ -0,0 +1,46 @@
+--- session-script example to modulate plugin parameter(s) globally
+--
+-- Ardour > Menu > Session > Scripting > Add Lua Script
+-- "Add" , select "Modulate Plugin Parameter", click "Add" + OK.
+--
+-----------------------------------------------------------------------------
+-- This script currently assumes you have a track named "Audio"
+-- which as a plugin at the top, where the first parameter has a range > 200
+-- e.g. "No Delay Line"
+--
+-- edit below..
+
+
+-- plugin descriptor
+ardour {
+ ["type"] = "session",
+ name = "Modulate Plugin Parameter",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An example session to modulate a plugin parameter.]]
+}
+
+function factory () -- generate a new script instance
+
+ local count = 0 -- script-instance "global" variable
+
+ -- the "run" function called at the beginning of every process cycle
+ return function (n_samples)
+ count = (count + 1) % 200; -- count process cycles
+ local tri = math.abs (100 - count) -- triangle wave 0..100
+
+ -- get the track named "Audio"
+ -- see also http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Session
+ -- and http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Route
+ local route = Session:route_by_name ("Audio")
+ assert (not route:isnil ()) -- make sure it exists
+
+ -- the 1st plugin (from top) on that track, ardour starts counting at zero
+ -- see also http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Processor
+ local plugin = route:nth_plugin (0)
+ assert (not plugin:isnil ()) -- make sure it exists
+
+ -- modulate the plugin's first parameter (0) from 200 .. 300
+ ARDOUR.LuaAPI.set_processor_param (plugin, 0, 200.0 + tri)
+ end
+end
diff --git a/share/scripts/__readable.lua b/share/scripts/__readable.lua
new file mode 100644
index 0000000000..b9a991a782
--- /dev/null
+++ b/share/scripts/__readable.lua
@@ -0,0 +1,47 @@
+ardour { ["type"] = "Snippet", name = "Readable Test" }
+
+function factory () return function ()
+
+ local file = "/tmp/reverbs/Large Wide Echo Hall.wav"
+ local rl = ARDOUR.Readable.load (Session, file)
+ local cmem = ARDOUR.DSP.DspShm (8192)
+
+ local d = cmem:to_float (0):array()
+ for i = 1, 8192 do d[i] = 0 end
+ d[1] = .5
+
+ local ar = ARDOUR.AudioRom.new_rom (cmem:to_float (0), 8192)
+
+ rl:push_back (ar)
+
+ local c = 1
+ for rd in rl:iter () do
+ local n_samples = rd:readable_length ()
+ assert (rd:n_channels () == 1)
+
+ local peak = 0
+ local pos = 0
+ repeat
+ -- read at most 8K samples starting at 'pos'
+ local s = rd:read (cmem:to_float (0), pos, 8192, 0)
+ pos = pos + s
+ -- access the raw audio data
+ -- http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+ local d = cmem:to_float (0):array()
+ -- iterate over the audio sample data
+ for i = 0, s do
+ if math.abs (d[i]) > peak then
+ peak = math.abs (d[i])
+ end
+ end
+ until s < 8192
+ assert (pos == n_samples)
+
+ if (peak > 0) then
+ print ("File:", file, "channel", c, "peak:", 20 * math.log (peak) / math.log(10), "dBFS")
+ else
+ print ("File:", file, "channel", c, " is silent")
+ end
+ c = c + 1;
+ end
+end end
diff --git a/share/scripts/_amp1.lua b/share/scripts/_amp1.lua
new file mode 100644
index 0000000000..03be3961b6
--- /dev/null
+++ b/share/scripts/_amp1.lua
@@ -0,0 +1,47 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Simple Amp",
+ category = "Example",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[
+ An Example DSP Plugin for processing audio, to
+ be used with Ardour's Lua scripting facility.]]
+}
+
+
+-- return possible i/o configurations
+function dsp_ioconfig ()
+ -- -1, -1 = any number of channels as long as input and output count matches
+ return { [1] = { audio_in = -1, audio_out = -1}, }
+end
+
+-- optional function, called when configuring the plugin
+function dsp_configure (ins, outs)
+ -- store configuration in global variable
+ audio_ins = ins:n_audio();
+ local audio_outs = outs:n_audio()
+ assert (audio_ins == audio_outs)
+end
+
+-- this variant asks for a complete *copy* of the
+-- audio data in a lua-table.
+-- after processing the data is copied back.
+--
+-- this also exemplifies the direct "connect and run" process function,
+-- where the channel-mapping needs to be done in lua.
+
+function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
+ for c = 1,audio_ins do
+ -- Note: lua starts counting at 1, ardour's ChanMapping::get() at 0
+ local ib = in_map:get(ARDOUR.DataType("audio"), c - 1); -- get id of mapped input buffer for given channel
+ local ob = out_map:get(ARDOUR.DataType("audio"), c - 1); -- get id of mapped output buffer for given channel
+ assert (ib ~= ARDOUR.ChanMapping.Invalid);
+ assert (ob ~= ARDOUR.ChanMapping.Invalid);
+ local a = bufs:get_audio (ib):data (offset):get_table(n_samples) -- copy audio-data from input buffer
+ for s = 1,n_samples do
+ a[s] = a[s] * 2; -- amplify data in lua table
+ end
+ bufs:get_audio(ob):data(offset):set_table(a, n_samples) -- copy back
+ end
+end
diff --git a/share/scripts/_amp2.lua b/share/scripts/_amp2.lua
new file mode 100644
index 0000000000..5a594d57e3
--- /dev/null
+++ b/share/scripts/_amp2.lua
@@ -0,0 +1,51 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Simple Amp II",
+ category = "Example",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[
+ An Example DSP Plugin for processing audio, to
+ be used with Ardour's Lua scripting facility.]]
+}
+
+-- see amp1.lua
+function dsp_ioconfig ()
+ return { [1] = { audio_in = -1, audio_out = -1}, }
+end
+
+function dsp_configure (ins, outs)
+ audio_ins = ins:n_audio();
+ local audio_outs = outs:n_audio()
+ assert (audio_ins == audio_outs)
+end
+
+
+-- this variant modifies the audio data in-place
+-- in Ardour's buffer.
+--
+-- It relies on the fact that by default Ardour requires
+-- plugins to process data in-place (zero copy).
+--
+-- Every assignment directly calls a c-function behind
+-- the scenes to get/set the value.
+-- It's a bit more efficient than "Amp I" on most systems.
+
+function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
+ for c = 1,audio_ins do
+ -- ensure that processing does happen in-place
+ local ib = in_map:get(ARDOUR.DataType("audio"), c - 1); -- get id of mapped input buffer for given cannel
+ local ob = out_map:get(ARDOUR.DataType("audio"), c - 1); -- get id of mapped output buffer for given cannel
+ assert (ib ~= ARDOUR.ChanMapping.Invalid);
+ assert (ob ~= ARDOUR.ChanMapping.Invalid);
+
+ local bi = bufs:get_audio(ib)
+ local bo = bufs:get_audio(ob)
+ assert (bi == bo)
+
+ local a = bufs:get_audio(ib):data(offset):array() -- get a reference (pointer to array)
+ for s = 1,n_samples do
+ a[s] = a[s] * 2; -- modify data in-place (shared with ardour)
+ end
+ end
+end
diff --git a/share/scripts/_amp3.lua b/share/scripts/_amp3.lua
new file mode 100644
index 0000000000..006a22afbb
--- /dev/null
+++ b/share/scripts/_amp3.lua
@@ -0,0 +1,44 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Simple Amp III",
+ category = "Example",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[
+ An Example DSP Plugin for processing audio, to
+ be used with Ardour's Lua scripting facility.]]
+}
+
+function dsp_ioconfig ()
+ return
+ {
+ { audio_in = -1, audio_out = -1},
+ }
+end
+
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "Gain", min = -20, max = 20, default = 6, unit="dB", scalepoints = { ["0"] = 0, ["twice as loud"] = 6 , ["half as loud"] = -6 } },
+ }
+end
+
+
+-- use ardour's vectorized functions
+--
+-- This is as efficient as Ardour doing it itself in C++
+-- Lua function overhead is negligible
+--
+-- this also exemplifies the /simpler/ way of delegating the
+-- channel-mapping to ardour.
+
+function dsp_run (ins, outs, n_samples)
+ local ctrl = CtrlPorts:array() -- get control port array (read/write)
+ local gain = ARDOUR.DSP.dB_to_coefficient (ctrl[1])
+ assert (#ins == #outs) -- ensure that we can run in-place (channel count matches)
+ for c = 1,#ins do
+ assert (ins[c] == outs[c]) -- check in-place
+ ARDOUR.DSP.apply_gain_to_buffer (ins[c], n_samples, gain); -- process in-place
+ end
+end
diff --git a/share/scripts/_biquad_filter.lua b/share/scripts/_biquad_filter.lua
new file mode 100644
index 0000000000..5dc6e3be1b
--- /dev/null
+++ b/share/scripts/_biquad_filter.lua
@@ -0,0 +1,280 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Biquad Filter",
+ category = "Filter",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[A Versatile Filter Plugin]]
+}
+
+function dsp_ioconfig ()
+ return
+ {
+ -- allow any number of I/O as long as port-count matches
+ { audio_in = -1, audio_out = -1},
+ }
+end
+
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "Enable", min = 0, max = 1, default = 1, bypass = true, toggled = true },
+ { ["type"] = "input", name = "Type", min = 0, max = 4, default = 0, enum = true, scalepoints =
+ {
+ ["Peaking"] = 0,
+ ["Low Shelf"] = 1,
+ ["High Shelf"] = 2,
+ ["Low Pass"] = 3,
+ ["High Pass"] = 4,
+ }
+ },
+ { ["type"] = "input", name = "Gain", min = -20, max = 20, default = 0, unit="dB" },
+ { ["type"] = "input", name = "Freq", min = 20, max = 20000, default = 1000, unit="Hz", logarithmic = true },
+ { ["type"] = "input", name = "Q", min = 0.1, max = 8, default = .707, logarithmic = true },
+ }
+end
+
+-- translate type parameter to DSP enum
+-- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR.DSP.Biquad.Type
+function map_type (t)
+ if t == 1 then
+ return ARDOUR.DSP.BiquadType.LowShelf
+ elseif t == 2 then
+ return ARDOUR.DSP.BiquadType.HighShelf
+ elseif t == 3 then
+ return ARDOUR.DSP.BiquadType.LowPass
+ elseif t == 4 then
+ return ARDOUR.DSP.BiquadType.HighPass
+ else
+ return ARDOUR.DSP.BiquadType.Peaking
+ end
+end
+
+function ctrl_data ()
+ local ctrl = CtrlPorts:array ()
+ if ctrl[1] <= 0 then -- when disabled
+ ctrl[3] = 0; -- force gain to 0dB
+ end
+ return ctrl
+end
+
+-- these globals are *not* shared between DSP and UI
+local filters = {} -- the biquad filter instances (DSP)
+local filt -- the biquad filter instance (GUI, response)
+local cur = {0, 0, 0, 0, 0} -- current parameters
+local lpf = 0.03 -- parameter low-pass filter time-constant
+local chn = 0 -- channel/filter count
+
+function dsp_init (rate)
+ self:shmem ():allocate (1) -- shared mem to tell the GUI the samplerate
+ local cfg = self:shmem ():to_int (0):array ()
+ cfg[1] = rate
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP:Biquad
+ filt = ARDOUR.DSP.Biquad (rate) -- initialize filter
+ lpf = 13000 / rate -- interpolation time constant
+end
+
+function dsp_configure (ins, outs)
+ assert (ins:n_audio () == outs:n_audio ())
+ local cfg = self:shmem ():to_int (0):array ()
+ local rate = cfg[1]
+ chn = ins:n_audio ()
+ for c = 1, chn do
+ filters[c] = ARDOUR.DSP.Biquad (rate) -- initialize filters
+ end
+ cur = {0, 0, 0, 0, 0}
+end
+
+-- helper functions for parameter interpolation
+function param_changed (ctrl)
+ if ctrl[2] == cur[2] and ctrl[3] == cur[3] and ctrl[4] == cur[4] and ctrl[5] == cur[5] then
+ return false
+ end
+ return true
+end
+
+function low_pass_filter_param (old, new, limit)
+ if math.abs (old - new) < limit then
+ return new
+ else
+ return old + lpf * (new - old)
+ end
+end
+
+-- apply parameters, re-compute filter coefficients if needed
+function apply_params (ctrl)
+ if not param_changed (ctrl) then
+ return
+ end
+
+ if cur[2] ~= ctrl[2] then
+ -- reset filter state when type changes
+ filt:reset ()
+ for k = 2,5 do cur[k] = ctrl[k] end
+ else
+ -- low-pass filter ctrl parameter values, smooth transition
+ cur[3] = low_pass_filter_param (cur[3], ctrl[3], 0.1) -- gain/dB
+ cur[4] = low_pass_filter_param (cur[4], ctrl[4], 1.0) -- freq/Hz
+ cur[5] = low_pass_filter_param (cur[5], ctrl[5], 0.01) -- quality
+ end
+
+ for c = 1, chn do
+ filters[c]:compute (map_type (cur[2]), cur[4], cur[5], cur[3])
+ end
+end
+
+
+-- the actual DSP callback
+function dsp_run (ins, outs, n_samples)
+ local changed = false
+ local siz = n_samples
+ local off = 0
+ local ctrl = ctrl_data ()
+
+ -- if a parameter was changed, process at most 64 samples at a time
+ -- and interpolate parameters until the current settings match
+ -- the target values
+ if param_changed (ctrl) then
+ changed = true
+ siz = 64
+ end
+
+ while n_samples > 0 do
+ if changed then apply_params (ctrl) end
+ if siz > n_samples then siz = n_samples end
+
+ -- process all channels
+ for c = 1,#ins do
+ -- check if output and input buffers for this channel are identical
+ -- http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+ if ins[c] == outs[c] then
+ filters[c]:run (ins[c]:offset (off), siz) -- in-place processing
+ else
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP
+ ARDOUR.DSP.copy_vector (outs[c]:offset (off), ins[c]:offset (off), siz)
+ filters[c]:run (outs[c]:offset (off), siz)
+ end
+ end
+
+ n_samples = n_samples - siz
+ off = off + siz
+ end
+
+ if changed then
+ -- notify display
+ self:queue_draw ()
+ end
+end
+
+
+-------------------------------------------------------------------------------
+--- inline display
+
+function round (n)
+ return math.floor (n + .5)
+end
+
+function freq_at_x (x, w)
+ -- x-axis pixel for given freq, power-scale
+ return 20 * 1000 ^ (x / w)
+end
+
+function x_at_freq (f, w)
+ -- frequency at given x-axis pixel
+ return w * math.log (f / 20.0) / math.log (1000.0)
+end
+
+function db_to_y (db, h)
+ -- y-axis gain mapping
+ if db < -20 then db = -20 end
+ if db > 20 then db = 20 end
+ return -.5 + 0.5 * h * (1 - db / 20)
+end
+
+function grid_db (ctx, w, h, db)
+ -- draw horizontal grid line
+ local y = -.5 + round (db_to_y (db, h))
+ ctx:move_to (0, y)
+ ctx:line_to (w, y)
+ ctx:stroke ()
+end
+
+function grid_freq (ctx, w, h, f)
+ -- draw vertical grid line
+ local x = -.5 + round (x_at_freq (f, w))
+ ctx:move_to (x, 0)
+ ctx:line_to (x, h)
+ ctx:stroke ()
+end
+
+function render_inline (ctx, w, max_h)
+ if not filt then
+ -- the GUI is separate from the DSP, but the GUI needs to know
+ -- the sample-rate that the DSP is using.
+ local shmem = self:shmem () -- get shared memory region
+ local cfg = shmem:to_int (0):array () -- "cast" into lua-table
+ -- instantiate filter (to calculate the transfer function's response)
+ filt = ARDOUR.DSP.Biquad (cfg[1])
+ end
+
+ -- set filter coefficients if they have changed
+ if param_changed (CtrlPorts:array ()) then
+ local ctrl = ctrl_data ()
+ for k = 2,5 do cur[k] = ctrl[k] end
+ filt:compute (map_type (cur[2]), cur[4], cur[5], cur[3])
+ end
+
+ -- calc height of inline display
+ local h = math.ceil (w * 10 / 16) -- use 16:10 aspect
+ h = 2 * round (h / 2) -- with an even number of vertical pixels
+ if (h > max_h) then h = max_h end -- but at most max-height
+
+ -- ctx is a http://cairographics.org/ context
+ -- http://manual.ardour.org/lua-scripting/class_reference/#Cairo:Context
+
+ -- clear background
+ ctx:rectangle (0, 0, w, h)
+ ctx:set_source_rgba (.2, .2, .2, 1.0)
+ ctx:fill ()
+
+ -- set line width: 1px
+ -- Note: a cairo pixel at [1,1] spans [0.5->1.5 , 0.5->1.5]
+ -- hence the offset -0.5 in various move_to(), line_to() calls
+ ctx:set_line_width (1.0)
+
+ -- draw grid
+ local dash3 = C.DoubleVector ()
+ dash3:add ({1, 3})
+ ctx:set_dash (dash3, 2) -- dotted line
+ ctx:set_source_rgba (.5, .5, .5, .5)
+ grid_db (ctx, w, h, 0)
+ grid_db (ctx, w, h, 6)
+ grid_db (ctx, w, h, 12)
+ grid_db (ctx, w, h, 18)
+ grid_db (ctx, w, h, -6)
+ grid_db (ctx, w, h, -12)
+ grid_db (ctx, w, h, -18)
+ grid_freq (ctx, w, h, 100)
+ grid_freq (ctx, w, h, 1000)
+ grid_freq (ctx, w, h, 10000)
+ ctx:unset_dash ()
+
+ -- draw transfer function line
+ ctx:set_source_rgba (.8, .8, .8, 1.0)
+ ctx:move_to (-.5, db_to_y (filt:dB_at_freq (freq_at_x (0, w)), h))
+ for x = 1,w do
+ local db = filt:dB_at_freq (freq_at_x (x, w))
+ ctx:line_to (-.5 + x, db_to_y (db, h))
+ end
+ ctx:stroke_preserve ()
+
+ -- fill area to zero under the curve
+ ctx:line_to (w, -.5 + h * .5)
+ ctx:line_to (0, -.5 + h * .5)
+ ctx:close_path ()
+ ctx:set_source_rgba (.5, .5, .5, .5)
+ ctx:fill ()
+
+ return {w, h}
+end
diff --git a/share/scripts/_calc_dsp_statistics.lua b/share/scripts/_calc_dsp_statistics.lua
new file mode 100644
index 0000000000..2d676055a8
--- /dev/null
+++ b/share/scripts/_calc_dsp_statistics.lua
@@ -0,0 +1,27 @@
+ardour { ["type"] = "Snippet", name = "Calculate DSP stats",
+ license = "MIT",
+ author = "Ardour Team",
+}
+
+function factory () return function ()
+
+ for t in Session:get_routes ():iter () do
+ local i = 0
+ while true do
+ local rv, stats
+ local proc = t:nth_processor (i)
+ if proc:isnil () then break end
+ if proc:to_plugininsert():isnil() then goto continue end
+
+ rv, stats = proc:to_plugininsert():get_stats (0, 0, 0, 0)
+ if not rv then goto continue end
+
+ print (string.format (" * %-28s | min: %.2f max: %.2f avg: %.3f std-dev: %.3f [ms]",
+ string.sub (proc:name() .. ' (' .. t:name() .. ')', 0, 28),
+ stats[1] / 1000.0, stats[2] / 1000.0, stats[3] / 1000.0, stats[4] / 1000.0))
+
+ ::continue::
+ i = i + 1
+ end
+ end
+end end
diff --git a/share/scripts/_cron.lua b/share/scripts/_cron.lua
new file mode 100644
index 0000000000..3f412d9ddd
--- /dev/null
+++ b/share/scripts/_cron.lua
@@ -0,0 +1,37 @@
+ardour {
+ ["type"] = "EditorHook",
+ name = "Timed Event Example",
+ author = "Ardour Lua Task Force",
+ description = "Perform actions at specific wallclock time, example record",
+}
+
+function signals ()
+ return LuaSignal.Set():add ({[LuaSignal.LuaTimerDS] = true})
+end
+
+function factory ()
+ local _last_time = 0
+ return function (signal, ref, ...)
+
+ -- calculate seconds since midnight
+ function hhmmss (hh, mm, ss) return hh * 3600 + mm * 60 + ss end
+
+ -- current seconds since midnight UTC
+ -- (unix-time is UTC, no leap seconds, a day always has 86400 sec)
+ local now = os.time () % 86400
+
+ -- event at 09:30:00 UTC (here: rec-arm + roll)
+ if (now >= hhmmss (09, 30, 00) and _last_time < hhmmss (09, 30, 00)) then
+ Session:maybe_enable_record (false)
+ Session:request_transport_speed (1.0, true, ARDOUR.TransportRequestSource.TRS_UI)
+ end
+
+ -- event at 09:32:00 UTC (here: rec-stop)
+ if (now >= hhmmss (09, 32, 00) and _last_time < hhmmss (09, 32, 00)) then
+ Session:disable_record (false, false)
+ Session:request_transport_speed (0.0, true, ARDOUR.TransportRequestSource.TRS_UI)
+ end
+
+ _last_time = now
+ end
+end
diff --git a/share/scripts/_dialog_test.lua b/share/scripts/_dialog_test.lua
new file mode 100644
index 0000000000..cc02f82a46
--- /dev/null
+++ b/share/scripts/_dialog_test.lua
@@ -0,0 +1,95 @@
+ardour { ["type"] = "Snippet", name = "Dialog Test" }
+
+function factory () return function ()
+ local md = LuaDialog.Message ("title", "hello", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close)
+ print (md:run())
+
+ md = nil
+ collectgarbage ()
+
+ -----------------------------------------------------------------
+ function basic_serialize (o)
+ if type(o) == "number" then
+ return tostring(o)
+ else
+ return string.format("%q", o)
+ end
+ end
+
+ function serialize (name, value)
+ local rv = name .. ' = '
+ collectgarbage()
+ if type(value) == "number" or type(value) == "string" or type(value) == "nil" or type (value) == "boolean" then
+ return rv .. basic_serialize(value) .. ' '
+ elseif type(value) == "table" then
+ rv = rv .. '{} '
+ for k,v in pairs(value) do
+ local fieldname = string.format("%s[%s]", name, basic_serialize(k))
+ rv = rv .. serialize(fieldname, v) .. ' '
+ end
+ return rv
+ elseif type(value) == "function" then
+ --return rv .. string.format("%q", string.dump(value, true))
+ return rv .. "(function)"
+ else
+ error('cannot serialize a ' .. type(value))
+ end
+ end
+ -----------------------------------------------------------------
+
+ function func () print "Hello" end
+
+ local dialog_options = {
+ { type = "checkbox", key = "onoff", default = true, title = "OnOff" },
+
+ { type = "entry", key = "text", default = "changeme", title = "Text Entry" },
+
+ {
+ type = "radio", key = "select", title = "RadioBtn", values =
+ {
+ ["Option 1"] = 1, ["Option 2"] = "2", ["Option A"] = 'A'
+ },
+ default = "Option 1"
+ },
+
+ {
+ type = "dropdown", key = "dropdown", title = "Menu", values =
+ {
+ ["Option 1"] = 1, ["Option 2"] = "2", ["Callback"] = func,
+ ["Option 4"] =
+ {
+ ["Option 4a"] = "test", ["Option 4b"] = 4.2
+ }
+ },
+ default = "Option 2"
+ },
+
+ { type = "fader", key = "gain", title = "Level", default = -10 }, -- unit = 'dB"
+
+ {
+ type = "slider", key = "freq", title = "Frequency", min = 20, max = 20000, scalepoints =
+ {
+ [20] = "20", [200] = "nice", [2000] = "2k", [10000] = "too much"
+ },
+ default = 500
+ },
+
+ { type = "heading", title = "Heading" },
+
+ { type = "number", key = "number", title = "Whatever", min = 0, max = 10, step = 1, digits = 2 },
+
+ { type = "file", key = "file", title = "Select a File", path = ARDOUR.LuaAPI.build_filename (Session:path (), Session:name () .. ".ardour") },
+
+ { type = "folder", key = "folder", title = "Select a Folder", path = Session:path() }
+ }
+
+ local od = LuaDialog.Dialog ("title", dialog_options)
+ local rv = od:run()
+ if (rv) then
+ print (serialize ("dialog", rv))
+ end
+
+ od = nil
+ collectgarbage ()
+
+end end
diff --git a/share/scripts/_dsp_plugin_communication.lua b/share/scripts/_dsp_plugin_communication.lua
new file mode 100644
index 0000000000..e422e08cca
--- /dev/null
+++ b/share/scripts/_dsp_plugin_communication.lua
@@ -0,0 +1,78 @@
+ardour { ["type"] = "dsp", name = "DSP Plugin Communication" }
+function dsp_ioconfig () return { { audio_in = -1, audio_out = -1} } end
+
+function dsp_init (rate)
+ self:shmem ():allocate (1)
+end
+
+function dsp_configure (ins, outs)
+end
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "output", name = "self", min = 0, max = 8},
+ { ["type"] = "output", name = "gain", min = 0, max = 2},
+ }
+end
+
+function dsp_run (ins, outs, n_samples)
+ local ctrl = CtrlPorts:array ()
+ local route = self:route ()
+ local shmem = self:shmem ()
+
+ -- count plugins
+ local i = 0;
+ local l = 0;
+ local s = -1; -- 'self' this plugin instance
+
+ -- iterate overall plugins on this track,
+ -- find all LuaProc instances of this plugin (unique_id),
+ repeat
+ local proc = route:nth_plugin (i)
+ if not proc:isnil ()
+ and not proc:to_insert():plugin (0):to_luaproc():isnil ()
+ and proc:to_insert():plugin (0):unique_id () == self:unique_id () then
+ if (self:id ():to_s() == proc:to_insert():plugin (0):id ():to_s()) then
+ s = l; -- *this* plugin instance
+ end
+ if l == 0 then
+ -- use shared-memory are of the first plugin instance for all.
+ --
+ -- (the first plugin writes there, all later plugins only read,
+ -- plugins on a track are executed in order, in the same thread)
+ shmem = proc:to_insert():plugin (0):to_luaproc():shmem ()
+ end
+ l = l + 1 -- count total instances of this plugin-type
+ end
+ i = i + 1
+ until proc:isnil ()
+
+ assert (s >= 0)
+ ctrl[1] = s;
+
+ -- calculate digital peak of all channels
+ local peak = 0
+ for c = 1,#ins do
+ if not ins[c]:sameinstance (outs[c]) then
+ ARDOUR.DSP.copy_vector (outs[c], ins[c], n_samples)
+ end
+ peak = ARDOUR.DSP.compute_peak(outs[c], n_samples, peak)
+ end
+
+
+ -- actual inter-plugin communication
+ local a = shmem:to_float (0):array ()
+ if s == 0 then
+ -- the first plugin saves the peak
+ a[0] = peak
+ ctrl[2] = -1
+ else
+ -- all later plugins display the difference to the first.
+ if (a[0] == 0) then
+ ctrl[2] = 1
+ else
+ ctrl[2] = peak / a[0]
+ end
+ end
+end
diff --git a/share/scripts/_dump_latency.lua b/share/scripts/_dump_latency.lua
new file mode 100644
index 0000000000..9303e29508
--- /dev/null
+++ b/share/scripts/_dump_latency.lua
@@ -0,0 +1,77 @@
+ardour { ["type"] = "Snippet", name = "Dump Latency",
+ license = "MIT",
+ author = "Ardour Team",
+}
+
+function factory () return function ()
+ local all_procs = true
+ local show_ports = true
+
+ print (" -- Session --")
+ print ("Worst Output Latency: ", Session:worst_output_latency ())
+ print ("Worst Input Latency: ", Session:worst_input_latency ())
+ print ("Worst Track Latency: ", Session:worst_route_latency ())
+ print ("Worst Latency Preroll: ", Session:worst_latency_preroll ())
+
+ print (" -- Routes --")
+ for t in Session:get_routes ():iter () do
+ print (string.format ("%-30s signal-latency: %4d align: %4d play: %4d || in: %4d out: %4d",
+ t:name(),
+ t:signal_latency (), t:playback_latency (false), t:playback_latency (true),
+ t:input():latency(), t:output():latency()))
+ local i = 0
+ while true do
+ local proc = t:nth_processor (i)
+ if proc:isnil () then break end
+ if all_procs and not proc:to_send():isnil () then
+ print (string.format (" * %-27s L: %4d in: %4d out: %4d capt: %4d play %4d DLY-SRC: %4d DLY-DST: %4d",
+ string.sub (proc:name(), 0, 27) , proc:signal_latency(),
+ proc:input_latency(), proc:output_latency(),
+ proc:capture_offset(), proc:playback_offset(),
+ proc:to_send():get_delay_in(), proc:to_send():get_delay_out()
+ ))
+ elseif all_procs or not proc:to_diskioprocessor():isnil () then
+ print (string.format (" * %-27s L: %4d in: %4d out: %4d capt: %4d play %4d",
+ string.sub (proc:name(), 0, 27) , proc:signal_latency(),
+ proc:input_latency(), proc:output_latency(),
+ proc:capture_offset(), proc:playback_offset()
+ ))
+ end
+ i = i + 1
+ end
+ end
+
+ if show_ports then
+ print (" -- Ports -- (latencies: port, priv, pub)")
+ local a = Session:engine()
+ _, t = a:get_ports (ARDOUR.DataType("audio"), ARDOUR.PortList())
+ -- table 't' holds argument references. t[2] is the PortList
+ for p in t[2]:iter() do
+ local lp = p:get_connected_latency_range (ARDOUR.LatencyRange(), true)
+ local lc = p:get_connected_latency_range (ARDOUR.LatencyRange(), false)
+ local ppl = p:private_latency_range (true)
+ local pcl = p:private_latency_range (false)
+ local bpl = p:public_latency_range (true)
+ local bcl = p:public_latency_range (false)
+ print (string.format ("%-30s play: (%4d, %4d) (%4d, %4d) (%4d, %4d) capt: (%4d, %4d) (%4d, %4d) (%4d, %4d)",
+ p:name(),
+ lp[1].min, lp[1].max, ppl.min, ppl.max, bpl.min, bpl.max,
+ lc[1].min, lc[1].max, pcl.min, pcl.max, bcl.min, bcl.max))
+ end
+ _, t = a:get_ports (ARDOUR.DataType("midi"), ARDOUR.PortList())
+ -- table 't' holds argument references. t[2] is the PortList
+ for p in t[2]:iter() do
+ local lp = p:get_connected_latency_range (ARDOUR.LatencyRange(), true)
+ local lc = p:get_connected_latency_range (ARDOUR.LatencyRange(), false)
+ local ppl = p:private_latency_range (true)
+ local pcl = p:private_latency_range (false)
+ local bpl = p:public_latency_range (true)
+ local bcl = p:public_latency_range (false)
+ print (string.format ("%-30s play: (%4d, %4d) (%4d, %4d) (%4d, %4d) capt: (%4d, %4d) (%4d, %4d) (%4d, %4d)",
+ p:name(),
+ lp[1].min, lp[1].max, ppl.min, ppl.max, bpl.min, bpl.max,
+ lc[1].min, lc[1].max, pcl.min, pcl.max, bcl.min, bcl.max))
+ end
+ end
+ collectgarbage ()
+end end
diff --git a/share/scripts/_dump_midiregion.lua b/share/scripts/_dump_midiregion.lua
new file mode 100644
index 0000000000..0c62a56479
--- /dev/null
+++ b/share/scripts/_dump_midiregion.lua
@@ -0,0 +1,20 @@
+ardour { ["type"] = "Snippet", name = "Dump MIDI Region" }
+
+function factory () return function ()
+ local sel = Editor:get_selection ()
+ for r in sel.regions:regionlist ():iter () do
+ local mr = r:to_midiregion ()
+ if mr:isnil () then goto next end
+
+ print (r:name (), "Pos:", r:position (), "Start:", r:start ())
+ local bfc = ARDOUR.BeatsSamplesConverter (Session:tempo_map (), r:position ())
+ local nl = ARDOUR.LuaAPI.note_list (mr:model ())
+ for n in nl:iter () do
+ print (" Note @", bfc:to (n:time ()),
+ ARDOUR.ParameterDescriptor.midi_note_name (n:note ()),
+ "Vel:", n:velocity ())
+ end
+ print ("----")
+ ::next::
+ end
+end end
diff --git a/share/scripts/_dump_playlists.lua b/share/scripts/_dump_playlists.lua
new file mode 100644
index 0000000000..656a5ead97
--- /dev/null
+++ b/share/scripts/_dump_playlists.lua
@@ -0,0 +1,31 @@
+ardour { ["type"] = "Snippet", name = "Dump Playlists" }
+
+function factory () return function ()
+
+ print ("Number of playlists:", Session:playlists():n_playlists())
+
+ print ()
+ print ("Used playlists:")
+ for p in Session:playlists():get_used():iter() do
+ print ("-", p:name(), p:n_regions())
+ end
+
+ print ()
+ print ("Unused playlists:")
+ for p in Session:playlists():get_unused():iter() do
+ print ("-", p:name(), p:n_regions())
+ end
+
+ print ()
+ print ("Playlists by Track:")
+ for r in Session:get_tracks():iter() do
+ print ("*", r:name())
+ for p in Session:playlists():playlists_for_track (r:to_track()):iter() do
+ if (p == r:to_track():playlist()) then
+ print (" >-", p:name(), p:n_regions())
+ else
+ print (" -", p:name(), p:n_regions())
+ end
+ end
+ end
+end end
diff --git a/share/scripts/_export_plugins_on_save.lua b/share/scripts/_export_plugins_on_save.lua
new file mode 100644
index 0000000000..c732717bd8
--- /dev/null
+++ b/share/scripts/_export_plugins_on_save.lua
@@ -0,0 +1,44 @@
+ardour {
+ ["type"] = "EditorHook",
+ name = "Save Extra Data (instruments)",
+ author = "Ardour Lua Task Force",
+ description = "Export custom data when the session is saved",
+}
+
+-- subscribe to signals
+-- http://manual.ardour.org/lua-scripting/class_reference/#LuaSignal.LuaSignal
+function signals ()
+ s = LuaSignal.Set()
+ s:add ({[LuaSignal.StateSaved] = true})
+ return s
+end
+
+-- create callback functions
+function factory () return function (signal, ...)
+ assert (signal == LuaSignal.StateSaved)
+
+ local all_instruments = {}
+
+ -- iterate over all routes
+ for r in Session:get_routes():iter() do
+ local proc = r:the_instrument() -- get instrument processor (if any)
+ if proc:isnil() then goto nextroute end -- skip tracks/busses without instrument
+ local pi = proc:to_insert() -- check if it's a plugin-insert
+ if pi:isnil() then goto nextroute end
+
+ local pp = pi:plugin (0) -- get first instance
+ all_instruments[r:name()] = string.format ("%s (%s)", proc:name(), pp:unique_id())
+
+ ::nextroute::
+ end
+
+ if next (all_instruments) ~= nil then -- check if table is not empty
+ -- write to a file in the session-folder
+ file = io.open (ARDOUR.LuaAPI.build_filename (Session:path(), Session:name () .. ".instruments.txt"), "w")
+ for nme, nfo in pairs (all_instruments) do
+ file:write (nme .. ": " .. nfo)
+ end
+ file:close()
+ end
+
+end end
diff --git a/share/scripts/_export_tracks.lua b/share/scripts/_export_tracks.lua
new file mode 100644
index 0000000000..7db8dabe84
--- /dev/null
+++ b/share/scripts/_export_tracks.lua
@@ -0,0 +1,10 @@
+ardour { ["type"] = "Snippet", name = "Export Track XML" }
+
+function factory () return function ()
+ local rlp = ARDOUR.RouteListPtr ()
+ local sel = Editor:get_selection ()
+ for r in sel.tracks:routelist ():iter () do
+ rlp:push_back (r)
+ end
+ print (Session:export_track_state (rlp, "/tmp/rexport"))
+end end
diff --git a/share/scripts/_fan_out_instrument.lua b/share/scripts/_fan_out_instrument.lua
new file mode 100644
index 0000000000..3fdf4ef907
--- /dev/null
+++ b/share/scripts/_fan_out_instrument.lua
@@ -0,0 +1,69 @@
+ardour { ["type"] = "EditorAction", name = "Fan Out Instrument",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Create Busses for every Instrument Output on selected Tracks]]
+}
+
+function factory () return function ()
+
+ local outputs = 2
+ local mst = Session:master_out();
+ if not mst:isnil() then
+ outputs = mst:n_inputs():n_audio()
+ end
+ mst = nil -- drop reference
+
+ local sel = Editor:get_selection ()
+ for r in sel.tracks:routelist ():iter () do
+ local proc = r:the_instrument ():to_insert()
+ if proc:isnil () then
+ print ("Track", r:name(), "does not have an instrument plugin")
+ goto next
+ end
+ local plugin = proc:plugin(0);
+
+ if (r:n_outputs ():n_audio() ~= proc:output_streams():n_audio()) then
+ print ("Instrument ", proc:name(), "outputs", proc:output_streams():n_audio(), "do not match track outputs", r:n_outputs ():n_audio())
+ goto next
+ end
+
+ -- collect port-group information, count target bus width
+ local targets = {}
+ for i = 1, proc:output_streams():n_audio() do
+ local pd = plugin:describe_io_port (ARDOUR.DataType("Audio"), false, i - 1)
+ local nn = proc:name() .. " " .. pd.group_name; -- TODO use track-name prefix?
+ targets[nn] = targets[nn] or 0
+ targets[nn] = targets[nn] + 1
+ end
+
+ if #targets < 2 then
+ print ("Instrument ", proc:name(), "has only 1 output bus. Nothing to fan out.")
+ goto next
+ end
+
+ -- create busses ; TODO retain order
+ for t,c in pairs (targets) do
+ local rt = Session:route_by_name (t)
+ if rt:isnil () then
+ Session:new_audio_route (c, outputs, nil, 1, t, ARDOUR.PresentationInfo.Flag.AudioBus, ARDOUR.PresentationInfo.max_order)
+ end
+ end
+
+ r:output():disconnect_all (nil)
+ r:panner_shell():set_bypassed (true)
+
+ -- connect the busses
+ for i = 1, proc:output_streams():n_audio() do
+ local pd = plugin:describe_io_port (ARDOUR.DataType("Audio"), false, i - 1)
+ local nn = proc:name() .. " " .. pd.group_name;
+ local rt = Session:route_by_name (nn)
+ assert (rt)
+
+ local op = r:output():audio (i - 1)
+ local ip = rt:input():audio (pd.group_channel)
+ op:connect (ip:name())
+ end
+
+ ::next::
+ end
+end end
diff --git a/share/scripts/_find_nonzero_sample.lua b/share/scripts/_find_nonzero_sample.lua
new file mode 100644
index 0000000000..c23d2b0b4d
--- /dev/null
+++ b/share/scripts/_find_nonzero_sample.lua
@@ -0,0 +1,74 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Find non zero audio sample",
+ author = "Ardour Team",
+ description = [[Find the position of first non-zero audio sample in selected regions.]]
+}
+
+function factory () return function ()
+ -- get Editor GUI Selection
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
+ local sel = Editor:get_selection ()
+
+ -- allocate a buffer (float* in C)
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP:DspShm
+ local cmem = ARDOUR.DSP.DspShm (8192)
+ local msg = ""
+
+ -- iterate over selected regions
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:RegionSelection
+ for r in sel.regions:regionlist ():iter () do
+ -- test if it's an audio region
+ if r:to_audioregion ():isnil () then
+ goto next
+ end
+
+ -- to read the Region data, we use the Readable interface of the Region
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Readable
+ local rd = r:to_readable ()
+
+ local n_samples = rd:readable_length ()
+ local n_channels = rd:n_channels ()
+
+ local nonzeropos = -1
+
+ -- iterate over all channels in Audio Region
+ for c = 0, n_channels -1 do
+ local pos = 0
+ repeat
+ -- read at most 8K samples of channel 'c' starting at 'pos'
+ local s = rd:read (cmem:to_float (0), pos, 8192, c)
+ -- access the raw audio data
+ -- http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+ local d = cmem:to_float (0):array()
+ -- iterate over the audio sample data
+ for i = 0, s do
+ if math.abs (d[i]) > 0 then
+ if (nonzeropos < 0 or pos + i < nonzeropos) then
+ nonzeropos = pos + i
+ end
+ break
+ end
+ end
+ pos = pos + s
+ if (nonzeropos >= 0 and pos > nonzeropos) then
+ break
+ end
+ until s < 8192
+ end
+
+ if (nonzeropos >= 0) then
+ msg = msg .. string.format("%s: %d\n", r:name (), nonzeropos + r:position())
+ else
+ msg = msg .. "Region: '%s' is silent\n"
+ end
+
+ ::next::
+ end
+
+ if (msg ~= "") then
+ local md = LuaDialog.Message ("First Non Zero Sample", msg, LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close)
+ md:run()
+ end
+
+end end
diff --git a/share/scripts/_fir.lua b/share/scripts/_fir.lua
new file mode 100644
index 0000000000..64448ef8f6
--- /dev/null
+++ b/share/scripts/_fir.lua
@@ -0,0 +1,36 @@
+ardour { ["type"] = "dsp", name = "Lua FIR Convolver", license = "MIT", author = "Ardour Lua Task Force", description = [[Another simple DSP example]] }
+
+function dsp_ioconfig () return
+ {
+ { audio_in = 1, audio_out = 1},
+ }
+end
+
+local conv
+
+function dsp_configure (ins, outs)
+ conv = ARDOUR.DSP.Convolution (Session, ins:n_audio (), outs:n_audio ())
+
+ local cmem = ARDOUR.DSP.DspShm (4)
+ cmem:clear ()
+ local d = cmem:to_float (0):array()
+ d[1] = .5
+ d[2] = .5
+ local ar = ARDOUR.AudioRom.new_rom (cmem:to_float (0), 4)
+ conv:add_impdata (0, 0, ar, 1.0, 0, 0, 0, 0)
+
+ cmem:to_float (0):set_table({1, -1, 0, 0}, 4)
+ ar = ARDOUR.AudioRom.new_rom (cmem:to_float (0), 3)
+ conv:add_impdata (0, 0, ar, 1.0, 0, 0, 0, 0)
+
+ conv:restart ()
+ collectgarbage ()
+end
+
+function dsp_latency ()
+ return conv:latency()
+end
+
+function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
+ conv:run (bufs, in_map, out_map, n_samples, offset)
+end
diff --git a/share/scripts/_hook_test.lua b/share/scripts/_hook_test.lua
new file mode 100644
index 0000000000..c5b00445ce
--- /dev/null
+++ b/share/scripts/_hook_test.lua
@@ -0,0 +1,41 @@
+ardour {
+ ["type"] = "EditorHook",
+ name = "Callback Example",
+ author = "Ardour Lua Task Force",
+ description = "Rewind On Solo Change, Write a file when regions are moved",
+}
+
+function signals ()
+ s = LuaSignal.Set()
+ --s:add ({[LuaSignal.SoloActive] = true, [LuaSignal.RegionPropertyChanged] = true})
+ s:add (
+ {
+ [LuaSignal.SoloActive] = true,
+ [LuaSignal.RegionPropertyChanged] = true
+ }
+ )
+ --for k,v in pairs (s:table()) do print (k, v) end
+ return s
+end
+
+function factory (params)
+ return function (signal, ref, ...)
+ print (signal, ref, ...)
+
+ if (signal == LuaSignal.SoloActive) then
+ Session:goto_start()
+ end
+
+ if (signal == LuaSignal.RegionPropertyChanged) then
+ obj,pch = ...
+ file = io.open ("/tmp/test" ,"a")
+ io.output (file)
+ io.write (string.format ("Region: '%s' pos-changed: %s, length-changed: %s\n",
+ obj:name (),
+ tostring (pch:containsSamplePos (ARDOUR.Properties.Start)),
+ tostring (pch:containsSamplePos (ARDOUR.Properties.Length))
+ ))
+ io.close (file)
+ end
+ end
+end
diff --git a/share/scripts/_insert_region_gaps.lua b/share/scripts/_insert_region_gaps.lua
new file mode 100644
index 0000000000..3a7f5ed8e9
--- /dev/null
+++ b/share/scripts/_insert_region_gaps.lua
@@ -0,0 +1,62 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Insert Gaps",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Insert gaps between all regions on selected tracks]]
+}
+
+function action_params ()
+ return
+ {
+ ["gap"] = { title = "Gap size (in sec)", default = "2" },
+ }
+end
+
+function factory () return function ()
+ -- get configuration
+ local p = params or {}
+ local gap = p["gap"] or 2
+ if gap <= 0 then gap = 2 end
+
+ local sel = Editor:get_selection () -- get current selection
+
+ local add_undo = false -- keep track of changes
+ Session:begin_reversible_command ("Insert Gaps")
+
+ -- iterate over all selected tracks
+ for route in sel.tracks:routelist ():iter () do
+ local track = route:to_track ()
+ if track:isnil () then goto continue end
+
+ -- get track's playlist
+ local playlist = track:playlist ()
+ local offset = 0
+
+ -- iterate over all regions in the playlist
+ for region in playlist:region_list():iter() do
+
+ -- preare for undo operation
+ region:to_stateful ():clear_changes ()
+
+ -- move region
+ region:set_position (region:position() + offset, 0)
+ offset = offset + Session:nominal_sample_rate () * gap
+
+ -- 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 (region:to_statefuldestructible ()):empty () then
+ add_undo = true
+ end
+ end
+ ::continue::
+ end
+
+ -- 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
+end end
diff --git a/share/scripts/_midi_lfo.lua b/share/scripts/_midi_lfo.lua
new file mode 100644
index 0000000000..fd8b2b07ef
--- /dev/null
+++ b/share/scripts/_midi_lfo.lua
@@ -0,0 +1,74 @@
+ardour {
+ ["type"] = "dsp",
+ name = "MIDI LFO",
+ category = "Example", -- Utility
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[MIDI CC LFO Example -- Triangle full scale CC Parameter automation]]
+}
+
+function dsp_ioconfig ()
+ return { { midi_in = 1, midi_out = 1, audio_in = 0, audio_out = 0}, }
+end
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "BPM", min = 40, max = 200, default = 60, unit="BPM"},
+ { ["type"] = "input", name = "CC", min = 0, max = 127, default = 1, integer = true },
+ }
+end
+
+local samplerate
+local time = 0
+local step = 0
+
+function dsp_init (rate)
+ samplerate = rate
+ local bpm = 120
+ spb = rate * 60 / bpm
+end
+
+function dsp_run (_, _, n_samples)
+ assert (type(midiin) == "table")
+ assert (type(midiout) == "table")
+
+ local ctrl = CtrlPorts:array ()
+ local bpm = ctrl[1]
+ local cc = ctrl[2]
+
+ local spb = samplerate * 60 / bpm -- samples per beat
+ local sps = spb / 254 -- samples per step (0..127..1 = 254 steps)
+
+ assert (sps > 1)
+ local i = 1
+ local m = 1
+
+ for ts = 1, n_samples do
+ time = time + 1
+
+ -- forward incoming midi
+ if i <= #midiin then
+ while midiin[i]["time"] == ts do
+ midiout[m] = midiin[i]
+ i = i + 1
+ m = m + 1
+ if i > #midiin then break end
+ end
+ end
+
+ -- inject LFO events
+ if time >= spb then
+ local val
+ if step > 127 then val = 254 - step else val = step end
+
+ midiout[m] = {}
+ midiout[m]["time"] = ts
+ midiout[m]["data"] = { 0xb0, cc, val }
+
+ m = m + 1
+ time = time - sps
+ if step == 253 then step = 0 else step = step + 1 end
+ end
+ end
+end
diff --git a/share/scripts/_midi_rec_start.lua b/share/scripts/_midi_rec_start.lua
new file mode 100644
index 0000000000..b2e03ba28b
--- /dev/null
+++ b/share/scripts/_midi_rec_start.lua
@@ -0,0 +1,37 @@
+ardour {
+ ["type"] = "session",
+ name = "MIDI Record Enable",
+ category = "Example", -- "Utility"
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An example script to start recording on note-on.]]
+}
+
+function factory ()
+ return function (n_samples)
+ if Session:actively_recording() then return end -- when recording already, do nothing
+ -- iterate over all MIDI ports
+ _, t = Session:engine ():get_ports (ARDOUR.DataType.midi (), ARDOUR.PortList ())
+ for p in t[2]:iter () do
+ -- skip output ports
+ if not p:receives_input () then goto next end
+ local midiport = p:to_midiport ()
+ -- and skip async event ports
+ if midiport:isnil () then goto next end
+ local mb = midiport:get_midi_buffer (n_samples) -- get the midi-data buffers
+ local events = mb:table () -- copy event list into lua table
+ for _,e in pairs (events) do -- iterate over all events in the midi-buffer
+ if (e:buffer():array()[1] & 0xf0) == 0x90 then -- note on
+ Session:maybe_enable_record (true) -- global record-enable from rt-context
+ -- maybe-enable may fail if there are no tracks or step-entry is active
+ -- roll transport if record-enable suceeded:
+ if ARDOUR.Session.RecordState.Enabled == Session:record_status() then
+ Session:request_transport_speed (1.0, true, ARDOUR.TransportRequestSource.TRS_UI) -- ...and go.
+ end
+ return
+ end
+ end
+ ::next::
+ end
+ end
+end
diff --git a/share/scripts/_midi_rewrite.lua b/share/scripts/_midi_rewrite.lua
new file mode 100644
index 0000000000..4dfc28a6c3
--- /dev/null
+++ b/share/scripts/_midi_rewrite.lua
@@ -0,0 +1,34 @@
+ardour {
+ ["type"] = "session",
+ name = "Rewrite Midi",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An example session script preprocesses midi buffers.]]
+}
+
+function factory ()
+ -- this function is called in every process cycle, before processing
+ return function (n_samples)
+ _, t = Session:engine ():get_ports (ARDOUR.DataType.midi (), ARDOUR.PortList ())
+ for p in t[2]:iter () do
+ if not p:receives_input () then goto next end
+
+ if not p:name () == "MIDI/midi_in 1" then goto next end
+
+ midiport = p:to_midiport ()
+ assert (not midiport:isnil ())
+ mb = midiport:get_midi_buffer (n_samples);
+
+ events = mb:table() -- copy event list into lua table
+ mb:silence (n_samples, 0); -- clear existing buffer
+
+ for _,e in pairs (events) do
+ -- e is-a http://manual.ardour.org/lua-scripting/class_reference/#Evoral:MidiEvent
+ e:set_channel (2)
+ mb:push_event (e)
+ end
+
+ ::next::
+ end
+ end
+end
diff --git a/share/scripts/_midifilter.lua b/share/scripts/_midifilter.lua
new file mode 100644
index 0000000000..48f61a8277
--- /dev/null
+++ b/share/scripts/_midifilter.lua
@@ -0,0 +1,39 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Midi Filter",
+ category = "Example", -- "Utility"
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An Example Midi Filter for prototyping.]]
+}
+
+function dsp_ioconfig ()
+ return { { midi_in = 1, midi_out = 1, audio_in = 0, audio_out = 0}, }
+end
+
+function dsp_run (_, _, n_samples)
+ assert (type(midiin) == "table")
+ assert (type(midiout) == "table")
+ local cnt = 1;
+
+ function tx_midi (time, data)
+ midiout[cnt] = {}
+ midiout[cnt]["time"] = time;
+ midiout[cnt]["data"] = data;
+ cnt = cnt + 1;
+ end
+
+ -- for each incoming midi event
+ for _,b in pairs (midiin) do
+ local t = b["time"] -- t = [ 1 .. n_samples ]
+ local d = b["data"] -- get midi-event
+ local event_type
+ if #d == 0 then event_type = -1 else event_type = d[1] >> 4 end
+
+ if (#d == 3 and event_type == 9) then -- note on
+ tx_midi (t, d)
+ elseif (#d == 3 and event_type == 8) then -- note off
+ tx_midi (t, d)
+ end
+ end
+end
diff --git a/share/scripts/_midigenerator.lua b/share/scripts/_midigenerator.lua
new file mode 100644
index 0000000000..9c093946f9
--- /dev/null
+++ b/share/scripts/_midigenerator.lua
@@ -0,0 +1,48 @@
+ardour {
+ ["type"] = "dsp",
+ name = "MIDI Generator",
+ category = "Example", -- "Utility"
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An Example Midi Generator for prototyping.]]
+}
+
+function dsp_ioconfig ()
+ return { { midi_out = 1, audio_in = 0, audio_out = 0}, }
+end
+
+local tme = 0 -- sample-counter
+local seq = 1 -- sequence-step
+local spb = 0 -- samples per beat
+
+local midi_sequence = {
+ { 0x90, 64, 127 },
+ { 0x80, 64, 0 },
+}
+
+function dsp_init (rate)
+ local bpm = 120
+ spb = rate * 60 / bpm
+ if spb < 2 then spb = 2 end
+end
+
+function dsp_run (_, _, n_samples)
+ assert (type(midiout) == "table")
+ assert (spb > 1)
+ local m = 1
+
+ for time = 1,n_samples do -- not very efficient
+ -- TODO, timestamp the sequence in beats, calc/skip to next event
+ tme = tme + 1
+
+ if tme >= spb then
+ midiout[m] = {}
+ midiout[m]["time"] = time
+ midiout[m]["data"] = midi_sequence[seq]
+
+ tme = 0
+ m = m + 1
+ if seq == #midi_sequence then seq = 1 else seq = seq + 1 end
+ end
+ end
+end
diff --git a/share/scripts/_midigenerator2.lua b/share/scripts/_midigenerator2.lua
new file mode 100644
index 0000000000..6bd904b097
--- /dev/null
+++ b/share/scripts/_midigenerator2.lua
@@ -0,0 +1,34 @@
+ardour {
+ ["type"] = "dsp",
+ name = "MIDI Generator II",
+ category = "Example",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An Example Midi Generator for prototyping.]]
+}
+
+function dsp_ioconfig ()
+ return { { midi_in = 1, midi_out = 1, audio_in = -1, audio_out = -1}, }
+end
+
+function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
+ local ob = out_map:get (ARDOUR.DataType ("midi"), 0)
+ if ob ~= ARDOUR.ChanMapping.Invalid then
+ local mb = bufs:get_midi (ob)
+
+ -- see _midigenerator.lua for
+ -- how to use a timed sequence
+
+ local ba = C.ByteVector () -- construct a byte vector
+ ba:add ({0x90, 64, 127}) -- add some data to the vector
+ -- send a message at cycle-start
+ mb:push_back (offset, ba:size (), ba:to_array());
+
+ ba:clear ()
+ ba:add ({0x80, 64, 127})
+ mb:push_back (n_samples - 1 - offset, ba:size (), ba:to_array());
+ end
+
+ -- passthrough audio, apply pin/channel mapping
+ ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("audio"))
+end
diff --git a/share/scripts/_osc_hook_example.lua b/share/scripts/_osc_hook_example.lua
new file mode 100644
index 0000000000..2ef81cd5bb
--- /dev/null
+++ b/share/scripts/_osc_hook_example.lua
@@ -0,0 +1,52 @@
+ardour {
+ ["type"] = "EditorHook",
+ name = "OSC Callback Example",
+ author = "Ardour Lua Task Force",
+ description = "Send OSC messages",
+}
+
+function action_params ()
+ return
+ {
+ ["uri"] = { title = "OSC URI ", default = "osc.udp://localhost:7890"},
+ }
+end
+
+
+function signals ()
+ s = LuaSignal.Set()
+ s:add (
+ {
+ [LuaSignal.SoloActive] = true,
+ [LuaSignal.RegionPropertyChanged] = true,
+ [LuaSignal.Exported] = true,
+ [LuaSignal.TransportStateChange] = true
+ }
+ )
+ return s
+end
+
+function factory (params)
+ return function (signal, ref, ...)
+ local uri = params["uri"] or "osc.udp://localhost:7890"
+ local tx = ARDOUR.LuaOSC.Address (uri)
+ -- debug print (stdout)
+ -- print (signal, ref, ...)
+
+ if (signal == LuaSignal.Exported) then
+ tx:send ("/session/exported", "ss", ...)
+ elseif (signal == LuaSignal.SoloActive) then
+ tx:send ("/session/solo_changed", "")
+ elseif (signal == LuaSignal.TransportStateChange) then
+ tx:send ("/session/transport", "if",
+ Session:transport_sample(), Session:transport_speed())
+ elseif (signal == LuaSignal.RegionPropertyChanged) then
+ obj,pch = ...
+ tx:send ("/region_property_changed", "sTTiii",
+ obj:name (),
+ (pch:containsSamplePos (ARDOUR.Properties.Start)),
+ (pch:containsSamplePos (ARDOUR.Properties.Length)),
+ obj:position (), obj:start (), obj:length ())
+ end
+ end
+end
diff --git a/share/scripts/_plot_graph.lua b/share/scripts/_plot_graph.lua
new file mode 100644
index 0000000000..9c8876dbbb
--- /dev/null
+++ b/share/scripts/_plot_graph.lua
@@ -0,0 +1,24 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Plot Process Graph",
+ author = "Ardour Team",
+ description = [[Export process graph to a graphviz file, and launch xdot]]
+}
+
+function factory () return function ()
+ if Session:plot_process_graph ("/tmp/ardour_graph.gv") then
+ os.forkexec ("/bin/sh", "-c", "xdot /tmp/ardour_graph.gv")
+ end
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ local txt = Cairo.PangoLayout (ctx, "Sans ".. math.ceil(height / 3) .. "px")
+ txt:set_alignment (Cairo.Alignment.Center);
+ txt:set_width (width);
+ txt:set_ellipsize (Cairo.EllipsizeMode.Middle);
+ txt:set_text ("plot\ngrph")
+ local tw, th = txt:get_pixel_size ()
+ ctx:move_to (0, .5 * (height - th))
+ txt:show_in_cairo_context (ctx)
+end end
diff --git a/share/scripts/_pong.lua b/share/scripts/_pong.lua
new file mode 100644
index 0000000000..8eef49f004
--- /dev/null
+++ b/share/scripts/_pong.lua
@@ -0,0 +1,260 @@
+ardour {
+ ["type"] = "dsp",
+ name = "a-Pong",
+ category = "Toy",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[A console classic for your console]]
+}
+
+-- return possible i/o configurations
+function dsp_ioconfig ()
+ -- -1, -1 = any number of channels as long as input and output count matches
+ return { [1] = { audio_in = -1, audio_out = -1}, }
+end
+
+-- control port(s)
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "Bar", min = 0, max = 1, default = 0.5 },
+ { ["type"] = "input", name = "Reset", min = 0, max = 1, default = 0, toggled = true },
+ { ["type"] = "input", name = "Difficulty", min = 1, max = 10, default = 3},
+ }
+end
+
+
+-- Game State (for this instance)
+-- NOTE: these variables are for the DSP part (not shared with the GUI instance)
+local sample_rate -- sample-rate
+local fps -- audio samples per game-step
+local game_time -- counts up to fps
+local game_score
+local ball_x, ball_y -- ball position [0..1]
+local dx, dy -- current ball speed
+local lost_sound -- audio-sample counter for game-over [0..3*fps]
+local ping_sound -- audio-sample counter for ping-sound [0..fps]
+local ping_phase -- ping note phase-difference per sample
+local ping_pitch
+
+function dsp_init (rate)
+ -- allocate a "shared memory" area to transfer state to the GUI
+ self:shmem ():allocate (3)
+ self:shmem ():clear ()
+ -- initialize some variables
+ sample_rate = rate
+ fps = rate / 25
+ ping_pitch = 752 / rate
+ ball_x = 0.5
+ ball_y = 0
+ dx = 0.00367
+ dy = 0.01063
+ game_score = 0
+ game_time = fps -- start the ball immediately (notify GUI)
+ ping_sound = fps -- set to end of synth cycle
+ lost_sound = 3 * fps
+end
+
+function queue_beep ()
+ -- queue 'ping' sound (unless one is already playing to prevent clicks)
+ if (ping_sound >= fps) then
+ -- major scale, 2 octaves
+ local scale = { 0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24 }
+ local midi_note = 60 + scale[1 + math.floor (math.random () * 14)]
+ ping_pitch = (440 / 32) * 2^((midi_note - 10.0) / 12.0) / sample_rate
+ ping_sound = 0
+ ping_phase = 0
+ end
+end
+
+-- callback: process "n_samples" of audio
+-- ins, outs are http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+-- pointers to the audio buffers
+function dsp_run (ins, outs, n_samples)
+ local ctrl = CtrlPorts:array () -- get control port array (read/write)
+
+ local changed = false -- flag to notify GUI on every game-step
+ game_time = game_time + n_samples
+
+ -- reset (allow to write automation from a given start-point)
+ -- ctrl[2] corresponds to the "Reset" input control
+ if ctrl[2] > 0 then
+ game_time = 0
+ ball_x = 0.5
+ ball_y = 0
+ dx = 0.00367
+ dy = 0.01063
+ game_score = 0
+ end
+
+ -- simple game engine
+ while game_time > fps and ctrl[2] <= 0 do
+ changed = true
+ game_time = game_time - fps
+
+ -- move the ball
+ ball_x = ball_x + dx * ctrl[3]
+ ball_y = ball_y + dy * ctrl[3]
+
+ -- reflect left/right
+ if ball_x >= 1 or ball_x <= 0 then
+ dx = -dx
+ queue_beep ()
+ end
+
+ -- single player (reflect top) -- TODO "stereo" version, 2 ctrls :)
+ if ball_y <= 0 then
+ dy = - dy y = 0
+ queue_beep ()
+ end
+
+ -- keep the ball in the field at all times
+ if ball_x >= 1 then ball_x = 1 end
+ if ball_x <= 0 then ball_x = 0 end
+
+ -- bottom edge
+ if ball_y > 1 then
+ local bar = ctrl[1] -- get bar position
+ if math.abs (bar - ball_x) < 0.1 then
+ -- reflect the ball
+ dy = - dy
+ ball_y = 1.0
+ dx = dx - 0.04 * (bar - ball_x)
+ -- make sure it's moving (not stuck on borders)
+ if math.abs (dx) < 0.0001 then dx = 0.0001 end
+ game_score = game_score + 1
+ queue_beep ()
+ else
+ -- game over, reset game
+ lost_sound = 0 -- re-start noise
+ ball_y = 0
+ game_score = 0
+ dx = 0.00367
+ end
+ end
+ end
+
+ -- forward audio if processing is not in-place
+ for c = 1,#outs do
+ -- check if output and input buffers for this channel are identical
+ -- http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+ if ins[c] ~= outs[c] then
+ -- fast (accelerated) copy
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP
+ ARDOUR.DSP.copy_vector (outs[c], ins[c], n_samples)
+ end
+ end
+
+ -- simple synth -- TODO Optimize
+ if ping_sound < fps then
+ -- cache audio data buffers for direct access, later
+ local abufs = {}
+ for c = 1,#outs do
+ abufs[c] = outs[c]:array()
+ end
+ -- simple sine synth with a sine-envelope
+ for s = 1, n_samples do
+ ping_sound = ping_sound + 1
+ ping_phase = ping_phase + ping_pitch
+ local snd = 0.7 * math.sin(6.283185307 * ping_phase) * math.sin (3.141592 * ping_sound / fps)
+ -- add synthesized sound to all channels
+ for c = 1,#outs do
+ abufs[c][s] = abufs[c][s] + snd
+ end
+ -- break out of the loop when the sound finished
+ if ping_sound >= fps then goto ping_end end
+ end
+ ::ping_end::
+ end
+
+ if lost_sound < 3 * fps then
+ local abufs = {}
+ for c = 1,#outs do
+ abufs[c] = outs[c]:array()
+ end
+ for s = 1, n_samples do
+ lost_sound = lost_sound + 1
+ -- -12dBFS white noise
+ local snd = 0.5 * (math.random () - 0.5)
+ for c = 1,#outs do
+ abufs[c][s] = abufs[c][s] + snd
+ end
+ if lost_sound >= 3 * fps then goto noise_end end
+ end
+ ::noise_end::
+ end
+
+ if changed then
+ -- notify the GUI
+ local shmem = self:shmem () -- get the shared memory region
+ local state = shmem:to_float (0):array () -- "cast" into lua-table
+ -- update data..
+ state[1] = ball_x
+ state[2] = ball_y
+ state[3] = game_score
+ -- ..and wake up the UI
+ self:queue_draw ()
+ end
+end
+
+
+-------------------------------------------------------------------------------
+--- inline display
+
+local txt = nil -- cache font description (in GUI context)
+
+function render_inline (ctx, w, max_h)
+ local ctrl = CtrlPorts:array () -- control port array
+ local shmem = self:shmem () -- shared memory region (game state from DSP)
+ local state = shmem:to_float (0):array () -- cast to lua-table
+
+ if (w > max_h) then
+ h = max_h
+ else
+ h = w
+ end
+
+ -- prepare text rendering
+ if not txt then
+ -- allocate PangoLayout and set font
+ --http://manual.ardour.org/lua-scripting/class_reference/#Cairo:PangoLayout
+ txt = Cairo.PangoLayout (ctx, "Mono 10px")
+ end
+
+ -- ctx is-a http://manual.ardour.org/lua-scripting/class_reference/#Cairo:Context
+ -- 2D vector graphics http://cairographics.org/
+
+ -- clear background
+ ctx:rectangle (0, 0, w, h)
+ ctx:set_source_rgba (.2, .2, .2, 1.0)
+ ctx:fill ()
+
+ -- print the current score
+ if (state[3] > 0) then
+ txt:set_text (string.format ("%.0f", state[3]));
+ local tw, th = txt:get_pixel_size ()
+ ctx:set_source_rgba (1, 1, 1, 1.0)
+ ctx:move_to (w - tw - 3, 3)
+ txt:show_in_cairo_context (ctx)
+ end
+
+ -- prepare line and dot rendering
+ ctx:set_line_cap (Cairo.LineCap.Round)
+ ctx:set_line_width (3.0)
+ ctx:set_source_rgba (.8, .8, .8, 1.0)
+
+ -- display bar
+ local bar_width = w * .1
+ local bar_space = w - bar_width
+
+ ctx:move_to (bar_space * ctrl[1], h - 3)
+ ctx:rel_line_to (bar_width, 0)
+ ctx:stroke ()
+
+ -- display ball
+ ctx:move_to (1 + state[1] * (w - 3), state[2] * (h - 5))
+ ctx:close_path ()
+ ctx:stroke ()
+
+ return {w, h}
+end
diff --git a/share/scripts/_rawmidi.lua b/share/scripts/_rawmidi.lua
new file mode 100644
index 0000000000..dba45f5483
--- /dev/null
+++ b/share/scripts/_rawmidi.lua
@@ -0,0 +1,110 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Midi Passthru",
+ category = "Example",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An Example Audio/MIDI Passthrough Plugin using Buffer Pointers]]
+}
+
+-- return possible audio i/o configurations
+function dsp_ioconfig ()
+ -- -1, -1 = any number of channels as long as input and output count matches
+ -- require 1 MIDI in, 1 MIDI out.
+ return { { midi_in = 1, midi_out = 1, audio_in = -1, audio_out = -1}, }
+end
+
+-- "dsp_runmap" uses Ardour's internal processor API, eqivalent to
+-- 'connect_and_run()". There is no overhead (mapping, translating buffers).
+-- The lua implementation is responsible to map all the buffers directly.
+function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
+
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:ChanMapping
+
+ local ib = in_map:get (ARDOUR.DataType ("midi"), 0) -- get index of the 1st mapped midi input buffer
+
+ if ib ~= ARDOUR.ChanMapping.Invalid then
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:MidiBuffer
+ local mb = bufs:get_midi (ib) -- get the mapped buffer
+ local events = mb:table () -- copy event list into a lua table
+
+ -- iterate over all MIDI events
+ for _, e in pairs (events) do
+ -- e is-a http://manual.ardour.org/lua-scripting/class_reference/#Evoral:MidiEvent
+
+ -- do something with the event e.g.
+ print (e:channel (), e:time (), e:size (), e:buffer ():array ()[1], e:buffer ():get_table (e:size ())[1])
+ end
+ end
+
+ ----
+ -- The following code is needed with "dsp_runmap" to work for arbitrary pin connections
+ -- this passes though all audio/midi data unprocessed.
+
+ ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("audio"))
+ ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("midi"))
+
+ -- equivalent lua code.
+ -- NOTE: the lua implementation below is intended for io-config [-1,-1].
+ -- It only works for actually mapped channels due to in_map:count() out_map:count()
+ -- being identical to the i/o pin count in this case.
+ --
+ -- Plugins that have multiple possible configurations will need to implement
+ -- dsp_configure() and remember the actual channel count.
+ --
+ -- ARDOUR.DSP.process_map() does iterate over the mapping itself and works generally.
+ -- Still the lua code below does lend itself as elaborate example.
+ --
+ --[[
+
+ local audio_ins = in_map:count (): n_audio () -- number of mapped audio input buffers
+ local audio_outs = out_map:count (): n_audio () -- number of mapped audio output buffers
+ assert (audio_outs, audio_ins) -- ioconfig [-1, -1]: must match
+
+ -- copy audio data if any
+ for c = 1, audio_ins do
+ local ib = in_map:get (ARDOUR.DataType ("audio"), c - 1) -- get index of mapped input buffer
+ local ob = out_map:get (ARDOUR.DataType ("audio"), c - 1) -- get index of mapped output buffer
+ if ib ~= ARDOUR.ChanMapping.Invalid and ob ~= ARDOUR.ChanMapping.Invalid and ib ~= ob then
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:AudioBuffer
+ ARDOUR.DSP.copy_vector (bufs:get_audio (ob):data (offset), bufs:get_audio (ib):data (offset), n_samples)
+ end
+ end
+ -- Clear unconnected output buffers.
+ -- In case we're processing in-place some buffers may be identical,
+ -- so this must be done *after* copying relvant data from that port.
+ for c = 1, audio_outs do
+ local ib = in_map:get (ARDOUR.DataType ("audio"), c - 1)
+ local ob = out_map:get (ARDOUR.DataType ("audio"), c - 1)
+ if ib == ARDOUR.ChanMapping.Invalid and ob ~= ARDOUR.ChanMapping.Invalid then
+ bufs:get_audio (ob):silence (n_samples, offset)
+ end
+ end
+
+ -- copy midi data
+ local midi_ins = in_map:count (): n_midi () -- number of midi input buffers
+ local midi_outs = out_map:count (): n_midi () -- number of midi input buffers
+
+ -- with midi_in=1, midi_out=1 in dsp_ioconfig
+ -- the following will always be true
+ assert (midi_ins == 1)
+ assert (midi_outs == 1)
+
+ for c = 1, midi_ins do
+ local ib = in_map:get (ARDOUR.DataType ("midi"), c - 1)
+ local ob = out_map:get (ARDOUR.DataType ("midi"), c - 1)
+ if ib ~= ARDOUR.ChanMapping.Invalid and ob ~= ARDOUR.ChanMapping.Invalid and ib ~= ob then
+ bufs:get_midi (ob):copy (bufs:get_midi (ib))
+ end
+ end
+ -- silence unused midi outputs
+ for c = 1, midi_outs do
+ local ib = in_map:get (ARDOUR.DataType ("midi"), c - 1)
+ local ob = out_map:get (ARDOUR.DataType ("midi"), c - 1)
+ if ib == ARDOUR.ChanMapping.Invalid and ob ~= ARDOUR.ChanMapping.Invalid then
+ bufs:get_midi (ob):silence (n_samples, offset)
+ end
+ end
+ --]]
+end
diff --git a/share/scripts/_region_transients.lua b/share/scripts/_region_transients.lua
new file mode 100644
index 0000000000..de42992dd1
--- /dev/null
+++ b/share/scripts/_region_transients.lua
@@ -0,0 +1,16 @@
+ardour { ["type"] = "Snippet", name = "Region Transient List" }
+
+function factory () return function ()
+ local sel = Editor:get_selection ()
+ for r in sel.regions:regionlist ():iter () do
+ local region_pos = r:position()
+ local region_off = r:start()
+ print (r:name(), r:position(), r:start())
+ local trans = r:transients()
+ for t in trans:iter() do
+ -- print absolute timeline position of transients
+ print (t + region_pos - region_off)
+ end
+ print ("----")
+ end
+end end
diff --git a/share/scripts/_remember_file.lua b/share/scripts/_remember_file.lua
new file mode 100644
index 0000000000..7a79fc861b
--- /dev/null
+++ b/share/scripts/_remember_file.lua
@@ -0,0 +1,37 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "File Name Test",
+ author = "Ardour Lua Taskforce",
+ description = [[Example Plugin to show to to select a file and remember the most recently used file.]]
+}
+
+function factory ()
+ local file_name_testscript_last_filename -- this acts as "global" variable, use a unique name
+ return function ()
+ print (file_name_testscript_last_filename) -- debug
+
+ --set filename to most recently used, fall back to use a default
+ local fn = file_name_testscript_last_filename or ARDOUR.LuaAPI.build_filename (Session:path (), Session:name () .. ".ardour")
+
+ -- prepare a dialog
+ local dialog_options = {
+ { type = "file", key = "file", title = "Select a File", path = fn }
+ }
+
+ -- show dialog
+ local od = LuaDialog.Dialog ("title", dialog_options)
+ local rv = od:run()
+
+ if rv then
+ -- remember most recently selected file
+ file_name_testscript_last_filename = rv['file']
+ LuaDialog.Message ("title", "set path to " .. file_name_testscript_last_filename, LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
+ else
+ -- unset most recently used filename on dialog "cancel"
+ file_name_testscript_last_filename = nil
+ end
+
+ od = nil
+ collectgarbage ()
+ end
+end
diff --git a/share/scripts/_rewind.lua b/share/scripts/_rewind.lua
new file mode 100644
index 0000000000..88e150612c
--- /dev/null
+++ b/share/scripts/_rewind.lua
@@ -0,0 +1,12 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Rewind",
+ author = "Ardour Lua Task Force",
+ description = [[An Example Ardour Editor Action Script.]]
+}
+
+function factory (params)
+ return function ()
+ Session:goto_start()
+ end
+end
diff --git a/share/scripts/_rgh_midi_track_trick.lua b/share/scripts/_rgh_midi_track_trick.lua
new file mode 100644
index 0000000000..85308be0b1
--- /dev/null
+++ b/share/scripts/_rgh_midi_track_trick.lua
@@ -0,0 +1,81 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Rob's 16 MIDI Trick Pony",
+ description = [[clearly broken approach to go about things]]
+}
+
+function route_setup ()
+ return {
+ ['Insert_at'] = ARDOUR.PresentationInfo.max_order,
+ ['name'] = 'Sweet16',
+ ['group'] = false, -- return value will be a RouteGroup* or nil
+ }
+end
+
+function factory (p) return function ()
+ local name = "Sweet16"
+ local insert_at = ARDOUR.PresentationInfo.max_order
+ local group = nil
+
+ -- check for "MIDI Channel Map" LV2 from x42 midifilters.lv2
+ if ARDOUR.LuaAPI.new_plugin_info ("http://gareus.org/oss/lv2/midifilter#channelmap", ARDOUR.PluginType.LV2):isnil () then
+ LuaDialog.Message ("16 MIDI Tracks", "Error: Plugin 'MIDI Simple Channel Map' was not found.", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
+ return
+ end
+
+ if type (p) == 'table' and p['how_many'] ~= nil then
+ -- used from the AddRouteDialog (or w/action_params)
+ name = p["name"] or 'Sweet16'
+ insert_at = p["insert_at"] or ARDOUR.PresentationInfo.max_order;
+ group = p["group"] or nil
+ else
+ -- used standalone, prompt for name and insert position
+ local dialog_options = {
+ { type = "entry", key = "name", default = 'Sweet16', title = "Name Prefix" },
+ { type = "entry", key = "group", default = '', title = "Group (empty for none)" },
+ { type = "dropdown", key = "insertpos", title = "Position", default = "Last", values =
+ {
+ ["First"] = ArdourUI.InsertAt.First,
+ ["Before Selection"] = ArdourUI.InsertAt.BeforeSelection,
+ ["After Selection"] = ArdourUI.InsertAt.AfterSelection,
+ ["Last"] = ArdourUI.InsertAt.Last
+ }
+ }
+ }
+
+ local od = LuaDialog.Dialog ("16 MIDI Tracks", dialog_options)
+ local rv = od:run()
+ if (not rv) then return end
+ name = rv['name'] or 'Sweet16'
+ if rv['insertpos'] then
+ insert_at = ArdourUI.translate_order (rv['insertpos'])
+ end
+ if rv['group'] and rv['group'] ~= '' then
+ group = Session:new_route_group (rv['group'])
+ end
+ end
+ collectgarbage ()
+
+ -- all systems go
+
+ local tl = Session:new_midi_track (
+ ARDOUR.ChanCount(ARDOUR.DataType ("midi"), 1),
+ ARDOUR.ChanCount(ARDOUR.DataType ("midi"), 1),
+ true, -- strict i/o
+ ARDOUR.PluginInfo(), nil, -- no instrument, no instrument preset
+ group,
+ 16, -- how many
+ name, insert_at, ARDOUR.TrackMode.Normal)
+
+ local i = 1
+ for track in tl:iter() do
+ local p = ARDOUR.LuaAPI.new_plugin(Session, "http://gareus.org/oss/lv2/midifilter#channelmap", ARDOUR.PluginType.LV2, "")
+ assert (not p:isnil ())
+ track:add_processor_by_index(p, 0, nil, true)
+ for j = 1, 16 do
+ ARDOUR.LuaAPI.set_processor_param (p, j, i)
+ end
+ i = i + 1
+ end
+ collectgarbage () -- drop references to tracks.
+end end
diff --git a/share/scripts/_route_template_generic_audio.lua b/share/scripts/_route_template_generic_audio.lua
new file mode 100644
index 0000000000..7dabce8aef
--- /dev/null
+++ b/share/scripts/_route_template_generic_audio.lua
@@ -0,0 +1,119 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Generic Audio Track",
+ description = [[Add Audio tracks, by default as many as there are physical inputs]]
+}
+
+-- If a route_setup function is present in an Editor Action Script
+-- the script is also listed in the "add track/bus" dialog as meta-template
+--
+-- The function is expected to return a Lua table. The table may be empty.
+function route_setup ()
+ local e = Session:engine()
+ local _, t = e:get_backend_ports ("", ARDOUR.DataType("audio"), ARDOUR.PortFlags.IsOutput | ARDOUR.PortFlags.IsPhysical, C.StringVector())
+ return
+ {
+ -- keys control which AddRouteDialog controls are made sensitive.
+ -- The following keys accept a default value to pre-seed the dialog.
+ ['how_many'] = t[4]:size(),
+ ['name'] = 'Audio',
+ ['channels'] = 2,
+ ['track_mode'] = ARDOUR.TrackMode.Normal,
+ ['strict_io'] = true,
+ -- these keys just need to be set (to something other than nil)
+ -- in order to set the control sensitives
+ ['insert_at'] = ARDOUR.PresentationInfo.max_order,
+ ['group'] = false, -- return value will be a RouteGroup*
+ ['instrument'] = nil, -- return value will be a PluginInfoPtr
+ }
+end
+
+-- The Script can be used as EditorAction in which case it *could*
+-- optionally provide instantiation parmaters..
+--[[
+function action_params ()
+ return
+ {
+ ['how_many'] = { title = "How Many tracks to add", default = "1" },
+ ["name"] = { title = "Track Name Prefix", default = "Audio" },
+ }
+end
+--]]
+
+
+function factory (p)
+ -- when used from the AddRouteDialog (or w/action_params)
+ if type (p) == 'table' and p['how_many'] ~= nil then
+ return function ()
+ -- When called from the AddRouteDialog, 'p' will be a table with
+ -- keys as described in route_setup() above.
+ local name = p["name"] or 'Audio'
+ local how_many = p["how_many"] or 1
+ local channels = p["channels"] or 1
+ local insert_at = p["insert_at"] or ARDOUR.PresentationInfo.max_order;
+ local group = p["group"] or nil
+ local mode = p["track_mode"] or ARDOUR.TrackMode.Normal
+ local strict_io = p["strict_io"] or false
+ local chan_out = 0
+
+ if ARDOUR.config():get_output_auto_connect() == ARDOUR.AutoConnectOption.AutoConnectMaster then
+ if not Session:master_out():isnil() then
+ chan_out = Session:master_out():n_inputs ():n_audio ()
+ end
+ end
+
+ if chan_out == 0 then
+ chan_out = channels;
+ end
+
+ local tl = Session:new_audio_track (channels, chan_out, group, how_many, name, insert_at, mode)
+
+ if strict_io then
+ for t in tl:iter() do
+ t:set_strict_io (true)
+ end
+ end
+ end
+ end
+ -- when used as standalone (no action parameters): interactive
+ return function ()
+ local e = Session:engine()
+ local _, t = e:get_backend_ports ("", ARDOUR.DataType("audio"), ARDOUR.PortFlags.IsOutput | ARDOUR.PortFlags.IsPhysical, C.StringVector())
+ local tracks = t[4]:size();
+
+ local dialog_options = {
+ { type = "number", key = "tracks", title = "Create Tracks", min = 1, max = 128, step = 1, digits = 0, default = tracks },
+ { type = "entry", key = "name", default = 'Audio', title = "Name Prefix" },
+ { type = "checkbox", key = "stereo", default = false, title = "Stereo" },
+ { type = "checkbox", key = "recarm", default = false, title = "Record Arm Tracks" },
+ }
+
+ local dlg = LuaDialog.Dialog ("Create Audio Tracks", dialog_options)
+ local rv = dlg:run()
+ if (not rv or rv['tracks'] == 0) then
+ return
+ end
+
+ local chan_in = stereo and 2 or 1
+ local chan_out = 0
+
+ if ARDOUR.config():get_output_auto_connect() == ARDOUR.AutoConnectOption.AutoConnectMaster then
+ if not Session:master_out():isnil() then
+ chan_out = Session:master_out():n_inputs ():n_audio ()
+ end
+ end
+
+ if chan_out == 0 then
+ chan_out = chan_in;
+ end
+
+ -- create tracks
+ local tl = Session:new_audio_track (chan_in, chan_out, nil, rv['tracks'], "", ARDOUR.PresentationInfo.max_order, ARDOUR.TrackMode.Normal)
+ -- and optionally record-arm them
+ if rv['recarm'] then
+ for track in tl:iter() do
+ track:rec_enable_control ():set_value (1, PBD.GroupControlDisposition.NoGroup)
+ end
+ end
+ end
+end
diff --git a/share/scripts/_route_template_generic_midi.lua b/share/scripts/_route_template_generic_midi.lua
new file mode 100644
index 0000000000..a62197d137
--- /dev/null
+++ b/share/scripts/_route_template_generic_midi.lua
@@ -0,0 +1,78 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Generic MIDI Track",
+ description = [[Example]]
+}
+
+-- If a route_setup function is present in an Editor Action Script
+-- the script is also listed in the "add track/bus" dialog as meta-template
+--
+-- The function is expected to return a Lua table. The table may be empty.
+function route_setup ()
+ return
+ {
+ -- keys control which AddRouteDialog controls are made sensitive.
+ -- The following keys accept a default value to pre-seed the dialog.
+ ['how_many'] = 1,
+ ['name'] = 'MIDI',
+ ['channels'] = nil,
+ ['track_mode'] = nil,
+ ['strict_io'] = true,
+ -- these keys just need to be set (to something other than nil)
+ -- in order to set the control sensitives
+ ['insert_at'] = ARDOUR.PresentationInfo.max_order,
+ ['group'] = false, -- return value will be a RouteGroup*
+ ['instrument'] = true, -- return value will be a PluginInfoPtr
+ }
+end
+
+-- The Script can be used as EditorAction in which case it can
+-- optionally provide instantiation parmaters
+function action_params ()
+ return
+ {
+ ['how_many'] = { title = "How Many tracks to add", default = "1" },
+ ["name"] = { title = "Track Name Prefix", default = "MIDI" },
+ ["instrument"] = { title = "Add Instrument", default = "true" },
+ }
+end
+
+
+function factory (params) return function ()
+ -- When called from the AddRouteDialog, 'params' will be a table with
+ -- keys as described in route_setup() above.
+
+ local p = params or route_setup ()
+ local name = p["name"] or 'Audio'
+ local how_many = p["how_many"] or 1
+ local insert_at = p["insert_at"] or ARDOUR.PresentationInfo.max_order;
+ local group = p["group"] or nil
+ local strict_io = p["strict_io"] or false
+ local instrument = p["instrument"] or nil
+
+ -- used in 'action-script mode'
+ if instrument == "true" then
+ instrument = ARDOUR.LuaAPI.new_plugin_info ("http://gareus.org/oss/lv2/gmsynth", ARDOUR.PluginType.LV2) -- general midi synth
+ if instrument:isnil () then
+ instrument = ARDOUR.LuaAPI.new_plugin_info ("https://community.ardour.org/node/7596", ARDOUR.PluginType.LV2) -- reasonable synth
+ end
+ if instrument:isnil () then
+ LuaDialog.Message ("MIDI track add", "Cannot find instrument plugin",
+ LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
+ return
+ end
+ end
+
+ -- add no instrument
+ if type (instrument) ~= "userdata" then
+ instrument = ARDOUR.PluginInfo ()
+ end
+
+ Session:new_midi_track(
+ ARDOUR.ChanCount(ARDOUR.DataType ("midi"), 1),
+ ARDOUR.ChanCount(ARDOUR.DataType ("audio"), 2),
+ strict_io,
+ instrument, nil,
+ group, how_many, name, insert_at, ARDOUR.TrackMode.Normal)
+
+end end
diff --git a/share/scripts/_rubberband_swing.lua b/share/scripts/_rubberband_swing.lua
new file mode 100644
index 0000000000..f9c703284d
--- /dev/null
+++ b/share/scripts/_rubberband_swing.lua
@@ -0,0 +1,175 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Swing It (Rubberband)",
+ license = "MIT",
+ author = "Ardour Team",
+description = [[
+Create a 'swing feel' in selected regions.
+
+The beat position of selected audio regions is analyzed,
+then the audio is time-stretched, moving 8th notes back in
+time while keeping 1/4-note beats in place to produce
+a rhythmic swing style.
+
+(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.
+ -- https://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Readable
+ -- This allows to read from the master-source of the given audio-region.
+ -- Any prior time-stretch or pitch-shift are ignored when reading, however
+ -- processing retains the previous settings
+ 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
+
+ -- construct a progress-dialog with cancle button
+ local pdialog = LuaDialog.ProgressWindow ("Rubberband", true)
+ -- progress dialog callbacks
+ 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 state (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
+
+ -- configure rubberband stretch tool
+ rb:set_strech_and_pitch (1, 1) -- no overall stretching, no pitch-shift
+ rb:set_mapping (beat_map) -- apply beat-map from/to
+
+ -- now stretch the region
+ 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
+
+ -- clean up, unload vamp plugin
+ vamp = nil
+ collectgarbage ()
+end end
+
+
+function icon (params) return function (ctx, width, height, fg)
+ local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil(width * .7) .. "px")
+ txt:set_text ("\u{266b}\u{266a}") -- 8th note symbols
+ local tw, th = txt:get_pixel_size ()
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ ctx:move_to (.5 * (width - tw), .5 * (height - th))
+ txt:show_in_cairo_context (ctx)
+end end
diff --git a/share/scripts/_session_load_hook.lua b/share/scripts/_session_load_hook.lua
new file mode 100644
index 0000000000..82546814f8
--- /dev/null
+++ b/share/scripts/_session_load_hook.lua
@@ -0,0 +1,32 @@
+ardour {
+ ["type"] = "EditorHook",
+ name = "Load Session Hook Example",
+ author = "Ardour Lua Task Force",
+ description = "Display some dialogs during session load and execute actions",
+}
+
+-- subscribe to signals
+-- http://manual.ardour.org/lua-scripting/class_reference/#LuaSignal.LuaSignal
+function signals ()
+ s = LuaSignal.Set()
+ s:add ({[LuaSignal.SetSession] = true})
+ return s
+end
+
+-- create callback functions
+function factory () return function (signal, ...)
+ assert (signal == LuaSignal.SetSession)
+ local md = LuaDialog.Message ("Set Session", "Loading Session:" .. Session:name(), LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close)
+ md:run()
+
+ local dialog_options = {
+ { type = "checkbox", key = "tempo", default = true, title = "Show Tempo Ruler" },
+ { type = "checkbox", key = "meter", default = true, title = "Show Meter Ruler" },
+ }
+ local dlg = LuaDialog.Dialog ("Tweak Rulers", dialog_options)
+ local rv = dlg:run()
+ if (rv) then
+ Editor:set_toggleaction ("Rulers", "toggle-tempo-ruler", rv['tempo'])
+ Editor:set_toggleaction ("Rulers", "toggle-meter-ruler", rv['meter'])
+ end
+end end
diff --git a/share/scripts/_session_test.lua b/share/scripts/_session_test.lua
new file mode 100644
index 0000000000..f6adea38c7
--- /dev/null
+++ b/share/scripts/_session_test.lua
@@ -0,0 +1,34 @@
+ardour {
+ ["type"] = "session",
+ name = "Good Night",
+ author = "Ardour Lua Task Force",
+ description = [[
+ Example Ardour Session Script.
+ Session scripts are called at the beginning of every process-callback (before doing any audio processing).
+ This example stops the transport after rolling for a configurable time which can be set when instantiating the script.]]
+}
+
+function sess_params ()
+ return
+ {
+ ["print"] = { title = "Debug Print (yes/no)", default = "no", optional = true },
+ ["time"] = { title = "Timeout (sec)", default = "90", optional = false },
+ }
+end
+
+function factory (params)
+ return function (n_samples)
+ local p = params["print"] or "no"
+ local timeout = params["time"] or 90
+ a = a or 0
+ if p ~= "no" then print (a, n_samples, Session:sample_rate (), Session:transport_rolling ()) end -- debug output (not rt safe)
+ if (not Session:transport_rolling()) then
+ a = 0
+ return
+ end
+ a = a + n_samples
+ if (a > timeout * Session:sample_rate()) then
+ Session:request_transport_speed(0.0, true, ARDOUR.TransportRequestSource.TRS_Engine)
+ end
+ end
+end
diff --git a/share/scripts/_smash.lua b/share/scripts/_smash.lua
new file mode 100644
index 0000000000..2ebcac19aa
--- /dev/null
+++ b/share/scripts/_smash.lua
@@ -0,0 +1,31 @@
+ardour { ["type"] = "dsp", name = "Sound Smasher", category = "Dynamics", license = "MIT", author = "Ardour Lua Task Force", description = [[Another simple DSP example]] }
+
+function dsp_ioconfig () return
+ -- -1, -1 = any number of channels as long as input and output count matches
+ { { audio_in = -1, audio_out = -1}, }
+end
+
+
+-- the DSP callback function to process audio audio
+-- "ins" and "outs" are http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+function dsp_run (ins, outs, n_samples)
+ for c = 1, #outs do -- for each output channel (count from 1 to number of output channels)
+
+ if ins[c] ~= outs[c] then -- if processing is not in-place..
+ ARDOUR.DSP.copy_vector (outs[c], ins[c], n_samples) -- ..copy data from input to output.
+ end
+
+ -- direct audio data access, in-place processing of output buffer
+ local buf = outs[c]:array() -- get channel's 'c' data as lua array reference
+
+ -- process all audio samples
+ for s = 1, n_samples do
+ buf[s] = math.atan (1.5707 * buf[s]) -- some non-linear gain.
+
+ -- NOTE: doing the maths per sample in lua is not super-efficient
+ -- (vs C/C++ vectorized functions -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP)
+ -- but it is very convenient, especially for prototypes and quick solutions.
+ end
+
+ end
+end
diff --git a/share/scripts/_sort_tracks_by_name.lua b/share/scripts/_sort_tracks_by_name.lua
new file mode 100644
index 0000000000..36dfdc970c
--- /dev/null
+++ b/share/scripts/_sort_tracks_by_name.lua
@@ -0,0 +1,36 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Track Sort",
+ author = "Ardour Lua Taskforce",
+ description = [[Sort tracks alphabetically by name]]
+}
+
+function factory () return function ()
+
+ -- sort compare function
+ -- a,b here are http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Route
+ -- return true if route "a" should be ordered before route "b"
+ function tsort (a, b)
+ return a:name() < b:name()
+ end
+
+ -- create a sortable list of tracks
+ local tracklist = {}
+ for t in Session:get_tracks():iter() do
+ table.insert(tracklist, t)
+ end
+
+ -- sort the list using the compare function
+ table.sort(tracklist, tsort)
+
+ -- traverse the sorted list and assign "presentation-order" to each track
+ local pos = 1;
+ for _, t in ipairs(tracklist) do
+ t:set_presentation_order(pos)
+ pos = pos + 1
+ end
+
+ -- drop all track references
+ tracklist = nil
+ collectgarbage ()
+end end
diff --git a/share/scripts/_spike_synth.lua b/share/scripts/_spike_synth.lua
new file mode 100644
index 0000000000..167e0e27d2
--- /dev/null
+++ b/share/scripts/_spike_synth.lua
@@ -0,0 +1,37 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Spike Synth",
+ category = "Instrument",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[A debug and test-instrumentation synth. This plugin is useful with Ardour's "Dummy" backend "Engine-Pulse" mode to verify capture alignment. This plugin generate the exact same audio-signal from MIDI data that the backend also generates: Note-on: +1, Note-off: -1.]]
+}
+
+function dsp_ioconfig ()
+ return { { midi_in = 1, audio_in = 0, audio_out = 1} }
+end
+
+function dsp_run (ins, outs, n_samples)
+ local a = {}
+ for s = 1, n_samples do a[s] = 0 end
+
+ for c = 1,#outs do
+ ARDOUR.DSP.memset (outs[c], 0, n_samples)
+ end
+
+ assert (type(midiin) == "table")
+ for _,b in pairs (midiin) do
+ local t = b["time"] -- t = [ 1 .. n_samples ]
+ local d = b["data"] -- get midi-event
+ if (#d == 3 and (d[1] & 240) == 144) then -- note on
+ for c = 1,#outs do
+ outs[c]:array()[t] = 1.0
+ end
+ end
+ if (#d == 3 and (d[1] & 240) == 128) then -- note off
+ for c = 1,#outs do
+ outs[c]:array()[t] = -1.0
+ end
+ end
+ end
+end
diff --git a/share/scripts/_split_benchmark.lua b/share/scripts/_split_benchmark.lua
new file mode 100644
index 0000000000..6ab60e44bc
--- /dev/null
+++ b/share/scripts/_split_benchmark.lua
@@ -0,0 +1,58 @@
+-- [disable CPU freq scaling for benchmark]
+-- create a session
+-- add 16 mono tracks
+-- record 2-3 mins on each track starting at 00:00:00:00
+-- rewind the playhead to 00:00:00:00
+-- run this script in Menu > Window. Scripting 10 times
+ardour { ["type"] = "EditorAction", name = "Split Benchmark" }
+
+function factory (params) return function ()
+
+ function split_at (pos)
+ local add_undo = false -- keep track if something has changed
+ Session:begin_reversible_command ("Auto Region Split")
+ for route in Session:get_tracks():iter() do
+ local playlist = route:to_track():playlist ()
+ playlist:to_stateful ():clear_changes ()
+ for region in playlist:regions_at (pos):iter () do
+ playlist:split_region (region, ARDOUR.MusicSample (pos, 0))
+ end
+ if not Session:add_stateful_diff_command (playlist:to_statefuldestructible ()):empty () then
+ add_undo = true
+ end
+ end
+ if add_undo then
+ Session:commit_reversible_command (nil)
+ else
+ Session:abort_reversible_command ()
+ end
+ end
+
+ function count_regions ()
+ local total = 0
+ for route in Session:get_tracks():iter() do
+ total = total + route:to_track():playlist():region_list():size()
+ end
+ return total
+ end
+
+ for x = 1, 3 do
+ local playhead = Session:transport_sample ()
+
+ local step = Session:samples_per_timecode_frame()
+ local n_steps = 20
+
+ local t_start = ARDOUR.LuaAPI.monotonic_time ()
+ for i = 1, n_steps do
+ split_at (playhead + step * i)
+ end
+ local t_end = ARDOUR.LuaAPI.monotonic_time ()
+
+ Session:request_locate((playhead + step * n_steps), ARDOUR.LocateTransportDisposition.MustStop, ARDOUR.TransportRequestSource.TRS_UI)
+ print (count_regions (), (t_end - t_start) / 1000 / n_steps)
+ collectgarbage ();
+ ARDOUR.LuaAPI.usleep(500000)
+ end
+
+
+end end
diff --git a/share/scripts/_stereo_to_mono.lua b/share/scripts/_stereo_to_mono.lua
new file mode 100644
index 0000000000..81fa0316a7
--- /dev/null
+++ b/share/scripts/_stereo_to_mono.lua
@@ -0,0 +1,57 @@
+ardour { ["type"] = "EditorAction", name = "Stereo to Mono",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Convert a Stereo Track into two Mono Tracks]]
+}
+
+
+function factory (params) return function ()
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
+ -- the Ardour Selection can include multiple items
+ -- (regions, tracks, ranges, markers, automation, midi-notes etc)
+ local sel = Editor:get_selection ()
+
+ -- for each track..
+ for t in sel.tracks:routelist ():iter () do
+ local track = t:to_track ()
+ if track:isnil() then goto next end
+
+ -- only audio tracks
+ local playlist = track:playlist ()
+ if playlist:data_type ():to_string () ~= "audio" then goto next end
+
+ -- skip tracks without any regions
+ if playlist:region_list ():size() == 0 then goto next end
+
+ -- we can't access diskstream n_channels()
+ local channels = track:n_inputs(): n_audio()
+
+ -- stereo only
+ if channels ~= 2 then goto next end
+
+ -- create 2 new tracks (using the name of the original track)(
+ local newtracks = Session:new_audio_track (2, 2, nil, 2, t:name(), ARDOUR.PresentationInfo.max_order, ARDOUR.TrackMode.Normal)
+ assert (newtracks:size() == 2)
+
+ for r in playlist:region_list ():iter () do
+ local region = r:to_audioregion ()
+ local rl = ARDOUR.RegionVector ()
+ local _, rv = region:separate_by_channel (rl)
+ assert (rv[1]:size () == 2)
+ -- 1:1 mapping of regions to new tacks
+ local plc = 1
+ for nr in rv[1]:iter () do
+ local pl = newtracks:table()[plc]:playlist()
+ pl:add_region (nr, r:position(), 1, false, 0, 0, false)
+ plc = plc + 1
+ end
+ end
+
+ -- TODO remove the old track
+
+ -- drop references for good.
+ collectgarbage ()
+ ::next::
+ end
+
+end end
diff --git a/share/scripts/_system_exec.lua b/share/scripts/_system_exec.lua
new file mode 100644
index 0000000000..281f5dee0b
--- /dev/null
+++ b/share/scripts/_system_exec.lua
@@ -0,0 +1,20 @@
+ardour { ["type"] = "EditorAction", name = "System Exec" }
+
+function factory () return function ()
+ -- ** EXAMPLES TO RUN EXTERNAL APPLICATIONS ** --
+
+ -- run a command in a shell and wait for it to complete.
+ --
+ -- Details: basically just system(3), except on Unix like systems with
+ -- memory-locking, this call is special-cased to use vfork and close
+ -- file-descriptors. On other systems it defaults to Lua's os-library
+ -- built-in os.execute system() call.
+ os.execute ("date > /tmp/testdate")
+
+ -- os.forkexec() works as fire-and-forget. execv(3) style
+ --
+ -- Details: It calls vfork() and exec() under the hood, passing each
+ -- argument separately to exec (and needs a full-path to the binary).
+ os.forkexec ("/usr/bin/xterm")
+ os.forkexec ("/bin/sh", "-c", "import --frame \"/tmp/scr_$(date).png\"")
+end end
diff --git a/share/scripts/_tempo_map_dump.lua b/share/scripts/_tempo_map_dump.lua
new file mode 100644
index 0000000000..7e85cd5451
--- /dev/null
+++ b/share/scripts/_tempo_map_dump.lua
@@ -0,0 +1,14 @@
+ardour { ["type"] = "Snippet", name = "Tempo Map Dump" }
+
+function factory () return function ()
+
+ local tm = Session:tempo_map ()
+ local ts = tm:tempo_section_at_sample (0)
+
+ while true do
+ print ("TS @", ts:sample(), " | ", ts:to_tempo():note_types_per_minute (), "..", ts:to_tempo():end_note_types_per_minute (), "bpm")
+ ts = tm:next_tempo_section (ts)
+ if not ts then break end
+ end
+
+end end
diff --git a/share/scripts/_toggle_monitor_section.lua b/share/scripts/_toggle_monitor_section.lua
new file mode 100644
index 0000000000..e097a94a32
--- /dev/null
+++ b/share/scripts/_toggle_monitor_section.lua
@@ -0,0 +1,10 @@
+ardour { ["type"] = "Snippet", name = "Toggle Monitor Section" }
+
+function factory () return function ()
+ if Session:monitor_out():isnil() then
+ ARDOUR.config():set_use_monitor_bus (true)
+ else
+ ARDOUR.config():set_use_monitor_bus (false)
+ collectgarbage ()
+ end
+end end
diff --git a/share/scripts/_tx_raw_midi_from_file.lua b/share/scripts/_tx_raw_midi_from_file.lua
new file mode 100644
index 0000000000..ac45cd1b10
--- /dev/null
+++ b/share/scripts/_tx_raw_midi_from_file.lua
@@ -0,0 +1,128 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Send Raw MIDI from File",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Read raw binary midi (.syx) from file and send it to a control port]]
+}
+
+function factory () return function ()
+
+ function portlist ()
+ local rv = {}
+ local a = Session:engine()
+ local _, t = a:get_ports (ARDOUR.DataType("midi"), ARDOUR.PortList())
+ for p in t[2]:iter() do
+ local amp = p:to_asyncmidiport ()
+ if amp:isnil() or not amp:sends_output() then goto continue end
+ rv[amp:name()] = amp
+ print (amp:name(), amp:sends_output())
+ ::continue::
+ end
+ return rv
+ end
+
+ local dialog_options = {
+ { type = "file", key = "file", title = "Select .syx MIDI file" },
+ { type = "dropdown", key = "port", title = "Target Port", values = portlist () }
+ }
+
+ local rv = LuaDialog.Dialog ("Select Taget", dialog_options):run ()
+ dialog_options = nil -- drop references (ports, shared ptr)
+ collectgarbage () -- and release the references immediately
+
+ if not rv then return end -- user cancelled
+
+ local f = io.open (rv["file"], "rb")
+
+ if not f then
+ LuaDialog.Message ("Raw MIDI Tx", "File Not Found", LuaDialog.MessageType.Error, LuaDialog.ButtonType.Close):run ()
+ goto out
+ end
+
+ do -- scope for 'local'
+ local size = f:seek("end") -- determine file size
+ f:seek("set", 0)
+
+ if size > 1048576 then
+ local ok = LuaDialog.Message ("Raw MIDI Tx",
+ string.format ("File is larger than 1MB.\nFile-size = %.1f kB\n\nContinue?", size / 1024),
+ LuaDialog.MessageType.Question, LuaDialog.ButtonType.Yes_No):run ()
+ if ok ~= LuaDialog.Response.Yes then
+ f:close ()
+ goto out
+ end
+ end
+ end
+
+ do -- scope for 'local'
+ local midi_byte_count = 0
+ local total_read = 0
+ local message_count = 0
+ local long_message = false
+
+ local async_midi_port = rv["port"] -- reference to port
+ local parser = ARDOUR.RawMidiParser () -- construct a MIDI parser
+
+ while true do
+ -- read file in 64byte chunks
+ local bytes = f:read (64)
+ if not bytes then break end
+ total_read = total_read + #bytes
+
+ -- parse MIDI data byte-by-byte
+ for i = 1, #bytes do
+ if parser:process_byte (bytes:byte (i)) then
+ if parser:buffer_size () > 255 then
+ long_message = true
+ print ("WARNING -- single large message > 255, bytes: ", parser:buffer_size ())
+ end
+ -- parsed complete normalized MIDI message, send it
+ async_midi_port:write (parser:midi_buffer (), parser:buffer_size (), 0)
+
+ -- Physical MIDI is sent at 31.25kBaud.
+ -- Every message is sent as 10bit message on the wire,
+ -- so every MIDI byte needs 320usec.
+ ARDOUR.LuaAPI.usleep (400 * parser:buffer_size ())
+
+ -- count msgs and valid bytes sent
+ midi_byte_count = midi_byte_count + parser:buffer_size ()
+ message_count = message_count + 1
+ if 0 == message_count % 50 then
+ -- print() wakes up the GUI, prevent stalling the event loop
+ print ("Sent", message_count, "messages, bytes so far: ", midi_byte_count)
+ end
+ end
+ end
+ end
+
+ f:close ()
+ print ("Sent", message_count, "messages, total bytes: ", midi_byte_count, "/", total_read)
+
+ if long_message then
+ LuaDialog.Message ("Raw MIDI Tx", "Dataset contained messages longer than 127 bytes. Which may or may not have been transmitted successfully.", LuaDialog.MessageType.Warning, LuaDialog.ButtonType.Close):run ()
+ end
+ end
+
+ ::out::
+ rv = nil
+ collectgarbage ()
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil(math.min (width, height) * .45) .. "px")
+ txt:set_text ("S")
+ ctx:move_to (1, 1)
+ txt:show_in_cairo_context (ctx)
+
+ txt:set_text ("Y")
+ local tw, th = txt:get_pixel_size ()
+ ctx:move_to (.5 * (width - tw), .5 * (height - th))
+ txt:show_in_cairo_context (ctx)
+
+ txt:set_text ("X")
+ tw, th = txt:get_pixel_size ()
+ ctx:move_to ((width - tw - 1), (height - th -1))
+ txt:show_in_cairo_context (ctx)
+end end
diff --git a/share/scripts/_vamp_example.lua b/share/scripts/_vamp_example.lua
new file mode 100644
index 0000000000..73552d638c
--- /dev/null
+++ b/share/scripts/_vamp_example.lua
@@ -0,0 +1,63 @@
+ardour { ["type"] = "Snippet", name = "Vamp Plugin Example" }
+
+function factory () return function ()
+
+ -- get a list of all available plugins
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:LuaAPI:Vamp
+ -- returns a http://manual.ardour.org/lua-scripting/class_reference/#C:StringVector
+ local plugins = ARDOUR.LuaAPI.Vamp.list_plugins ();
+ for id in plugins:iter () do
+ print ("--", id)
+ end
+
+ local sel = Editor:get_selection ()
+
+ -- load the Vamp Plugin with Id "libardourvampplugins:dBTP"
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:LuaAPI:Vamp
+ local vamp = ARDOUR.LuaAPI.Vamp("libardourvampplugins:dBTP", Session:nominal_sample_rate())
+ print (vamp:plugin():getName())
+
+ -- for each selected region
+ for r in sel.regions:regionlist ():iter () do
+ print ("Region:", r:name ())
+
+ -- run the plugin, analyze the first channel of the audio-region
+ vamp:analyze (r:to_readable (), 0, nil)
+
+ -- get analysis results
+ local f = vamp:plugin ():getRemainingFeatures ()
+
+ -- f is-a Vamp::Plugin::FeatureSet aka std::map<int, Vamp::Plugin::FeatureList>
+ -- http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:FeatureSet
+ for id, featlist in f:iter () do
+ print (id, featlist)
+ end
+
+ -- get the first FeatureList
+ local featurelist = f:table()[0]
+ -- Vamp::Plugin::FeatureList is a typedef for std::vector<Feature>
+ for feat in featurelist:iter () do
+ print ("-", feat.label)
+ end
+
+ -- get the first feature..
+ -- http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:Feature
+ local feature = featurelist:at(0)
+ -- ..and the values of the feature, which is-a std::vector<float>
+ local values = feature.values
+ -- iterate over the std::vector<float>
+ for val in values:iter () do
+ print ("*", val)
+ end
+
+ -- access the first element of Vamp::Plugin::Feature's "values" vector
+ -- http://manual.ardour.org/lua-scripting/class_reference/#C:FloatVector
+ local value = values:at(0)
+ -- in case of libardourvampplugins:dBTP that's the true-peak (signal value)
+ local dbtp = 20 * math.log (value) / math.log(10) -- convert it to dB
+ print (string.format ("Region '%s': %.2f dBTP", r:name (), dbtp))
+
+ -- reset the plugin for the next iteration
+ vamp:reset ()
+ end
+end end
diff --git a/share/scripts/_vamp_note_example.lua b/share/scripts/_vamp_note_example.lua
new file mode 100644
index 0000000000..6a93dd8ee4
--- /dev/null
+++ b/share/scripts/_vamp_note_example.lua
@@ -0,0 +1,68 @@
+ardour { ["type"] = "Snippet", name = "Vamp Audio Transcription Example" }
+
+function factory () return function ()
+
+ -- 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 ()
+ local sr = Session:nominal_sample_rate ()
+
+ -- Instantiate a Vamp Plugin
+ -- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:LuaAPI:Vamp
+ local vamp = ARDOUR.LuaAPI.Vamp ("libardourvampplugins:qm-transcription", sr)
+
+ -- prepare progress dialog
+ local progress_total = 0;
+ local progress_part = 0
+ local pdialog = LuaDialog.ProgressWindow ("Audio to MIDI", true)
+ function cb (_, pos)
+ return pdialog:progress ((pos + progress_part) / progress_total, "Analyzing")
+ end
+
+ -- calculate max progress
+ for r in sel.regions:regionlist ():iter () do
+ progress_total = progress_total + r:to_readable ():readable_length ()
+ end
+
+ -- 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
+
+ vamp:analyze (r:to_readable (), 0, cb)
+
+ if pdialog:canceled () then
+ goto out
+ end
+
+ progress_part = progress_part + r:to_readable ():readable_length ()
+ pdialog:progress (progress_part / progress_total, "Post Processing")
+
+ print ("-- Post Processing: ", r:name ())
+
+ -- post-processing takes longer than actually parsing the data :(
+ local f = vamp:plugin ():getRemainingFeatures ()
+
+ local fl = f:table ()[0]
+ print (" Time (sample) | Len | Midi-Note");
+ if fl then for f in fl:iter () do
+ assert (f.hasTimestamp and f.hasDuration);
+ local ft = Vamp.RealTime.realTime2Frame (f.timestamp, sr)
+ local fd = Vamp.RealTime.realTime2Frame (f.duration, sr)
+ local fn = f.values:at (0) -- midi note number
+ print (string.format (" %14d %7d %d", ft, fd, fn))
+ end end
+
+ -- reset the plugin (prepare for next iteration)
+ vamp:reset ()
+ end
+
+ ::out::
+ -- hide modal progress dialog and destroy it
+ pdialog:done ();
+ pdialog = nil
+ vamp = nil;
+ collectgarbage ()
+
+end end
diff --git a/share/scripts/_vamp_onset_example.lua b/share/scripts/_vamp_onset_example.lua
new file mode 100644
index 0000000000..adcdc16982
--- /dev/null
+++ b/share/scripts/_vamp_onset_example.lua
@@ -0,0 +1,83 @@
+ardour { ["type"] = "Snippet", name = "Vamp Onset Detection Example" }
+
+function factory () return function ()
+
+ -- 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 a Vamp Plugin
+ -- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:LuaAPI:Vamp
+ --
+ -- here: the "Queen Mary Note Onset Detector" Vamp plugin (which comes with Ardour)
+ -- http://vamp-plugins.org/plugin-doc/qm-vamp-plugins.html#qm-onsetdetector
+ local vamp = ARDOUR.LuaAPI.Vamp("libardourvampplugins:qm-onsetdetector", Session:nominal_sample_rate())
+
+ -- prepare table to hold results
+ local onsets = {}
+
+ -- 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
+
+ -- prepare lua table to hold results for the given region (by name)
+ onsets[r:name ()] = {}
+
+ -- callback to handle Vamp-Plugin analysis results
+ function callback (feats)
+ -- "feats" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:FeatureSet
+ -- get the first output. here: Detected note onset times
+ local fl = feats:table()[0]
+ -- "fl" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:FeatureList
+ -- which may be empty or not nil
+ if fl then
+ -- iterate over returned features
+ for f in fl:iter () do
+ -- "f" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:Feature
+ if f.hasTimestamp then
+ local fn = Vamp.RealTime.realTime2Frame (f.timestamp, 48000)
+ --print ("-", f.timestamp:toString(), fn)
+ table.insert (onsets[r:name ()], fn)
+ end
+ end
+ end
+ return false -- continue, !cancel
+ end
+
+ -- Configure Vamp plugin
+ --
+ -- One could query the Parameter and Program List:
+ -- http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin
+ -- but since the Plugin is known, we can directly refer to the plugin doc:
+ -- http://vamp-plugins.org/plugin-doc/qm-vamp-plugins.html#qm-onsetdetector
+ vamp:plugin ():setParameter ("dftype", 3);
+ vamp:plugin ():setParameter ("sensitivity", 50);
+ vamp:plugin ():setParameter ("whiten", 0);
+ -- in this case the above (3, 50, 0) is equivalent to
+ --vamp:plugin ():selectProgram ("General purpose");
+
+ -- run the plugin, analyze the first channel of the audio-region
+ --
+ -- This uses a "high-level" convenience wrapper provided by Ardour
+ -- which reads raw audio-data from the region and and calls
+ -- f = vamp:plugin ():process (); callback (f)
+ vamp:analyze (r:to_readable (), 0, callback)
+
+ -- get remaining features (end of analyis)
+ callback (vamp:plugin ():getRemainingFeatures ())
+
+ -- reset the plugin (prepare for next iteration)
+ vamp:reset ()
+ end
+
+ -- print results
+ for n,o in pairs(onsets) do
+ print ("Onset analysis for region:", n)
+ for _,t in ipairs(o) do
+ print ("-", t)
+ end
+ end
+
+end end
diff --git a/share/scripts/_vamp_tempomap_example.lua b/share/scripts/_vamp_tempomap_example.lua
new file mode 100644
index 0000000000..1c216c06f3
--- /dev/null
+++ b/share/scripts/_vamp_tempomap_example.lua
@@ -0,0 +1,85 @@
+ardour { ["type"] = "Snippet", name = "Vamp TempoMap Test" }
+
+function factory () return function ()
+
+ -- 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 table to hold results
+ local beats = {}
+ local bars = {}
+
+ -- 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
+
+ -- prepare lua table to hold results for the given region (by name)
+ beats[r:name ()] = {}
+ bars[r:name ()] = {}
+
+ -- callback to handle Vamp-Plugin analysis results
+ function callback (feats)
+ -- "feats" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:FeatureSet
+
+ -- get the first output. here: Beats, estimated beat locations & beat-number
+ local fl = feats:table()[0]
+ -- "fl" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:FeatureList
+ -- which may be empty or not nil
+ if fl then
+ -- iterate over returned features
+ for f in fl:iter () do
+ -- "f" is-a http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:Feature
+ if f.hasTimestamp then
+ local fn = Vamp.RealTime.realTime2Frame (f.timestamp, 48000)
+ table.insert (beats[r:name ()], {pos = fn, beat = f.label})
+ end
+ end
+ end
+
+ -- get the 2nd output. here: estimated bar locations
+ local fl = feats:table()[1]
+ if fl then
+ for f in fl:iter () do
+ if f.hasTimestamp then
+ local fn = Vamp.RealTime.realTime2Frame (f.timestamp, 48000)
+ table.insert (bars[r:name ()], fn)
+ end
+ end
+ end
+ return false -- continue, !cancel
+ end
+
+ vamp:plugin ():setParameter ("Beats Per Bar", 4); -- TODO ask
+
+ -- run the plugin, analyze the first channel of the audio-region
+ vamp:analyze (r:to_readable (), 0, callback)
+ -- get remaining features (end of analyis)
+ callback (vamp:plugin ():getRemainingFeatures ())
+ -- reset the plugin (prepare for next iteration)
+ vamp:reset ()
+ end
+
+ -- print results (for now)
+ -- TODO: calculate and set tempo-map
+ for n,o in pairs(bars) do
+ print ("Bar analysis for region:", n)
+ for _,t in ipairs(o) do
+ print ("-", t)
+ end
+ end
+ for n,o in pairs(beats) do
+ print ("Beat analysis for region:", n)
+ for _,t in ipairs(o) do
+ print ("-", t['pos'], t['beat'])
+ end
+ end
+
+end end
diff --git a/share/scripts/_varispeed_callback.lua b/share/scripts/_varispeed_callback.lua
new file mode 100644
index 0000000000..fe3f2721c1
--- /dev/null
+++ b/share/scripts/_varispeed_callback.lua
@@ -0,0 +1,32 @@
+ardour {
+ ["type"] = "EditorHook",
+ name = "Varispeed Test - 100ms Callback",
+ author = "Ardour Lua Task Force",
+ description = "An example script that invokes a callback a every 0.1sec and modifies the transport speed",
+}
+
+function signals ()
+ s = LuaSignal.Set()
+ s:add (
+ {
+ [LuaSignal.LuaTimerDS] = true,
+ }
+ )
+ return s
+end
+
+function factory (params)
+ -- upindex variables
+ local cnt = 0
+ local speed = 0
+ local delta = 0.01
+ return function (signal, ref, ...)
+ cnt = (cnt + 1) % 5 -- divide clock: every half a second
+ if cnt == 0 then
+ if speed < -0.25 then delta = delta * -1 end
+ if speed > 0.25 then delta = delta * -1 end
+ speed = speed + delta
+ Session:request_transport_speed (speed, true, ARDOUR.TransportRequestSource.TRS_UI)
+ end
+ end
+end
diff --git a/share/scripts/_vca_slave_assign.lua b/share/scripts/_vca_slave_assign.lua
new file mode 100644
index 0000000000..616c0abd98
--- /dev/null
+++ b/share/scripts/_vca_slave_assign.lua
@@ -0,0 +1,73 @@
+ardour { ["type"] = "Snippet", name = "VCA Slave Examples",
+ license = "MIT",
+ author = "Ardour Team",
+}
+
+function factory () return function ()
+ -- find possible masters & slave, allow selection in dropdown menu
+ local targets = {}
+ local sources = {}
+ local have_masters = false
+ local have_slaves = false
+
+ for v in Session:vca_manager ():vcas() :iter () do -- for each VCA
+ sources [v:name ()] = v
+ have_masters = true
+ end
+
+ for s in Session:get_stripables ():iter () do -- for every track/bus/vca
+ if s:is_monitor () or s:is_auditioner () then goto nextroute end -- skip special routes
+ targets [s:name ()] = s
+ have_slaves = true;
+ ::nextroute::
+ end
+
+ -- bail out if there are no parameters
+ if not have_slaves then
+ LuaDialog.Message ("VCA Slave Example", "No Slavables found", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
+ sources = nil
+ collectgarbage ()
+ return
+ end
+ if not have_masters then
+ LuaDialog.Message ("VCA Slave Example", "No VCA masters found", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
+ targets = nil
+ collectgarbage ()
+ return
+ end
+
+ -- create a dialog, ask user which master to assign to which slave
+ local dialog_options = {
+ { type = "dropdown", key = "master", title = "Control Master", values = sources },
+ { type = "dropdown", key = "slave", title = "Control Slave", values = targets }
+ }
+ local rv = LuaDialog.Dialog ("Select VCA assignment", dialog_options):run ()
+
+ targets = nil -- drop references (the table holds shared-pointer references to all strips)
+ collectgarbage () -- and release the references immediately
+
+ if not rv then return end -- user canceled the operation
+
+ -- parse user response
+ local mst = rv["master"]
+ local slv = rv["slave"]
+ assert (not slv:to_slavable ():isnil ())
+
+ -- test if mst is already controlled by slv (directly or indirectly)
+ -- if so, don't allow the connection
+ if (not slv:to_slavable ():assigned_to (Session:vca_manager(), mst)) then
+ -- if slv controlled by master, disconnect it
+ if (slv:slaved_to (mst)) then
+ slv:to_slavable ():unassign (mst)
+ else
+ slv:to_slavable ():assign (mst)
+ end
+ else
+ LuaDialog.Message ("VCA Slave Example", "Recursive VCA assignment ignored", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
+ end
+
+ -- drop references
+ mst = nil slv = nil
+ collectgarbage ()
+end end
+
diff --git a/share/scripts/a_slow_mute.lua b/share/scripts/a_slow_mute.lua
new file mode 100644
index 0000000000..a9ef12b754
--- /dev/null
+++ b/share/scripts/a_slow_mute.lua
@@ -0,0 +1,66 @@
+ardour {
+ ["type"] = "dsp",
+ name = "a-Slow-Mute",
+ category = "Amplifier",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Mute button with slow fade in/out]]
+}
+
+function dsp_ioconfig ()
+ -- -1, -1 = any number of channels as long as input and output count matches
+ return { { audio_in = -1, audio_out = -1} }
+end
+
+
+function dsp_params ()
+ return { { ["type"] = "input", name = "Mute", min = 0, max = 1, default = 0, toggled = true } }
+end
+
+local cur_gain = 1
+local lpf = 0.002 -- parameter low-pass filter time-constant
+
+function dsp_init (rate)
+ lpf = 100 / rate -- interpolation time constant
+end
+
+function low_pass_filter_param (old, new, limit)
+ if math.abs (old - new) < limit then
+ return new
+ else
+ return old + lpf * (new - old)
+ end
+end
+
+-- the DSP callback function
+-- "ins" and "outs" are http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+function dsp_run (ins, outs, n_samples)
+ local ctrl = CtrlPorts:array() -- get control port array
+ local target_gain = ctrl[1] > 0 and 0.0 or 1.0; -- when muted, target_gain = 0.0; otherwise use 1.0
+ local siz = n_samples -- samples remaining to process
+ local off = 0 -- already processed samples
+ local changed = false
+
+ -- if the target gain changes, process at most 32 samples at a time,
+ -- and interpolate gain until the current settings match the target values
+ if cur_gain ~= target_gain then
+ changed = true
+ siz = 32
+ end
+
+ while n_samples > 0 do
+ if siz > n_samples then siz = n_samples end
+ if changed then
+ cur_gain = low_pass_filter_param (cur_gain, target_gain, 0.001)
+ end
+
+ for c = 1,#ins do -- process all channels
+ if ins[c] ~= outs[c] then
+ ARDOUR.DSP.copy_vector (outs[c]:offset (off), ins[c]:offset (off), siz)
+ end
+ ARDOUR.DSP.apply_gain_to_buffer (outs[c]:offset (off), siz, cur_gain);
+ end
+ n_samples = n_samples - siz
+ off = off + siz
+ end
+end
diff --git a/share/scripts/access_action.lua b/share/scripts/access_action.lua
new file mode 100644
index 0000000000..b50c6386d8
--- /dev/null
+++ b/share/scripts/access_action.lua
@@ -0,0 +1,37 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Shortcut",
+ license = "MIT",
+ author = "me",
+ description = [[Trigger a keyboard shortcut. You will be prompted for the shortcut's action in the next step.]]
+}
+
+function action_params ()
+ local actionlist = {
+ {
+ type = "dropdown", key = "action", title = "Action", values = ArdourUI:actionlist(),
+ default = "Save"
+ }
+ }
+
+ local rv = LuaDialog.Dialog ("Select Action", actionlist):run ()
+ if not rv then -- user cancelled
+ return { ["x-script-abort"] = { title = "", preseeded = true} }
+ end
+
+ local action = rv["action"]
+ local name = "Shortcut - " .. action
+ return {
+ ["action"] = { title = "Action to trigger", default = action, preseeded = true},
+ ["x-script-name"] = { title = "Unique Script name", default = name, preseeded = true},
+ }
+end
+
+function factory (params) return function ()
+ local p = params or { }
+ local as = assert (p["action"])
+ local sp = assert (as:find('/'))
+ local group = assert (as:sub(0, sp - 1))
+ local item = assert (as:sub(1 + sp))
+ Editor:access_action (group, item)
+end end
diff --git a/share/scripts/add_filters.lua b/share/scripts/add_filters.lua
new file mode 100644
index 0000000000..77b41c17bf
--- /dev/null
+++ b/share/scripts/add_filters.lua
@@ -0,0 +1,54 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Add Filters",
+ license = "MIT",
+ author = "PSmith",
+ description = [[Add 'HPF/LPF' Lua Processor to all Tracks]]
+}
+
+function action_params ()
+ return
+ {
+ ["unique"] = { title = "Only add HPF/LPF if not already present (yes/no)", default = "yes"},
+ ["position"] = { title = "Insert Position from top (0,..)", default = "0"},
+ }
+end
+
+
+function factory (params)
+ return function ()
+ -- get configuration
+ local p = params or {}
+ local uniq = p["unique"] or "yes"
+ local pos = p["position"] or 0
+
+ -- loop over all tracks
+ for t in Session:get_tracks():iter() do
+ local insert = true;
+
+ -- check if filters are present
+ if uniq ~= "no" then
+ local proc;
+ local i = 0;
+ repeat
+ -- get Nth Ardour::Processor
+ proc = t:nth_plugin (i)
+ -- check if it's a filter
+ if (not proc:isnil() and proc:display_name () == "a-High/Low Pass Filter") then
+ insert = false;
+ end
+ i = i + 1
+ until proc:isnil() or insert == false
+ end
+
+ -- create a new processor and insert it
+ if insert then
+ local a = ARDOUR.LuaAPI.new_luaproc(Session, "a-High/Low Pass Filter");
+ if (not a:isnil()) then
+ t:add_processor_by_index(a, pos, nil, true)
+ a = nil -- explicitly drop shared-ptr reference
+ end
+ end
+ end
+ end
+end
diff --git a/share/scripts/addscopes.lua b/share/scripts/addscopes.lua
new file mode 100644
index 0000000000..d180f03765
--- /dev/null
+++ b/share/scripts/addscopes.lua
@@ -0,0 +1,75 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Add Scopes",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Add 'Inline Scope' Lua Processor to all Tracks]]
+}
+
+function action_params ()
+ return
+ {
+ ["unique"] = { title = "Only add Scope if non is present already (yes/no)", default = "yes"},
+ ["position"] = { title = "Insert Position from top (0,..)", default = "0"},
+ }
+end
+
+
+function factory (params)
+ return function ()
+ -- get configuration
+ local p = params or {}
+ local uniq = p["unique"] or "yes"
+ local pos = p["position"] or 0
+
+ -- loop over all tracks
+ for t in Session:get_tracks():iter() do
+ local insert = true;
+
+ -- check if a scope is already present
+ if uniq ~= "no" then
+ local proc;
+ local i = 0;
+ repeat
+ -- get Nth Ardour::Processor
+ proc = t:nth_plugin (i)
+ -- check if it's a scope
+ if (not proc:isnil() and proc:display_name () == "a-Inline Scope") then
+ insert = false;
+ end
+ i = i + 1
+ until proc:isnil() or insert == false
+ end
+
+ -- create a new processor and insert it
+ if insert then
+ local a = ARDOUR.LuaAPI.new_luaproc(Session, "a-Inline Scope");
+ if (not a:isnil()) then
+ t:add_processor_by_index(a, pos, nil, true)
+ ARDOUR.LuaAPI.set_processor_param (a, 0, 5) -- timescale 5sec
+ -- ARDOUR.LuaAPI.set_processor_param (a, 1, 1) -- logscale on
+ -- ARDOUR.LuaAPI.set_processor_param (a, 2, 3) -- "Max" height
+ a = nil -- explicitly drop shared-ptr reference
+ end
+ end
+ end
+ end
+end
+
+
+function icon (params) return function (ctx, width, height)
+ local wh = math.min (width, height) * .5
+ local x0 = math.ceil (wh * .4)
+ local x1 = math.floor (wh * 1.6)
+ ctx:translate (math.floor (width * .5 - wh), math.floor (height * .5 - wh))
+ ctx:rectangle (wh * .4, wh * .4, wh * 1.2, wh * 1.2)
+ ctx:set_source_rgba (.7, .7, .7, 1)
+ ctx:fill ()
+ ctx:set_line_width (1)
+ ctx:set_source_rgba (.0, .0, .0, 1)
+ ctx:move_to (x0, wh)
+ for x = x0, x1 do
+ ctx:line_to (x, wh - math.sin (2 * math.pi * (x-x0) / (x1-x0)) * wh * .5)
+ end
+ ctx:stroke ()
+end end
diff --git a/share/scripts/amp4.lua b/share/scripts/amp4.lua
new file mode 100644
index 0000000000..276b4a0af6
--- /dev/null
+++ b/share/scripts/amp4.lua
@@ -0,0 +1,119 @@
+ardour {
+ ["type"] = "dsp",
+ name = "a-Amplifier",
+ category = "Amplifier",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Versatile +/- 20dB multichannel amplifier]]
+}
+
+function dsp_ioconfig ()
+ return
+ {
+ -- -1, -1 = any number of channels as long as input and output count matches
+ { audio_in = -1, audio_out = -1},
+ }
+end
+
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "Gain", min = -20, max = 20, default = 0, unit="dB"},
+ }
+end
+
+local lpf = 0.02 -- parameter low-pass filter time-constant
+local cur_gain = 0 -- current smoothed gain (dB)
+
+-- called once when plugin is instantiated
+function dsp_init (rate)
+ lpf = 780 / rate -- interpolation time constant
+end
+
+function low_pass_filter_param (old, new, limit)
+ if math.abs (old - new) < limit then
+ return new
+ else
+ return old + lpf * (new - old)
+ end
+end
+
+-- the DSP callback function
+-- "ins" and "outs" are http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+function dsp_run (ins, outs, n_samples)
+ local ctrl = CtrlPorts:array() -- get control port array (read/write)
+ local siz = n_samples -- samples remaining to process
+ local off = 0 -- already processed samples
+ local changed = false
+
+ -- if the gain parameter was changed, process at most 32 samples at a time
+ -- and interpolate gain until the current settings match the target values
+ if cur_gain ~= ctrl[1] then
+ changed = true
+ siz = 32
+ end
+
+ while n_samples > 0 do
+ if siz > n_samples then siz = n_samples end -- process at most "remaining samples"
+ if changed then
+ -- smooth gain changes above 0.02 dB difference
+ cur_gain = low_pass_filter_param (cur_gain, ctrl[1], 0.02)
+ end
+
+ local gain = ARDOUR.DSP.dB_to_coefficient (cur_gain) -- 10 ^ (0.05 * cur_gain)
+
+ for c = 1,#ins do -- process all channels
+ -- check if output and input buffers for this channel are identical
+ -- http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+ if ins[c] ~= outs[c] then
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP
+ ARDOUR.DSP.copy_vector (outs[c]:offset (off), ins[c]:offset (off), siz)
+ end
+ ARDOUR.DSP.apply_gain_to_buffer (outs[c]:offset (off), siz, gain); -- apply-gain, process in-place
+ end
+ n_samples = n_samples - siz
+ off = off + siz
+ end
+
+--[[
+ if changed then
+ self:queue_draw () -- notify display
+ end
+--]]
+end
+
+-------------------------------------------------------------------------------
+--- inline display + text example
+
+--[[
+local txt = nil -- cache pango context globally
+
+function render_inline (ctx, w, max_h)
+ local ctrl = CtrlPorts:array () -- get control ports
+
+ if not txt then
+ -- allocate PangoLayout and set font
+ --http://manual.ardour.org/lua-scripting/class_reference/#Cairo:PangoLayout
+ txt = Cairo.PangoLayout (ctx, "Mono 8px")
+ end
+
+ txt:set_text (string.format ("%+.2f dB", ctrl[1]));
+ tw, th = txt:get_pixel_size ()
+
+ local h = th + 4 -- use text-height with 4px padding
+ if (h > max_h) then h = max_h end -- but at most max-height
+
+ -- clear background
+ ctx:rectangle (0, 0, w, h)
+ ctx:set_source_rgba (.2, .2, .2, 1.0)
+ ctx:fill ()
+
+ -- center text
+ ctx:set_source_rgba (.8, .8, .8, 1.0)
+ ctx:move_to (.5 * (w - tw), .5 * (h - th))
+ txt:show_in_cairo_context (ctx)
+
+ return {w, h}
+end
+--]]
diff --git a/share/scripts/bounce_replace.lua b/share/scripts/bounce_replace.lua
new file mode 100644
index 0000000000..da657628ab
--- /dev/null
+++ b/share/scripts/bounce_replace.lua
@@ -0,0 +1,101 @@
+ardour { ["type"] = "EditorAction", name = "Bounce+Replace Regions",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Bounce selected regions with processing and replace region]]
+}
+
+function factory (params) return 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 selection
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
+ local sel = Editor:get_selection ()
+
+ -- prepare undo operation
+ Session:begin_reversible_command ("Bounce+Replace Regions")
+ local add_undo = false -- keep track if something has changed
+
+ -- Iterate over Regions part of the selection
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:RegionSelection
+ for r in sel.regions:regionlist ():iter () do
+ -- each of the items 'r' is a
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Region
+
+ local track = find_track_for_region (r:to_stateful():id())
+ local playlist = track:playlist ()
+
+ -- clear existing changes, prepare "diff" of state for undo
+ playlist:to_stateful ():clear_changes ()
+
+ -- bounce the region with processing
+ local region = track:bounce_range (r:position (), r:position() + r:length (), ARDOUR.InterThreadInfo (), track:main_outs (), false);
+
+ -- remove old region..
+ playlist:remove_region (r);
+ -- ..and add the newly bounced one
+ playlist:add_region (region, 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
+
+ -- 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
+
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ local wh = math.min (width, height) * .5
+ local ar = wh * .2
+
+ ctx:set_line_width (1)
+ function stroke_outline (c)
+ ctx:set_source_rgba (0, 0, 0, 1)
+ ctx:stroke_preserve ()
+ ctx:set_source_rgba (c, c, c, 1)
+ ctx:fill ()
+ end
+
+ ctx:translate (math.floor (width * .5 - wh), math.floor (height * .5 - wh))
+ ctx:rectangle (wh - wh * .6, wh - .7 * wh, wh * 1.2, .5 * wh)
+ stroke_outline (.7)
+
+ ctx:rectangle (wh - wh * .6, wh + .1 * wh, wh * 1.2, .5 * wh)
+ stroke_outline (.9)
+
+ -- arrow
+ ctx:set_source_rgba (0, 1, 0, 1)
+ ctx:set_line_width (ar * .7)
+
+ ctx:move_to (wh, wh - .5 * wh)
+ ctx:rel_line_to (0, wh)
+ ctx:stroke ()
+
+ ctx:move_to (wh, wh + .5 * wh)
+ ctx:rel_line_to (-ar, -ar)
+ ctx:rel_line_to (2 * ar, 0)
+ ctx:close_path ()
+ ctx:stroke ()
+
+
+
+end end
diff --git a/share/scripts/bypass_all_plugins.lua b/share/scripts/bypass_all_plugins.lua
new file mode 100644
index 0000000000..913228101f
--- /dev/null
+++ b/share/scripts/bypass_all_plugins.lua
@@ -0,0 +1,22 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Bypass Plugins",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Bypass Plugins on selected tracks]]
+}
+
+function factory () return function ()
+ local sel = Editor:get_selection ()
+ for r in sel.tracks:routelist ():iter () do
+ local i = 0;
+ while 1 do -- iterate over all plugins/processors
+ local proc = r:nth_plugin (i)
+ if proc:isnil () then
+ break
+ end
+ proc:to_insert():deactivate()
+ i = i + 1
+ end
+ end
+end end
diff --git a/share/scripts/create_drum_tracks.lua b/share/scripts/create_drum_tracks.lua
new file mode 100644
index 0000000000..0f16f87a70
--- /dev/null
+++ b/share/scripts/create_drum_tracks.lua
@@ -0,0 +1,73 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Create Drum Tracks",
+ author = "PSmith",
+ description = [[Creates 8 new tracks with representative names and colors.]]
+}
+
+function factory () return function ()
+ local names = {
+ "Kick",
+ "Snare",
+ "Hat",
+ "Fl Tom",
+ "OH L",
+ "OH R",
+ "Room 1",
+ "Room 2"
+ }
+
+ local color = 0xff8800ff --orange
+
+ local i = 1
+ while names[i] do
+ local tl = Session:new_audio_track (1, 2, nil, 1, names[i],
+ ARDOUR.PresentationInfo.max_order,
+ ARDOUR.TrackMode.Normal)
+
+ for track in tl:iter () do
+ track:presentation_info_ptr ():set_color (color)
+ end
+
+ i = i + 1
+ end --foreach track
+
+end end -- function factory
+
+
+function icon (params) return function (ctx, width, height)
+ local x = width * .5
+ local y = height * .5
+ local r = math.min (x, y) * .7
+ ctx:save ()
+ ctx:translate (x, y)
+ ctx:scale (1, .5)
+ ctx:translate (-x, -y)
+ ctx:arc (x, y, r, 0, 2 * math.pi)
+ ctx:set_source_rgba (.9, .9, 1, 1)
+ ctx:fill ()
+ ctx:arc (x, y, r, 0, math.pi)
+ ctx:arc_negative (x, y * 1.6, r, math.pi, 0)
+ ctx:set_source_rgba (.7, .7, .7, 1)
+ ctx:fill ()
+ ctx:restore ()
+
+ ctx:set_source_rgba (.6, .4, .2, 1)
+ ctx:translate (x, y)
+ ctx:scale (.7, 1)
+ ctx:translate (-x, -y)
+ ctx:set_line_cap (Cairo.LineCap.Round)
+
+ function drumstick (xp, lr)
+ ctx:set_line_width (r * .3)
+ ctx:move_to (x * xp, y)
+ ctx:close_path ()
+ ctx:stroke ()
+ ctx:set_line_width (r * .2)
+ ctx:move_to (x * xp, y)
+ ctx:rel_line_to (lr * x, y)
+ ctx:stroke ()
+ end
+ drumstick (1.2, 1.2)
+ drumstick (0.7, -.5)
+end end
diff --git a/share/scripts/delete_xrun_markers.lua b/share/scripts/delete_xrun_markers.lua
new file mode 100644
index 0000000000..5512e653b6
--- /dev/null
+++ b/share/scripts/delete_xrun_markers.lua
@@ -0,0 +1,40 @@
+ardour { ["type"] = "EditorAction", name = "Delete xrun markers", author = "Ardour Team", description = [[Delete all xrun markers in the current session]] }
+
+function factory () return function ()
+ for l in Session:locations():list():iter() do
+ if l:is_mark() and string.find (l:name(), "^xrun%d*$") then
+ Session:locations():remove (l);
+ end
+ end
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ local mh = height - 3.5;
+ local m3 = width / 3;
+ local m6 = width / 6;
+
+ ctx:set_line_width (.5)
+
+ ctx:set_source_rgba (.8, .2, .2, 1.0)
+ ctx:move_to (width / 2 - m6, 2)
+ ctx:rel_line_to (m3, 0)
+ ctx:rel_line_to (0, mh * 0.4)
+ ctx:rel_line_to (-m6, mh * 0.6)
+ ctx:rel_line_to (-m6, -mh * 0.6)
+ ctx:close_path ()
+ ctx:fill_preserve ()
+ ctx:set_source_rgba (.0, .0, .0, 1.0)
+ ctx:stroke ()
+
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ ctx:set_line_width (1)
+
+ ctx:move_to (width * .2, height * .2)
+ ctx:line_to (width * .8, height * .8)
+ ctx:stroke ()
+
+ ctx:move_to (width * .8, height * .2)
+ ctx:line_to (width * .2, height * .8)
+ ctx:stroke ()
+
+end end
diff --git a/share/scripts/export_mp4chaps.lua b/share/scripts/export_mp4chaps.lua
new file mode 100644
index 0000000000..61c78c3a2f
--- /dev/null
+++ b/share/scripts/export_mp4chaps.lua
@@ -0,0 +1,78 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Export markers as mp4chaps",
+ author = "Johannes Mueller",
+ description = [[
+Exports MP4chaps of all markers except xruns. The markers are stored in the
+export directory of the session in mp4 chapter marks format. The filename
+is mp4chaps.txt
+
+Note that there's always a chapter mark "Intro" at 00:00:00.000 as some
+players can't live without it. If there are no exportable markers, the file
+is not created.
+
+This is a bit more convenient than the export option, as one does not
+have to wait for the export.
+]],
+ license = "GPLv2"
+}
+
+function factory (unused_params) return function ()
+
+ local fr = Session:sample_rate()
+ local chaps = {}
+
+ for l in Session:locations():list():iter() do
+ local name = l:name()
+ if not l:is_mark() or string.find(name, "^xrun%d*$") then
+ goto next end
+
+ local t = l:start() - Session:current_start_sample()
+ local h = math.floor(t / (3600*fr))
+ local r = t - (h*3600*fr)
+ local m = math.floor(r / (60*fr))
+ r = r - m*60*fr
+ local s = math.floor(r / fr)
+ r = r - s*fr
+ local ms = math.floor(r*1000/fr)
+ table.insert(chaps, string.format("%02d:%02d:%02d.%03d %s\n", h, m, s, ms, name))
+ ::next::
+ end
+
+ if next(chaps) == nil then
+ goto out end
+
+ table.insert(chaps, "00:00:00.000 Intro\n")
+ table.sort(chaps)
+
+ file = io.open(ARDOUR.LuaAPI.build_filename (Session:path(), "export", "mp4chaps.txt"), "w")
+ for i, line in ipairs(chaps) do
+ file:write(line)
+ end
+ file:close()
+
+ ::out::
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ local mh = height - 3.5;
+ local m3 = width / 3;
+ local m6 = width / 6;
+
+ ctx:set_line_width (.5)
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+
+ ctx:move_to (width / 2 - m6, 2)
+ ctx:rel_line_to (m3, 0)
+ ctx:rel_line_to (0, mh * 0.4)
+ ctx:rel_line_to (-m6, mh * 0.6)
+ ctx:rel_line_to (-m6, -mh * 0.6)
+ ctx:close_path ()
+ ctx:stroke ()
+
+ local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil(math.min (width, height) * .5) .. "px")
+ txt:set_text ("MP4")
+ local tw, th = txt:get_pixel_size ()
+ ctx:move_to (.5 * (width - tw), .5 * (height - th))
+ txt:show_in_cairo_context (ctx)
+end end
diff --git a/share/scripts/faders_to_trims.lua b/share/scripts/faders_to_trims.lua
new file mode 100644
index 0000000000..3fd62833f2
--- /dev/null
+++ b/share/scripts/faders_to_trims.lua
@@ -0,0 +1,72 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Faders to Trims",
+ license = "MIT",
+ author = "PSmith",
+ description = [[Add 'Trim' plugins to all tracks. Move the fader value into the trim.]]
+}
+
+function action_params ()
+ return
+ {
+ }
+end
+
+
+function factory (params)
+ return function ()
+ -- loop over all tracks
+ for t in Session:get_tracks():iter() do
+
+ fader_value = t:gain_control():get_value()
+ if fader_value == 1 then
+ goto skip
+ end
+ if t:gain_control():automation_state() ~= ARDOUR.AutoState.Off then
+ goto skip
+ end
+
+ -- TODO: skip MIDI tracks without or with a post-fader synth
+ -- (fader is MIDI-velocity)
+
+ v = math.log(fader_value, 10)
+ trim_gain = 20*v
+ fader_pos = 0
+ local proc;
+ local i = 0;
+
+ repeat
+ -- find the fader proc
+ proc = t:nth_processor (i)
+ if (not proc:isnil() and proc:display_name () == "Fader") then
+ fader_pos = i
+ end
+ i = i + 1
+ until proc:isnil()
+
+ -- apply the trim
+ trim = t:nth_processor (fader_pos+1)
+ if (not trim:isnil() and trim:display_name () == "a-Amplifier") then
+ --existing trim found; sum the trim and the fader gain, and set the trim to that value
+ cur_gain = ARDOUR.LuaAPI.get_processor_param (trim, 0)
+ ARDOUR.LuaAPI.set_processor_param (trim, 0, trim_gain+cur_gain)
+ else
+ --create a new Trim processor, and set its value to match the fader
+ local a = ARDOUR.LuaAPI.new_luaproc(Session, "a-Amplifier");
+ if (not a:isnil()) then
+ t:add_processor_by_index(a, fader_pos-1, nil, true)
+ ARDOUR.LuaAPI.set_processor_param (a, 0, trim_gain)
+ a = nil -- explicitly drop shared-ptr reference
+ end
+ end
+
+ --zero the fader gain
+ t:gain_control():set_value(1, PBD.GroupControlDisposition.NoGroup)
+
+ ::skip::
+
+ end --foreach track
+
+ end --function
+
+end --factory
diff --git a/share/scripts/jump_to_marker.lua b/share/scripts/jump_to_marker.lua
new file mode 100644
index 0000000000..ea92020c83
--- /dev/null
+++ b/share/scripts/jump_to_marker.lua
@@ -0,0 +1,67 @@
+ardour { ["type"] = "EditorAction", name = "Jump to Marker",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Jump to the first marker matching a given pattern]]
+}
+
+function factory () return function ()
+ local keep = false
+
+ ::restart::
+
+ local dlg = LuaDialog.Dialog ("Search and Jump to Marker",
+ {
+ { type = "entry", key = "marker", default = '', title = "Marker Prefix" },
+ { type = "checkbox", key = "keep", default = keep, title = "Keep Dialog Open" },
+ })
+
+ local rv = dlg:run()
+ if not rv then return end
+
+ keep = rv['keep']
+
+ if (rv['marker'] == "") then
+ if keep then goto restart end
+ return
+ end
+
+ for l in Session:locations():list():iter() do
+ if l:is_mark() and string.find (l:name(), "^" .. rv['marker'] .. ".*$") then
+ Session:request_locate (l:start (), ARDOUR.LocateTransportDisposition.RollIfAppropriate, ARDOUR.TransportRequestSource.TRS_UI)
+ if keep then goto restart end
+ return
+ end
+ end
+
+ LuaDialog.Message ("Jump to Marker", "No marker matching the given pattern was found.", LuaDialog.MessageType.Warning, LuaDialog.ButtonType.Close):run ()
+
+ if keep then goto restart end
+end end
+
+
+function icon (params) return function (ctx, width, height, fg)
+ local mh = height - 3.5;
+ local m3 = width / 3;
+ local m6 = width / 6;
+
+ ctx:set_line_width (.5)
+
+ -- compare to gtk2_ardour/marker.cc "Marker"
+ ctx:set_source_rgba (.6, .6, .6, 1.0)
+ ctx:move_to (width / 2 - m6, 2)
+ ctx:rel_line_to (m3, 0)
+ ctx:rel_line_to (0, mh * 0.4)
+ ctx:rel_line_to (-m6, mh * 0.6)
+ ctx:rel_line_to (-m6, -mh * 0.6)
+ ctx:close_path ()
+ ctx:fill_preserve ()
+ ctx:set_source_rgba (.0, .0, .0, 1.0)
+ ctx:stroke ()
+
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil(math.min (width, height) * .5) .. "px")
+ txt:set_text ("txt")
+ local tw, th = txt:get_pixel_size ()
+ ctx:move_to (.5 * (width - tw), .5 * (height - th))
+ txt:show_in_cairo_context (ctx)
+end end
diff --git a/share/scripts/lfo_automation.lua b/share/scripts/lfo_automation.lua
new file mode 100644
index 0000000000..a69df1af1d
--- /dev/null
+++ b/share/scripts/lfo_automation.lua
@@ -0,0 +1,189 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Add LFO automation to region",
+ version = "0.1.1",
+ license = "MIT",
+ author = "Daniel Appelt",
+ description = [[Add LFO-like plugin automation to selected region]]
+}
+
+function factory (unused_params)
+ return function ()
+ -- Retrieve the first selected region
+ -- TODO: the following statement should do just that, no!?
+ -- local region = Editor:get_selection().regions:regionlist():front()
+ local region = nil
+ for r in Editor:get_selection().regions:regionlist():iter() do
+ if region == nil then region = r end
+ end
+
+ -- Bail out if no region was selected
+ if region == nil then
+ LuaDialog.Message("Add LFO automation to region", "Please select a region first!",
+ LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
+ return
+ end
+
+ -- Identify the track the region belongs to. There really is no better way?!
+ local track = nil
+ for route in Session:get_tracks():iter() do
+ for r in route:to_track():playlist():region_list():iter() do
+ if r == region then track = route:to_track() end
+ end
+ end
+
+ -- Get a list of all available plugin parameters on the track. This looks ugly. For the original code
+ -- see https://github.com/Ardour/ardour/blob/master/scripts/midi_cc_to_automation.lua
+ local targets = {}
+ local i = 0
+ while true do -- iterate over all plugins on the route
+ if track:nth_plugin(i):isnil() then break end
+
+ local proc = track:nth_plugin(i) -- ARDOUR.LuaAPI.plugin_automation() expects a proc not a plugin
+ local plug = proc:to_insert():plugin(0)
+ local plug_label = i .. ": " .. plug:name() -- Handle ambiguity if there are multiple plugin instances
+ local n = 0 -- Count control-ports separately. ARDOUR.LuaAPI.plugin_automation() only returns those.
+ for j = 0, plug:parameter_count() - 1 do -- Iterate over all parameters
+ if plug:parameter_is_control(j) then
+ local label = plug:parameter_label(j)
+ if plug:parameter_is_input(j) and label ~= "hidden" and label:sub(1,1) ~= "#" then
+ -- We need two return values: the plugin-instance and the parameter-id. We use a function to
+ -- return both values in order to avoid another sub-menu level in the dropdown.
+ local nn = n -- local scope for return value function
+ targets[plug_label] = targets[plug_label] or {}
+ targets[plug_label][label] = function() return {["p"] = proc, ["n"] = nn} end
+ end
+ n = n + 1
+ end
+ end
+
+ i = i + 1
+ end
+
+ -- Bail out if there are no plugin parameters
+ if next(targets) == nil then
+ LuaDialog.Message("Add LFO automation to region", "No plugin parameters found.",
+ LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
+ region, track, targets = nil, nil, nil
+ collectgarbage()
+ return
+ end
+
+ -- Display dialog to select (plugin and) plugin parameter, and LFO cycle type + min / max
+ local dialog_options = {
+ { type = "heading", title = "Add LFO automation to region", align = "left"},
+ { type = "dropdown", key = "param", title = "Plugin parameter", values = targets },
+ { type = "dropdown", key = "wave", title = "Waveform", values = {
+ ["Ramp up"] = 1, ["Ramp down"] = 2, ["Triangle"] = 3, ["Sine"] = 4,
+ ["Exp up"] = 5, ["Exp down"] = 6, ["Log up"] = 7, ["Log down"] = 8 } },
+ { type = "number", key = "cycles", title = "No. of cycles", min = 1, max = 16, step = 1, digits = 0 },
+ { type = "slider", key = "min", title = "Minimum in %", min = 0, max = 100, digits = 1 },
+ { type = "slider", key = "max", title = "Maximum in %", min = 0, max = 100, digits = 1, default = 100 }
+ }
+ local rv = LuaDialog.Dialog("Select target", dialog_options):run()
+
+ -- Return if the user cancelled
+ if not rv then
+ region, track, targets = nil, nil, nil
+ collectgarbage()
+ return
+ end
+
+ -- Parse user response
+ assert(type(rv["param"]) == "function")
+ local pp = rv["param"]() -- evaluate function, retrieve table {["p"] = proc, ["n"] = nn}
+ local al, _, pd = ARDOUR.LuaAPI.plugin_automation(pp["p"], pp["n"])
+ local wave = rv["wave"]
+ local cycles = rv["cycles"]
+ -- Compute minimum and maximum requested parameter values
+ local lower = pd.lower + rv["min"] / 100 * (pd.upper - pd.lower)
+ local upper = pd.lower + rv["max"] / 100 * (pd.upper - pd.lower)
+ track, targets, rv, pd = nil, nil, nil, nil
+ assert(not al:isnil())
+
+ -- Define lookup tables for our waves. Empty ones will be calculated in a separate step.
+ -- TODO: at this point we already know which one is needed, still we compute all.
+ local lut = {
+ { 0, 1 }, -- ramp up
+ { 1, 0 }, -- ramp down
+ { 0, 1, 0 }, -- triangle
+ {}, -- sine
+ {}, -- exp up
+ {}, -- exp down
+ {}, -- log up
+ {} -- log down
+ }
+
+ -- Calculate missing look up tables
+ local log_min = math.exp(-2 * math.pi)
+ for i = 0, 20 do
+ -- sine
+ lut[4][i+1] = 0.5 * math.sin(i * math.pi / 10) + 0.5
+ -- exp up
+ lut[5][i+1] = math.exp(-2 * math.pi + i * math.pi / 10)
+ -- log up
+ lut[7][i+1] = -math.log(1 + (i / log_min - i) / 20) / math.log(log_min)
+ end
+ -- "down" variants just need the values in reverse order
+ for i = 21, 1, -1 do
+ -- exp down
+ lut[6][22-i] = lut[5][i]
+ -- log down
+ lut[8][22-i] = lut[7][i]
+ end
+
+ -- Initialize undo
+ Session:begin_reversible_command("Add LFO automation to region")
+ local before = al:get_state() -- save previous state (for undo)
+ al:clear_list() -- clear target automation-list
+
+ local values = lut[wave]
+ local last = nil
+ for i = 0, cycles - 1 do
+ -- cycle length = region:length() / cycles
+ local cycle_start = region:position() - region:start() + i * region:length() / cycles
+ local offset = region:length() / cycles / (#values - 1)
+
+ for k, v in pairs(values) do
+ local pos = cycle_start + (k - 1) * offset
+ if k == 1 and v ~= last then
+ -- Move event one sample further. A larger offset might be needed to avoid unwanted effects.
+ pos = pos + 1
+ end
+
+ if k > 1 or v ~= last then
+ -- Create automation point re-scaled to parameter target range. Do not create a new point
+ -- at cycle start if the last cycle ended on the same value.
+ al:add(pos, lower + v * (upper - lower), false, true)
+ end
+ last = v
+ end
+ end
+
+ -- remove dense events
+ al:thin (20) -- threashold of area below curve
+
+ -- TODO: display the modified automation lane in the time line in order to make the change visible!
+
+ -- Save undo
+ -- TODO: in Ardour 5.12 this does not lead to an actual entry in the undo list?!
+ Session:add_command(al:memento_command(before, al:get_state()))
+ Session:commit_reversible_command(nil)
+
+ region, al, lut = nil, nil, nil
+ collectgarbage()
+ end
+end
+
+function icon (params) return function (ctx, width, height, fg)
+ local yc = height * .5
+ local x0 = math.ceil (width * .1)
+ local x1 = math.floor (width * .9)
+ ctx:set_line_width (1)
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ ctx:move_to (x0, yc * 1.5)
+ for x = x0, x1 do
+ ctx:line_to (x, yc + math.cos (3 * math.pi * (x-x0) / (x1-x0)) * yc * .5)
+ end
+ ctx:stroke ()
+end end
diff --git a/share/scripts/list_plugins.lua b/share/scripts/list_plugins.lua
new file mode 100644
index 0000000000..6a6efe12b8
--- /dev/null
+++ b/share/scripts/list_plugins.lua
@@ -0,0 +1,50 @@
+ardour { ["type"] = "EditorAction", name = "List Plugins",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[List and count plugins used in this session]]
+}
+
+function factory () return function ()
+ local rv = "Plugins used in this session:\n<span face=\"mono\">CNT | TYPE | NAME</span>"
+ local all_plugs = {}
+
+ for r in Session:get_routes ():iter () do
+ if r:is_monitor () or r:is_auditioner () then goto nextroute end -- skip special routes
+ local i = 0
+ while true do
+ local proc = r:nth_plugin (i)
+ if proc:isnil () then break end
+ local pi = proc:to_insert () -- we know it's a plugin-insert (we asked for nth_plugin)
+ local pp = pi:plugin (0)
+ local id = pi:type() .. "-" .. pp:unique_id()
+ local cnt = 0
+ if all_plugs[id] then cnt = all_plugs[id]['cnt'] end
+ all_plugs[id] = { name = proc:name(), ["type"] = pi:type(), id = pp:unique_id(), cnt = (cnt + 1) }
+ i = i + 1
+ end
+ ::nextroute::
+ end
+
+ function plugintypestr (t)
+ if (t == ARDOUR.PluginType.LADSPA) then return "LADSPA" end
+ if (t == ARDOUR.PluginType.LV2) then return "LV2" end
+ if (t == ARDOUR.PluginType.AudioUnit) then return "AU" end
+ if (t == ARDOUR.PluginType.Windows_VST) then return "VST" end
+ if (t == ARDOUR.PluginType.LXVST) then return "VST" end
+ if (t == ARDOUR.PluginType.MacVST) then return "VST" end
+ if (t == ARDOUR.PluginType.Lua) then return "Lua" end
+ return "??"
+ end
+
+ for k,v in pairs (all_plugs) do
+ print (string.format ("%2d * %-6s %-30s (%s)", v['cnt'], plugintypestr(v['type']), v['name'], v['id']))
+ rv = rv .. "\n" .. string.format ("%2d * %-6s %s", v['cnt'], plugintypestr(v['type']), v['name'])
+ end
+
+ LuaDialog.Message ("All Plugins", "<span face=\"mono\">" .. rv .. "</span>", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
+
+ all_plugs = nil
+ rv = ""
+ collectgarbage ();
+
+end end
diff --git a/share/scripts/ltc_reader.lua b/share/scripts/ltc_reader.lua
new file mode 100644
index 0000000000..b86dc009ef
--- /dev/null
+++ b/share/scripts/ltc_reader.lua
@@ -0,0 +1,82 @@
+ardour {
+ ["type"] = "dsp",
+ name = "LTC Reader",
+ category = "Utility",
+ author = "Ardour Team",
+ license = "MIT",
+ description = [[Linear Timecode Decoder with mixer strip inline display]]
+}
+
+function dsp_ioconfig ()
+ return { { audio_in = 1, audio_out = 1}, }
+end
+
+function dsp_init (rate)
+ timeout = rate
+ samplerate = rate
+ ltc_reader = ARDOUR.DSP.LTCReader (rate / 25, ARDOUR.DSP.LTC_TV_STANDARD.LTC_TV_FILM_24)
+ self:shmem():allocate(5)
+end
+
+function dsp_run (ins, outs, n_samples)
+ if ins[1] ~= outs[1] then
+ ARDOUR.DSP.copy_vector (outs[1]:offset (0), ins[1]:offset (0), n_samples)
+ end
+ ltc_reader:write (ins[1]:offset (0), n_samples, 0)
+ timeout = timeout + n_samples
+ local to_ui = self:shmem():to_int(0):array()
+ local rv
+ repeat
+ local tc
+ rv, tc = ltc_reader:read (0, 0, 0, 0)
+ if rv >= 0 then
+ timeout = 0
+ self:shmem():atomic_set_int (0, 1)
+ self:shmem():atomic_set_int (1, tc[1])
+ self:shmem():atomic_set_int (2, tc[2])
+ self:shmem():atomic_set_int (3, tc[3])
+ self:shmem():atomic_set_int (4, tc[4])
+ self:queue_draw ()
+ end
+ until rv < 0
+ if timeout > samplerate then
+ if 0 ~= self:shmem():atomic_get_int (0) then
+ self:shmem():atomic_set_int (0, 0)
+ self:queue_draw ()
+ end
+ end
+end
+
+-------------------------------------------------------------------------------
+-- inline UI
+--
+local txt = nil -- a pango context
+local vpadding = 2
+
+function render_inline (ctx, displaywidth, max_h)
+ if not txt then
+ txt = Cairo.PangoLayout (ctx, "Mono 10px")
+ end
+
+ local d = self:shmem():to_int(0):array()
+ if d[1] == 0 then
+ txt:set_text("--:--:--:--")
+ else
+ txt:set_text(string.format("%02d:%02d:%02d:%02d", d[2], d[3], d[4], d[5]))
+ end
+
+ -- compute the size of the display
+ local txtwidth, lineheight = txt:get_pixel_size()
+ local displayheight = math.min(vpadding + (lineheight + vpadding), max_h)
+
+ -- clear background
+ ctx:rectangle (0, 0, displaywidth, displayheight)
+ ctx:set_source_rgba (.2, .2, .2, 1.0)
+ ctx:fill ()
+ ctx:set_source_rgba (.8, .8, .8, 1.0)
+ ctx:move_to ((displaywidth - txtwidth) / 2, 1)
+ txt:show_in_cairo_context (ctx)
+
+ return {displaywidth, displayheight}
+end
+
diff --git a/share/scripts/meter_tap.lua b/share/scripts/meter_tap.lua
new file mode 100644
index 0000000000..645af522bd
--- /dev/null
+++ b/share/scripts/meter_tap.lua
@@ -0,0 +1,42 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Meter Tap",
+ author = "Ardour Lua Taskforce",
+ description = [[Change Metering Point for tracks in your session.]]
+}
+
+function factory () return function ()
+
+ local dialog_options = {
+ { type = "label", colspan = 5, title = "" },
+ { type = "radio", col = 1, colspan = 7, key = "select", title = "", values ={ ["Set All: Input"] = ARDOUR.MeterPoint.MeterInput, ["Set All: Pre Fader"] = ARDOUR.MeterPoint.MeterPreFader, ["Set All: Post Fader"] = ARDOUR.MeterPoint.MeterPostFader, ["Set All: Output"] = ARDOUR.MeterPoint.MeterOutput, ["Set All: Custom"] = ARDOUR.MeterPoint.MeterCustom}, default = "Set All: Input"},
+ { type = "label", colspan = 5, title = "" },
+ { type = "checkbox", col=1, colspan = 1, key = "select-tracks", default = true, title = "Selected tracks only"},
+ { type = "checkbox", col=2, colspan = 1, key = "rec-tracks", default = true, title = "Record Enabled tracks only"},
+ { type = "label", colspan = 5, title = "" },
+ }
+
+ local rv = LuaDialog.Dialog("Change all Meter Taps:", dialog_options):run()
+ if not rv then return end -- user cancelled
+
+ local rl;
+ if rv['select-tracks'] then
+ rl = Editor:get_selection ()
+ else
+ rl = Session:get_routes()
+ end
+
+ local meter_point = rv['select']
+
+ for route in rl:iter() do
+ if not(route:to_track():isnil()) then
+ if rv['rec-tracks'] then
+ if route:rec_enable_control():get_value() == 1.0 then
+ route:to_track():set_meter_point(meter_point, false)
+ end
+ else
+ route:to_track():set_meter_point(meter_point, false)
+ end
+ end
+ end
+end end
diff --git a/share/scripts/midi_cc_to_automation.lua b/share/scripts/midi_cc_to_automation.lua
new file mode 100644
index 0000000000..a1bab91a05
--- /dev/null
+++ b/share/scripts/midi_cc_to_automation.lua
@@ -0,0 +1,130 @@
+ardour { ["type"] = "EditorAction", name = "MIDI CC to Plugin Automation",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Parse a given MIDI control changes (CC) from all selected MIDI regions and convert them into plugin parameter automation]]
+}
+
+function factory () return function ()
+ -- find possible target parameters, collect them in a nested table
+ -- [track-name] -> [plugin-name] -> [parameters]
+ -- to allow selection in a dropdown menu
+ local targets = {}
+ local have_entries = false
+ for r in Session:get_routes ():iter () do -- for every track/bus
+ if r:is_monitor () or r:is_auditioner () then goto nextroute end -- skip special routes
+ local i = 0
+ while true do -- iterate over all plugins on the route
+ local proc = r:nth_plugin (i)
+ if proc:isnil () then break end
+ local plug = proc:to_insert ():plugin (0) -- we know it's a plugin-insert (we asked for nth_plugin)
+ local n = 0 -- count control-ports
+ for j = 0, plug:parameter_count () - 1 do -- iterate over all plugin parameters
+ if plug:parameter_is_control (j) then
+ local label = plug:parameter_label (j)
+ if plug:parameter_is_input (j) and label ~= "hidden" and label:sub (1,1) ~= "#" then
+ local nn = n --local scope for return value function
+ -- create table parents only if needed (if there's at least one parameter)
+ if not targets [r:name ()] then targets [r:name ()] = {} end
+ -- TODO handle ambiguity if there are 2 plugins with the same name on the same track
+ if not targets [r:name ()][proc:display_name ()] then targets [r:name ()][proc:display_name ()] = {} end
+ -- we need 2 return values: the plugin-instance and the parameter-id, so we use a table (associative array)
+ -- however, we cannot directly use a table: the dropdown menu would expand it as another sub-menu.
+ -- so we produce a function that will return the table.
+ targets [r:name ()][proc:display_name ()][label] = function () return {["p"] = proc, ["n"] = nn} end
+ have_entries = true
+ end
+ n = n + 1
+ end
+ end
+ i = i + 1
+ end
+ ::nextroute::
+ end
+
+ -- bail out if there are no parameters
+ if not have_entries then
+ LuaDialog.Message ("CC to Plugin Automation", "No Plugins found", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
+ targets = nil
+ collectgarbage ()
+ return
+ end
+
+ -- create a dialog, ask user which MIDI-CC to map and to what parameter
+ local dialog_options = {
+ { type = "heading", title = "MIDI CC Source", align = "left" },
+ { type = "number", key = "channel", title = "Channel", min = 1, max = 16, step = 1, digits = 0 },
+ { type = "number", key = "ccparam", title = "CC Parameter", min = 0, max = 127, step = 1, digits = 0 },
+ { type = "heading", title = "Target Track and Plugin", align = "left"},
+ { type = "dropdown", key = "param", title = "Target Parameter", values = targets }
+ }
+ local rv = LuaDialog.Dialog ("Select Taget", dialog_options):run ()
+
+ targets = nil -- drop references (the table holds shared-pointer references to all plugins)
+ collectgarbage () -- and release the references immediately
+
+ if not rv then return end -- user cancelled
+
+ -- parse user response
+
+ assert (type (rv["param"]) == "function")
+ local midi_channel = rv["channel"] - 1 -- MIDI channel 0..15
+ local cc_param = rv["ccparam"]
+ local pp = rv["param"]() -- evaluate function, retrieve table {["p"] = proc, ["n"] = nn}
+ local al, _, pd = ARDOUR.LuaAPI.plugin_automation (pp["p"], pp["n"])
+ rv = nil -- drop references
+ assert (not al:isnil ())
+ assert (midi_channel >= 0 and midi_channel < 16)
+ assert (cc_param >= 0 and cc_param < 128)
+
+ -- all systems go
+ local add_undo = false
+ Session:begin_reversible_command ("CC to Automation")
+ local before = al:get_state () -- save previous state (for undo)
+ al:clear_list () -- clear target automation-list
+
+ -- for all selected MIDI regions
+ local sel = Editor:get_selection ()
+ for r in sel.regions:regionlist ():iter () do
+ local mr = r:to_midiregion ()
+ if mr:isnil () then goto next end
+
+ -- get list of MIDI-CC events for the given channel and parameter
+ local ec = mr:control (Evoral.Parameter (ARDOUR.AutomationType.MidiCCAutomation, midi_channel, cc_param), false)
+ if ec:isnil () then goto next end
+ if ec:list ():events ():size () == 0 then goto next end
+
+ -- MIDI events are timestamped in "bar-beat" units, we need to convert those
+ -- using the tempo-map, relative to the region-start
+ local bfc = ARDOUR.DoubleBeatsSamplesConverter (Session:tempo_map (), r:start ())
+
+ -- iterate over CC-events
+ for av in ec:list ():events ():iter () do
+ -- re-scale event to target range
+ local val = pd.lower + (pd.upper - pd.lower) * av.value / 127
+ -- and add it to the target-parameter automation-list
+ al:add (r:position () - r:start () + bfc:to (av.when), val, false, true)
+ add_undo = true
+ end
+ ::next::
+ end
+
+ -- save undo
+ if add_undo then
+ local after = al:get_state ()
+ Session:add_command (al:memento_command (before, after))
+ Session:commit_reversible_command (nil)
+ else
+ Session:abort_reversible_command ()
+ LuaDialog.Message ("CC to Plugin Automation", "No data was converted. Was a MIDI-region with CC-automation selected? ", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
+ end
+ collectgarbage ()
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil (height / 3) .. "px")
+ txt:set_text ("CC\nPA")
+ local tw, th = txt:get_pixel_size ()
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ ctx:move_to (.5 * (width - tw), .5 * (height - th))
+ txt:show_in_cairo_context (ctx)
+end end
diff --git a/share/scripts/midi_remap.lua b/share/scripts/midi_remap.lua
new file mode 100644
index 0000000000..4fb7919e23
--- /dev/null
+++ b/share/scripts/midi_remap.lua
@@ -0,0 +1,97 @@
+ardour {
+ ["type"] = "dsp",
+ name = "MIDI Note Mapper",
+ category = "Utility",
+ license = "MIT",
+ author = "Alby Musaelian",
+ description = [[Map arbitrary MIDI notes to others. Affects Note On/Off and polyphonic key pressure. Note that if a single note is mapped multiple times, the last mapping wins -- MIDI events are never duplicated.]]
+}
+
+-- The number of remapping pairs to allow. Increasing this (at least in theory)
+-- decreases performace, so it's set fairly low as a default. The user can
+-- increase this if they have a need to.
+N_REMAPINGS = 10
+
+OFF_NOTE = -1
+
+function dsp_ioconfig ()
+ return { { midi_in = 1, midi_out = 1, audio_in = 0, audio_out = 0}, }
+end
+
+
+function dsp_params ()
+
+ local map_scalepoints = {}
+ map_scalepoints["None"] = OFF_NOTE
+ for note=0,127 do
+ local name = ARDOUR.ParameterDescriptor.midi_note_name(note)
+ map_scalepoints[string.format("%03d (%s)", note, name)] = note
+ end
+
+ local map_params = {}
+
+ i = 1
+ for mapnum = 1,N_REMAPINGS do
+ -- From and to
+ for _,name in pairs({"| #" .. mapnum .. " Map note", "|__ to"}) do
+ map_params[i] = {
+ ["type"] = "input",
+ name = name,
+ min = -1,
+ max = 127,
+ default = OFF_NOTE,
+ integer = true,
+ enum = true,
+ scalepoints = map_scalepoints
+ }
+ i = i + 1
+ end
+ end
+
+ return map_params
+end
+
+function dsp_run (_, _, n_samples)
+ assert (type(midiin) == "table")
+ assert (type(midiout) == "table")
+ local cnt = 1;
+
+ function tx_midi (time, data)
+ midiout[cnt] = {}
+ midiout[cnt]["time"] = time;
+ midiout[cnt]["data"] = data;
+ cnt = cnt + 1;
+ end
+
+ -- We build the translation table every buffer because, as far as I can tell,
+ -- there's no way to only rebuild it when the parameters have changed.
+ -- As a result, it has to be updated every buffer for the parameters to have
+ -- any effect.
+
+ -- Restore translation table
+ local translation_table = {}
+ local ctrl = CtrlPorts:array()
+ for i=1,N_REMAPINGS*2,2 do
+ if not (ctrl[i] == OFF_NOTE) then
+ translation_table[ctrl[i]] = ctrl[i + 1]
+ end
+ end
+
+ -- for each incoming midi event
+ for _,b in pairs (midiin) do
+ local t = b["time"] -- t = [ 1 .. n_samples ]
+ local d = b["data"] -- get midi-event
+ local event_type
+ if #d == 0 then event_type = -1 else event_type = d[1] >> 4 end
+
+ if (#d == 3) and (event_type == 9 or event_type == 8 or event_type == 10) then -- note on, note off, poly. afterpressure
+ -- Do the mapping - 2 is note byte for these types
+ d[2] = translation_table[d[2]] or d[2]
+ if not (d[2] == OFF_NOTE) then
+ tx_midi (t, d)
+ end
+ else
+ tx_midi (t, d)
+ end
+ end
+end
diff --git a/share/scripts/midimon.lua b/share/scripts/midimon.lua
new file mode 100644
index 0000000000..7f58fd34de
--- /dev/null
+++ b/share/scripts/midimon.lua
@@ -0,0 +1,213 @@
+ardour {
+ ["type"] = "dsp",
+ name = "a-MIDI Monitor",
+ category = "Visualization",
+ license = "GPLv2",
+ author = "Ardour Team",
+ description = [[Display recent MIDI events inline in the mixer strip]]
+}
+
+local maxevents = 20
+local ringsize = maxevents * 3
+local evlen = 3
+local hpadding, vpadding = 4, 2
+
+function dsp_ioconfig ()
+ return { { midi_in = 1, midi_out = 1, audio_in = -1, audio_out = -1}, }
+end
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input",
+ name = "Font size",
+ doc = "Text size used by the monitor to display midi events",
+ min = 4, max = 12, default = 7, integer = true },
+ { ["type"] = "input",
+ name = "Line count",
+ doc = "How many events will be shown at most",
+ min = 1, max = maxevents, default = 6, integer = true },
+ { ["type"] = "input",
+ name = "Hexadecimal",
+ doc = "If enabled, values will be printed in hexadecimal notation",
+ min = 0, max = 1, default = 0, toggled = true },
+ { ["type"] = "input",
+ name = "System messages",
+ doc = "If enabled, the monitor will show System Control and Real-Time messages",
+ min = 0, max = 1, default = 0, toggled = true },
+ { ["type"] = "input",
+ name = "Numeric Notes",
+ doc = "If enabled, note-events displayed numerically",
+ min = 0, max = 1, default = 0, toggled = true },
+ }
+end
+
+function dsp_init (rate)
+ -- create a shmem space to hold latest midi events
+ -- a int representing the index of the last event, and
+ -- a C-table as storage for events.
+ self:shmem():allocate(1 + ringsize*evlen)
+ self:shmem():atomic_set_int(0, 1)
+ local buffer = self:shmem():to_int(1):array()
+ for i = 1, ringsize*evlen do
+ buffer[i] = -1 -- sentinel for empty slot
+ end
+end
+
+function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
+ local pos = self:shmem():atomic_get_int(0)
+ local buffer = self:shmem():to_int(1):array()
+
+ -- passthrough all data
+ ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("audio"))
+ ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("midi"))
+
+ -- then fill the event buffer
+ local ib = in_map:get (ARDOUR.DataType ("midi"), 0) -- index of 1st midi input
+
+ if ib ~= ARDOUR.ChanMapping.Invalid then
+ local events = bufs:get_midi (ib):table () -- copy event list into a lua table
+
+ -- iterate over all MIDI events
+ for _, e in pairs (events) do
+ local ev = e:buffer():array()
+ pos = pos % ringsize + 1
+ -- copy the data
+ for j = 1, math.min(e:size(),evlen) do
+ buffer[(pos-1)*evlen + j] = ev[j]
+ end
+ -- zero unused slots
+ for j = e:size()+1, evlen do
+ buffer[(pos-1)*evlen + j] = 0
+ end
+ end
+ end
+
+ self:shmem():atomic_set_int(0, pos)
+
+ self:queue_draw ()
+end
+
+local txt = nil -- a pango context
+local cursize = 0
+local hex = nil
+local format_note = nil
+local show_scm = nil
+
+function format_note_name(b)
+ return string.format ("%5s", ARDOUR.ParameterDescriptor.midi_note_name (b))
+end
+
+function format_note_num(b)
+ return string.format (hex, b)
+end
+
+
+function show_midi(ctx, x, y, buffer, event)
+ local base = (event - 1) * evlen
+ if buffer[base+1] == -1 then return false end
+ local evtype = buffer[base + 1] >> 4
+ local channel = (buffer[base + 1] & 15) + 1 -- for System Common Messages this has no use
+ if evtype == 8 then
+ txt:set_text(string.format("%02u \u{2669}Off%s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
+ elseif evtype == 9 then
+ txt:set_text(string.format("%02u \u{2669}On %s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
+ elseif evtype == 10 then
+ txt:set_text(string.format("%02u \u{2669}KP %s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
+ elseif evtype == 11 then
+ txt:set_text(string.format("%02u CC " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
+ elseif evtype == 12 then
+ txt:set_text(string.format("%02u PRG " .. hex, channel, buffer[base+2]))
+ elseif evtype == 13 then
+ txt:set_text(string.format("%02u KP " .. hex, channel, buffer[base+2]))
+ elseif evtype == 14 then
+ txt:set_text(string.format("%02u PBnd" .. hex, channel, buffer[base+2] | buffer[base+3] << 7))
+ elseif show_scm > 0 then -- System Common Message
+ local message = buffer[base + 1] & 15
+ if message == 0 then
+ txt:set_text("-- SysEx")
+ elseif message == 1 then
+ txt:set_text(string.format("-- Time Code" .. hex, buffer[base+2]))
+ elseif message == 2 then
+ txt:set_text(string.format("-- Song Pos" .. hex, buffer[base+2] | buffer[base+3] << 7))
+ elseif message == 3 then
+ txt:set_text(string.format("-- Select Song" .. hex, buffer[base+2]))
+ elseif message == 6 then
+ txt:set_text("-- Tune Rq")
+ elseif message == 8 then
+ txt:set_text("-- Timing")
+ elseif message == 10 then
+ txt:set_text("-- Start")
+ elseif message == 11 then
+ txt:set_text("-- Continue")
+ elseif message == 12 then
+ txt:set_text("-- Stop")
+ elseif message == 14 then
+ txt:set_text("-- Active")
+ elseif message == 15 then
+ txt:set_text("-- Reset")
+ else
+ return false
+ end
+ else
+ return false
+ end
+ ctx:move_to (x, y)
+ txt:show_in_cairo_context (ctx)
+ return true
+end
+
+function render_inline (ctx, displaywidth, max_h)
+ local ctrl = CtrlPorts:array ()
+ local pos = self:shmem():atomic_get_int(0)
+ local buffer = self:shmem():to_int(1):array()
+ local count = ctrl[2]
+
+ if not txt or cursize ~= ctrl[1] then
+ cursize = math.floor(ctrl[1])
+ txt = Cairo.PangoLayout (ctx, "Mono " .. cursize)
+ end
+
+ if ctrl[3] > 0 then hex = " %02X" else hex = " %3u" end
+ show_scm = ctrl[4]
+
+ if ctrl[5] > 0 then
+ format_note = format_note_num
+ else
+ format_note = format_note_name
+ end
+
+ -- compute the size of the display
+ txt:set_text("0")
+ local _, lineheight = txt:get_pixel_size()
+ local displayheight = math.min(vpadding + (lineheight + vpadding) * count, max_h)
+
+ -- compute starting position (pango anchors text at north-west corner)
+ local x, y = hpadding, displayheight - lineheight - vpadding
+
+ -- clear background
+ ctx:rectangle (0, 0, displaywidth, displayheight)
+ ctx:set_source_rgba (.2, .2, .2, 1.0)
+ ctx:fill ()
+
+ -- color of latest event
+ ctx:set_source_rgba (1.0, 1.0, 1.0, 1.0)
+
+ -- print events
+ for i = pos, 1, -1 do
+ if y < 0 then break end
+ if show_midi(ctx, x, y, buffer, i) then
+ y = y - lineheight - vpadding
+ ctx:set_source_rgba (.8, .8, .8, 1.0)
+ end
+ end
+ for i = ringsize, pos+1, -1 do
+ if y < 0 then break end
+ if show_midi(ctx, x, y, buffer, i) then
+ y = y - lineheight - vpadding
+ ctx:set_source_rgba (.8, .8, .8, 1.0)
+ end
+ end
+
+ return {displaywidth, displayheight}
+end
diff --git a/share/scripts/mixer_screenshot.lua b/share/scripts/mixer_screenshot.lua
new file mode 100644
index 0000000000..f8e8ded2db
--- /dev/null
+++ b/share/scripts/mixer_screenshot.lua
@@ -0,0 +1,46 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Mixer Screenshot",
+ author = "Ardour Team",
+ description = [[Save a screenshot of the complete mixer-window, regardless of scrollbars or visible screen area]]
+}
+
+function factory () return function ()
+ local rv = LuaDialog.Dialog ("Save Mixer Screenshot",
+ {
+ { type = "createfile", key = "file", title = "", path = ARDOUR.LuaAPI.build_filename(Session:path(), "export", "mixer.png") },
+ }):run()
+
+ if (rv) then
+ if (ARDOUR.LuaAPI.file_test (rv['file'], ARDOUR.LuaAPI.FileTest.Exists)) then
+ local ok = LuaDialog.Message ("File Exists", "File '".. rv['file'] .. "' exists.\nReplace?", LuaDialog.MessageType.Question, LuaDialog.ButtonType.Yes_No):run ()
+ if ok ~= LuaDialog.Response.Yes then
+ return
+ end
+ end
+ ArdourUI.mixer_screenshot (rv['file'])
+ end
+ collectgarbage ()
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ local wh = math.min (width, height) * .5
+ ctx:translate (math.floor (width * .5 - wh), math.floor (height * .5 - wh))
+
+ ctx:rectangle (wh * .6, wh * .6, wh * .8, wh * .8)
+ ctx:set_source_rgba (.1, .1, .1, 1)
+ ctx:fill ()
+
+ ctx:set_line_width (1)
+ ctx:set_source_rgba (.9, .9, .9, 1)
+
+ ctx:move_to (wh * 0.3, wh * 0.6)
+ ctx:line_to (wh * 1.5, wh * 0.6)
+ ctx:line_to (wh * 1.5, wh * 1.7)
+ ctx:stroke ()
+
+ ctx:move_to (wh * 0.6, wh * 0.3)
+ ctx:line_to (wh * 0.6, wh * 1.4)
+ ctx:line_to (wh * 1.8, wh * 1.4)
+ ctx:stroke ()
+end end
diff --git a/share/scripts/mixer_settings_recall.lua b/share/scripts/mixer_settings_recall.lua
new file mode 100644
index 0000000000..c8f683eb98
--- /dev/null
+++ b/share/scripts/mixer_settings_recall.lua
@@ -0,0 +1,470 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Recall Mixer Settings",
+ author = "Mixbus Team",
+ description = [[
+
+ Recalls mixer settings outined by files
+ created by Store Mixer Settings.
+
+ Allows for some room to change Source
+ and Destination.
+
+ ]]
+}
+
+function factory ()
+
+ local acoraida_monicas_last_used_recall_file
+
+ return function ()
+
+ local user_cfg = ARDOUR.user_config_directory(-1)
+ local local_path = ARDOUR.LuaAPI.build_filename(Session:path(), 'mixer_settings')
+ local global_path = ARDOUR.LuaAPI.build_filename(user_cfg, 'mixer_settings')
+
+ local invalidate = {}
+
+ function exists(file)
+ local ok, err, code = os.rename(file, file)
+ if not ok then
+ if code == 13 then -- Permission denied, but it exists
+ return true
+ end
+ end return ok, err
+ end
+
+ function whoami()
+ if not pcall(function() local first_check = Session:get_mixbus(0) end) then
+ return "Ardour"
+ else
+ local second_check = Session:get_mixbus(11)
+ if second_check:isnil() then
+ return "Mixbus"
+ else
+ return "32C"
+ end
+ end
+ end
+
+ function isdir(path)
+ return exists(path.."/")
+ end
+
+ function get_processor_by_name(track, name)
+ local i = 0
+ local proc = track:nth_processor(i)
+ repeat
+ if(proc:display_name() == name) then
+ return proc
+ else
+ i = i + 1
+ end
+ proc = track:nth_processor(i)
+ until proc:isnil()
+ end
+
+ function new_plugin(name, type)
+ local plugin = ARDOUR.LuaAPI.new_plugin(Session, name, type, "")
+ if not(plugin:isnil()) then return plugin end
+ end
+
+ function group_by_id(id)
+ local id = tonumber(id)
+ for g in Session:route_groups():iter() do
+ local group_id = tonumber(g:to_stateful():id():to_s())
+ if group_id == id then return g end
+ end
+ end
+
+ function group_by_name(name)
+ for g in Session:route_groups():iter() do
+ if g:name() == name then return g end
+ end
+ end
+
+ function route_groupid_interrogate(t)
+ local group = false
+ for g in Session:route_groups():iter() do
+ for r in g:route_list():iter() do
+ if r:name() == t:name() then group = g:to_stateful():id():to_s() end
+ end
+ end return group
+ end
+
+ function route_group_interrogate(t)
+ for g in Session:route_groups():iter() do
+ for r in g:route_list():iter() do
+ if r:name() == t:name() then return g end
+ end
+ end
+ end
+
+ function recall(debug, path, dry_run)
+ local file = io.open(path, "r")
+ assert(file, "File not found!")
+ local bypass_routes = {}
+
+ local i = 0
+ for l in file:lines() do
+ --print(i, l)
+
+ local create_groups = dry_run["create_groups"]
+ local skip_line = false
+
+ local plugin, route, group = false, false, false
+ local f = load(l)
+
+ if debug then
+ --print('create_groups ' .. tostring(create_groups))
+ print(i, string.sub(l, 0, 29), f)
+ end
+
+ if f then f() end
+
+ if instance["route_id"] then route = true end
+ if instance["plugin_id"] then plugin = true end
+ if instance["group_id"] then group = true end
+
+ if group then
+ local g_id = instance["group_id"]
+ local routes = instance["routes"]
+ local name = instance["name"]
+ local group = group_by_id(g_id)
+ if not(group) then
+ if create_groups then
+ local group = Session:new_route_group(name)
+ for _, v in pairs(routes) do
+ local rt = Session:route_by_id(PBD.ID(v))
+ if rt:isnil() then rt = Session:route_by_name(name) end
+ if not(rt:isnil()) then group:add(rt) end
+ end
+ end
+ end
+ end
+
+ if route then
+ local substitution = tonumber(dry_run["destination-"..i])
+ if substitution == 0 then
+ bypass_routes[#bypass_routes + 1] = instance["route_id"]
+ goto nextline
+ end
+
+ local old_order = ARDOUR.ProcessorList()
+ local route_id = instance["route_id"]
+ local r_id = PBD.ID(instance["route_id"])
+ local muted, soloed = instance["muted"], instance["soloed"]
+ local order = instance["order"]
+ local cache = instance["cache"]
+ local group = instance["group"]
+ local group_name = instance["group_name"]
+ local name = instance["route_name"]
+ local gc, tc, pc = instance["gain_control"], instance["trim_control"], instance["pan_control"]
+ local sends = instance["sends"]
+
+ if not(substitution == instance["route_id"]) then
+ print('SUBSTITUTION FOR: ', name, substitution, Session:route_by_id(PBD.ID(substitution)):name())
+ --bypass_routes[#bypass_routes + 1] = route_id
+ was_subbed = true
+ r_id = PBD.ID(substitution)
+ end
+
+ local rt = Session:route_by_id(r_id)
+ if rt:isnil() then rt = Session:route_by_name(name) end
+ if rt:isnil() then goto nextline end
+
+ if sends then
+ for i, data in pairs(sends) do
+ i = i-1
+ for j, ctrl in pairs({
+ rt:send_level_controllable(i),
+ rt:send_enable_controllable(i),
+ rt:send_pan_azimuth_controllable(i),
+ rt:send_pan_azimuth_enable_controllable(i),
+ }) do
+ if not(ctrl:isnil()) then
+ local value = data[j]
+ if value then
+ if debug then
+ print("Setting " .. ctrl:name() .. " to value " .. value)
+ end
+ ctrl:set_value(value, PBD.GroupControlDisposition.NoGroup)
+ end
+ end
+ end
+ end
+ end
+
+ local cur_group_id = route_groupid_interrogate(rt)
+ if not(group) and (cur_group_id) then
+ local g = group_by_id(cur_group_id)
+ if g then g:remove(rt) end
+ end
+
+ local rt_group = group_by_name(group_name)
+ if rt_group then rt_group:add(rt) end
+
+ well_known = {'PRE', 'Trim', 'EQ', 'Comp', 'Fader', 'POST'}
+ protected_instrument = false
+ for k, v in pairs(order) do
+ local proc = Session:processor_by_id(PBD.ID(1))
+ if not(was_subbed) then
+ proc = Session:processor_by_id(PBD.ID(v))
+ end
+ if proc:isnil() then
+ for id, sub_tbl in pairs(cache) do
+ local name = sub_tbl[1]
+ local type = sub_tbl[2]
+ if v == id then
+ proc = new_plugin(name, type)
+ for _, control in pairs(well_known) do
+ if name == control then
+ proc = get_processor_by_name(rt, control)
+ invalidate[v] = proc:to_stateful():id():to_s()
+ goto nextproc
+ end
+ end
+ if not(proc) then goto nextproc end
+ if not(proc:isnil()) then
+ rt:add_processor_by_index(proc, 0, nil, true)
+ invalidate[v] = proc:to_stateful():id():to_s()
+ end
+ end
+ end
+ end
+ ::nextproc::
+ if proc and not(proc:isnil()) then old_order:push_back(proc) end
+ if not(old_order:empty()) and not(protected_instrument) then
+ if not(rt:to_track():to_midi_track():isnil()) then
+ if not(rt:the_instrument():isnil()) then
+ protected_instrument = true
+ old_order:push_back(rt:the_instrument())
+ end
+ end
+ end
+ end
+ rt:reorder_processors(old_order, nil)
+ if muted then rt:mute_control():set_value(1, 1) else rt:mute_control():set_value(0, 1) end
+ if soloed then rt:solo_control():set_value(1, 1) else rt:solo_control():set_value(0, 1) end
+ rt:gain_control():set_value(gc, 1)
+ rt:trim_control():set_value(tc, 1)
+ if pc ~= false and not(rt:is_master()) then rt:pan_azimuth_control():set_value(pc, 1) end
+ end
+
+ if plugin then
+ --if the plugin is owned by a route
+ --we decided not to use, skip it
+ for _, v in pairs(bypass_routes) do
+ if instance["owned_by_route_id"] == v then
+ goto nextline
+ end
+ end
+
+ local enable = {}
+ local params = instance["parameters"]
+ local p_id = instance["plugin_id"]
+ local act = instance["active"]
+
+ for k, v in pairs(invalidate) do --invalidate any deleted plugin's id
+ if p_id == k then
+ p_id = v
+ end
+ end
+
+ local proc = Session:processor_by_id(PBD.ID(p_id))
+ if proc:isnil() then goto nextline end
+ local plug = proc:to_insert():plugin(0)
+
+ for k, v in pairs(params) do
+ local label = plug:parameter_label(k)
+ if string.find(label, "Assign") or string.find(label, "Enable") then --@ToDo: Check Plugin type == LADSPA or VST?
+ enable[k] = v --queue any assignments/enables for after the initial parameter recalling to duck the 'in-on-change' feature
+ end
+ print(string.format("%s (Port: %s) -> %s", label, k, v))
+ ARDOUR.LuaAPI.set_processor_param(proc, k, v)
+ end
+
+ for k, v in pairs(enable) do
+ ARDOUR.LuaAPI.set_processor_param(proc, k, v)
+ end
+ if act then proc:activate() else proc:deactivate() end
+ end
+
+ ::nextline::
+ i = i + 1
+
+ end
+ end
+
+ function dry_run(debug, path)
+ --returns a dialog-able table of
+ --everything we do (logically)
+ --in the recall function
+ local route_values = {['----'] = "0"}
+ for r in Session:get_routes():iter() do
+ route_values[r:name()] = r:to_stateful():id():to_s()
+ end
+
+ local i = 0
+ local dry_table = {
+ {type = "label", align="right", key="col-1-title", col=0, colspan=1, title = 'Source:'},
+ {type = "label", align="left", key="col-2-title", col=1, colspan=1, title = 'Destination:'},
+ }
+ local file = io.open(path, "r")
+ assert(file, "File not found!")
+ pad = 0
+ for l in file:lines() do
+ local do_plugin, do_route, do_group = false, false, false
+ local f = load(l)
+
+ if debug then
+ print(i, string.sub(l, 0, 29), f)
+ end
+
+ if f then f() end
+
+ if instance["route_id"] then do_route = true end
+ if instance["plugin_id"] then do_plugin = true end
+ if instance["group_id"] then do_group = true end
+
+ if do_group then
+ local group_id = instance["group_id"]
+ local group_name = instance["name"]
+ local dlg_title, action_title = "", ""
+
+ local group_ptr = group_by_id(group_id)
+
+ if not(group_ptr) then
+ dlg_title = string.format("(Group) %s.", group_name)
+ --action_title = "will create and use settings"
+ else
+ dlg_title = string.format("(Group) %s.", group_ptr:name())
+ --action_title = "will use group settings"
+ end
+ table.insert(dry_table, {
+ order=pad, type = "label", align="right", key = "group-"..i , col = 0, colspan = 1, title = dlg_title
+ })
+ pad = pad + 1
+ end
+
+ if do_route then
+ local route_id = instance["route_id"]
+ local route_name = instance["route_name"]
+ local dlg_title = ""
+
+ local route_ptr = Session:route_by_id(PBD.ID(route_id))
+
+ if route_ptr:isnil() then
+ route_ptr = Session:route_by_name(route_name)
+ if not(route_ptr:isnil()) then
+ dlg_title = string.format("%s", route_ptr:name())
+ --action_title = "will use route settings"
+ else
+ dlg_title = string.format("%s", route_name)
+ --action_title = "will be ignored"
+ end
+ else
+ dlg_title = string.format("%s", route_ptr:name())
+ --action_title = "will use route settings"
+ end
+ if route_ptr:isnil() then name = route_name else name = route_ptr:name() end
+
+ table.insert(dry_table, {
+ order=instance['pi_order']+pad, type = "label", align="right", key = "route-"..i , col = 0, colspan = 1, title = dlg_title
+ })
+ table.insert(dry_table, {
+ type = "dropdown", align="left", key = "destination-"..i, col = 1, colspan = 1, title = "", values = route_values, default = name or "----"
+ })
+ end
+ i = i + 1
+ end
+ table.insert(dry_table, {
+ type = "checkbox", col=0, colspan=2, align="left", key = "create_groups", default = true, title = "Create Groups if necessary?"
+ })
+ return dry_table
+ end
+
+ local global_vs_local_dlg = {
+ { type = "label", col=0, colspan=20, align="left", title = "" },
+ {
+ type = "radio", col=0, colspan=20, align="left", key = "recall-dir", title = "", values =
+ {
+ ['Pick from Global Settings'] = 1, ['Pick from Local Settings'] = 2, ['Last Used Recall File'] = 3,
+ },
+ default = 'Last Used Recall File'
+ },
+ { type = "label", col=0, colspan=20, align="left", title = ""},
+ }
+
+ local recall_options = {
+ { type = "label", col=0, colspan=10, align="left", title = "" },
+ { type = "file", col=0, colspan=15, align="left", key = "file", title = "Select a Settings File", path = ARDOUR.LuaAPI.build_filename(Session:path(), "export", "params.lua") },
+ { type = "label", col=0, colspan=10, align="left", title = "" },
+ }
+
+ local gvld = LuaDialog.Dialog("Recall Mixer Settings:", global_vs_local_dlg):run()
+
+ if not(gvld) then
+ return
+ else
+ if gvld['recall-dir'] == 1 then
+ local global_ok = isdir(global_path)
+ local global_default_path = ARDOUR.LuaAPI.build_filename(global_path, string.format("FactoryDefault-%s.lua", whoami()))
+ print(global_default_path)
+ if global_ok then
+ recall_options[2]['path'] = global_default_path
+ local rv = LuaDialog.Dialog("Recall Mixer Settings:", recall_options):run()
+ if not(rv) then return end
+ local dry_return = LuaDialog.Dialog("Recall Mixer Settings:", dry_run(false, rv['file'])):run()
+ if dry_return then
+ acoraida_monicas_last_used_recall_file = rv['file']
+ recall(false, rv['file'], dry_return)
+ else
+ return
+ end
+ else
+ LuaDialog.Message ("Recall Mixer Settings:",
+ global_path .. ' does not exist!\nPlease run Store Mixer Settings first.',
+ LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
+ end
+ end
+
+ if gvld['recall-dir'] == 2 then
+ local local_ok = isdir(local_path)
+ local local_default_path = ARDOUR.LuaAPI.build_filename(local_path, 'asdf')
+ print(local_default_path)
+ if local_ok then
+ recall_options[2]['path'] = local_default_path
+ local rv = LuaDialog.Dialog("Recall Mixer Settings:", recall_options):run()
+ if not(rv) then return end
+ local dry_return = LuaDialog.Dialog("Recall Mixer Settings:", dry_run(false, rv['file'])):run()
+ if dry_return then
+ acoraida_monicas_last_used_recall_file = rv['file']
+ recall(true, rv['file'], dry_return)
+ else
+ return
+ end
+ else
+ LuaDialog.Message ("Recall Mixer Settings:",
+ local_path .. 'does not exist!\nPlease run Store Mixer Settings first.',
+ LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
+ end
+ end
+
+ if gvld['recall-dir'] == 3 then
+ if acoraida_monicas_last_used_recall_file then
+ local dry_return = LuaDialog.Dialog("Recall Mixer Settings:", dry_run(false, acoraida_monicas_last_used_recall_file)):run()
+ if dry_return then
+ recall(true, acoraida_monicas_last_used_recall_file, dry_return)
+ else
+ return
+ end
+ else
+ LuaDialog.Message ("Script has no record of last used file:",
+ 'Please pick a recall file and then this option will be available',
+ LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run()
+ end
+ end
+ end
+
+end end
diff --git a/share/scripts/mixer_settings_store.lua b/share/scripts/mixer_settings_store.lua
new file mode 100644
index 0000000000..1718a83ddd
--- /dev/null
+++ b/share/scripts/mixer_settings_store.lua
@@ -0,0 +1,383 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Store Mixer Settings",
+ author = "Mixbus Team",
+ description = [[
+
+ Stores the current Mixer state as a file
+ that can be read and recalled arbitrarily
+ by it's companion script, Recall Mixer Settings.
+
+ Supports: processor settings, grouping,
+ mute, solo, gain, trim, pan and processor ordering,
+ plus re-adding certain deleted plugins.
+
+ ]]
+}
+
+function factory () return function ()
+
+ local user_cfg = ARDOUR.user_config_directory(-1)
+ local local_path = ARDOUR.LuaAPI.build_filename(Session:path(), 'mixer_settings')
+ local global_path = ARDOUR.LuaAPI.build_filename(user_cfg, 'mixer_settings')
+
+ function exists(file)
+ local ok, err, code = os.rename(file, file)
+ if not ok then
+ if code == 13 then -- Permission denied, but it exists
+ return true
+ end
+ end return ok, err
+ end
+
+ function whoami()
+ if not pcall(function() local first_check = Session:get_mixbus(0) end) then
+ return "Ardour"
+ else
+ local second_check = Session:get_mixbus(11)
+ if second_check:isnil() then
+ return "Mixbus"
+ else
+ return "32C"
+ end
+ end
+ end
+
+ function isdir(path)
+ return exists(path.."/")
+ end
+
+ function setup_paths()
+ local global_ok, local_ok = false, false
+
+ if not(isdir(global_path)) then
+ global_ok, _, _ = os.execute('mkdir '.. global_path)
+ if global_ok == 0 then
+ global_ok = true
+ end
+ else
+ global_ok = true
+ end
+ if not(isdir(local_path)) then
+ local_ok, _, _ = os.execute('mkdir '.. local_path)
+ if local_ok == 0 then
+ local_ok = true
+ end
+ else
+ local_ok = true
+ end
+ return global_ok, local_ok
+ end
+
+ function get_processor_by_name(track, name)
+ local i = 0
+ local proc = track:nth_processor(i)
+ repeat
+ if(proc:display_name() == name) then
+ return proc
+ else
+ i = i + 1
+ end
+ proc = track:nth_processor(i)
+ until proc:isnil()
+ end
+
+ function group_by_id(id)
+ local id = tonumber(id)
+ for g in Session:route_groups():iter() do
+ local group_id = tonumber(g:to_stateful():id():to_s())
+ if group_id == id then return g end
+ end
+ end
+
+ function group_by_name(name)
+ for g in Session:route_groups():iter() do
+ if g:name() == name then return g end
+ end
+ end
+
+ function route_groupid_interrogate(t)
+ local group = false
+ for g in Session:route_groups():iter() do
+ for r in g:route_list():iter() do
+ if r:name() == t:name() then group = g:to_stateful():id():to_s() end
+ end
+ end return group
+ end
+
+ function route_group_interrogate(t)
+ for g in Session:route_groups():iter() do
+ for r in g:route_list():iter() do
+ if r:name() == t:name() then return g end
+ end
+ end
+ end
+
+ function empty_last_store(path) --empty current file from last run
+ local file = io.open(path, "w")
+ --file:write(string.format("instance = { whoami = '%s' }", whoami())
+ file:write("")
+ file:close()
+ end
+
+ function mark_tracks(selected, path)
+
+ empty_last_store(path)
+
+ local route_string = [[instance = {
+ route_id = %d,
+ route_name = '%s',
+ gain_control = %s,
+ trim_control = %s,
+ pan_control = %s,
+ sends = {%s},
+ muted = %s,
+ soloed = %s,
+ order = {%s},
+ cache = {%s},
+ group = %s,
+ group_name = '%s',
+ pi_order = %d
+ }]]
+
+ local group_string = [[instance = {
+ group_id = %s,
+ name = '%s',
+ routes = {%s},
+ }]]
+
+ local processor_string = [[instance = {
+ plugin_id = %d,
+ type = %d,
+ display_name = '%s',
+ owned_by_route_name = '%s',
+ owned_by_route_id = %d,
+ parameters = {%s},
+ active = %s,
+ }]]
+
+ local group_route_string = " [%d] = %s,"
+ local proc_order_string = " [%d] = %d,"
+ local proc_cache_string = " [%d] = {'%s', %d},"
+ local params_string = " [%d] = %s,"
+
+ --ensure easy-to-read formatting doesn't make it through
+ local route_string = string.gsub(route_string, "[\n\t%s]", "")
+ local group_string = string.gsub(group_string, "[\n\t%s]", "")
+ local processor_string = string.gsub(processor_string, "[\n\t%s]", "")
+
+ local sel = Editor:get_selection ()
+ local groups_to_write = {}
+ local i = 0
+
+ local tracks = Session:get_stripables()
+
+ if selected then tracks = sel.tracks:routelist() end
+
+ for r in tracks:iter() do
+ local group = route_group_interrogate(r)
+ if group then
+ local already_there = false
+ for _, v in pairs(groups_to_write) do
+ if group == v then
+ already_there = true
+ end
+ end
+ if not(already_there) then
+ groups_to_write[#groups_to_write + 1] = group
+ end
+ end
+ end
+
+ for _, g in pairs(groups_to_write) do
+ local tmp_str = ""
+ for t in g:route_list():iter() do
+ tmp_str = tmp_str .. string.format(group_route_string, i, t:to_stateful():id():to_s())
+ i = i + 1
+ end
+ local group_str = string.format(
+ group_string,
+ g:to_stateful():id():to_s(),
+ g:name(),
+ tmp_str
+ )
+
+ file = io.open(path, "a")
+ file:write(group_str, "\r\n")
+ file:close()
+ end
+
+ for r in tracks:iter() do
+ if r:is_monitor () or r:is_auditioner () or not(r:to_vca():isnil()) then goto nextroute end -- skip special routes
+ r = r:to_route()
+ if r:isnil() then goto nextroute end
+ local order = ARDOUR.ProcessorList()
+ local x = 0
+ repeat
+ local proc = r:nth_processor(x)
+ if not proc:isnil() then
+ order:push_back(proc)
+ end
+ x = x + 1
+ until proc:isnil()
+
+
+ local route_group = route_group_interrogate(r)
+ if route_group then route_group = route_group:name() else route_group = "" end
+ local rid = r:to_stateful():id():to_s()
+ local pan = r:pan_azimuth_control()
+ if pan:isnil() then pan = false else pan = pan:get_value() end --sometimes a route doesn't have pan, like the master.
+
+ -- Get send information, if any.
+ local send_string = ""
+ local i = 0
+ repeat
+ local fmt = "{%s, %s, %s, %s}"
+ string.gsub(fmt, "[\n\t]", "")
+ local values = {}
+ for j, ctrl in pairs({
+ r:send_level_controllable(i),
+ r:send_enable_controllable(i),
+ r:send_pan_azimuth_controllable(i),
+ r:send_pan_azimuth_enable_controllable(i),
+ }) do
+ if not(ctrl:isnil()) then
+ values[#values + 1] = ctrl:get_value()
+ else
+ values[#values + 1] = "nil"
+ end
+ end
+ send_string = send_string .. string.format(fmt, table.unpack(values))
+ send_string = send_string .. ","
+ i = i + 1
+ until r:send_enable_controllable(i):isnil()
+
+ print(send_string)
+
+ local order_nmbr = 0
+ local tmp_order_str, tmp_cache_str = "", ""
+ for p in order:iter() do
+ local ptype
+ if not(p:to_insert():isnil()) then
+ ptype = p:to_insert():plugin(0):get_info().type
+ else
+ ptype = 99
+ end
+ local pid = p:to_stateful():id():to_s()
+ if not(string.find(p:display_name(), "latcomp")) then
+ tmp_order_str = tmp_order_str .. string.format(proc_order_string, order_nmbr, pid)
+ tmp_cache_str = tmp_cache_str .. string.format(proc_cache_string, pid, p:display_name(), ptype)
+ end
+ order_nmbr = order_nmbr + 1
+ end
+
+ local route_str = string.format(
+ route_string,
+ rid,
+ r:name(),
+ ARDOUR.LuaAPI.ascii_dtostr(r:gain_control():get_value()),
+ ARDOUR.LuaAPI.ascii_dtostr(r:trim_control():get_value()),
+ tostring(pan),
+ send_string,
+ r:muted(),
+ r:soloed(),
+ tmp_order_str,
+ tmp_cache_str,
+ route_groupid_interrogate(r),
+ route_group,
+ r:presentation_info_ptr():order()
+ )
+
+ file = io.open(path, "a")
+ file:write(route_str, "\n")
+ file:close()
+
+ local i = 0
+ while true do
+ local params = {}
+ local proc = r:nth_plugin (i)
+ if proc:isnil () then break end
+ local active = proc:active()
+ local id = proc:to_stateful():id():to_s()
+ local plug = proc:to_insert ():plugin (0)
+ local ptype = proc:to_insert():plugin(0):get_info().type
+ local n = 0 -- count control-ports
+ for j = 0, plug:parameter_count () - 1 do -- iterate over all plugin parameters
+ if plug:parameter_is_control (j) then
+ local label = plug:parameter_label (j)
+ if plug:parameter_is_input (j) and label ~= "hidden" and label:sub (1,1) ~= "#" then
+ --local _, _, pd = ARDOUR.LuaAPI.plugin_automation(proc, n)
+ local val = ARDOUR.LuaAPI.get_processor_param(proc, j, true)
+ print(r:name(), "->", proc:display_name(), label, val)
+ params[j] = val
+ end
+ n = n + 1
+ end
+ end
+ i = i + 1
+
+ local tmp_params_str = ""
+ for k, v in pairs(params) do
+ tmp_params_str = tmp_params_str .. string.format(params_string, k, ARDOUR.LuaAPI.ascii_dtostr(v))
+ end
+
+ local proc_str = string.format(
+ processor_string,
+ id,
+ ptype,
+ proc:display_name(),
+ r:name(),
+ r:to_stateful():id():to_s(),
+ tmp_params_str,
+ active
+ )
+ file = io.open(path, "a")
+ file:write(proc_str, "\n")
+ file:close()
+ end
+ ::nextroute::
+ end
+ end
+
+ local store_options = {
+ { type = "label", col=0, colspan=1, align="right", title = "Name:" },
+ { type = "entry", col=1, colspan=1, align="left" , key = "filename", default = Session:name(), title=""},
+ { type = "label", col=0, colspan=1, align="right", title = "Location:" },
+ {
+ type = "radio", col=1, colspan=3, align="left", key = "store-dir", title = "", values =
+ {
+ ['Global (accessible from any session)'] = 1, ['Local (this session only)'] = 2
+ },
+ default = 'Locally (this session only)'
+ },
+ { type = "hseparator", title="", col=0, colspan = 3},
+ { type = "label", col=0, colspan=1, align="right", title = "Selected Tracks Only:" },
+ { type = "checkbox", col=1, colspan=1, align="left", key = "selected", default = false, title = ""},
+ --{ type = "label", col=0, colspan=2, align="left", title = ''},
+ --{ type = "label", col=0, colspan=2, align="left", title = "Global Path: " .. global_path},
+ --{ type = "label", col=0, colspan=2, align="left", title = "Local Path: " .. local_path},
+ }
+
+ local global_ok, local_ok = setup_paths()
+
+ if global_ok and local_ok then
+ local rv = LuaDialog.Dialog("Store Mixer Settings:", store_options):run()
+
+ if not(rv) then return end
+
+ local filename = rv['filename']
+ if rv['store-dir'] == 1 then
+ local store_path = ARDOUR.LuaAPI.build_filename(global_path, string.format("%s-%s.lua", filename, whoami()))
+ local selected = rv['selected']
+ mark_tracks(selected, store_path)
+ end
+
+ if rv['store-dir'] == 2 then
+ local store_path = ARDOUR.LuaAPI.build_filename(local_path, string.format("%s-%s.lua", filename, whoami()))
+ print(store_path)
+ local selected = rv['selected']
+ mark_tracks(selected, store_path)
+ end
+ end
+
+end end
diff --git a/share/scripts/mute_all_tracks.lua b/share/scripts/mute_all_tracks.lua
new file mode 100644
index 0000000000..e15c0530c0
--- /dev/null
+++ b/share/scripts/mute_all_tracks.lua
@@ -0,0 +1,21 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Mute All Tracks",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Mute All Tracks in the Session]]
+}
+
+function factory () return function ()
+ local ctrls = ARDOUR.ControlListPtr () -- create a list of controls to change
+
+ for r in Session:get_tracks ():iter () do -- iterate over all tracks in the session
+ ctrls:push_back (r:mute_control()) -- add the track's mute-control to the list of of controls to be changed
+ end
+
+ -- of more than one control is to be changed..
+ if ctrls:size() > 0 then
+ -- do it (queue change in realtime-context) set to "1" ; here 'muted'
+ Session:set_controls (ctrls, 1, PBD.GroupControlDisposition.NoGroup)
+ end
+end end
diff --git a/share/scripts/new_playlist.lua b/share/scripts/new_playlist.lua
new file mode 100644
index 0000000000..54563cd6a5
--- /dev/null
+++ b/share/scripts/new_playlist.lua
@@ -0,0 +1,17 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "New Playlist",
+ license = "MIT",
+ author = "Ardour Lua Taskforce",
+ description = [[Prompts and builds a new playlist for every track in the session.]]
+}
+
+function factory () return function ()
+
+ for r in Session:get_tracks():iter() do
+ local rtav = Editor:rtav_from_route(r) -- lookup RTAV
+ Editor:new_playlists(rtav:to_timeaxisview())
+ end
+
+collectgarbage()
+end end
diff --git a/share/scripts/noisegen.lua b/share/scripts/noisegen.lua
new file mode 100644
index 0000000000..bf7b006160
--- /dev/null
+++ b/share/scripts/noisegen.lua
@@ -0,0 +1,111 @@
+ardour {
+ ["type"] = "dsp",
+ name = "NoiseGen",
+ category = "Instrument",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Noise Generator (v-1.02)]]
+}
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "White/Pink", min = 0, max = 1, default = 0, toggled = true },
+ { ["type"] = "input", name = "Gain", min = -60, max = 0, default = -18, unit="dB" },
+ }
+end
+
+function dsp_ioconfig ()
+ return { [1] = { audio_in = -1, audio_out = -1}, }
+end
+
+local sr = 0
+
+function dsp_init (rate)
+ sr = rate
+end
+
+local ao = 0
+local draw = 0
+
+function dsp_run (ins, outs, n_samples)
+
+ local a = {} -- init array
+ local ctrl = CtrlPorts:array ()
+ local noise = ctrl[1] or 0
+ local amplitude = ARDOUR.DSP.dB_to_coefficient (ctrl[2]) or ARDOUR.DSP.dB_to_coefficient (-18)
+
+ local b0 = 0.0
+ local b1 = 0.0
+ local b2 = 0.0
+ local b3 = 0.0
+ local b4 = 0.0
+ local b5 = 0.0
+ local b6 = 0.0
+
+ --Pink noise generation courtesy of Paul Kellet's refined method
+ --http://www.musicdsp.org/files/pink.txt
+ --If 'white' consists of uniform random numbers,
+ --the pink noise will have an almost gaussian distribution.
+ for s = 1, n_samples do
+ if noise == 0 then
+ a[s] = amplitude * 2 * (math.random() - 0.5)
+ end
+ if noise == 1 then
+ white = (amplitude * 0.25) * 2 * (math.random() - 0.5)
+ b0 = 0.99886 * b0 + white * 0.0555179;
+ b1 = 0.99332 * b1 + white * 0.0750759;
+ b2 = 0.96900 * b2 + white * 0.1538520;
+ b3 = 0.86650 * b3 + white * 0.3104856;
+ b4 = 0.55000 * b4 + white * 0.5329522;
+ b5 = -0.7616 * b5 - white * 0.0168980;
+ b6 = white * 0.115926;
+ a[s] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
+ end
+ end
+
+ if (draw > (sr/15)) then
+ self:queue_draw()
+ draw = 0
+ end
+
+ -- passes array a {} into buffer
+ for c = 1,#outs do
+ outs[c]:set_table(a, n_samples)
+ end
+ draw = draw + n_samples
+end
+
+function render_inline (ctx, w, max_h) --inline display
+ local ctrl = CtrlPorts:array()
+ h = 30
+ p = 0
+ inc = 0
+ ycy = 0.5
+ pink = false
+ local amplitude = ARDOUR.DSP.dB_to_coefficient(ctrl[2])
+ if ctrl[1] == 1 then pink = true end
+ if pink then inc = 0.7/w end
+
+ --draw rectangle
+ ctx:rectangle(0, 0, w, h)
+ ctx:set_source_rgba(0, 0, 0, 1.0)
+ ctx:fill()
+ ctx:set_line_width(1.5)
+ ctx:set_source_rgba(0.8, 0.8, 0.8, 1.0)
+
+ l_x = 0
+ l_y = 0
+ for x = 0,w do
+ if pink then ycy = 0.3 else ycy = 0.5 end --slant slightly like an actual pink noise spectrum
+ y = math.log(20^amplitude) * (math.random() - 0.5) - p
+ yc = ycy * h + ((-0.5 * h) * y)
+ ctx:move_to (x, yc + 3)
+ ctx:line_to (l_x, l_y + 3)
+ l_x = x
+ l_y = yc
+ ctx:stroke()
+ p = p + inc
+ end
+ return {w, h + 6}
+end
diff --git a/share/scripts/normalize_all_tracks.lua b/share/scripts/normalize_all_tracks.lua
new file mode 100644
index 0000000000..2886c9b5c5
--- /dev/null
+++ b/share/scripts/normalize_all_tracks.lua
@@ -0,0 +1,58 @@
+ardour { ["type"] = "EditorAction",
+ name = "Normalize All Tracks",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Normalize all regions using a common gain-factor per track.]]
+}
+
+function factory () return function ()
+ -- target values -- TODO: use a LuaDialog.Dialog and ask..
+ local target_peak = -1 --dBFS
+ local target_rms = -18 --dBFS/RMS
+
+ -- prepare undo operation
+ Session:begin_reversible_command ("Normalize Tracks")
+ local add_undo = false -- keep track if something has changed
+
+ -- loop over all tracks in the session
+ for track in Session:get_tracks():iter() do
+ local norm = 0 -- per track gain
+ -- loop over all regions on track
+ for r in track:to_track():playlist():region_list():iter() do
+ -- test if it's an audio region
+ local ar = r:to_audioregion ()
+ if ar:isnil () then goto next end
+
+ local peak = ar:maximum_amplitude (nil)
+ local rms = ar:rms (nil)
+ -- check if region is silent
+ if (peak > 0) then
+ local f_rms = rms / 10 ^ (.05 * target_rms)
+ local f_peak = peak / 10 ^ (.05 * target_peak)
+ local tg = (f_peak > f_rms) and f_peak or f_rms -- max (f_peak, f_rms)
+ norm = (tg > norm) and tg or norm -- max (tg, norm)
+ end
+ ::next::
+ end
+
+ -- apply same gain to all regions on track
+ if norm > 0 then
+ for r in track:to_track():playlist():region_list():iter() do
+ local ar = r:to_audioregion ()
+ if ar:isnil () then goto skip end
+ ar:to_stateful ():clear_changes ()
+ ar:set_scale_amplitude (1 / norm)
+ add_undo = true
+ ::skip::
+ end
+ end
+ end
+
+ -- all done. now commit the combined undo operation
+ if add_undo then
+ -- the 'nil' command here means to use all collected diffs
+ Session:commit_reversible_command (nil)
+ else
+ Session:abort_reversible_command ()
+ end
+end end
diff --git a/share/scripts/notch_bank.lua b/share/scripts/notch_bank.lua
new file mode 100644
index 0000000000..76901920d9
--- /dev/null
+++ b/share/scripts/notch_bank.lua
@@ -0,0 +1,125 @@
+ardour {
+ ["type"] = "dsp",
+ name = "a-Notch Bank",
+ category = "Filter",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[Notch Filter Bank; useful to remove noise with a harmonic spectum (e.g, mains hum, GSM signals, charge-pump noise, etc).
+Note: this plugin is not suitable to be automated, it is intended for static noise only.]]
+}
+
+------------------------------------------------------------------
+-- this is a quick/dirty example filter: no de-click, no de-zipper
+-------------------------------------------------------------------
+
+-- configuration
+local max_stages = 100
+
+-- plugin i/o ports
+function dsp_ioconfig ()
+ return
+ {
+ -- allow any number of I/O as long as port-count matches
+ { audio_in = -1, audio_out = -1},
+ }
+end
+
+-- plugin control ports
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "Base Freq", min = 10, max = 2000, default = 100, unit="Hz", logarithmic = true },
+ { ["type"] = "input", name = "Quality", min = 1.0, max = 100.0, default = 8.0, logarithmic = true },
+ { ["type"] = "input", name = "Stages", min = 1.0, max = max_stages, default = 8.0, integer = true },
+ }
+end
+
+-- plugin instance state
+local filters = {} -- the biquad filter instances
+local chn = 0 -- configured channel count
+local sample_rate = 0 -- configured sample-rate
+local limit = 0 -- max number of stages (given freq & sample-rate)
+
+-- cached control ports (keep track of changed)
+local freq = 0
+local qual = 0
+
+-- dsp_init is called once when instantiating the plugin
+function dsp_init (rate)
+ -- remember the sample-rate
+ sample_rate = rate
+end
+
+-- dsp_configure is called every time when the channel-count
+-- changes, and at least once at the beginning.
+function dsp_configure (ins, outs)
+ assert (ins:n_audio () == outs:n_audio ())
+
+ -- explicit cleanup
+ filters = {}
+ collectgarbage ()
+
+ -- remember audio-channels
+ chn = ins:n_audio ()
+
+ -- set up filter instances for all channels
+ for c = 1, chn do
+ filters[c] = {}
+ for i = 1, max_stages do
+ filters[c][i] = ARDOUR.DSP.Biquad (sample_rate)
+ end
+ end
+end
+
+-- the actual process function, called every cycle
+-- ins, outs are audio-data arrays
+-- http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+-- n_samples are the number of samples to process
+function dsp_run (ins, outs, n_samples)
+ -- make sure input and output count matches...
+ assert (#ins == #outs)
+ -- ...and matches the configured number of channels
+ assert (#ins == chn)
+
+ local ctrl = CtrlPorts:array () -- get control parameters as array
+ -- ctrl[] .. correspond to the parameters given in in dsp_params()
+
+ -- test if the plugin-parameters have changed
+ if freq ~= ctrl[1] or qual ~= ctrl[2] then
+ -- remember current settings
+ freq = ctrl[1]
+ qual = ctrl[2]
+ -- calc max number of states to configure/process
+ limit = math.floor (sample_rate / (2 * freq)) -- at most up to SR / 2
+ if limit > max_stages then limit = max_stages end
+
+ -- re-compute the filter coefficients for all filters
+ for c = 1, chn do -- for each channel
+ for i = 1, limit do -- and for each filter stage
+ -- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP:Biquad
+ -- and for a list of available types, see
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR.DSP.Biquad.Type
+ -- the parameters are type, frequency, quality(bandwidth), gain
+ filters[c][i]:compute (ARDOUR.DSP.BiquadType.Notch, freq * i, qual * i, 0)
+ end
+ end
+ end
+
+ -- limit the number of process stages
+ local stages = math.floor (ctrl['3']) -- current user-set parameter
+ if stages < 1 then stages = 1 end -- at least one stage...
+ if stages > limit then stages = limit end
+
+ -- process all channels
+ for c = 1, chn do
+ -- when not processing in-place, copy the data from input to output first
+ if ins[c] ~= outs[c] then
+ ARDOUR.DSP.copy_vector (outs[c], ins[c], n_samples)
+ end
+
+ -- run all stages, in-place on the output buffer
+ for i = 1, stages do
+ filters[c][i]:run (outs[c], n_samples)
+ end
+ end
+end
diff --git a/share/scripts/periodic_backup.lua b/share/scripts/periodic_backup.lua
new file mode 100644
index 0000000000..ff274a7a08
--- /dev/null
+++ b/share/scripts/periodic_backup.lua
@@ -0,0 +1,47 @@
+ardour {
+ ["type"] = "EditorHook",
+ name = "Periodically Save Snapshot",
+ author = "Ardour Lua Task Force",
+ description = "Save a session-snapshot peridocally (every 15mins) named after the current date-time",
+}
+
+-- subscribe to signals
+-- http://manual.ardour.org/lua-scripting/class_reference/#LuaSignal.LuaSignal
+function signals ()
+ return LuaSignal.Set():add ({[LuaSignal.LuaTimerS] = true})
+end
+
+-- create callback function
+function factory ()
+ local _last_snapshot_time = 0 -- persistent variable
+ local _snapshot_interval = 60 * 15 -- 15 minutes
+
+ -- callback function which invoked when signal is emitted, every 100ms
+ return function (signal, ref, ...)
+
+ local now = os.time (); -- unix-time, seconds since 1970
+
+ -- skip initial save when script is loaded
+ if (_last_snapshot_time == 0) then
+ _last_snapshot_time = now;
+ end
+
+ -- every 15 mins
+ if (now > _last_snapshot_time + _snapshot_interval) then
+
+ -- don't save while recording, may interfere with recording
+ if Session:actively_recording() then
+ -- queue 30 sec after rec-stop
+ _last_snapshot_time = now - _snapshot_interval + 30
+ return
+ end
+
+ _last_snapshot_time = now
+ -- format date-time (avoid colon)
+ local snapshot_name = os.date ("%Y-%m-%d %H.%M.%S", now)
+ -- save session -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Session
+ Session:save_state ("backup " .. snapshot_name, false, false, false)
+ end
+
+ end
+end
diff --git a/share/scripts/post_export_save_hook.lua b/share/scripts/post_export_save_hook.lua
new file mode 100644
index 0000000000..3b31ee320b
--- /dev/null
+++ b/share/scripts/post_export_save_hook.lua
@@ -0,0 +1,26 @@
+ardour {
+ ["type"] = "EditorHook",
+ name = "Save Snapshot after Export",
+ author = "Ardour Lua Task Force",
+ description = "Save a session-snapshot on export, named after the export-timespan",
+}
+
+-- subscribe to signals
+-- http://manual.ardour.org/lua-scripting/class_reference/#LuaSignal.LuaSignal
+function signals ()
+ s = LuaSignal.Set()
+ s:add ({[LuaSignal.Exported] = true})
+ return s
+end
+
+-- create callback functions
+function factory ()
+ -- callback function which invoked when signal is emitted
+ return function (signal, ref, ...)
+ -- 'Exported' passes 2 strings: current time-span name, path to exported file
+ -- (see C++ libs/ardour/export_handler.cc Session::Exported )
+ local timespan_name, file_path = ...
+ -- save session -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Session
+ Session:save_state ("export-" .. timespan_name, false, false, false)
+ end
+end
diff --git a/share/scripts/preare_record_example.lua b/share/scripts/preare_record_example.lua
new file mode 100644
index 0000000000..48e063d326
--- /dev/null
+++ b/share/scripts/preare_record_example.lua
@@ -0,0 +1,82 @@
+--[[
+
+# Example script to prepare the Ardour session for recording
+
+Usually there's a certain state needed to actually start the recording. This example
+script treats the situation of a podcast recording. When starting the recording the
+following settings have to be ensured.
+
+* Session has to be recenabled
+* Tracks have to be recenabled
+* Gain automation have to set on write in order to record events from mute buttons
+* The playhead has to be at 00:00:00.000
+* The last (failed) capture has to be cleared
+* Location markers have to be cleared
+
+So this script automizes away the task and lets the podcast moderator by just one
+action (for example triggerd by a Wiimote) prepare the session for recording.
+
+It can be used for example with the python script of the Linux podcasting hacks:
+https://github.com/linux-podcasting-hacks/wiimote-recording-control
+
+Not that this script is more meant as an demo script to demonstrate the
+possibilities of the Lua interface.
+
+--]]
+
+ardour {
+ ["type"] = "EditorAction",
+ name = "Prepare recording for podcast",
+ author = "Johannes Mueller",
+ description = [[
+Prepares the Ardour session for podcast recording.
+
+* Sets the gain automation to "Write" so that muting buttons work.
+* Recenables all tracks.
+* Clears all markers.
+* Erases the last capture (assuming that it was a failed one)
+* Rewinds the session to starting point.
+* Recenables the session.
+]]
+}
+
+function factory (unused) return function()
+ if Session:actively_recording() then
+ return end
+
+ for t in Session:get_tracks():iter() do
+ t:gain_control():set_automation_state(ARDOUR.AutoState.Write)
+ t:rec_enable_control():set_value(1, PBD.GroupControlDisposition.UseGroup)
+ end
+
+ for l in Session:locations():list():iter() do
+ if l:is_mark() then
+ Session:locations():remove(l)
+ end
+ end
+
+ Session:goto_start()
+ Editor:remove_last_capture()
+ Session:maybe_enable_record()
+
+end end
+
+function icon (params) return function (ctx, width, height)
+ local x = width * .5
+ local y = height * .5
+ local r = math.min (x, y) * .55
+
+ ctx:arc (x, y, r, 0, 2 * math.pi)
+ ctx:set_source_rgba (.9, .3, .3, 1.)
+ ctx:fill_preserve ()
+ ctx:set_source_rgba (0, 0, 0, .8)
+ ctx:set_line_width (1)
+ ctx:stroke ()
+
+ local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil(r * 1.5) .. "px")
+ txt:set_text ("P")
+ local tw, th = txt:get_pixel_size ()
+ ctx:set_source_rgba (0, 0, 0, 1.0)
+ ctx:move_to (.5 * (width - tw), .5 * (height - th))
+ txt:show_in_cairo_context (ctx)
+end end
diff --git a/share/scripts/remove_unknown_procs.lua b/share/scripts/remove_unknown_procs.lua
new file mode 100644
index 0000000000..044e85b3b8
--- /dev/null
+++ b/share/scripts/remove_unknown_procs.lua
@@ -0,0 +1,44 @@
+ardour { ["type"] = "EditorAction", name = "Remove Unknown Plugins",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Remove all unknown plugins/processors from all tracks and busses]]
+}
+
+function factory (params) return function ()
+ -- iterate over all tracks and busses (aka routes)
+ for route in Session:get_routes ():iter () do
+ -- route is-a http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Route
+ local i = 0;
+ -- we need to iterate one-by one, removing a processor invalidates the list
+ repeat
+ proc = route:nth_processor (i)
+ -- proc is a http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Processor
+ -- try cast it to http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:UnknownProcessor
+ if not proc:isnil () and not proc:to_unknownprocessor ():isnil () then
+ route:remove_processor (proc, nil, true)
+ else
+ i = i + 1
+ end
+ until proc:isnil ()
+ end
+end end
+
+
+function icon (params) return function (ctx, width, height, fg)
+ local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil (math.min (width, height) * .5) .. "px")
+ txt:set_text ("Fx")
+ local tw, th = txt:get_pixel_size ()
+ ctx:move_to (.5 * (width - tw), .5 * (height - th))
+ txt:layout_cairo_path (ctx)
+
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ ctx:set_line_width (3)
+ ctx:stroke_preserve ()
+
+ ctx:set_source_rgba (.8, .2, .2, 1)
+ ctx:set_line_width (2)
+ ctx:stroke_preserve ()
+
+ ctx:set_source_rgba (0, 0, 0, 1)
+ ctx:fill ()
+end end
diff --git a/share/scripts/reset_mixer.lua b/share/scripts/reset_mixer.lua
new file mode 100644
index 0000000000..faf7aad477
--- /dev/null
+++ b/share/scripts/reset_mixer.lua
@@ -0,0 +1,249 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Reset Mixer",
+ license = "MIT",
+ author = "Ben Loftis, Nikolaus Gullotta, Maxime Lecoq",
+ description = [[Resets key Mixer settings after user-prompt (warning: this cannot be undone)]]
+}
+
+function factory() return function()
+ local sp_radio_buttons = {Bypass="bypass", Remove="remove", Nothing=false}
+ local dlg = {
+ {type="label", align="left", colspan="3", title="Please select below the items you want to reset:" },
+ {type="label", align="left", colspan="3", title="(Warning: this cannot be undone!)\n" },
+
+ {type="heading", align ="center", colspan="3", title = "Common Controls:" },
+ {type="checkbox", key="fader", default=true, title="Fader" },
+ {type="checkbox", key="mute", default=true, title="Mute" },
+ {type="checkbox", key="solo", default=true, title="Solo" },
+ {type="checkbox", key="trim", default=true, title="Trim" },
+ {type="checkbox", key="pan", default=true, title="Pan (All)" },
+ {type="checkbox", key="phase", default=true, title="Phase" },
+ {type="checkbox", key="sends", default=true, title="Sends" },
+ {type="checkbox", key="eq", default=true, title="EQ" },
+ {type="checkbox", key="comp", default=true, title="Compressor" },
+
+ {type="heading", align="center", colspan="3", title="Processors:" },
+ {type="radio", key="plugins", title="Plug-ins", values=sp_radio_buttons, default="Bypass" },
+ {type="radio", key="io", title="Sends/Inserts", values=sp_radio_buttons, default="Bypass" },
+
+ {type="hseparator", title=""},
+
+ {type="heading", align="center", colspan="3", title="Misc." },
+ {type="checkbox", key="auto", colspan="3", title = "Automation (switch to manual mode)" },
+ {type="checkbox", key="rec", colspan="3", title = "Disable Record" },
+ {type="checkbox", key="groups", colspan="3", title = "Groups" },
+ {type="checkbox", key="vcas", colspan="3", title = "VCAs (unassign all)" },
+ }
+
+ function reset(ctrl, disp, auto)
+ local disp = disp or PBD.GroupControlDisposition.NoGroup
+
+ if not(ctrl:isnil()) then
+ local pd = ctrl:desc()
+ ctrl:set_value(pd.normal, disp)
+
+ if auto then
+ ctrl:set_automation_state(auto)
+ end
+ end
+ end
+
+ function reset_eq_controls(route, disp, auto)
+ if route:isnil() then
+ return
+ end
+
+ local disp = disp or PBD.GroupControlDisposition.NoGroup
+
+ reset(route:eq_enable_controllable(), disp, auto)
+
+ local i = 0
+ repeat
+ for _,ctrl in pairs({
+ route:eq_freq_controllable(i),
+ route:eq_gain_controllable(i),
+ route:eq_q_controllable(i),
+ }) do
+ reset(ctrl, disp, auto)
+ end
+ i = i + 1
+ until route:eq_freq_controllable(i):isnil()
+ end
+
+ function reset_comp_controls(route, disp, auto)
+ if route:isnil() then
+ return
+ end
+
+ local disp = disp or PBD.GroupControlDisposition.NoGroup
+
+ for _,ctrl in pairs({
+ route:comp_enable_controllable(),
+ route:comp_makeup_controllable(),
+ route:comp_mode_controllable(),
+ route:comp_speed_controllable(),
+ route:comp_threshold_controllable(),
+ }) do
+ reset(ctrl, disp, auto)
+ end
+ end
+
+ function reset_send_controls(route, disp, auto)
+ if route:isnil() then
+ return
+ end
+
+ local disp = disp or PBD.GroupControlDisposition.NoGroup
+
+ local i = 0
+ repeat
+ for _,ctrl in pairs({
+ route:send_level_controllable(i),
+ route:send_enable_controllable(i),
+ route:send_pan_azimuth_controllable(i),
+ route:send_pan_azimuth_enable_controllable(i),
+ }) do
+ reset(ctrl, disp, auto)
+ end
+ i = i + 1
+ until route:send_enable_controllable(i):isnil()
+ end
+
+ function reset_plugin_automation(plugin, state)
+ if plugin:to_insert():isnil() then
+ return
+ end
+
+ local plugin = plugin:to_insert()
+ local pc = plugin:plugin(0):parameter_count()
+ for c = 0, pc do
+ local ac = plugin:to_automatable():automation_control(Evoral.Parameter(ARDOUR.AutomationType.PluginAutomation, 0, c), false)
+ if not(ac:isnil()) then
+ ac:set_automation_state(state)
+ end
+ end
+ end
+
+ function reset_plugins(route, prefs, auto)
+ if route:isnil() then
+ return
+ end
+
+ local i = 0
+ local queue = {}
+ repeat
+ -- Plugins are queued to not invalidate this loop
+ local proc = route:nth_processor(i)
+ if not(proc:isnil()) then
+ if prefs["auto"] then
+ reset_plugin_automation(proc, auto)
+ end
+ if prefs["plugins"] then
+ local insert = proc:to_insert()
+ if not(insert:isnil()) then
+ if insert:is_channelstrip() or not(insert:display_to_user()) then
+ ARDOUR.LuaAPI.reset_processor_to_default(insert)
+ else
+ queue[#queue + 1] = proc
+ end
+ end
+ end
+ if prefs["io"] then
+ local io_proc = proc:to_ioprocessor()
+ if not(io_proc:isnil()) then
+ queue[#queue + 1] = proc
+ end
+ end
+ end
+ i = i + 1
+ until proc:isnil()
+
+ -- Deal with queue now
+ for _, proc in pairs(queue) do
+ if not(proc:to_insert():isnil()) then
+ if prefs["plugins"] == "remove" then
+ route:remove_processor(proc, nil, true)
+ elseif prefs["plugins"] == "bypass" then
+ proc:deactivate()
+ end
+ end
+ if not(proc:to_ioprocessor():isnil()) then
+ if prefs["io"] == "remove" then
+ route:remove_processor(proc, nil, true)
+ elseif prefs["io"] == "bypass" then
+ proc:deactivate()
+ end
+ end
+ end
+ end
+
+ local pref = LuaDialog.Dialog("Reset Mixer", dlg):run()
+
+ if not(pref) then goto pass_script end
+ assert(pref, "Dialog box was cancelled or is nil")
+
+ for route in Session:get_routes():iter() do
+ local disp = PBD.GroupControlDisposition.NoGroup
+ local auto = nil
+
+ if pref["auto"] then
+ auto = ARDOUR.AutoState.Off
+ end
+
+ if pref["eq"] then reset_eq_controls(route, disp, auto) end
+ if pref["comp"] then reset_comp_controls(route, disp, auto) end
+ if pref["sends"] then reset_send_controls(route, disp, auto) end
+ reset_plugins(route, pref, auto)
+
+ if pref["rec"] then
+ reset(route:rec_enable_control(), disp, auto)
+ reset(route:rec_safe_control(), disp, auto)
+ end
+
+ if pref["fader"] then
+ reset(route:gain_control(), disp, auto)
+ end
+
+ if pref["phase"] then
+ reset(route:phase_control(), disp, auto)
+ end
+
+ if pref["trim"] then
+ reset(route:trim_control(), disp, auto)
+ end
+
+ if pref["mute"] then
+ reset(route:mute_control(), disp, auto)
+ end
+
+ if pref["solo"] then
+ reset(route:solo_control(), disp, auto)
+ end
+
+ if pref["pan"] then
+ reset(route:pan_azimuth_control(), disp, auto)
+ reset(route:pan_elevation_control(), disp, auto)
+ reset(route:pan_frontback_control(), disp, auto)
+ reset(route:pan_lfe_control(), disp, auto)
+ reset(route:pan_width_control(), disp, auto)
+ end
+
+ if pref["vcas"] then
+ local slave = route:to_slavable()
+ if not(slave:isnil()) then
+ for vca in Session:vca_manager():vcas():iter() do
+ slave:unassign(vca)
+ end
+ end
+ end
+ end
+
+ if pref["groups"] then
+ for group in Session:route_groups():iter() do
+ Session:remove_route_group(group)
+ end
+ end
+ ::pass_script::
+ collectgarbage()
+end end \ No newline at end of file
diff --git a/share/scripts/s_chanmap.lua b/share/scripts/s_chanmap.lua
new file mode 100644
index 0000000000..7bc070c8cc
--- /dev/null
+++ b/share/scripts/s_chanmap.lua
@@ -0,0 +1,34 @@
+ardour { ["type"] = "Snippet", name = "plugin channel-map dev" }
+
+function factory () return function ()
+ -- first track needs to be stereo and have a stereo plugin
+ -- (x42-eq with spectrum display, per channel processing,
+ -- and pre/post visualization is very handy here)
+
+ function checksetup (r)
+ -- fail if Route ID 1 is not present or not stereo
+ assert (r and not r:isnil())
+ assert (r:n_inputs():n_audio() == 2)
+ -- check first Plugin and make sure it is a "Plugin Insert"
+ if not r:nth_plugin(0):isnil() and not r:nth_plugin(0):to_insert():isnil() then return end
+ -- insert x42-eq at the top.
+ local proc = ARDOUR.LuaAPI.new_plugin(Session, "http://gareus.org/oss/lv2/fil4#stereo", ARDOUR.PluginType.LV2, "");
+ r:add_processor_by_index(proc, 0, nil, true)
+ end
+
+ r = Session:get_remote_nth_route(1)
+ checksetup (r)
+ pi = r:nth_plugin(0):to_insert()
+
+ pi:set_no_inplace (true)
+
+ cm = ARDOUR.ChanMapping()
+ --cm:set(ARDOUR.DataType("Audio"), 0, 0)
+ cm:set(ARDOUR.DataType("Audio"), 1, 0)
+ pi:set_input_map (0, cm)
+
+ cm = ARDOUR.ChanMapping()
+ --cm:set(ARDOUR.DataType("Audio"), 0, 0)
+ cm:set(ARDOUR.DataType("Audio"), 1, 0)
+ pi:set_output_map (0, cm)
+end end
diff --git a/share/scripts/s_fader_automation.lua b/share/scripts/s_fader_automation.lua
new file mode 100644
index 0000000000..3a9cb30eef
--- /dev/null
+++ b/share/scripts/s_fader_automation.lua
@@ -0,0 +1,58 @@
+ardour { ["type"] = "Snippet", name = "Fader Automation" }
+
+function factory () return function ()
+ local playhead = Session:transport_sample ()
+ local samplerate = Session:nominal_sample_rate ()
+
+ -- get selected tracks
+ rl = Editor:get_selection ().tracks:routelist ()
+
+ -- prepare undo operation
+ Session:begin_reversible_command ("Fancy Fade Out")
+ local add_undo = false -- keep track if something has changed
+
+ -- iterate over selected tracks
+ for r in rl:iter () do
+ local ac = r:amp ():gain_control () -- ARDOUR:AutomationControl
+ local al = ac:alist () -- ARDOUR:AutomationList (state, high-level)
+
+ -- set automation state to "Touch"
+ ac:set_automation_state (ARDOUR.AutoState.Touch)
+
+ -- query the value at the playhead position
+ local g = al:eval (playhead)
+
+ -- get state for undo
+ local before = al:get_state ()
+
+ -- delete all events after the playhead...
+ al:truncate_end (playhead)
+
+ -- ...and generate some new ones.
+ for i=0,50 do
+ -- use a sqrt fade-out (the shape is recognizable, and otherwise
+ -- not be possible to achieve with existing ardour fade shapes)
+ al:add (playhead + i * samplerate / 50,
+ g * (1 - math.sqrt (i / 50)),
+ false, true)
+ end
+
+ -- remove dense events
+ al:thin (20)
+
+ -- save undo
+ local after = al:get_state ()
+ Session:add_command (al:memento_command (before, after))
+ add_undo = true
+
+ ::out::
+ end
+
+ -- all done, commit the combined Undo Operation
+ if add_undo then
+ -- the 'nil' Commend here mean to use the collected diffs added above
+ Session:commit_reversible_command (nil)
+ else
+ Session:abort_reversible_command ()
+ end
+end end
diff --git a/share/scripts/s_foreach_track.lua b/share/scripts/s_foreach_track.lua
new file mode 100644
index 0000000000..c1c6ed8da1
--- /dev/null
+++ b/share/scripts/s_foreach_track.lua
@@ -0,0 +1,10 @@
+ardour { ["type"] = "Snippet", name = "Foreach Track" }
+
+function factory () return function ()
+ for r in Session:get_tracks():iter() do
+ print (r:name())
+ -- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Track
+ -- for available methods e.g.
+ r:set_active (true, nil)
+ end
+end end
diff --git a/share/scripts/s_group_color.lua b/share/scripts/s_group_color.lua
new file mode 100644
index 0000000000..d8c0785ed7
--- /dev/null
+++ b/share/scripts/s_group_color.lua
@@ -0,0 +1,11 @@
+ardour { ["type"] = "Snippet", name = "Randomize Group Colors" }
+
+function factory () return function ()
+ for grb in Session:route_groups ():iter () do
+ local r = math.random (0, 255)
+ local g = math.random (0, 255)
+ local b = math.random (0, 255)
+ local rgba = (r << 24) + (g << 16) + (b << 8) + 0xff
+ grp:set_rgba(rgba)
+ end
+end end
diff --git a/share/scripts/s_import_files.lua b/share/scripts/s_import_files.lua
new file mode 100644
index 0000000000..72cca29f8e
--- /dev/null
+++ b/share/scripts/s_import_files.lua
@@ -0,0 +1,14 @@
+ardour { ["type"] = "Snippet", name = "Import File(s) Example" }
+
+function factory (params) return function ()
+ local files = C.StringVector();
+
+ files:push_back("/tmp/test.wav")
+
+ local pos = -1
+ Editor:do_import (files,
+ Editing.ImportDistinctFiles, Editing.ImportAsTrack, ARDOUR.SrcQuality.SrcBest,
+ ARDOUR.MidiTrackNameSource.SMFTrackName, ARDOUR.MidiTempoMapDisposition.SMFTempoIgnore,
+ pos, ARDOUR.PluginInfo())
+
+end end
diff --git a/share/scripts/s_plugin_automation.lua b/share/scripts/s_plugin_automation.lua
new file mode 100644
index 0000000000..2b04bae638
--- /dev/null
+++ b/share/scripts/s_plugin_automation.lua
@@ -0,0 +1,43 @@
+ardour { ["type"] = "Snippet", name = "Plugin automation" }
+
+function factory () return function ()
+ -- query playhead position and session sample-rate
+ local playhead = Session:transport_sample ()
+ local samplerate = Session:nominal_sample_rate ()
+
+ -- get Track/Bus with RID 3
+ local r = Session:get_remote_nth_route(3)
+ -- make sure the track object exists
+ assert (not r:isnil ())
+
+ -- get AutomationList, ControlList and ParameterDescriptor
+ -- of the first plugin's first parameter
+ -- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:LuaAPI
+ local al, cl, pd = ARDOUR.LuaAPI.plugin_automation (r:nth_plugin (0), 0)
+
+ if not al:isnil () then
+ print ("Parameter Range", pd.lower, pd.upper)
+ print ("Current value", cl:eval (playhead))
+
+ -- prepare undo operation
+ Session:begin_reversible_command ("Automatix")
+ -- remember current AutomationList state
+ local before = al:get_state()
+
+ -- remove future automation
+ cl:truncate_end (playhead)
+
+ -- add new data points after the playhead 1 sec, min..max
+ -- without guard-points, but with initial (..., false, true)
+ for i=0,10 do
+ cl:add (playhead + i * samplerate / 10,
+ pd.lower + math.sqrt (i / 10) * (pd.upper - pd.lower),
+ false, true)
+ end
+
+ -- save undo
+ local after = al:get_state()
+ Session:add_command (al:memento_command(before, after))
+ Session:commit_reversible_command (nil)
+ end
+end end
diff --git a/share/scripts/s_plugin_reorder.lua b/share/scripts/s_plugin_reorder.lua
new file mode 100644
index 0000000000..11fb0e056a
--- /dev/null
+++ b/share/scripts/s_plugin_reorder.lua
@@ -0,0 +1,23 @@
+ardour { ["type"] = "Snippet", name = "Plugin Order Reverse" }
+
+function factory () return function ()
+ local sel = Editor:get_selection ()
+ -- for each selected track/bus
+ for r in sel.tracks:routelist ():iter () do
+ print ("Route:", r:name ())
+ local neworder = ARDOUR.ProcessorList(); -- create a PluginList
+ local i = 0;
+ repeat -- iterate over all plugins/processors
+ local proc = r:nth_processor (i)
+ if not proc:isnil () then
+ -- append plugin to list
+ neworder:push_back(proc)
+ end
+ i = i + 1
+ until proc:isnil ()
+ -- reverse list
+ neworder:reverse()
+ -- and set new order
+ r:reorder_processors (neworder, nil)
+ end
+end end
diff --git a/share/scripts/s_pluginutils.lua b/share/scripts/s_pluginutils.lua
new file mode 100644
index 0000000000..e99bef7b93
--- /dev/null
+++ b/share/scripts/s_pluginutils.lua
@@ -0,0 +1,57 @@
+ardour { ["type"] = "Snippet", name = "Plugin Utils" }
+
+function factory () return function ()
+
+ -------------------------------------------------------------------------------
+ -- List all Plugins
+ for p in ARDOUR.LuaAPI.list_plugins():iter() do
+ print (p.name, p.unique_id, p.type)
+ local psets = p:get_presets()
+ if not empty:empty() then
+ for pset in psets:iter() do
+ print (" - ", pset.label)
+ end
+ end
+ end
+
+ -------------------------------------------------------------------------------
+ -- add a Plugin (here LV2) to all mono tracks that contain the pattern "dru"
+ -- and load a plugin-preset (if it exists)
+ for r in Session:get_routes():iter() do
+ if (string.match (r:name(), "dru") and r:n_inputs():n_audio() == 1) then
+ local proc = ARDOUR.LuaAPI.new_plugin(Session, "http://gareus.org/oss/lv2/fil4#mono", ARDOUR.PluginType.LV2, "cutbass");
+ assert (not proc:isnil())
+ r:add_processor_by_index(proc, 0, nil, true)
+ end
+ end
+
+
+ -------------------------------------------------------------------------------
+ -- load a plugin preset
+ route = Session:get_remote_nth_route(2)
+ assert (route)
+ -- to 4th plugin (from top), ardour starts counting at zero
+ plugin = route:nth_plugin(3):to_insert():plugin(0)
+ assert (not plugin:isnil())
+ ps = plugin:preset_by_label("cutbass") -- get preset by name
+ assert (ps)
+ print (ps.uri)
+ plugin:load_preset (ps)
+
+
+ -------------------------------------------------------------------------------
+ -- add a LuaProcessor (here "Scope") to all tracks
+ for t in Session:get_tracks():iter() do
+ local pos = 0 -- insert at the top
+
+ -- the following two lines are equivalent
+ --local proc = ARDOUR.LuaAPI.new_luaproc(Session, "a-Inline Scope");
+ local proc = ARDOUR.LuaAPI.new_plugin (Session, "a-Inline Scope", ARDOUR.PluginType.Lua, "");
+ assert (not proc:isnil())
+
+ t:add_processor_by_index(proc, pos, nil, true)
+ -- optionally set some parameters
+ ARDOUR.LuaAPI.set_processor_param (proc, 0, 5) -- timescale 5sec
+ end
+
+end end
diff --git a/share/scripts/s_portengine.lua b/share/scripts/s_portengine.lua
new file mode 100644
index 0000000000..ebca9ff696
--- /dev/null
+++ b/share/scripts/s_portengine.lua
@@ -0,0 +1,35 @@
+ardour { ["type"] = "Snippet", name = "portengine" }
+function factory () return function ()
+
+ local a = Session:engine()
+ print ("----- Port objects from Ardour's engine ----");
+ _, t = a:get_ports (ARDOUR.DataType("audio"), ARDOUR.PortList())
+ -- table 't' holds argument references. t[2] is the PortList
+ for p in t[2]:iter() do
+ local lp = p:get_connected_latency_range (ARDOUR.LatencyRange(), true)
+ local lc = p:get_connected_latency_range (ARDOUR.LatencyRange(), false)
+ print (p:name(), " -- Play lat.", lp[1].min, lp[1].max, "Capt lat.", lc[1].min, lc[1].max)
+ end
+
+ print ("----- Port names queries from the backend ----");
+ _, t = a:get_backend_ports ("", ARDOUR.DataType("audio"), 0, C.StringVector())
+ -- table 't' holds argument references. t[4] is the StringVector
+ for n in t[4]:iter() do
+ print (n)
+ end
+
+ print ("----- Connections from the backend ----");
+ _, t = a:get_backend_ports ("", ARDOUR.DataType("audio"), ARDOUR.PortFlags.IsOutput, C.StringVector())
+ for n in t[4]:iter() do
+ local printed_name = false;
+ local _, ct = a:get_connections (n, C.StringVector())
+ for c in ct[2]:iter() do
+ if (not printed_name) then
+ printed_name = true;
+ print (n)
+ end
+ print (" ->", c)
+ end
+ end
+
+end end
diff --git a/share/scripts/s_region_gain.lua b/share/scripts/s_region_gain.lua
new file mode 100644
index 0000000000..648e76629c
--- /dev/null
+++ b/share/scripts/s_region_gain.lua
@@ -0,0 +1,82 @@
+ardour { ["type"] = "Snippet", name = "Set Region Gain" }
+
+function factory () return function ()
+ -- get Editor GUI Selection
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
+ local sel = Editor:get_selection ()
+
+ -- allocate a buffer (float* in C)
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP:DspShm
+ local cmem = ARDOUR.DSP.DspShm (8192)
+
+ -- prepare undo operation
+ Session:begin_reversible_command ("Lua Region Gain")
+ local add_undo = false -- keep track if something has changed
+
+ -- iterate over selected regions
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:RegionSelection
+ for r in sel.regions:regionlist ():iter () do
+ -- test if it's an audio region
+ if r:to_audioregion ():isnil () then
+ goto next
+ end
+
+ -- to read the Region data, we use the Readable interface of the Region
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Readable
+ local rd = r:to_readable ()
+
+ local n_samples = rd:readable_length ()
+ local n_channels = rd:n_channels ()
+
+ local peak = 0 -- the audio peak to be calculated
+
+ -- iterate over all channels in Audio Region
+ for c = 0, n_channels -1 do
+ local pos = 0
+ repeat
+ -- read at most 8K samples of channel 'c' starting at 'pos'
+ local s = rd:read (cmem:to_float (0), pos, 8192, c)
+ pos = pos + s
+ -- access the raw audio data
+ -- http://manual.ardour.org/lua-scripting/class_reference/#C:FloatArray
+ local d = cmem:to_float (0):array()
+ -- iterate over the audio sample data
+ for i = 0, s do
+ if math.abs (d[i]) > peak then
+ peak = math.abs (d[i])
+ end
+ end
+ until s < 8192
+ assert (pos == n_samples)
+ end
+
+ if (peak > 0) then
+ print ("Region:", r:name (), "peak:", 20 * math.log (peak) / math.log(10), "dBFS")
+ else
+ print ("Region:", r:name (), " is silent")
+ end
+
+ -- normalize region
+ if (peak > 0) then
+ -- prepare for undo
+ r:to_stateful ():clear_changes ()
+ -- apply gain
+ r:to_audioregion (): set_scale_amplitude (1 / peak)
+ -- save changes (if any) to undo command
+ if not Session:add_stateful_diff_command (r:to_statefuldestructible ()):empty () then
+ add_undo = true
+ end
+ end
+
+ ::next::
+ end
+
+ -- all done. now commit the combined undo operation
+ if add_undo then
+ -- the 'nil' command here means to use all collected diffs
+ Session:commit_reversible_command (nil)
+ else
+ Session:abort_reversible_command ()
+ end
+
+end end
diff --git a/share/scripts/s_region_gain2.lua b/share/scripts/s_region_gain2.lua
new file mode 100644
index 0000000000..cec56ca412
--- /dev/null
+++ b/share/scripts/s_region_gain2.lua
@@ -0,0 +1,63 @@
+ardour { ["type"] = "Snippet", name = "Normalize Regions" }
+
+function factory () return function ()
+ -- get Editor GUI Selection
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
+ local sel = Editor:get_selection ()
+
+ -- prepare undo operation
+ Session:begin_reversible_command ("Lua Normalize")
+ local add_undo = false -- keep track if something has changed
+
+ -- iterate over selected regions
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:RegionSelection
+ for r in sel.regions:regionlist ():iter () do
+ -- test if it's an audio region
+ local ar = r:to_audioregion ();
+ if ar:isnil () then
+ goto next
+ end
+
+ local peak = ar:maximum_amplitude (nil);
+ local rms = ar:rms (nil);
+
+ if (peak > 0) then
+ print ("Region:", r:name (), "peak:", 20 * math.log (peak) / math.log(10), "dBFS")
+ print ("Region:", r:name (), "rms :", 20 * math.log (rms) / math.log(10), "dBFS")
+ else
+ print ("Region:", r:name (), " is silent")
+ end
+
+ -- normalize region
+ if (peak > 0) then
+ -- prepare for undo
+ r:to_stateful ():clear_changes ()
+ -- calculate gain.
+ local f_rms = rms / 10 ^ (.05 * -18) -- -18dBFS/RMS
+ local f_peak = peak / 10 ^ (.05 * -1) -- -1dbFS/peak
+ -- apply gain
+ if (f_rms > f_peak) then
+ print ("Region:", r:name (), "RMS normalized by:", -20 * math.log (f_rms) / math.log(10), "dB")
+ ar:set_scale_amplitude (1 / f_rms)
+ else
+ print ("Region:", r:name (), "peak normalized by:", -20 * math.log (f_peak) / math.log(10), "dB")
+ ar:set_scale_amplitude (1 / f_peak)
+ end
+ -- save changes (if any) to undo command
+ if not Session:add_stateful_diff_command (r:to_statefuldestructible ()):empty () then
+ add_undo = true
+ end
+ end
+
+ ::next::
+ end
+
+ -- all done. now commit the combined undo operation
+ if add_undo then
+ -- the 'nil' command here means to use all collected diffs
+ Session:commit_reversible_command (nil)
+ else
+ Session:abort_reversible_command ()
+ end
+
+end end
diff --git a/share/scripts/s_replaceplugin.lua b/share/scripts/s_replaceplugin.lua
new file mode 100644
index 0000000000..4cef07c7ba
--- /dev/null
+++ b/share/scripts/s_replaceplugin.lua
@@ -0,0 +1,11 @@
+ardour { ["type"] = "Snippet", name = "Replace Plugin" }
+
+function factory () return function ()
+
+ route = Session:get_remote_nth_route(1)
+ old = route:nth_plugin(0)
+ new = ARDOUR.LuaAPI.new_plugin(Session, "http://gareus.org/oss/lv2/fil4#stereo", ARDOUR.PluginType.LV2, "");
+ route:replace_processor (old, new, nil)
+ old = nil new = nil -- explicitly drop references (unless they're local vars)
+
+end end
diff --git a/share/scripts/s_selection.lua b/share/scripts/s_selection.lua
new file mode 100644
index 0000000000..5dd58a43c7
--- /dev/null
+++ b/share/scripts/s_selection.lua
@@ -0,0 +1,60 @@
+ardour { ["type"] = "Snippet", name = "Editor Selection" }
+
+function factory () return function ()
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
+ -- the Ardour Selection can include multiple items
+ -- (regions, tracks, ranges, markers, automation, midi-notes etc)
+ local sel = Editor:get_selection ()
+
+ --
+ -- At the point of writing the following data items are available
+ --
+
+ -- Range selection, total span of all ranges (0, 0 if no time range is selected)
+ if sel.time:start () < sel.time:end_sample () then
+ print ("Total Range:", sel.time:start (), sel.time:end_sample ())
+ end
+
+ -- Range selection, individual ranges.
+ for ar in sel.time:iter () do
+ -- each of the items is a
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:AudioRange
+ print ("Range:", ar.id, ar.start, ar._end)
+ end
+
+ -- Track/Bus Selection
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:TrackSelection
+ for r in sel.tracks:routelist ():iter () do
+ -- each of the items is a
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Route
+ print ("Route:", r:name ())
+ end
+
+ -- Region selection
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:RegionSelection
+ for r in sel.regions:regionlist ():iter () do
+ -- each of the items is a
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Region
+ print ("Region:", r:name ())
+ end
+
+ -- Markers
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:MarkerSelection
+ -- Note: Marker selection is not cleared and currently (Ardour-4.7) points
+ -- to the most recently selected marker.
+ for m in sel.markers:iter () do
+ -- each of the items is a
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOURUI::ArdourMarker
+ print ("Marker:", m:name (), m:position(), m:_type())
+ end
+
+ ----------------------------------------------------------
+ -- The total time extents of all selected regions and ranges
+ local ok, ext = Editor:get_selection_extents (0, 0)
+ if ok then
+ print ("Selection Extents:", ext[1], ext[2])
+ else
+ print ("No region or range is selected")
+ end
+
+end end
diff --git a/share/scripts/s_showhide_track.lua b/share/scripts/s_showhide_track.lua
new file mode 100644
index 0000000000..289367643d
--- /dev/null
+++ b/share/scripts/s_showhide_track.lua
@@ -0,0 +1,23 @@
+ardour { ["type"] = "Snippet", name = "Show/Hide TimeAxisView" }
+
+function factory () return function ()
+ -- get a route from the session by Presentation-Order
+ -- http://ardourman/lua-scripting/class_reference/#ARDOUR:Session
+ local route = Session:get_remote_nth_route(2)
+ assert (route) -- abort if it does not exist
+ print (route:name())
+
+ -- the GUI timeline representation of a Track/Bus is a "Route Time Axis View" Object
+ local rtav = Editor:rtav_from_route (route) -- lookup RTAV
+
+ -- the show/hide state applies to any "Time Axis View", cast RTAV to TAV.
+ Editor:hide_track_in_display (rtav:to_timeaxisview(), false --[[true: only if selected; false: any]])
+
+
+ -- look up the route named "Audio"
+ route = Session:route_by_name("Audio")
+ assert (route) -- abort if it does not exist
+
+ Editor:show_track_in_display (Editor:rtav_from_route (route):to_timeaxisview(), false --[[move into view]])
+
+end end
diff --git a/share/scripts/s_thin_automation.lua b/share/scripts/s_thin_automation.lua
new file mode 100644
index 0000000000..a86b6c71d0
--- /dev/null
+++ b/share/scripts/s_thin_automation.lua
@@ -0,0 +1,47 @@
+ardour { ["type"] = "Snippet", name = "Thin Fader Automation" }
+
+-- --TODO--
+-- For a fully fledged EditorAction this script should
+-- offer a dropdown to select automation of all paramaters
+-- (not just the fader)
+-- see scripts/midi_cc_to_automation.lua and
+-- scripts/mixer_settings_store.lua
+-- Thinning Area should also be a numeric-entry or slider
+
+function factory () return function ()
+ -- get selected tracks
+ rl = Editor:get_selection ().tracks:routelist ()
+
+ -- prepare undo operation
+ Session:begin_reversible_command ("Thin Automation")
+ local add_undo = false -- keep track if something has changed
+
+ -- iterate over selected tracks
+ for r in rl:iter () do
+
+ -- get the Fader (aka "amp") control
+ local ac = r:amp ():gain_control () -- ARDOUR:AutomationControl
+ local al = ac:alist () -- ARDOUR:AutomationList
+
+ -- get state for undo
+ local before = al:get_state ()
+
+ -- remove dense events
+ al:thin (50) -- threashold of area below curve
+
+ -- save undo
+ local after = al:get_state ()
+ Session:add_command (al:memento_command (before, after))
+ add_undo = true
+
+ ::out::
+ end
+
+ -- all done, commit the combined Undo Operation
+ if add_undo then
+ -- the 'nil' Commend here mean to use the collected diffs added above
+ Session:commit_reversible_command (nil)
+ else
+ Session:abort_reversible_command ()
+ end
+end end
diff --git a/share/scripts/s_timecode.lua b/share/scripts/s_timecode.lua
new file mode 100644
index 0000000000..9f6601f903
--- /dev/null
+++ b/share/scripts/s_timecode.lua
@@ -0,0 +1,21 @@
+ardour { ["type"] = "Snippet", name = "Timecode" }
+
+function factory () return function ()
+
+ local samplerate = 48000 -- samples per second
+
+ -- generic convert, explicitly provide Timecode (fps) and sample-rate
+ -- http://manual.ardour.org/lua-scripting/class_reference/#Timecode.TimecodeFormat
+ hh, mm, ss, ff = ARDOUR.LuaAPI.sample_to_timecode (Timecode.TimecodeFormat.TC25, samplerate, 1920)
+ print (ARDOUR.LuaAPI.sample_to_timecode (Timecode.TimecodeFormat.TC25, samplerate, 1920))
+
+ -- generic convert, explicitly provide Timecode (fps) and sample-rate
+ local s = ARDOUR.LuaAPI.timecode_to_sample (Timecode.TimecodeFormat.TC25, samplerate, 10, 11, 12, 13)
+ assert (25 * (10 * 3600 + 11 * 60 + 12 ) + 13 == s * 25 / samplerate)
+
+ -- use session-settings: sample-rate and timecode format is taken from the
+ -- current session. Note that the sample-rate includes pull-up/down
+ print (Session:sample_to_timecode_lua (12345))
+ print (Session:timecode_to_sample_lua (10, 11, 12, 13))
+
+end end
diff --git a/share/scripts/s_track_props.lua b/share/scripts/s_track_props.lua
new file mode 100644
index 0000000000..ad983f8364
--- /dev/null
+++ b/share/scripts/s_track_props.lua
@@ -0,0 +1,48 @@
+ardour { ["type"] = "Snippet", name = "Track Properties" }
+
+function factory () return function ()
+ --- iterate over all tracks
+ for t in Session:get_tracks():iter() do
+ -- t is-a http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Track
+
+ -- operate one those with "Drum" in the name
+ if (t:name ():find ("Drum")) then
+
+ -- print the name, and number of audio in/out
+ -- see also http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:ChanCount
+ print (t:name (), "| Audio In:", t:n_inputs ():n_audio (), "Audio Out:", t:n_outputs ():n_audio ())
+
+ -- get the track's http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:PresentationInfo
+ pi = t:presentation_info_ptr ()
+
+ -- set the track's color to orange - hex RGBA
+ pi:set_color (0xff8800ff)
+
+ -- phase invert the 1st channel
+ t:phase_control():set_phase_invert (1, true)
+
+ -- solo the track -- and only the track, not other tracks grouped with it.
+ --
+ -- Note that changing solo/mute needs to propagate implicit solo/mute.
+ -- These changes have to be done atomically, so that all
+ -- related solo/mute change simultaneously at the same time.
+ -- This can only be done from realtime-context, so we need to queue a session-rt
+ -- event using the session realtime-event dispatch mechanism:
+ Session:set_control (t:solo_control(), 1, PBD.GroupControlDisposition.NoGroup)
+
+ -- unmute the track, this also examplifies how one could use lists to modify
+ -- multiple controllables at the same time (they should be of the same
+ -- paramater type - e.g. mute_control() of multiple tracks, they'll all
+ -- change simultaneously in rt-context)
+ local ctrls = ARDOUR.ControlListPtr ()
+ ctrls:push_back (t:mute_control()) -- we could add more controls to change via push_back
+ Session:set_controls (ctrls, 0, PBD.GroupControlDisposition.NoGroup)
+
+ -- add a track comment
+ t:set_comment ("This is a Drum Track", nil)
+
+ -- and set the fader to -7dB == 10 ^ (0.05 * -7)
+ t:gain_control():set_value (10 ^ (0.05 * -7), PBD.GroupControlDisposition.NoGroup)
+ end
+ end
+end end
diff --git a/share/scripts/s_vamp_plugin_index.lua b/share/scripts/s_vamp_plugin_index.lua
new file mode 100644
index 0000000000..b559f57e89
--- /dev/null
+++ b/share/scripts/s_vamp_plugin_index.lua
@@ -0,0 +1,45 @@
+ardour { ["type"] = "Snippet", name = "Vamp Plugin List" }
+function factory () return function ()
+
+ local plugins = ARDOUR.LuaAPI.Vamp.list_plugins ();
+ for id in plugins:iter () do
+ local vamp = ARDOUR.LuaAPI.Vamp(id, Session:nominal_sample_rate())
+ local vp = vamp:plugin ()
+ print (" --- VAMP Plugin ---")
+ print ("Id:", vp:getIdentifier ())
+ print ("Name:", vp:getName ())
+ print ("Description:", vp:getDescription ())
+
+ local progs = vp:getPrograms();
+ if not progs:empty () then
+ print ("Preset(s):")
+ for p in progs:iter () do
+ print (" *", p)
+ end
+ end
+
+ local params = vp:getParameterDescriptors ()
+ if not params:empty () then
+ print ("Parameters(s):")
+ for p in params:iter () do
+ -- http://manual.ardour.org/lua-scripting/class_reference/#Vamp:PluginBase:ParameterDescriptor
+ print (" * Id:", p.identifier, "Name:", p.name, "Desc:", p.description)
+ local i = 0; for vn in p.valueNames:iter() do
+ print (" ^^ ", i, " -> ", vn)
+ i = i + 1
+ end
+ end
+ end
+
+ local feats = vp:getOutputDescriptors ()
+ if not feats:empty () then
+ print ("Output(s):")
+ for p in feats:iter () do
+ -- http://manual.ardour.org/lua-scripting/class_reference/#Vamp:Plugin:OutputDescriptor
+ print (" * Id:", p.identifier, "Name:", p.name, "Desc:", p.description)
+ end
+ end
+
+ end
+end end
+
diff --git a/share/scripts/s_whoami.lua b/share/scripts/s_whoami.lua
new file mode 100644
index 0000000000..d391d65095
--- /dev/null
+++ b/share/scripts/s_whoami.lua
@@ -0,0 +1,22 @@
+ardour { ["type"] = "Snippet", name = "Who Am I?" }
+
+function factory() return function()
+
+function whoami()
+ --pcall is the lua equivalent
+ --of try: ... catch: ...
+ if not pcall(function() local first_check = Session:get_mixbus(0) end) then
+ return "Ardour"
+ else
+ local second_check = Session:get_mixbus(11)
+ if second_check:isnil() then
+ return "Mixbus"
+ else
+ return "32C"
+ end
+ end
+end
+
+print(whoami())
+
+end end
diff --git a/share/scripts/scope.lua b/share/scripts/scope.lua
new file mode 100644
index 0000000000..1952c100c4
--- /dev/null
+++ b/share/scripts/scope.lua
@@ -0,0 +1,229 @@
+ardour {
+ ["type"] = "dsp",
+ name = "a-Inline Scope",
+ category = "Visualization",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Mixer strip inline waveform display]]
+}
+
+-- return possible i/o configurations
+function dsp_ioconfig ()
+ -- -1, -1 = any number of channels as long as input and output count matches
+ return { [1] = { audio_in = -1, audio_out = -1}, }
+end
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "Timescale", min = .1, max = 5, default = 2, unit="sec", logarithmic = true },
+ { ["type"] = "input", name = "Logscale", min = 0, max = 1, default = 0, toggled = true },
+ { ["type"] = "input", name = "Height (Aspect)", min = 0, max = 3, default = 1, enum = true, scalepoints =
+ {
+ ["Min"] = 0,
+ ["16:10"] = 1,
+ ["1:1"] = 2,
+ ["Max"] = 3
+ }
+ },
+ }
+end
+
+
+function dsp_init (rate)
+ -- global variables (DSP part only)
+ samplerate = rate
+ bufsiz = 6 * rate
+ dpy_hz = rate / 25
+ dpy_wr = 0
+end
+
+function dsp_configure (ins, outs)
+ -- store configuration in global variable
+ audio_ins = ins:n_audio ()
+ -- allocate shared memory area
+ -- this is used to speed up DSP computaton (using a C array)
+ -- and to share data with the GUI
+ self:shmem ():allocate (4 + bufsiz * audio_ins)
+ self:shmem ():clear ()
+ self:shmem ():atomic_set_int (0, 0)
+ local cfg = self:shmem ():to_int (1):array ()
+ cfg[1] = samplerate
+ cfg[2] = bufsiz
+ cfg[3] = audio_ins
+end
+
+function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
+ local shmem = self:shmem ()
+ local write_ptr = shmem:atomic_get_int (0)
+
+ for c = 1,audio_ins do
+ -- Note: lua starts counting at 1, ardour's ChanMapping::get() at 0
+ local ib = in_map:get (ARDOUR.DataType ("audio"), c - 1); -- get id of mapped input buffer for given cannel
+ local ob = out_map:get (ARDOUR.DataType ("audio"), c - 1); -- get id of mapped output buffer for given cannel
+ local chn_off = 4 + bufsiz * (c - 1)
+ if (ib ~= ARDOUR.ChanMapping.Invalid) then
+ if (write_ptr + n_samples < bufsiz) then
+ ARDOUR.DSP.copy_vector (shmem:to_float (write_ptr + chn_off), bufs:get_audio (ib):data (offset), n_samples)
+ else
+ local w0 = bufsiz - write_ptr;
+ ARDOUR.DSP.copy_vector (shmem:to_float (write_ptr + chn_off), bufs:get_audio (ib):data (offset), w0)
+ ARDOUR.DSP.copy_vector (shmem:to_float (chn_off) , bufs:get_audio (ib):data (offset + w0), n_samples - w0)
+ end
+ if (ob ~= ARDOUR.ChanMapping.Invalid and ib ~= ob) then
+ ARDOUR.DSP.copy_vector (bufs:get_audio (ob):data (offset), bufs:get_audio (ib):data (offset), n_samples)
+ end
+ else
+ if (write_ptr + n_samples < bufsiz) then
+ ARDOUR.DSP.memset (shmem:to_float (write_ptr + chn_off), 0, n_samples)
+ else
+ local w0 = bufsiz - write_ptr;
+ ARDOUR.DSP.memset (shmem:to_float (write_ptr + chn_off), 0, w0)
+ ARDOUR.DSP.memset (shmem:to_float (chn_off) , 0, n_samples - w0)
+ end
+ end
+ end
+ -- clear unconnected inplace buffers
+ for c = 1,audio_ins do
+ local ib = in_map:get (ARDOUR.DataType ("audio"), c - 1); -- get id of mapped input buffer for given cannel
+ local ob = out_map:get (ARDOUR.DataType ("audio"), c - 1); -- get id of mapped output buffer for given cannel
+ if (ib == ARDOUR.ChanMapping.Invalid and ob ~= ARDOUR.ChanMapping.Invalid) then
+ bufs:get_audio (ob):silence (n_samples, offset)
+ end
+ end
+
+ write_ptr = (write_ptr + n_samples) % bufsiz
+ shmem:atomic_set_int (0, write_ptr)
+
+ -- emit QueueDraw every FPS
+ dpy_wr = dpy_wr + n_samples
+ if (dpy_wr > dpy_hz) then
+ dpy_wr = dpy_wr % dpy_hz;
+ self:queue_draw ()
+ end
+end
+
+
+-- helper function for drawing symmetric grid
+function gridline (ctx, x, xr, h, val)
+ ctx:move_to (math.floor (.5 + x + val * xr) -.5, 1)
+ ctx:line_to (math.floor (.5 + x + val * xr) -.5, h - 1)
+ ctx:stroke ()
+ ctx:move_to (math.floor (.5 + x - val * xr) -.5, 1)
+ ctx:line_to (math.floor (.5 + x - val * xr) -.5, h - 1)
+ ctx:stroke ()
+end
+
+function render_inline (ctx, w, max_h)
+ local ctrl = CtrlPorts:array () -- get control port array (read/write)
+ local shmem = self:shmem () -- get shared memory region
+ local cfg = shmem:to_int (1):array () -- "cast" into lua-table
+ local rate = cfg[1]
+ local buf_size = cfg[2]
+ local n_chn = cfg[3]
+
+ -- get settings
+ local timescale = ctrl[1] or 1.0 -- display size in seconds
+ local logscale = ctrl[2] or 0; logscale = logscale > 0 -- logscale
+ local hmode = ctrl[3] or 1 -- height mode
+
+ -- calc height
+ if hmode == 0 then
+ h = math.ceil (w * 10 / 16)
+ if (h > 44) then
+ h = 44
+ end
+ elseif (hmode == 2) then
+ h = w
+ elseif (hmode == 3) then
+ h = max_h
+ else
+ h = math.ceil (w * 10 / 16)
+ end
+
+ if (h > max_h) then
+ h = max_h
+ end
+
+ -- display settings
+ local spp = math.floor (timescale * rate / (h - 2)) -- samples per pixel
+ local spl = spp * (h - 1) -- total number of audio samples to read
+ local read_ptr = (shmem:atomic_get_int (0) + buf_size - spl - 1) % buf_size -- read pointer
+ local xr = math.ceil ((w - 2) * (0.47 / n_chn)) -- x-axis range (per channel)
+
+ -- clear background
+ ctx:rectangle (0, 0, w, h)
+ ctx:set_source_rgba (.2, .2, .2, 1.0)
+ ctx:fill ()
+
+ -- prepare drawing
+ ctx:set_line_width (1.0)
+ local dash3 = C.DoubleVector ()
+ dash3:add ({1, 3})
+ local dash4 = C.DoubleVector ()
+ dash4:add ({1, 4})
+
+ -- plot every channel
+ for c = 1,n_chn do
+ local x = math.floor ((w - 2) * (c - .5) / n_chn) + 1.5 -- x-axis center for given channel
+
+ -- draw grid --
+ ctx:set_source_rgba (.5, .5, .5, 1.0)
+ ctx:move_to (x, 1) ctx:line_to (x, h - 1) ctx:stroke ()
+
+ ctx:set_dash (dash4, 2)
+ ctx:set_source_rgba (.4, .4, .4, 1.0)
+ if (logscale) then
+ gridline (ctx, x, xr, h, ARDOUR.DSP.log_meter(-18))
+ gridline (ctx, x, xr, h, ARDOUR.DSP.log_meter(-6))
+ ctx:set_dash (dash3, 2)
+ ctx:set_source_rgba (.5, .1, .1, 1.0)
+ gridline (ctx, x, xr, h, ARDOUR.DSP.log_meter(-3))
+ else
+ gridline (ctx, x, xr, h, .1258)
+ gridline (ctx, x, xr, h, .5)
+ ctx:set_dash (dash3, 2)
+ ctx:set_source_rgba (.5, .1, .1, 1.0)
+ gridline (ctx, x, xr, h, .7079)
+ end
+ ctx:unset_dash ()
+ ctx:set_source_rgba (.5, .1, .1, 0.7)
+ gridline (ctx, x, xr, h, 1)
+
+
+ -- prepare waveform display drawing
+ ctx:set_source_rgba (.8, .8, .8, .7)
+ ctx:save ()
+ ctx:rectangle (math.floor (x - xr), 0, math.ceil (2 * xr), h)
+ ctx:clip ()
+
+ local chn_off = 4 + buf_size * (c - 1)
+ local buf_off = read_ptr;
+
+ -- iterate over every y-axis pixel
+ for y = 1, h - 1 do
+ local s_min = 0
+ local s_max = 0
+ -- calc min/max values for given range
+ if (buf_off + spp < buf_size) then
+ _, s_min, s_max = table.unpack (ARDOUR.DSP.peaks (shmem:to_float (chn_off + buf_off), s_min, s_max, spp))
+ else
+ local r0 = buf_size - buf_off;
+ _, s_min, s_max = table.unpack (ARDOUR.DSP.peaks (shmem:to_float (chn_off + buf_off), s_min, s_max, r0))
+ _, s_min, s_max = table.unpack (ARDOUR.DSP.peaks (shmem:to_float (chn_off) , s_min, s_max, spp - r0))
+ end
+ buf_off = (buf_off + spp) % buf_size;
+
+ if (logscale) then
+ s_max = ARDOUR.DSP.log_meter_coeff (s_max)
+ s_min = - ARDOUR.DSP.log_meter_coeff (-s_min)
+ end
+
+ ctx:move_to (x + s_min * xr, h - y + .5)
+ ctx:line_to (x + s_max * xr, h - y + .5)
+ end
+ ctx:stroke ()
+ ctx:restore ()
+ end
+ return {w, h}
+end
diff --git a/share/scripts/select_every_2nd_region.lua b/share/scripts/select_every_2nd_region.lua
new file mode 100644
index 0000000000..30ad519483
--- /dev/null
+++ b/share/scripts/select_every_2nd_region.lua
@@ -0,0 +1,61 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Region Select/2",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[select every 2nd region on all selected tracks]]
+}
+
+-- select every 2nd region on all selected tracks
+function factory () return function ()
+
+ local sl = ArdourUI.SelectionList () -- empty selection list
+
+ local sel = Editor:get_selection () -- get current selection
+ -- for each selected track/bus..
+ for route in sel.tracks:routelist ():iter () do
+ -- consider only tracks
+ local track = route:to_track ()
+ if track:isnil() then
+ goto continue
+ end
+
+ local skip = false;
+ -- iterate over all regions of the given track
+ for region in track:playlist():region_list():iter() do
+ if skip then
+ -- skip every 2nd region
+ skip = false;
+ else
+ skip = true;
+ -- get RegionView (GUI object to be selected)
+ local rv = Editor:regionview_from_region (region)
+ -- add it to the list of Objects to be selected
+ sl:push_back (rv);
+ end
+ end
+ ::continue::
+ end
+
+ -- set/replace current selection in the editor
+ Editor:set_selection (sl, ArdourUI.SelectionOp.Set);
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ local wh = math.min (width, height) * .5
+ ctx:translate (math.floor (width * .5 - wh), math.floor (height * .5 - wh))
+
+ ctx:set_line_width (1)
+ ctx:rectangle (wh * .25, wh * .75, wh * 1.5 , .5 * wh)
+ ctx:set_source_rgba (0, 0, 0, 1)
+ ctx:stroke_preserve ()
+ ctx:set_source_rgba (.9, .9, .9, 1)
+ ctx:fill ()
+
+ ctx:set_source_rgba (1, 0, 0, 1)
+ ctx:rectangle (.5 + math.ceil(wh * 0.25), .5 + math.ceil(wh * .75), math.floor(wh * .5) - 1, math.floor(.5 * wh) - 1)
+ ctx:stroke_preserve ()
+
+ ctx:rectangle (.5 + math.ceil(wh * 1.25), .5 + math.ceil(wh * .75), math.floor(wh * .5) - 1, math.floor(.5 * wh) - 1)
+ ctx:stroke_preserve ()
+end end
diff --git a/share/scripts/send_to_bus.lua b/share/scripts/send_to_bus.lua
new file mode 100644
index 0000000000..95a13f95ca
--- /dev/null
+++ b/share/scripts/send_to_bus.lua
@@ -0,0 +1,41 @@
+ardour { ["type"] = "EditorAction", name = "Send Tracks to Bus",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Create a Bus and add aux-sends from all selected tracks]]
+}
+
+function factory () return function ()
+ -- find number of channels to use for the new bus, follow master-bus' inputs
+ local chn = 2
+ local mst = Session:master_out ();
+ if not mst:isnil () then
+ chn = mst:n_inputs ():n_audio ()
+ end
+ mst = nil -- explicitly drop reference
+
+ local sel = Editor:get_selection () -- get selection
+ local tracks = ARDOUR.RouteListPtr () -- create a new list
+
+ -- find selected *tracks*, add to tracks list
+ for r in sel.tracks:routelist ():iter () do
+ if not r:to_track ():isnil () then
+ tracks:push_back (r)
+ end
+ end
+
+ if tracks:size () > 0 then
+ local bus = Session:new_audio_route (chn, chn, nil, 1, "", ARDOUR.PresentationInfo.Flag.AudioBus, ARDOUR.PresentationInfo.max_order)
+ if bus:size () > 0 then
+ Session:add_internal_sends (bus:front (), ARDOUR.Placement.PostFader, tracks);
+ end
+ end
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil (math.min (width, height) * .5) .. "px")
+ txt:set_text ("\u{2192}B") -- "->B"
+ local tw, th = txt:get_pixel_size ()
+ ctx:move_to (.5 * (width - tw), .5 * (height - th))
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ txt:show_in_cairo_context (ctx)
+end end
diff --git a/share/scripts/session_template_advanced.lua b/share/scripts/session_template_advanced.lua
new file mode 100644
index 0000000000..1eec0349ce
--- /dev/null
+++ b/share/scripts/session_template_advanced.lua
@@ -0,0 +1,59 @@
+ardour {
+ ["type"] = "SessionInit",
+ name = "Advanced Session",
+ description = [[Allows to configure master-bus and autoconnect]],
+ master_bus = 0
+}
+
+function factory () return function ()
+
+ local auto_connect_in = {
+ [0] = "Manually",
+ [1] = "automatically to physical inputs",
+ }
+
+ local auto_connect_out = {
+ [0] = "Manually",
+ [1] = "automatically to physical outputs",
+ [2] = "automatically to master bus",
+ }
+
+ local dialog_options = {
+ { type = "heading", title = "Customize Session: " .. Session:name () },
+ { type = "number", key = "master", title = "Master bus channels", min = 0, max = 24, step = 1, digits = 0, default = 2 },
+ { type = "checkbox", key = "monitor", title = "Add monitor section", default = ARDOUR.config():get_use_monitor_bus () },
+ { type = "dropdown", key = "ac_input", title = "Autoconnect Inputs",
+ values = {
+ [auto_connect_in[0]] = 0,
+ [auto_connect_in[1]] = 1,
+ },
+ default = auto_connect_in[ARDOUR.config():get_input_auto_connect ()]
+ },
+ { type = "dropdown", key = "ac_output", title = "Autoconnect Outputs",
+ values = {
+ [auto_connect_out[0]] = 0,
+ [auto_connect_out[1]] = 1,
+ [auto_connect_out[2]] = 2,
+ },
+ default = auto_connect_out[ARDOUR.config():get_output_auto_connect ()]
+ },
+ }
+
+ local dlg = LuaDialog.Dialog ("Template Setup", dialog_options)
+ local rv = dlg:run()
+ if (not rv) then return end
+
+ if rv['master'] > 0 then
+ local count = ARDOUR.ChanCount ( ARDOUR.DataType("audio"), rv['master'])
+ Session:add_master_bus (count)
+ end
+
+ if rv['monitor'] then
+ ARDOUR.config():set_use_monitor_bus (true)
+ end
+
+ ARDOUR.config():set_input_auto_connect (rv['ac_input'])
+ ARDOUR.config():set_output_auto_connect (rv['ac_output'])
+
+ Session:save_state("");
+end end
diff --git a/share/scripts/session_template_record.lua b/share/scripts/session_template_record.lua
new file mode 100644
index 0000000000..9656fd1473
--- /dev/null
+++ b/share/scripts/session_template_record.lua
@@ -0,0 +1,60 @@
+ardour {
+ ["type"] = "SessionInit",
+ name = "Recording Session",
+ description = [[Add as many mono tracks to the new session as there are physical audio inputs and optionally record-arm them.]]
+}
+
+---- For use with templates: Session Template setup-hook
+--
+-- If a script named 'template.lua' exists in a session-template folder
+-- the function produced by the 'factory' function of the script is called
+-- once after creating the session from the template.
+--
+-- (e.g. ~/.config/ardour5/templates/Template-Name/template.lua)
+--
+--
+---- For use as meta-session (specic session-setup scripts)
+--
+-- Every Lua script in the script-folder of type "SessionInit"
+-- is listed as implicit template in the new-session dialog.
+-- The function produced by the scripts `factory` function is called
+-- once after creating a new, empty session.
+--
+---- For use as meta-session (general purpose Actions)
+--
+-- In some cases normal action scripts can also serve as session-setup
+-- To include those ActionScripts in the template-list the script needs
+-- to implement an additional function
+-- function session_setup () return true end;
+-- The script's factory will be called without any parameters
+
+function factory () return function ()
+ local e = Session:engine()
+ -- from the engine's POV readable/capture ports are "outputs"
+ local _, t = e:get_backend_ports ("", ARDOUR.DataType("audio"), ARDOUR.PortFlags.IsOutput | ARDOUR.PortFlags.IsPhysical, C.StringVector())
+ -- table 't' holds argument references. t[4] is the C.StringVector (return value)
+ local tracks = t[4]:size();
+
+ local dialog_options = {
+ { type = "heading", title = "Customize Session: " .. Session:name () },
+ { type = "number", key = "tracks", title = "Create Tracks", min = 1, max = 128, step = 1, digits = 0, default = tracks },
+ { type = "checkbox", key = "recarm", default = false, title = "Record Arm Tracks" },
+ }
+
+ local dlg = LuaDialog.Dialog ("Template Setup", dialog_options)
+ local rv = dlg:run()
+ if (not rv or rv['tracks'] == 0) then
+ return
+ end
+
+ -- create tracks
+ local tl = Session:new_audio_track (1, 2, nil, rv['tracks'], "", ARDOUR.PresentationInfo.max_order, ARDOUR.TrackMode.Normal)
+ -- and optionally record-arm them
+ if rv['recarm'] then
+ for track in tl:iter() do
+ track:rec_enable_control ():set_value (1, PBD.GroupControlDisposition.NoGroup)
+ end
+ end
+
+ Session:save_state("");
+end end
diff --git a/share/scripts/set_automation_mode.lua b/share/scripts/set_automation_mode.lua
new file mode 100644
index 0000000000..ee99ac1a1b
--- /dev/null
+++ b/share/scripts/set_automation_mode.lua
@@ -0,0 +1,83 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Engage Automation",
+ author = "Ardour Team",
+ description = [[Set automation mode of various controls (fader, trim, mute, pan), for all selected tracks.]]
+}
+
+function factory() return function()
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR.AutoState
+ local auto_state = ARDOUR.AutoState.Touch
+ local with_plugins = true
+
+ -- Ask user which mode to use, and whether to include plugins
+ local dialog_options = {
+ { type = "label", align="left", title = "Select automation state to apply all selected tracks:" },
+ {
+ type = "dropdown", key = "as", title="", values =
+ {
+ ["Manual"] = ARDOUR.AutoState.Off,
+ ["Touch"] = ARDOUR.AutoState.Touch,
+ ["Write"] = ARDOUR.AutoState.Write,
+ ["Play"] = ARDOUR.AutoState.Play,
+ },
+ default = "Touch"
+ },
+ { type = "checkbox", key = "plug", default = true, title = "Also set plugin controls" }
+ }
+ local rv = LuaDialog.Dialog ("Select Automation State", dialog_options):run()
+ if not rv then return end
+ auto_state = rv['as']
+ with_plugins = rv['plug']
+
+ -- helper function to check if given ARDOUR:AutomationControl exists
+ function maybe_set_automation_state (ac)
+ if not ac:isnil() then
+ ac:set_automation_state (auto_state)
+ end
+ end
+
+ -- helper function to iterate over all automatable parameters of a plugin
+ function set_plugin_control_mode (pi)
+ local pc = pi:plugin (0):parameter_count()
+ for c = 0, pc do
+ local ac = pi:to_automatable():automation_control (Evoral.Parameter (ARDOUR.AutomationType.PluginAutomation, 0, c), false)
+ if not ac:isnil () then
+ ac:set_automation_state (auto_state)
+ end
+ end
+ end
+
+ -- get selected tracks
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
+ local sel = Editor:get_selection ()
+
+ if sel.tracks:routelist ():empty() then
+ LuaDialog.Message ("Select Automation State", "No Tracks are selected.", LuaDialog.MessageType.Info, LuaDialog.ButtonType.Close):run ()
+ return
+ end
+
+ -- iterate over selected tracks
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:TrackSelection
+ for r in sel.tracks:routelist ():iter () do
+ -- r is-a http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Route
+ -- which interhits from ARDOUR:Stripable
+
+ -- set route's direct control
+ r:gain_control ():set_automation_state (auto_state)
+ maybe_set_automation_state (r:trim_control ())
+ maybe_set_automation_state (r:mute_control ())
+ maybe_set_automation_state (r:pan_azimuth_control ())
+ maybe_set_automation_state (r:pan_width_control ())
+
+ -- for every plugin
+ local i = 0
+ while with_plugins do
+ local proc = r:nth_plugin (i)
+ if proc:isnil () then break end
+ set_plugin_control_mode (proc:to_insert ())
+ i = i + 1
+ end
+
+ end
+end end
diff --git a/share/scripts/singen.lua b/share/scripts/singen.lua
new file mode 100644
index 0000000000..8b22c1257b
--- /dev/null
+++ b/share/scripts/singen.lua
@@ -0,0 +1,94 @@
+ardour {
+ ["type"] = "dsp",
+ name = "SinGen",
+ category = "Instrument",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Sine Wave Generator (v1.2)]]
+}
+
+local lpf = 0
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "Frequency", min = 20, max = 20000, default = 1000, unit="Hz", logarithmic = true },
+ { ["type"] = "input", name = "Gain", min = -90, max = 0, default = -18, unit="dB" },
+ }
+end
+
+function dsp_ioconfig ()
+ return { [1] = { audio_in = -1, audio_out = -1}, }
+end
+
+function dsp_init (rate)
+ r = rate
+ lpf = 2048 / rate
+end
+
+function low_pass_filter_param(old, new, limit)
+ if math.abs (old - new) < limit then
+ return new
+ else
+ return old + lpf * (new - old)
+ end
+end
+
+local p = 0
+local fo = 0
+local ao = 0
+
+function dsp_run (ins, outs, n_samples)
+ local ctrl = CtrlPorts:array() --call parameters
+
+ local a = {} --init array
+ local f = ctrl[1] or 1000
+ local amp = low_pass_filter_param(ao, ARDOUR.DSP.dB_to_coefficient(ctrl[2]), 0.02)
+ local inc = f / r
+
+ for s = 1, n_samples do --fill table with fragments of a sine wave
+ p = p + inc
+ a[s] = amp * math.sin(p * (2 * math.pi))
+ end
+
+ for c = 1,#outs do
+ outs[c]:set_table(a, n_samples) --passes array into buffer
+ end
+
+ if (f ~= fo) or (a ~= ao) then
+ self:queue_draw()
+ end
+ fo = f
+ ao = amp
+end
+
+function render_inline (ctx, w, max_h) --inline display
+ local ctrl = CtrlPorts:array()
+ h = 30
+ p = 0
+ inc = 1/w
+ f = ctrl[1] / 1000
+ if f < 0.5 then f = 0.5 end
+ if f > 8 then f = 8 end
+
+ --draw rectangle
+ ctx:rectangle(0, 0, w, h)
+ ctx:set_source_rgba(0, 0, 0, 1.0)
+ ctx:fill()
+ ctx:set_line_width(1.5)
+ ctx:set_source_rgba(0.8, 0.8, 0.8, 1.0)
+
+ l_x = 0
+ l_y = 0
+ for x = 0,w do
+ y = ARDOUR.DSP.dB_to_coefficient(ctrl[2]) * math.sin(f * (2 * math.pi * (p)))
+ yc = 0.5 * h + ((-0.5 * h) * y)
+ ctx:move_to (x, yc + 3)
+ ctx:line_to (l_x, l_y + 3)
+ l_x = x
+ l_y = yc
+ ctx:stroke()
+ p = p + inc
+ end
+ return {w, h + 6}
+end \ No newline at end of file
diff --git a/share/scripts/spectrogram.lua b/share/scripts/spectrogram.lua
new file mode 100644
index 0000000000..86419682b4
--- /dev/null
+++ b/share/scripts/spectrogram.lua
@@ -0,0 +1,363 @@
+ardour {
+ ["type"] = "dsp",
+ name = "a-Inline Spectrogram",
+ category = "Visualization",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Mixer strip inline spectrum display]]
+}
+
+-- return possible i/o configurations
+function dsp_ioconfig ()
+ -- -1, -1 = any number of channels as long as input and output count matches
+ return { [1] = { audio_in = -1, audio_out = -1}, }
+end
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "Logscale", min = 0, max = 1, default = 0, toggled = true },
+ { ["type"] = "input", name = "1/f scale", min = 0, max = 1, default = 1, toggled = true },
+ { ["type"] = "input", name = "FFT Size", min = 0, max = 4, default = 3, enum = true, scalepoints =
+ {
+ ["512"] = 0,
+ ["1024"] = 1,
+ ["2048"] = 2,
+ ["4096"] = 3,
+ ["8192"] = 4,
+ }
+ },
+ { ["type"] = "input", name = "Height (Aspect)", min = 0, max = 3, default = 1, enum = true, scalepoints =
+ {
+ ["Min"] = 0,
+ ["16:10"] = 1,
+ ["1:1"] = 2,
+ ["Max"] = 3
+ }
+ },
+ { ["type"] = "input", name = "Range", min = 20, max = 160, default = 60, unit="dB"},
+ { ["type"] = "input", name = "Offset", min = -40, max = 40, default = 0, unit="dB"},
+ }
+end
+
+-- symbolic names for shmem offsets
+local SHMEM_RATE = 0
+local SHMEM_WRITEPTR = 1
+local SHMEM_AUDIO = 2
+
+-- a C memory area.
+-- It needs to be in global scope.
+-- When the variable is set to nil, the allocated memory is free()ed.
+-- the memory can be interpeted as float* for use in DSP, or read/write
+-- to a C++ Ringbuffer instance.
+-- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP:DspShm
+local cmem = nil
+
+function dsp_init (rate)
+ -- global variables (DSP part only)
+ dpy_hz = rate / 25
+ dpy_wr = 0
+
+ -- create a shared memory area to hold the sample rate, the write_pointer,
+ -- and (float) audio-data. Make it big enough to store 2s of audio which
+ -- should be enough. If not, the DSP will overwrite the oldest data anyway.
+ self:shmem ():allocate(2 + 2 * rate)
+ self:shmem ():clear()
+ self:shmem ():atomic_set_int (SHMEM_RATE, rate)
+ self:shmem ():atomic_set_int (SHMEM_WRITEPTR, 0)
+
+ -- allocate memory, local mix buffer
+ cmem = ARDOUR.DSP.DspShm (8192)
+end
+
+-- "dsp_runmap" uses Ardour's internal processor API, eqivalent to
+-- 'connect_and_run()". There is no overhead (mapping, translating buffers).
+-- The lua implementation is responsible to map all the buffers directly.
+function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
+ -- here we sum all audio input channels and then copy the data to a
+ -- custom-made circular table for the GUIs to process later
+
+ local audio_ins = in_map:count (): n_audio () -- number of audio input buffers
+ local ccnt = 0 -- processed channel count
+ local mem = cmem:to_float(0) -- a "FloatArray", float* for direct C API usage from the previously allocated buffer
+ local rate = self:shmem ():atomic_get_int (SHMEM_RATE)
+ local write_ptr = self:shmem ():atomic_get_int (SHMEM_WRITEPTR)
+
+ local ringsize = 2 * rate
+ local ptr_wrap = math.floor(2^50 / ringsize) * ringsize
+
+ for c = 1,audio_ins do
+ -- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:ChanMapping
+ -- Note: lua starts counting at 1, ardour's ChanMapping::get() at 0
+ local ib = in_map:get (ARDOUR.DataType ("audio"), c - 1) -- get index of mapped input buffer
+ local ob = out_map:get (ARDOUR.DataType ("audio"), c - 1) -- get index of mapped output buffer
+
+ -- check if the input is connected to a buffer
+ if (ib ~= ARDOUR.ChanMapping.Invalid) then
+
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:AudioBuffer
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:DSP
+ if c == 1 then
+ -- first channel, copy as-is
+ ARDOUR.DSP.copy_vector (mem, bufs:get_audio (ib):data (offset), n_samples)
+ else
+ -- all other channels, add to existing data.
+ ARDOUR.DSP.mix_buffers_no_gain (mem, bufs:get_audio (ib):data (offset), n_samples)
+ end
+ ccnt = ccnt + 1;
+
+ -- copy data to output (if not processing in-place)
+ if (ob ~= ARDOUR.ChanMapping.Invalid and ib ~= ob) then
+ ARDOUR.DSP.copy_vector (bufs:get_audio (ob):data (offset), bufs:get_audio (ib):data (offset), n_samples)
+ end
+ end
+ end
+
+ -- Clear unconnected output buffers.
+ -- In case we're processing in-place some buffers may be identical,
+ -- so this must be done *after processing*.
+ for c = 1,audio_ins do
+ local ib = in_map:get (ARDOUR.DataType ("audio"), c - 1)
+ local ob = out_map:get (ARDOUR.DataType ("audio"), c - 1)
+ if (ib == ARDOUR.ChanMapping.Invalid and ob ~= ARDOUR.ChanMapping.Invalid) then
+ bufs:get_audio (ob):silence (n_samples, offset)
+ end
+ end
+
+ -- Normalize gain (1 / channel-count)
+ if ccnt > 1 then
+ ARDOUR.DSP.apply_gain_to_buffer (mem, n_samples, 1 / ccnt)
+ end
+
+ -- if no channels were processed, feed silence.
+ if ccnt == 0 then
+ ARDOUR.DSP.memset (mem, 0, n_samples)
+ end
+
+ -- write data to the circular table
+ if (write_ptr % ringsize + n_samples < ringsize) then
+ ARDOUR.DSP.copy_vector (self:shmem ():to_float (SHMEM_AUDIO + write_ptr % ringsize), mem, n_samples)
+ else
+ local chunk = ringsize - write_ptr % ringsize
+ ARDOUR.DSP.copy_vector (self:shmem ():to_float (SHMEM_AUDIO + write_ptr % ringsize), mem, chunk)
+ ARDOUR.DSP.copy_vector (self:shmem ():to_float (SHMEM_AUDIO), cmem:to_float (chunk), n_samples - chunk)
+ end
+ self:shmem ():atomic_set_int (SHMEM_WRITEPTR, (write_ptr + n_samples) % ptr_wrap)
+
+ -- emit QueueDraw every FPS
+ -- TODO: call every FFT window-size worth of samples, at most every FPS
+ dpy_wr = dpy_wr + n_samples
+ if (dpy_wr > dpy_hz) then
+ dpy_wr = dpy_wr % dpy_hz
+ self:queue_draw ()
+ end
+end
+
+----------------------------------------------------------------
+-- GUI
+
+local fft = nil
+local read_ptr = 0
+local line = 0
+local img = nil
+local fft_size = 0
+local last_log = false
+
+
+function render_inline (ctx, w, max_h)
+ local ctrl = CtrlPorts:array () -- get control port array (read/write)
+ local rate = self:shmem ():atomic_get_int (SHMEM_RATE)
+ if not cmem then
+ cmem = ARDOUR.DSP.DspShm (0)
+ end
+
+ -- get settings
+ local logscale = ctrl[1] or 0; logscale = logscale > 0 -- x-axis logscale
+ local pink = ctrl[2] or 0; pink = pink > 0 -- 1/f scale
+ local fftsizeenum = ctrl[3] or 3 -- fft-size enum
+ local hmode = ctrl[4] or 1 -- height mode enum
+ local dbrange = ctrl[5] or 60
+ local gaindb = ctrl[6] or 0
+
+ local fftsize
+ if fftsizeenum == 0 then fftsize = 512
+ elseif fftsizeenum == 1 then fftsize = 1024
+ elseif fftsizeenum == 2 then fftsize = 2048
+ elseif fftsizeenum == 4 then fftsize = 8192
+ else fftsize = 4096
+ end
+
+ if fftsize ~= fft_size then
+ fft_size = fftsize
+ fft = nil
+ end
+
+ if dbrange < 20 then dbrange = 20; end
+ if dbrange > 160 then dbrange = 160; end
+ if gaindb < -40 then dbrange = -40; end
+ if gaindb > 40 then dbrange = 40; end
+
+
+ if not fft then
+ fft = ARDOUR.DSP.FFTSpectrum (fft_size, rate)
+ cmem:allocate (fft_size)
+ end
+
+ if last_log ~= logscale then
+ last_log = logscale
+ img = nil
+ line = 0
+ end
+
+ -- calc height
+ if hmode == 0 then
+ h = math.ceil (w * 10 / 16)
+ if (h > 44) then
+ h = 44
+ end
+ elseif (hmode == 2) then
+ h = w
+ elseif (hmode == 3) then
+ h = max_h
+ else
+ h = math.ceil (w * 10 / 16)
+ end
+ if (h > max_h) then
+ h = max_h
+ end
+
+ -- re-create image surface
+ if not img or img:get_width() ~= w or img:get_height () ~= h then
+ img = Cairo.ImageSurface (Cairo.Format.ARGB32, w, h)
+ line = 0
+ end
+ local ictx = img:context ()
+
+ local bins = fft_size / 2 - 1 -- fft bin count
+ local bpx = bins / w -- bins per x-pixel (linear)
+ local fpb = rate / fft_size -- freq-step per bin
+ local f_e = rate / 2 / fpb -- log-scale exponent
+ local f_b = w / math.log (fft_size / 2) -- inverse log-scale base
+ local f_l = math.log (fft_size / rate) * f_b -- inverse logscale lower-bound
+
+ local mem = cmem:to_float (0)
+
+ local ringsize = 2 * rate
+ local ptr_wrap = math.floor(2^50 / ringsize) * ringsize
+
+ local write_ptr
+ function read_space()
+ write_ptr = self:shmem ():atomic_get_int (SHMEM_WRITEPTR)
+ local space = (write_ptr - read_ptr + ptr_wrap) % ptr_wrap
+ if space > ringsize then
+ -- the GUI lagged too much and unread data was overwritten
+ -- jump to the oldest audio still present in the ringtable
+ read_ptr = write_ptr - ringsize
+ space = ringsize
+ end
+ return space
+ end
+
+ while (read_space() >= fft_size) do
+ -- read one window from the circular table
+ if (read_ptr % ringsize + fft_size < ringsize) then
+ ARDOUR.DSP.copy_vector (mem, self:shmem ():to_float (SHMEM_AUDIO + read_ptr % ringsize), fft_size)
+ else
+ local chunk = ringsize - read_ptr % ringsize
+ ARDOUR.DSP.copy_vector (mem, self:shmem ():to_float (SHMEM_AUDIO + read_ptr % ringsize), chunk)
+ ARDOUR.DSP.copy_vector (cmem:to_float(chunk), self:shmem ():to_float (SHMEM_AUDIO), fft_size - chunk)
+ end
+ read_ptr = (read_ptr + fft_size) % ptr_wrap
+
+ -- process one line
+ fft:set_data_hann (mem, fft_size, 0)
+ fft:execute ()
+
+ -- draw spectrum
+ assert (bpx >= 1)
+
+ -- scroll
+ if line == 0 then line = h - 1; else line = line - 1; end
+
+ -- clear this line
+ ictx:set_source_rgba (0, 0, 0, 1)
+ ictx:rectangle (0, line, w, 1)
+ ictx:fill ()
+
+ for x = 0, w - 1 do
+ local pk = 0
+ local b0, b1
+ if logscale then
+ -- 20 .. 20k
+ b0 = math.floor (f_e ^ (x / w))
+ b1 = math.floor (f_e ^ ((x + 1) / w))
+ else
+ b0 = math.floor (x * bpx)
+ b1 = math.floor ((x + 1) * bpx)
+ end
+
+ if b1 >= b0 and b1 <= bins and b0 >= 0 then
+ for i = b0, b1 do
+ local level = gaindb + fft:power_at_bin (i, pink and i or 1) -- pink ? i : 1
+ if level > -dbrange then
+ local p = (dbrange + level) / dbrange
+ if p > pk then pk = p; end
+ end
+ end
+ end
+ if pk > 0.0 then
+ if pk > 1.0 then pk = 1.0; end
+ ictx:set_source_rgba (ARDOUR.LuaAPI.hsla_to_rgba (.70 - .72 * pk, .9, .3 + pk * .4));
+ ictx:rectangle (x, line, 1, 1)
+ ictx:fill ()
+ end
+ end
+ end
+
+ -- copy image surface
+ if line == 0 then
+ img:set_as_source (ctx, 0, 0)
+ ctx:rectangle (0, 0, w, h)
+ ctx:fill ()
+ else
+ local yp = h - line - 1;
+ img:set_as_source (ctx, 0, yp)
+ ctx:rectangle (0, yp, w, line)
+ ctx:fill ()
+
+ img:set_as_source (ctx, 0, -line)
+ ctx:rectangle (0, 0, w, yp)
+ ctx:fill ()
+ end
+
+
+ -- draw grid on top
+ function x_at_freq (f)
+ if logscale then
+ return f_l + f_b * math.log (f)
+ else
+ return 2 * w * f / rate;
+ end
+ end
+
+ function grid_freq (f)
+ -- draw vertical grid line
+ local x = .5 + math.floor (x_at_freq (f))
+ ctx:move_to (x, 0)
+ ctx:line_to (x, h)
+ ctx:stroke ()
+ end
+
+ -- draw grid on top
+ local dash3 = C.DoubleVector ()
+ dash3:add ({1, 3})
+ ctx:set_line_width (1.0)
+ ctx:set_dash (dash3, 2) -- dotted line
+ ctx:set_source_rgba (.5, .5, .5, .8)
+ grid_freq (100)
+ grid_freq (1000)
+ grid_freq (10000)
+ ctx:unset_dash ()
+
+ return {w, h}
+end
diff --git a/share/scripts/split_all_markers.lua b/share/scripts/split_all_markers.lua
new file mode 100644
index 0000000000..001c4ae432
--- /dev/null
+++ b/share/scripts/split_all_markers.lua
@@ -0,0 +1,122 @@
+ardour { ["type"] = "EditorAction", name = "Marker Split",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Split regions on selected tracks at all locations markers]]
+}
+
+function factory (params) return function ()
+
+ local loc = Session:locations () -- all marker locations
+
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:Selection
+ local sel = Editor:get_selection ()
+
+ -- prepare undo operation
+ -- see http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Session
+ Session:begin_reversible_command ("Auto Region Split")
+ local add_undo = false -- keep track if something has changed
+
+ -- Track/Bus Selection -- iterate over all Editor-GUI selected tracks
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ArdourUI:TrackSelection
+ for r in sel.tracks:routelist ():iter () do
+ -- each of the items 'r' is-a
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Route
+
+ local track = r:to_track () -- see if it's a track
+ if track:isnil () then
+ -- if not, skip it
+ goto continue
+ end
+
+ -- get track's playlist
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Playlist
+ local playlist = track:playlist ()
+
+ -- clear existing changes, prepare "diff" of state for undo
+ playlist:to_stateful ():clear_changes ()
+
+ -- iterate over all location markers
+ for l in loc:list ():iter () do
+ if l:is_mark() then
+ -- get all regions on the given track's playlist (may be stacked)
+ for reg in playlist:regions_at (l:start ()):iter () do
+ playlist:split_region (reg, ARDOUR.MusicSample (l:start(), 0))
+ -- the above operation will invalidate the playlist's region list:
+ -- split creates 2 new regions.
+ --
+ -- Hence this script does it the way it does: the inner-most loop
+ -- is over playlist-regions.
+ end
+ end
+ end
+
+ -- collect undo data
+ if not Session:add_stateful_diff_command (playlist:to_statefuldestructible ()):empty () then
+ -- is something has changed, we need to save it at the end.
+ add_undo = true
+ end
+
+ ::continue::
+ end
+
+ -- 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
+
+end end
+
+
+-- render an icon for the toolbar action-button
+-- this is genrally square width == height.
+-- The background is set according to the theme (leave transparent when drawing).
+-- A foreground color is passed as parameter 'fg'
+--
+-- ctx is-a http://manual.ardour.org/lua-scripting/class_reference/#Cairo:Context
+-- 2D vector graphics http://cairographics.org/
+function icon (params) return function (ctx, width, height, fg)
+ local mh = height - 3.5;
+ local m3 = width / 3;
+ local m6 = width / 6;
+
+ ctx:set_line_width (.5)
+
+ -- compare to gtk2_ardour/marker.cc "Marker"
+ ctx:set_source_rgba (.8, .8, .2, 1.0)
+ ctx:move_to (width / 2 - m6, 2)
+ ctx:rel_line_to (m3, 0)
+ ctx:rel_line_to (0, mh * 0.4)
+ ctx:rel_line_to (-m6, mh * 0.6)
+ ctx:rel_line_to (-m6, -mh * 0.6)
+ ctx:close_path ()
+ ctx:fill_preserve ()
+ ctx:set_source_rgba (.0, .0, .0, 1.0)
+ ctx:stroke ()
+
+ -- draw an arrow <--|--> on top, using the foreground color
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ ctx:set_line_width (1)
+
+ ctx:move_to (width * .5, height * .4)
+ ctx:line_to (width * .5, height * .6)
+ ctx:stroke ()
+
+ ctx:move_to (2, height * .5)
+ ctx:line_to (width - 2, height * .5)
+ ctx:stroke ()
+
+ ctx:move_to (width - 2, height * .5)
+ ctx:rel_line_to (-m6, -m6)
+ ctx:rel_line_to (0, m3)
+ ctx:close_path ()
+ ctx:fill ()
+
+ ctx:move_to (2, height * .5)
+ ctx:rel_line_to (m6, -m6)
+ ctx:rel_line_to (0, m3)
+ ctx:close_path ()
+ ctx:fill ()
+end end
diff --git a/share/scripts/stop_at_marker.lua b/share/scripts/stop_at_marker.lua
new file mode 100644
index 0000000000..35de3510c4
--- /dev/null
+++ b/share/scripts/stop_at_marker.lua
@@ -0,0 +1,49 @@
+ardour {
+ ["type"] = "session",
+ name = "Stop at Marker",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An example session script which stops the transport on every location marker when rolling forward.]]
+}
+
+function factory ()
+ return function (n_samples)
+ if (not Session:transport_rolling ()) then
+ -- not rolling, nothing to do.
+ return
+ end
+
+ local pos = Session:transport_sample () -- current playhead position
+ local loc = Session:locations () -- all marker locations
+
+ -- find first marker after the current playhead position, ignore loop + punch ranges
+ -- (this only works when rolling forward, to extend this example see
+ -- http://manual.ardour.org/lua-scripting/class_reference/#ARDOUR:Locations )
+ --
+ local m = loc:first_mark_after (pos, false)
+
+ if (m == -1) then
+ -- no marker was found
+ return
+ end
+
+ -- due to `first_mark_after(pos)` "m" is always > "pos":
+ -- assert(pos < m)
+ --
+ -- This callback happens from within the process callback:
+ --
+ -- this cycle's end = next cycle start = pos + n_samples.
+ --
+ -- Note that if "m" is exactly at cycle's end, that marker
+ -- will be at "pos" in the next cycle. Since we ask for
+ -- "first_mark_after pos", the marker would not be found.
+ --
+ -- So even though "pos + n_samples" is barely reached,
+ -- we need to stop at "m" in the cycle that crosses or ends at "m".
+ if (pos + n_samples >= m) then
+ -- asking to locate to "m" ensures that playback continues at "m"
+ -- and the same marker will not be taken into account.
+ Session:request_locate (m, ARDOUR.LocateTransportDisposition.MustStop, ARDOUR.TransportRequestSource.TRS_Engine)
+ end
+ end
+end
diff --git a/share/scripts/store_recall_mixer.lua b/share/scripts/store_recall_mixer.lua
new file mode 100644
index 0000000000..53562b7ee2
--- /dev/null
+++ b/share/scripts/store_recall_mixer.lua
@@ -0,0 +1,575 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Mixer Store",
+ author = "Ardour Lua Taskforce",
+ description = [[
+ Stores the current Mixer state as a file
+ that can be read and recalled arbitrarily.
+ Supports: processor settings, grouping,
+ mute, solo, gain, trim, pan and processor ordering,
+ plus re-adding certain deleted plugins.
+ ]]
+}
+
+function factory() return function()
+
+ local invalidate = {}
+ local path = ARDOUR.LuaAPI.build_filename(Session:path(), "export", "params.lua")
+
+ function mismatch_dialog(mismatch_str, checkbox_str)
+ --string.format("Track didn't match ID: %d, but did match track in session: %s", 999, 'track')
+ local dialog = {
+ { type = "label", colspan = 5, title = mismatch_str },
+ { type = "checkbox", col=1, colspan = 1, key = "use", default = true, title = checkbox_str },
+ }
+ local mismatch_return = LuaDialog.Dialog("", dialog):run()
+ if mismatch_return then
+ return mismatch_return['use']
+ else
+ return false
+ end
+ end
+
+ function get_processor_by_name(track, name)
+ local i = 0
+ local proc = track:nth_processor(i)
+ repeat
+ if(proc:display_name() == name) then
+ return proc
+ else
+ i = i + 1
+ end
+ proc = track:nth_processor(i)
+ until proc:isnil()
+ end
+
+ function new_plugin(name)
+ for x = 0, 6 do
+ local plugin = ARDOUR.LuaAPI.new_plugin(Session, name, x, "")
+ if not(plugin:isnil()) then return plugin end
+ end
+ end
+
+ function group_by_id(id)
+ local id = tonumber(id)
+ for g in Session:route_groups():iter() do
+ local group_id = tonumber(g:to_stateful():id():to_s())
+ if group_id == id then return g end
+ end
+ end
+
+ function group_by_name(name)
+ for g in Session:route_groups():iter() do
+ if g:name() == name then return g end
+ end
+ end
+
+ function route_groupid_interrogate(t)
+ local group = false
+ for g in Session:route_groups():iter() do
+ for r in g:route_list():iter() do
+ if r:name() == t:name() then group = g:to_stateful():id():to_s() end
+ end
+ end return group
+ end
+
+ function route_group_interrogate(t)
+ for g in Session:route_groups():iter() do
+ for r in g:route_list():iter() do
+ if r:name() == t:name() then return g end
+ end
+ end
+ end
+
+ function empty_last_store() --empty current file from last run
+ local file = io.open(path, "w")
+ file:write("")
+ file:close()
+ end
+
+ function mark_tracks(selected)
+
+ empty_last_store()
+
+ local route_string = [[instance = {
+ route_id = %d,
+ route_name = '%s',
+ gain_control = %f,
+ trim_control = %f,
+ pan_control = %s,
+ muted = %s,
+ soloed = %s,
+ order = {%s},
+ cache = {%s},
+ group = %s,
+ group_name = '%s'
+ }]]
+
+ local group_string = [[instance = {
+ group_id = %s,
+ name = '%s',
+ routes = {%s},
+ }]]
+
+ local processor_string = [[instance = {
+ plugin_id = %d,
+ display_name = '%s',
+ owned_by_route_name = '%s',
+ owned_by_route_id = %d,
+ parameters = {%s},
+ active = %s,
+ }]]
+
+ local group_route_string = " [%d] = %s,"
+ local proc_order_string = " [%d] = %d,"
+ local proc_cache_string = " [%d] = '%s',"
+ local params_string = " [%d] = %f,"
+
+ --ensure easy-to-read formatting doesn't make it through
+ local route_string = string.gsub(route_string, "[\n\t]", "")
+ local group_string = string.gsub(group_string, "[\n\t]", "")
+ local processor_string = string.gsub(processor_string, "[\n\t]", "")
+
+ local sel = Editor:get_selection ()
+ local groups_to_write = {}
+ local i = 0
+
+ local tracks = Session:get_routes()
+
+ if selected then tracks = sel.tracks:routelist() end
+
+ for r in tracks:iter() do
+ local group = route_group_interrogate(r)
+ if group then
+ local already_there = false
+ for _, v in pairs(groups_to_write) do
+ if group == v then
+ already_there = true
+ end
+ end
+ if not(already_there) then
+ groups_to_write[#groups_to_write + 1] = group
+ end
+ end
+ end
+
+ for _, g in pairs(groups_to_write) do
+ local tmp_str = ""
+ for t in g:route_list():iter() do
+ tmp_str = tmp_str .. string.format(group_route_string, i, t:to_stateful():id():to_s())
+ i = i + 1
+ end
+ local group_str = string.format(
+ group_string,
+ g:to_stateful():id():to_s(),
+ g:name(),
+ tmp_str
+ )
+
+ file = io.open(path, "a")
+ file:write(group_str, "\r\n")
+ file:close()
+ end
+
+ for r in tracks:iter() do
+ if r:is_monitor () or r:is_auditioner () then goto nextroute end -- skip special routes
+
+ local order = ARDOUR.ProcessorList()
+ local x = 0
+ repeat
+ local proc = r:nth_processor(x)
+ if not proc:isnil() then
+ order:push_back(proc)
+ end
+ x = x + 1
+ until proc:isnil()
+
+ local route_group = route_group_interrogate(r)
+ if route_group then route_group = route_group:name() else route_group = "" end
+ local rid = r:to_stateful():id():to_s()
+ local pan = r:pan_azimuth_control()
+ if pan:isnil() then pan = false else pan = pan:get_value() end --sometimes a route doesn't have pan, like the master.
+
+ local order_nmbr = 0
+ local tmp_order_str, tmp_cache_str = "", ""
+ for p in order:iter() do
+ local pid = p:to_stateful():id():to_s()
+ if not(string.find(p:display_name(), "latcomp")) then
+ tmp_order_str = tmp_order_str .. string.format(proc_order_string, order_nmbr, pid)
+ tmp_cache_str = tmp_cache_str .. string.format(proc_cache_string, pid, p:display_name())
+ end
+ order_nmbr = order_nmbr + 1
+ end
+
+ local route_str = string.format(
+ route_string,
+ rid,
+ r:name(),
+ r:gain_control():get_value(),
+ r:trim_control():get_value(),
+ tostring(pan),
+ r:muted(),
+ r:soloed(),
+ tmp_order_str,
+ tmp_cache_str,
+ route_groupid_interrogate(r),
+ route_group
+ )
+
+ file = io.open(path, "a")
+ file:write(route_str, "\n")
+ file:close()
+
+ local i = 0
+ while true do
+ local params = {}
+ local proc = r:nth_plugin (i)
+ if proc:isnil () then break end
+ local active = proc:active()
+ local id = proc:to_stateful():id():to_s()
+ local plug = proc:to_insert ():plugin (0)
+ local n = 0 -- count control-ports
+ for j = 0, plug:parameter_count () - 1 do -- iterate over all plugin parameters
+ if plug:parameter_is_control (j) then
+ local label = plug:parameter_label (j)
+ if plug:parameter_is_input (j) and label ~= "hidden" and label:sub (1,1) ~= "#" then
+ local _, _, pd = ARDOUR.LuaAPI.plugin_automation(proc, n)
+ local val = ARDOUR.LuaAPI.get_processor_param(proc, j, true)
+ --print(r:name(), "->", proc:display_name(), label, val)
+ params[n] = val
+ end
+ n = n + 1
+ end
+ end
+ i = i + 1
+
+ local tmp_params_str = ""
+ for k, v in pairs(params) do
+ tmp_params_str = tmp_params_str .. string.format(params_string, k, v)
+ end
+
+ local proc_str = string.format(
+ processor_string,
+ id,
+ proc:display_name(),
+ r:name(),
+ r:to_stateful():id():to_s(),
+ tmp_params_str,
+ active
+ )
+ file = io.open(path, "a")
+ file:write(proc_str, "\n")
+ file:close()
+ end
+ ::nextroute::
+ end
+ end
+
+ function recall(debug, dry_run)
+ local file = io.open(path, "r")
+ assert(file, "File not found!")
+ local bypass_routes = {}
+
+ local i = 0
+ for l in file:lines() do
+ --print(i, l)
+
+ local exec_line = dry_run["dothis-"..i]
+ local skip_line = false
+ if not(exec_line == nil) and not(exec_line) then
+ skip_line = true
+ end
+
+ local plugin, route, group = false, false, false
+ local f = load(l)
+
+ if debug then
+ print(i, string.sub(l, 0, 29), f)
+ end
+
+ if f then f() end
+
+ if instance["route_id"] then route = true end
+ if instance["plugin_id"] then plugin = true end
+ if instance["group_id"] then group = true end
+
+ if group then
+ if skip_line then goto nextline end
+
+ local g_id = instance["group_id"]
+ local routes = instance["routes"]
+ local name = instance["name"]
+ local group = group_by_id(g_id)
+ if not(group) then
+ local group = Session:new_route_group(name)
+ for _, v in pairs(routes) do
+ local rt = Session:route_by_id(PBD.ID(v))
+ if rt:isnil() then rt = Session:route_by_name(name) end
+ if not(rt:isnil()) then group:add(rt) end
+ end
+ end
+ end
+
+ if route then
+ local substitution = tonumber(dry_run["destination-"..i])
+ if skip_line or (substitution == 0) then
+ bypass_routes[#bypass_routes + 1] = instance["route_id"]
+ goto nextline
+ end
+
+ local old_order = ARDOUR.ProcessorList()
+ local route_id = instance["route_id"]
+ local r_id = PBD.ID(instance["route_id"])
+ local muted, soloed = instance["muted"], instance["soloed"]
+ local order = instance["order"]
+ local cache = instance["cache"]
+ local group = instance["group"]
+ local group_name = instance["group_name"]
+ local name = instance["route_name"]
+ local gc, tc, pc = instance["gain_control"], instance["trim_control"], instance["pan_control"]
+
+ if not(substitution == instance["route_id"]) then
+ print('SUBSTITUTION FOR: ', name, substitution, Session:route_by_id(PBD.ID(substitution)):name())
+ --bypass_routes[#bypass_routes + 1] = route_id
+ was_subbed = true
+ r_id = PBD.ID(substitution)
+ end
+
+ local rt = Session:route_by_id(r_id)
+ if rt:isnil() then rt = Session:route_by_name(name) end
+ if rt:isnil() then goto nextline end
+
+ local cur_group_id = route_groupid_interrogate(rt)
+ if not(group) and (cur_group_id) then
+ local g = group_by_id(cur_group_id)
+ if g then g:remove(rt) end
+ end
+
+ local rt_group = group_by_name(group_name)
+ if rt_group then rt_group:add(rt) end
+
+ well_known = {'PRE', 'Trim', 'EQ', 'Comp', 'Fader', 'POST'}
+
+ for k, v in pairs(order) do
+ local proc = Session:processor_by_id(PBD.ID(1))
+ if not(was_subbed) then
+ proc = Session:processor_by_id(PBD.ID(v))
+ end
+ if proc:isnil() then
+ for id, name in pairs(cache) do
+ if v == id then
+ proc = new_plugin(name)
+ for _, control in pairs(well_known) do
+ if name == control then
+ proc = get_processor_by_name(rt, control)
+ invalidate[v] = proc:to_stateful():id():to_s()
+ goto nextproc
+ end
+ end
+ if not(proc) then goto nextproc end
+ if not(proc:isnil()) then
+ rt:add_processor_by_index(proc, 0, nil, true)
+ invalidate[v] = proc:to_stateful():id():to_s()
+ end
+ end
+ end
+ end
+ ::nextproc::
+ if proc and not(proc:isnil()) then old_order:push_back(proc) end
+ end
+ rt:reorder_processors(old_order, nil)
+ if muted then rt:mute_control():set_value(1, 1) else rt:mute_control():set_value(0, 1) end
+ if soloed then rt:solo_control():set_value(1, 1) else rt:solo_control():set_value(0, 1) end
+ rt:gain_control():set_value(gc, 1)
+ rt:trim_control():set_value(tc, 1)
+ if pc ~= false then rt:pan_azimuth_control():set_value(pc, 1) end
+ end
+
+ if plugin then
+ if skip_line then goto nextline end
+
+ --if the plugin is owned by a route
+ --we decided not to use, skip it
+ for _, v in pairs(bypass_routes) do
+ if instance["owned_by_route_id"] == v then
+ goto nextline
+ end
+ end
+
+ local enable = {}
+ local params = instance["parameters"]
+ local p_id = instance["plugin_id"]
+ local act = instance["active"]
+
+ for k, v in pairs(invalidate) do --invalidate any deleted plugin's id
+ if p_id == k then
+ p_id = v
+ end
+ end
+
+ local proc = Session:processor_by_id(PBD.ID(p_id))
+ if proc:isnil() then goto nextline end
+ local plug = proc:to_insert():plugin(0)
+
+ for k, v in pairs(params) do
+ local label = plug:parameter_label(k)
+ if string.find(label, "Assign") or string.find(label, "Enable") then --@ToDo: Check Plugin type == LADSPA or VST?
+ enable[k] = v --queue any assignments/enables for after the initial parameter recalling to duck the 'in-on-change' feature
+ end
+ ARDOUR.LuaAPI.set_processor_param(proc, k, v)
+ end
+
+ for k, v in pairs(enable) do
+ ARDOUR.LuaAPI.set_processor_param(proc, k, v)
+ end
+ if act then proc:activate() else proc:deactivate() end
+ end
+
+ ::nextline::
+ i = i + 1
+
+ end
+ end
+
+ function dry_run(debug)
+ --returns a dialog-able table of
+ --everything we do (logically)
+ --in the recall function
+ local route_values = {['----'] = "0"}
+ for r in Session:get_routes():iter() do
+ route_values[r:name()] = r:to_stateful():id():to_s()
+ end
+
+ local i = 0
+ local dry_table = {
+ {type = "label", align = "left", key = "col-0-title" , col = 0, colspan = 1, title = 'Source Settings:'},
+ {type = "label", align = "left", key = "col-0-title" , col = 1, colspan = 1, title = 'Actions:'},
+ {type = "label", align = "left", key = "col-2-title" , col = 2, colspan = 1, title = 'Destination:'},
+ {type = "label", align = "left", key = "col-2-title" , col = 3, colspan = 1, title = 'Do this?'},
+ }
+ local file = io.open(path, "r")
+ assert(file, "File not found!")
+
+ for l in file:lines() do
+ local do_plugin, do_route, do_group = false, false, false
+ local f = load(l)
+
+ if debug then
+ print(i, string.sub(l, 0, 29), f)
+ end
+
+ if f then f() end
+
+ if instance["route_id"] then do_route = true end
+ if instance["plugin_id"] then do_plugin = true end
+ if instance["group_id"] then do_group = true end
+
+ if do_group then
+ local group_id = instance["group_id"]
+ local group_name = instance["name"]
+ local dlg_title, action_title = "", ""
+
+ local group_ptr = group_by_id(group_id)
+
+ if not(group_ptr) then
+ new_group = Session:new_route_group(group_name)
+ dlg_title = string.format("Cannot Find: (Group) %s.", group_name, new_group:name())
+ action_title = "will create and use settings"
+ else
+ dlg_title = string.format("Found by ID: (Group) %s.", group_ptr:name())
+ action_title = "will use group settings"
+ end
+ table.insert(dry_table, {
+ type = "label", align = "left", key = "group-"..i , col = 0, colspan = 1, title = dlg_title
+ })
+ table.insert(dry_table, {
+ type = "label", align = "left", key = "group-"..i , col = 1, colspan = 1, title = action_title
+ })
+ table.insert(dry_table, {
+ type = "checkbox", col=3, colspan = 1, key = "dothis-"..i, default = true, title = "line:"..i
+ })
+ end
+
+ if do_route then
+ local route_id = instance["route_id"]
+ local route_name = instance["route_name"]
+ local dlg_title = ""
+
+ local route_ptr = Session:route_by_id(PBD.ID(route_id))
+
+ if route_ptr:isnil() then
+ route_ptr = Session:route_by_name(route_name)
+ if not(route_ptr:isnil()) then
+ dlg_title = string.format("Found by Name: (Rotue) %s", route_ptr:name())
+ action_title = "will use route settings"
+ else
+ dlg_title = string.format("Cannot Find: (Route) %s", route_name)
+ action_title = "will be ignored"
+ end
+ else
+ dlg_title = string.format("Found by ID: (Route) %s", route_ptr:name())
+ action_title = "will use route settings"
+ end
+ if route_ptr:isnil() then name = route_name else name = route_ptr:name() end
+ table.insert(dry_table, {
+ type = "label", align = "left", key = "route-"..i , col = 0, colspan = 1, title = dlg_title
+ })
+ table.insert(dry_table, {
+ type = "label", align = "left", key = "action-"..i , col = 1, colspan = 1, title = action_title
+ })
+ table.insert(dry_table, {
+ type = "dropdown", align = "left", key = "destination-"..i, col = 2, colspan = 1, title = "", values = route_values, default = name or "----"
+ })
+ table.insert(dry_table, {
+ type = "checkbox", col=3, colspan = 1, key = "dothis-"..i, default = true, title = "line"..i
+ })
+ end
+ i = i + 1
+ end
+ return dry_table
+ end
+
+ local dialog_options = {
+ { type = "label", colspan = 5, title = "" },
+ { type = "radio", col = 1, colspan = 7, key = "select", title = "", values ={ ["Store"] = "store", ["Recall"] = "recall" }, default = "Store"},
+ { type = "label", colspan = 5, title = "" },
+ }
+
+ local store_options = {
+ { type = "label", colspan = 5, title = "" },
+ { type = "checkbox", col=1, colspan = 1, key = "selected", default = false, title = "Selected tracks only"},
+ { type = "entry", col=2, colspan = 10, key = "filename", default = "params", title = "Store name" },
+ { type = "label", colspan = 5, title = "" },
+ }
+
+ local recall_options = {
+ { type = "label", colspan = 5, title = "" },
+ { type = "file", col =1, colspan = 10, key = "file", title = "Select a File", path = ARDOUR.LuaAPI.build_filename(Session:path(), "export", "params.lua") },
+ { type = "label", colspan = 5, title = "" },
+ }
+
+ local rv = LuaDialog.Dialog("Mixer Store:", dialog_options):run()
+
+ if rv then
+ local choice = rv["select"]
+ if choice == "store" then
+ local srv = LuaDialog.Dialog("Mixer Store:", store_options):run()
+ if srv then
+ empty_last_store() --ensures that params.lua will exist for the recall dialog
+ path = ARDOUR.LuaAPI.build_filename(Session:path(), "export", srv["filename"] .. ".lua")
+ mark_tracks(srv['selected'])
+ end
+ end
+
+ if choice == "recall" then
+ local rrv = LuaDialog.Dialog("Mixer Store:", recall_options):run()
+ if rrv then
+ if rrv['file'] ~= path then path = rrv['file'] end
+ --recall(true)
+ local dry_return = LuaDialog.Dialog("Mixer Store:", dry_run(true)):run()
+ if dry_return then recall(true, dry_return) end
+ end
+ end
+ end
+collectgarbage()
+end end
diff --git a/share/scripts/synth1.lua b/share/scripts/synth1.lua
new file mode 100644
index 0000000000..de68c58e46
--- /dev/null
+++ b/share/scripts/synth1.lua
@@ -0,0 +1,100 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Simple Synth",
+ category = "Instrument",
+ license = "MIT",
+ author = "Ardour Lua Task Force",
+ description = [[An Example Synth for Prototyping.]]
+}
+
+function dsp_ioconfig ()
+ return
+ {
+ -- { midi_in = 1, audio_in = 0, audio_out = -1}, -- any number of channels
+ -- { midi_in = 1, audio_in = 0, audio_out = 1}, -- values > 0, precisely N channels
+ { midi_in = 1, audio_in = 0, audio_out = 2}, -- values > 0, precisely N channels
+ { midi_in = 1, audio_in = 0, audio_out = 4}, -- values > 0, precisely N channels
+ { midi_in = 1, audio_in = 0, audio_out = 8}, -- values > 0, precisely N channels
+ -- { midi_in = 1, audio_in = 0, audio_out = -6}, -- values < -2, up to -N channels, here 1,..,6
+ }
+end
+
+local note_table = {}
+local active_notes = {}
+local phases = {}
+local env = .01;
+
+function dsp_init (rate)
+ for n = 1,128 do
+ note_table [n] = (440 / 32) * 2^((n - 10.0) / 12.0) / rate
+ end
+ env = 100 / rate
+end
+
+function dsp_run (ins, outs, n_samples)
+ -- initialize output buffer
+ local a = {}
+ for s = 1, n_samples do a[s] = 0 end
+
+
+ -- very basic synth, simple sine, basic envelope
+ local function synth (s_start, s_end)
+ for n,v in pairs (active_notes) do
+ local vel = v["vel"] or 0
+ local tgt = v["tvel"];
+ for s = s_start,s_end do
+ local phase = phases[n] or 0
+ vel = vel + env * (tgt - vel)
+ a[s] = a[s] + math.sin(6.283185307 * phase) * vel / 167
+ phase = phase + note_table[n]
+ if (phase > 1.0) then
+ phases[n] = phase - 2.0
+ else
+ phases[n] = phase
+ end
+ end
+ if vel < 1 and tgt == 0 then
+ active_notes[n] = nil
+ else
+ active_notes[n]["vel"] = vel;
+ end
+ end
+ end
+
+ local tme = 1
+ -- parse midi messages
+ assert (type(midiin) == "table") -- global table of midi events (for now)
+ for _,b in pairs (midiin) do
+ local t = b["time"] -- t = [ 1 .. n_samples ]
+
+ -- synth sound until event
+ synth(tme, t)
+ tme = t + 1
+
+ local d = b["data"] -- get midi-event
+ -- we ignore the midi channel
+ if (#d == 3 and (d[1] & 240) == 144) then -- note on
+ local n = 1 + d[2];
+ active_notes[n] = active_notes[n] or {}
+ active_notes[n]["tvel"] = d[3]
+ end
+ if (#d == 3 and (d[1] & 240) == 128) then -- note off
+ local n = 1 + d[2];
+ active_notes[n] = active_notes[n] or {}
+ active_notes[n]["tvel"] = 0
+ end
+ if (#d == 3 and (d[1] & 240) == 176) then -- CC
+ if (d[2] == 120 or d[2] == 123) then -- panic
+ active_notes = {}
+ end
+ end
+ end
+
+ -- synth rest of cycle
+ synth(tme, n_samples)
+
+ -- copy
+ for c = 1,#outs do
+ outs[c]:set_table(a, n_samples)
+ end
+end
diff --git a/share/scripts/tomsloop.lua b/share/scripts/tomsloop.lua
new file mode 100644
index 0000000000..d397ab9e60
--- /dev/null
+++ b/share/scripts/tomsloop.lua
@@ -0,0 +1,315 @@
+ardour { ["type"] = "EditorAction", name = "Tom's Loop",
+ license = "MIT",
+ author = "Ardour Team",
+ description = [[Bounce the loop-range of all non muted audio tracks, paste at playhead]]
+}
+
+-- main method, every custom (i.e. non-ardour) method must be defined *inside* factory()
+function factory (params) return function ()
+
+-- when this script is called as an action, the output will be printed to the ardour log window
+ function print_help()
+ printf("See source for help.")
+---[[
+ print("")
+ print("---------------------------------------------------------------------")
+ print("")
+ print("Manual for \"Tom’s Loop\" Ardour Lua Script")
+ print("")
+ print("---------------------------------------------------------------------")
+ print("---------------------------------------------------------------------")
+ print("")
+ print("Table of Contents")
+ print("")
+ print("1. The first test")
+ print("2. Using mute and solo")
+ print("3. Combining region clouds to a defined length")
+ print("")
+ print("Abstract: This script for Ardour (>=4.7 git) operates on the time")
+ print("line. It allows to copy and combine specific portions within the loop")
+ print("range to a later point on the time line with one single action")
+ print("command. Everything that can be heard within the loop range is")
+ print("considered for this process, namely non-muted regions on non-muted or")
+ print("soloed tracks that are fully or partially inside the loop range. This")
+ print("still sounds a bit abstract and will be more obvious with the")
+ print("following example cases of use.")
+ print("")
+ print("For convenience, it’s recommended to bind the script to a keyboard")
+ print("shortcut in order to quickly and easily access the \"Tom’s Loop\"")
+ print("scripted action.")
+ print("")
+ print("-Open dialog \"Script Manager\" via menu Edit/Scripted Actions/Script")
+ print("Manager")
+ print("")
+ print("-In tab \"Action Scripts\", select a line and press button \"Add/Set\"")
+ print("")
+ print("-In dialog \"Add Lua Action\", select \"Tom’s Loop\" from the drop down")
+ print("menu and hit \"Add\"")
+ print("")
+ print("-In dialog \"Set Script Parameter\" just hit \"Add\" again")
+ print("")
+ print("-Close dialog \"Script Manager\"")
+ print("")
+ print("-Open dialog \"Bindings Editor\" via menu Window/Bindings Editor")
+ print("")
+ print("-In tab \"Editor\", expand \"Editor\", look for entry \"Tom’s loop\",")
+ print("select it")
+ print("")
+ print("-Hit the keyboard shortcut to assign to this scripted action")
+ print("")
+ print("-Close dialog \"Key Bindings\"")
+ print("")
+ print("An alternative way to quickly access a scripted action is to enable")
+ print("\"Action Script Button Visibility\" in \"Preferences/GUI\".")
+ print("")
+ print("---------------------------------------------------------------------")
+ print("")
+ print("1. The first test")
+ print("")
+ print("---------------------------------------------------------------------")
+ print("")
+ print("-Record a short sequence of audio input or import a wave file to a")
+ print("track to get a region")
+ print("")
+ print("-Set a loop range inside that one region")
+ print("")
+ print("-Place the playhead after the loop range, possibly after the region,")
+ print("non-rolling")
+ print("")
+ print(" _L====L_ V")
+ print(" .____|____|____________. |")
+ print(" |R1__|_x__|____________| |")
+ print("")
+ print("-Call \"Tom’s Loop\" via the previously created shortcut")
+ print("")
+ print("This results in a new region created at the playhead, with the length")
+ print("of the loop range, containing audio of the original region. The")
+ print("playhead moved to the end of this new region so that subsequent calls")
+ print("to \"Tom’s Loop\" will result in a gap less series of regions.")
+ print("")
+ print(" _L====L_ --> V")
+ print(" .____|____|____________. .____|")
+ print(" |R1__|_x__|____________| |_x__|")
+ print("")
+ print("-Repeat calling \"Tom’s Loop\"")
+ print("")
+ print("This creates multiple copies of the loop range to line up one after")
+ print("each other.")
+ print("")
+ print(" _L====L_ --> V")
+ print(" .____|____|____________. .______________|")
+ print(" |R1__|_x__|____________| |_x__|_x__|_x__|")
+ print("")
+ print("-Set a different loop range and call \"Tom’s Loops\" again")
+ print("")
+ print("This will create a new region with the length of the new loop range")
+ print("at the playhead.")
+ print("")
+ print(" _L=======L_ --> V")
+ print(" ._______|_______|______. .______________________|")
+ print(" |R1_____|_X_____|______| |_x__|_x__|_x__|_X_____|")
+ print("")
+ print("By using \"Tom’s Loop\", the loop range - which can be easily set with")
+ print("the handles - and the playhead it’s easy to create any sequence of")
+ print("existing regions on the time line. This can be useful during the")
+ print("arrangement phase where macro parts of the session are already")
+ print("temporally layed out (in the loop) but not part of the final")
+ print("arrangement yet. The process is non-destructive in a sense that the")
+ print("existing regions layout in the current loop range won’t be touched or")
+ print("replaced. The newly created regions are immediately visible on the")
+ print("time line at the playhead position.")
+ print("")
+ print("")
+ print("---------------------------------------------------------------------")
+ print("")
+ print("2. Using mute and solo")
+ print("")
+ print("---------------------------------------------------------------------")
+ print("")
+ print("Creating a sequence of regions like described above respects the")
+ print("current mute and solo state of a track. Variations of the loop are")
+ print("thus easy to create, further supporting the arrangement process.")
+ print("")
+ print(" _L====L_ --> V")
+ print(" .____|____|____________. ._________. |")
+ print(" |R1__|_x__|____________| |_x__|_x__| |")
+ print(" .__|R2|_y__|________|_. |_y__|_________|")
+ print(" |R3___|_z__|__________| |_z__|_z__|")
+ print("")
+ print("")
+ print("---------------------------------------------------------------------")
+ print("")
+ print("3. Combining region clouds to a defined length")
+ print("")
+ print("---------------------------------------------------------------------")
+ print("")
+ print("Multiple small regions say on a percussive track can be simplified")
+ print("for later arrangement keeping the temporal relations by combining")
+ print("them. Using \"Tom’s Loop\", the resulting regions will not only combine")
+ print("the regions but also automatically extend or shrink the new regions")
+ print("start and end point so that it is exactly of the wished length equal")
+ print("to the loop range.")
+ print("")
+ print("_L======================L_ --> V")
+ print(" | .____ .___. _____|_______. .______________________|")
+ print(" | |R1_| |R2_| |R3__|_______| |______________________|")
+ print("")
+ print("See also: Lua Action Bounce+Replace Regions")
+ print("")
+ print("")
+--]]
+ end -- print_help()
+
+ local n_paste = 1
+ assert (n_paste > 0)
+
+ local proc = ARDOUR.LuaAPI.nil_proc () -- bounce w/o processing
+ local itt = ARDOUR.InterThreadInfo () -- bounce progress info (unused)
+
+ local loop = Session:locations ():auto_loop_location ()
+ local playhead = Session:transport_sample ()
+
+ -- make sure we have a loop, and the playhead (edit point) is after it
+ if not loop then
+ print_help();
+ print ("Error: A Loop range must be set.")
+ goto errorout
+ end
+ assert (loop:start () < loop:_end ())
+ if loop:_end () > playhead then
+ print_help();
+ print ("Error: The Playhead (paste point) needs to be after the loop.")
+ goto errorout
+ end
+
+ -- prepare undo operation
+ Session:begin_reversible_command ("Tom's Loop")
+ local add_undo = false -- keep track if something has changed
+
+ -- prefer solo'ed tracks
+ local soloed_track_found = false
+ for route in Session:get_tracks ():iter () do
+ if route:soloed () then
+ soloed_track_found = true
+ break
+ end
+ end
+
+ -- count regions that are bounced
+ local n_regions_created = 0
+
+ -- loop over all tracks in the session
+ for route in Session:get_tracks ():iter () do
+ if soloed_track_found then
+ -- skip not soloed tracks
+ if not route:soloed () then
+ goto continue
+ end
+ end
+
+ -- skip muted tracks (also applies to soloed + muted)
+ if route:muted () then
+ goto continue
+ end
+
+ -- at this point the track is either soloed (if at least one track is soloed)
+ -- or not muted (if no track is soloed)
+
+ -- test if bouncing is possible
+ local track = route:to_track ()
+ if not track:bounceable (proc, false) then
+ goto continue
+ end
+
+ -- only audio tracks
+ local playlist = track:playlist ()
+ if playlist:data_type ():to_string () ~= "audio" then
+ goto continue
+ end
+
+ -- check if there is at least one unmuted region in the loop-range
+ local reg_unmuted_count = 0
+ for reg in playlist:regions_touched (loop:start (), loop:_end ()):iter () do
+ if not reg:muted() then
+ reg_unmuted_count = reg_unmuted_count + 1
+ end
+ end
+
+ if reg_unmuted_count < 1 then
+ goto continue
+ end
+
+ -- clear existing changes, prepare "diff" of state for undo
+ playlist:to_stateful ():clear_changes ()
+
+ -- do the actual work
+ local region = track:bounce_range (loop:start (), loop:_end (), itt, proc, false)
+ playlist:add_region (region, playhead, n_paste, false, 0, 0, false)
+
+ n_regions_created = n_regions_created + 1
+
+ -- 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
+
+ ::continue::
+ end -- for all routes
+
+ --advance playhead so it's just after the newly added regions
+ if n_regions_created > 0 then
+ Session:request_locate (playhead + loop:length() * n_paste, ARDOUR.LocateTransportDisposition.MustStop, ARDOUR.TransportRequestSource.TRS_UI)
+ end
+
+ -- 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
+
+ print ("bounced " .. n_regions_created .. " regions from loop range (" .. loop:length() .. " samples) to playhead @ sample # " .. playhead)
+ ::errorout::
+end -- end of anonymous action script function
+end -- end of script factory
+
+
+function icon (params) return function (ctx, width, height)
+ local x = width * .5
+ local y = height * .5
+ local r = math.min (x, y)
+
+ ctx:set_line_width (1)
+
+ function stroke_outline ()
+ ctx:set_source_rgba (0, 0, 0, 1)
+ ctx:stroke_preserve ()
+ ctx:set_source_rgba (1, 1, 1, 1)
+ ctx:fill ()
+ end
+
+ ctx:rectangle (x - r * .6, y - r * .05, r * .6, r * .3)
+ stroke_outline ()
+
+ ctx:arc (x, y, r * .61, math.pi, 0.2 * math.pi)
+ ctx:arc_negative (x, y, r * .35, 0.2 * math.pi, math.pi);
+ stroke_outline ()
+
+ function arc_arrow (rad, ang)
+ return x - rad * math.sin (ang * 2.0 * math.pi), y - rad * math.cos (ang * 2.0 * math.pi)
+ end
+
+ ctx:move_to (arc_arrow (r * .36, .72))
+ ctx:line_to (arc_arrow (r * .17, .72))
+ ctx:line_to (arc_arrow (r * .56, .60))
+ ctx:line_to (arc_arrow (r * .75, .72))
+ ctx:line_to (arc_arrow (r * .62, .72))
+
+ ctx:set_source_rgba (0, 0, 0, 1)
+ ctx:stroke_preserve ()
+ ctx:close_path ()
+ ctx:set_source_rgba (1, 1, 1, 1)
+ ctx:fill ()
+end end
diff --git a/share/scripts/track_organizer.lua b/share/scripts/track_organizer.lua
new file mode 100644
index 0000000000..15caf2c9ff
--- /dev/null
+++ b/share/scripts/track_organizer.lua
@@ -0,0 +1,100 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Track Organizer",
+ author = "Mixbus Lua Taskforce",
+ description = [[Easily modifiable session overview and track property editor]]
+}
+
+function factory () return function ()
+
+ local rbow = { ["----"] = "", ["Red"] = 0xD10000FF, ["Orange"] = 0xFF6622FF, ["Yellow"] = 0xFFDA21FF,
+ ["Green"] = 0x33DD00FF, ["Blue"] = 0x1133CCFF, ["Indigo"] = 0x220066FF, ["Violet"] = 0x330044FF
+}
+
+ --now starting to build our dialog
+ local dialog_options = {
+ { type = "label", colspan="4", title = "Change your Track settings here:" },
+ { type = "heading", title = "Track", col = 0, colspan = 1 },
+ { type = "heading", title = "Group", col = 1, colspan = 1 },
+ { type = "heading", title = "Comment", col = 2, colspan = 1 },
+ { type = "heading", title = "Color", col = 3, colspan = 1 },
+ }
+
+ --group option payload
+ --@ToDo: Add 'fake' groups for people to select, create them if they want it
+ local pl = {["----"] = "", ["Drums"] = "Drums", ["Bass"] = "Bass", ["Guitars"] = "Guitars",
+ ["Keys"] = "Keys", ["Strings"] = "Strings", ["Vox"] = "Vox"
+}
+
+ for g in Session:route_groups():iter() do
+ pl[g:name()] = g
+ end
+
+ --helper function to find default group option
+ function interrogate(t)
+ local v = "----"
+ for g in Session:route_groups():iter() do
+ for r in g:route_list():iter() do
+ if r:name() == t:name() then v = g:name() end
+ end
+ end return v
+ end
+
+ function find_color(t)
+ local c = "----"
+ for k, v in pairs(rbow) do
+ if(t:presentation_info_ptr():color() == v) then c = k end
+ end return c
+ end
+
+ --insert an entry into our dialog_options table for each track with appropriate info
+ for t in Session:get_tracks():iter() do
+ table.insert(dialog_options, {
+ type = "entry", key = t:name() .. ' n', col = 0, colspan = 1, default = t:name(), title = "" --@ToDo: Shorten Names so they can still see what track they're changing?
+ }) --name
+ table.insert(dialog_options, {
+ type = "dropdown", key = t:name() .. ' g', col = 1, colspan = 1, title = "", values = pl, default = interrogate(t)
+ }) --group
+ table.insert(dialog_options, {
+ type = "entry", key = t:name() .. ' cm', col = 2, colspan = 1, default = t:comment(), title = ""
+ }) --comment
+ table.insert(dialog_options, {
+ type = "dropdown", key = t:name() .. ' c', col = 3, colspan = 1, title = "", values = rbow, default = find_color(t)
+ }) --color
+ end
+
+ --run dialog_options
+ local rv = LuaDialog.Dialog("Track Organizer", dialog_options):run()
+ if not(rv) then goto script_end end
+ assert(rv, 'Dialog box was cancelled or is ' .. type(rv))
+
+ --begin track operation
+ for t in Session:get_tracks():iter() do
+ local cgrp = interrogate(t)
+ local name = rv[t:name() .. ' n' ]
+ local ngrp = rv[t:name() .. ' g' ]
+ local cmnt = rv[t:name() .. ' cm']
+ local colr = rv[t:name() .. ' c' ]
+
+ if t:name() ~= name then t:set_name(name) end
+
+ if t:comment() ~= cmnt then t:set_comment(cmnt, nil) end
+
+ if not(colr == "") then t:presentation_info_ptr():set_color(colr) end
+
+ if type(ngrp) == "userdata" then
+ if cgrp ~= ngrp:name() then
+ ngrp:add(t)
+ end
+ end
+
+ if type(ngrp) == "string" and not(ngrp == "") then
+ ngrp = Session:new_route_group(ngrp)
+ if cgrp ~= ngrp:name() then
+ ngrp:add(t)
+ end
+ end
+ end
+ ::script_end::
+ collectgarbage()
+end end
diff --git a/share/scripts/transparent_regions.lua b/share/scripts/transparent_regions.lua
new file mode 100644
index 0000000000..7db81fb7af
--- /dev/null
+++ b/share/scripts/transparent_regions.lua
@@ -0,0 +1,28 @@
+ardour {
+ ["type"] = "EditorHook",
+ name = "Make all Regions Transparent",
+ author = "Ardour Lua Task Force",
+ description = "While the transport is looping, all regions become transparent.",
+}
+
+function signals ()
+ return LuaSignal.Set():add (
+ {
+ [LuaSignal.TransportStateChange] = true,
+ [LuaSignal.TransportLooped] = true,
+ }
+ )
+end
+
+function factory ()
+ return function (signal, ref, ...)
+ local all_regions = ARDOUR.RegionFactory.regions()
+ for _, r in all_regions:iter() do
+ local ar = r:to_audioregion ();
+ if ar:isnil () then goto next end
+ if ar:opaque () then
+ ar:set_opaque (false)
+ end
+ ::next::
+ end
+end end \ No newline at end of file
diff --git a/share/scripts/vamp_audio_to_midi.lua b/share/scripts/vamp_audio_to_midi.lua
new file mode 100644
index 0000000000..2f9101196c
--- /dev/null
+++ b/share/scripts/vamp_audio_to_midi.lua
@@ -0,0 +1,112 @@
+ardour {
+ ["type"] = "EditorAction",
+ name = "Polyphonic Audio to MIDI",
+ license = "MIT",
+ author = "Ardour Team",
+description = [[
+Analyze audio from the selected audio region to a selected MIDI region.
+
+A MIDI region on the target track will have to be created first (use the pen tool).
+
+This script uses the Polyphonic Transcription VAMP plugin from Queen Mary Univ, London.
+The plugin works best at 44.1KHz input sample rate, and is tuned for piano and guitar music. Velocity is not estimated.
+]]
+}
+
+function factory () return function ()
+ local sel = Editor:get_selection ()
+ local sr = Session:nominal_sample_rate ()
+ local tm = Session:tempo_map ()
+ local vamp = ARDOUR.LuaAPI.Vamp ("libardourvampplugins:qm-transcription", sr)
+ local midi_region = nil
+ local audio_regions = {}
+ local start_time = Session:current_end_sample ()
+ local end_time = Session:current_start_sample ()
+ local max_pos = 0
+ local cur_pos = 0
+ for r in sel.regions:regionlist ():iter () do
+ if r:to_midiregion():isnil() then
+ local st = r:position()
+ local ln = r:length()
+ local et = st + ln
+ if st < start_time then
+ start_time = st
+ end
+ if et > end_time then
+ end_time = et
+ end
+ table.insert(audio_regions, r)
+ max_pos = max_pos + r:to_readable ():readable_length ()
+ else
+ midi_region = r:to_midiregion()
+ end
+ end
+
+ if #audio_regions == 0 then
+ LuaDialog.Message ("Polyphonic Audio to MIDI", "No source audio region(s) selected.\nAt least one audio-region to be analyzed need to be selected.", LuaDialog.MessageType.Error, LuaDialog.ButtonType.Close):run ()
+ return
+ end
+ if not midi_region then
+ LuaDialog.Message ("Polyphonic Audio to MIDI", "No target MIDI region selected.\nA MIDI region, ideally empty, and extending beyond the selected audio-region(s) needs to be selected.", LuaDialog.MessageType.Error, LuaDialog.ButtonType.Close):run ()
+ return
+ end
+
+ midi_region:set_initial_position(start_time)
+ midi_region:set_length(end_time - start_time, 0)
+
+ local pdialog = LuaDialog.ProgressWindow ("Audio to MIDI", true)
+ function progress (_, pos)
+ return pdialog:progress ((cur_pos + pos) / max_pos, "Analyzing")
+ end
+
+ for i,ar in pairs(audio_regions) do
+ local a_off = ar:position ()
+ local b_off = midi_region:quarter_note () - midi_region:start_beats ()
+
+ vamp:analyze (ar:to_readable (), 0, progress)
+
+ if pdialog:canceled () then
+ goto out
+ end
+
+ cur_pos = cur_pos + ar:to_readable ():readable_length ()
+ pdialog:progress (cur_pos / max_pos, "Generating MIDI")
+
+ local fl = vamp:plugin ():getRemainingFeatures ():at (0)
+ if fl and fl:size() > 0 then
+ local mm = midi_region:midi_source(0):model()
+ local midi_command = mm:new_note_diff_command ("Audio2Midi")
+ for f in fl:iter () do
+ local ft = Vamp.RealTime.realTime2Frame (f.timestamp, sr)
+ local fd = Vamp.RealTime.realTime2Frame (f.duration, sr)
+ local fn = f.values:at (0)
+
+ local bs = tm:exact_qn_at_sample (a_off + ft, 0)
+ local be = tm:exact_qn_at_sample (a_off + ft + fd, 0)
+
+ local pos = Evoral.Beats (bs - b_off)
+ local len = Evoral.Beats (be - bs)
+ local note = ARDOUR.LuaAPI.new_noteptr (1, pos, len, fn + 1, 0x7f)
+ midi_command:add (note)
+ end
+ mm:apply_command (Session, midi_command)
+ end
+ -- reset the plugin (prepare for next iteration)
+ vamp:reset ()
+ end
+
+ ::out::
+ pdialog:done ();
+ pdialog = nil
+ vamp = nil;
+ collectgarbage ()
+end end
+
+function icon (params) return function (ctx, width, height, fg)
+ local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil(width * .7) .. "px")
+ txt:set_text ("\u{2669}") -- quarter note symbol UTF8
+ local tw, th = txt:get_pixel_size ()
+ ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
+ ctx:move_to (.5 * (width - tw), .5 * (height - th))
+ txt:show_in_cairo_context (ctx)
+end end
diff --git a/share/scripts/voice_activate.lua b/share/scripts/voice_activate.lua
new file mode 100644
index 0000000000..ef4380b9bb
--- /dev/null
+++ b/share/scripts/voice_activate.lua
@@ -0,0 +1,53 @@
+ardour {
+ ["type"] = "dsp",
+ name = "Voice/Level Activate",
+ category = "Utility",
+ author = "Ardour Lua Task Force",
+ license = "MIT",
+ description = [[Roll the transport when the signal level on the plugin's input exceeds a given threshold.]]
+}
+
+function dsp_ioconfig ()
+ return
+ {
+ -- support all in/out as long as input port count equals output port count
+ { audio_in = -1, audio_out = -1},
+ }
+end
+
+function dsp_params ()
+ return
+ {
+ { ["type"] = "input", name = "Threshold", min = -20, max = 0, default = -6, doc = "Threshold in dBFS for all channels" },
+ { ["type"] = "output", name = "Level", min = -120, max = 0 },
+ }
+end
+
+function dsp_configure (ins, outs)
+ n_channels = ins:n_audio()
+end
+
+function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
+ local ctrl = CtrlPorts:array() -- get control port array (read/write)
+
+ if Session:transport_rolling() then
+ -- don't do anything if the transport is already rolling
+ ctrl[2] = -math.huge -- set control output port value
+ return
+ end
+
+ local threshold = 10 ^ (.05 * ctrl[1]) -- dBFS to coefficient
+ local level = -math.huge
+
+ for c = 1,n_channels do
+ local b = in_map:get(ARDOUR.DataType("audio"), c - 1) -- get id of audio-buffer for the given channel
+ if b ~= ARDOUR.ChanMapping.Invalid then -- check if channel is mapped
+ local a = ARDOUR.DSP.compute_peak(bufs:get_audio(b):data(offset), n_samples, 0) -- compute digital peak
+ if a > threshold then
+ Session:request_transport_speed (1.0, true, ARDOUR.TransportRequestSource.TRS_UI)
+ end
+ if a > level then level = a end -- max level of all channels
+ end
+ end
+ ctrl[2] = ARDOUR.DSP.accurate_coefficient_to_dB (level) -- set control output port value
+end
diff --git a/share/scripts/wscript b/share/scripts/wscript
new file mode 100644
index 0000000000..a820a1fc13
--- /dev/null
+++ b/share/scripts/wscript
@@ -0,0 +1,16 @@
+#!/usr/bin/python
+
+import os
+
+top = '.'
+out = 'build'
+
+def configure(conf):
+ pass
+
+def build(bld):
+ scripts = bld.path.ant_glob ('*.lua', excl=['^_*'])
+ bld.install_files (os.path.join(bld.env['DATADIR'], 'scripts'), scripts)
+
+def options(opt):
+ pass