Hey, as you know Duplex is my brainchild, so I’ll try to answer your question as thoroughly as possible
Simple question, complex answer: when lights are turned on and off in Duplex, this involves a lot of things. I’m sorry I can’t give you a more straightforward answer than that, but Duplex really is based on a number of abstractions. For example, pre-optimization before the output stage means that a MIDI message which would tell a light to turn on doesn’t necessarily output anything if the light is already turned on. Although it obviously takes a few extra CPU cycles to compute this, it generally makes Duplex way more responsive on MIDI devices which would otherwise “suffocate” when they receive too many messages. As an example, flipping through pages in the matrix on the Launchpad is really snappy.
I could explain how the message is constructed, how it is abstracted from the hardware, etc. but instead, let’s look at how a button is implemented into an application:
local c = UIToggleButton(self.display)
c.group_name = "Some group in my device control-map"
c.on_change = function(obj)
-- do something
end
In the context of an application class, this is all the code it takes to instantiate a standard toggle-button. It’s a toggle-button, but it can also be made to respond to the whole set of press/hold/release events.
Then, the device configuration acts as a “permanent mapping” between that part of the application and your controller.
You can of course also choose roll your own user-interface components, but you would want to keep them really simple. For example, although possible, it wouldn’t really make any sense to write a “grid” user interface component, because such a thing is too versatile to describe in generic terms. It might work for one application, but the next…? It would be more practical to make a simple, stateless control and then script the hell out of it. For example, Daxton wrote the following button class for the Duplex Stepsequencer - a simple button with no internal state (features separate press/held/release events, but no toggle).
[luabox]
class ‘UIStepSeqButton’ (UIComponent)
function UIStepSeqButton:__init(display)
TRACE(‘UIStepSeqButton:__init’)
UIComponent.__init(self,display)
self.palette = {
foreground = table.rcopy(display.palette.color_1),
}
self.add_listeners(self)
– external event handlers
self.on_press = nil
self.on_release = nil
self.on_hold = nil
end
– user input via button
function UIStepSeqButton:do_press()
TRACE(“UIStepSeqButton:do_press()”)
if (self.on_press ~= nil) then
local msg = self:get_msg()
if not (self.group_name == msg.group_name) then
return
end
if not self:test(msg.column,msg.row) then
return
end
self:on_press()
end
end
– … and release
function UIStepSeqButton:do_release()
TRACE(“UIStepSeqButton:do_release()”)
if (self.on_release ~= nil) then
local msg = self:get_msg()
if not (self.group_name == msg.group_name) then
return
end
if not self:test(msg.column,msg.row) then
return
end
self:on_release()
end
end
– user input via (held) button
– on_hold() is the optional handler method
function UIStepSeqButton:do_hold()
TRACE(“UIStepSeqButton:do_hold()”)
if (self.on_hold ~= nil) then
local msg = self:get_msg()
if not (self.group_name == msg.group_name) then
return
end
if not self:test(msg.column,msg.row) then
return
end
self:on_hold()
end
end
function UIStepSeqButton:draw()
TRACE(“UIStepSeqButton:draw”)
local color = self.palette.foreground
local point = CanvasPoint()
point:apply(color)
– if the color is completely dark, this is also how
– LED buttons will represent the value (turned off)
if(get_color_average(color.color)>0x00)then
point.val = true
else
point.val = false
end
self.canvas:fill(point)
UIComponent.draw(self)
end
function UIStepSeqButton:add_listeners()
self._display.device.message_stream:add_listener(
self, DEVICE_EVENT_BUTTON_PRESSED,
function() self:do_press() end )
self._display.device.message_stream:add_listener(
self,DEVICE_EVENT_BUTTON_HELD,
function() self:do_hold() end )
self._display.device.message_stream:add_listener(
self,DEVICE_EVENT_BUTTON_RELEASED,
function() self:do_release() end )
end
function UIStepSeqButton:remove_listeners()
self._display.device.message_stream:remove_listener(
self,DEVICE_EVENT_BUTTON_PRESSED)
self._display.device.message_stream:remove_listener(
self,DEVICE_EVENT_BUTTON_HELD)
self._display.device.message_stream:remove_listener(
self,DEVICE_EVENT_BUTTON_RELEASED)
end
[/luabox]
Obviously, as this button is lower-level stuff, there’s quite a bit of “framework overhead”. What you are interested in probably lies within the draw() method?
Finally, there’s the question of going through MIDI mapping. This is the path I deliberately chose not to go with Duplex, because of the following limitations:
- MIDI mapping is one-to-one (could you describe something like Grid Pie with anything but a few hundred individual mappings?). Duplex mappings, on the other hand, are dynamic and group-based - you don’t need to map each button and you don’t need to specify how many buttons your controller has, you only need to provide the name of the control-map group.
- I want automatic (or one-click) activation of my preferred device configuration(s), without having to go through the process of mapping each song
There’s still limitations in Duplex (no support for relative knobs, >7bit MIDI message support), but they are becoming fewer with each release.