Last active
June 6, 2023 10:00
-
-
Save helgoboss/902d78137248da1683791e19d435be06 to your computer and use it in GitHub Desktop.
ReaLearn Lua MIDI script templates for programming the Novation SL MkIII via InControl API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- ReaLearn Lua MIDI script template for programming the screens and LEDs of the Novation SL MkIII keyboard via InControl API | |
-- | |
-- The section "Function" contains reusable helper functions for creating MIDI messages that change something on the | |
-- screen. The section "Code" makes use of the helper functions, so this is the section that you probably want to | |
-- adjust to your needs. | |
-- | |
-- Hardware setup: Put the SL MkIII into InControl mode by pressing the corresponding button on the keyboard! | |
-- ReaLearn setup: Use the MIDI output "SL MkIII InControl" and add a mapping with a Lua "MIDI script" source! | |
-- | |
-- See https://fael-downloads-prod.focusrite.com/customer/prod/s3fs-public/downloads/SLMkIII_Programmer%27s_Guide.pdf | |
-- for more info. | |
-- ## Functions ## | |
function to_ascii(text) | |
local bytes = {} | |
if text ~= nil then | |
for i = 1, string.len(text) do | |
local byte = string.byte(text, i) | |
if byte < 128 then | |
table.insert(bytes, byte) | |
end | |
end | |
end | |
-- Null terminator | |
table.insert(bytes, 0) | |
return bytes | |
end | |
function concat_table(t1, t2) | |
for i = 1, #t2 do | |
t1[#t1 + 1] = t2[i] | |
end | |
end | |
function create_msg(content) | |
-- SysEx Header | |
local msg = { | |
0xf0, 0x00, 0x20, 0x29, 0x02, 0x0a, 0x01, | |
} | |
-- Content | |
concat_table(msg, content) | |
-- End of SysEx | |
table.insert(msg, 0xf7) | |
return msg | |
end | |
--- Creates a message for changing the layout. | |
--- | |
--- @param layout_index number layout index (0 = empty, 1 = knob, 2 = box) | |
function create_screen_layout_msg(layout_index) | |
return create_msg({ 0x01, layout_index }) | |
end | |
--- Creates a message for displaying a notification on the center screen. | |
--- | |
--- @param line1 string first line of text | |
--- @param line2 string second line of text | |
function create_notification_text_msg(line1, line2) | |
local content = { 0x04 } | |
concat_table(content, to_ascii(line1)) | |
concat_table(content, to_ascii(line2)) | |
return create_msg(content) | |
end | |
--- Creates a message for changing multiple screen properties. | |
--- | |
--- @param changes table list of property changes | |
function create_screen_props_msg(changes) | |
local content = { 0x02 } | |
for i = 1, #changes do | |
if changes[i] ~= nil then | |
concat_table(content, changes[i]) | |
end | |
end | |
return create_msg(content) | |
end | |
--- Creates a text property change. | |
--- | |
--- @param column_index number in which column to display the text | |
--- @param object_index number in which location to display the text | |
--- @param text string the actual text | |
function create_text_prop_change(column_index, object_index, text) | |
local change = { | |
-- Column Index | |
column_index, | |
-- Property Type "Text" | |
0x01, | |
-- Object Index | |
object_index, | |
} | |
concat_table(change, to_ascii(text)) | |
return change | |
end | |
--- Creates a value property change. | |
--- | |
--- If it's about setting the knob value, it's usually more intuitive to not | |
--- do this via script but by enabling feedback on the corresponding encoder | |
--- mappings, then you need just one mapping for both control and feedback. | |
--- | |
--- @param column_index number in which column to change the value | |
--- @param object_index number the kind of value to change (see programmer's guide) | |
--- @param value number the value | |
function create_value_prop_change(column_index, object_index, value) | |
return { | |
-- Column Index | |
column_index, | |
-- Property Type "Value" | |
0x03, | |
-- Object Index | |
object_index, | |
-- Color bytes | |
value | |
} | |
end | |
--- Creates an RGB color property change. | |
--- | |
--- If the given color is `nil`, this function returns `nil`. | |
--- | |
--- @param column_index number in which column to change the color | |
--- @param object_index number in which location to change the color | |
--- @param color table the RGB color (table with properties r, g and b) | |
function create_rgb_color_prop_change(column_index, object_index, color) | |
if color == null then | |
return nil | |
end | |
return { | |
-- Column Index | |
column_index, | |
-- Property Type "RGB color" | |
0x04, | |
-- Object Index | |
object_index, | |
-- Color bytes | |
math.floor(color.r / 2), | |
math.floor(color.g / 2), | |
math.floor(color.b / 2), | |
} | |
end | |
--- Creates a message for changing the state of an LED. | |
--- | |
--- Passing a `nil` color will make the LED go off. | |
--- | |
--- @param led_index number which LED to talk to | |
--- @param led_behavior number LED behavior (1 = solid, 2 = flashing with previously set solid color, 3 = pulsating) | |
--- @param color table the RGB color (table with properties r, g and b) | |
function create_led_msg(led_index, led_behavior, color) | |
if color == nil then | |
color = { r = 0, g = 0, b = 0 } | |
end | |
return create_msg({ | |
0x03, | |
led_index, | |
led_behavior, | |
math.floor(color.r / 2), | |
math.floor(color.g / 2), | |
math.floor(color.b / 2), | |
}) | |
end | |
-- ## Code ## | |
-- In this example, we send a bunch of different changes at once in order to demonstrate the possibilities. In a real-world | |
-- scenario you would probably send less changes within one mapping. | |
local column = 0 | |
local white = { r = 255, g = 255, b = 255 } | |
return { | |
-- In real-world scenarios, you probably want to set the address to a number that uniquely and | |
-- globally identifies the screen element that you are changing. Otherwise, ReaLearn has no way | |
-- to prevent unnecessary duplicate feedback and might exhibit strange behavior when switching | |
-- between multiple mappings that change the same screen element. | |
address = nil, | |
messages = { | |
-- Display a short notification on the center screen | |
create_notification_text_msg("Hello", "World"), | |
-- Use the knob layout for all normal screens | |
create_screen_layout_msg(1), | |
-- Send some properties for the configured column | |
create_screen_props_msg({ | |
-- Set the top bar color to the color configured in the "Glue" section | |
create_rgb_color_prop_change(column, 0, context.feedback_event.color), | |
-- Make the knob visible (by making it white) | |
create_rgb_color_prop_change(column, 1, white), | |
-- Make the bottom bar color appear white (when lower text selected) | |
create_rgb_color_prop_change(column, 2, white), | |
-- Make the upper text label show the parameter value | |
create_text_prop_change(column, 0, y), | |
-- Make the lower text label show some other text | |
create_text_prop_change(column, 3, "Volume"), | |
-- Make the lower text label appear selected | |
create_value_prop_change(column, 1, 1), | |
}), | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment