# Preset Lua extension

This document describes the Lua Extension for the Electra One MIDI Controller firmware. The extension introduces procedural programming to the Electra One Preset format.

Lua is a scripting programming language; you can find detailed information about it on the Official Lua site (opens new window).

Note

To utilize the Electra One Lua Extension described in this document, you must have Firmware version 3.5 or later installed.

# A brief overview

The Electra One Preset Lua extension empowers you to seamlessly integrate Lua function calls into your presets. The current implementation offers a diverse range of functionalities, including:

  • Sending and receiving MIDI messages.
  • Triggering Lua functions when control values change.
  • Formatting display values.
  • Modifying the visibility, location, name, and color of controls.
  • Executing custom patch dump request calls.
  • Crafting your own SysEx parsers.
  • Calculating checksums and generating custom SysEx template bytes.
  • Decoding packed and nibbelized SysEx data.
  • Initiating Lua functions based on MIDI clock and transport control.
  • Creating sequences of MIDI data, clock messages, and MIDI LFOs.

The core concept behind this extension is to establish a clear distinction between the static data defined within declarative JSON and the dynamic processing of this data at runtime through Lua scripting. The JSON preset serves as the foundation, pre-loading all pages, lists, devices, groups, and controls. Once the preset is loaded, the Lua Extension comes into play, enabling you to manipulate these objects to serve specific purposes. It's important to note that the Lua Extension API cannot create new objects, but it possesses the capability to modify, reposition, and adjust the visibility of existing objects.

# Uploading the scripts

To enable Lua script extension functions within a preset, you must first upload the Lua script file. The uploaded script is then associated with the currently active preset. Should a Lua script already exist for a particular preset, the upload SysEx call will overwrite it.

In practical terms, each preset can have one Lua script assigned, forming a combination of the JSON preset (.epr file) and the Lua script (.lua file).

# Uploading the scripts with the Preset Editor

The Preset editor (opens new window) allows you to easily create, edit, and upload a Lua script into a preset. This is the preferred way to work with lua. However, The Electra One MIDI Controller allows you to upload the scripts via a SysEx call.

# Uploading the scripts with a SysEx call

0xF0 0x00 0x21 0x45 0x01 0x0C script-source-code 0xF7
  • 0xF0 SysEx header byte
  • 0x00 0x21 0x45 Electra One MIDI manufacturer Id
  • 0x01 Upload data
  • 0x0C Lua script file
  • script-source-code bytes representing ASCII characters of the Lua script source code
  • 0xF7 SysEx closing byte

# Executing a Lua command with a SysEx call

This is a call to execute arbitrary Lua commands, which can be viewed as an API endpoint for controlling Electra One presets from external devices and applications.

This feature allows you to remotely manage Electra One presets using Lua commands, providing a powerful means of interaction between the controller and external devices or applications. It enables the execution of custom Lua scripts or functions to control various aspects of your MIDI setup, enhancing the flexibility and adaptability of the Electra One MIDI controller in diverse scenarios.

0xF0 0x00 0x21 0x45 0x08 0x0D lua-command-text 0xF7
  • 0xF0 SysEx header byte
  • 0x00 0x21 0x45 Electra One MIDI manufacturer Id
  • 0x08 Execute command
  • 0x0D Lua command
  • lua-command-text ASCII bytes representing the log message
  • 0xF7 SysEx closing byte

The lua-command-text is a free-form string containing a Lua command to be executed, with a maximum length limited to 128 characters. It is advisable to use predefined functions whenever possible.

# An example of the lua-command-text
print ("Hello MIDI world!")

# The structure of the script

The Electra One Lua script is organized into four distinct building blocks:

  • The Setup Section: This section is where you can initialize and configure various settings and parameters essential for your script. It serves as the starting point for your script's execution and often includes setup tasks like defining global variables, setting up MIDI connections, or configuring other necessary resources.
  • The Standard Functions: These are predefined functions that come with the Electra One Lua scripting environment. They serve as the core functionality for interacting with the MIDI controller and its features. Standard functions can be used to send and receive MIDI messages, manipulate controls, and handle various aspects of the controller's behavior.
  • The Standard Callbacks: Electra One provides a set of standard callback functions that allow your Lua script to respond to specific events and triggers. These callbacks are invoked automatically by the system when certain actions occur. For example, you can use callbacks to react to control value changes or button presses, providing a dynamic and interactive element to your script.
  • The User Functions: These are custom functions that you define to extend the functionality of your Lua script. User functions are where you can implement unique behavior, process data, and create specific responses to tailor the script to your needs. These functions give you the flexibility to customize the Electra One experience according to your requirements.

By understanding and effectively utilizing these four building blocks, you can create powerful and customized Lua scripts for your Electra One MIDI controller, enhancing its capabilities and tailoring its behavior to suit your specific needs.

Let's use following example to demonstrate it:

-- Display controls related to specific value of another control

-- a function to hide all controls within the groups
function hideAllGroups (groups)
    for groupId = 0, #groups do
        for i, controlId in ipairs (groups[groupId]) do
            control = controls.get (controlId)
            control:setVisible (false)
        end
    end
end

-- show given control group
function showGroup (groups, groupId)
    for i, controlId in ipairs (groups[groupId]) do
        control = controls.get (controlId)
        control:setSlot (i + 1)
    end
end

-- the callback function called from the preset
function displayGroup (valueObject, value)
    hideAllGroups (controlGroups)
    showGroup (controlGroups, value)
end

-- a standard callback function to handle PATCH REQUEST event
function patch.onRequest (device)
  print ("Requesting patches from device " .. device.id);
  midi.sendProgramChange (PORT_1, device.channel, 10)
end


-- set the initial state. group 0 is displayed

-- define assignment of controls to groups
controlGroups = {
    [0] = { 20, 21, 22 },
    [1] = { 26, 27, 28 },
    [2] = { 32, 33 }
}

function preset.onLoad()
    showGroup (controlGroups, 0)
end

print ("Lua ext initialized")

# The setup

The setup section encompasses all source code that exists outside of any specific function, residing within the global context of the script. In this section, you can perform a variety of tasks, including calling standard functions, user-defined functions, initializing global variables, and more. Below is an example of the setup section from the script:

-- set the initial state. group 0 is displayed

-- define assignment of controls to groups
controlGroups = {
    [0] = { 20, 21, 22 },
    [1] = { 26, 27, 28 },
    [2] = { 32, 33 }
}

function preset.onLoad()
    showGroup (controlGroups, 0)
end

print ("Lua ext initialized")

The primary purpose of the setup section is to prepare your extension for handling application events at a later stage. It is executed immediately after the preset is loaded. The location of the setup section within the script does not affect its functionality; it is not required to be at the top. If you plan to use your own user-defined functions within the setup, it's recommended to place the setup part below the definition of the user functions or incorporate them within the preset.onLoad() function for organized script execution.

# The standard functions

Standard functions encompass functions derived from both the Lua standard libraries and the Electra One extension libraries. These functions provide a broad spectrum of capabilities, including tasks such as printing, mathematical operations, MIDI messaging, and interactions with user interface (UI) components.

Detailed descriptions of these standard functions are available in the official Lua documentation (opens new window) as well as within this document.

As an example, the print function serves as a typical representation of a standard function.

print ("Lua ext initialized")

# The standard callbacks

The Electra One Lua Extension introduces a set of predefined event handlers, often referred to as callbacks. These callbacks are automatically triggered in response to specific events, offering you the flexibility to assign custom functionality to them.

For instance, consider the following example:

-- a standard callback function to handle PATCH REQUEST event
function patch.onRequest (device)
  print ("Requesting patches from device " .. device.id);
  midi.sendProgramChange (PORT_1, device.channel, 10)
end

In this code snippet, the patch.onRequest function represents a standard callback that responds to the "PATCH REQUEST" event. When such an event occurs, this callback executes the specified actions. These standard callbacks enable you to tailor the behavior of your Lua script in response to various events, enhancing the interactivity and adaptability of the Electra One MIDI controller.

# The user functions

As a user, you have the creative freedom to define and encapsulate your own functionality within user functions. In fact, you are encouraged to do so, as user functions serve as building blocks for constructing more intricate and programmatic elements in your Lua script.

User functions are your way of organizing and extending the capabilities of your script. They allow you to encapsulate specific tasks or behaviors, making your script more modular and easier to manage.

For example, let's consider the displayGroup callback from the source code example provided earlier. This function is user-defined and is bound to a function callback hook within the preset JSON.

-- the callback function called from the preset
function displayGroup (valueObject, value)
    hideAllGroups (controlGroups)
    showGroup (controlGroups, value)
end

# Lua extension API

# Logger

The logging is the key element to understanding what is happening inside the controller. Electra One Lua API provides the print () command that writes texts that can be observed in the ElectraOne Console application. The log messages created with the print () function are always prefixed with the lua: text.

