Files
virtual-controller/plugin-reaper-realearn/resources/controller-presets/factory/softube/console1-mk2-lib/preset-common.luau
T
Paul Lipscomb e58f06d9fa Vendor helgoboss/helgobox (ReaLearn) as basis for custom UI fork
Stripped upstream git history; starting point for replacing the native
SWELL/Win32 mapping UI with something more suited to bulk editing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 17:38:29 -04:00

723 lines
22 KiB
Lua

--!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