Skip to content
Permalink
7c553e4f0f
Go to file
 
 
Cannot retrieve contributors at this time
1507 lines (1327 sloc) 35.4 KB
-- menu.lua
-- norns screen-based navigation module
local tab = require 'tabutil'
local util = require 'util'
local paramset = require 'paramset'
local fx = require 'effects'
local cs = require 'controlspec'
local menu = {}
-- global functions for scripts
key = norns.none
enc = norns.none
redraw = norns.blank
cleanup = norns.none
-- level enums
local pMIX = 0
local pHOME = 1
local pSELECT = 2
local pPREVIEW = 3
local pPARAMS = 4
local pSYSTEM = 5
local pAUDIO = 6
local pWIFI = 7
local pSYNC = 8
local pUPDATE = 9
local pLOG = 10
local pSLEEP = 11
-- page pointer
local m = {}
m.key = {}
m.enc = {}
m.redraw = {}
m.init = {}
m.deinit = {}
menu.mode = false
menu.page = pHOME
menu.alt = false
menu.scripterror = false
menu.errormsg = ""
-- mix paramset
mix = paramset.new()
cs.MAIN_LEVEL = cs.new(-math.huge,0,'db',0,0,"dB")
mix:add_control("output",cs.MAIN_LEVEL)
mix:set_action("output",
function(x) norns.audio.output_level(x) end)
mix:add_control("input",cs.MAIN_LEVEL)
mix:set_action("input",
function(x)
norns.audio.input_level(1,x)
norns.audio.input_level(2,x)
end)
cs.MUTE_LEVEL = cs.new(-math.huge,0,'db',0,-math.huge,"dB")
mix:add_control("monitor",cs.MUTE_LEVEL)
mix:set_action("monitor",
function(x) norns.audio.monitor_level(x) end)
mix:add_option("monitor mode",{"STEREO","MONO"})
mix:set_action("monitor mode",
function(x)
if x == 1 then norns.audio.monitor_stereo()
else norns.audio.monitor_mono() end
end)
mix:add_number("headphone",0, 63, 40)
mix:set_action("headphone",
function(x) gain_hp(x) end)
-- TODO TAPE (rec) modes: OUTPUT, OUTPUT+MONITOR, OUTPUT/MONITOR SPLIT
-- TODO TAPE (playback) VOL, SPEED?
-- ControlSpec.new(minval, maxval, warp, step, default, units)
mix:add_separator()
mix:add_option("aux fx", {"OFF","ON"}, 2)
mix:set_action("aux fx",
function(x)
if x == 1 then
fx.aux_fx_off()
else
fx.aux_fx_on()
end
end)
cs.DB_LEVEL = cs.new(-math.huge,18,'db',0,0,"dB")
cs.DB_LEVEL_MUTE = cs.new(-math.huge,18,'db',0,-math.huge,"dB")
cs.DB_LEVEL_9DB = cs.new(-math.huge,18,'db',0,-9,"dB")
mix:add_control("aux output level", cs.DB_LEVEL_9DB)
mix:set_action("aux output level",
function(x) fx.aux_fx_output_level(x) end)
mix:add_control("aux input1 level", cs.DB_LEVEL_MUTE)
mix:set_action("aux input1 level",
function(x) fx.aux_fx_input_level(1,x) end)
mix:add_control("aux input2 level", cs.DB_LEVEL_MUTE)
mix:set_action("aux input2 level",
function(x) fx.aux_fx_input_level(2,x) end)
mix:add_control("aux input1 pan", cs.PAN)
mix:set_action("aux input1 pan",
function(x) fx.aux_fx_input_pan(1,x) end)
mix:add_control("aux input2 pan", cs.PAN)
mix:set_action("aux input2 pan",
function(x) fx.aux_fx_input_pan(2,x) end)
mix:add_control("aux return level", cs.DB_LEVEL)
mix:set_action("aux return level",
function(x) fx.aux_fx_return_level(x) end)
cs.IN_DELAY = cs.new(20,100,'lin',0,60,'ms')
mix:add_control("rev in delay", cs.IN_DELAY)
mix:set_action("rev in delay",
function(x) fx.aux_fx_param("in_delay",x) end)
cs.LF_X = cs.new(50,1000,'exp',0,200,'hz')
mix:add_control("rev lf x", cs.LF_X)
mix:set_action("rev lf x",
function(x) fx.aux_fx_param("lf_x",x) end)
cs.RT60 = cs.new(0.1,8,'lin',0,3,'s')
mix:add_control("rev low rt60", cs.RT60)
mix:set_action("rev low rt60",
function(x) fx.aux_fx_param("low_rt60",x) end)
mix:add_control("rev mid rt60", cs.RT60)
mix:set_action("rev mid rt60",
function(x) fx.aux_fx_param("mid_rt60",x) end)
cs.HF_DAMP = cs.new(1500,20000,'exp',0,6000,'hz')
mix:add_control("rev hf damping", cs.HF_DAMP)
mix:set_action("rev hf damping",
function(x) fx.aux_fx_param("hf_damping",x) end)
--[[
cs.EQ_FREQ1 = cs.new(40,2500,'exp',0,315,'hz')
mix:add_control("rev eq1 freq", cs.EQ_FREQ1)
mix:set_action("rev eq1 freq",
function(x) fx.aux_fx_param("eq1_freq",x) end)
cs.EQ_LVL = cs.new(-15,15,'lin',0,0,"dB")
mix:add_control("rev eq1 level", cs.EQ_LVL)
mix:set_action("rev eq1 level",
function(x) fx.aux_fx_param("eq1_level",x) end)
cs.EQ_FREQ2 = cs.new(160,10000,'exp',0,1500,'hz')
mix:add_control("rev eq2 freq", cs.EQ_FREQ2)
mix:set_action("rev eq2 freq",
function(x) fx.aux_fx_param("eq2_freq",x) end)
mix:add_control("rev eq2 level", cs.EQ_LVL)
mix:set_action("rev eq2 level",
function(x) fx.aux_fx_param("eq2_level",x) end)
--]]
--cs.LEVEL = cs.new(-70,40,'lin',0,0,'dB')
--mix:add_control("rev_level", cs.LEVEL)
--mix:set_action("rev_level",
--function(x) fx.aux_fx_param("level",x) end)
mix:add_separator()
mix:add_option("insert fx", {"OFF","ON"})
mix:set_action("insert fx",
function(x)
if x == 1 then
fx.insert_fx_off()
else
fx.insert_fx_on()
end
end)
cs.MIX = cs.new(0,1,'lin',0,1,'')
mix:add_control("insert mix", cs.MIX)
mix:set_action("insert mix",
function(x) fx.insert_fx_mix(x) end)
cs.RATIO = cs.new(1,20,'lin',0,4,'')
mix:add_control("comp ratio", cs.RATIO)
mix:set_action("comp ratio",
function(x) fx.insert_fx_param("level",x) end)
cs.THRESH = cs.new(-100,10,'db',0,-18,'dB')
mix:add_control("comp threshold", cs.THRESH)
mix:set_action("comp threshold",
function(x) fx.insert_fx_param("threshold",x) end)
cs.ATTACK = cs.new(1,1000,'exp',0,5,'ms')
mix:add_control("comp attack", cs.ATTACK)
mix:set_action("comp attack",
function(x) fx.insert_fx_param("attack",x) end)
cs.RELEASE = cs.new(1,1000,'exp',0,50,'ms')
mix:add_control("comp release", cs.RELEASE)
mix:set_action("comp release",
function(x) fx.insert_fx_param("release",x) end)
cs.MAKEUP = cs.new(-20,60,'db',0,9,'dB')
mix:add_control("comp makeup gain", cs.MAKEUP)
mix:set_action("comp makeup gain",
function(x) fx.insert_fx_param("makeup_gain",x) end)
local pending = false
-- metro for key hold detection
local metro = require 'metro'
local t = metro[31]
t.time = 0.25
t.count = 1
t.callback = function(stage)
menu.key(1,1)
pending = false
end
-- metro for status updates
local u = metro[30]
-- assigns key/enc/screen handlers after user script has loaded
norns.menu = {}
norns.menu.init = function()
menu.set_mode(menu.mode)
end
norns.menu.status = function() return menu.mode end
norns.menu.set = function(new_enc, new_key, new_redraw)
menu.penc = new_enc
menu.key = new_key
menu.redraw = new_redraw
end
norns.menu.get_enc = function() return menu.penc end
norns.menu.get_key = function() return menu.key end
norns.menu.get_redraw = function() return menu.redraw end
norns.scripterror = function(msg)
local msg = msg;
if msg == nil then msg = "" end
print("### SCRIPT ERROR: "..msg)
menu.errormsg = msg
menu.scripterror = true
menu.set_page(pHOME)
menu.set_mode(true)
end
norns.init_done = function(status)
menu.set_page(pHOME)
if status == true then
menu.scripterror = false
m.params.pos = 0
menu.set_mode(false)
end
m.params.init_map()
m.params.read(norns.state.folder_name..".pmap")
end
-- input redirection
menu.enc = function(n, delta)
if n==1 and menu.alt == false then mix:delta("output",delta)
else menu.penc(n, delta) end
end
norns.key = function(n, z)
-- key 1 detect for short press
if n == 1 then
if z == 1 then
menu.alt = true
pending = true
t:start()
elseif z == 0 and pending == true then
menu.alt = false
if menu.mode == true and menu.scripterror == false then
menu.set_mode(false)
else menu.set_mode(true) end
t:stop()
pending = false
elseif z == 0 then
menu.alt = false
menu.key(n,z) -- always 1,0
if menu.mode == true then menu.redraw() end
else
menu.key(n,z) -- always 1,1
end
-- key 2/3 pass
else
menu.key(n,z)
end
end
-- menu set mode
menu.set_mode = function(mode)
if mode == false then
menu.mode = false
m.deinit[menu.page]()
redraw = norns.script.redraw
menu.key = key
norns.encoders.callback = enc
norns.encoders.set_accel(0,true)
norns.encoders.set_sens(0,1)
redraw()
else -- enable menu mode
menu.mode = true
menu.alt = false
redraw = norns.none
screen.font_face(0)
screen.font_size(8)
screen.line_width(1)
menu.set_page(menu.page)
norns.encoders.callback = menu.enc
norns.encoders.set_accel(0,true)
norns.encoders.set_sens(1,1)
norns.encoders.set_sens(2,0.5)
norns.encoders.set_sens(3,0.5)
end
end
-- set page
menu.set_page = function(page)
m.deinit[menu.page]()
menu.page = page
menu.key = m.key[page]
menu.penc = m.enc[page]
menu.redraw = m.redraw[page]
m.init[page]()
menu.redraw()
end
-- --------------------------------------------------
-- interfaces
-----------------------------------------
-- MIX
m.mix = {}
local tOFF = 0
local tREC = 1
local tPLAY = 2
local tsREC = 0
local tsPAUSE = 1
local paPLAY = 0
local paPAUSE = 1
local paSTOP = 2
local tape = {}
tape.key = false
tape.mode = tOFF
tape.lastmode = tREC
tape.selectmode = false
tape.playaction = paPLAY
tape.status = 0
tape.name = ""
tape.time = 0
tape.playfile = ""
m.key[pMIX] = function(n,z)
if n==3 and z==1 and tape.key == false then
menu.set_page(pHOME)
elseif n==2 then
if z==1 then
tape.key = true
if tape.mode == tOFF then tape.selectmode = true end
else
tape.key = false
tape.selectmode = false
end
elseif n==3 and tape.key == true and z==1 then
if tape.mode == tOFF then
if tape.lastmode == tREC then
-- REC: ready
tape.selectmode = false
tape.name = os.date("%y-%m-%d_%H-%M") .. ".aif"
tape_new(tape.name)
print("new tape > "..tape.name)
tape.mode = tREC
tape.status = tsPAUSE
redraw()
else
-- PLAY: select tape
local playfile_callback = function(path)
if path ~= "cancel" then
tape.playfile = path
tape.playfile = tape.playfile:match("[^/]*$") -- strip path from name
tape_open(tape.playfile)
tape.mode = tPLAY
tape.status = tsPAUSE
tape.playaction = paPLAY
tape.metro = metro.alloc()
tape.metro.callback = function() tape.time = tape.time + 1 end
tape.metro.count = -1
tape.metro.time = 1
tape.time = 0
else
tape.mode = tOFF
end
tape.key = false
tape.selectmode = false
redraw()
end
fileselect.enter(os.getenv("HOME").."/dust/audio/tape", playfile_callback)
end
elseif tape.mode == tREC and tape.status == tsPAUSE then
-- REC: start recording
tape.status = tsREC
tape_start_rec()
tape.metro = metro.alloc()
tape.metro.callback = function() tape.time = tape.time + 1 end
tape.metro.count = -1
tape.metro.time = 1
tape.time = 0
tape.metro:start()
elseif tape.mode == tREC and tape.status == tsREC then
-- REC: stop recording
print("stopping tape")
tape_stop_rec()
tape.mode = tOFF
tape.metro:stop()
elseif tape.mode == tPLAY and tape.playaction == paPLAY then
tape_play()
tape.metro:start()
tape.status = paPLAY
tape.playaction = paPAUSE
elseif tape.mode == tPLAY and tape.playaction == paPAUSE then
tape_pause()
tape.metro:stop()
tape.status = paPAUSE
tape.playaction = paPLAY
elseif tape.mode == tPLAY and tape.playaction == paSTOP then
tape_stop()
tape.metro:stop()
tape.mode = tOFF
end
end
end
m.enc[pMIX] = function(n,d)
if n==2 then
if tape.key == false then
mix:delta("input",d)
end
elseif n==3 then
if tape.key == false then
mix:delta("monitor",d)
elseif tape.selectmode == true then
if d < 0 then tape.lastmode = tREC
else tape.lastmode = tPLAY end
elseif tape.mode == tPLAY then
if d < 0 then tape.playaction = paSTOP
elseif tape.status == paPLAY then tape.playaction = paPAUSE
elseif tape.status == paPAUSE then tape.playaction = paPLAY
end
end
end
end
m.redraw[pMIX] = function()
local n
screen.clear()
screen.aa(1)
screen.line_width(1)
local x = -40
screen.level(2)
n = (60+mix:get("output"))/60*48
screen.rect(x+42.5,56.5,2,-n)
screen.stroke()
screen.level(15)
n = m.mix.out1/64*48
screen.rect(x+48.5,56.5,2,-n)
screen.stroke()
n = m.mix.out2/64*48
screen.rect(x+54.5,56.5,2,-n)
screen.stroke()
screen.level(2)
n = (60+mix:get("input"))/60*48
screen.rect(x+64.5,56.5,2,-n)
screen.stroke()
screen.level(15)
n = m.mix.in1/64*48
screen.rect(x+70.5,56.5,2,-n)
screen.stroke()
n = m.mix.in2/64*48
screen.rect(x+76.5,56.5,2,-n)
screen.stroke()
screen.level(2)
n = (60+mix:get("monitor"))/60*48
screen.rect(x+86.5,56.5,2,-n)
screen.stroke()
screen.level(1)
screen.move(2,64)
screen.text("out")
screen.move(24,64)
screen.text("in")
screen.move(46,64)
screen.text("mon")
if tape.selectmode then
screen.level(10)
screen.move(90,40)
if tape.lastmode == tPLAY then
screen.text_center("TAPE > PLAY")
else
screen.text_center("TAPE > REC")
end
end
if tape.mode == tREC then
screen.move(90,40)
if tape.status == tsPAUSE then
screen.text_center("READY")
elseif tape.status == tsREC then
screen.text_center("RECORDING")
screen.move(90,48)
local min = math.floor(tape.time / 60)
local sec = tape.time % 60
screen.text_center(string.format("%02d:%02d",min,sec))
end
elseif tape.mode == tPLAY then
screen.move(90,40)
if tape.key then
if tape.playaction == paPAUSE then
screen.text_center("TAPE > PAUSE")
elseif tape.playaction == paPLAY then
screen.text_center("TAPE > PLAY")
elseif tape.playaction == paSTOP then
screen.text_center("TAPE > STOP")
end
else
screen.text_center("TAPE")
end
screen.level(4)
screen.move(90,48)
local min = math.floor(tape.time / 60)
local sec = tape.time % 60
screen.text_center(string.format("%02d:%02d",min,sec))
screen.move(90,32)
screen.text_center(tape.playfile)
end
screen.level(1)
screen.move(127,64)
if menu.alt == false then screen.text_right(norns.battery_percent)
else screen.text_right(norns.battery_current.."mA") end
screen.update()
end
m.init[pMIX] = function()
norns.vu = m.mix.vu
m.mix.in1 = 0
m.mix.in2 = 0
m.mix.out1 = 0
m.mix.out2 = 0
end
m.deinit[pMIX] = function()
norns.vu = norns.none
end
m.mix.vu = function(in1,in2,out1,out2)
m.mix.in1 = in1
m.mix.in2 = in2
m.mix.out1 = out1
m.mix.out2 = out2
menu.redraw()
end
-----------------------------------------
-- HOME
m.home = {}
m.home.pos = 0
m.home.list = {"SELECT >", "PARAMETERS >", "SYSTEM >", "SLEEP >"}
m.home.len = 4
m.init[pHOME] = norns.none
m.deinit[pHOME] = norns.none
m.key[pHOME] = function(n,z)
if n == 2 and z == 1 then
menu.set_page(pMIX)
elseif n == 3 and z == 1 then
local choices = {pSELECT, pPARAMS, pSYSTEM, pSLEEP}
menu.set_page(choices[m.home.pos+1])
end
end
m.enc[pHOME] = function(n,delta)
if n == 2 then
m.home.pos = m.home.pos + delta
if m.home.pos > m.home.len - 1 then m.home.pos = m.home.len - 1
elseif m.home.pos < 0 then m.home.pos = 0 end
menu.redraw()
end
end
m.redraw[pHOME] = function()
screen.clear()
-- draw current script loaded
screen.move(0,10)
screen.level(15)
local line = string.upper(norns.state.name)
if(menu.scripterror) then line = line .. " (error: " .. menu.errormsg .. ")" end
screen.text(line)
-- draw file list and selector
for i=3,6 do
screen.move(0,10*i)
line = string.gsub(m.home.list[i-2],'.lua','')
if(i==m.home.pos+3) then
screen.level(15)
else
screen.level(4)
end
screen.text(string.upper(line))
end
screen.update()
end
----------------------------------------
-- SELECT
m.sel = {}
m.sel.pos = 0
m.sel.list = util.scandir(script_dir)
m.sel.len = tab.count(m.sel.list)
m.sel.depth = 0
m.sel.folderpos = {}
m.sel.folders = {}
m.sel.path = ""
m.sel.file = ""
m.sel.dir = function()
local path = script_dir
for k,v in pairs(m.sel.folders) do
path = path .. v
end
print("path: "..path)
return path
end
m.init[pSELECT] = function()
if m.sel.depth == 0 then
m.sel.list = util.scandir(script_dir)
else
m.sel.list = util.scandir(m.sel.dir())
end
m.sel.len = tab.count(m.sel.list)
end
m.deinit[pSELECT] = norns.none
m.key[pSELECT] = function(n,z)
-- back
if n==2 and z==1 then
if m.sel.depth > 0 then
print('back')
m.sel.folders[m.sel.depth] = nil
m.sel.depth = m.sel.depth - 1
-- FIXME return to folder position
m.sel.list = util.scandir(m.sel.dir())
m.sel.len = tab.count(m.sel.list)
m.sel.pos = m.sel.folderpos[m.sel.depth] or 0
menu.redraw()
else
menu.set_page(pHOME)
end
-- select
elseif n==3 and z==1 then
m.sel.file = m.sel.list[m.sel.pos+1]
if string.find(m.sel.file,'/') then
print("folder")
m.sel.folderpos[m.sel.depth] = m.sel.pos
m.sel.depth = m.sel.depth + 1
m.sel.folders[m.sel.depth] = m.sel.file
m.sel.list = util.scandir(m.sel.dir())
m.sel.len = tab.count(m.sel.list)
m.sel.pos = 0
menu.redraw()
else
local path = ""
for k,v in pairs(m.sel.folders) do
path = path .. v
end
m.sel.path = path .. m.sel.file
menu.set_page(pPREVIEW)
end
end
end
m.enc[pSELECT] = function(n,delta)
-- scroll file list
if n==2 then
m.sel.pos = util.clamp(m.sel.pos + delta, 0, m.sel.len - 1)
menu.redraw()
end
end
m.redraw[pSELECT] = function()
-- draw file list and selector
screen.clear()
screen.level(15)
for i=1,6 do
if (i > 2 - m.sel.pos) and (i < m.sel.len - m.sel.pos + 3) then
screen.move(0,10*i)
line = string.gsub(m.sel.list[i+m.sel.pos-2],'.lua','')
if(i==3) then
screen.level(15)
else
screen.level(4)
end
screen.text(string.upper(line))
end
end
screen.update()
end
-----------------------------------------
-- PREVIEW
m.pre = {}
m.pre.meta = {}
m.init[pPREVIEW] = function()
m.pre.meta = norns.script.metadata(m.sel.path)
m.pre.len = tab.count(m.pre.meta)
if m.pre.len == 0 then
table.insert(m.pre.meta, string.gsub(m.sel.file,'.lua','') .. " (no metadata)")
end
m.pre.state = 0
m.pre.pos = 0
m.pre.posmax = m.pre.len - 8
if m.pre.posmax < 0 then m.pre.posmax = 0 end
end
m.deinit[pPREVIEW] = norns.none
m.key[pPREVIEW] = function(n,z)
if n==3 and m.pre.state == 1 then
norns.script.load(m.sel.path)
elseif n ==3 and z == 1 then
m.pre.state = 1
elseif n == 2 and z == 1 then
menu.set_page(pSELECT)
end
end
m.enc[pPREVIEW] = function(n,d)
if n==2 then
m.pre.pos = util.clamp(m.pre.pos + d, 0, m.pre.posmax)
menu.redraw()
end
end
m.redraw[pPREVIEW] = function()
screen.clear()
screen.level(15)
local i
for i=1,8 do
if i <= m.pre.len then
screen.move(0,i*8-2)
screen.text(m.pre.meta[i+m.pre.pos])
end
end
screen.update()
end
-----------------------------------------
-- PARAMS
m.params = {}
m.params.pos = 0
m.params.n = 0
m.params.loadable = true
m.params.altpos = 1
m.params.map = {}
m.params.init_map = function()
for i = 1,params.count do m.params.map[i] = -1 end
end
m.key[pPARAMS] = function(n,z)
if menu.alt then
if n==3 and z==1 then
if m.params.altpos == 1 then
if m.params.n == 0 then
params:read(norns.state.folder_name..".pset")
else
params:read(norns.state.folder_name.."-"..string.format("%02d",m.params.n)..".pset")
end
m.params.action = 15
m.params.action_text = "loaded"
elseif m.params.altpos == 2 then
if m.params.n == 0 then
params:write(norns.state.folder_name..".pset")
else
params:write(norns.state.folder_name.."-"..string.format("%02d",m.params.n)..".pset")
end
m.params.action = 15
m.params.action_text = "saved"
m.params.loadable = true
end
menu.redraw()
end
elseif n==2 and z==1 then
menu.set_page(pHOME)
elseif n==3 and z==1 then
if not m.params.midimap then
if params:t(m.params.pos+1) == params.tFILE then
fileselect.enter(os.getenv("HOME").."/dust", m.params.newfile)
end
else
m.params.midilearn = not m.params.midilearn
end
end
end
m.params.newfile = function(file)
if file ~= "cancel" then
params:set(m.params.pos+1,file)
menu.redraw()
end
end
m.enc[pPARAMS] = function(n,d)
if menu.alt then
if n == 2 then
m.params.altpos = util.clamp(m.params.altpos+d, 1, 3)
menu.redraw()
elseif n==3 then
if m.params.altpos < 3 then
m.params.n = util.clamp(m.params.n + d,0,100)
local path
local f
if m.params.n == 0 then
path = data_dir..norns.state.folder_name..".pset"
f=io.open(path,"r")
else
path =data_dir..norns.state.folder_name.."-"..string.format("%02d",m.params.n)..".pset"
f=io.open(path ,"r")
end
--print("pset: "..path)
if f~=nil then
m.params.loadable = true
io.close(f)
else
m.params.loadable = false
end
menu.redraw()
else
m.params.midimap = d > 0
menu.redraw()
end
end
elseif n==2 then
local prev = m.params.pos
m.params.pos = util.clamp(m.params.pos + d, 0, params.count - 1)
if m.params.pos ~= prev then menu.redraw() end
elseif n==3 and params.count > 0 then
if not m.params.midimap then
params:delta(m.params.pos+1,d)
menu.redraw()
else
m.params.map[m.params.pos+1] = util.clamp(m.params.map[m.params.pos+1]+d,-1,127)
menu.redraw()
end
end
end
m.redraw[pPARAMS] = function()
screen.clear()
if(params.count > 0) then
if not menu.alt then
local i
for i=1,6 do
if (i > 2 - m.params.pos) and (i < params.count - m.params.pos + 3) then
if i==3 then screen.level(15) else screen.level(4) end
local param_index = i+m.params.pos-2
if params:t(param_index) == params.tSEPARATOR then
screen.move(0,10*i)
screen.text(params:string(param_index))
else
screen.move(0,10*i)
screen.text(params:get_name(param_index))
if m.params.midimap then
if params:t(param_index) == params.tCONTROL then
screen.move(127,10*i)
if m.params.map[param_index] >= 0 then
screen.text_right(m.params.map[param_index])
else
screen.text_right("-")
end
end
else
screen.move(127,10*i)
screen.text_right(params:string(param_index))
end
end
end
end
if m.params.midilearn then
screen.level(15)
screen.move(80,30)
screen.text("(learn)")
end
else -- menu.alt == true -- param save/load
screen.level((m.params.altpos == 1) and 15 or 4)
screen.move(0,30)
screen.text("load")
if m.params.altpos == 1 then
screen.move(127,30)
screen.level(m.params.loadable and 10 or 1)
if m.params.n == 0 then
screen.text_right("default")
else
screen.text_right(string.format("%02d",m.params.n))
end
end
screen.level((m.params.altpos == 2) and 15 or 4)
screen.move(0,40)
screen.text("save")
if m.params.altpos == 2 then
screen.move(127,40)
screen.level(m.params.loadable and 10 or 4)
if m.params.n == 0 then
screen.text_right("default")
else
screen.text_right(string.format("%02d",m.params.n))
end
end
screen.level((m.params.altpos == 3) and 15 or 4)
screen.move(0,50)
screen.text("midi-cc mapping")
screen.move(127,50)
screen.text_right(m.params.midimap and "on" or "off")
screen.move(0,10)
screen.level(m.params.action)
screen.text(m.params.action_text)
end
else
screen.move(0,10)
screen.level(4)
screen.text("no parameters")
end
screen.update()
end
m.init[pPARAMS] = function()
m.params.midimap = false
m.params.midilearn = false
m.params.action_text = ""
m.params.action = 0
u.callback = function()
if m.params.action > 0 then m.params.action = m.params.action - 1 end
menu.redraw()
end
u.time = 0.2
u.count = -1
u:start()
end
m.deinit[pPARAMS] = function()
m.params.write(norns.state.folder_name..".pmap")
m.params.midilearn = false
u:stop()
end
norns.menu_midi_event = function(data)
if data[1] == 176 then -- cc
if m.params.midilearn then
if params:t(m.params.pos+1) == params.tCONTROL then
m.params.map[m.params.pos+1] = data[2]
menu.redraw()
end
m.params.midilearn = false
else
local p = tab.key(m.params.map,data[2])
if p then
params:set_raw(p,data[3]/127)
end
--print(data[2] .. " " .. data[3])
end
end
end
function m.params.write(filename)
local function quote(s)
return '"'..s:gsub('"', '\\"')..'"'
end
-- check for subfolder in filename, create subfolder if it doesn't exist
local subfolder, found = string.gsub(filename,"/(.*)","")
if found==1 then
local fd = io.open(data_dir..subfolder,"r")
if fd then
io.close(fd)
else
print("creating subfolder")
os.execute("mkdir "..data_dir..subfolder)
end
end
-- write file
local fd = io.open(data_dir..filename, "w+")
io.output(fd)
for k,v in pairs(m.params.map) do
io.write(string.format("%s: %d\n", quote(tostring(k)), v))
end
io.close(fd)
end
function m.params.read(filename)
local function unquote(s)
return s:gsub('^"', ''):gsub('"$', ''):gsub('\\"', '"')
end
print("READING PMAP")
local fd = io.open(data_dir..filename, "r")
if fd then
io.close(fd)
for line in io.lines(data_dir..filename) do
--local name, value = string.match(line, "(\".-\")%s*:%s*(.*)")
local name, value = string.match(line, "(\".-\")%s*:%s*(.*)")
if name and value then
--print(unquote(name) .. " : " .. value)
m.params.map[tonumber(unquote(name),10)] = tonumber(value)
end
end
else
--print("m.params.read: "..filename.." not read.")
end
end
-----------------------------------------
-- SYSTEM
m.sys = {}
m.sys.pos = 0
m.sys.list = {"audio > ", "wifi >", "sync >", "update >", "log >"}
m.sys.pages = {pAUDIO, pWIFI, pSYNC, pUPDATE, pLOG}
m.sys.input = 0
m.sys.disk = ""
m.key[pSYSTEM] = function(n,z)
if n==2 and z==1 then
norns.state.save()
menu.set_page(pHOME)
elseif n==3 and z==1 then
menu.set_page(m.sys.pages[m.sys.pos+1])
end
end
m.enc[pSYSTEM] = function(n,delta)
if n==2 then
m.sys.pos = m.sys.pos + delta
m.sys.pos = util.clamp(m.sys.pos, 0, #m.sys.list - 1)
menu.redraw()
end
end
m.redraw[pSYSTEM] = function()
screen.clear()
for i=1,#m.sys.list do
screen.move(0,10*i+10)
if(i==m.sys.pos+1) then
screen.level(15)
else
screen.level(4)
end
screen.text(string.upper(m.sys.list[i]))
end
if m.sys.pos==1 and (m.sys.input == 0 or m.sys.input == 1) then
screen.level(15) else screen.level(4)
end
screen.move(127,30)
if wifi.state == 2 then m.sys.net = wifi.ip
else m.sys.net = wifi.status end
screen.text_right(m.sys.net)
screen.level(2)
screen.move(127,40)
screen.text_right("disk free: "..m.sys.disk)
screen.move(127,50)
screen.text_right(norns.version.update)
screen.update()
end
m.init[pSYSTEM] = function()
m.sys.disk = util.os_capture("df -hl | grep '/dev/root' | awk '{print $4}'")
u.callback = function()
m.sysquery()
menu.redraw()
end
u.time = 3
u.count = -1
u:start()
end
m.deinit[pSYSTEM] = function()
u:stop()
end
m.sysquery = function()
wifi.update()
end
-----------------------------------------
-- WIFI
m.wifi = {}
m.wifi.pos = 0
m.wifi.list = {"off","hotspot","network >"}
m.wifi.len = 3
m.wifi.selected = 1
m.wifi.try = ""
m.key[pWIFI] = function(n,z)
if n==2 and z==1 then
menu.set_page(pSYSTEM)
elseif n==3 and z==1 then
if m.wifi.pos == 0 then
print "wifi off"
wifi.off()
elseif m.wifi.pos == 1 then
print "wifi hotspot"
wifi.hotspot()
elseif m.wifi.pos == 2 then
m.wifi.try = wifi.scan_list[m.wifi.selected]
textentry.enter(m.wifi.passdone, wifi.psk)
end
end
end
m.wifi.passdone = function(txt)
if txt ~= nil then
os.execute("~/norns/wifi.sh select \""..m.wifi.try.."\" \""..txt.."\" &")
wifi.on()
end
menu.redraw()
end
m.enc[pWIFI] = function(n,delta)
if n==2 then
m.wifi.pos = m.wifi.pos + delta
if m.wifi.pos > m.wifi.len - 1 then m.wifi.pos = m.wifi.len - 1
elseif m.wifi.pos < 0 then m.wifi.pos = 0 end
menu.redraw()
elseif n==3 and m.wifi.pos == 2 then
m.wifi.selected = util.clamp(1,m.wifi.selected+delta,wifi.scan_count)
menu.redraw()
end
end
m.redraw[pWIFI] = function()
screen.clear()
screen.level(15)
screen.move(0,10)
if wifi.state == 2 then
screen.text("status: router "..wifi.ssid)
else screen.text("status: "..wifi.status) end
if wifi.state > 0 then
screen.level(4)
screen.move(0,20)
screen.text(wifi.ip)
if wifi.state == 2 then
screen.move(127,20)
screen.text_right(wifi.signal .. "dBm")
end
end
screen.move(0,40+wifi.state*10)
screen.text("-")
for i=1,m.wifi.len do
screen.move(8,30+10*i)
line = m.wifi.list[i]
if(i==m.wifi.pos+1) then
screen.level(15)
else
screen.level(4)
end
screen.text(string.upper(line))
end
screen.move(127,60)
if m.wifi.pos==2 then screen.level(15) else screen.level(4) end
if wifi.scan_count > 0 then
screen.text_right(wifi.scan_list[m.wifi.selected])
else screen.text_right("NONE") end
screen.update()
end
m.init[pWIFI] = function()
wifi.scan()
wifi.update()
--m.wifi.selected = wifi.scan_active
m.wifi.selected = 1
u.time = 1
u.count = -1
u.callback = function()
wifi.update()
menu.redraw()
end
u:start()
end
m.deinit[pWIFI] = function()
u:stop()
end
-----------------------------------------
-- AUDIO
m.audio = {}
m.audio.pos = 0
m.key[pAUDIO] = function(n,z)
if n==2 and z==1 then
menu.set_page(pSYSTEM)
elseif n==3 and z==1 then
if mix:t(m.audio.pos+1) == mix.tFILE then
fileselect.enter(os.getenv("HOME").."/dust", m.audio.newfile)
end
end
end
m.audio.newfile = function(file)
if file ~= "cancel" then
mix:set(m.audio.pos+1,file)
menu.redraw()
end
end
m.enc[pAUDIO] = function(n,d)
if n==2 then
local prev = m.audio.pos
m.audio.pos = util.clamp(m.audio.pos + d, 0, mix.count - 1)
if m.audio.pos ~= prev then menu.redraw() end
elseif n==3 then
mix:delta(m.audio.pos+1,d)
menu.redraw()
end
end
m.redraw[pAUDIO] = function()
screen.clear()
local i
for i=1,6 do
if (i > 2 - m.audio.pos) and (i < mix.count - m.audio.pos + 3) then
if i==3 then screen.level(15) else screen.level(4) end
local param_index = i+m.audio.pos-2
if mix:t(param_index) == mix.tSEPARATOR then
screen.move(0,10*i)
screen.text(mix:string(param_index))
else
screen.move(0,10*i)
screen.text(mix:get_name(param_index))
screen.move(127,10*i)
screen.text_right(mix:string(param_index))
end
end
end
screen.update()
end
m.init[pAUDIO] = function()
u.callback = function() menu.redraw() end
u.time = 1
u.count = -1
u:start()
end
m.deinit[pAUDIO] = function()
u:stop()
end
-----------------------------------------
-- SYNC
m.sync = {}
m.sync.pos = 0
m.key[pSYNC] = function(n,z)
if n==2 and z==1 then
menu.set_page(pSYSTEM)
elseif n==3 and z==1 and m.sync.disk=='' then
menu.set_page(pSYSTEM)
elseif n==3 and z==1 and m.sync.pos==0 then
m.sync.busy = true
menu.redraw()
os.execute("sudo rsync --recursive --links --verbose --update $HOME/dust/ "..m.sync.disk.."/dust; sudo sync")
norns.log.post("sync to usb")
menu.set_page(pSYSTEM)
elseif n==3 and z==1 and m.sync.pos==1 then
m.sync.busy = true
menu.redraw()
os.execute("rsync --recursive --links --verbose --update "..m.sync.disk.."/dust/ $HOME/dust; sudo sync")
norns.log.post("sync from usb")
menu.set_page(pSYSTEM)
elseif n==3 and z==1 and m.sync.pos==2 then
os.execute("sudo umount "..m.sync.disk)
norns.log.post("usb disk ejected")
menu.set_page(pSYSTEM)
end
end
m.enc[pSYNC] = function(n,delta)
if n==2 then
m.sync.pos = util.clamp(m.sync.pos+delta, 0, 2)
menu.redraw()
end
end
m.redraw[pSYNC] = function()
screen.clear()
screen.level(10)
if m.sync.disk=='' then
screen.move(0,30)
screen.text("no usb disk available")
elseif m.sync.busy then
screen.move(0,30)
screen.text("usb sync... (wait)")
else
screen.level(m.sync.pos==0 and 10 or 3)
screen.move(0,40)
screen.text("SYNC TO USB")
screen.level(m.sync.pos==1 and 10 or 3)
screen.move(0,50)
screen.text("SYNC FROM USB")
screen.level(m.sync.pos==2 and 10 or 3)
screen.move(0,60)
screen.text("EJECT USB")
end
screen.update()
end
m.init[pSYNC] = function()
m.sync.pos = 0
m.sync.busy = false
m.sync.disk = util.os_capture("lsblk -o mountpoint | grep media")
end
m.deinit[pSYNC] = function()
end
-----------------------------------------
-- UPDATE
m.update = {}
m.update.pos = 0
m.update.confirm = false
m.key[pUPDATE] = function(n,z)
if z == 1 then menu.set_page(pSYSTEM) end
end
m.enc[pUPDATE] = function(n,delta)
if n==2 then
m.update.pos = util.clamp(m.update.pos+delta, 0, #m.update.list - 1) --4 = options
menu.redraw()
end
end
m.redraw[pUPDATE] = function()
screen.clear()
screen.level(15)
screen.move(64,32)
if m.update.checking then
screen.text_center("checking for updates")
elseif m.update.found then
screen.text_center("update found")
screen.move(64,42)
screen.text_center("reboot to apply")
else
screen.text_center("no updates found")
end
screen.update()
end
m.init[pUPDATE] = function()
m.update.confirm = false
m.update.pos = 0
m.update.checking = true
m.update.found = false
menu.redraw()
m.update.found = norns.update.check()
m.update.checking = false
menu.redraw()
end
m.deinit[pUPDATE] = function()
end
-----------------------------------------
-- LOG
m.log = {}
m.log.pos = 0
m.key[pLOG] = function(n,z)
if n==2 and z==1 then
menu.set_page(pSYSTEM)
elseif n==3 and z==1 then
m.log.pos = 0
menu.redraw()
end
end
m.enc[pLOG] = function(n,delta)
if n==2 then
m.log.pos = util.clamp(m.log.pos+delta, 0, math.max(norns.log.len()-7,0))
menu.redraw()
end
end
m.redraw[pLOG] = function()
screen.clear()
screen.level(10)
for i=1,8 do
screen.move(0,(i*8)-1)
screen.text(norns.log.get(i+m.log.pos))
end
screen.update()
end
m.init[pLOG] = function()
m.log.pos = 0
u.time = 1
u.count = -1
u.callback = menu.redraw
u:start()
end
m.deinit[pLOG] = function()
u:stop()
end
-----------------------------------------
-- SLEEP
m.key[pSLEEP] = function(n,z)
if n==2 and z==1 then
menu.set_page(pHOME)
elseif n==3 and z==1 then
print("SLEEP")
--TODO fade out screen then run the shutdown script
m.sleep = true
menu.redraw()
norns.state.save()
cleanup()
if tape.mode == tREC then tape_stop_rec() end
norns.audio.output_level(-100)
gain_hp(0)
--norns.audio.set_audio_level(0)
wifi.off()
os.execute("sleep 0.5; sudo shutdown now")
end
end
m.enc[pSLEEP] = norns.none
m.redraw[pSLEEP] = function()
screen.clear()
screen.move(48,40)
if m.sleep then
screen.level(1)
screen.text("sleep.")
else
screen.level(15)
screen.text("sleep?")
end
--TODO do an animation here! fade the volume down
screen.update()
end
m.init[pSLEEP] = norns.none
m.deinit[pSLEEP] = norns.none