The log messages are, in fact, SysEx messages sent to the CTRL port. They carry the timestamp and the text of the message. For more details about the console logs, please review the Electra's MIDI implementation

As the logging relies on the standard SysEx messaging, users can develop their own log viewers or integrate Electra logs to their own applications.

The logger output can be enabled or disabled. When disabled, no log messages are sent over the MIDI. Please refer to Logger enable / disable (opens new window) to get information about managing the logger. The logger is disabled for performance reasons by default.

# Functions

print (text)

A function to print text to the ElectraOne Console log view.

  • text - string, a text message to be displayed in the console log.
# Example script
-- Printing to the console log

print ("This message will be shown in the ElectraOne console")

for i = 1, 10 do
    print ("message #" .. i)
end

The example script will produce following output in the ElectraOne Console

Hello world output

# Controls

The controls module provides functionality to manage preset controls. It is not meant to change properties of individual controls. The individual controls are managed by manipulating the Control object, see below.

# Functions

controls.get (controlId)

Retrieves a reference to a Control object (userdata). A control is a representation of a fader, list, and other types of controls.

  • controlId - integer, a numeric identifier of the control. id attribute from the preset.
  • returns - userdata, a reference to a control object. :::
# Example script
-- Retrieving a reference to given control

local control = controls.get (1)

# Control

A representation of a Control object. It holds the data and functions to modify itself.

# Functions

<control>:getId ()

Retrieves an identifier of the Control. The identifier is assigned to the control in the preset JSON.

  • returns - integer, identifier of the control (1 .. 432).
# Example script
-- Retrieving a control and getting its Id

local volumeControl = controls.get (10)
print ("got Control with Id " .. volumeControl:getId ())

<control>:setVisible (shouldBeVisible)

Changes the visibility of given control. The initial visibility is set in the Preset JSON. The setVisibility method may change the visibility at the run-time.

  • shouldBeVisible - boolean, desired state of the visibility.

<control>:isVisible ()

Retrieves a status of control's visibility.

  • returns - boolean, true when the control is visible.
# Example script
-- a function to toggle visibility of a control

function toggleControl (control)
    control:setVisible (not control:isVisible ())
end

<control>:setName (name)

Sets a new name of the control.

  • name - string, a new name to be assigned to the control.

<control>:getName ()

Retrieves current name of the control.

  • returns - string, current name of the control.
# Example script
-- print out a name of given control

function printName (controlId)
    local control = controls.get (controlId)
    print ("Name: " .. control:getName ())
end

<control>:setColor (color)

Sets a new color of the control. Due to performance reasons, only predefined six colors are available at present time.

  • color - integer, a new color to be used (see Globals for details).

<control>:getColor ()

Retrieves current color of the control.

  • returns - integer, current color of the control (see Globals for details).
# Example script
-- A callback function that changes color of the control
-- when its value exceeds 100

function functionCallback (valueObject, value)
    local control = valueObject:getControl()

    if (value > 100) then
        control:setColor (RED)
    else
        control:setColor (WHITE)
    end
end

<control>:setBounds ({ x, y, width, height })

Changes position and dimensions (bounds) of the control. The helpers library provides functions to convert bounds to preset slots.

  • bounds - array, a array consisting of x, y, width, height boundary box attributes.

<control>:getBounds ()

Retrieves current position and dimensions (bounds) of the control.

  • returns - array, an array consisting of x, y, width, height boundary box attributes.

X, Y, WIDTH, HEIGHT variables are available to access the bounding box attributes, (see Globals for details).

# Example script
-- print out position and dimensions of given control

control = controls.get (2)
control:setBounds ({ 200, 200, 170, 65 })
bounds = control:getBounds ()
print ("current bounds: " ..
    "x=" .. bounds[X] ..
    ", y=" .. bounds[Y] ..
    ", width=" .. bounds[WIDTH] ..
    ", height=" .. bounds[HEIGHT])

<control>:setPot (controlSet, pot)

Assigns the control to given controlSet and pot.

  • controlSet - integer, a numeric identifier of the control set (see Globals for details).
  • pot - integer, a numeric identifier of the pot (see Globals for details).
# Example script
-- Reassign the control to different controlSet and pot

control = controls.get (1)
control:setPot (CONTROL_SET_1, POT_2)

<control>:setSlot (slot)

Moves the given control to a preset slot on the current page. The control set and potentiometer values are assigned accordingly, and the control is made visible.

  • slot - integer, a numeric identifier of the preset slot (1 .. 36).

<control>:setSlot (slot, pageId)

Moves the given control to a preset slot on a particular page. The control settings and potentiometer values are assigned accordingly, and the control is then made visible.

  • slot - integer, a numeric identifier of the preset slot (1 .. 36).
  • pageId - integer, a numeric identifier of the page (1 .. 12).
# Example script
-- Change location of the control within the 6x6 grid

control = controls.get (1)
control:setSlot (7)

<control>:getValueIds ()

Retrieves a list of all valueIds associated with the control. The valueIds are defined in the JSON preset.

  • returns - array, a list of value identifier strings.
# Example script
-- list all value Ids of a control

local control = controls.get (1)
local valueIds = control:getValueIds ()

for i, valueId in ipairs (valueIds) do
    print (valueId)
end

<control>:getValue (valueId)

Retrieves the Value object of given control using the valueId handle.
The valueId is defined in the JSON preset. If not present, it defaults to "value"

  • valueId - string, an identifier of the value within the control definition.
  • returns - userdata, a reference to a value object.
# Example script
-- Display min and max display values

local control = controls.get (1)
local value = control:getValue ("attack")

print ("value min: " .. value:getMin ())
print ("value max: " .. value:getMax ())

<control>:getValues ()

Retrieves a list of all value objects associated with the control. The value objects are defined in the JSON preset.

  • returns - array, a list of references to userdata value objects.
# Example script
-- list all value objects of a control

local control = controls.get (1)
local valueObjects = control:getValues ()

for i, valueObject in ipairs (valueObjects) do
    print (string.format ("%s.%s", control:getName (), valueObject:getId ()))
end

<control>:print ()

Prints all attributes of the control to the Logger output.

# Value

A representation of a Value object within the Control. A Control object contains one or more Value objects, each identified by the valueId. The Value object describes the properties of the data value that users can change with their interaction. The Value holds the data and functions to modify it.

# Functions

<value>:getId ()

Retrieves the identifier of the Value. The identifier is assigned to the Value in the preset JSON.

  • returns - string, identifier of the Value.

<value>:setDefault (defaultValue)

Sets the default display value of the Value object

  • defaultValue - integer, the default display value.

<value>:getDefault ()

Retrieves the default display value of the Value object

  • returns - integer, the default display value.

<value>:setMin (minumumValue)

Sets the minimum display value of the Value object

  • minimumValue - integer, the minimum display value.

<value>:getMin ()

Retrieves the minimum display value of the Value object

  • returns - integer, the minimum display value.

value:setMax (maximumValue)

Sets the maximum display value of the Value object

  • maximumValue - integer, the maximum display value.

<value>:getMax ()

Retrieves the maximum display value of the Value object

  • returns - integer, the maximum display value.

value:setRange (minimumValue, maximumValue, defaultValue, applyToMessage)

Sets the range of display value of the Value object. Optionally, the range can be propagated to underlying MIDI value.

  • minimumValue - integer, the minimum display value.
  • maximumValue - integer, the maximum display value.
  • defaultValue - integer, the default display value.
  • applyToMessage - boolean, when true, the same range is applied to underlying Message object.

value:overrideValue (valueText)

Forces the control to display an alternative text instead of current value. Overriden value text also replaces output of Value Formatters.

  • valueText - string, a text to be displayed instead of current value.

value:cancelOverride ()

Removes the text value set with value:overrideValue () and instructs the controller to display the current value.

<value>:setOverlayId (overlayId)

Assigns an overlay list to the Value object

  • overlayId - integer, an identifier of the overlay, as specified in the preset.

<value>:getOverlayId ()

Retrieves the overlay assigned to the Value object

  • returns - integer, an identifier of the overlay, as specified in the preset.
--  swap overlay lists of two controls

listA = controls.get (1)
listB = controls.get (2)

valueA = listA:getValue ("value")
valueB = listB:getValue ("value")

print ("list A: " .. valueA:getOverlayId ())
print ("list B: " .. valueB:getOverlayId ())

valueB:setOverlayId (1)
valueA:setOverlayId (2)

<value>:getMessage ()

Retrieves the MIDI message object assigned to the Value object

  • returns - userdata, a reference to a Message object.
-- Get the message associated with the release value

local value = control:getValue ("release")
local message = value.getMessage ()

<value>:print ()

Prints all attributes of the control value to the Logger output.

# Message

The Message consists of a subset of attributes of the Message object from preset JSON.

# Functions

<message>:setDeviceId (deviceId)

Sets the identifier of counterparty device that receives and sends this message.

