Skip to content
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
432 lines (396 sloc) 12.1 KB
--- Pedal
-- @classmod Pedal
local UI = require "ui"
local Controlspecs = include("lib/ui/util/controlspecs")
local ScreenState = include("lib/ui/util/screen_state")
local Label = include("lib/ui/util/label")
local ModMatrixUtil = include("lib/ui/util/modmatrix")
local MiUgensInstaller = include("lib/ui/util/mi_ugens_installer")
local Pedal = {}
Pedal.id = "pedal"
Pedal.required_files = {}
Pedal.engine_state = "ready"
Pedal.installer = MiUgensInstaller
function Pedal:new(bypass_by_default)
i = {}
setmetatable(i, self)
self.__index = self
-- SUBCLASS: must set the pedal ID, e.g. self.id = "reverb"
i.section_index = 1;
i.tab_bypass_label = Label.new({y = 56})
i.bypass_by_default = bypass_by_default
i.tab_mix_dial = UI.Dial.new(0, 12, 22, 50, 0, 100, 1)
i.modmatrix = ModMatrixUtil:new()
-- SUBCLASS: must call this to complete setup
-- i:_complete_initialization()
return i
end
function Pedal.params()
-- SUBCLASS: must define
-- return Pedal._default_params as the last element in the table
end
function Pedal:name(short)
-- SUBCLASS: must define
-- return short and "short_name" or "long_name", where short_name is ideally <= 5 characters
end
-- Inner implementation, called by subclasses
function Pedal:_complete_initialization()
self:_initialize_widgets()
self:_update_section()
self:_update_active_widgets()
self._param_id_to_widget = {}
for section_index, section in ipairs(self._param_ids) do
for tab_index, tab in ipairs(section) do
for param_index, param_id in ipairs(tab) do
self._param_id_to_widget[param_id] = self._widgets[section_index][tab_index][param_index]
local param_value = params:get(param_id)
if param_id == self.id .. "_bypass" then
param_value = self.bypass_by_default and 2 or 1
params:set(param_id, param_value)
end
self:_set_value_from_param_value(param_id, param_value)
end
end
end
self:_add_param_actions()
end
function Pedal._default_params(id_prefix)
local bypass_control = {
id = id_prefix .. "_bypass",
name = "Bypass",
type = "option",
options = {"Effect Enabled", "Bypassed"},
}
local mix_control = {
id = id_prefix .. "_mix",
name = "Dry/Wet",
type = "control",
controlspec = Controlspecs.MIX,
}
local in_gain_control = {
id = id_prefix .. "_in_gain",
name = "In Gain",
type = "control",
controlspec = Controlspecs.GAIN,
}
local out_gain_control = {
id = id_prefix .. "_out_gain",
name = "Out Gain",
type = "control",
controlspec = Controlspecs.GAIN,
}
return {{bypass_control, mix_control}, {in_gain_control, out_gain_control}}
end
function Pedal:_default_section()
return {"Bypass & Mix", "In & Out Gains"}
end
-- Public interface
function Pedal:add_params()
local param_ids = {}
local params_to_add = {}
for section_index, section in ipairs(self.params()) do
param_ids[section_index] = {}
for tab_index, tab in ipairs(section) do
param_ids[section_index][tab_index] = {}
for param_index, param in ipairs(tab) do
param_ids[section_index][tab_index][param_index] = param.id
table.insert(params_to_add, param)
end
end
end
self._param_ids = param_ids
local param_ids_flat = {}
local params_by_id = {}
params:add_group(self:name(), #params_to_add)
for i, param in ipairs(params_to_add) do
table.insert(param_ids_flat, param.id)
params_by_id[param.id] = param
params:add(param)
end
self._param_ids_flat = param_ids_flat
self._params_by_id = params_by_id
end
-- Called when the page is scrolled to
function Pedal:enter(arcify)
if params:get("arc_mode") == 1 then
for i=1,3 do
if i <= #self._param_ids_flat - 3 then
arcify:map_encoder_via_params(i, self._param_ids_flat[i])
else
arcify:map_encoder_via_params(i, "none")
end
end
arcify:map_encoder_via_params(4, self.id .. "_mix")
end
-- TODO: consider changing arcification when you switch "horizontal" pages?
end
function Pedal:is_engine_ready()
return self.engine_state == "ready"
end
function Pedal:update_engine_state()
if self.engine_state ~= "pending" then
return
end
self.engine_state_poll = poll.set(self.id .. "_ready_poll")
self.engine_state_poll.callback = function(engine_state)
if engine_state == 2 then
self.engine_state = "pending"
else
self.engine_state = engine_state == 1 and "ready" or "needs_restart"
self.engine_state_poll:stop()
self.engine_state_poll = nil
end
end
self.engine_state_poll.time = 0.25
self.engine_state_poll:start()
end
function Pedal:key(n, z)
-- Key-up currently has no meaning
if z ~= 1 then
return false
end
-- Change the focused tab
local direction = 0
if n == 2 then
direction = -1
elseif n == 3 then
direction = 1
end
-- Going beyond the edge of the current section takes to another section (either direction)
if self.tabs.index + direction > #self.tabs.titles or self.tabs.index + direction == 0 then
self.section_index = (self.section_index + direction) % #self.sections
-- Handle how modulo interacts with 1-indexing
if self.section_index == 0 then
self.section_index = #self.sections
end
self:_update_section()
-- If we're moving left, enter a section on the right-most tab
if direction == -1 then
self.tabs:set_index(#self.tabs.titles)
end
else
self.tabs:set_index_delta(direction, false)
end
self:_update_active_widgets()
return true
end
function Pedal:enc(n, delta)
-- Change the value of a focused widget
local param_id = nil
-- If there's only one widget, always use it
if #self._param_ids[self.section_index][self.tabs.index] == 1 then
param_id = self._param_ids[self.section_index][self.tabs.index][1]
else
local widget_index = n - 1
param_id = self._param_ids[self.section_index][self.tabs.index][widget_index]
end
if param_id == nil then
return false
end
params:delta(param_id, delta)
return true
end
function Pedal:redraw()
self.tabs:redraw()
for tab_index, tab in ipairs(self._widgets[self.section_index]) do
for widget_index, widget in ipairs(tab) do
widget:redraw()
end
end
-- Left arrow when there's a section to our left
if self.section_index > 1 then
screen.move(0, 6)
screen.level(3)
screen.text("<")
end
-- Right arrow when there's a section to our left
if self.section_index < #self.sections then
screen.move(128, 6)
screen.level(3)
screen.text_right(">")
end
-- Name of pedal at the bottom, leaving room for the descender
screen.move(64, 62)
screen.level(15)
screen.text_center(self:name())
-- Prevent a stray line being drawn
screen.stroke()
end
function Pedal:render_as_tab(offset, width, is_active)
local center_x = offset + (width / 2)
self.tab_mix_dial = UI.Dial.new(center_x - 11, 16, 22, self.tab_mix_dial.value, 0, 100, 1)
self.tab_mix_dial.active = is_active
self.tab_mix_dial:redraw()
if self.tab_bypass_label.text == "ON" then
self.tab_bypass_label.level = is_active and 15 or 6
else
self.tab_bypass_label.level = is_active and 3 or 1
end
self.tab_bypass_label.x = center_x
self.tab_bypass_label:redraw()
end
function Pedal:toggle_bypass()
local bypass_param_id = self.id .. "_bypass"
local is_currently_bypassed = params:get(bypass_param_id) == 2
params:set(bypass_param_id, is_currently_bypassed and 1 or 2)
end
function Pedal:scroll_mix(delta)
params:delta(self.id .. "_mix", delta)
end
-- Inner implementation
function Pedal:cleanup()
if self.engine_state_poll ~= nil then
self.engine_state_poll:stop()
self.engine_state_poll = nil
end
self.modmatrix = nil
-- TODO: Any additional cleanup needed?
end
function Pedal:_add_param_actions()
for section_index, section in ipairs(self._param_ids) do
for tab_index, tab in ipairs(section) do
for param_index, param_id in ipairs(tab) do
params:set_action(param_id, function(value)
self:_set_value_from_param_value(param_id, value)
end)
end
end
end
end
function Pedal:_initialize_widgets()
local widgets = {}
for section_index, section in ipairs(self:params()) do
widgets[section_index] = {}
for tab_index, tab in ipairs(section) do
widgets[section_index][tab_index] = {}
for param_index, param in ipairs(tab) do
local x, y = self:_position_for_widget(section_index, tab_index, param_index, param.type)
if param.type == "control" then
local is_dbs = (Controlspecs.is_gain(param.controlspec) or Controlspecs.is_boostcut(param.controlspec))
widgets[section_index][tab_index][param_index] = UI.Dial.new(
x, y, 22,
param.controlspec.default,
param.controlspec.minval,
param.controlspec.maxval,
Controlspecs.is_mix(param.controlspec) and 1 or param.controlspec.step,
(not is_dbs) and param.controlspec.minval or 0,
is_dbs and {0} or {}
)
elseif param.type == "option" then
widgets[section_index][tab_index][param_index] = Label.new({
x = x,
y = y,
text = param.default or param.options[1],
})
end
end
end
end
self._widgets = widgets
end
function Pedal:_position_for_widget(section_index, tab_index, widget_index, widget_type)
local tabs = self:params()[section_index]
local widgets = tabs[tab_index]
if #tabs == 1 then
if #widgets == 1 then
if widget_type == "control" then return 54, 19.5 end
return 64, 36
end
if widget_index == 1 then
if widget_type == "control" then return 21, 19.5 end
return 32, 36
end
if widget_type == "control" then return 86, 19.5 end
return 96, 36
end
local x, y
if #widgets == 1 then
if widget_type == "control" then
x, y = 21, 19.5
else
x, y = 32, 36
end
else
local other_is_dial = widgets[(widget_index % 2) + 1].type == "control"
if widget_index == 1 then
if widget_type == "control" then
if other_is_dial then
x, y = 9, 13
else
x, y = 21, 13
end
else
if other_is_dial then
x, y = 32, 18
else
x, y = 32, 26
end
end
else
if widget_type == "control" then
if other_is_dial then
x, y = 34.5, 26
else
x, y = 22, 26
end
else
if other_is_dial then
x, y = 32, 52
else
x, y = 32, 46
end
end
end
end
if tab_index == 2 then
x = x + 64
end
return x, y
end
function Pedal:_set_value_from_param_value(param_id, value)
local coerced_value = value
if param_id == self.id .. "_bypass" then
self.tab_bypass_label.text = value == 1 and "ON" or "OFF"
elseif param_id == self.id .. "_mix" then
self.tab_mix_dial:set_value(coerced_value)
end
local widget = self._param_id_to_widget[param_id]
if widget.__index == UI.Dial then
widget:set_value(coerced_value)
else
-- We use the un-coerced value
widget.text = self._params_by_id[param_id].options[value]
-- Then coerce the index to zero-indexed for the engine
coerced_value = value - 1
end
ScreenState.mark_screen_dirty(true)
self:_message_engine_for_param_change(param_id, coerced_value)
end
function Pedal:_message_engine_for_param_change(param_id, value)
param = self._params_by_id[param_id]
local coerced_value = self.modmatrix:mod(param, value)
if param.controlspec ~= nil then
if Controlspecs.is_mix(param.controlspec) then
coerced_value = coerced_value / 100.0
elseif Controlspecs.is_gain(param.controlspec) then
coerced_value = util.dbamp(coerced_value)
end
end
engine[param_id](coerced_value)
end
function Pedal:_update_section()
self.tabs = UI.Tabs.new(1, self.sections[self.section_index])
end
function Pedal:_update_active_widgets()
for tab_index, tab in ipairs(self._widgets[self.section_index]) do
for widget_index, widget in ipairs(tab) do
local is_active = tab_index == self.tabs.index
if widget.__index == UI.Dial then
widget.active = is_active
else
widget.level = is_active and 15 or 3
end
end
end
end
return Pedal