Skip to content
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
800 lines (739 sloc) 27.6 KB
--- Board
-- @classmod Board
local ControlSpec = require "controlspec"
local UI = require "ui"
local tabutil = require "tabutil"
local Controlspecs = include("lib/ui/util/controlspecs")
local ScreenState = include("lib/ui/util/screen_state")
local Alert = include("lib/ui/util/alert")
local OptionalPedals = include("lib/ui/util/optional_pedals")
-- All possible pedals, ordered by something like how common they are
local pedal_classes = {
include("lib/ui/pedals/delay"),
include("lib/ui/pedals/reverb"),
include("lib/ui/pedals/overdrive"),
include("lib/ui/pedals/chorus"),
include("lib/ui/pedals/tremolo"),
include("lib/ui/pedals/distortion"),
include("lib/ui/pedals/flanger"),
include("lib/ui/pedals/phaser"),
include("lib/ui/pedals/compressor"),
include("lib/ui/pedals/sustain"),
include("lib/ui/pedals/bitcrusher"),
include("lib/ui/pedals/wavefolder"),
include("lib/ui/pedals/ring_modulator"),
include("lib/ui/pedals/pitch_shifter"),
include("lib/ui/pedals/sub_boost"),
include("lib/ui/pedals/vibrato"),
include("lib/ui/pedals/auto_wah"),
include("lib/ui/pedals/lofi"),
include("lib/ui/pedals/rings"),
include("lib/ui/pedals/clouds"),
include("lib/ui/pedals/amp_simulator"),
include("lib/ui/pedals/equalizer"),
include("lib/ui/pedals/tuner"),
}
local MAX_SLOTS = math.min(4, #pedal_classes)
local EMPTY_PEDAL = "None"
local pedal_names = {EMPTY_PEDAL}
for i, pedal_class in ipairs(pedal_classes) do
table.insert(pedal_names, pedal_class:name())
end
local CLICK_DURATION = 0.7
local CPU_BASELINE = 8
local CPU_ALERT_THRESHOLD = 70
local Board = {
pedal_classes = pedal_classes
}
function Board:new(
add_page,
insert_page_at_index,
remove_page,
swap_page,
set_page_index
)
local i = {}
setmetatable(i, self)
self.__index = self
-- Callbacks to our parent when we take page-editing actions
i._add_page = add_page
i._insert_page_at_index = insert_page_at_index
i._remove_page = remove_page
i._swap_page = swap_page
i._set_page_index = set_page_index
i.pedals = {}
i._pending_pedal_class_index = 0
i._manual_action_will_require_param_sync = false
i._is_syncing_to_params = false
i._alt_key_down_time = nil
i._alt_action_taken = false
i._add_bypassed = false
i._alert = nil
i._cpu_alert_pedal = nil
i._pedal_install_flow = nil
i:_setup_tabs()
i:_add_param_actions()
return i
end
function Board:add_optional_pedals_if_ready()
for i, pedal_class in ipairs(pedal_classes) do
OptionalPedals.add_if_ready(pedal_class)
end
end
function Board:add_params()
params:add({
id="num_input_channels",
name="Input Mode",
type="option",
options={"Mono", "Stereo"},
default=(params:get('monitor_mode') == 1 and 2 or 1),
action=function(value)
local coerced_value = value
if value == "Stereo" then coerced_value = 2 elseif value == "Mono" then coerced_value = 1 end
engine.set_num_input_channels(coerced_value)
end
})
params:add({
id="input_amp",
name="Input Gain",
type="control",
controlspec=Controlspecs.BOOSTCUT,
action=function(value) engine.set_input_amp(util.dbamp(value)) end
})
params:add({
id="output_amp",
name="Output Gain",
type="control",
controlspec=Controlspecs.BOOSTCUT,
action=function(value) engine.set_output_amp(util.dbamp(value)) end
})
-- Don't add the group, as for now we're just hiding these params
-- params:add_group("Board", MAX_SLOTS)
for i = 1, MAX_SLOTS do
local param_id = "pedal_" .. i
params:add({
id=param_id,
name="Pedal " .. i,
type="option",
options=pedal_names,
-- actions are set up during construction in _add_param_actions
})
-- We're primarily hiding these because this page's UI enforces certain ways to change the board,
-- while editing via params directly removes some of those restrictions in ways that are difficult to handle
-- (e.g., putting EMPTY_PEDAL between other pedals, selecting the same pedal twice, etc.))
-- TODO: figure out why norns/lua/core/paramset:hide doesn't lookup string param_ids for hiding properly
params:hide(params.lookup[param_id])
end
-- Tell each pedal type to set up its params
for i, pedal_class in ipairs(pedal_classes) do
pedal_class:add_params()
end
end
function Board:_add_param_actions()
for i = 1, MAX_SLOTS do
local param_id = "pedal_" .. i
params:set_action(param_id, function(value)
self:_set_pedal_by_index(i, value)
end)
end
end
function Board:enter(arcify)
-- Called when the page is scrolled to
self.tabs:set_index(1)
self:_set_pending_pedal_class_to_match_tab(self.tabs.index)
-- Arcify encoders control Mix% for each pedal on the board
if params:get("arc_mode") == 1 then
for i=1,4 do
if i <= #self.pedals then
arcify:map_encoder_via_params(i, self.pedals[i].id .. "_mix")
else
arcify:map_encoder_via_params(i, "none")
end
end
end
end
function Board:key(n, z)
-- While showing a pedal install flow, K2 cancels, K3 is delegated to the install flow
if self._pedal_install_flow ~= nil then
if z ~= 1 then return false end
if n == 2 then
if self._pedal_install_flow:can_cancel() then
self._pedal_install_flow = nil
return true
end
return false
elseif n == 3 then
if self._pedal_install_flow.state == self._pedal_install_flow.InstallerStates.ALL_READY then
self._pedal_install_flow = nil
else
return self._pedal_install_flow:key(n, z)
end
else
return false
end
end
-- While showing the CPU alert, K2 cancels, K3 confirms
if self._alert ~= nil then
if z ~= 1 then return false end
if self._cpu_alert_pedal ~= nil then
if n == 2 then
self._alert = nil
self._cpu_alert_pedal = nil
return true
elseif n == 3 then
local param_value = self._cpu_alert_pedal[1]
local index = self._cpu_alert_pedal[2]
self._alert = nil
self._cpu_alert_pedal = nil
params:set("pedal_" .. index, param_value)
return true
end
return false
else
-- Dismiss other alerts with no side-effects
if n == 2 or n == 3 then
self._alert = nil
return true
end
return false
end
end
if n == 2 then
-- Key down on K2 enables alt mode
if z == 1 then
self._alt_key_down_time = util.time()
-- Record what tab we started on for our K2+E2 pedal reordering feature (ignoring the new slot)
if not self:_is_new_slot(self.tabs.index) then
self._reorder_source = self.tabs.index
self._reorder_destination = self.tabs.index
end
return false
end
-- Key up on K2 after scrolling E2 commits a pedal re-order
if self._reorder_source ~= self._reorder_destination then
self._alt_key_down_time = nil
self._alt_action_taken = false
self:_reorder_pedals()
return true
end
-- Key up on K2 after an alt action was taken, or even just after a longer held time, counts as nothing
if self._alt_key_down_time then
local key_down_duration = util.time() - self._alt_key_down_time
self._alt_key_down_time = nil
if self._alt_action_taken or key_down_duration > CLICK_DURATION then
self._alt_action_taken = false
return false
end
end
-- Otherwise we count this key-up as a click on K2
-- K2 click means nothing on the New slot
if self:_is_new_slot(self.tabs.index) then
return false
end
-- Jump to focused pedal's page
self._set_page_index(self.tabs.index + 1)
return true
elseif n == 3 then
-- Key-up on K3 has no meaning
if z == 0 then
return false
end
local add_or_switch = self:_is_new_slot(self.tabs.index) or self:_slot_has_pending_switch(self.tabs.index)
-- Alt+K3 means toggle bypass on current pedal
if self:_is_alt_mode() and not self:_is_new_slot(self.tabs.index) and not add_or_switch then
self.pedals[self.tabs.index]:toggle_bypass()
self._alt_action_taken = true
return true
end
local param_value = self:_pending_pedal_class() and self:_param_value_for_pedal_name(self:_pending_pedal_class():name()) or 1
-- If the pedal is not ready, show a message explaining how to set up the pedal
if add_or_switch and (self:_pending_pedal_class() and not self:_pending_pedal_class():is_engine_ready()) then
self._pedal_install_flow = self:_pending_pedal_class().installer:new(self:_pending_pedal_class())
return true
end
-- If we are in alt mode when adding or swapping a pedal, we want to make the new pedal already bypassed
self._add_bypassed = false
if self:_is_alt_mode() and param_value ~= 1 then
self._add_bypassed = true
-- In some sense an alt-action was taken, but we're going to turn off alt mode immediately here anyway
self._alt_action_taken = false
self._alt_key_down_time = nil
end
-- We're on the new slot, so add the pending pedal
if self:_is_new_slot(self.tabs.index) then
local would_push_cpu = self:_check_cpu_load(param_value, self.tabs.index)
if would_push_cpu then
self:_show_cpu_alert(param_value, self.tabs.index)
else
params:set("pedal_" .. self.tabs.index, param_value)
end
return true
end
-- We're on an existing slot, and the pending pedal type is different than the current type
if self:_slot_has_pending_switch(self.tabs.index) then
local would_push_cpu = self:_check_cpu_load(param_value, self.tabs.index)
if would_push_cpu then
self:_show_cpu_alert(param_value, self.tabs.index)
else
if param_value == 1 then
-- If we're removing a pedal, we're going to need to do some local->param syncing
self._manual_action_will_require_param_sync = true
end
params:set("pedal_" .. self.tabs.index, param_value)
end
return true
end
end
return false
end
function Board:enc(n, delta)
-- Encoders do nothing while showing an alert or install flow
if self._alert ~= nil then return false end
if self._pedal_install_flow ~= nil then return false end
if n == 2 then
-- Alt+E2 re-orders pedals
if self:_is_alt_mode() then
-- Alt+E2 doesn't do anything on the New slot
if self:_is_new_slot(self.tabs.index) then
return false
end
self._alt_action_taken = true
local direction = util.clamp(delta, -1, 1)
self._reorder_destination = util.clamp(self._reorder_destination + direction, 1, #self.pedals)
self:_setup_tabs()
return true
end
-- Change which pedal slot is focused
self.tabs:set_index_delta(util.clamp(delta, -1, 1), false)
self:_set_pending_pedal_class_to_match_tab(self.tabs.index)
return true
elseif n == 3 then
-- Alt+E3 changes wet/dry
if self:_is_alt_mode() then
-- Alt+E3 doesn't do anything on the New slot
if self:_is_new_slot(self.tabs.index) then
return false
end
self.pedals[self.tabs.index]:scroll_mix(delta)
self._alt_action_taken = true
return true
end
-- Change the type of pedal we're considering adding or switching to
if self:_pending_pedal_class() == nil then
self._pending_pedal_class_index = 0
end
-- Allow selecting EMPTY_PEDAL to remove the pedal
local minimum = 0
-- We don't want to allow selection of a pedal already in use in another slot
-- (primarily due to technical restrictions in how params work)
-- So we make list of pedal classes in the same order as master list, but removing classes in use by other tabs
local indexes_of_active_pedals = {}
local current_pedal_class_index_at_current_tab = 0
for i = 1, #self.pedals do
local pedal_class_index = self:_get_pedal_class_index_for_tab(i)
table.insert(indexes_of_active_pedals, pedal_class_index)
if i == self.tabs.index then
current_pedal_class_index_at_current_tab = pedal_class_index
end
end
local valid_pedal_classes = {}
local pending_index_in_valid_classes = minimum
for i, pedal_class in ipairs(pedal_classes) do
if i == current_pedal_class_index_at_current_tab or not tabutil.contains(indexes_of_active_pedals, i) then
table.insert(valid_pedal_classes, pedal_class)
if i == self._pending_pedal_class_index then
pending_index_in_valid_classes = #valid_pedal_classes
end
end
end
-- We then take our index within that more limited list, and have the encoder scroll us within the limited list
local new_index_in_valid_classes = util.clamp(pending_index_in_valid_classes + delta, minimum, #valid_pedal_classes)
-- Finally, we map this index within the limited list back to the index in the master list
if new_index_in_valid_classes == 0 then
-- If we've selected EMPTY_PEDAL, then we don't need to do any more work, just use that directly
self._pending_pedal_class_index = 0
else
self._pending_pedal_class_index = tabutil.key(pedal_classes, valid_pedal_classes[new_index_in_valid_classes])
end
return true
end
return false
end
function Board:redraw()
if self._sync_to_params_on_next_redraw then
self:_sync_pedals_to_params(true)
end
if self._alert ~= nil then
self._alert:redraw()
return
end
if self._pedal_install_flow ~= nil then
self._pedal_install_flow:redraw()
return
end
self.tabs:redraw()
for i, title in ipairs(self.tabs.titles) do
render_index = i
-- If we're mid-pedal-reorder, render the after-reorder state
if self._reorder_source ~= self._reorder_destination then
if self._reorder_source > self._reorder_destination then
if i == self._reorder_destination then
render_index = self._reorder_source
elseif i > self._reorder_destination and i <= self._reorder_source then
render_index = i - 1
end
else
if i == self._reorder_destination then
render_index = self._reorder_source
elseif i >= self._reorder_source and i < self._reorder_destination then
render_index = i + 1
end
end
end
self:_render_tab_content(render_index)
end
end
function Board:cleanup()
-- Remove possible circular references
self._add_page = nil
self._remove_page = nil
self._swap_page = nil
self._set_page_index = nil
if self._pedal_install_flow ~= nil then
self._pedal_install_flow:cleanup()
end
end
function Board:_setup_tabs()
local tab_names = {}
local use_short_names = self:_use_short_names()
for i, pedal in ipairs(self.pedals) do
pedal_name_index = i
-- If we're mid-pedal-reorder, render the after-reorder state
if self._reorder_source ~= self._reorder_destination then
if self._reorder_source > self._reorder_destination then
if i == self._reorder_destination then
pedal_name_index = self._reorder_source
elseif i > self._reorder_destination and i <= self._reorder_source then
pedal_name_index = i - 1
end
else
if i == self._reorder_destination then
pedal_name_index = self._reorder_source
elseif i >= self._reorder_source and i < self._reorder_destination then
pedal_name_index = i + 1
end
end
end
table.insert(tab_names, self.pedals[pedal_name_index]:name(use_short_names))
end
-- Only add the New slot if we're not yet at the max
if #self.pedals ~= MAX_SLOTS then
local new_pedal_title = #self.pedals == 0 and "Add Pedal?" or "Add?"
table.insert(tab_names, new_pedal_title)
end
self.tabs = UI.Tabs.new(1, tab_names)
-- If we're mid-pedal-reorder, show the reorder destination as the active tab
if self._reorder_destination then
self.tabs:set_index(self._reorder_destination)
end
end
function Board:_render_tab_content(i)
local offset, width = self:_get_offset_and_width(i)
if i == self.tabs.index then
local center_x = offset + (width / 2)
local center_y = 38
if self:_is_new_slot(i) then
if self:_pending_pedal_class() == nil then
-- Render "No Pedal Selected" as centered text
screen.move(center_x, center_y)
screen.text_center(self:_name_of_pending_pedal())
else
-- Render "Add {name of the new pedal}" as centered text
screen.level(self:_pending_pedal_class():is_engine_ready() and 15 or 6)
screen.move(center_x, center_y - 4)
screen.text_center(self:_use_short_names() and "+" or "Add")
screen.move(center_x, center_y + 4)
screen.text_center(self:_name_of_pending_pedal())
screen.level(15)
end
-- Prevent a stray line being drawn
screen.stroke()
return
elseif self._reorder_source ~= self._reorder_destination and i == self._reorder_destination then
-- Render "Move here" as centered text
screen.level(15)
screen.move(center_x, center_y - 4)
screen.text_center("Move")
screen.move(center_x, center_y + 4)
screen.text_center("here")
-- Prevent a stray line being drawn
screen.stroke()
return
elseif self:_slot_has_pending_switch(i) then
local use_short_names = self:_use_short_names()
if self:_pending_pedal_class() == nil then
-- Render "Remove" as centered text
screen.move(center_x, center_y)
screen.text_center(use_short_names and "X" or "Remove")
else
-- Render "Switch to {name of the new pedal}" as centered text
screen.level(self:_pending_pedal_class():is_engine_ready() and 15 or 6)
screen.move(center_x, center_y - 4)
screen.text_center(use_short_names and "->" or "Switch to")
screen.move(center_x, center_y + 4)
screen.text_center(self:_name_of_pending_pedal())
screen.level(15)
end
-- Prevent a stray line being drawn
screen.stroke()
return
end
end
-- The New slot renders nothing if not highlighted
if self:_is_new_slot(i) then
return
end
-- If we're mid-pedal-reorder, render the after-reorder state
render_as_active = i == self.tabs.index
if self._reorder_source ~= self._reorder_destination then
-- We've already rendered a tab as active in this case
render_as_active = false
if self._reorder_source > self._reorder_destination then
if i == self._reorder_destination then
i = self._reorder_source
elseif i > self._reorder_destination and i <= self._reorder_source then
i = i - 1
end
else
if i == self._reorder_destination then
i = self._reorder_source
elseif i >= self._reorder_source and i < self._reorder_destination then
i = i + 1
end
end
end
-- Defer to the pedal instance to render as tab
self.pedals[i]:render_as_tab(offset, width, render_as_active)
end
function Board:_get_offset_and_width(i)
local num_tabs = (#self.tabs.titles == 0) and 1 or #self.tabs.titles
local width = 128 / num_tabs
local offset = width * (i - 1)
return offset, width
end
function Board:_pending_pedal_class()
if self._pending_pedal_class_index == nil or self._pending_pedal_class_index == 0 then
return nil
end
return pedal_classes[self._pending_pedal_class_index]
end
function Board:_slot_has_pending_switch(i)
local pending_pedal_class = self:_pending_pedal_class()
if self:_pending_pedal_class() == nil then
-- The EMPTY_PEDAL is definitely a switch!
return true
end
return pending_pedal_class.__index ~= self.pedals[i].__index
end
function Board:_name_of_pending_pedal()
local pending_pedal_class = self:_pending_pedal_class()
local use_short_names = self:_use_short_names()
if pending_pedal_class == nil then
if #self.pedals == 0 then
return "E3 to choose, K3 to add"
end
return use_short_names and "None" or "No Selection"
end
return pending_pedal_class:name(use_short_names)
end
function Board:_is_new_slot(i)
-- None of the slots are the New slot if we're already at the maximum number of slots
if #self.pedals == MAX_SLOTS then
return false
end
return i == #self.tabs.titles
end
function Board:_get_pedal_class_index_for_tab(i)
if self:_is_new_slot(i) then
return 0
end
local pedal_class_at_i = self.pedals[i].__index
-- TODO: port to tabutil.key
for i, pedal_class in ipairs(pedal_classes) do
if pedal_class_at_i == pedal_class then
return i
end
end
end
function Board:_set_pending_pedal_class_to_match_tab(i)
self._pending_pedal_class_index = self:_get_pedal_class_index_for_tab(i)
end
function Board:_use_short_names()
return #self.pedals >= 2
end
function Board:_is_alt_mode()
return self._alt_key_down_time ~= nil
end
function Board:_param_value_for_pedal_name(_pedal_name)
for i, pedal_name in ipairs(pedal_names) do
if pedal_name == _pedal_name then
return i
end
end
return 1
end
function Board:_reorder_pedals()
local pedal_instance_to_move = self.pedals[self._reorder_source]
-- remove pedal at reorder_source
engine.remove_pedal_at_index(self._reorder_source - 1, 1) -- The engine is zero-indexed. Also, tell engine to not free the synth
table.remove(self.pedals, self._reorder_source)
self._remove_page(self._reorder_source + 1, false) -- The parent has a page for each pedal at 1 beyond the slot index on the board
-- insert the pedal at reorder_destination
engine.insert_pedal_at_index(self._reorder_destination - 1, pedal_instance_to_move.id) -- The engine is zero-indexed
table.insert(self.pedals, self._reorder_destination, pedal_instance_to_move)
self._insert_page_at_index(self._reorder_destination + 1, pedal_instance_to_move) -- The parent has a page for each pedal at 1 beyond the slot index on the board
-- Re-initialize tabs, pending pedal class, sync params, and clear out re-order state
local reorder_destination = self._reorder_destination
self._reorder_source = nil
self._reorder_destination = nil
self:_setup_tabs()
self.tabs:set_index(reorder_destination)
self:_set_pending_pedal_class_to_match_tab(self.tabs.index)
self:_sync_pedals_to_params(force)
end
function Board:_set_pedal_by_index(slot, name_index)
if self._is_syncing_to_params then
-- See _sync_pedals_to_params for explanation
if slot == MAX_SLOTS then
self._is_syncing_to_params = false
self._manual_action_will_require_param_sync = false
end
return
end
local pedal_class_index = name_index - 1
-- The parent has a page for each pedal at 1 beyond the slot index on the board
local page_index = slot + 1
if pedal_class_index == 0 then
-- This option means we are removing the pedal in this slot
local engine_index = slot - 1 -- The engine is zero-indexed
engine.remove_pedal_at_index(engine_index, 0)
table.remove(self.pedals, slot)
self:_sync_pedals_to_params()
self:_setup_tabs()
self:_set_pending_pedal_class_to_match_tab(self.tabs.index)
self._remove_page(page_index, true)
return
end
pedal_class = pedal_classes[pedal_class_index]
-- If this slot index is beyond our existing pedals, it adds a new pedal
if slot > #self.pedals then
-- pedal instantiation
local pedal_instance = pedal_class:new(self._add_bypassed)
engine.add_pedal(pedal_instance.id)
table.insert(self.pedals, pedal_instance)
self:_setup_tabs()
self._add_page(pedal_instance)
else
-- Otherwise, it swaps out an existing pedal for a new one
-- If this is just the same pedal that's already there, do nothing
if pedal_class.__index == self.pedals[slot].__index then
return
end
-- pedal instantiation
local pedal_instance = pedal_class:new(self._add_bypassed)
-- The engine is zero-indexed
local engine_index = slot - 1
engine.swap_pedal_at_index(engine_index, pedal_instance.id)
self.pedals[slot] = pedal_instance
self:_setup_tabs()
self._swap_page(page_index, pedal_instance)
end
self._add_bypassed = false
self._set_page_index(page_index)
ScreenState.mark_screen_dirty(true)
end
function Board:_sync_pedals_to_params(force)
-- Removing a pedal or changing pedal order affects more parameters than just one.
-- This function keeps the board's and engine's pedal list in sync with the parameter values
-- by iterating over all the slots and setting the parameter to the board's value.
-- During this process, we turn off the normal param-setting side-effects
-- (as we got here via the side-effect we wanted and the board's pedals are in the right state.
-- we don't want more side-effects)
if force or self._manual_action_will_require_param_sync then
self._sync_to_params_on_next_redraw = false
self._is_syncing_to_params = true
for i = 1, MAX_SLOTS do
local param_id = "pedal_" .. i
local param_value = 1 -- the EMPTY_PEDAL param_value if there's no pedal at self.pedals[i]
if i <= #self.pedals then
param_value = self:_param_value_for_pedal_name(self.pedals[i]:name())
end
local current_value = params:get(param_id)
if current_value ~= param_value then
params:set(param_id, param_value)
elseif i == MAX_SLOTS then
-- params:set has no effect if current value == param_value.
-- So, if we would have cleaned up as a side-effect of the param's action (which we do when i == MAX_SLOTS),
-- we instead clean up in here directly
self._is_syncing_to_params = false
self._manual_action_will_require_param_sync = false
end
end
else
-- This happened via the norns menu, not an in-app action.
-- If they're in the norns menu and just took an editing action,
-- it would be confusing to alter/overwrite their edits.
-- Instead, sync params once we're back in this app (detected via a call to redraw)
self._sync_to_params_on_next_redraw = true
end
end
function Board:_check_cpu_load(param_value, pedal_index)
if param_value == 1 then return false end
local cpu_load_before = 0
for i, pedal in ipairs(self.pedals) do
cpu_load_before = cpu_load_before + pedal.peak_cpu
end
local cpu_load_after = 0
for i, pedal in ipairs(self.pedals) do
local pedal_to_use = i == pedal_index and pedal_classes[param_value - 1] or pedal
cpu_load_after = cpu_load_after + pedal_to_use.peak_cpu
end
if pedal_index > #self.pedals then
cpu_load_after = cpu_load_after + pedal_classes[param_value - 1].peak_cpu
end
if cpu_load_after > cpu_load_before and (cpu_load_after + CPU_BASELINE) > CPU_ALERT_THRESHOLD then
return true
end
end
function Board:_show_cpu_alert(param_value, pedal_index)
if param_value == 1 then return end
local cpu_explanations = {"", ""}
for i, pedal in ipairs(self.pedals) do
local pedal_to_use = i == pedal_index and pedal_classes[param_value - 1] or pedal
local explanations_index = math.ceil(i / 2)
local cpu_explanation = cpu_explanations[explanations_index]
cpu_explanation = cpu_explanation .. pedal_to_use:name(true) .. ": " .. pedal_to_use.peak_cpu .. "% CPU"
if i < #self.pedals then cpu_explanation = cpu_explanation .. ", " end
cpu_explanations[explanations_index] = cpu_explanation
end
if pedal_index > #self.pedals then
local pedal_to_use = pedal_classes[param_value - 1]
local explanations_index = math.ceil(pedal_index / 2)
local cpu_explanation = cpu_explanations[explanations_index]
if string.len(cpu_explanation) > 0 then
cpu_explanation = cpu_explanation .. ", "
end
cpu_explanation = cpu_explanation .. pedal_to_use:name(true) .. ": " .. pedal_to_use.peak_cpu .. "% CPU"
cpu_explanations[explanations_index] = cpu_explanation
end
local lines = {"May cause dropped samples:"}
for i, cpu_explanation in ipairs(cpu_explanations) do
if string.len(cpu_explanation) > 0 then
table.insert(lines, cpu_explanation)
end
end
table.insert(lines, "Are you sure? K3 to confirm.")
self._alert = Alert.new(lines)
self._cpu_alert_pedal = {param_value, pedal_index}
end
return Board