Note, if calling this function will create a new ParameterMap entry, it is developer's responsibility to set its value.

  • deviceId - integer, a numeric identifier of Electra's parameter type (1 .. 32)

<message>:getDeviceId ()

Retrieves the identifier of counterparty device that receives and sends this message.

  • returns - integer, a numeric identifier of the device (1 .. 32).

<message>:setType (type)

Sets the type of the MIDI message object

  • type - integer, a numeric identifier of Electra's parameter type (0 .. 16), see Globals for details.

<message>:getType ()

Retrieves the type of the MIDI message. For the list of message types, please refer to the overview in the Globals section.

  • returns - integer, a numeric identifier of Electra's parameter type (0 .. 16), see Globals for details.

<message>:setParameterNumber (parameterNumber)

Sets the type of the MIDI message object

  • parameterNumber - integer, a numeric identifier of the parameter (0 .. 16383)

<message>:getParameterNumber ()

Retrieves the identifier of the parameter as it as specified in the preset JSON.

  • returns - integer, a numeric identifier of the parameter (0 .. 16383).

<message>:getValue ()

Retrieves the current MIDI value of the Message object.

  • returns - integer, current MIDI value (0 .. 16383).

<value>:setMin (minumumValue)

Sets the minimum MIDI value of the Message object

  • minimumValue - integer, the minimum MIDI value.

<value>:getMin ()

Retrieves the minimum MIDI value of the Message object

  • returns - integer, the minimum MIDI value.

<value>:setMax (maximumValue)

Sets the maximum MIDI value of the Message object

  • maximumValue - integer, the maximum MIDI value.

<value>:getMax ()

Retrieves the maximum MIDI value of the Message object

  • returns - integer, the maximum MIDI value.

<value>:setRange (minimumValue, maximumValue)

Sets the range of MIDI values for the Message object

  • minimumValue - integer, the maximum MIDI value.
  • maximumValue - integer, the maximum MIDI value.

<value>:setValue (newValue)

Sets the new MIDI values of the Message object. If the new value differs from the current value, an outgoing MIDI message will be sent.

  • newValue - integer, the new MIDI value.

<message>:getOffValue ()

Retrieves the Off MIDI value of the Pad control.

  • returns - integer, MIDI value of the Off state (0 .. 16383).

<message>:getOnValue ()

Retrieves the On MIDI value of the Pad control.

  • returns - integer, MIDI value of the On state (0 .. 16383).
-- Print info about the message

function valueCallback (valueObject, value)
    local message = valueObject:getMessage ()

    print ("Device Id: " .. message:getDeviceId ())
    print ("Type: " .. message:getType ())
    print ("Parameter Number: " .. message:getParameterNumber ())
    print ("Current value: " .. message:getValue ())
end

<message>:print ()

Prints all attributes of the message to the Logger output.

# Overlays

The overlays module provides functionality for managing preset overlays. An overlay is a list of discrete MIDI values. Each item consists of a MIDI value, a text label, and optional bitmap data. Overlays provide data for List controls or can be used to override display values for Faders.

# Functions

overlays.get (overlayId)

Retrieves a reference to an overlay object (userdata).

  • overlayId - integer, a numeric identifier of the overlay. id attribute from the preset.
  • returns - userdata, a reference to a overlay object.

overlays.create (overlayId, overlayData)

Creates a new overlay and returns a reference to it (userdata).

  • overlayId - integer, a numeric identifier of the overlay. id attribute from the preset.
  • overlayData - table, a table with Overlay items data.
  • returns - userdata, a reference to a overlay object.
overlayData = {
    { value = 1, label = "Room" },
    { value = 2, label = "Hall" },
    { value = 3, label = "Plate" },
    { value = 4, label = "Spring" }
}

# Overlay

A representation of a Overlay object. It holds the data and functions to work with the overlay.

<overlay>:print ()

Prints all attributes of the overlay to the Logger output.

# Example script

-- Define reverb and delay types with associated values and labels
local listReverbTypes = {
    { value = 1, label = "Room" },
    { value = 2, label = "Hall" },
    { value = 3, label = "Plate" },
    { value = 4, label = "Spring" }
}

-- Create a new overlay
overlays.create(2, listReverbTypes)

# Device

A representation of a Device object. It holds the data and functions to modify it.

# Functions

<device>:getId ()

Retrieves the identifier of the Device. The identifier is assigned to the device in the preset JSON.

  • returns - integer, identifier of the device (1 .. 32).

# Pages

The pages module provides functionality to manage preset pages.

# Functions

pages.get (pageId)

Retrieves a reference to a page object (userdata).

  • pageId - integer, a numeric identifier of the page (1 .. 12). id attribute from the preset.
  • returns - userdata, a reference to a page object.

pages.getActive ()

Retrieves a reference to a page object (userdata) of current active page.

  • returns - userdata, a reference to a page object.

pages.display (pageId)

Displays given preset page.

  • pageId - integer, a numeric identifier of the page (1 .. 12). id attribute from the preset.

pages.setActiveControlSet (controlSetId)

Changes current control set.

  • controlSetId - integer, a numeric identifier of the control set (1 .. 3).

pages.getActiveControlSet ()

Retrieves an identifier of the current active control set.

  • returns - integer, a numeric identifier of the control set (1 .. 3).

pages.onChange (newPageId, oldPageId)

A callback function that is called when page is changed.

  • newPageId - integer, a numeric identifier of the page (1 .. 12). id attribute from the preset.
  • oldPageId - integer, a numeric identifier of the page (1 .. 12). id attribute from the preset.

# Example script

-- Retrieve a reference to given page

local page = pages.get (3)

# Page

A representation of a Page object. It holds the data and functions to modify itself.

# Functions

<page>:getId ()

Retrieves the identifier of the Page. The identifier is assigned to the page in the preset JSON.

  • returns - integer, identifier of the page (1 .. 12).

<page>:setName (name)

Sets a new name to a given page.

  • name - string, a new name to be used.

<page>:getName ()

Retrieves current name of given page.

  • returns - string, current name of the page.
# Example script
-- change name of a pge

local page = pages.get (1)

page:setName ("LPF")
print ("page name: " .. page:getName ())

<page>:print ()

Prints all attributes of the page to the Logger output.

# Groups

The groups module provides functionality to manage groups within the preset. A Group is a graphics object used to improve layout and structure of the preset pages.

# Functions

groups.get (groupId)

Retrieves a reference to a group object (userdata). The groupId can be defined in the preset JSON. If not, group ids are assigned to to groups automatically. Starting with 1 and following the order or groups in the JSON.

  • groupId - integer, a numeric identifier of the group. id attribute from the preset.
  • returns - userdata, a reference to a group object.

# Example script

-- Retrieve a reference to given group

local group = groups.get (1)

# Group

A representation of a Group object. The Group object holds the data and functions to modify it.

# Functions

<group>:getId ()

Retrieves the identifier of the group. The identifier is assigned to the group in the preset JSON or generated automatically.

  • returns - integer, identifier of the page (1 .. 432).

<group>:setLabel (label)

Sets a new label of a given group. The label gives a name to the group. When an empty string is provided, the label is now shown.

  • label - string, a new label to be shown at the top of the group.

<group>:getLabel ()

Retrieves current label of given group.

  • returns - string, current label of the group.

<group>:setVisible (shouldBeVisible)

Changes the visibility of given group.

  • shouldBeVisible - boolean, desired state of the visibility.

<group>:isVisible ()

Gets status of given group's visibility.

  • returns - boolean, true when the group is visible.

<group>:setColor (color)

Sets a new color of the group. Due to performance reasons, only predefined six colors are available at present time.

  • color - integer, a new color to be used (see Globals for details).

<group>:getColor ()

Retrieves current color of the group.

  • returns - integer, current color of the control (see Globals for details).

<group>:setBounds ({ x, y, width, height })

Changes position and dimensions (bounds) of the group.

  • bounds - array, a array consisting of x, y, width, height boundary box attributes.

<group>:getBounds ()

Retrieves current position and dimensions (bounds) of the group.

  • returns - array, an array consisting of x, y, width, height boundary box attributes.

X, Y, WIDTH, HEIGHT variables are available to access the bounding box attributes (see Globals for details).

<group>:setSlot (slot, width, height)

Moves given group to a slot on current page. The width represents a span accross the slots. Optionally, a height can be specified to form a rectangle group.

  • slot - integer, a numeric identifier of the preset slot (1 .. 36).
  • width - integer, a numeric identifier of the preset slot (1 .. 6).
  • height - integer, a numeric identifier of the preset slot (0 .. 6).

<group>:setVariant (variant)

Sets a variant of the group.

  • variant - integer, an identifier of the group variant (see Globals for details).
# Example script
-- change group slot and dimentions

-- Verical line only
local group1 = groups.get (1)

print ("Label name: " .. group1:getLabel ())
group1:setSlot (3, 2)

-- Renctangle group
local group2 = groups.get (2)

print ("Label name: " .. group2:getLabel ())
group2:setSlot (9, 2, 2)

# Devices

The devices module provides functionality to manage preset devices. A device represents a connected instrument, such as a synth, sampler, FX unit. The device consists of information about the port and channel where the device is connected.

# Functions

devices.get (deviceId)

Retrieves a reference to a device object (userdata).

  • deviceId - integer, a numeric identifier of the device. id attribute from the preset.
  • returns - userdata, a reference to a device object.
# Example script
-- Retrieving a reference to given device

local device = devices.get (1)

# Device

A representation of a Device object. It holds the data and functions to modify it.

# Functions

<device>:getId ()

Retrieves the identifier of the Device. The identifier is assigned to the device in the preset JSON.

  • returns - integer, identifier of the device (1 .. 32).

<device>:setName (name)

Assigns a new name to a given device.

  • name - string, a new new to be assigned to the device.

<device>:getName ()

Retrieves current name of given device.

  • returns - string, current name of the device.

<device>:setPort (port)

Assigns given device to a hardware port.

  • port - integer, a port identifier (see Globals for details).

<device>:getPort ()

Gets an identifier of the hardware port currently assigned to the device.

  • returns - integer, a port identifier (see Globals for details).

<device>:setChannel (channel)

Assigns given device to a MIDI channel.

  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).

<device>:getChannel ()

Gets an identifier of the MIDI channel currently assigned to the device.

  • returns - integer, a numeric representation of the MIDI channel (1 .. 16).

<device>:setRate (rate)

Sets rate of sending of MIDI messages to the device.

  • rate - integer, time [in milliseconds] between sending individual MIDI messages (10 .. 1000).

<device>:getRate ()

Gets setting of current rate.

  • returns - integer, time [in milliseconds] between sending individual MIDI messages (10 .. 1000, or 0 if no rate is applied to the message sending).
# Example script
-- This needs to reflect the preset device settings

AccessVirusDeviceId = 2


-- Display info about the device

local device = devices.get (AccessVirusDeviceId)
print ("device port: " .. device:getPort ())
print ("device channel: " .. device:getChannel ())


-- A function to set channel of device with a Control

function setChannel (control, value)
    device = devices.get (AccessVirusDeviceId)
    device:setChannel (value)
end

# Parameter Map

The Parameter map is the heart of the Electra Controller firmware. It is used to store and retrieve information about all parameter values across all connected devices. Whenever a MIDI message is received, pot turned, or a value change made with the touch, the information about the change is routed to the Parameter map and the map, in turn, updates all relevant components and sends MIDI messages out.

# Functions

parameterMap.resetAll ()

Clears all entries of the parameterMap.

parameterMap.resetDevice (deviceId)

Resets all parameters of given device to zero.

  • deviceId - integer, a numeric identifier of the device (1..32).

parameterMap.set (deviceId, parameterType, parameterNumber, midiValue)

Sets a midiValue of particular Electra parameter within the parameter map.

  • deviceId - integer, a numeric identifier of the device (1 .. 32).
  • parameterType - integer, a numeric identifier of Electra's parameter type (see Globals for details).
  • ParameterNumber - integer, a numeric identifier of the parameter (0 .. 16383).
  • midiValue - integer, a MIDI value (0 .. 16383).

parameterMap.apply (deviceId, parameterType, parameterNumber, midiValueFragment)

Applies a partial value to current value of particular Electra parameter within the parameter map. The midiValueFragment is ORed to the parameter value.

  • deviceId - integer, a numeric identifier of the device (1 .. 32).
  • parameterType - integer, a numeric identifier of Electra's parameter type (see Globals for details).
  • ParameterNumber - integer, a numeric identifier of the parameter (0 .. 16383).
  • midiValueFragment - integer, a MIDI value frangement to be applied (0 .. 16383).

parameterMap.modulate(deviceId, parameterType, parameterNumber, modulationValue, depth)

This function applies modulation to a parameter value stored in the parameter map. The modulated value is emitted, but the parameter value itself remains unchanged.

  • deviceId - An integer that serves as a numeric identifier for the device (ranging from 1 to 32).
  • parameterType - An integer representing Electra's parameter type (refer to Globals for more details).
  • parameterNumber - An integer identifying the parameter (ranging from 0 to 16383).
  • modulationValue - A numeric value representing the modulation, for example, the output from an LFO (-1.0 to 1.0).
  • depth - An integer indicating the depth of the modulation effect (ranging from 0 to 100).

parameterMap.get (deviceId, parameterType, parameterNumber)

Sets a midiValue of particular Electra parameter within the parameter map.

  • deviceId - integer, a numeric identifier of the device (1 .. 32).
  • parameterType - integer, a numeric identifier of Electra's parameter type (see Globals for details).
  • ParameterNumber - integer, a numeric identifier of the parameter (0 .. 16383).
  • returns - integer, a MIDI value of given parameter (0 .. 16383).

parameterMap.getValues (deviceId, parameterType, parameterNumber)

Retrieves a list of all value objects associated with the ParameterMap entry. The value objects are defined in the JSON preset.

  • deviceId - integer, a numeric identifier of the device (1 .. 32).
  • parameterType - integer, a numeric identifier of Electra's parameter type (see Globals for details).
  • ParameterNumber - integer, a numeric identifier of the parameter (0 .. 16383).
  • returns - array, a list of references to userdata value objects.

parameterMap.onChange (valueObjects, origin, midiValue)

An onChange is a user function called whenever there is a change made to the ParameterMap. The function is provided with a list of all associated value objects and the current MIDI value.

  • valueObjects - array, a list of references to userdata value objects.
  • origin - interger, a numeric identifier of the change origin (see Globals for details).
  • midiValue - integer, a MIDI value (0 .. 16383).
# Example script
-- Display info about the change in the ParameterMap

function parameterMap.onChange (valueObjects, origin, midiValue)
    print (string.format ("a new midiValue %d from origin %d",
        midiValue, origin))

        for i, valueObject in ipairs (valueObjects) do
            local control = valueObject:getControl ()
            print (string.format ("affects control value %s.%s",
                control:getName (), valueObject:getId ()))
        end
end

parameterMap.keep ()

Saves the state of the parameterMap

parameterMap.recall ()

Recalls state that was previously saved with parameterMap.keep () function call.

parameterMap.forget ()

Removes and forgets state that was previously saved with parameterMap.keep () function call.

parameterMap.print ()

Prints all parameterMap entries to the Logger output.

# Value formatters

Value formatter is a user function used to format the display value of a control. It is a function that takes a display value as an input and computes a value that will be displayed. The formatted value is returned in the form of a string, therefore, given the user a vast range of formatting possibilities.

To invoke the formatter function, it needs to be assigned to a Value in the preset JSON first. It is done by adding a formatter attribute to the value object.

# Example preset JSON

"values": [
   {
      "message": {
         "deviceId": 1,
         "type": "cc7",
         "parameterNumber": 2,
         "min": 0,
         "max": 127
      },
      "id": "value",
      "min": 0,
      "max": 127,
      "formatter": "formatFractions"
   }
]

For more detailed information please review the Electra's MIDI implementation page.

# Functions

<formatterFunction> (valueObject, value)

A user function to transform the input display value to a text string that is displayed on the LCD.

  • valueObject - userdata, a reference to a value object.
  • value - integer, a display value as defined by the preset JSON.
  • returns - string, transformed version of the input display value.
# Example script
-- Convert number to a range with decimal numbers
function formatFractions (valueObject, value)
    return (string.format("%.1f", value / 20))
end

-- add percentage to the value
function addPercentage (valueObject, value)
    return (value .. "%")
end

# Value function callbacks

Value function callback is a user function allowing running complex user actions whenever the control value is changed.

To invoke the callback function, it needs to be assigned to a Value in the preset JSON first. It is done by adding a function attribute to the value object. You may see the callback function as an alternative to the message. While the message represent a statically defined MIDI message, function is a dynamic Lua function call run on the value change.

# Example preset JSON

"values": [
   {
      "message": {
         "deviceId": 1,
         "type": "cc7",
         "parameterNumber": 2,
         "min": 0,
         "max": 127
      },
      "id": "attack",
      "min": 0,
      "max": 127,
      "function": "highlightOnOverload"
   }
]

For more detailed information please review the Electra's MIDI implementation page.

# Functions

<callbackFunction> (valueObject, value)

A user function to run custom Lua extension function.

  • valueObject - userdata, a reference to a value object.
  • value - integer, a display value as defined by the preset JSON. :::
# Example script
function highlightOnOverload (valueObject, value)
    if (value > 64) then
        control:setColor (ORANGE)
    else
        control:setColor (WHITE)
    end
end

# SysEx byte function

A SysEx byte functions may be used in SysEx templates, patch requests, and patch response headers to calculate and insert bytes to specific locations of the SysEx message. The function is provided information about the device and a parameter number. It is expected to return one byte containing a 7bit value.

