Starting with version 4.7.213, Ardour supports Lua scripts.

This Documentation is Work in Progress and far from complete. Also the documented API may be subject to change.

Preface

There are cases that Ardour cannot reasonably cater to with core functionality alone, either because they're session specific or user specific edge cases.

Examples for these include voice-activate (record-arm specific tracks and roll transport depending on signal levels), rename all regions after a specific timecode, launch an external application when a certain track is soloed, generate automation curves or simply provide a quick shortcut for a custom batch operation.

Cases like this call for means to extend the DAW without actually changing the DAW itself. This is where scripting comes in.

"Scripting" refers to tasks that could alternatively be executed step-by-step by a human operator.

Lua is a tiny and simple language which is easy to learn, yet allows for comprehensive solutions. Lua is also a glue language it allows to tie existing component in Ardour together in unprecedented ways, and most importantly Lua is one of the few scripting-languages which can be safely used in a real-time environment.

A good introduction to Lua is the book Programming in Lua. The first edition is available online, but if you have the means buy a copy of the book, it not only helps to support the Lua project, but provides for a much nicer reading and learning experience.

Overview

The core of Ardour is a real-time audio engine that runs and processes audio. One interfaces with an engine by sending it commands. Scripting can be used to interact with or modify the active Ardour session, just like a user uses the Editor/Mixer GUI to modify the state or parameters of the session.

Doing this programmatically requires some knowledge about the objects used internally. Most Ardour C++ objects and their methods are directly exposed to Lua and one can call functions or modify variables:

C++
session->set_transport_speed (1.0);
Lua
Session:set_transport_speed (1.0)

You may notice that there is only a small syntactic difference in this case. While C++ requires recompiling the application for every change, Lua script can be loaded, written or modified while the application is running. Lua also abstracts away many of the C++ complexities such as object lifetime, type conversion and null-pointer checks.

Close ties with the underlying C++ components is where the power of scripting comes from. A script can orchestrate interaction of lower-level components which take the bulk of the CPU time of the final program.

At the time of writing Ardour integrates Lua 5.3.2: Lua 5.3 reference manual.

Integration

Like Control surfaces and the GUI, Lua Scripts are confined to certain aspects of the program. Ardour provides the framework and runs Lua (not the other way around).

In Ardour's case Lua is available:

Editor Action ScriptsUser initiated actions (menu, shortcuts) for batch processing
Editor Hooks/CallbacksEvent triggered actions for the Editor/Mixer GUI
Session ScriptsScripts called at the start of every audio cycle (session, real-time)
DSP ScriptsAudio/Midi processor - plugins with access to the Ardour session (per track/bus, real-time)
Script ConsoleAction Script commandline

There are is also a special mode:

Commandline ToolReplaces the complete Editor GUI, direct access to libardour (no GUI) from the commandline.
Be aware that the vast majority of complex functionality is provided by the Editor UI.

Managing Scripts

Ardour searches for Lua scripts in the scripts folder in $ARDOUR_DATA_PATH, Apart from scripts included directly with Ardour, this includes

GNU/Linux$HOME/.config/ardour5/scripts
Mac OS X$HOME/Library/Preferences/Ardour5/scripts
Windows%localappdata%\ardour5\scripts

Files must end with .lua file extension.

Scripts are managed via the GUI

Editor Action ScriptsMenu → Edit → Scripted Actions → Manage
Editor Hooks/CallbacksMenu → Edit → Scripted Actions → Manage
Session ScriptsMenu → Session → Scripting → Add/Remove Script
DSP ScriptsMixer-strip → context menu (right click) → New Lua Proc
Script ConsoleMenu → Window → Scripting

Script Layout

A minimal example script looks like:


	ardour {
	  ["type"]    = "EditorAction",
	  name        = "Rewind",
	}

	function factory (unused_params)
	  return function ()
	   Session:goto_start()  -- rewind the transport
	  end
	end

The common part for all scripts is the "Descriptor". It's a Lua function which returns a table (key/values) with the following keys (the keys are case-sensitive):

type [required]one of "DSP", "Session", "EditorHook", "EditorAction" (the type is not case-sensitive)
name [required]Name/Title of the script
authorYour Name
licenseThe license of the script (e.g. "GPL" or "MIT")
descriptionA longer text explaining to the user what the script does

Scripts that come with Ardour (currently mostly examples) can be found in the Source Tree.

Action Scripts

Action scripts are the simplest form. An anonymous Lua function is called whenever the action is triggered. A simple action script is shown above.

There are 10 action script slots available, each of which is a standard GUI action available from the menu and hence can be bound to a keyboard shortcut.

Session Scripts

Session scripts similar to Actions Scripts, except the anonymous function is called periodically every process cycle. The function receives a single parameter - the number of audio samples which are processed in the given cycle


ardour {
  ["type"]    = "session",
  name        = "Example Session Script",
  description = [[
  An Example Ardour Session Script.
  This example stops the transport after rolling for a specific time.]]
}


-- instantiation options, these are passed to the "factory" method below
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:frame_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:frame_rate()) then
      Session:request_transport_speed(0.0, true)
    end
  end
end

Action Hooks

Action hook scripts must define an additional function which returns a Set of Signal that which trigger the callback (documenting available slots and their parameters remains to be done).


ardour {
  ["type"]    = "EditorHook",
  name        = "Hook Example",
  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
    }
  )
  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:containsFramePos (ARDOUR.Properties.Start)),
        tostring (pch:containsFramePos (ARDOUR.Properties.Length))
        ))
      io.close (file)
    end
  end
end

DSP Scripts

See the scripts folder for examples for now.

Some notes for further doc:

Accessing Ardour Objects

The top most object in Ardour is the ARDOUR::Session. Fundamentally, a Session is just a collection of other things: Routes (tracks, busses), Sources (Audio/Midi), Regions, Playlists, Locations, Tempo map, Undo/Redo history, Ports, Transport state and controls, etc.

Every Lua interpreter can access it via the global variable Session.

GUI context interpreters also have an additional object in the global environment: The Ardour Editor. The Editor provides access to high level functionality which is otherwise triggered via GUI interaction such as undo/redo, open/close windows, select objects, drag/move regions. It also holds the current UI state: snap-mode, zoom-range, etc. The Editor also provides complex operations such as "import audio" which under the hood, creates a new Track, adds a new Source Objects (for every channel) with optional resampling, creates both playlist and regions and loads the region onto the Track all the while displaying a progress information to the user.

Documenting the bound C++ methods and class hierarchy is somewhere on the ToDo list. Meanwhile luabindings.cc is the best we can offer.

Concepts

Ardour is a highly multithreaded application and interaction between the different threads, particularly real-time threads, needs to to be done with care. This part has been abstracted away by providing separate Lua interpreters in different contexts and restricting available interaction:

The available interfaces differ between contexts. For example, it is not possible to create new tracks or import audio from real-time context; while it is not possible to modify audio buffers from the GUI thread.

Current State

Fully functional, yet still in a prototyping stage:

Examples

Apart from the scripts included with the source-code here are a few examples without further comments...

Editor Console Examples


print (Session:route_by_remote_id(1):name())

a = Session:route_by_remote_id(1);
print (a:name());

print(Session:get_tracks():size())

for i, v in ipairs(Session:unknown_processors():table()) do print(v) end
for i, v in ipairs(Session:get_tracks():table()) do print(v:name()) end

for t in Session:get_tracks():iter() do print(t:name()) end
for r in Session:get_routes():iter() do print(r:name()) end


Session:tempo_map():add_tempo(ARDOUR.Tempo(100,4), Timecode.BBT_TIME(4,1,0))


Editor:set_zoom_focus(Editing.ZoomFocusRight)
print(Editing.ZoomFocusRight);
Editor:set_zoom_focus(1)


files = C.StringVector();
files:push_back("/home/rgareus/data/coding/ltc-tools/smpte.wav")
pos = -1
Editor:do_import(files, Editing.ImportDistinctFiles, Editing.ImportAsTrack, ARDOUR.SrcQuality.SrcBest, pos, ARDOUR.PluginInfo())

#or in one line:
Editor:do_import(C.StringVector():add({"/path/to/file.wav"}), Editing.ImportDistinctFiles, Editing.ImportAsTrack, ARDOUR.SrcQuality.SrcBest, -1, ARDOUR.PluginInfo())

# called when a new session is loaded:
function new_session (name) print("NEW SESSION:", name) end


# read/set/describe a plugin parameter
route = Session:route_by_remote_id(1)
processor = route:nth_plugin(0)
plugininsert = processor:to_insert()

plugin = plugininsert:plugin(0)
print (plugin:label())
print (plugin:parameter_count())

x = ARDOUR.ParameterDescriptor ()
_, t = plugin:get_parameter_descriptor(2, x) -- port #2
paramdesc = t[2]
print (paramdesc.lower)

ctrl = Evoral.Parameter(ARDOUR.AutomationType.PluginAutomation, 0, 2)
ac = plugininsert:automation_control(ctrl, false)
print (ac:get_value ())
ac:set_value(1.0, PBD.GroupControlDisposition.NoGroup)

# the same using a convenience wrapper:
route = Session:route_by_remote_id(1)
proc = t:nth_plugin (i)
ARDOUR.LuaAPI.set_processor_param (proc, 2, 1.0)

Commandline Session

The standalone tool luasession allows one to access an Ardour session directly from the commandline. Interaction is limited by the fact that most actions in Ardour are provided by the Editor GUI.

luasession provides only two special functions load_session and close_session and exposes the AudioEngine instance as global variable.


for i,_ in AudioEngine:available_backends():iter() do print (i.name) end

backend = AudioEngine:set_backend("ALSA", "", "")
print (AudioEngine:current_backend_name())

for i,_ in backend:enumerate_devices():iter() do print (i.name) end

backend:set_input_device_name("HDA Intel PCH")
backend:set_output_device_name("HDA Intel PCH")

print (backend:buffer_size())
print (AudioEngine:get_last_backend_error())

s = load_session ("/home/rgareus/Documents/ArdourSessions/lua2/", "lua2")
s:request_transport_speed (1.0)
print (s:transport_rolling())
s:goto_start()
close_session()