Reorganize top-level directories with clearer naming convention

app -> app-desktop-macos, presets -> app-presets, server -> remote-server,
daw-config-reaper -> osc-config-daw, plugin-reaper-realearn -> plugin-reaper-relearn.
Updated run.py and presets.py path references accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Paul Lipscomb
2026-07-15 17:51:05 -04:00
parent e58f06d9fa
commit 7ecc718f5d
2256 changed files with 11 additions and 5 deletions
@@ -0,0 +1 @@
../../api/luau/.stylua.toml
@@ -0,0 +1 @@
../../api/luau/.vscode
@@ -0,0 +1 @@
../../api/luau/README.md
@@ -0,0 +1,254 @@
--- name: APC mini mk2
--- realearn_version: 2.16.0-pre.8
--- author: helgoboss
--- device_manufacturer: Akai
--- device_name: APC mini mk2
--- description: |
--- This controller preset exposes all control elements of the APC mini mk2 in a neutral way. It supports
--- ReaLearn color feedback as well as Playtime slot status feedback.
--- midi_identity_pattern: F0 7E ? 06 02 47 4F 00 19 * F7
--- # This device exposes 2 USB MIDI ports, one for "Notes" and one for "Control". We are
--- # interested in "Control". Both MIDI ports reply with the same device identity. If we
--- # don't provide a MIDI output device pattern, it's possible that ReaLearn's automatic
--- # controller detection feature takes the "Notes" port ... and things wouldn't work!
--- # On Windows, the naming is different, that's why there's also "MIDIOUT2".
--- midi_output_port_patterns: ["macos:*Control*", "windows:APC mini mk2"]
--- provided_schemes: [akai/apc-mini-mk2, grid]
--!strict
-- Configuration
local resolve_shift = false
-- Requires
local preset_runtime = require("preset_runtime")
local realearn = require("realearn")
-- Define MIDI scripts
local common_lua = preset_runtime.include_str("akai/apc-mk2-lib/compartment-common.luau")
local function build_pad_script(pad_index: number): string
return `return require("compartment").pad_script({pad_index}, y, context)`
end
-- Parameters
local shift_param = realearn.Parameter {
index = 0,
name = "Shift",
}
local parameters: { realearn.Parameter }? = if resolve_shift then { shift_param } else nil
-- Single buttons
local function simple_button(id: string, name: string, key_number: number): realearn.Mapping
return realearn.Mapping {
id = id,
name = name,
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = key_number,
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
end
local mappings: { realearn.Mapping } = {
-- Main fader
{
id = "fm",
feedback_enabled = false,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 56,
character = "Range",
},
target = realearn.Target.Virtual {
id = "main/fader",
},
},
}
-- Shift
local no_shift_activation_condition: realearn.ActivationCondition?
if resolve_shift then
-- The activation condition reflecting the state that shift is not pressed.
no_shift_activation_condition = realearn.ActivationCondition.Modifier {
modifiers = {
{
parameter = 0,
on = false,
},
},
}
-- Mapping to make shift button switch to other set of virtual control elements
local shift_mapping = realearn.Mapping {
name = "Shift",
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 122,
},
target = realearn.Target.CompartmentParameterValue {
parameter = realearn.CompartmentParameterDescriptor.ById {
index = 0,
},
},
}
table.insert(mappings, shift_mapping)
-- Alternative set of virtual control elements
local alt_elements = {
{ key = 104, id = "cursor-up" },
{ key = 105, id = "cursor-down" },
{ key = 106, id = "cursor-left" },
{ key = 107, id = "cursor-right" },
{ key = 100, id = "volume" },
{ key = 101, id = "pan" },
{ key = 102, id = "sends" },
{ key = 103, id = "device" },
{ key = 112, id = "stop-clip" },
{ key = 113, id = "solo" },
{ key = 114, id = "mute" },
{ key = 115, id = "record-arm" },
{ key = 116, id = "track-select" },
{ key = 117, id = "drum" },
{ key = 118, id = "note" },
{ key = 119, id = "stop-all-clips" },
}
for _, element in ipairs(alt_elements) do
local mapping = realearn.Mapping {
activation_condition = realearn.ActivationCondition.Modifier {
modifiers = {
{
parameter = 0,
on = true,
},
},
},
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = element.key,
},
target = realearn.Target.Virtual {
id = element.id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
else
no_shift_activation_condition = nil
local mapping = simple_button("shift", "Shift", 122)
table.insert(mappings, mapping)
end
-- Channel faders
for i = 0, 7 do
local human_i = i + 1
local mapping = realearn.Mapping {
id = `f{human_i}`,
feedback_enabled = false,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 48 + i,
character = "Range",
},
target = realearn.Target.Virtual {
id = i,
},
}
table.insert(mappings, mapping)
end
-- Clip launch buttons
for col = 0, 7 do
local human_col = col + 1
for row = 0, 7 do
local human_row = row + 1
local key_number_offset = (7 - row) * 8
local id = `col{human_col}/row{human_row}/pad`
local control_mapping = realearn.Mapping {
id = id,
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = key_number_offset + col,
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
local feedback_mapping = realearn.Mapping {
id = `{id}-feedback`,
control_enabled = false,
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = build_pad_script(key_number_offset + col),
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
table.insert(mappings, control_mapping)
table.insert(mappings, feedback_mapping)
end
end
-- Clip stop buttons
for col = 0, 7 do
local human_col = col + 1
local id = `col{human_col}/stop`
local mapping = realearn.Mapping {
id = id,
activation_condition = no_shift_activation_condition,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 100 + col,
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
-- Scene launch buttons
for row = 0, 7 do
local human_row = row + 1
local id = `row{human_row}/play`
local mapping = realearn.Mapping {
id = id,
activation_condition = no_shift_activation_condition,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 112 + row,
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
return realearn.Compartment {
parameters = parameters,
mappings = mappings,
common_lua = common_lua,
custom_data = {
grid = {
column_count = 8,
row_count = 8,
},
},
}
@@ -0,0 +1,251 @@
--- name: APC mini
--- realearn_version: 2.16.0-pre.8
--- author: helgoboss
--- device_manufacturer: Akai
--- device_name: APC mini
--- description: |
--- This controller preset exposes all control elements of the APC mini mk2 in a neutral way. It supports
--- Playtime slot status feedback.
--- midi_identity_pattern: F0 7E ? 06 02 47 28 00 19 * F7
--- provided_schemes: [akai/apc-mini, grid]
--!strict
-- Configuration
local resolve_shift = false
-- Requires
local realearn = require("realearn")
-- Parameters
local shift_param = realearn.Parameter {
index = 0,
name = "Shift",
}
local parameters: { realearn.Parameter }? = if resolve_shift then { shift_param } else nil
-- Single buttons
local function simple_button(id: string, name: string, key_number: number): realearn.Mapping
return realearn.Mapping {
id = id,
name = name,
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = key_number,
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
end
local mappings: { realearn.Mapping } = {
-- Main fader
{
id = "fm",
feedback_enabled = false,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 56,
character = "Range",
},
target = realearn.Target.Virtual {
id = "main/fader",
},
},
}
-- Shift
local no_shift_activation_condition: realearn.ActivationCondition?
if resolve_shift then
-- The activation condition reflecting the state that shift is not pressed.
no_shift_activation_condition = realearn.ActivationCondition.Modifier {
modifiers = {
{
parameter = 0,
on = false,
},
},
}
-- Mapping to make shift button switch to other set of virtual control elements
local shift_mapping = realearn.Mapping {
name = "Shift",
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 98,
},
target = realearn.Target.CompartmentParameterValue {
parameter = realearn.CompartmentParameterDescriptor.ById {
index = 0,
},
},
}
table.insert(mappings, shift_mapping)
-- Alternative set of virtual control elements
local alt_elements = {
{ key = 64, id = "cursor-up" },
{ key = 65, id = "cursor-down" },
{ key = 66, id = "cursor-left" },
{ key = 67, id = "cursor-right" },
{ key = 68, id = "volume" },
{ key = 69, id = "pan" },
{ key = 70, id = "sends" },
{ key = 71, id = "device" },
{ key = 82, id = "stop-clip" },
{ key = 83, id = "solo" },
{ key = 84, id = "record-arm" },
{ key = 85, id = "mute" },
{ key = 86, id = "track-select" },
{ key = 89, id = "stop-all-clips" },
}
for _, element in ipairs(alt_elements) do
local mapping = realearn.Mapping {
activation_condition = realearn.ActivationCondition.Modifier {
modifiers = {
{
parameter = 0,
on = true,
},
},
},
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = element.key,
},
target = realearn.Target.Virtual {
id = element.id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
else
no_shift_activation_condition = nil
local mapping = simple_button("shift", "Shift", 98)
table.insert(mappings, mapping)
end
-- Channel faders
for i = 0, 7 do
local human_i = i + 1
local mapping = realearn.Mapping {
id = `f{human_i}`,
feedback_enabled = false,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 48 + i,
character = "Range",
},
target = realearn.Target.Virtual {
id = i,
},
}
table.insert(mappings, mapping)
end
-- Clip launch buttons
local feedback_value_table = realearn.FeedbackValueTable.FromTextToDiscrete {
value = {
-- Off
["playtime.slot_state.empty"] = 0,
-- Red
["playtime.slot_state.armed"] = 0,
-- Yellow
["playtime.slot_state.stopped"] = 5,
-- Green blinking
["playtime.slot_state.scheduled_for_play_start"] = 2,
-- Green
["playtime.slot_state.playing"] = 1,
-- Yellow
["playtime.slot_state.paused"] = 5,
-- Yellow blinking
["playtime.slot_state.scheduled_for_play_stop"] = 6,
-- Yellow blinking
["playtime.slot_state.scheduled_for_play_restart"] = 6,
-- Red blinking
["playtime.slot_state.scheduled_for_record_start"] = 4,
-- Red
["playtime.slot_state.recording"] = 3,
-- Yellow blinking
["playtime.slot_state.scheduled_for_record_stop"] = 6,
},
}
for col = 0, 7 do
local human_col = col + 1
for row = 0, 7 do
local human_row = row + 1
local key_number_offset = (7 - row) * 8
local id = `col{human_col}/row{human_row}/pad`
local mapping = realearn.Mapping {
id = id,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = key_number_offset + col,
},
glue = {
feedback_value_table = feedback_value_table,
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
table.insert(mappings, mapping)
end
end
-- Clip stop buttons
for col = 0, 7 do
local human_col = col + 1
local id = `col{human_col}/stop`
local mapping = realearn.Mapping {
id = id,
activation_condition = no_shift_activation_condition,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 64 + col,
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
-- Scene launch buttons
for row = 0, 7 do
local human_row = row + 1
local id = `row{human_row}/play`
local mapping = realearn.Mapping {
id = id,
activation_condition = no_shift_activation_condition,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 82 + row,
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
return realearn.Compartment {
parameters = parameters,
mappings = mappings,
custom_data = {
grid = {
column_count = 8,
row_count = 8,
},
},
}
@@ -0,0 +1,293 @@
--!strict
local midi_script = require("midi_script_source_runtime")
local module = {}
type FeedbackEntry = {
behavior: number,
color: number?,
}
local dark = 0x91
local medium = 0x93
local bright = 0x96
local pulsing = 0x9A
local blinking = 0x9B
local red_color = 6
local default_color = 3
local feedback_table: { [string]: FeedbackEntry? } = {
["playtime.slot_state.empty"] = {
behavior = dark,
color = 0,
},
["playtime.slot_state.armed"] = {
behavior = dark,
color = red_color,
},
["playtime.slot_state.stopped"] = {
behavior = dark,
},
["playtime.slot_state.ignited"] = {
behavior = dark,
},
["playtime.slot_state.scheduled_for_play_start"] = {
behavior = medium,
},
["playtime.slot_state.playing"] = {
behavior = pulsing,
},
["playtime.slot_state.paused"] = {
behavior = medium,
},
["playtime.slot_state.scheduled_for_play_stop"] = {
behavior = blinking,
},
["playtime.slot_state.scheduled_for_play_restart"] = {
behavior = blinking,
},
["playtime.slot_state.scheduled_for_record_start"] = {
behavior = medium,
color = red_color,
},
["playtime.slot_state.recording"] = {
behavior = pulsing,
color = red_color,
},
["playtime.slot_state.scheduled_for_record_stop"] = {
behavior = blinking,
color = red_color,
},
}
--- Although I got the true RGB colors to work, it was impossible to combine them with blinking
--- or pulsing behavior. So back to good old color palette.
local color_palette: { midi_script.RgbColor } = {
{r = 0x00, g= 0x00, b = 0x00},
{r = 0x1E, g= 0x1E, b = 0x1E},
{r = 0x7F, g= 0x7F, b = 0x7F},
{r = 0xFF, g= 0xFF, b = 0xFF},
{r = 0xFF, g= 0x4C, b = 0x4C},
{r = 0xFF, g= 0x00, b = 0x00},
{r = 0x59, g= 0x00, b = 0x00},
{r = 0x19, g= 0x00, b = 0x00},
{r = 0xFF, g= 0xBD, b = 0x6C},
{r = 0xFF, g= 0x54, b = 0x00},
{r = 0x59, g= 0x1D, b = 0x00},
{r = 0x27, g= 0x1B, b = 0x00},
{r = 0xFF, g= 0xFF, b = 0x4C},
{r = 0xFF, g= 0xFF, b = 0x00},
{r = 0x59, g= 0x59, b = 0x00},
{r = 0x19, g= 0x19, b = 0x00},
{r = 0x88, g= 0xFF, b = 0x4C},
{r = 0x54, g= 0xFF, b = 0x00},
{r = 0x1D, g= 0x59, b = 0x00},
{r = 0x14, g= 0x2B, b = 0x00},
{r = 0x4C, g= 0xFF, b = 0x4C},
{r = 0x00, g= 0xFF, b = 0x00},
{r = 0x00, g= 0x59, b = 0x00},
{r = 0x00, g= 0x19, b = 0x00},
{r = 0x4C, g= 0xFF, b = 0x5E},
{r = 0x00, g= 0xFF, b = 0x19},
{r = 0x00, g= 0x59, b = 0x0D},
{r = 0x00, g= 0x19, b = 0x02},
{r = 0x4C, g= 0xFF, b = 0x88},
{r = 0x00, g= 0xFF, b = 0x55},
{r = 0x00, g= 0x59, b = 0x1D},
{r = 0x00, g= 0x1F, b = 0x12},
{r = 0x4C, g= 0xFF, b = 0xB7},
{r = 0x00, g= 0xFF, b = 0x99},
{r = 0x00, g= 0x59, b = 0x35},
{r = 0x00, g= 0x19, b = 0x12},
{r = 0x4C, g= 0xC3, b = 0xFF},
{r = 0x00, g= 0xA9, b = 0xFF},
{r = 0x00, g= 0x41, b = 0x52},
{r = 0x00, g= 0x10, b = 0x19},
{r = 0x4C, g= 0x88, b = 0xFF},
{r = 0x00, g= 0x55, b = 0xFF},
{r = 0x00, g= 0x1D, b = 0x59},
{r = 0x00, g= 0x08, b = 0x19},
{r = 0x4C, g= 0x4C, b = 0xFF},
{r = 0x00, g= 0x00, b = 0xFF},
{r = 0x00, g= 0x00, b = 0x59},
{r = 0x00, g= 0x00, b = 0x19},
{r = 0x87, g= 0x4C, b = 0xFF},
{r = 0x54, g= 0x00, b = 0xFF},
{r = 0x19, g= 0x00, b = 0x64},
{r = 0x0F, g= 0x00, b = 0x30},
{r = 0xFF, g= 0x4C, b = 0xFF},
{r = 0xFF, g= 0x00, b = 0xFF},
{r = 0x59, g= 0x00, b = 0x59},
{r = 0x19, g= 0x00, b = 0x19},
{r = 0xFF, g= 0x4C, b = 0x87},
{r = 0xFF, g= 0x00, b = 0x54},
{r = 0x59, g= 0x00, b = 0x1D},
{r = 0x22, g= 0x00, b = 0x13},
{r = 0xFF, g= 0x15, b = 0x00},
{r = 0x99, g= 0x35, b = 0x00},
{r = 0x79, g= 0x51, b = 0x00},
{r = 0x43, g= 0x64, b = 0x00},
{r = 0x03, g= 0x39, b = 0x00},
{r = 0x00, g= 0x57, b = 0x35},
{r = 0x00, g= 0x54, b = 0x7F},
{r = 0x00, g= 0x00, b = 0xFF},
{r = 0x00, g= 0x45, b = 0x4F},
{r = 0x25, g= 0x00, b = 0xCC},
{r = 0x7F, g= 0x7F, b = 0x7F},
{r = 0x20, g= 0x20, b = 0x20},
{r = 0xFF, g= 0x00, b = 0x00},
{r = 0xBD, g= 0xFF, b = 0x2D},
{r = 0xAF, g= 0xED, b = 0x06},
{r = 0x64, g= 0xFF, b = 0x09},
{r = 0x10, g= 0x8B, b = 0x00},
{r = 0x00, g= 0xFF, b = 0x87},
{r = 0x00, g= 0xA9, b = 0xFF},
{r = 0x00, g= 0x2A, b = 0xFF},
{r = 0x3F, g= 0x00, b = 0xFF},
{r = 0x7A, g= 0x00, b = 0xFF},
{r = 0xB2, g= 0x1A, b = 0x7D},
{r = 0x40, g= 0x21, b = 0x00},
{r = 0xFF, g= 0x4A, b = 0x00},
{r = 0x88, g= 0xE1, b = 0x06},
{r = 0x72, g= 0xFF, b = 0x15},
{r = 0x00, g= 0xFF, b = 0x00},
{r = 0x3B, g= 0xFF, b = 0x26},
{r = 0x59, g= 0xFF, b = 0x71},
{r = 0x38, g= 0xFF, b = 0xCC},
{r = 0x5B, g= 0x8A, b = 0xFF},
{r = 0x31, g= 0x51, b = 0xC6},
{r = 0x87, g= 0x7F, b = 0xE9},
{r = 0xD3, g= 0x1D, b = 0xFF},
{r = 0xFF, g= 0x00, b = 0x5D},
{r = 0xFF, g= 0x7F, b = 0x00},
{r = 0xB9, g= 0xB0, b = 0x00},
{r = 0x90, g= 0xFF, b = 0x00},
{r = 0x83, g= 0x5D, b = 0x07},
{r = 0x39, g= 0x2b, b = 0x00},
{r = 0x14, g= 0x4C, b = 0x10},
{r = 0x0D, g= 0x50, b = 0x38},
{r = 0x15, g= 0x15, b = 0x2A},
{r = 0x16, g= 0x20, b = 0x5A},
{r = 0x69, g= 0x3C, b = 0x1C},
{r = 0xA8, g= 0x00, b = 0x0A},
{r = 0xDE, g= 0x51, b = 0x3D},
{r = 0xD8, g= 0x6A, b = 0x1C},
{r = 0xFF, g= 0xE1, b = 0x26},
{r = 0x9E, g= 0xE1, b = 0x2F},
{r = 0x67, g= 0xB5, b = 0x0F},
{r = 0x1E, g= 0x1E, b = 0x30},
{r = 0xDC, g= 0xFF, b = 0x6B},
{r = 0x80, g= 0xFF, b = 0xBD},
{r = 0x9A, g= 0x99, b = 0xFF},
{r = 0x8E, g= 0x66, b = 0xFF},
{r = 0x40, g= 0x40, b = 0x40},
{r = 0x75, g= 0x75, b = 0x75},
{r = 0xE0, g= 0xFF, b = 0xFF},
{r = 0xA0, g= 0x00, b = 0x00},
{r = 0x35, g= 0x00, b = 0x00},
{r = 0x1A, g= 0xD0, b = 0x00},
{r = 0x07, g= 0x42, b = 0x00},
{r = 0xB9, g= 0xB0, b = 0x00},
{r = 0x3F, g= 0x31, b = 0x00},
{r = 0xB3, g= 0x5F, b = 0x00},
{r = 0x4B, g= 0x15, b = 0x02},
{r = 0x40, g= 0x40, b = 0x40},
{r = 0x75, g= 0x75, b = 0x75},
{r = 0xE0, g= 0xFF, b = 0xFF},
{r = 0xA0, g= 0x00, b = 0x00},
{r = 0x35, g= 0x00, b = 0x00},
{r = 0x1A, g= 0xD0, b = 0x00},
{r = 0x07, g= 0x42, b = 0x00},
{r = 0xB9, g= 0xB0, b = 0x00},
{r = 0x3F, g= 0x31, b = 0x00},
{r = 0xB3, g= 0x5F, b = 0x00},
{r = 0x4B, g= 0x15, b = 0x02},
}
--- Returns the 0-based index within the given palette array.
local function find_closest_color_in_palette(color: midi_script.RgbColor, palette: {midi_script.RgbColor}): number
local ifurthest = 0
local furthest = 3 * math.pow(255, 2) + 1
for i,c in palette do
if color.r == c.r and color.g == c.g and color.b == c.b then
return i - 1
end
local distance = math.pow((color.r - c.r), 2) + math.pow((color.g - c.g), 2) + math.pow((color.b - c.b), 2)
if distance < furthest then
furthest = distance
ifurthest = i - 1
end
end
return ifurthest
end
function module.pad_script(pad_index: number, y: midi_script.InputValue, context: midi_script.Context): midi_script.Output
local address = pad_index
local off_color = 0x00
local off_output = {
address = address,
messages = {
{ bright, pad_index, off_color },
},
}
-- Handle "off" feedback
if y == nil then
return off_output
end
-- Handle numeric feedback
if type(y) == "number" then
-- Translate number to solid feedback color, just as with APC Key 25 mk1
local color = math.floor(y * 127)
return {
address = address,
messages = {
{ bright, pad_index, color },
},
}
end
-- Handle text feedback
if type(y) == "string" then
-- This is a grid controller, typically used for controlling a Playtime matrix, so we should be able to
-- handle text feedback that conforms to Playtime's clip state convention.
local entry = feedback_table[y]
if entry == nil then
-- Unknown texts switch the LED off
return off_output
end
local explicit_color = entry.color
if explicit_color ~= nil then
-- Switch LED to explicit color
return {
address = address,
messages = {
{ entry.behavior, pad_index, explicit_color },
},
}
end
-- No explicit color means we should use the event color (which is set in the "Glue" section)
local event_color = context.feedback_event.color
if event_color == nil then
-- Event has default color / no specific color
return {
address = address,
messages = {
{ entry.behavior, pad_index, default_color },
},
}
end
-- Find color available on the controller that's closest to the desired RGB color
local closest_color = find_closest_color_in_palette(event_color, color_palette)
return {
address = address,
messages = {
{ entry.behavior, pad_index, closest_color },
},
}
end
-- Complex feedback is highly individual. It doesn't make sense to handle this in a general-purpose controller preset.
return {
address = address,
messages = {},
}
end
return module
@@ -0,0 +1,26 @@
--- name: X-Touch One
--- realearn_version: 2.16.0
--- author: helgoboss
--- description: |
--- This controller preset implements support for the Behringer X-Touch One controller in MCU mode.
---
--- The MC modes cover all buttons and work great with the "Mackie Control" preset. A potential
--- downside is that the device itself controls the "Master" button, so you cant use that button in
--- ReaLearn to customize its logic.
--- setup_instructions: |
--- Select the correct mode by holding the stop button and pressing the encoder:
---
--- - If you have firmware < 1.10 (the firmware is displayed at startup), use mode "MC Std"
--- - If you have at least firmware 1.10, use mode "MC user"
--- device_manufacturer: Behringer
--- device_name: X-Touch One
--- provided_schemes: [daw]
--!strict
local commons = require("mackie/control-universal-lib/preset-common")
return commons.create_compartment {
companion_data = nil,
support_x_touch_colors = true,
}
@@ -0,0 +1,35 @@
--- name: Midi Fighter Twister - Grid
--- realearn_version: 2.16.0-pre.8
--- author: helgoboss
--- device_manufacturer: DJ TechTools
--- device_name: Midi Fighter Twister
--- description: |
--- This controller preset exposes the push encoders of the Midi Fighter Twister with the "grid" scheme, which means
--- it uses virtual element IDs such as "col1/row1/pad". This makes it compatible with main presets that are
--- built for generic grid controllers.
--- setup_instructions: |
--- The Midi Fighter Twister is a very customizable controller. Please follow the instructions at
--- https://github.com/helgoboss/helgobox/blob/master/doc/controllers.adoc#dj-techtools-midi-fighter-twister
--- to make it ready for ReaLearn.
--- midi_identity_pattern: F0 7E ? 06 02 00 01 79 05 00 01 00 * F7
--- provided_schemes: [djtechtools/midi-fighter-twister/grid, grid]
--!strict
local realearn = require("realearn")
local commons = require("djtechtools/midi-fighter-twister-lib/preset-common")
return commons.create_compartment {
create_push_target = function (col, row)
return realearn.Target.Virtual {
id = `col{col +1 }/row{row + 1}/pad`,
character = "Multi",
}
end,
create_turn_target = function (col, row)
return realearn.Target.Virtual {
id = `col{col +1 }/row{row + 1}/knob`,
character = "Multi",
}
end,
}
@@ -0,0 +1,268 @@
--!strict
local midi_script = require("midi_script_source_runtime")
local module = {}
local color_palette: { midi_script.RgbColor } = {
{ r = 0, g = 0, b = 0 },
{ r = 0, g = 0, b = 255 },
{ r = 0, g = 21, b = 255 },
{ r = 0, g = 34, b = 255 },
{ r = 0, g = 46, b = 255 },
{ r = 0, g = 59, b = 255 },
{ r = 0, g = 68, b = 255 },
{ r = 0, g = 80, b = 255 },
{ r = 0, g = 93, b = 255 },
{ r = 0, g = 106, b = 255 },
{ r = 0, g = 119, b = 255 },
{ r = 0, g = 127, b = 255 },
{ r = 0, g = 140, b = 255 },
{ r = 0, g = 153, b = 255 },
{ r = 0, g = 165, b = 255 },
{ r = 0, g = 178, b = 255 },
{ r = 0, g = 191, b = 255 },
{ r = 0, g = 199, b = 255 },
{ r = 0, g = 212, b = 255 },
{ r = 0, g = 225, b = 255 },
{ r = 0, g = 238, b = 255 },
{ r = 0, g = 250, b = 255 },
{ r = 0, g = 255, b = 250 },
{ r = 0, g = 255, b = 237 },
{ r = 0, g = 255, b = 225 },
{ r = 0, g = 255, b = 212 },
{ r = 0, g = 255, b = 199 },
{ r = 0, g = 255, b = 191 },
{ r = 0, g = 255, b = 178 },
{ r = 0, g = 255, b = 165 },
{ r = 0, g = 255, b = 153 },
{ r = 0, g = 255, b = 140 },
{ r = 0, g = 255, b = 127 },
{ r = 0, g = 255, b = 119 },
{ r = 0, g = 255, b = 106 },
{ r = 0, g = 255, b = 93 },
{ r = 0, g = 255, b = 80 },
{ r = 0, g = 255, b = 67 },
{ r = 0, g = 255, b = 59 },
{ r = 0, g = 255, b = 46 },
{ r = 0, g = 255, b = 33 },
{ r = 0, g = 255, b = 21 },
{ r = 0, g = 255, b = 8 },
{ r = 0, g = 255, b = 0 },
{ r = 12, g = 255, b = 0 },
{ r = 25, g = 255, b = 0 },
{ r = 38, g = 255, b = 0 },
{ r = 51, g = 255, b = 0 },
{ r = 63, g = 255, b = 0 },
{ r = 72, g = 255, b = 0 },
{ r = 84, g = 255, b = 0 },
{ r = 97, g = 255, b = 0 },
{ r = 110, g = 255, b = 0 },
{ r = 123, g = 255, b = 0 },
{ r = 131, g = 255, b = 0 },
{ r = 144, g = 255, b = 0 },
{ r = 157, g = 255, b = 0 },
{ r = 170, g = 255, b = 0 },
{ r = 182, g = 255, b = 0 },
{ r = 191, g = 255, b = 0 },
{ r = 203, g = 255, b = 0 },
{ r = 216, g = 255, b = 0 },
{ r = 229, g = 255, b = 0 },
{ r = 242, g = 255, b = 0 },
{ r = 255, g = 255, b = 0 },
{ r = 255, g = 246, b = 0 },
{ r = 255, g = 233, b = 0 },
{ r = 255, g = 220, b = 0 },
{ r = 255, g = 208, b = 0 },
{ r = 255, g = 195, b = 0 },
{ r = 255, g = 187, b = 0 },
{ r = 255, g = 174, b = 0 },
{ r = 255, g = 161, b = 0 },
{ r = 255, g = 148, b = 0 },
{ r = 255, g = 135, b = 0 },
{ r = 255, g = 127, b = 0 },
{ r = 255, g = 114, b = 0 },
{ r = 255, g = 102, b = 0 },
{ r = 255, g = 89, b = 0 },
{ r = 255, g = 76, b = 0 },
{ r = 255, g = 63, b = 0 },
{ r = 255, g = 55, b = 0 },
{ r = 255, g = 42, b = 0 },
{ r = 255, g = 29, b = 0 },
{ r = 255, g = 16, b = 0 },
{ r = 255, g = 4, b = 0 },
{ r = 255, g = 0, b = 4 },
{ r = 255, g = 0, b = 16 },
{ r = 255, g = 0, b = 29 },
{ r = 255, g = 0, b = 42 },
{ r = 255, g = 0, b = 55 },
{ r = 255, g = 0, b = 63 },
{ r = 255, g = 0, b = 76 },
{ r = 255, g = 0, b = 89 },
{ r = 255, g = 0, b = 102 },
{ r = 255, g = 0, b = 114 },
{ r = 255, g = 0, b = 127 },
{ r = 255, g = 0, b = 135 },
{ r = 255, g = 0, b = 148 },
{ r = 255, g = 0, b = 161 },
{ r = 255, g = 0, b = 174 },
{ r = 255, g = 0, b = 186 },
{ r = 255, g = 0, b = 195 },
{ r = 255, g = 0, b = 208 },
{ r = 255, g = 0, b = 221 },
{ r = 255, g = 0, b = 233 },
{ r = 255, g = 0, b = 246 },
{ r = 255, g = 0, b = 255 },
{ r = 242, g = 0, b = 255 },
{ r = 229, g = 0, b = 255 },
{ r = 216, g = 0, b = 255 },
{ r = 204, g = 0, b = 255 },
{ r = 191, g = 0, b = 255 },
{ r = 182, g = 0, b = 255 },
{ r = 169, g = 0, b = 255 },
{ r = 157, g = 0, b = 255 },
{ r = 144, g = 0, b = 255 },
{ r = 131, g = 0, b = 255 },
{ r = 123, g = 0, b = 255 },
{ r = 110, g = 0, b = 255 },
{ r = 97, g = 0, b = 255 },
{ r = 85, g = 0, b = 255 },
{ r = 72, g = 0, b = 255 },
{ r = 63, g = 0, b = 255 },
{ r = 50, g = 0, b = 255 },
{ r = 38, g = 0, b = 255 },
{ r = 25, g = 0, b = 255 },
{ r = 240, g = 240, b = 225 },
}
type FeedbackEntry = {
behavior: number,
color: number?,
}
local dark = 30
local medium = 40
local bright = 47
local pulsing = 12
local blinking = 8
local red_color = 83
local default_color = 21
local feedback_table: { [string]: FeedbackEntry? } = {
["playtime.slot_state.empty"] = {
behavior = bright,
color = 0,
},
["playtime.slot_state.armed"] = {
behavior = dark,
color = red_color,
},
["playtime.slot_state.stopped"] = {
behavior = dark,
},
["playtime.slot_state.ignited"] = {
behavior = dark,
},
["playtime.slot_state.scheduled_for_play_start"] = {
behavior = medium,
},
["playtime.slot_state.playing"] = {
behavior = pulsing,
},
["playtime.slot_state.paused"] = {
behavior = dark,
},
["playtime.slot_state.scheduled_for_play_stop"] = {
behavior = blinking,
},
["playtime.slot_state.scheduled_for_play_restart"] = {
behavior = blinking,
},
["playtime.slot_state.scheduled_for_record_start"] = {
behavior = medium,
color = red_color,
},
["playtime.slot_state.recording"] = {
behavior = pulsing,
color = red_color,
},
["playtime.slot_state.scheduled_for_record_stop"] = {
behavior = blinking,
color = red_color,
},
}
--- Returns the 0-based index within the given palette array.
local function find_closest_color_in_palette(color: midi_script.RgbColor, palette: { midi_script.RgbColor }): number
local ifurthest = 0
local furthest = 3 * math.pow(255, 2) + 1
for i, c in palette do
if color.r == c.r and color.g == c.g and color.b == c.b then
return i - 1
end
local distance = math.pow((color.r - c.r), 2) + math.pow((color.g - c.g), 2) + math.pow((color.b - c.b), 2)
if distance < furthest then
furthest = distance
ifurthest = i - 1
end
end
return ifurthest
end
local function create_output(pad_index: number, color_index: number, behavior: number): midi_script.Output
return {
address = pad_index,
messages = {
{ 0xB1, pad_index, color_index },
{ 0xB2, pad_index, behavior },
},
}
end
function module.pad_script(
pad_index: number,
y: midi_script.InputValue,
context: midi_script.Context
): midi_script.Output
local off_color = 0x00
local off_output = create_output(pad_index, off_color, bright)
-- Handle "off" feedback
if y == nil then
return off_output
end
-- Handle numeric feedback
if type(y) == "number" then
-- Translate number to solid feedback color.
local color = math.floor(y * 127)
return create_output(pad_index, color, bright)
end
-- Handle text feedback
if type(y) == "string" then
-- This can be used as a grid controller for controlling a Playtime matrix, so we should be able to
-- handle text feedback that conforms to Playtime's clip state convention.
local entry = feedback_table[y]
if entry == nil then
-- Unknown texts switch the LED off
return off_output
end
local explicit_color = entry.color
if explicit_color ~= nil then
-- Switch LED to explicit color
return create_output(pad_index, explicit_color, entry.behavior)
end
-- No explicit color means we should use the event color (which is set in the "Glue" section)
local event_color = context.feedback_event.color
if event_color == nil then
-- Event has default color / no specific color
return create_output(pad_index, default_color, entry.behavior)
end
-- Find color available on the controller that's closest to the desired RGB color
local closest_color = find_closest_color_in_palette(event_color, color_palette)
return create_output(pad_index, closest_color, entry.behavior)
end
-- Complex feedback is highly individual. It doesn't make sense to handle this in a general-purpose controller preset.
return {
address = pad_index,
messages = {},
}
end
return module
@@ -0,0 +1,605 @@
--!strict
local preset_runtime = require("preset_runtime")
local realearn = require("realearn")
local module = {}
export type MidiFighterTwisterPresetConfig = {
create_turn_target: (col: number, row: number) -> realearn.Target,
create_push_target: (col: number, row: number) -> realearn.Target,
}
local common_lua = preset_runtime.include_str("djtechtools/midi-fighter-twister-lib/compartment-common.luau")
local function build_pad_script(pad_index: number): string
return `return require("compartment").pad_script({pad_index}, y, context)`
end
function module.create_compartment(config: MidiFighterTwisterPresetConfig): realearn.Compartment
-- Preparation
local function pad_control(col: number, row: number, cc_number: number): realearn.Mapping
return realearn.Mapping {
id = `col{col + 1}/row{row + 1}/pad`,
feedback_enabled = false,
source = realearn.Source.MidiControlChangeValue {
channel = 1,
controller_number = cc_number,
character = "Button",
},
target = config.create_push_target(col, row),
}
end
local function pad_feedback(col: number, row: number, cc_number: number): realearn.Mapping
return realearn.Mapping {
id = `col{col + 1}/row{row + 1}/pad/feedback`,
control_enabled = false,
visible_in_projection = false,
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = build_pad_script(cc_number),
},
target = config.create_push_target(col, row)
}
end
local function knob(col: number, row: number, cc_number: number): realearn.Mapping
return realearn.Mapping {
id = `col{col + 1}/row{row + 1}/knob`,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = cc_number,
character = "Relative2",
},
glue = {
step_factor_interval = { 1, 100 }
},
target = config.create_turn_target(col, row),
}
end
local function side_button(id: string, cc_number: number): realearn.Mapping
return realearn.Mapping {
id = id,
feedback_enabled = false,
source = realearn.Source.MidiControlChangeValue {
channel = 3,
controller_number = cc_number,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
end
local mappings: { realearn.Mapping } = {
-- Side buttons
side_button("bank-left", 8),
side_button("cursor-left", 9),
side_button("ch-left", 10),
side_button("bank-right", 11),
side_button("cursor-right", 12),
side_button("ch-right", 13),
}
-- Grid
local column_count = 4
local row_count = 4
for col = 0, column_count - 1 do
for row = 0, row_count - 1 do
local cc_number = row * row_count + col
table.insert(mappings, pad_control(col, row, cc_number))
table.insert(mappings, pad_feedback(col, row, cc_number))
table.insert(mappings, knob(col, row, cc_number))
end
end
-- Companion data
local companion_data = {
controls = {
{
height = 50,
id = "col4/row1/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col4/row1/knob",
"col4/row1/pad",
},
shape = "circle",
width = 50,
x = 400,
y = 0,
},
{
height = 50,
id = "col1/row2/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col1/row2/knob",
"col1/row2/pad",
},
shape = "circle",
width = 50,
x = 100,
y = 100,
},
{
height = 50,
id = "col4/row2/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col4/row2/knob",
"col4/row2/pad",
},
shape = "circle",
width = 50,
x = 400,
y = 100,
},
{
height = 50,
id = "col1/row1/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col1/row1/knob",
"col1/row1/pad",
},
shape = "circle",
width = 50,
x = 100,
y = 0,
},
{
height = 50,
id = "col3/row1/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col3/row1/knob",
"col3/row1/pad",
},
shape = "circle",
width = 50,
x = 300,
y = 0,
},
{
height = 50,
id = "col2/row1/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col2/row1/knob",
"col2/row1/pad",
},
shape = "circle",
width = 50,
x = 200,
y = 0,
},
{
height = 50,
id = "col2/row2/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col2/row2/knob",
"col2/row2/pad",
},
shape = "circle",
width = 50,
x = 200,
y = 100,
},
{
height = 50,
id = "col3/row2/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col3/row2/knob",
"col3/row2/pad",
},
shape = "circle",
width = 50,
x = 300,
y = 100,
},
{
height = 50,
id = "col2/row3/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col2/row3/knob",
"col2/row3/pad",
},
shape = "circle",
width = 50,
x = 200,
y = 200,
},
{
height = 50,
id = "col3/row3/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col3/row3/knob",
"col3/row3/pad",
},
shape = "circle",
width = 50,
x = 300,
y = 200,
},
{
height = 50,
id = "col4/row4/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col4/row4/knob",
"col4/row4/pad",
},
shape = "circle",
width = 50,
x = 400,
y = 300,
},
{
height = 50,
id = "col1/row4/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col1/row4/knob",
"col1/row4/pad",
},
shape = "circle",
width = 50,
x = 100,
y = 300,
},
{
height = 50,
id = "col2/row4/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col2/row4/knob",
"col2/row4/pad",
},
shape = "circle",
width = 50,
x = 200,
y = 300,
},
{
height = 50,
id = "col3/row4/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col3/row4/knob",
"col3/row4/pad",
},
shape = "circle",
width = 50,
x = 300,
y = 300,
},
{
height = 50,
id = "col1/row3/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col1/row3/knob",
"col1/row3/pad",
},
shape = "circle",
width = 50,
x = 100,
y = 200,
},
{
height = 50,
id = "col4/row3/knob",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "center",
["sizeConstrained"] = true,
},
mappings = {
"col4/row3/knob",
"col4/row3/pad",
},
shape = "circle",
width = 50,
x = 400,
y = 200,
},
{
height = 50,
id = "bank-right",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "belowBottom",
["sizeConstrained"] = true,
},
mappings = {
"bank-right",
},
shape = "rectangle",
width = 50,
x = 500,
y = 50,
},
{
height = 50,
id = "bank-left",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "belowBottom",
["sizeConstrained"] = true,
},
mappings = {
"bank-left",
},
shape = "rectangle",
width = 50,
x = 0,
y = 50,
},
{
height = 50,
id = "a78b277e-cfbf-4b2b-9cc6-1a550aeb87fd",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "belowBottom",
["sizeConstrained"] = true,
},
mappings = {
"ch-left",
},
shape = "rectangle",
width = 50,
x = 0,
y = 250,
},
{
height = 50,
id = "e312d2a2-ecf1-4189-95af-4174c43a750c",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "belowBottom",
["sizeConstrained"] = true,
},
mappings = {
"ch-right",
},
shape = "rectangle",
width = 50,
x = 500,
y = 250,
},
{
height = 50,
id = "cursor-left",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "belowBottom",
["sizeConstrained"] = true,
},
mappings = {
"cursor-left",
},
shape = "rectangle",
width = 50,
x = 0,
y = 150,
},
{
height = 50,
id = "cursor-right",
["labelOne"] = {
angle = 0,
position = "aboveTop",
["sizeConstrained"] = true,
},
["labelTwo"] = {
angle = 0,
position = "belowBottom",
["sizeConstrained"] = true,
},
mappings = {
"cursor-right",
},
shape = "rectangle",
width = 50,
x = 500,
y = 150,
},
},
["gridDivisionCount"] = 2,
["gridSize"] = 50,
}
-- Result
return realearn.Compartment {
mappings = mappings,
common_lua = common_lua,
custom_data = {
companion = companion_data,
numbered = {
multi_count = column_count * row_count,
button_count = column_count * row_count,
},
grid = {
column_count = column_count,
row_count = row_count,
},
},
}
end
return module
@@ -0,0 +1,35 @@
--- name: Midi Fighter Twister - Numbered
--- realearn_version: 2.16.0-pre.8
--- author: helgoboss
--- device_manufacturer: DJ TechTools
--- device_name: Midi Fighter Twister
--- description: |
--- This controller preset exposes the push encoders of the Midi Fighter Twister with the "numbered" scheme, which means
--- it uses virtual element IDs such as button 5 or knob 3. This makes it compatible with main presets that are
--- built for generic controllers that have a row of buttons and knobs.
--- setup_instructions: |
--- The Midi Fighter Twister is a very customizable controller. Please follow the instructions at
--- https://github.com/helgoboss/helgobox/blob/master/doc/controllers.adoc#dj-techtools-midi-fighter-twister
--- to make it ready for ReaLearn.
--- midi_identity_pattern: F0 7E ? 06 02 00 01 79 05 00 01 00 * F7
--- provided_schemes: [djtechtools/midi-fighter-twister/numbered, numbered]
--!strict
local realearn = require("realearn")
local commons = require("djtechtools/midi-fighter-twister-lib/preset-common")
return commons.create_compartment {
create_push_target = function (col, row)
return realearn.Target.Virtual {
id = row * 4 + col,
character = "Button",
}
end,
create_turn_target = function (col, row)
return realearn.Target.Virtual {
id = row * 4 + col,
character = "Multi",
}
end,
}
@@ -0,0 +1,729 @@
--!strict
local realearn = require("realearn")
local module = {}
export type McuPresetConfig = {
companion_data: any,
support_x_touch_colors: boolean,
}
function module.create_compartment(config: McuPresetConfig): realearn.Compartment
local channel_count = 8;
-- Replace with "XTouchMackieLcd" in order to be able to benefit from colors when using X-Touch.
local lcd_kind = "MackieLcd";
local binary_eight = {
[0] = "000",
[1] = "001",
[2] = "010",
[3] = "011",
[4] = "100",
[5] = "101",
[6] = "110",
[7] = "111",
}
local groups: { realearn.Group } = {
{
id = "fader",
name = "Fader",
},
{
id = "v-pot",
name = "V-Pot",
},
{
id = "v-select",
name = "V-Select",
},
{
id = "fader-touch",
name = "Fader Touch",
},
{
id = "select",
name = "Select",
},
{
id = "mute",
name = "Mute",
},
{
id = "solo",
name = "Solo",
},
{
id = "record-ready",
name = "Record-ready",
},
{
id = "v-pot-leds",
name = "V-Pot LEDs",
},
{
id = "lcd",
name = "LCD",
},
{
id = "meter",
name = "Meter",
},
}
local mappings: { realearn.Mapping } = {
{
id = "main/fader",
group = "fader",
source = realearn.Source.MidiPitchBendChangeValue {
channel = 8,
},
target = realearn.Target.Virtual {
id = "main/fader",
},
},
{
id = "jog",
feedback_enabled = false,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 60,
character = "Relative1",
fourteen_bit = false,
},
target = realearn.Target.Virtual {
id = "jog",
},
},
{
id = "main/fader/touch",
group = "fader-touch",
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 112,
},
target = realearn.Target.Virtual {
id = "main/fader/touch",
character = "Button",
learnable = false,
},
},
{
id = "marker",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 84,
},
target = realearn.Target.Virtual {
id = "marker",
character = "Button",
},
},
{
id = "read",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 74,
},
target = realearn.Target.Virtual {
id = "read",
character = "Button",
},
},
{
id = "write",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 75,
},
target = realearn.Target.Virtual {
id = "write",
character = "Button",
},
},
{
id = "ch-left",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 48,
},
target = realearn.Target.Virtual {
id = "ch-left",
character = "Button",
},
},
{
id = "ch-right",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 49,
},
target = realearn.Target.Virtual {
id = "ch-right",
character = "Button",
},
},
{
id = "bank-left",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 46,
},
target = realearn.Target.Virtual {
id = "bank-left",
character = "Button",
},
},
{
id = "bank-right",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 47,
},
target = realearn.Target.Virtual {
id = "bank-right",
character = "Button",
},
},
{
id = "rewind",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 91,
},
target = realearn.Target.Virtual {
id = "rewind",
character = "Button",
},
},
{
id = "fast-fwd",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 92,
},
target = realearn.Target.Virtual {
id = "fast-fwd",
character = "Button",
},
},
{
id = "play",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 94,
},
target = realearn.Target.Virtual {
id = "play",
character = "Button",
},
},
{
id = "stop",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 93,
},
target = realearn.Target.Virtual {
id = "stop",
character = "Button",
},
},
{
id = "record",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 95,
},
target = realearn.Target.Virtual {
id = "record",
character = "Button",
},
},
{
id = "cycle",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 86,
},
target = realearn.Target.Virtual {
id = "cycle",
character = "Button",
},
},
{
id = "zoom",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 100,
},
target = realearn.Target.Virtual {
id = "zoom",
character = "Button",
},
},
{
id = "scrub",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 101,
},
target = realearn.Target.Virtual {
id = "scrub",
character = "Button",
},
},
{
id = "cursor-left",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 98,
},
target = realearn.Target.Virtual {
id = "cursor-left",
character = "Button",
},
},
{
id = "cursor-right",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 99,
},
target = realearn.Target.Virtual {
id = "cursor-right",
character = "Button",
},
},
{
id = "cursor-up",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 96,
},
target = realearn.Target.Virtual {
id = "cursor-up",
character = "Button",
},
},
{
id = "cursor-down",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 97,
},
target = realearn.Target.Virtual {
id = "cursor-down",
character = "Button",
},
},
{
id = "nudge",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 85,
},
target = realearn.Target.Virtual {
id = "nudge",
character = "Button",
},
},
{
id = "drop",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 87,
},
target = realearn.Target.Virtual {
id = "drop",
character = "Button",
},
},
{
id = "replace",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 88,
},
target = realearn.Target.Virtual {
id = "replace",
character = "Button",
},
},
{
id = "click",
source = {
kind = "MidiNoteVelocity",
channel = 0,
key_number = 89,
},
target = realearn.Target.Virtual {
id = "click",
character = "Button",
},
},
{
id = "solo",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 90,
},
target = realearn.Target.Virtual {
id = "solo",
character = "Button",
},
},
{
id = "f1",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 54,
},
target = realearn.Target.Virtual {
id = "f1",
character = "Button",
},
},
{
id = "f2",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 55,
},
target = realearn.Target.Virtual {
id = "f2",
character = "Button",
},
},
{
id = "f3",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 56,
},
target = realearn.Target.Virtual {
id = "f3",
character = "Button",
},
},
{
id = "f4",
source = {
kind = "MidiNoteVelocity",
channel = 0,
key_number = 57,
},
target = realearn.Target.Virtual {
id = "f4",
character = "Button",
},
},
{
id = "f5",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 58,
},
target = realearn.Target.Virtual {
id = "f5",
character = "Button",
},
},
{
id = "f6",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 59,
},
target = realearn.Target.Virtual {
id = "f6",
character = "Button",
},
},
{
id = "smpte-beats",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 53,
},
target = realearn.Target.Virtual {
id = "smpte-beats",
character = "Button",
},
},
{
id = "lcd/assignment",
group = "lcd",
control_enabled = false,
source = realearn.Source.MackieSevenSegmentDisplay {
},
target = realearn.Target.Virtual {
id = "lcd/assignment",
},
},
{
id = "lcd/timecode",
group = "lcd",
control_enabled = false,
source = realearn.Source.MackieSevenSegmentDisplay {
scope = "Tc",
},
target = realearn.Target.Virtual {
id = "lcd/timecode",
},
},
}
-- For each channel
for ch = 0, channel_count - 1 do
local human_ch = ch + 1
local prefix = "ch"..human_ch.."/"
local v_select = realearn.Mapping {
id = prefix.."v-select",
group = "v-select",
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 32 + ch,
},
target = realearn.Target.Virtual {
id = prefix.."v-select",
character = "Button",
},
}
local fader_touch = realearn.Mapping {
id = prefix.."fader/touch",
group = "fader-touch",
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 104 + ch,
},
target = realearn.Target.Virtual {
id = prefix.."fader/touch",
character = "Button",
learnable = false,
},
}
local select = realearn.Mapping {
id = prefix.."select",
group = "select",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 24 + ch,
},
target = realearn.Target.Virtual {
id = prefix.."select",
character = "Button",
},
}
local fader = realearn.Mapping {
id = prefix.."fader",
group = "fader",
source = realearn.Source.MidiPitchBendChangeValue {
channel = ch,
},
target = realearn.Target.Virtual {
id = prefix.."fader",
},
}
local v_pot_control = realearn.Mapping {
id = prefix.."v-pot/control",
group = "v-pot",
feedback_enabled = false,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 16 + ch,
character = "Relative3",
fourteen_bit = false,
},
glue = {
step_factor_interval = {1, 100},
},
target = realearn.Target.Virtual {
id = prefix.."v-pot",
},
}
local v_pot_feedback_default = realearn.Mapping {
id = prefix.."v-pot/feedback",
group = "v-pot-leds",
control_enabled = false,
source = realearn.Source.MidiRaw {
pattern = "B0 3"..ch.." [0000 dcba]",
},
glue = {
source_interval = {0, 0.75},
},
target = realearn.Target.Virtual {
id = prefix.."v-pot",
},
}
local v_pot_feedback_wrap = realearn.Mapping {
id = prefix.."v-pot/wrap",
group = "v-pot-leds",
control_enabled = false,
source = realearn.Source.MidiRaw {
pattern = "B0 3"..ch.." [0010 dcba]",
},
glue = {
source_interval = {0, 0.75},
},
target = realearn.Target.Virtual {
id = prefix.."v-pot/wrap",
},
}
local v_pot_feedback_boost_cut = realearn.Mapping {
id = prefix.."v-pot/boost-cut",
group = "v-pot-leds",
control_enabled = false,
source = realearn.Source.MidiRaw {
pattern = "B0 3"..ch.." [0001 dcba]",
},
glue = {
source_interval = {0.05, 0.75},
},
target = realearn.Target.Virtual {
id = prefix.."v-pot/boost-cut",
},
}
local v_pot_feedback_single = realearn.Mapping {
id = prefix.."v-pot/single",
group = "v-pot-leds",
control_enabled = false,
source = realearn.Source.MidiRaw {
pattern = "B0 3"..ch.." [0000 dcba]",
},
glue = {
source_interval = {0, 0.75},
},
target = realearn.Target.Virtual {
id = prefix.."v-pot/single",
},
}
local v_pot_feedback_spread = realearn.Mapping {
id = prefix.."v-pot/spread",
group = "v-pot-leds",
control_enabled = false,
source = realearn.Source.MidiRaw {
pattern = "B0 3"..ch.." [0011 dcba]",
},
glue = {
source_interval = {0, 0.4},
},
target = realearn.Target.Virtual {
id = prefix.."v-pot/spread",
},
}
local mute = realearn.Mapping {
id = prefix.."mute",
group = "mute",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 16 + ch,
},
target = {
kind = "Virtual",
id = prefix.."mute",
character = "Button",
},
}
local solo = realearn.Mapping {
id = prefix.."solo",
group = "solo",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 8 + ch,
},
target = realearn.Target.Virtual {
id = prefix.."solo",
character = "Button",
},
}
local record_ready = realearn.Mapping {
id = prefix.."record-ready",
group = "record-ready",
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 0 + ch,
},
target = realearn.Target.Virtual {
id = prefix.."record-ready",
character = "Button",
},
}
local normal_lcd_line_1 = realearn.Source.MackieLcd {
channel = ch,
line = 0,
}
local x_touch_lcd_line_1 = realearn.Source.XTouchMackieLcd {
channel = ch,
line = 0,
}
local lcd_line1 = realearn.Mapping {
id = prefix.."lcd/line1",
group = "lcd",
control_enabled = false,
source = if config.support_x_touch_colors then x_touch_lcd_line_1 else normal_lcd_line_1,
target = realearn.Target.Virtual {
id = prefix.."lcd/line1",
},
}
local normal_lcd_line_2 = realearn.Source.MackieLcd {
channel = ch,
line = 1,
}
local x_touch_lcd_line_2 = realearn.Source.XTouchMackieLcd {
channel = ch,
line = 1,
}
local lcd_line2 = realearn.Mapping {
id = prefix.."lcd/line2",
group = "lcd",
control_enabled = false,
source = if config.support_x_touch_colors then x_touch_lcd_line_2 else normal_lcd_line_2,
target = realearn.Target.Virtual {
id = prefix.."lcd/line2",
},
}
local meter = realearn.Mapping {
id = prefix.."meter/peak",
group = "meter",
control_enabled = false,
source = realearn.Source.MidiRaw {
pattern = "D0 [0"..binary_eight[ch].." dcba]",
},
target = realearn.Target.Virtual {
id = prefix.."meter/peak",
},
}
table.insert(mappings, v_select)
table.insert(mappings, fader_touch)
table.insert(mappings, select)
table.insert(mappings, fader)
table.insert(mappings, v_pot_control)
table.insert(mappings, v_pot_feedback_default)
table.insert(mappings, v_pot_feedback_wrap)
table.insert(mappings, v_pot_feedback_boost_cut)
table.insert(mappings, v_pot_feedback_single)
table.insert(mappings, v_pot_feedback_spread)
table.insert(mappings, mute)
table.insert(mappings, solo)
table.insert(mappings, record_ready)
table.insert(mappings, lcd_line1)
table.insert(mappings, lcd_line2)
table.insert(mappings, meter)
end
return realearn.Compartment {
groups = groups,
mappings = mappings,
custom_data = {
companion = config.companion_data,
},
}
end
return module
@@ -0,0 +1,21 @@
--- name: Mackie Control Universal
--- realearn_version: 2.16.0
--- author: helgoboss
--- device_manufacturer: Mackie
--- device_name: Control Universal
--- description: |
--- This controller preset implements the Mackie Control Universal (MCU) MIDI protocol. It is supported by many
--- DAW control surfaces out there. I have tested it with Behringer X-Touch One, Behringer X-Touch Compact and
--- iCON Platform M+.
--- setup_instructions: |
--- You probably must put your controller into MCU mode for using this controller preset.
--- provided_schemes: [daw]
--!strict
local commons = require("mackie/control-universal-lib/preset-common")
return commons.create_compartment {
companion_data = nil,
support_x_touch_colors = false,
}
@@ -0,0 +1 @@
../../api/luau/midi_script_source_runtime.luau
@@ -0,0 +1,330 @@
--!strict
local midi_script = require("midi_script_source_runtime")
local module = {}
--- There's no way to put pads in a pulsing state with the true RGB sys-ex messages, so
--- we are back to an old-fashioned color palette. Whatever, close enough.
local color_palette: { midi_script.RgbColor } = {
{ r = 0x61, g = 0x61, b = 0x61 },
{ r = 0xb3, g = 0xb3, b = 0xb3 },
{ r = 0xdd, g = 0xdd, b = 0xdd },
{ r = 0xff, g = 0xff, b = 0xff },
{ r = 0xfc, g = 0xb3, b = 0xb3 },
{ r = 0xfa, g = 0x61, b = 0x61 },
{ r = 0xdd, g = 0x61, b = 0x61 },
{ r = 0xb3, g = 0x61, b = 0x61 },
{ r = 0xfe, g = 0xf3, b = 0xd5 },
{ r = 0xfb, g = 0xb3, b = 0x61 },
{ r = 0xdd, g = 0x8c, b = 0x61 },
{ r = 0xb3, g = 0x76, b = 0x61 },
{ r = 0xfc, g = 0xee, b = 0xa1 },
{ r = 0xfb, g = 0xff, b = 0x61 },
{ r = 0xdd, g = 0xdd, b = 0x61 },
{ r = 0xb3, g = 0xb3, b = 0x61 },
{ r = 0xdd, g = 0xff, b = 0xa1 },
{ r = 0xc2, g = 0xff, b = 0x61 },
{ r = 0xa1, g = 0xdd, b = 0x61 },
{ r = 0x81, g = 0xb3, b = 0x61 },
{ r = 0xc2, g = 0xff, b = 0xb3 },
{ r = 0x61, g = 0xfe, b = 0x61 },
{ r = 0x61, g = 0xdd, b = 0x61 },
{ r = 0x61, g = 0xb3, b = 0x61 },
{ r = 0xc2, g = 0xff, b = 0xc2 },
{ r = 0x61, g = 0xfe, b = 0x8c },
{ r = 0x61, g = 0xdd, b = 0x76 },
{ r = 0x61, g = 0xb3, b = 0x6b },
{ r = 0xc2, g = 0xfe, b = 0xcc },
{ r = 0x61, g = 0xfe, b = 0xcc },
{ r = 0x61, g = 0xdd, b = 0xa1 },
{ r = 0x61, g = 0xb3, b = 0x81 },
{ r = 0xc2, g = 0xfe, b = 0xf3 },
{ r = 0x61, g = 0xfd, b = 0xe9 },
{ r = 0x61, g = 0xdd, b = 0xc2 },
{ r = 0x61, g = 0xb3, b = 0x96 },
{ r = 0xc2, g = 0xf3, b = 0xff },
{ r = 0x61, g = 0xee, b = 0xff },
{ r = 0x61, g = 0xc7, b = 0xdd },
{ r = 0x61, g = 0xa1, b = 0xb3 },
{ r = 0xc2, g = 0xdd, b = 0xff },
{ r = 0x61, g = 0xc7, b = 0xff },
{ r = 0x61, g = 0xa1, b = 0xdd },
{ r = 0x61, g = 0x81, b = 0xb3 },
{ r = 0xa1, g = 0x8c, b = 0xff },
{ r = 0x61, g = 0x61, b = 0xff },
{ r = 0x61, g = 0x61, b = 0xdd },
{ r = 0x61, g = 0x61, b = 0xb3 },
{ r = 0xcc, g = 0xb3, b = 0xff },
{ r = 0xa1, g = 0x61, b = 0xff },
{ r = 0x81, g = 0x61, b = 0xdd },
{ r = 0x76, g = 0x61, b = 0xb3 },
{ r = 0xfe, g = 0xb3, b = 0xff },
{ r = 0xfe, g = 0x61, b = 0xff },
{ r = 0xdd, g = 0x61, b = 0xdd },
{ r = 0xb3, g = 0x61, b = 0xb3 },
{ r = 0xfd, g = 0xb3, b = 0xd5 },
{ r = 0xfc, g = 0x61, b = 0xc2 },
{ r = 0xdd, g = 0x61, b = 0xa1 },
{ r = 0xb3, g = 0x61, b = 0x8c },
{ r = 0xfa, g = 0x76, b = 0x61 },
{ r = 0xe9, g = 0xb3, b = 0x61 },
{ r = 0xdd, g = 0xc2, b = 0x61 },
{ r = 0xa1, g = 0xa1, b = 0x61 },
{ r = 0x61, g = 0xb3, b = 0x61 },
{ r = 0x61, g = 0xb3, b = 0x8c },
{ r = 0x61, g = 0x8c, b = 0xd5 },
{ r = 0x61, g = 0x61, b = 0xff },
{ r = 0x61, g = 0xb3, b = 0xb3 },
{ r = 0x8c, g = 0x62, b = 0xf3 },
{ r = 0xcc, g = 0xb3, b = 0xc2 },
{ r = 0x8c, g = 0x76, b = 0x81 },
{ r = 0xfa, g = 0x61, b = 0x61 },
{ r = 0xf3, g = 0xff, b = 0xa1 },
{ r = 0xee, g = 0xfc, b = 0x61 },
{ r = 0xcc, g = 0xff, b = 0x60 },
{ r = 0x76, g = 0xdd, b = 0x61 },
{ r = 0x61, g = 0xfe, b = 0xcc },
{ r = 0x61, g = 0xe9, b = 0xff },
{ r = 0x61, g = 0xa1, b = 0xff },
{ r = 0x8c, g = 0x61, b = 0xff },
{ r = 0xcc, g = 0x61, b = 0xfc },
{ r = 0xee, g = 0x8c, b = 0xdd },
{ r = 0xa1, g = 0x76, b = 0x61 },
{ r = 0xfb, g = 0xa1, b = 0x61 },
{ r = 0xdd, g = 0xf9, b = 0x62 },
{ r = 0xd5, g = 0xff, b = 0x8c },
{ r = 0x61, g = 0xfe, b = 0x61 },
{ r = 0xb3, g = 0xff, b = 0xa1 },
{ r = 0xcc, g = 0xfc, b = 0xd5 },
{ r = 0xb3, g = 0xfe, b = 0xf6 },
{ r = 0xcc, g = 0xe4, b = 0xff },
{ r = 0xa1, g = 0xc2, b = 0xf6 },
{ r = 0xd5, g = 0xc2, b = 0xf9 },
{ r = 0xf9, g = 0x8c, b = 0xff },
{ r = 0xfc, g = 0x61, b = 0xcc },
{ r = 0xfb, g = 0xc2, b = 0x61 },
{ r = 0xf3, g = 0xee, b = 0x61 },
{ r = 0xe4, g = 0xff, b = 0x61 },
{ r = 0xdd, g = 0xcc, b = 0x61 },
{ r = 0xb3, g = 0xa1, b = 0x61 },
{ r = 0x61, g = 0xba, b = 0x76 },
{ r = 0x76, g = 0xc2, b = 0x8c },
{ r = 0x81, g = 0x81, b = 0xa1 },
{ r = 0x81, g = 0x8c, b = 0xcc },
{ r = 0xcc, g = 0xaa, b = 0x81 },
{ r = 0xdd, g = 0x61, b = 0x61 },
{ r = 0xf9, g = 0xb3, b = 0xa1 },
{ r = 0xf9, g = 0xba, b = 0x76 },
{ r = 0xfc, g = 0xf3, b = 0x8d },
{ r = 0xe9, g = 0xf9, b = 0xa1 },
{ r = 0xd5, g = 0xee, b = 0x76 },
{ r = 0x81, g = 0x81, b = 0xa1 },
{ r = 0xf9, g = 0xf9, b = 0xd5 },
{ r = 0xdd, g = 0xfc, b = 0xe4 },
{ r = 0xe9, g = 0xe9, b = 0xff },
{ r = 0xe4, g = 0xd5, b = 0xff },
{ r = 0xb3, g = 0xb3, b = 0xb3 },
{ r = 0xd5, g = 0xd5, b = 0xd5 },
{ r = 0xf9, g = 0xff, b = 0xff },
{ r = 0xe9, g = 0x61, b = 0x61 },
{ r = 0xaa, g = 0x62, b = 0x61 },
{ r = 0x81, g = 0xf6, b = 0x62 },
{ r = 0x61, g = 0xb3, b = 0x61 },
{ r = 0xf3, g = 0xee, b = 0x61 },
{ r = 0xb3, g = 0xa1, b = 0x61 },
{ r = 0xee, g = 0xc2, b = 0x61 },
{ r = 0xc2, g = 0x76, b = 0x61 },
}
--- Returns the 0-based index within the given palette array.
local function find_closest_color_in_palette(color: midi_script.RgbColor, palette: { midi_script.RgbColor }): number
local ifurthest = 0
local furthest = 3 * math.pow(255, 2) + 1
for i, c in palette do
if color.r == c.r and color.g == c.g and color.b == c.b then
return i - 1
end
local distance = math.pow((color.r - c.r), 2) + math.pow((color.g - c.g), 2) + math.pow((color.b - c.b), 2)
if distance < furthest then
furthest = distance
ifurthest = i - 1
end
end
return ifurthest
end
type FeedbackEntry = {
behavior: number,
color: (number | midi_script.RgbColor)?,
brightness: number?,
}
local solid = 0x90
local flashing = 0x91
local pulsing = 0x92
local black_color = 0
local green_color = 0x15
local white_color = { r = 255, g = 255, b = 255 }
local red_color = { r = 255, g = 0, b = 0 }
local dark = 0.75
local medium = 0.90
local function create_feedback_table(play_color: number?): { [string]: FeedbackEntry? }
return {
["playtime.slot_state.empty"] = {
behavior = solid,
color = black_color,
},
["playtime.slot_state.armed"] = {
behavior = solid,
color = red_color,
brightness = dark,
},
["playtime.slot_state.stopped"] = {
behavior = solid,
brightness = dark,
},
["playtime.slot_state.ignited"] = {
behavior = solid,
brightness = dark,
},
["playtime.slot_state.scheduled_for_play_start"] = {
behavior = flashing,
color = play_color,
},
["playtime.slot_state.playing"] = {
behavior = pulsing,
color = play_color,
},
["playtime.slot_state.paused"] = {
behavior = solid,
brightness = medium,
},
["playtime.slot_state.scheduled_for_play_stop"] = {
behavior = flashing,
},
["playtime.slot_state.scheduled_for_play_restart"] = {
behavior = flashing,
color = play_color,
},
["playtime.slot_state.scheduled_for_record_start"] = {
behavior = solid,
color = red_color,
brightness = medium,
},
["playtime.slot_state.recording"] = {
behavior = pulsing,
color = red_color,
},
["playtime.slot_state.scheduled_for_record_stop"] = {
behavior = flashing,
color = red_color,
}
}
end
-- Uses original color when playing (currently disabled, will probably be a configuration option in the future)
local colorful_feedback_table = create_feedback_table(nil)
-- Uses green color when playing
local green_feedback_table = create_feedback_table(green_color)
local function build_messages(
pad_index: number,
behavior: number,
color_index: number
): { midi_script.MidiMessage }
if behavior == pulsing then
return {
{ 0x92, pad_index, color_index },
}
elseif behavior == flashing then
-- Flashing between actual color and black
return {
{ 0x90, pad_index, color_index },
{ 0x91, pad_index, black_color },
}
else
-- Solid
return {
{ 0x90, pad_index, color_index },
}
end
end
local function adjust_brightness(c: midi_script.RgbColor, factor: number): midi_script.RgbColor
return {
r = math.floor(c.r * factor),
g = math.floor(c.g * factor),
b = math.floor(c.b * factor),
}
end
local function transform_and_find_closest_color_in_palette(original_color: midi_script.RgbColor, brightness: number?): number
local transformed_color = if brightness then adjust_brightness(original_color, brightness) else original_color
local index = find_closest_color_in_palette(transformed_color, color_palette)
if index == 0 then
-- Don't let it become black just because of the darkening
return find_closest_color_in_palette(original_color, color_palette)
end
return index
end
local function get_palette_color_index(entry: FeedbackEntry, rgb_color: midi_script.RgbColor?): number
if type(entry.color) == "number" then
return entry.color
end
local final_rgb_color = entry.color or rgb_color or white_color
return transform_and_find_closest_color_in_palette(final_rgb_color, entry.brightness)
end
function module.pad_script(
pad_index: number,
y: midi_script.InputValue,
context: midi_script.Context
): midi_script.Output
local address = pad_index
local off_output = {
address = pad_index,
messages = build_messages(pad_index, solid, black_color),
}
-- Handle "off" feedback
if y == nil then
return off_output
end
-- Handle numeric feedback
if type(y) == "number" then
-- Translate number to solid feedback color
local color_index = math.floor(y * 127)
return {
address = address,
messages = {
{ solid, pad_index, color_index },
},
}
end
-- Handle text feedback
if type(y) == "string" then
-- This is a grid controller, typically used for controlling a Playtime matrix, so we should be able to
-- handle text feedback that conforms to Playtime's clip state convention.
local entry = green_feedback_table[y]
if entry == nil then
-- Unknown texts switch the LED off
return off_output
end
local color_index = get_palette_color_index(entry, context.feedback_event.color)
return {
address = pad_index,
messages = build_messages(pad_index, entry.behavior, color_index),
}
end
-- Complex feedback is highly individual. It doesn't make sense to handle this in a general-purpose controller preset.
return {
address = address,
messages = {},
}
end
return module
@@ -0,0 +1,152 @@
--- name: Launchpad Mini mk3 - Live mode
--- realearn_version: 2.16.0-pre.8
--- author: helgoboss
--- device_manufacturer: Novation
--- device_name: Launchpad Mini mk3
--- description: |
--- This controller preset exposes all control elements of the Launchpad Mini mk3 in a neutral way. It supports
--- ReaLearn color feedback as well as Playtime slot status feedback.
--- midi_identity_pattern: F0 7E ? 06 02 00 20 29 13 01 00 00 * F7
--- midi_output_port_patterns: ["macos:*DAW*", "windows:LPMiniMK3 MIDI"]
--- provided_schemes: [novation/launchpad-mini-mk3/live, grid]
--!strict
-- Requires
local preset_runtime = require("preset_runtime")
local realearn = require("realearn")
-- Define MIDI scripts
local common_lua = preset_runtime.include_str("novation/launchpad-lib/compartment-common.luau")
local function build_pad_script(pad_index: number): string
return `return require("compartment").pad_script({pad_index}, y, context)`
end
-- Single buttons
local function simple_button(id: string, name: string, cc: number): realearn.Mapping
return realearn.Mapping {
id = id,
name = name,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = cc,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
end
local init_mapping = realearn.Mapping {
name = "Enter DAW mode and select session layout",
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = [[
local standalone_mode = 0x00
local daw_mode = 0x01
local session_layout = 0x00
local mode = if y == nil then standalone_mode else daw_mode
local enter_or_leave_daw_mode_msg = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0D, 0x10, mode, 0xF7 }
local select_session_layout = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0D, 0x00, session_layout, 0xF7 }
local messages = {
enter_or_leave_daw_mode_msg,
}
if y then
table.insert(messages, select_session_layout)
end
return {
messages = messages
}
]]
},
target = realearn.Target.Dummy {
}
}
local mappings = {
init_mapping,
simple_button("cursor-up", "Up", 91),
simple_button("cursor-down", "Down", 92),
simple_button("cursor-left", "Left", 93),
simple_button("cursor-right", "Right", 94),
simple_button("session", "Session", 95),
simple_button("drums", "Drums", 96),
simple_button("keys", "Keys", 97),
simple_button("user", "User", 98),
}
-- Clip launch buttons
for col = 0, 7 do
local human_col = col + 1
for row = 0, 7 do
local human_row = row + 1
local key_number_offset = 11 + (7 - row) * 10
local id = `col{human_col}/row{human_row}/pad`
local control_mapping = realearn.Mapping {
id = id,
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = key_number_offset + col,
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
local feedback_mapping = realearn.Mapping {
id = `{id}-feedback`,
control_enabled = false,
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = build_pad_script(key_number_offset + col),
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, control_mapping)
table.insert(mappings, feedback_mapping)
end
end
-- Scene launch buttons
for row = 0, 7 do
local human_row = row + 1
local id = `row{human_row}/play`
local mapping = realearn.Mapping {
id = id,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 19 + (7 - row) * 10,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
return realearn.Compartment {
common_lua = common_lua,
mappings = mappings,
custom_data = {
grid = {
column_count = 8,
row_count = 8,
},
}
}
@@ -0,0 +1,140 @@
--- name: Launchpad mk1 - Live mode
--- realearn_version: 2.16.0-pre.8
--- author: helgoboss
--- device_manufacturer: Novation
--- device_name: Launchpad mk1
--- description: |
--- This controller preset exposes all control elements of the Launchpad mk1 in a neutral way. It supports Playtime slot status feedback.
---
--- This device cannot be auto-detected because it doesn't support the MIDI device inquiry protocol.
--- provided_schemes: [novation/launchpad-mk1, grid]
--!strict
-- Requires
local realearn = require("realearn")
-- Single buttons
local function simple_button(id: string, name: string, cc: number): realearn.Mapping
return realearn.Mapping {
id = id,
name = name,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = cc,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
end
local init_mapping = realearn.Mapping {
name = "Enable flashing LEDs",
source = realearn.Source.MidiRaw {
pattern = "B0 00 28",
},
target = realearn.Target.Dummy {
}
}
local mappings = {
init_mapping,
simple_button("cursor-up", "Up", 104),
simple_button("cursor-down", "Down", 105),
simple_button("cursor-left", "Left", 106),
simple_button("cursor-right", "Right", 107),
simple_button("session", "session", 108),
simple_button("user-1", "user 1", 109),
simple_button("user-2", "user 2", 110),
simple_button("mixer", "mixer", 111),
}
-- Clip launch buttons
local leds = {
off = 12,
red_low = 13,
red_full = 15,
amber_low = 29,
amber_full = 63,
yellow_full = 62,
green_low = 28,
green_full = 60,
red_flashing = 11,
amber_flashing = 59,
yellow_flashing = 58,
green_flashing = 56,
}
for col = 0, 7 do
local human_col = col + 1
for row = 0, 7 do
local human_row = row + 1
local key_number_offset = row * 16
local id = `col{human_col}/row{human_row}/pad`
local control_mapping = realearn.Mapping {
id = id,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = key_number_offset + col,
},
glue = realearn.Glue {
feedback_value_table = {
kind = "FromTextToDiscrete",
value = {
["playtime.slot_state.empty"] = leds.off,
["playtime.slot_state.armed"] = leds.red_low,
["playtime.slot_state.stopped"] = leds.amber_low,
["playtime.slot_state.ignited"] = leds.amber_low,
["playtime.slot_state.scheduled_for_play_start"] = leds.green_flashing,
["playtime.slot_state.playing"] = leds.green_full,
["playtime.slot_state.paused"] = leds.amber_low,
["playtime.slot_state.scheduled_for_play_stop"] = leds.amber_flashing,
["playtime.slot_state.scheduled_for_play_restart"] = leds.green_flashing,
["playtime.slot_state.scheduled_for_record_start"] = leds.red_flashing,
["playtime.slot_state.recording"] = leds.red_full,
["playtime.slot_state.scheduled_for_record_stop"] = leds.green_flashing,
},
}
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, control_mapping)
end
end
-- Scene launch buttons
for row = 0, 7 do
local human_row = row + 1
local id = `row{human_row}/play`
local mapping = realearn.Mapping {
id = id,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = 8 + row * 16,
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
return realearn.Compartment {
mappings = mappings,
custom_data = {
grid = {
column_count = 8,
row_count = 8,
},
}
}
@@ -0,0 +1,160 @@
--- name: Launchpad Pro mk2 - Live mode
--- realearn_version: 2.16.0-pre.8
--- author: helgoboss
--- device_manufacturer: Novation
--- device_name: Launchpad Pro mk2
--- description: |
--- This controller preset exposes all control elements of the Launchpad Pro mk2 in a neutral way. It supports
--- ReaLearn color feedback as well as Playtime slot status feedback.
--- # I leave part of the family code dynamic. It's not always 00: https://github.com/helgoboss/helgobox/issues/939
--- midi_identity_pattern: F0 7E ? 06 02 00 20 29 51 00 ? 00 * F7
--- midi_output_port_patterns: ["macos:*Live*", "windows:*Launchpad Pro"]
--- provided_schemes: [novation/launchpad-pro-mk2/live, grid]
--!strict
-- Requires
local preset_runtime = require("preset_runtime")
local realearn = require("realearn")
-- Define MIDI scripts
local common_lua = preset_runtime.include_str("novation/launchpad-lib/compartment-common.luau")
local function build_pad_script(pad_index: number): string
return `return require("compartment").pad_script({pad_index}, y, context)`
end
-- Single buttons
local function simple_button(id: string, name: string, cc: number): realearn.Mapping
return realearn.Mapping {
id = id,
name = name,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = cc,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
end
local init_mapping = realearn.Mapping {
name = "Enter live mode",
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = [[
local live_mode = 0x00
local standalone_mode = 0x01
local mode = if y == nil then standalone_mode else live_mode
return {
messages = {
{ 0xF0, 0x00, 0x20, 0x29, 0x02, 0x10, 0x21, mode, 0xF7 }
}
}
]]
},
target = realearn.Target.Dummy {
}
}
local mappings = {
init_mapping,
simple_button("cursor-up", "Up", 91),
simple_button("cursor-down", "Down", 92),
simple_button("cursor-left", "Left", 93),
simple_button("cursor-right", "Right", 94),
simple_button("session", "Session", 95),
simple_button("note", "Note", 96),
simple_button("device", "Device", 97),
simple_button("user", "User", 98),
simple_button("shift", "Shift", 80),
simple_button("click", "Click", 70),
simple_button("undo", "Undo", 60),
simple_button("delete", "Delete", 50),
simple_button("quantize", "Quantise", 40),
simple_button("duplicate", "Duplicate", 30),
simple_button("double", "Double", 20),
simple_button("record", "Record", 10),
simple_button("record-arm", "Record Arm", 1),
simple_button("track-select", "Track Select", 2),
simple_button("mute", "Mute", 3),
simple_button("solo", "Solo", 4),
simple_button("volume", "Volume", 5),
simple_button("pan", "Pan", 6),
simple_button("sends", "Sends", 7),
simple_button("stop-clip", "Stop Clip", 8),
}
-- Clip launch buttons
for col = 0, 7 do
local human_col = col + 1
for row = 0, 7 do
local human_row = row + 1
local key_number_offset = 11 + (7 - row) * 10
local id = `col{human_col}/row{human_row}/pad`
local control_mapping = realearn.Mapping {
id = id,
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = key_number_offset + col,
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
local feedback_mapping = realearn.Mapping {
id = `{id}-feedback`,
control_enabled = false,
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = build_pad_script(key_number_offset + col),
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
table.insert(mappings, control_mapping)
table.insert(mappings, feedback_mapping)
end
end
-- Scene launch buttons
for row = 0, 7 do
local human_row = row + 1
local id = `row{human_row}/play`
local mapping = realearn.Mapping {
id = id,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 19 + (7 - row) * 10,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
return realearn.Compartment {
common_lua = common_lua,
mappings = mappings,
custom_data = {
grid = {
column_count = 8,
row_count = 8,
},
}
}
@@ -0,0 +1,185 @@
--- name: Launchpad Pro mk3 - Live mode
--- realearn_version: 2.16.0-pre.8
--- author: helgoboss
--- device_manufacturer: Novation
--- device_name: Launchpad Pro mk3
--- description: |
--- This controller preset exposes all control elements of the Launchpad Pro mk3 in a neutral way. It supports
--- ReaLearn color feedback as well as Playtime slot status feedback.
--- midi_identity_pattern: F0 7E ? 06 02 00 20 29 23 01 00 00 * F7
--- midi_output_port_patterns: ["macos:*DAW*", "windows:*MIDIOUT3*"]
--- provided_schemes: [novation/launchpad-pro-mk3/live, grid]
--!strict
-- Requires
local preset_runtime = require("preset_runtime")
local realearn = require("realearn")
-- Define MIDI scripts
local common_lua = preset_runtime.include_str("novation/launchpad-lib/compartment-common.luau")
local function build_pad_script(pad_index: number): string
return `return require("compartment").pad_script({pad_index}, y, context)`
end
-- Single buttons
local function simple_button(id: string, name: string, cc: number): realearn.Mapping
return realearn.Mapping {
id = id,
name = name,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = cc,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
end
local init_mapping = realearn.Mapping {
name = "Enter DAW mode and select session layout",
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = [[
local standalone_mode = 0x00
local daw_mode = 0x01
local session_layout = 0x00
local session_layout_page = 0x00
local mode = if y == nil then standalone_mode else daw_mode
local enter_or_leave_daw_mode_msg = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0E, 0x10, mode, 0xF7 }
local select_session_layout = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0E, 0x00, session_layout, session_layout_page, 0x00, 0xF7 }
local messages = {
enter_or_leave_daw_mode_msg,
}
if y then
table.insert(messages, select_session_layout)
end
return {
messages = messages
}
]]
},
target = realearn.Target.Dummy {
}
}
local mappings = {
init_mapping,
simple_button("shift", "Shift", 90),
simple_button("cursor-left", "Left", 91),
simple_button("cursor-right", "Right", 92),
simple_button("session", "Session", 93),
simple_button("cursor-up", "Up", 80),
simple_button("cursor-down", "Down", 70),
simple_button("delete", "Clear", 60),
simple_button("duplicate", "Duplicate", 50),
simple_button("quantize", "Quantise", 40),
simple_button("fixed-length", "Fixed length", 30),
simple_button("play", "Play", 20),
simple_button("record", "Record", 10),
simple_button("record-arm", "Record Arm", 1),
simple_button("mute", "Mute", 2),
simple_button("solo", "Solo", 3),
simple_button("volume", "Volume", 4),
simple_button("pan", "Pan", 5),
simple_button("sends", "Sends", 6),
simple_button("device", "Device", 7),
simple_button("stop-clip", "Stop Clip", 8),
}
-- Clip launch buttons
for col = 0, 7 do
local human_col = col + 1
for row = 0, 7 do
local human_row = row + 1
local key_number_offset = 11 + (7 - row) * 10
local id = `col{human_col}/row{human_row}/pad`
local control_mapping = realearn.Mapping {
id = id,
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = key_number_offset + col,
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
local feedback_mapping = realearn.Mapping {
id = `{id}-feedback`,
control_enabled = false,
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = build_pad_script(key_number_offset + col),
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
table.insert(mappings, control_mapping)
table.insert(mappings, feedback_mapping)
end
end
-- Scene launch buttons
for row = 0, 7 do
local human_row = row + 1
local id = `row{human_row}/play`
local mapping = realearn.Mapping {
id = id,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 19 + (7 - row) * 10,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
-- Column action buttons
for col = 0, 7 do
local human_col = col + 1
local id = `col{human_col}/action`
local mapping = realearn.Mapping {
id = id,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 101 + col,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
return realearn.Compartment {
common_lua = common_lua,
mappings = mappings,
custom_data = {
grid = {
column_count = 8,
row_count = 8,
},
}
}
@@ -0,0 +1,152 @@
--- name: Launchpad X - Live mode
--- realearn_version: 2.16.0-pre.8
--- author: helgoboss
--- device_manufacturer: Novation
--- device_name: Launchpad X
--- description: |
--- This controller preset exposes all control elements of the Launchpad X in a neutral way. It supports
--- ReaLearn color feedback as well as Playtime slot status feedback.
--- midi_identity_pattern: F0 7E ? 06 02 00 20 29 03 01 00 00 * F7
--- midi_output_port_patterns: ["macos:*DAW*", "windows:LPX MIDI"]
--- provided_schemes: [novation/launchpad-x/live, grid]
--!strict
-- Requires
local preset_runtime = require("preset_runtime")
local realearn = require("realearn")
-- Define MIDI scripts
local common_lua = preset_runtime.include_str("novation/launchpad-lib/compartment-common.luau")
local function build_pad_script(pad_index: number): string
return `return require("compartment").pad_script({pad_index}, y, context)`
end
-- Single buttons
local function simple_button(id: string, name: string, cc: number): realearn.Mapping
return realearn.Mapping {
id = id,
name = name,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = cc,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
end
local init_mapping = realearn.Mapping {
name = "Enter DAW mode and select session layout",
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = [[
local standalone_mode = 0x00
local daw_mode = 0x01
local session_layout = 0x00
local mode = if y == nil then standalone_mode else daw_mode
local enter_or_leave_daw_mode_msg = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0C, 0x10, mode, 0xF7 }
local select_session_layout = { 0xF0, 0x00, 0x20, 0x29, 0x02, 0x0C, 0x00, session_layout, 0xF7 }
local messages = {
enter_or_leave_daw_mode_msg,
}
if y then
table.insert(messages, select_session_layout)
end
return {
messages = messages
}
]]
},
target = realearn.Target.Dummy {
}
}
local mappings = {
init_mapping,
simple_button("cursor-up", "Up", 91),
simple_button("cursor-down", "Down", 92),
simple_button("cursor-left", "Left", 93),
simple_button("cursor-right", "Right", 94),
simple_button("session", "Session", 95),
simple_button("note", "Note", 96),
simple_button("custom", "Custom", 97),
simple_button("record", "Record", 98),
}
-- Clip launch buttons
for col = 0, 7 do
local human_col = col + 1
for row = 0, 7 do
local human_row = row + 1
local key_number_offset = 11 + (7 - row) * 10
local id = `col{human_col}/row{human_row}/pad`
local control_mapping = realearn.Mapping {
id = id,
feedback_enabled = false,
source = realearn.Source.MidiNoteVelocity {
channel = 0,
key_number = key_number_offset + col,
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
local feedback_mapping = realearn.Mapping {
id = `{id}-feedback`,
control_enabled = false,
source = realearn.Source.MidiScript {
script_kind = "Lua",
script = build_pad_script(key_number_offset + col),
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
table.insert(mappings, control_mapping)
table.insert(mappings, feedback_mapping)
end
end
-- Scene launch buttons
for row = 0, 7 do
local human_row = row + 1
local id = `row{human_row}/play`
local mapping = realearn.Mapping {
id = id,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = 19 + (7 - row) * 10,
character = "Button",
},
target = realearn.Target.Virtual {
id = id,
character = "Button",
},
}
table.insert(mappings, mapping)
end
return realearn.Compartment {
common_lua = common_lua,
mappings = mappings,
custom_data = {
grid = {
column_count = 8,
row_count = 8,
},
}
}
@@ -0,0 +1 @@
../../api/luau/playtime.luau
@@ -0,0 +1 @@
../../api/luau/preset_runtime.luau
@@ -0,0 +1 @@
../../api/luau/realearn.luau
@@ -0,0 +1,722 @@
--!strict
local realearn = require("realearn")
local module = {}
export type PresetMode = "native" | "midi-absolute" | "midi-relative"
export type PresetConfig = {
mode: PresetMode,
}
function module.create_compartment(config: PresetConfig): realearn.Compartment
-- Config
-- With this, we can simulate true relative control.
--
-- In MIDI mode, the encoders of the Console 1 send absolute control values, not relative ones. This is
-- not optimal because it means we can't define step sizes, wrap etc. However, as long as feedback is enabled, at least we don't
-- need to suffer from parameter jumps (a common disadvantage of absolute control).
-- This is because the Console 1 doesn't just use the feedback to set the LED ring, it also resets its internal encoder
-- value to the incoming feedback value.
--
-- If we simulate relative control, we get the advantages of relative control. However, it doesn't
-- work so nicely with feedback :( The "reset-internal-encoder-value" mechanism mentioned above interferes.
-- Without further treatment, this makes the control stuck. We can make control work by not sending echo feedback
-- but then the indicated LED ring value is incorrect when we turn the encoder (however, it is correct when changing the parameter
-- in REAPER itself - as long as we don't disable feedback completely).
-- The technique we use right now is to compare with the previously sent feedback value - if available - and otherwise the previously
-- sent control value. This sort of works, but there can be glitches because it's not always sure that the last-sent feedback value
-- has actually been received by the controller.
local simulate_relative_control_in_midi_mode = config.mode == "midi-relative"
local native_mode = config.mode == "native"
-- Code
local realearn = require("realearn")
local sysex_to_relative_transformation = [[
y_type = 1;
y = prev_timestamp == 0 ? (
1
) : (
// Implement encoder acceleration based on timestamp diffs
diff_millis = (realearn_timestamp - prev_timestamp) * 1000;
added = 10 / diff_millis;
// realearn_dbg(added);
1 + added
);
prev_timestamp = realearn_timestamp;
]]
local abs_to_rel_transformation = [[
y_type = 1;
// If the last-sent feedback value is available, we use this one as reference (because the controller
// resets its internal value to the last-sent feedback value). If not (e.g. if feedback is disabled),
// we use the last-sent control value as reference.
ref = realearn_last_feedback_value == prev_last_feedback_value ? prev_x : realearn_last_feedback_value;
y = count == 0 ? (
// On first invocation, we don't have any reference value to compare to
0
) : x > ref || x == 1 ? (
// Controller sends higher number than reference value or encoder hit right boundary. Looks like an increment.
1
) : x < ref || x == 0 ? (
// Controller sends lower number than reference value or encoder hit left boundary. Looks like a decrement.
-1
) : (
// No change
none
);
prev_last_feedback_value = last_feedback_value;
prev_x = x;
count += 1;
]]
local mappings = {}
local function button(
id: string,
target: realearn.Target,
name: string,
cc: number?,
on_sysex: string,
off_sysex: string
)
local cc_source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = cc,
character = "Button",
}
if native_mode then
local on_mapping = realearn.Mapping {
id = `{id}-on`,
feedback_enabled = false,
name = name,
source = realearn.Source.MidiRaw {
pattern = on_sysex,
},
target = target,
}
table.insert(mappings, on_mapping)
local off_mapping = realearn.Mapping {
id = `{id}-off`,
feedback_enabled = false,
name = name,
source = realearn.Source.MidiRaw {
pattern = off_sysex,
},
glue = {
reverse = true,
},
target = target,
}
table.insert(mappings, off_mapping)
if cc then
local feedback_mapping = realearn.Mapping {
id = `{id}-feedback`,
control_enabled = false,
name = name,
source = cc_source,
target = target,
}
table.insert(mappings, feedback_mapping)
end
else
local mapping = realearn.Mapping {
id = id,
name = name,
source = cc_source,
target = target,
}
table.insert(mappings, mapping)
end
end
local function named_button(id: string, name: string, cc: number?, on_sysex: string, off_sysex: string)
local target = realearn.Target.Virtual {
id = id,
character = "Button",
}
button(id, target, name, cc, on_sysex, off_sysex)
end
local function numbered_button(index: number, name: string, cc: number, on_sysex: string, off_sysex: string)
local target = realearn.Target.Virtual {
id = index,
character = "Button",
}
button(tostring(index), target, name, cc, on_sysex, off_sysex)
end
local function encoder(id: string, name: string, cc: number, cw_sysex: string, ccw_sysex: string)
local cc_source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = cc,
character = "Range",
-- Preventing echo feedback would make the LED ring show the incorrect value when we turn the encoder
-- (it would be correct when changing the parameter in REAPER itself).
-- feedback_behavior = "PreventEchoFeedback",
}
local target = realearn.Target.Virtual {
id = id,
character = "Multi",
}
if native_mode then
local cw_mapping = realearn.Mapping {
id = `{id}-cw`,
feedback_enabled = false,
name = name,
source = realearn.Source.MidiRaw {
pattern = cw_sysex,
character = "Button",
},
glue = realearn.Glue {
control_transformation = sysex_to_relative_transformation,
step_factor_interval = {1, 100},
},
target = target,
}
local ccw_mapping = realearn.Mapping {
id = `{id}-ccw`,
feedback_enabled = false,
name = name,
source = realearn.Source.MidiRaw {
pattern = ccw_sysex,
character = "Button",
},
glue = {
control_transformation = sysex_to_relative_transformation,
step_factor_interval = {1, 100},
reverse = true,
},
target = target,
}
local feedback_mapping = realearn.Mapping {
id = `{id}-feedback`,
control_enabled = false,
name = name,
source = cc_source,
target = target,
}
table.insert(mappings, cw_mapping)
table.insert(mappings, ccw_mapping)
table.insert(mappings, feedback_mapping)
else
local mapping = realearn.Mapping {
id = id,
name = name,
source = cc_source,
glue = realearn.Glue {
control_transformation = if simulate_relative_control_in_midi_mode then abs_to_rel_transformation else nil,
},
target = target,
}
table.insert(mappings, mapping)
end
end
local function meter(id: string, name: string, cc: number)
local m = realearn.Mapping {
id = id,
name = name,
control_enabled = false,
source = realearn.Source.MidiControlChangeValue {
channel = 0,
controller_number = cc,
character = "Range",
},
target = realearn.Target.Virtual {
id = id,
character = "Multi",
},
}
table.insert(mappings, m)
end
-- Named buttons in row 1
named_button("display-on", "Display: On", 102, "", "")
named_button(
"display/mode",
"Display: Mode",
104,
"F0 7D 00 00 00 00 02 00 01 7F 01 02 55 0A 0C F7",
"F0 7D 00 00 00 00 02 00 01 00 41 38 39 27 01 F7"
)
named_button(
"page/prev",
"Page: -",
97,
"F0 7D 00 00 00 00 02 00 03 7F 33 34 37 08 0D F7",
"F0 7D 00 00 00 00 02 00 03 00 73 0E 5B 25 00 F7"
)
named_button(
"page/next",
"Page: +",
96,
"F0 7D 00 00 00 00 02 00 04 7F 7C 75 21 4F 0E F7",
"F0 7D 00 00 00 00 02 00 04 00 3C 4F 4D 62 03 F7"
)
named_button(
"track/group",
"Track: Group",
123,
"F0 7D 00 00 00 00 02 00 19 7F 03 19 4D 53 06 F7",
"F0 7D 00 00 00 00 02 00 19 00 43 23 21 7E 0B F7"
)
named_button(
"track/copy",
"Track: Copy",
120,
"F0 7D 00 00 00 00 02 00 1A 7F 28 34 1E 10 07 F7",
"F0 7D 00 00 00 00 02 00 1A 00 68 0E 72 3D 0A F7"
)
named_button(
"order",
"Order",
14,
"F0 7D 00 00 00 00 02 00 3A 7F 3D 30 3A 32 0A F7",
"F0 7D 00 00 00 00 02 00 3A 00 7D 0A 56 1F 07 F7"
)
named_button(
"external-sidechain",
"External Sidechain",
17,
"F0 7D 00 00 00 00 02 00 3B 7F 24 2B 0B 73 0A F7",
"F0 7D 00 00 00 00 02 00 3B 00 64 11 67 5E 07 F7"
)
-- Numbered buttons in row 1
numbered_button(
0,
`Select track 1`,
21,
"F0 7D 00 00 00 00 02 00 05 7F 65 6E 10 0E 0E F7",
"F0 7D 00 00 00 00 02 00 05 00 25 54 7C 23 03 F7"
)
numbered_button(
1,
`Select track 2`,
22,
"F0 7D 00 00 00 00 02 00 06 7F 4E 43 43 4D 0F F7",
"F0 7D 00 00 00 00 02 00 06 00 0E 79 2F 60 02 F7"
)
numbered_button(
2,
`Select track 3`,
23,
"F0 7D 00 00 00 00 02 00 07 7F 57 58 72 0C 0F F7",
"F0 7D 00 00 00 00 02 00 07 00 17 62 1E 21 02 F7"
)
numbered_button(
3,
`Select track 4`,
24,
"F0 7D 00 00 00 00 02 00 08 7F 50 40 6E 43 02 F7",
"F0 7D 00 00 00 00 02 00 08 00 10 7A 02 6E 0F F7"
)
numbered_button(
4,
`Select track 5`,
25,
"F0 7D 00 00 00 00 02 00 09 7F 49 5B 5F 02 02 F7",
"F0 7D 00 00 00 00 02 00 09 00 09 61 33 2F 0F F7"
)
numbered_button(
5,
`Select track 6`,
26,
"F0 7D 00 00 00 00 02 00 0A 7F 62 76 0C 41 03 F7",
"F0 7D 00 00 00 00 02 00 0A 00 22 4C 60 6C 0E F7"
)
numbered_button(
6,
"Select track 7",
27,
"F0 7D 00 00 00 00 02 00 0B 7F 7B 6D 3D 00 03 F7",
"F0 7D 00 00 00 00 02 00 0B 00 3B 57 51 2D 0E F7"
)
numbered_button(
7,
"Select track 8",
28,
"F0 7D 00 00 00 00 02 00 0C 7F 34 2C 2B 47 00 F7",
"F0 7D 00 00 00 00 02 00 0C 00 74 16 47 6A 0D F7"
)
numbered_button(
8,
"Select track 9",
29,
"F0 7D 00 00 00 00 02 00 0D 7F 2D 37 1A 06 00 F7",
"F0 7D 00 00 00 00 02 00 0D 00 6D 0D 76 2B 0D F7"
)
numbered_button(
9,
"Select track 10",
30,
"F0 7D 00 00 00 00 02 00 0E 7F 06 1A 49 45 01 F7",
"F0 7D 00 00 00 00 02 00 0E 00 46 20 25 68 0C F7"
)
numbered_button(
10,
"Select track 11",
31,
"F0 7D 00 00 00 00 02 00 0F 7F 1F 01 78 04 01 F7",
"F0 7D 00 00 00 00 02 00 0F 00 5F 3B 14 29 0C F7"
)
numbered_button(
11,
"Select track 12",
32,
"F0 7D 00 00 00 00 02 00 10 7F 52 5B 76 1A 08 F7",
"F0 7D 00 00 00 00 02 00 10 00 12 61 1A 37 05 F7"
)
numbered_button(
12,
"Select track 13",
33,
"F0 7D 00 00 00 00 02 00 11 7F 4B 40 47 5B 08 F7",
"F0 7D 00 00 00 00 02 00 11 00 0B 7A 2B 76 05 F7"
)
numbered_button(
13,
"Select track 14",
34,
"F0 7D 00 00 00 00 02 00 12 7F 60 6D 14 18 09 F7",
"F0 7D 00 00 00 00 02 00 12 00 20 57 78 35 04 F7"
)
numbered_button(
14,
"Select track 15",
35,
"F0 7D 00 00 00 00 02 00 13 7F 79 76 25 59 09 F7",
"F0 7D 00 00 00 00 02 00 13 00 39 4C 49 74 04 F7"
)
numbered_button(
15,
"Select track 16",
36,
"F0 7D 00 00 00 00 02 00 14 7F 36 37 33 1E 0A F7",
"F0 7D 00 00 00 00 02 00 14 00 76 0D 5F 33 07 F7"
)
numbered_button(
16,
"Select track 17",
37,
"F0 7D 00 00 00 00 02 00 15 7F 2F 2C 02 5F 0A F7",
"F0 7D 00 00 00 00 02 00 15 00 6F 16 6E 72 07 F7"
)
numbered_button(
17,
"Select track 18",
38,
"F0 7D 00 00 00 00 02 00 16 7F 04 01 51 1C 0B F7",
"F0 7D 00 00 00 00 02 00 16 00 44 3B 3D 31 06 F7"
)
numbered_button(
18,
"Select track 19",
39,
"F0 7D 00 00 00 00 02 00 17 7F 1D 1A 60 5D 0B F7",
"F0 7D 00 00 00 00 02 00 17 00 5D 20 0C 70 06 F7"
)
numbered_button(
19,
"Select track 20",
40,
"F0 7D 00 00 00 00 02 00 18 7F 1A 02 7C 12 06 F7",
"F0 7D 00 00 00 00 02 00 18 00 5A 38 10 3F 0B F7"
)
-- Buttons in row 2
named_button(
"shape",
"Shape",
53,
"F0 7D 00 00 00 00 02 00 21 7F 14 06 71 28 01 F7",
"F0 7D 00 00 00 00 02 00 21 00 54 3C 1D 05 0C F7"
)
named_button(
"eq",
"Equalizer",
80,
"F0 7D 00 00 00 00 02 00 27 7F 42 5C 56 2E 02 F7",
"F0 7D 00 00 00 00 02 00 27 00 02 66 3A 03 0F F7"
)
named_button(
"comp",
"Compressor",
46,
"F0 7D 00 00 00 00 02 00 34 7F 23 33 17 3C 07 F7",
"F0 7D 00 00 00 00 02 00 34 00 63 09 7B 11 0A F7"
)
-- Buttons in row 3
named_button(
"eq/low/type",
"EQ: Low type",
93,
"F0 7D 00 00 00 00 02 00 28 7F 45 44 4A 61 0F F7",
"F0 7D 00 00 00 00 02 00 28 00 05 7E 26 4C 02 F7"
)
named_button(
"eq/high/type",
"EQ: High type",
65,
"F0 7D 00 00 00 00 02 00 31 7F 5E 44 63 79 05 F7",
"F0 7D 00 00 00 00 02 00 31 00 1E 7E 0F 54 08 F7"
)
-- Buttons in row 4
named_button(
"shape/hard-gate",
"Shape: Hard Gate",
59,
"F0 7D 00 00 00 00 02 00 23 7F 26 30 13 2A 00 F7",
"F0 7D 00 00 00 00 02 00 23 00 66 0A 7F 07 0D F7"
)
named_button(
"solo",
"Solo",
13,
"F0 7D 00 00 00 00 02 00 3F 7F 40 47 4E 77 08 F7",
"F0 7D 00 00 00 00 02 00 3F 00 00 7D 22 5A 05 F7"
)
named_button(
"mute",
"Mute",
12,
"F0 7D 00 00 00 00 02 00 40 7F 68 60 2B 4E 04 F7",
"F0 7D 00 00 00 00 02 00 40 00 28 5A 47 63 09 F7"
)
-- Remaining buttons
named_button(
"filters-to-compressor",
"Filters to Compressor",
61,
"F0 7D 00 00 00 00 02 00 1E 7F 4C 58 5B 14 05 F7",
"F0 7D 00 00 00 00 02 00 1E 00 0C 62 37 39 08 F7"
)
named_button(
"phase-inv",
"Phase Inv.",
108,
"F0 7D 00 00 00 00 02 00 1F 7F 55 43 6A 55 05 F7",
"F0 7D 00 00 00 00 02 00 1F 00 15 79 06 78 08 F7"
)
named_button(
"preset",
"Preset",
58,
"F0 7D 00 00 00 00 02 00 20 7F 0D 1D 40 69 01 F7",
"F0 7D 00 00 00 00 02 00 20 00 4D 27 2C 44 0C F7"
)
if native_mode then
named_button(
"fine-adjust",
"Fine Adjust",
nil,
"F0 7D 00 00 00 00 02 00 02 7F 2A 2F 06 49 0D F7",
"F0 7D 00 00 00 00 02 00 02 00 6A 15 6A 64 00 F7"
)
end
-- Encoders in row 1
encoder(
"high-cut",
"High Cut",
105,
"F0 7D 00 00 00 00 02 00 1C 7F 7E 6E 39 16 04 F7",
"F0 7D 00 00 00 00 02 00 1C 00 3E 54 55 3B 09 F7"
)
encoder(
"shape/gate-release",
"Shape: Gate Release",
56,
"F0 7D 00 00 00 00 02 00 24 7F 69 71 05 6D 03 F7",
"F0 7D 00 00 00 00 02 00 24 00 29 4B 69 40 0E F7"
)
encoder(
"eq/low-mid/type",
"EQ: Low Mid Type",
90,
"F0 7D 00 00 00 00 02 00 2C 7F 21 28 0F 65 0D F7",
"F0 7D 00 00 00 00 02 00 2C 00 61 12 63 48 00 F7"
)
encoder(
"eq/high-mid/type",
"EQ: High Mid Type",
87,
"F0 7D 00 00 00 00 02 00 2F 7F 0A 05 5C 26 0C F7",
"F0 7D 00 00 00 00 02 00 2F 00 4A 3F 30 0B 01 F7"
)
encoder(
"comp/attack",
"Comp: Attack",
51,
"F0 7D 00 00 00 00 02 00 37 7F 08 1E 44 7F 06 F7",
"F0 7D 00 00 00 00 02 00 37 00 48 24 28 52 0B F7"
)
encoder(
"drive",
"Drive",
15,
"F0 7D 00 00 00 00 02 00 3C 7F 6B 6A 1D 34 09 F7",
"F0 7D 00 00 00 00 02 00 3C 00 2B 50 71 19 04 F7"
)
-- Encoders in row 2
encoder(
"shape/gate",
"Shape: Gate",
54,
"F0 7D 00 00 00 00 02 00 22 7F 3F 2B 22 6B 00 F7",
"F0 7D 00 00 00 00 02 00 22 00 7F 11 4E 46 0D F7"
)
encoder(
"comp/ratio",
"Comp: Ratio",
49,
"F0 7D 00 00 00 00 02 00 35 7F 3A 28 26 7D 07 F7",
"F0 7D 00 00 00 00 02 00 35 00 7A 12 4A 50 0A F7"
)
-- Encoders in row 3
encoder(
"low-cut",
"Low Cut",
103,
"F0 7D 00 00 00 00 02 00 1D 7F 67 75 08 57 04 F7",
"F0 7D 00 00 00 00 02 00 1D 00 27 4F 64 7A 09 F7"
)
encoder(
"shape/sustain",
"Shape: Sustain",
55,
"F0 7D 00 00 00 00 02 00 25 7F 70 6A 34 2C 03 F7",
"F0 7D 00 00 00 00 02 00 25 00 30 50 58 01 0E F7"
)
encoder(
"eq/low/freq",
"EQ: Low Frequency",
92,
"F0 7D 00 00 00 00 02 00 29 7F 5C 5F 7B 20 0F F7",
"F0 7D 00 00 00 00 02 00 29 00 1C 65 17 0D 02 F7"
)
encoder(
"eq/low-mid/freq",
"EQ: Low Mid Frequency",
89,
"F0 7D 00 00 00 00 02 00 2B 7F 6E 69 19 22 0E F7",
"F0 7D 00 00 00 00 02 00 2B 00 2E 53 75 0F 03 F7"
)
encoder(
"eq/high-mid/freq",
"EQ: High Mid Frequency",
86,
"F0 7D 00 00 00 00 02 00 2E 7F 13 1E 6D 67 0C F7",
"F0 7D 00 00 00 00 02 00 2E 00 53 24 01 4A 01 F7"
)
encoder(
"eq/high/freq",
"EQ: High Frequency",
83,
"F0 7D 00 00 00 00 02 00 32 7F 75 69 30 3A 04 F7",
"F0 7D 00 00 00 00 02 00 32 00 35 53 5C 17 09 F7"
)
encoder(
"comp/release",
"Comp: Release",
48,
"F0 7D 00 00 00 00 02 00 38 7F 0F 06 58 30 0B F7",
"F0 7D 00 00 00 00 02 00 38 00 4F 3C 34 1D 06 F7"
)
encoder(
"character",
"Character",
18,
"F0 7D 00 00 00 00 02 00 3D 7F 72 71 2C 75 09 F7",
"F0 7D 00 00 00 00 02 00 3D 00 32 4B 40 58 04 F7"
)
-- Encoders in row 4
encoder(
"input/gain",
"Input Gain",
107,
"F0 7D 00 00 00 00 02 00 1B 7F 31 2F 2F 51 07 F7",
"F0 7D 00 00 00 00 02 00 1B 00 71 15 43 7C 0A F7"
)
encoder(
"comp/parallel-dry-wet",
"Comp: Parallel Dry/Wet",
50,
"F0 7D 00 00 00 00 02 00 36 7F 11 05 75 3E 06 F7",
"F0 7D 00 00 00 00 02 00 36 00 51 3F 19 13 0B F7"
)
-- Encoders in row 5
encoder(
"shape/punch",
"Shape: Punch",
57,
"F0 7D 00 00 00 00 02 00 26 7F 5B 47 67 6F 02 F7",
"F0 7D 00 00 00 00 02 00 26 00 1B 7D 0B 42 0F F7"
)
encoder(
"eq/low/gain",
"EQ: Low Gain",
91,
"F0 7D 00 00 00 00 02 00 2A 7F 77 72 28 63 0E F7",
"F0 7D 00 00 00 00 02 00 2A 00 37 48 44 4E 03 F7"
)
encoder(
"eq/low-mid/gain",
"EQ: Low Mid Gain",
88,
"F0 7D 00 00 00 00 02 00 2D 7F 38 33 3E 24 0D F7",
"F0 7D 00 00 00 00 02 00 2D 00 78 09 52 09 00 F7"
)
encoder(
"eq/high-mid/gain",
"EQ: High Mid Gain",
85,
"F0 7D 00 00 00 00 02 00 30 7F 47 5F 52 38 05 F7",
"F0 7D 00 00 00 00 02 00 30 00 07 65 3E 15 08 F7"
)
encoder(
"eq/high/gain",
"EQ: High Gain",
82,
"F0 7D 00 00 00 00 02 00 33 7F 6C 72 01 7B 04 F7",
"F0 7D 00 00 00 00 02 00 33 00 2C 48 6D 56 09 F7"
)
encoder(
"comp/threshold",
"Comp: Threshold",
47,
"F0 7D 00 00 00 00 02 00 39 7F 16 1D 69 71 0B F7",
"F0 7D 00 00 00 00 02 00 39 00 56 27 05 5C 06 F7"
)
encoder(
"pan",
"Pan",
10,
"F0 7D 00 00 00 00 02 00 3E 7F 59 5C 7F 36 08 F7",
"F0 7D 00 00 00 00 02 00 3E 00 19 66 13 1B 05 F7"
)
encoder(
"output/volume",
"Volume",
7,
"F0 7D 00 00 00 00 02 00 41 7F 71 7B 1A 0F 04 F7",
"F0 7D 00 00 00 00 02 00 41 00 31 41 76 22 09 F7"
)
-- Meters
meter("input/meter/left", "Input Meter: Left channel", 110)
meter("input/meter/right", "Input Meter: Right channel", 111)
meter("output/meter/left", "Output Meter: Left channel", 112)
meter("output/meter/right", "Output Meter: Right channel", 113)
meter("shape/meter", "Shape Meter", 114)
meter("comp/meter", "Compressor Meter", 115)
return realearn.Compartment {
mappings = mappings,
}
end
return module
@@ -0,0 +1,51 @@
--- name: Console 1 mk2 - MIDI absolute mode
--- realearn_version: 2.16.12
--- author: helgoboss
--- device_manufacturer: Softube
--- device_name: Console 1 mk2
--- description: |
--- This controller preset implements support for the Softube Console 1 controller in MIDI mode, enabling absolute control.
---
--- Pros:
--- * LED feedback without glitches
--- * Button "On" is available and can be customized
---
--- Cons:
--- * Buttons can't be turned into momentary buttons (because Console1 doesn't send any message on release)
--- * True relative control is not possible (no user-defined step sizes and no wrap)
--- * Without pressing "Fine Adjust", encoder control is very coarse
--- * Button "Fine Adjust" can't be customized (it just switches to fine control on hardware side)
--- * In order to avoid parameter jumps with the encoders, feedback must be enabled (alternatively, you can use
--- ReaLearn's takeover modes)
---
--- setup_instructions: |
--- 1. Connect the controller
--- 2. Ensure that the Console 1 On-Screen Display software is **not** running!
---
--- provided_schemes: [softube/console1-mk2]
--!strict
-- Config
-- With this, we can simulate true relative control.
--
-- In MIDI mode, the encoders of the Console 1 send absolute control values, not relative ones. This is
-- not optimal because it means we can't define step sizes, wrap etc. However, as long as feedback is enabled, at least we don't
-- need to suffer from parameter jumps (a common disadvantage of absolute control).
-- This is because the Console 1 doesn't just use the feedback to set the LED ring, it also resets its internal encoder
-- value to the incoming feedback value.
--
-- If we simulate relative control, we get the advantages of relative control. However, it doesn't
-- work so nicely with feedback :( The "reset-internal-encoder-value" mechanism mentioned above interferes.
-- Without further treatment, this makes the control stuck. We can make control work by not sending echo feedback
-- but then the indicated LED ring value is incorrect when we turn the encoder (however, it is correct when changing the parameter
-- in REAPER itself - as long as we don't disable feedback completely).
-- The technique we use right now is to compare with the previously sent feedback value - if available - and otherwise the previously
-- sent control value. This sort of works, but there can be glitches because it's not always sure that the last-sent feedback value
-- has actually been received by the controller.
local commons = require("softube/console1-mk2-lib/preset-common")
return commons.create_compartment {
mode = "midi-absolute",
}
@@ -0,0 +1,33 @@
--- name: Console 1 mk2 - MIDI relative mode
--- realearn_version: 2.16.12
--- author: helgoboss
--- device_manufacturer: Softube
--- device_name: Console 1 mk2
--- description: |
--- This controller preset implements support for the Softube Console 1 controller in MIDI mode, enabling relative control.
---
--- Pros:
--- * True relative control
--- * Button "On" is available and can be customized
---
--- Cons:
--- * Buttons can't be turned into momentary buttons (because Console1 doesn't send any message on release)
--- * Occasional control and feedback glitches are possible (Console1 encoders send absolute messages in a way that makes
--- it hard to convert them into relative messages)
--- * Reacting to encoder increments (or decrements) only via encoder filter doesn't work
--- * Button "Fine Adjust" doesn't have any effect and can't be customized
---
--- setup_instructions: |
--- 1. Connect the controller
--- 2. Ensure that the Console 1 On-Screen Display software is **not** running!
---
--- provided_schemes: [softube/console1-mk2]
--!strict
-- Config
local commons = require("softube/console1-mk2-lib/preset-common")
return commons.create_compartment {
mode = "midi-relative",
}
@@ -0,0 +1,31 @@
--- name: Console 1 mk2 - Native mode
--- realearn_version: 2.16.12
--- author: helgoboss
--- device_manufacturer: Softube
--- device_name: Console 1 mk2
--- description: |
--- This controller preset implements support for the Softube Console 1 controller in native mode.
---
--- Pros:
--- * True relative control without glitches
--- * True momentary buttons
--- * LED feedback without glitches
--- * Button "Fine Adjust" is available and can be customized
---
--- Cons:
--- * Button "On" is not available
--- * Using Softube plug-ins that connect to the Console 1 is not possible (they would interfere)
---
--- setup_instructions: |
--- 1. Connect the controller
--- 2. Start the Console 1 On-Screen Display software
--- 3. Make sure to not use any Softube plug-ins
---
--- provided_schemes: [softube/console1-mk2]
--!strict
local commons = require("softube/console1-mk2-lib/preset-common")
return commons.create_compartment {
mode = "native",
}