# Example preset JSON

The following snippet demonstrates use of Lua functions in both, the patch request and the response header. In this particular case, it is used to request and match SysEx patch dump from TX7 on a specific MIDI channel.

"devices":[
   {
      "id":1,
      "name":"Yamaha DX7",
      "port":1,
      "channel":16,
      "patch":[
         {
            "request":[
               "43",
               {
                  "type":"function",
                  "name":"getRequestByte"
               },
               "00"
            ],
            "responses":[
               {
                  "header":[
                     "43",
                     {
                        "type":"function",
                        "name":"getResponseByte"
                     },
                     "00",
                     "01",
                     "1B"
                  ],
                  "rules":[
                     {
                        "id":136,
                        "pPos":0,
                        "byte":136,
                        "bPos":0,
                        "size":1,
                        "msg":"sysex"
                     }
                  ]
               }
            ]
         }
      ]
   }
]

The following snippet shows how to use the Lua SysEx byte function in the SysEx template.

"values":[
   {
	  "id":"value",
	  "message":{
		 "type":"sysex",
		 "deviceId":1,
		 "data":[
			"43",
			{
			   "type":"function",
			   "name":"getChannelByte"
			},
			"00",
			"66",
			{
			   "type":"value",
			   "rules":[
				  {
					 "parameterNumber":102,
					 "bitWidth":5,
					 "byteBitPosition":0
				  }
			   ]
			}
		 ],
		 "parameterNumber":102,
		 "min":0,
		 "max":31
	  },
	  "min":0,
	  "max":31
   }
]
# Functions

<sysexByteFunction> (deviceObject, parameterNumber)

A function to insert a calculated SysEx byte.

  • deviceObject - userdata, a reference to a device object.
  • parameterNumber - integer, parameter number, provided when used in the SysEx template.
  • returns - byte, 7bit value that will be inserted to the SysEx message. :::
# Example script
-- returns a byte that TX7 uses to identify the MIDI channel

function getChannelByte (device)
    return (0x10 + (device:getChannel() - 1))
end

# SysexBlock

A library to work with SysEx message. On contrary to simple arrays of bytes, SysexBlock allows users to work with large SysEx message efficiently.

# Functions

<sysexBlock>:getLength ()

Retrieves the total length of the SysEx message. The length includes the leading and trailing 0xF0 and 0xF7 bytes.

  • length - integer, a number of bytes in the SysexBlock object.

<sysexBlock>:getManufacturerSysexId ()

Retrieves the SysEx manufacturer identifier from the SysexBlock object. It is either one byte-wide number or a number componsed of the three bytes with LSB at position 3 and MSB at position 1.

  • sysexManufacturerId - integer, an identifier of SysEx Manufacturer id.

<sysexBlock>:seek (position)

Sets the SysexBlock's current position at given ofset.

  • position - integer, the position of the read/write pointer in the SysexBlock.

<sysexBlock>:read ()

Reads one byte from current position within the SysexBlock. The read/write pointer is automatically increased after the read is completed.

  • returns - byte, the byte value at current SysexBlock position, or -1.

<sysexBlock>:peek (position)

Reads one byte from position provided as an input parameter. The read/write pointer is not affected by the peek operation.

  • returns - byte, the byte value at current SysexBlock position, or -1.

# Patch

A library to handle requesting patch dumps and parsing patch dump SysEx MIDI messages. The patch.onResponse () callback is called when the Patch response header, as defined in the preset JSON, is matched. This means, you need to define the Patch object in the Preset if you want the patch callbacks to be invoked.

The following Patch definition is the bare minimum implementation. The patch.onReponse () will be called whenever a SysEx message with leading bytes 67, 0, 0, 1, 27 is received.

"patch":[
   {
      "responses":[
         {
            "id":1,
            "header":[
               67,
               0,
               0,
               1,
               27
            ]
         }
      ]
   }
]

# Functions

patch.onRequest (device)

A callback to send a patch request to a particular device. The function is called upon the [PATCH REQUEST] button has been pressed and it is sent to all devices that have a patch request defined in their patch definition.

  • device - data table, a device description data structure (see below).

patch.onResponse (device, responseId, sysexBlock)

A callback to handle incoming SysEx message that matched the Patch response definition.

  • device - data table, a device description data structure (see below).
  • responseId - integer, a numeric identifier of the matching Patch response (1 .. 127).
  • sysexBlock - light userdata, an object holding the received SysEx message (see below).

patch.requestAll ()

Sends patch requests to all connected devices.

# Device data table
device = {
  id = 1,                 -- a device Id
  port = 0                -- a numeric port identifier
  channel = 1,            -- a channel number
}
# Example script
-- Issue a patch requests
patch.requestAll ()

-- Send a program change
function patch.onRequest (device)
    print ("Requesting patches...");

    if (device.id == 1) then
        midi.sendProgramChange (PORT_1, device.channel, 10)
    end
end

-- Parse an incoming response
function patch.onResponse (device, responseId, sysexBlock)
    -- print the header information
    print ("device id = " .. device.id)
    print ("device channel = " .. device.channel)
    print ("device port = " .. device.port)
    print ("responseId = " .. responseId)
    print ("manufacturer Id = " .. sysexBlock:getManufacturerSysexId ())

    -- print the received data
    for i = 1, sysexBlock:getLength() do
        print ("data[" .. i .. "] = " .. sysexBlock:peek(i))
    end

        -- update two parameters
    parameterMap.set (device.id, PT_CC7, 1, sysexBlock:peek (7));
    parameterMap.set (device.id, PT_CC7, 2, sysexBlock:peek (8));
end

# Preset

The preset library provides functions and callbacks to handle events related to presets.

# Functions

preset.onLoad ()

A callback function that is called immediately after the preset is loaded.

preset.onExit ()

A callback function that is called before a new preset is loaded.

# Timer

The timer library provides functionality to run perpetual task. The timer calls timer.onTick () function at given time periods or BPM. The timer makes it possible to implement MIDI clocks, LFOs, and many other repetitive processes. The timer is disabled by default and the initial rate is 120 BMP.

# Functions

timer.enable ()

Enable the timer. Once the timer is enabled, the timer.onTick () is run at given time periods.

timer.disable ()

Disable the timer. The period of the timer is kept.

timer.isEnabled ()

Get the status of the timer.

  • returns - boolean, true when the timer is enabled.

timer.setPeriod ()

Set the period to run the timer ticks.

  • period - integer, period specified in milliseconds (10..60000).

timer.getPeriod ()

Get the period of the timer ticks.

  • returns - integer, period specified in milliseconds.

timer.setBpm ()

Set the BPM of running the timer ticks.

  • period - integer, period specified in BPM (1..6000).

timer.getBpm ()

Get the BPM of the timer ticks.

  • returns - integer, period specified in BPM.

timer.onTick ()

A user function that will be run at the start of every timer cycle.

# Example script
-- A naive MIDI LFO implementation

faderValue = 0

timer.enable ()
timer.setBpm (120 * 16)

function timer.onTick ()
    parameterMap.set (1, PT_CC7, 1, faderValue)
    faderValue = math.fmod (faderValue + 1, 127)
end

# Transport

The transport library is similar to the timer. The main difference is that the tick signal is not generated by the library itself but requires MIDI real-time system and clock messages. The transport makes it possible to implement repetitive processes that are synced to the external MIDI clock. The transport is disabled by default.

# Functions

transport.enable ()

Enable the transport. Once the timer is enabled, the transport callback user functions will be called when related MIDI messages are received.

transport.disable ()

Disable the transport. Keep the transport disabled when you do not use it. You will save processing resources.

transport.isEnabled ()

Get the status of the transport.

  • returns - boolean, true when the transport is enabled.

transport.onClock (midiInput)

A callback to handle incoming MIDI Clock message. There are 24 Clock messages to one quarter note.

  • midiInput - data table, information about where the message came from.

transport.onStart (midiInput)

A callback to handle incoming MIDI System real-time Start message.

  • midiInput - data table, information about where the message came from.

transport.onStop (midiInput)

A callback to handle incoming MIDI System real-time Stop message.

  • midiInput - data table, information about where the message came from.

transport.onContinue (midiInput)

A callback to handle incoming MIDI System real-time Continue message.

  • midiInput - data table, information about where the message came from.

transport.onSongSelect (midiInput, songNumber)

A callback to handle incoming MIDI Song Select message.

  • midiInput - data table, information about where the message came from.
  • songNumber - integer, a numeric identifier of the song (0 .. 127).

transport.onSongPosition (midiInput, position)

A callback to handle incoming MIDI Song Position message.

  • midiInput - data table, information about where the message came from.
  • position - integer, a number of beats from the start of the song (0 .. 16383). :::
# Example script
faderValue = 0

