-
-
Notifications
You must be signed in to change notification settings - Fork 20
ReaLearn Presets
This page shows some ReaLearn main and controller presets that are not related to particular controllers. Feel free to add your own!
This preset exposes each note on a MIDI keyboard as a numbered virtual control elements. Each controller mapping is named like the corresponding note (for example "C#2"). That makes it easier to identify in the main compartment which note triggers which action.
You can use this preset whenever you want to map MIDI keyboard notes to targets of your choice.
local mappings = {}
local note_names = {
"C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
"A",
"A#",
"B",
}
for i = 0, 127 do
local note = {
name = note_names[i % 12 + 1] .. math.floor(i / 12) - 1,
feedback_enabled = false,
source = {
kind = "MidiNoteVelocity",
key_number = i,
},
target = {
kind = "Virtual",
character = "Multi",
id = i
},
}
table.insert(mappings, note)
end
return {
kind = "ControllerCompartment",
value = {
mappings = mappings,
},
}
This preset exposes each key on your QWERTY keyboard as a named virtual control element (for example "key/p").
local mappings = {}
local special_key_ids = {
[ "," ] = "comma",
[ "." ] = "period",
}
function get_key_id(ascii_code)
local char = string.lower(string.char(ascii_code))
return special_key_ids[char] or char
end
function key_mapping(modifiers, ascii_code)
return {
source = {
kind = "Key",
keystroke = {
modifiers = modifiers,
key = ascii_code,
},
},
target = {
kind = "Virtual",
character = "Button",
id = "key/" .. get_key_id(ascii_code),
},
}
end
-- Digits
for i = 48, 57 do
table.insert(mappings, key_mapping(1, i))
end
-- Letters
for i = 65, 90 do
table.insert(mappings, key_mapping(1, i))
end
-- Comma and dot
table.insert(mappings, key_mapping(0, 44))
table.insert(mappings, key_mapping(0, 46))
return {
kind = "ControllerCompartment",
value = {
mappings = mappings,
},
}
This preset emulates the typical "Mackie Control"-style DAW control. Not completely, but a large part. It is compatible with any controller preset that provides the DAW scheme. It is a perpetual work in progress and will be improved over time.
This preset is a built-in factory preset. You can easily use it by selecting Main preset › Generic › DAW control.
This preset works with grid controllers that have column stop buttons, e.g. the Akai APC Key 25 Mk2.
Each column stop button toggles the arm state of a corresponding instrument track, switching it on or off for live playing, as you desire. The name of each instrument track must start with "Instrument 01", "Instrument 02", etc.
Best of all: It works with visual LED feedback!
-- Configuration
local column_count = 8
-- Functions
function format_as_two_digits(n)
if n < 10 then
return "0" .. tostring(n)
else
return tostring(n)
end
end
-- Mappings
local mappings = {}
for col = 0, column_count - 1 do
local human_col = col + 1
local two_digit_col = format_as_two_digits(human_col)
local mapping = {
source = {
kind = "Virtual",
id = "col" .. human_col .. "/stop",
character = "Button",
},
glue = {
absolute_mode = "ToggleButton",
},
target = {
kind = "TrackArmState",
track = {
address = "ByName",
name ="Instrument " .. two_digit_col .. "*",
},
},
}
table.insert(mappings, mapping)
end
return {
kind = "MainCompartment",
value = {
mappings = mappings,
},
}
This preset works with grid controllers, e.g. the Akai APC Key 25 Mk2.
-
Each grid button toggles the mute state of a corresponding instrument layer track, allowing you to add or remove that layer from the audible sound, as you desire.
-
The name of each instrument layer track must start with the layer name followed by 01, 02, etc. Example: "Drums 01: Acoustic".
-
The layers can be configured in the below script.
-
Each scene start button recalls a certain combination of layers. You can save the current combination by pressing a scene button while pressing the "Shift" key (on the grid controller, not on your QWERTY keyboard).
Best of all: It works with visual LED feedback!
-- Configuration
local column_count = 8
local row_count = 5
local layers = {
"Drums",
"Bass",
"Filler",
"Keys",
}
-- Functions
function format_as_two_digits(n)
if n < 10 then
return "0" .. tostring(n)
else
return tostring(n)
end
end
-- Mappings
local parameters = {
{
index = 0,
name = "Shift",
},
}
local groups = {
{
id = "slots",
name = "Slots",
tags = { "slot" }
},
{
id = "scenes",
name = "Scenes",
},
{
id = "modifiers",
name = "Modifiers",
},
}
local mappings = {
{
name = "Shift",
group = "modifiers",
source = {
kind = "Virtual",
id = "shift",
character = "Button",
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
fx = {
address = "This",
},
index = 0,
},
},
}
}
-- Slots
for col = 0, column_count - 1 do
for human_row, layer in ipairs(layers) do
local human_col = col + 1
local two_digit_col = format_as_two_digits(human_col)
local mapping = {
name = layer .. " " .. human_col,
group = "slots",
source = {
kind = "Virtual",
id = "col" .. human_col .. "/row" .. human_row .. "/pad",
character = "Button",
},
glue = {
source_interval = { 0.04, 1.0 },
absolute_mode = "ToggleButton",
reverse = true,
},
target = {
kind = "TrackMuteState",
track = {
address = "ByName",
name = layer .. " " .. two_digit_col .. "*",
},
},
}
table.insert(mappings, mapping)
end
end
-- Scenes
for row = 0, row_count - 1 do
local human_row = row + 1
local save_scene_mapping = {
name = "Save scene " .. human_row,
group = "scenes",
activation_condition = {
kind = "Modifier",
modifiers = {
{
parameter = 0,
on = true,
},
},
},
source = {
kind = "Virtual",
id = "row" .. human_row .. "/play",
character = "Button",
},
target = {
kind = "TakeMappingSnapshot",
tags = {
"slot",
},
active_mappings_only = false,
snapshot_id = "scene_" .. human_row,
},
}
local load_scene_mapping = {
name = "Scene " .. human_row,
group = "scenes",
activation_condition = {
kind = "Modifier",
modifiers = {
{
parameter = 0,
on = false,
},
},
},
source = {
kind = "Virtual",
id = "row" .. human_row .. "/play",
character = "Button",
},
target = {
kind = "LoadMappingSnapshot",
tags = {
"slot",
},
active_mappings_only = false,
snapshot = {
kind = "ById",
id = "scene_" .. human_row,
},
default_value = {
kind = "Unit",
value = 1
}
},
}
table.insert(mappings, save_scene_mapping)
table.insert(mappings, load_scene_mapping)
end
return {
kind = "MainCompartment",
value = {
parameters = parameters,
groups = groups,
mappings = mappings,
},
}
This preset makes it possible to use a one-channel DAW controller (e.g. X-Touch One) to control Pot Browser.
return {
kind = "MainCompartment",
version = "2.15.0-pre.3",
value = {
parameters = {
{
index = 0,
name = "Filter bank",
value_count = 10,
},
},
groups = {
{
id = "RZQLaoQOQwZFBCRaH8kHB",
name = "Filter pages",
},
{
id = "tRUhYa0RGcup1XIyV3zvr",
name = "Preset browsing",
},
{
id = "C_6AY80tdoM1Cy9FHi7XN",
name = "Filter page \"Character\"",
activation_condition = {
kind = "Bank",
parameter = 0,
bank_index = 4,
},
},
{
id = "EFpF3FW5Lp6k6jKBSh6_I",
name = "Filter page \"Sub type\"",
activation_condition = {
kind = "Bank",
parameter = 0,
bank_index = 3,
},
},
{
id = "lpnoZyrYUdSappcySl38d",
name = "Filter page \"Type\"",
activation_condition = {
kind = "Bank",
parameter = 0,
bank_index = 2,
},
},
{
id = "5A6qajMq6KFFVs8sVEIGV",
name = "Filter page \"Bank\"",
activation_condition = {
kind = "Bank",
parameter = 0,
bank_index = 1,
},
},
{
id = "zbTTUwkU-D-xslTQjGa3m",
name = "Filter page \"Instrument\"",
activation_condition = {
kind = "Bank",
parameter = 0,
bank_index = 0,
},
},
},
mappings = {
{
id = "MaMuToR-Qp8dB1it-ESP_",
name = "Choose instrument",
group = "RZQLaoQOQwZFBCRaH8kHB",
source = {
kind = "Virtual",
id = "f1",
character = "Button",
},
glue = {
target_interval = {0, 0},
out_of_range_behavior = "Min",
step_size_interval = {0.01, 0.05},
step_factor_interval = {1, 5},
button_filter = "PressOnly",
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
index = 0,
},
},
},
{
id = "7sIZfWbiSjYGqZR8ebE3m",
name = "Choose bank",
group = "RZQLaoQOQwZFBCRaH8kHB",
source = {
kind = "Virtual",
id = "f2",
character = "Button",
},
glue = {
target_interval = {0.1111111111111111, 0.1111111111111111},
out_of_range_behavior = "Min",
step_size_interval = {0.01, 0.05},
step_factor_interval = {1, 5},
button_filter = "PressOnly",
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
index = 0,
},
},
},
{
id = "1ZSzf-B4xTurAkVFkss3y",
name = "Choose type",
group = "RZQLaoQOQwZFBCRaH8kHB",
source = {
kind = "Virtual",
id = "f3",
character = "Button",
},
glue = {
target_interval = {0.2222222222222222, 0.2222222222222222},
out_of_range_behavior = "Min",
step_size_interval = {0.01, 0.05},
step_factor_interval = {1, 5},
button_filter = "PressOnly",
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
index = 0,
},
},
},
{
id = "t3jwdnxgLSgNwnYhfV3bX",
name = "Choose sub type",
group = "RZQLaoQOQwZFBCRaH8kHB",
source = {
kind = "Virtual",
id = "f4",
character = "Button",
},
glue = {
target_interval = {0.3333333333333333, 0.3333333333333333},
out_of_range_behavior = "Min",
step_size_interval = {0.01, 0.05},
step_factor_interval = {1, 5},
button_filter = "PressOnly",
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
index = 0,
},
},
},
{
id = "XrMjoK-jksKjBvkJR560T",
name = "Choose character",
group = "RZQLaoQOQwZFBCRaH8kHB",
source = {
kind = "Virtual",
id = "f5",
character = "Button",
},
glue = {
target_interval = {0.4444444444444444, 0.4444444444444444},
out_of_range_behavior = "Min",
step_size_interval = {0.01, 0.05},
step_factor_interval = {1, 5},
button_filter = "PressOnly",
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
index = 0,
},
},
},
{
id = "GW6k2reFw5uSNWg2gFGqS",
name = "Browse",
group = "zbTTUwkU-D-xslTQjGa3m",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "jog",
},
glue = {
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "Bank",
},
},
{
id = "nVTUaxgroOo_YfugMyk0X",
name = "Browse",
group = "5A6qajMq6KFFVs8sVEIGV",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "jog",
},
glue = {
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "SubBank",
},
},
{
id = "LryQpIKLKRMLwHA6NfDTB",
name = "Browse",
group = "lpnoZyrYUdSappcySl38d",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "jog",
},
glue = {
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "Category",
},
},
{
id = "rKGURMBGDTLps-HDQGbz-",
name = "Browse",
group = "EFpF3FW5Lp6k6jKBSh6_I",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "jog",
},
glue = {
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "SubCategory",
},
},
{
id = "BWAF5SbeC58ABUfgs15Ul",
name = "Browse",
group = "C_6AY80tdoM1Cy9FHi7XN",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "jog",
},
glue = {
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "Mode",
},
},
{
id = "d0avz2uKc1Kc3jot4FJF7",
name = "Display",
group = "C_6AY80tdoM1Cy9FHi7XN",
control_enabled = false,
source = {
kind = "MackieSevenSegmentDisplay",
scope = "Tc",
},
glue = {
absolute_mode = "IncrementalButton",
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
feedback = {
kind = "Text",
},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "Mode",
},
},
{
id = "PG926UQqFa1tKbX7klaq1",
name = "Browse presets coarse",
group = "tRUhYa0RGcup1XIyV3zvr",
source = {
kind = "Virtual",
id = "ch1/fader",
},
glue = {
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
},
target = {
kind = "BrowsePotPresets",
},
},
{
id = "OpDDrpDG2hAv5gDjYubfk",
name = "Browse presets fine",
group = "tRUhYa0RGcup1XIyV3zvr",
source = {
kind = "Virtual",
id = "ch1/v-pot",
},
glue = {
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
},
target = {
kind = "BrowsePotPresets",
},
},
{
id = "iw_ng6_2JADZESl3X2axi",
name = "Previous preset",
group = "tRUhYa0RGcup1XIyV3zvr",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "cursor-left",
character = "Button",
},
glue = {
absolute_mode = "IncrementalButton",
reverse = true,
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
},
target = {
kind = "BrowsePotPresets",
},
},
{
id = "6qovylkC3n0J9DqkeIhTO",
name = "Next preset",
group = "tRUhYa0RGcup1XIyV3zvr",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "cursor-right",
character = "Button",
},
glue = {
absolute_mode = "IncrementalButton",
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
},
target = {
kind = "BrowsePotPresets",
},
},
{
id = "RJDETxxijd4tIv36zzv51",
name = "Display preset name",
group = "tRUhYa0RGcup1XIyV3zvr",
control_enabled = false,
source = {
kind = "MackieLcd",
channel = 0,
},
glue = {
absolute_mode = "IncrementalButton",
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
feedback = {
kind = "Text",
},
},
target = {
kind = "BrowsePotPresets",
},
},
{
id = "9XuuQ0Tl315m561HPwdz1",
name = "Say preset name",
group = "tRUhYa0RGcup1XIyV3zvr",
enabled = false,
control_enabled = false,
source = {
kind = "MidiDeviceChanges",
},
glue = {
absolute_mode = "IncrementalButton",
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
feedback = {
kind = "Text",
},
},
target = {
kind = "BrowsePotPresets",
},
},
{
id = "Kw572-DOMjW2-lAX-lTks",
name = "Preview preset",
group = "tRUhYa0RGcup1XIyV3zvr",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "play",
character = "Button",
},
glue = {
step_size_interval = {0.01, 0.05},
feedback = {
kind = "Text",
},
},
target = {
kind = "PreviewPotPreset",
},
},
{
id = "a_OaKe1Uo_FATkupnHi1z",
name = "Load preset",
group = "tRUhYa0RGcup1XIyV3zvr",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "ch1/v-select",
character = "Button",
},
glue = {
step_size_interval = {0.01, 0.05},
feedback = {
kind = "Text",
},
},
target = {
kind = "LoadPotPreset",
fx = {
address = "ByIndex",
chain = {
address = "Track",
track = {
address = "ById",
id = "B63E9F43-C847-FA42-A5DD-EAE998F42C78",
},
},
index = 1,
},
},
},
{
id = "IEVDSWvucmp0a60j0NSeG",
name = "Show FX on load",
group = "tRUhYa0RGcup1XIyV3zvr",
feedback_enabled = false,
source = {
kind = "Virtual",
id = "ch1/v-select",
character = "Button",
},
glue = {
target_interval = {1, 1},
step_size_interval = {0.01, 0.05},
feedback = {
kind = "Text",
},
},
target = {
kind = "FxVisibility",
fx = {
address = "ByIndex",
chain = {
address = "Track",
track = {
address = "ById",
id = "B63E9F43-C847-FA42-A5DD-EAE998F42C78",
},
},
index = 1,
},
},
},
{
id = "kuUh62ZJXpQa9AS_s9WvM",
name = "Display param name",
group = "tRUhYa0RGcup1XIyV3zvr",
source = {
kind = "MackieLcd",
channel = 0,
line = 0,
},
glue = {
step_size_interval = {0.01, 0.05},
step_factor_interval = {1, 5},
feedback = {
kind = "Text",
text_expression = "{{ target.fx_parameter.name }}",
},
},
target = {
kind = "LastTouched",
included_targets = {
"RoutePan",
"TrackMuteState",
"TrackAutomationMode",
"TrackArmState",
"TrackVolume",
"TrackSoloState",
"TrackMonitoringMode",
"FxOnOffState",
"TrackPan",
"FxParameterValue",
"AutomationModeOverride",
"RouteVolume",
"BrowseFxPresets",
"PlayRate",
"TrackSelectionState",
"Tempo",
},
},
},
{
id = "VJjcCHS4L9XXAEZaggw_t",
name = "Display param value",
group = "tRUhYa0RGcup1XIyV3zvr",
source = {
kind = "MackieLcd",
channel = 0,
line = 1,
},
glue = {
step_size_interval = {0.01, 0.05},
step_factor_interval = {1, 5},
feedback = {
kind = "Text",
},
},
target = {
kind = "LastTouched",
included_targets = {
"TrackSoloState",
"TrackSelectionState",
"PlayRate",
"RouteVolume",
"TrackArmState",
"Tempo",
"TrackMuteState",
"TrackPan",
"RoutePan",
"AutomationModeOverride",
"TrackMonitoringMode",
"TrackVolume",
"FxParameterValue",
"TrackAutomationMode",
"BrowseFxPresets",
"FxOnOffState",
},
},
},
{
id = "OCsMy1ZHN-pNVtrHPHUqD",
name = "Display",
group = "5A6qajMq6KFFVs8sVEIGV",
control_enabled = false,
source = {
kind = "MackieSevenSegmentDisplay",
scope = "Tc",
},
glue = {
absolute_mode = "IncrementalButton",
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
feedback = {
kind = "Text",
text_expression = "{{ target.item.name }}",
},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "SubBank",
},
},
{
id = "YCoIZWojUdlV4HLG_rpJu",
name = "Display",
group = "zbTTUwkU-D-xslTQjGa3m",
control_enabled = false,
source = {
kind = "MackieSevenSegmentDisplay",
scope = "Tc",
},
glue = {
absolute_mode = "IncrementalButton",
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
feedback = {
kind = "Text",
},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "Bank",
},
},
{
id = "_NzMo9-xSF5LUmblvGLBJ",
name = "Display",
group = "EFpF3FW5Lp6k6jKBSh6_I",
control_enabled = false,
source = {
kind = "MackieSevenSegmentDisplay",
scope = "Tc",
},
glue = {
absolute_mode = "IncrementalButton",
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
feedback = {
kind = "Text",
text_expression = "{{ target.item.name }}",
},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "SubCategory",
},
},
{
id = "m9DAsw94DscrY0TO0n3Fn",
name = "Display",
group = "lpnoZyrYUdSappcySl38d",
control_enabled = false,
source = {
kind = "MackieSevenSegmentDisplay",
scope = "Tc",
},
glue = {
absolute_mode = "IncrementalButton",
step_size_interval = {0.000041237113402061855, 0.000041237113402061855},
feedback = {
kind = "Text",
},
},
target = {
kind = "BrowsePotFilterItems",
item_kind = "Category",
},
},
},
},
}
This preset makes it possible to use multi-channel DAW controllers (e.g. iCON Platform M+) to control Playtime.
-- Constants
local channel_count = 8
-- Utility functions
--- Converts the given key-value table to an array table.
function to_array(t)
local array = {}
for _, v in pairs(t) do
table.insert(array, v)
end
return array
end
--- Returns a new table that's the given table turned into an array
--- and sorted by the `index` key.
function sorted_by_index(t)
local sorted = to_array(t)
local compare_index = function(left, right)
return left.index < right.index
end
table.sort(sorted, compare_index)
return sorted
end
--- Clones a table.
function clone(t)
local new_table = {}
for k, v in pairs(t) do
new_table[k] = v
end
return new_table
end
--- Returns a new table that is the result of merging t2 into t1.
---
--- Values in t2 have precedence.
---
--- The result will be mergeable as well. This is good for "modifier chaining".
function merged(t1, t2)
local result = clone(t1)
for key, new_value in pairs(t2) do
local old_value = result[key]
if old_value and type(old_value) == "table" and type(new_value) == "table" then
-- Merge table value as well
result[key] = merged(old_value, new_value)
else
-- Simple use new value
result[key] = new_value
end
end
return make_mergeable(result)
end
--- Makes it possible to merge this table with another one via "+" operator.
function make_mergeable(t)
local metatable = {
__add = merged
}
setmetatable(t, metatable)
return t
end
function PartialMapping(t)
return make_mergeable(t)
end
-- Parameters
local params = {
column_offset = {
index = 0,
name = "Column offset",
value_count = 10000,
},
row_offset = {
index = 1,
name = "Row offset",
value_count = 10000,
},
}
-- Domain functions
function current_slot(channel_index)
return {
address = "Dynamic",
column_expression = "p[0] + " .. channel_index,
row_expression = "p[1]"
}
end
function current_column(channel_index)
return {
address = "Dynamic",
expression = "p[0] + " .. channel_index,
}
end
function button(id)
return PartialMapping {
source = {
kind = "Virtual",
character = "Button",
id = id,
},
}
end
function multi(id)
return PartialMapping {
source = {
kind = "Virtual",
character = "Multi",
id = id,
},
}
end
function transport_action(action)
return PartialMapping {
target = {
kind = "TransportAction",
action = action,
},
}
end
function clip_transport_action(action, channel_index)
return PartialMapping {
target = {
kind = "ClipTransportAction",
slot = current_slot(channel_index),
action = action,
record_only_if_track_armed = true,
stop_column_if_slot_empty = true,
},
}
end
function clip_name_feedback(channel_index)
return PartialMapping {
control_enabled = false,
glue = {
feedback = {
kind = "Text",
text_expression = "{{ target.clip.name }}"
},
},
target = {
kind = "ClipManagement",
slot = current_slot(channel_index),
action = {
kind = "EditClip",
},
},
}
end
function clip_position_feedback(channel_index)
return PartialMapping {
control_enabled = false,
target = {
kind = "ClipSeek",
slot = current_slot(channel_index),
-- TODO-high-playtime-after-release The Platform M+ seems to don't like resolution High (skips messages). Maybe time for #533.
feedback_resolution = "Beat",
},
}
end
function clip_volume(channel_index)
return {
target = {
kind = "ClipVolume",
slot = current_slot(channel_index),
},
}
end
function toggle()
return PartialMapping {
glue = {
absolute_mode = "ToggleButton",
},
}
end
function scroll_horizontally(amount)
return scroll(params.column_offset.index, amount)
end
function scroll_vertically(amount)
return scroll(params.row_offset.index, amount)
end
function scroll(param_index, amount)
local abs_amount = math.abs(amount)
return {
glue = {
absolute_mode = "IncrementalButton",
step_factor_interval = { abs_amount, abs_amount },
reverse = amount < 0,
feedback = {
kind = "Numeric",
transformation = "x = 1",
},
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
index = param_index,
},
},
}
end
function channel_button(channel_index, id)
return button("ch" .. (channel_index + 1) .. "/" .. id)
end
function channel_multi(channel_index, id)
return multi("ch" .. (channel_index + 1) .. "/" .. id)
end
function control_off()
return PartialMapping {
control_enabled = false,
}
end
function feedback_off()
return PartialMapping {
feedback_enabled = false,
}
end
function column_track_target(channel_index, target_kind, exclusive)
return {
glue = {
absolute_mode = "ToggleButton",
},
target = {
kind = target_kind,
track = {
address = "FromClipColumn",
column = current_column(channel_index),
context = "Playback",
},
exclusivity = exclusive and "WithinFolderOnOnly",
},
}
end
-- Mappings
local mappings = {
button("play") + toggle() + transport_action("PlayPause"),
button("stop") + transport_action("Stop"),
button("cursor-left") + scroll_horizontally(-1),
button("cursor-right")+ scroll_horizontally(1),
button("cursor-up") + scroll_vertically(-1),
button("cursor-down") + scroll_vertically(1),
}
for ch = 0, channel_count - 1 do
table.insert(mappings, channel_button(ch, "v-select") + toggle() + feedback_off() + clip_transport_action("RecordPlayStop", ch))
table.insert(mappings, channel_button(ch, "select") + toggle() + control_off() + clip_transport_action("RecordPlayStop", ch))
table.insert(mappings, channel_multi(ch, "fader") + clip_position_feedback(ch))
table.insert(mappings, channel_multi(ch, "v-pot") + clip_volume(ch))
table.insert(mappings, channel_button(ch, "mute") + column_track_target(ch, "TrackMuteState"))
table.insert(mappings, channel_button(ch, "solo") + column_track_target(ch, "TrackSoloState"))
table.insert(mappings, channel_button(ch, "record-ready") + column_track_target(ch, "TrackArmState", true))
end
-- Result
return {
kind = "MainCompartment",
value = {
parameters = sorted_by_index(params),
mappings = mappings,
},
}
This preset makes it possible to use one-channel DAW controllers (e.g. X-Touch One) to control Playtime.
-- Utility functions
--- Converts the given key-value table to an array table.
function to_array(t)
local array = {}
for _, v in pairs(t) do
table.insert(array, v)
end
return array
end
--- Returns a new table that's the given table turned into an array
--- and sorted by the `index` key.
function sorted_by_index(t)
local sorted = to_array(t)
local compare_index = function(left, right)
return left.index < right.index
end
table.sort(sorted, compare_index)
return sorted
end
--- Clones a table.
function clone(t)
local new_table = {}
for k, v in pairs(t) do
new_table[k] = v
end
return new_table
end
--- Returns a new table that is the result of merging t2 into t1.
---
--- Values in t2 have precedence.
---
--- The result will be mergeable as well. This is good for "modifier chaining".
function merged(t1, t2)
local result = clone(t1)
for key, new_value in pairs(t2) do
local old_value = result[key]
if old_value and type(old_value) == "table" and type(new_value) == "table" then
-- Merge table value as well
result[key] = merged(old_value, new_value)
else
-- Simple use new value
result[key] = new_value
end
end
return make_mergeable(result)
end
--- Makes it possible to merge this table with another one via "+" operator.
function make_mergeable(t)
local metatable = {
__add = merged
}
setmetatable(t, metatable)
return t
end
function PartialMapping(t)
return make_mergeable(t)
end
-- Parameters
local params = {
column_offset = {
index = 0,
name = "Column offset",
value_count = 10000,
},
row_offset = {
index = 1,
name = "Row offset",
value_count = 10000,
},
}
-- Domain functions
function current_slot()
return PartialMapping {
address = "Dynamic",
column_expression = "p[0]",
row_expression = "p[1]"
}
end
function button(id)
return PartialMapping {
source = {
kind = "Virtual",
character = "Button",
id = id,
},
}
end
function multi(id)
return PartialMapping {
source = {
kind = "Virtual",
character = "Multi",
id = id,
},
}
end
function clip_transport_action(action)
return PartialMapping {
target = {
kind = "ClipTransportAction",
slot = current_slot(),
action = action,
record_only_if_track_armed = true,
stop_column_if_slot_empty = true,
},
}
end
function clip_name_feedback()
return PartialMapping {
control_enabled = false,
glue = {
feedback = {
kind = "Text",
text_expression = "{{ target.clip.name }}"
},
},
target = {
kind = "ClipManagement",
slot = current_slot(),
action = "EditClip",
},
}
end
function clip_position_text_feedback()
return PartialMapping {
control_enabled = false,
glue = {
feedback = {
kind = "Text",
},
},
target = {
kind = "ClipSeek",
slot = current_slot(),
feedback_resolution = "High",
},
}
end
function clip_position_numeric_feedback()
return PartialMapping {
control_enabled = false,
target = {
kind = "ClipSeek",
slot = current_slot(),
feedback_resolution = "High",
},
}
end
function clip_volume()
return {
target = {
kind = "ClipVolume",
slot = current_slot(),
},
}
end
function press_only()
return PartialMapping {
glue = {
button_filter = "PressOnly",
},
}
end
function toggle()
return PartialMapping {
glue = {
absolute_mode = "ToggleButton",
},
}
end
function scroll_horizontally(amount)
return scroll(params.column_offset.index, amount)
end
function scroll_vertically(amount)
return scroll(params.row_offset.index, amount)
end
function scroll(param_index, amount)
local abs_amount = math.abs(amount)
return {
glue = {
absolute_mode = "IncrementalButton",
step_factor_interval = { abs_amount, abs_amount },
reverse = amount < 0,
feedback = {
kind = "Numeric",
transformation = "x = 1",
},
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
index = param_index,
},
},
}
end
-- Mappings
local mappings = {
button("cycle") + toggle() + clip_transport_action("Looped"),
button("stop") + clip_transport_action("Stop"),
button("play") + press_only() + clip_transport_action("PlayStop"),
button("record") + toggle() + clip_transport_action("RecordStop"),
button("cursor-left") + scroll_horizontally(-1),
button("cursor-right")+ scroll_horizontally(1),
button("cursor-up") + scroll_vertically(-1),
button("cursor-down") + scroll_vertically(1),
--multi("ch1/fader") + clip_volume(),
multi("ch1/fader") + clip_position_numeric_feedback(),
multi("ch1/lcd/line1") + clip_name_feedback(),
multi("lcd/timecode") + clip_position_text_feedback(),
}
-- Result
return {
kind = "MainCompartment",
value = {
parameters = sorted_by_index(params),
mappings = mappings,
},
}
This preset makes it possible to use a grid controller (e.g. Launchpad) to control Playtime.
-- Constants
local column_count = 4
local row_count = 4
local mode_count = 100
-- Utilities
function set_keys_as_ids(t)
for key, value in pairs(t) do
value.id = key
end
end
function to_array(t)
local array = {}
for _, v in pairs(t) do
table.insert(array, v)
end
return array
end
function sorted_by_index(t)
local sorted = to_array(t)
local compare_index = function(left, right)
return left.index < right.index
end
table.sort(sorted, compare_index)
return sorted
end
-- Modes
local modes = {
-- OK Record/play/stop clips (push)
-- OK Set clip volume (turn)
normal = {
index = 0,
label = "Normal",
},
-- OK Undo/redo
-- OK Stop all
-- OK Play
-- OK Click
-- Cycle through normal mode knob functions (vol, pan, send)
global = {
index = 1,
label = "Global",
button = "bank-left",
},
-- OK Solo/arm/mute/select column tracks
track = {
index = 2,
label = "Track",
button = "cursor-left",
},
-- OK Delete clip (long press)
-- OK Quantize clip (double press)
slot = {
index = 3,
label = "Slot",
button = "ch-left",
},
-- OK Scroll horizontally
-- OK Scroll vertically
nav = {
index = 4,
label = "Navigation",
button = "bank-right",
},
-- OK Stop column
column = {
index = 5,
label = "Column",
button = "cursor-right",
},
}
local sorted_modes = sorted_by_index(modes)
local mode_labels = {}
for _, mode in ipairs(sorted_modes) do
table.insert(mode_labels, mode.label)
end
-- Parameters
local params = {
column_offset = {
index = 0,
name = "Column offset",
value_count = 10000,
},
row_offset = {
index = 1,
name = "Row offset",
value_count = 10000,
},
mode = {
index = 2,
name = "Mode",
value_count = mode_count,
value_labels = mode_labels,
},
}
-- Groups
function mode_is(mode_index)
return {
kind = "Bank",
parameter = params.mode.index,
bank_index = mode_index,
}
end
function display_slot_feedback_condition()
return {
kind = "Expression",
condition = "p[2] == 0 || p[2] == 4 || p[2] == 3",
}
end
local groups = {
slot_state_feedback = {
name = "Slot state feedback",
activation_condition = display_slot_feedback_condition(),
},
clip_play = {
name = "Clip play",
activation_condition = mode_is(modes.normal.index),
},
clip_volume = {
name = "Clip volume",
activation_condition = mode_is(modes.normal.index),
},
clip_pos_feedback = {
name = "Clip position feedback",
activation_condition = display_slot_feedback_condition(),
},
global = {
name = modes.global.label,
activation_condition = mode_is(modes.global.index),
},
nav = {
name = modes.nav.label,
activation_condition = mode_is(modes.nav.index),
},
track = {
name = modes.track.label,
activation_condition = mode_is(modes.track.index),
},
slot = {
name = modes.slot.label,
activation_condition = mode_is(modes.slot.index),
},
column = {
name = modes.column.label,
activation_condition = mode_is(modes.column.index),
},
}
set_keys_as_ids(groups)
-- Domain functions
function create_cell_id(col, row, id)
return "col" .. (col + 1) .. "/row" .. (row + 1) .. "/" .. id
end
function create_coordinate_expression(param, index)
return "p[" .. param .. "] + " .. index
end
function create_col_expression(col)
return create_coordinate_expression(params.column_offset.index, col)
end
function create_row_expression(row)
return create_coordinate_expression(params.row_offset.index, row)
end
function create_slot_selector(col, row)
return {
address = "Dynamic",
column_expression = create_col_expression(col),
row_expression = create_row_expression(row)
}
end
function clip_play(col, row)
return {
name = "Rec/play",
group = groups.clip_play.id,
feedback_enabled = false,
source = {
kind = "Virtual",
character = "Button",
id = create_cell_id(col, row, "pad"),
},
glue = {
absolute_mode = "ToggleButton",
},
target = {
kind = "ClipTransportAction",
slot = create_slot_selector(col, row),
action = "RecordPlayStop",
record_only_if_track_armed = true,
stop_column_if_slot_empty = true,
},
}
end
function clip_volume(col, row)
return {
name = "Vol",
group = groups.clip_volume.id,
feedback_enabled = false,
source = {
kind = "Virtual",
character = "Multi",
id = create_cell_id(col, row, "knob"),
},
target = {
kind = "ClipVolume",
slot = create_slot_selector(col, row),
},
}
end
function slot_state_feedback(col, row)
return {
group = groups.slot_state_feedback.id,
control_enabled = false,
visible_in_projection = false,
source = {
kind = "Virtual",
character = "Button",
id = create_cell_id(col, row, "pad"),
},
glue = {
feedback = {
kind = "Text",
text_expression = "{{ target.slot_state.id }}",
},
},
target = {
kind = "ClipTransportAction",
slot = create_slot_selector(col, row),
action = "PlayStop",
},
}
end
function clip_position_feedback(col, row)
return {
group = groups.clip_pos_feedback.id,
visible_in_projection = false,
control_enabled = false,
source = {
kind = "Virtual",
character = "Multi",
id = create_cell_id(col, row, "knob"),
},
target = {
kind = "ClipSeek",
slot = create_slot_selector(col, row),
feedback_resolution = "High",
},
}
end
function global_matrix_button(button_id, matrix_action, name)
return {
name = name or matrix_action,
activation_condition = mode_is(modes.global.index),
source = {
kind = "Virtual",
id = button_id,
character = "Button",
},
target = {
kind = "ClipMatrixAction",
action = matrix_action,
},
}
end
function global_transport_button(button_id, transport_action, absolute_mode)
return {
activation_condition = mode_is(modes.global.index),
source = {
kind = "Virtual",
id = button_id,
character = "Button",
},
glue = {
absolute_mode = absolute_mode,
},
target = {
kind = "TransportAction",
action = transport_action,
},
}
end
function global_reaper_action_button(button_id, command_id, name)
return {
name = name,
activation_condition = mode_is(modes.global.index),
source = {
kind = "Virtual",
id = button_id,
character = "Button",
},
target = {
kind = "ReaperAction",
command = command_id,
invocation = "Trigger",
},
}
end
function scroll(multi_id, offset_param_index, name)
return {
name = name,
group = groups.nav.id,
source = {
kind = "Virtual",
id = multi_id,
character = "Multi",
},
glue = {
step_factor_interval = { -3, -3 },
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
index = offset_param_index,
},
},
}
end
function clip_delete(col, row)
return {
name = "Quantize/delete",
group = groups.slot.id,
feedback_enabled = false,
source = {
kind = "Virtual",
character = "Button",
id = create_cell_id(col, row, "pad"),
},
glue = {
fire_mode = {
kind = "AfterTimeout",
timeout = 1000,
},
},
target = {
kind = "ClipManagement",
slot = create_slot_selector(col, row),
action = "ClearSlot",
},
}
end
function track_toggle_target(col, row, target_kind, name, exclusive)
return {
name = name,
group = groups.track.id,
source = {
kind = "Virtual",
character = "Button",
id = create_cell_id(col, row, "pad"),
},
glue = {
absolute_mode = "ToggleButton",
},
target = {
kind = target_kind,
track = {
address = "FromClipColumn",
column = {
address = "Dynamic",
expression = create_col_expression(col),
},
context = "Playback",
},
exclusivity = exclusive and "WithinFolderOnOnly",
},
}
end
function column_action(col, row, action)
return {
name = action,
group = groups.column.id,
source = {
kind = "Virtual",
character = "Button",
id = create_cell_id(col, row, "pad"),
},
target = {
kind = "ClipColumnAction",
column = {
address = "Dynamic",
expression = create_col_expression(col),
},
action = action
},
}
end
function clip_quantize(col, row)
return {
group = groups.slot.id,
visible_in_projection = false,
feedback_enabled = false,
source = {
kind = "Virtual",
character = "Button",
id = create_cell_id(col, row, "pad"),
},
glue = {
absolute_mode = "ToggleButton",
fire_mode = {
kind = "OnDoublePress",
},
},
target = {
kind = "ClipManagement",
slot = create_slot_selector(col, row),
action = "EditClip",
},
}
end
-- TODO-high-playtime-after-release Make short press toggle and long press be momentary.
-- Problem 1: "Fire after timeout" somehow doesn't have an effect.
-- Problem 2: "Fire after timeout" doesn't switch off when button released (in new versions it does!)
function mode_button(button_id, mode)
local target_value = mode.index / (mode_count - 1)
return {
name = mode.label,
feedback_enabled = false,
source = {
kind = "Virtual",
id = button_id,
character = "Button",
},
glue = {
target_interval = { 0, target_value }
},
target = {
kind = "FxParameterValue",
parameter = {
address = "ById",
index = params.mode.index,
},
},
}
end
-- Content
local mappings = {
global_matrix_button("col4/row4/pad", "Stop", "Stop all clips"),
global_matrix_button("col3/row1/pad", "Undo"),
global_matrix_button("col4/row1/pad", "Redo"),
global_transport_button("col4/row3/pad", "PlayPause", "ToggleButton"),
global_transport_button("col3/row3/pad", "Stop", "Normal"),
global_reaper_action_button("col4/row2/pad", 40364, "Click"),
scroll("col1/row1/knob", params.column_offset.index, "Scroll left/right"),
scroll("col1/row2/knob", params.row_offset.index, "Scroll up/down"),
}
-- Mode buttons
for _, mode in pairs(modes) do
if mode.button then
table.insert(mappings, mode_button(mode.button, mode))
end
end
-- Grid
for col = 0, column_count - 1 do
table.insert(mappings, track_toggle_target(col, 0, "TrackSoloState", "Solo"))
table.insert(mappings, track_toggle_target(col, 1, "TrackArmState", "Arm", true))
table.insert(mappings, track_toggle_target(col, 2, "TrackMuteState", "Mute"))
table.insert(mappings, track_toggle_target(col, 3, "TrackSelectionState", "Select", true))
table.insert(mappings, column_action(col, 3, "Stop"))
for row = 0, row_count - 1 do
table.insert(mappings, clip_play(col, row))
table.insert(mappings, clip_volume(col, row))
table.insert(mappings, clip_delete(col, row))
table.insert(mappings, clip_quantize(col, row))
table.insert(mappings, slot_state_feedback(col, row))
table.insert(mappings, clip_position_feedback(col, row))
end
end
-- Result
return {
kind = "MainCompartment",
value = {
parameters = sorted_by_index(params),
groups = to_array(groups),
mappings = mappings,
},
}
This preset makes it possible to use the TouchOSC template "Simple Mk2 / Matrix" to control Playtime.
-- TouchOSC: Simple Mk2 / Matrix
local mappings = {}
local feedback_value_table = {
kind = "FromTextToContinuous",
value = {
["playtime.slot_state.empty"] = 0.0,
["playtime.slot_state.stopped"] = 0.2,
["playtime.slot_state.scheduled_for_play_start"] = 0.4,
["playtime.slot_state.playing"] = 1.0,
["playtime.slot_state.paused"] = 0.3,
["playtime.slot_state.scheduled_for_play_stop"] = 0.4,
["playtime.slot_state.scheduled_for_play_restart"] = 0.4,
["playtime.slot_state.scheduled_for_record_start"] = 0.4,
["playtime.slot_state.recording"] = 1.0,
["playtime.slot_state.scheduled_for_record_stop"] = 0.4,
}
}
function multitoggle_button(index)
return {
kind = "Osc",
address = "/4/multitoggle/" .. index,
argument = {
index = 0,
},
}
end
for col = 0, 7 do
for row = 0, 7 do
local mapping = {
source = multitoggle_button(row * 8 + col + 1),
glue = {
-- Standard TouchOSC template tries to be too clever and toggles itself :(
-- So we use a toggle-always transformation.
absolute_mode = "Normal",
control_transformation = "y = y > 0.5 ? 0 : 1",
feedback = {
kind = "Text",
text_expression = "{{ target.slot_state.id }}",
},
feedback_value_table = feedback_value_table,
},
target = {
kind = "ClipTransportAction",
slot = {
address = "ByIndex",
column_index = col,
row_index = row,
},
action = "RecordPlayStop",
record_only_if_track_armed = true,
stop_column_if_slot_empty = true,
},
}
table.insert(mappings, mapping)
end
end
return {
kind = "MainCompartment",
value = {
mappings = mappings,
},
}
This preset makes it possible to use your QWERTY keyboard to control Playtime.
local column_key_sets = {
{ "1", "q", "a", "z", },
{ "2", "w", "s", "x", },
{ "3", "e", "d", "c", },
{ "4", "r", "f", "v", },
{ "5", "t", "g", "b", },
{ "6", "y", "h", "n", },
{ "7", "u", "j", "m", },
{ "8", "i", "k", "comma", },
}
function scene_play(key, row_index)
return {
source = {
kind = "Virtual",
character = "Button",
id = "key/" .. key,
},
target = {
kind = "ClipRowAction",
row = {
address = "ByIndex",
index = row_index,
},
action = "PlayScene",
},
}
end
local mappings = {
scene_play("9", 0),
scene_play("o", 1),
scene_play("l", 2),
scene_play("period", 3),
}
for col, column_key_set in ipairs(column_key_sets) do
for row, row_key in ipairs(column_key_set) do
local mapping = {
source = {
kind = "Virtual",
character = "Button",
id = "key/" .. row_key,
},
glue = {
--absolute_mode = "Toggle",
},
target = {
kind = "ClipTransportAction",
slot = {
address = "ByIndex",
column_index = col - 1,
row_index = row - 1,
},
action = "PlayStop",
stop_column_if_slot_empty = true,
play_start_timing = {
kind = "Immediately"
},
play_stop_timing = {
kind = "Immediately"
},
},
}
table.insert(mappings, mapping)
end
end
return {
kind = "MainCompartment",
value = {
mappings = mappings,
},
}
Learn how to contribute to this Wiki!