function preset.onLoad ()  
  if (not transport.isEnabled ()) then
    transport.enable ()
  end

  print ("Transport enabled: " .. (transport.isEnabled () and "yes"  or "no"))
end

function transport.onClock (midiInput)
  parameterMap.set (1, PT_CC7, 1, faderValue)
  faderValue = faderValue + 1

  if (faderValue > 127) then
    faderValue = 0
  end
end

function transport.onStart (midiInput)
  print ("Start")
end

function transport.onStop (midiInput)
  print ("Stop")
end

function transport.onContinue (midiInput)
  print ("Continue")
end

function transport.onSongPosition (midiInput, position)
  print ("Song position " .. position)
end

function transport.onSongSelect (midiInput, songNumber)
  print ("Song select " .. songNumber)
end

# MIDI callbacks

The MIDI callbacks are here to process incoming MIDI messages. There is one general callback function onMessage () that is called when any type of MIDI message is received. There is also an array of callbacks for specific MIDI messages. These callbacks are called only when a specific MIDI message is received.

The callback function is registered by the Electra One Lua interpreter as soon as the callback function defined in the Lua script. Once the callback is defined, the firmware registers an extra hook to run the function. This consumes some of the processing resources. It is advised not to leave empty callback functions in your scripts.

The first parameter of all callback functions is the midiInput. The midiInput is a data table that describes the origin of the message.

midiInput = {
  interface = "USB dev",  -- a name of the IO interface where the messages was received
  port = 0                -- a numeric port identifier
}

Another important data structure is the midiMessage data table. the midiMessage carries the information about a MIDI message broken down do individual attributes. Different types of MIDI messages are represented with slightly different format of the midiMessage data table. The fields channel, type, data1, data2 are, however, common to all types of messages.

For example, a Control Change message can be access either as:

midiMessage = {
    channel = 1,
    type = CONTROL_CHANGE,
    data1 = 1,
    data2 = 127
}

or

midiMessage = {
    channel = 1,
    type = CONTROL_CHANGE,
    controllerNumber = 1,
    value = 127
}

The full description of all midiMessage variants is provided later in this document.

# Functions

midi.onMessage (midiInput, midiMessage)

A callback to handle all types of incoming MIDI messages.

  • midiInput - data table, information about where the message came from.
  • midiMessage - data table, description of the incoming MIDI message

midi.onNoteOn (midiInput, channel, noteNumber, velocity)

A callback to handle incoming MIDI Note On message.

  • midiInput - data table, information about where the message came from.
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • noteNumber - integer, an identifier of the MIDI note (0 .. 127).
  • velocity - integer, a velocity (0 .. 127).

midi.onNoteOff (midiInput, channel, noteNumber, velocity)

A callback to handle incoming MIDI Note Off message.

  • midiInput - data table, information about where the message came from.
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • noteNumber - integer, an identifier of the MIDI note (0 .. 127).
  • velocity - integer, a velocity (0 .. 127).

midi.onControlChange (midiInput, channel, controllerNumber, value)

A callback to handle incoming MIDI Control Change (CC) message.

  • midiInput - data table, information about where the message came from.
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • controllerNumber - integer, an identifier of the Control Change (0 .. 127).
  • value - integer, a value to be sent (0 .. 127).

midi.onAfterTouchPoly (midiInput, channel, noteNumber, pressure)

A callback to handle incoming MIDI Polyphonic Aftertouch message.

  • midiInput - data table, information about where the message came from.
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • noteNumber - integer, an identifier of the MIDI note (0 .. 127).
  • pressure - integer, a value representing the pressure applied (0 .. 127).

midi.onAfterTouchChannel (midiInput, channel, pressure)

A callback to handle incoming MIDI Channel Aftertouch message.

  • midiInput - data table, information about where the message came from.
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • pressure - integer, a value representing the pressure applied (0 .. 127).

midi.onProgramChange (midiInput, channel, programNumber)

A callback to handle incoming MIDI Program change message.

  • midiInput - data table, information about where the message came from.
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • programNumber - integer, an identifier of the CC (0 .. 127).

midi.onPitchBend (midiInput, channel, value)

A callback to handle incoming MIDI Pitch bend message.

  • midiInput - data table, information about where the message came from.
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • value - integer, an amount of Pitch Bend to be applied (-8191 .. 8192).

midi.onSongSelect (midiInput, songNumber)

A callback to handle incoming MIDI Song Select message.

  • midiInput - data table, information about where the message came from.
  • songNumber - integer, a numeric identifier of the song (0 .. 127).

midi.onSongPosition (midiInput, position)

A callback to handle incoming MIDI Song Position message.

  • midiInput - data table, information about where the message came from.
  • songPosition - integer, a number of beats from start of the song (0 .. 16383).

midi.onClock (midiInput)

A callback to handle incoming MIDI Clock message. There are 24 Clock messages to one quarter note.

  • midiInput - data table, information about where the message came from.

midi.onStart (midiInput)

A callback to handle incoming MIDI System real-time Start message.

  • midiInput - data table, information about where the message came from.

midi.onStop (midiInput)

A callback to handle incoming MIDI System real-time Stop message.

  • midiInput - data table, information about where the message came from.

midi.onContinue (midiInput)

A callback to handle incoming MIDI System real-time Continue message.

  • midiInput - data table, information about where the message came from.

midi.onActiveSensing (midiInput)

A callback to handle incoming MIDI Active Sensing message.

  • midiInput - data table, information about where the message came from.

midi.onSystemReset (midiInput)

A callback to handle incoming MIDI System Reset message.

  • midiInput - data table, information about where the message came from.

midi.onTuneRequest (midiInput)

A callback to handle incoming MIDI Tune Request message.

  • midiInput - data table, information about where the message came from.

midi.onSysex (midiInput, sysexBlock)

A callback to handle incoming MIDI SysEx message.

  • midiInput - data table, information about where the message came from.
  • sysexBlock - data table, an object holding the received SysEx message (see below).

# Example script 1

-- Receiving MIDI messages
--
-- Receiving MIDI messages with a generic midi.onMessage() callback

function midi.onMessage (midiInput, midiMessage)
    if midiMessage.type == SYSEX then
        print ("sysex message received: interface=" .. midiInput.interface)
        local sysexBlock = midiMessage.sysexBlock

        for i = 1, sysexBlock:getLength () do
            print (string.format ("data[%d] = %d", i, sysexBlock:peek (i)))
        end
    else
        -- generic approach using the data1 and data2
        print ("midi message received: interface=" .. midiInput.interface ..
               " channel=" .. midiMessage.channel ..
               " type=" .. midiMessage.type ..
               " data1=" .. midiMessage.data1 ..
               " data2=" .. midiMessage.data2)

        -- Message type specific attributes
        if midiMessage.type == NOTE_ON then
            print ("noteOn received: interface=" .. midiInput.interface ..
                   " channel=" .. midiMessage.channel ..
                   " noteNumber=" .. midiMessage.noteNumber ..
                   " velocity=" .. midiMessage.velocity)
        end
    end
end

# Example script 2

-- Receiving MIDI messages
--
-- Receiving MIDI messages with callbacks specific to MIDI message type

function midi.onControlChange (midiInput, channel, controllerNumber, value)
    print ("controlChange received: interface=" .. midiInput.interface ..
           " channel=" .. channel ..
           " controllerNumber=" .. controllerNumber .. " value=" .. value)
end


function midi.onNoteOn (midiInput, channel, noteNumber, velocity)
    print ("noteOn received: interface=" .. midiInput.interface ..
           " channel=" .. channel ..
           " noteNumber=" .. noteNumber .. " velocity=" .. velocity)
end


function midi.onNoteOff (midiInput, channel, noteNumber, velocity)
    print ("noteOff received: interface=" .. midiInput.interface ..
           " channel=" .. channel ..
           " noteNumber=" .. noteNumber .. " velocity=" .. velocity)
end


function midi.onAfterTouchPoly (midiInput, channel, noteNumber, pressure)
    print ("afterTouchPoly received: interface=" .. midiInput.interface ..
           " channel=" .. channel ..
           " noteNumber=" .. noteNumber .. " pressure=" .. pressure)
end


function midi.onProgramChange (midiInput, channel, programNumber)
    print ("programChange received: interface=" .. midiInput.interface ..
           " channel=" .. channel ..
           " programNumber=" .. programNumber)
end


function midi.onAfterTouchChannel (midiInput, channel, pressure)
    print ("afterTouchChannel received: interface=" .. midiInput.interface ..
           " channel=" .. channel ..
           " pressure=" .. pressure)
end


function midi.onPitchBendChannel (midiInput, channel, value)
    print ("pitchBend received: interface=" .. midiInput.interface ..
           " channel=" .. channel ..
           " value=" .. value)
end


function midi.onSongSelect (midiInput, songNumber)
    print ("songSelect received: interface=" .. midiInput.interface ..
           " songNumber=" .. songNumber)
end


function midi.onSongPosition (midiInput, position)
    print ("songPosition received: interface=" .. midiInput.interface ..
           " position=" .. position)
end


function midi.onClock (midiInput)
    print ("midi clock received: interface=" .. midiInput.interface)
end


function midi.onStart (midiInput)
    print ("start received: interface=" .. midiInput.interface)
end


function midi.onStop (midiInput)
    print ("stop received: interface=" .. midiInput.interface)
end


function midi.onContinue (midiInput)
    print ("continue received: interface=" .. midiInput.interface)
end


function midi.onActiveSensing (midiInput)
    print ("active sensing received: interface=" .. midiInput.interface)
end


function midi.onSystemReset (midiInput)
    print ("system reset received: interface=" .. midiInput.interface)
end


function midi.onTuneRequest (midiInput)
    print ("tune request received: interface=" .. midiInput.interface)
end


function midi.onSysex (midiInput, sysexBlock)
    print ("sysex message received: interface=" .. midiInput.interface)

    -- print the received data
    for i = 1, sysexBlock:getLength () do
        print (string.format ("data[%d] = %d", i, sysexBlock:peek (i)))
    end
end

# MIDI functions

The MIDI library provides functions to send raw MIDI messages. There are two ways of sending MIDI messages out. It can be done either by composing a midiMessage data table and passing it to generic midi.sendMessage () function, or by calling functions that send specific types of the MIDI messages, eg. midi.sendNoteOn ().

All functions send MIDI messages to all Electra's interfaces (USB Dev, USB host, MIDI IO). The idea is that this will follow the configuration of the low-level router of the Electra One controller. This might change in near future.

# Functions

midi.sendMessage (port, midiMessage)

A function to send a MIDI message defined as a midiMessage data table.

  • port - integer, a port identifier (see Globals for details).
  • midiMessage - data table, an outgoing MIDI message (see Globals for details).

midi.sendNoteOn (port, channel, noteNumber, velocity)

A function to send a Note On MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • noteNumber - integer, an identifier of the MIDI note (0 .. 127).
  • velocity - integer, a velocity (0 .. 127).

midi.sendNoteOff (port, channel, noteNumber, velocity)

A function to send a Note Off MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • noteNumber - integer, an identifier of the MIDI note (0 .. 127).
  • velocity - integer, a velocity (0 .. 127).

midi.sendControlChange (port, channel, controllerNumber, value)

A function to send a Control Change MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • controllerNumber - integer, an identifier of the Control Change (0 .. 127).
  • value - integer, a value to be sent (0 .. 127).

midi.sendAfterTouchPoly (port, channel, noteNumber, pressure)

A function to send a Polyphonic Aftertouch MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • noteNumber - integer, an identifier of the MIDI note (0 .. 127).
  • pressure - integer, a value representing the pressure applied (0 .. 127).

midi.sendAfterTouchChannel (port, channel, pressure)

A function to send a Channel Aftertouch MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • pressure - integer, a value representing the pressure applied (0 .. 127).

midi.sendProgramChange (port, channel, programNumber)

A function to send a Program Change MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • programNumber - integer, an identifier of the CC (0 .. 127).

midi.sendPitchBend (port, channel, value)

A function to send a Pitch Bend MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • value - integer, an amount of Pitch Bend to be applied (-8191 .. 8192).

midi.sendSongSelect (port, songNumber)

A function to send a Song Select MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • songNumber - integer, a numeric identifier of the song (0 .. 127).

midi.sendSongPosition (port, position)

A function to send a Song Position MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • songPosition - integer, a number of beats from start of the song (0 .. 16383).

midi.sendClock (port)

A function to send a System real-time Clock MIDI message.

  • port - integer, a port identifier (see Globals for details).

midi.sendStart (port)

A function to send a System real-time Start MIDI message.

  • port - integer, a port identifier (see Globals for details).

midi.sendStop (port)

A function to send a System real-time Stop MIDI message.

  • port - integer, a port identifier (see Globals for details).

midi.sendContinue (port)

A function to send a System real-time Continue MIDI message.

  • port - integer, a port identifier (see Globals for details).

midi.sendActiveSensing (port)

A function to send a Active Sensing MIDI message.

  • port - integer, a port identifier (see Globals for details).

midi.sendSystemReset (port)

A function to send a System Reset MIDI message.

  • port - integer, a port identifier (see Globals for details).

midi.sendTuneRequest (port)

A function to send a Tune Request MIDI message.

  • port - integer, a port identifier (see Globals for details).

midi.sendSysex (port, data)

A function to send a Sysex MIDI message. Currently limited to 256 bytes.

  • port - integer, a port identifier (see Globals for details).
  • data - array, an array with sequence of bytes to be sent. Do not enter F0 and F7 bytes.

midi.sendNrpn (port, channel, parameterNumber, value)

A function to send a NRPN MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • parameterNumber - integer, an identifier of the NRPN (0 .. 16383).
  • value - integer, a value to be sent (0 .. 16383).
  • lsbFirst - boolean, when true, the lsb and msb bytes will be swapped.

midi.sendRpn (port, channel, parameterNumber, value)

A function to send a RPN MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • parameterNumber - integer, an identifier of the RPN (0 .. 16383).
  • value - integer, a value to be sent (0 .. 16383).
  • lsbFirst - boolean, when true, the lsb and msb bytes will be swapped.

midi.sendControlChange14 (port, channel, controllerNumber, value)

A function to send a Control Change 14bit MIDI message.

  • port - integer, a port identifier (see Globals for details).
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • controllerNumber - integer, an identifier of the NRPN (0 .. 31).
  • value - integer, a value to be sent (0 .. 16383).
  • lsbFirst - boolean, when true, the lsb and msb bytes will be swapped.

midi.flush ()

A function to flush (force the sending of) all data from MIDI queues. Normally, outbound MIDI data is sent at regular time intervals. However, in certain situations, such as when a blocking call interrupts the processing users may wish to flush their MIDI data before entering the blocking call.

# Example script
-- Sending MIDI messages using the sendMessage ()


-- Control Change
midiMessage = {
   channel = 1,
   type = CONTROL_CHANGE,
   controllerNumber = 1,
   value = 127
}
midi.sendMessage (PORT_1, midiMessage)


-- Note On
midiMessage = {
   channel = 1,
   type = NOTE_ON,
   noteNumber = 60,
   velocity = 100
}
midi.sendMessage (PORT_1, midiMessage)


-- Note Off
midiMessage = {
   channel = 1,
   type = NOTE_OFF,
   noteNumber = 60,
   velocity = 100
}
midi.sendMessage (PORT_1, midiMessage)


-- Program Change
midiMessage = {
   channel = 1,
   type = PROGRAM_CHANGE,
   programNumber = 10
}
midi.sendMessage (PORT_1, midiMessage)


-- Pitch Bend
midiMessage = {
   channel = 1,
   type = PITCH_BEND,
   value = 513
}
midi.sendMessage (PORT_1, midiMessage)


-- Poly Pressure
midiMessage = {
   channel = 1,
   type = POLY_PRESSURE,
   noteNumber = 60,
   pressure = 100
}
midi.sendMessage (PORT_1, midiMessage)


-- Channel Pressure
midiMessage = {
   channel = 1,
   type = CHANNEL_PRESSURE,
   pressure = 64
}
midi.sendMessage (PORT_1, midiMessage)


-- Clock
midiMessage = {
   type = CLOCK
}
midi.sendMessage (PORT_1, midiMessage)


-- Start
midiMessage = {
   type = START
}
midi.sendMessage (PORT_1, midiMessage)


-- Stop
midiMessage = {
   type = STOP
}
midi.sendMessage (PORT_1, midiMessage)


-- Continue
midiMessage = {
   type = CONTINUE
}
midi.sendMessage (PORT_1, midiMessage)


-- Active Sensing
midiMessage = {
   type = ACTIVE_SENSING
}
midi.sendMessage (PORT_1, midiMessage)


-- System Reset
midiMessage = {
   type = RESET
}
midi.sendMessage (PORT_1, midiMessage)


-- Song Select
local ss = {
   type = SONG_SELECT,
   songNumber = 20
}
midi.sendMessage (PORT_1, ss)


-- Song Position
midiMessage = {
   type = SONG_POSITION,
   position = 10
}
midi.sendMessage (PORT_1, midiMessage)


-- Tune Request
midiMessage = {
   type = TUNE_REQUEST
}
midi.sendMessage (PORT_1, midiMessage)
# Example script
-- Sending MIDI messages out

print ("Sending MIDI out demo loaded")


-- Control change
midi.sendControlChange (PORT_1, 1, 10, 64)

-- Notes
midi.sendNoteOn (PORT_1, 1, 60, 100)
midi.sendNoteOff (PORT_1, 1, 60, 100)

-- Program change
midi.sendProgramChange (PORT_1, 1, 10)

-- Pitch bend
midi.sendPitchBend (PORT_1, 1, 513)

-- Polyphonic aftertouch
midi.sendAfterTouchPoly (PORT_1, 1, 60, 100)

-- Channel aftertouch
midi.sendAfterTouchChannel (PORT_1, 1, 100)

-- Send NRPN
midi.sendNrpn (PORT_1, 1, 512, 8192)

-- Send RPN
midi.sendRpn (PORT_1, 1, 1, 4096)

-- Send Control change 14bit
midi.sendControlChange14Bit (PORT_1, 1, 1, 2048)

-- Clock
midi.sendClock (PORT_1)

-- Start
midi.sendStart (PORT_1)

-- Stop
midi.sendStop (PORT_1)

-- Continue
midi.sendContinue (PORT_1)

-- Active sensing
midi.sendActiveSensing (PORT_1)

-- System reset
midi.sendSystemReset (PORT_1)

-- Song select
midi.sendSongSelect (PORT_1, 1)

-- Song position
midi.sendSongPosition (PORT_1, 200)

-- Tune request
midi.sendTuneRequest (PORT_1)

-- SysEx
midi.sendSysex (PORT_1, { 67, 32, 0 })

# Events

Events library provides a programatic way of managing what event notifications will Electra One emit as well as callback functions to handle emitted events.

# Functions

events.subscribe (eventFlags)

A function to instruct the controller what event notifications should be emitted. Subscribed events are result in calls to event callback functions and sending out SysEx event notifications.

  • eventFlags - integer, a byte where each bit represents an event type to be subscribed. (see Globals for details). Note, currently only PAGES and POTS are supported.
# Example script
-- Sending MIDI messages out

print ("Events demo")

events.subscribe(PAGES | POTS)
events.setPort(PORT_CTRL)

function events.onPageChange(newPageId, oldPageId)
  print ("old: " .. oldPageId)
  print ("new:" .. newPageId)
end

function events.onPotTouch(potId, controlId, touched)
  print ("potId: " .. potId)
  print ("controlId: " .. controlId)
  print ("touched: " .. (touched and "yes" or "no"))
end

events.setPort (port)

A function to set the USB dev port where the event notifications will be sent as SysEx messages.

  • port - integer, a port identifier (see Globals for details).

events.onPageChange (newPageId, oldPageId)

A callback function that is called when user switches preset page.

  • newPageId - integer, a numeric identifier of the page (1 .. 12). id attribute from the preset.
  • oldPageId - integer, a numeric identifier of the page (1 .. 12). id attribute from the preset.

events.onPotTouch (potId, controlId, touched)

A callback function that is called when user touches or releases the controller knobs.

  • potId - integer, an identifier of the pot (1 .. 12).
  • controlId - integer, an identifier of the control that received the touch event (1 .. 1023).
  • touched - boolean, set to true for initial touch, false for releasing the pot.

# Info

Info library provides a programatic way to display an informative text in the status bar at the bottom of the screen.

# Functions

info.setText (text)

A function to to display the text in the bottom status bar.

  • text - string, a text to be displayed. Up to 20 characters long.
# Example script
-- Display an info text 

info.setText("Hello world")

# Window

The window library allows more precise control over painting components and graphics on the controller screen.

# Functions

window.repaint ()

Forces repaint of current window.

window.stop ()

Stops the process of automatic repainting of components. This is useful when running larger batches of control updates. Using window.stop () and window.resume () speeds up the update and makes the changes to be displayed at once.

window.resume ()

Resumes the process of automatic repainting of components. The complete repaint of whole window is forced at the moment when the repainting process is resumed.

# Helpers

The helpers library consists of helper functions to make handling of certain common situations easier.

# Functions

slotToBounds (slot)

Converts a preset slot to a boundary box data table.

  • slot - integer, a numeric identifier of the preset slot (1 .. 36).
  • returns - array, an array consisting of x, y, width, height boundary box attributes.

boundsToSlot (bounds)

Converts a bounding box (bounds) to slot.

  • bounds - array, an array consisting of x, y, width, height boundary box attributes
  • returns - integer, a numeric identifier of the preset slot (1 .. 36).

delay (millis)

The function waits for a specified amount of time. On mkI, the delay is implemented as a blocking call, which halts all data processing for the specified duration. However, on mkII, only the Lua program waits while other components of the system continue processing the data.

  • millis - integer, number of milliseconds to wait ( 1 .. 5000).
# Example script
-- Move control to given slot

control = controls.get (1)
control:setBounds (helpers.slotToBounds (6))

# Data structures

# midiInput

midiInput is a data table that describes the origin of incoming MIDI messages. The consists of information about the MIDI interface and the port identifier.

  • interface - integer, an identifier of Electra's MIDI interface. (see Globals for details).
  • port - integer, a port identifier (see Globals for details).
# Example
midiInput = {
  interface = MIDI_IO,  -- a name of the IO interface where the messages was received
  port = PORT_1                -- a numeric port identifier
}

# midiMessage

The midiMessage data table carries the information about a MIDI message broken down do individual attributes. Different types of MIDI messages are represented with slightly different format of the midiMessage data table. The fields channel, type, data1, data2 are, however, common to all types of messages.

For example, a Control Change message can be access either as:

midiMessage = {
    channel = 1,
    type = CONTROL_CHANGE,
    data1 = 1,
    data2 = 127
}

or

midiMessage = {
    channel = 1,
    type = CONTROL_CHANGE,
    controllerNumber = 1,
    value = 127
}
  • channel - integer, a numeric representation of the MIDI channel (1 .. 16).
  • type - integer, an identifier of the MIDI message type (see Globals for details).
  • data1 - integer, the first data byte of MIDI message (0 .. 127).
  • data2 - integer, the second data byte of MIDI message (0 .. 127).
  • MIDI message type specific attrbutes are listed below.
# Attributes specific to MIDI message types
MIDI message type Attributes
NOTE_ON noteNumber

velocity
NOTE_OFF noteNumber

velocity
CONTROL_CHANGE controllerNumber

value
POLY_PRESSURE noteNumber

pressure
CHANNEL_PRESSURE pressure
PROGRAM_CHANGE programNumber
PITCH_BEND value
SONG_SELECT songNumber
SONG_POSITION songPosition

# Globals

The global variables are used to identify common constants that can be used instead of numbers.

# Hardware ports

Identifiers of the MIDI ports.

  • PORT_1
  • PORT_2
  • PORT_CTRL

# Interfaces

Types of MIDI interfaces.

  • MIDI_IO
  • USB_DEV
  • USB_HOST

# Change origins

Identifiers of the sources of the MIDI value change. Origin is passed as a parameter of the ParameterMap onChange callback.

  • INTERNAL
  • MIDI
  • LUA

# Parameter types

Types of Electra MIDI parameters. These types are higher abstraction of the standard MIDI message types.

  • PT_VIRTUAL
  • PT_CC7
  • PT_CC14
  • PT_NRPN
  • PT_RPN
  • PT_NOTE
  • PT_PROGRAM
  • PT_SYSEX
  • PT_START
  • PT_STOP
  • PT_TUNE
  • PT_ATPOLY
  • PT_ATCHANNEL
  • PT_PITCHBEND
  • PT_SPP
  • PT_RELCC
  • PT_NONE

# Control sets

Identifiers of the control sets. The control sets are groups of controls assigned to the pots.

  • CONTROL_SET_1
  • CONTROL_SET_2
  • CONTROL_SET_3

# Pots

Identifiers of the hardware pots. The pots are the rotary knobs to change the control values.

  • POT_1
  • POT_2
  • POT_3
  • POT_4
  • POT_5
  • POT_6
  • POT_7
  • POT_8
  • POT_9
  • POT_10
  • POT_11
  • POT_12

# Colors

Identifiers of standard Electra colors.

  • WHITE
  • RED
  • ORANGE
  • BLUE
  • GREEN
  • PURPLE

# Variants

  • VT_DEFAULT
  • VT_HIGHLIGHTED

# Bounding box

Identifiers of individual attributes of the bounding box (bounds).

  • X
  • Y
  • WIDTH
  • HEIGHT

# MIDI message types

Identifiers of standard MIDI messages.

  • CONTROL_CHANGE
  • NOTE_ON
  • NOTE_OFF
  • PROGRAM_CHANGE
  • POLY_PRESSURE
  • CHANNEL_PRESSURE
  • PITCH_BEND
  • CLOCK
  • START
  • STOP
  • CONTINUE
  • ACTIVE_SENSING
  • RESET
  • SONG_SELECT
  • SONG_POSITION
  • TUNE_REQUEST
  • TIME_CODE_QUARTER_FRAME
  • SYSEX

# Controller events

Flags indentifying individual types of events.

  • NONE
  • PAGES
  • CONTROL_SETS
  • USB_HOST_PORT
  • POTS
  • TOUCH
  • BUTTONS
  • WINDOWS