├── .gitmodules ├── README.md ├── lib ├── boughs.lua └── draw.lua └── bowering.lua /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "crow"] 2 | path = crow 3 | url = https://github.com/monome/bowery 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bowering 2 | 3 | a crow script loader for norns 4 | 5 | demonstrates usage of the new crow `public` library 6 | 7 | ### Requirements 8 | 9 | - norns (version in [this PR](https://github.com/monome/norns/pull/1362)) 10 | - crow (version [3.0.0+](https://github.com/monome/crow/tree/THREE)) 11 | 12 | ### Documentation 13 | 14 | - TODO add links 15 | -------------------------------------------------------------------------------- /lib/boughs.lua: -------------------------------------------------------------------------------- 1 | --- boughs 2 | -- extensions to keep the core script clear 3 | 4 | local b = {} 5 | 6 | 7 | -- return true for files with ".lua" extension 8 | function b.is_luafile( filename ) 9 | local dot = string.find( string.reverse(filename), '%.') 10 | if not dot then return false end -- subfolders are ignored 11 | local ext = string.sub( filename, 1-dot ) 12 | return ext == 'lua' 13 | end 14 | 15 | 16 | -- filter out elements of 't' that don't satisfy 'predicate' 17 | -- NOTE: modifies the table in place 18 | function b.filter( predicate, t ) 19 | for k,v in pairs(t) do 20 | if not predicate(v) then table.remove(t,k) end 21 | end 22 | return t 23 | end 24 | 25 | 26 | return b -------------------------------------------------------------------------------- /bowering.lua: -------------------------------------------------------------------------------- 1 | --- bowering 2 | -- v1 @trentgill 3 | -- 4 | -- run crow scripts from the 5 | -- bowery collection. 6 | -- 7 | -- E1: select script 8 | -- K1: (hold) load script 9 | -- 10 | -- more soon ^^ 11 | 12 | -- UI settings 13 | local show_console = true -- displays messages from crow on-screen 14 | 15 | 16 | -- global state 17 | boughs = include('lib/boughs') 18 | draw = include('lib/draw') 19 | 20 | local BOWERYPATH = norns.state.path .. "crow/" 21 | local scripts = {} 22 | local script_count = 0 23 | local selected_script = 0 -- none 24 | local current_script = 0 -- none 25 | -- local show_description = false 26 | local selected_param = 0 -- none 27 | local console = "" 28 | local alt_param = false 29 | local is_freeze = false 30 | local viewall = false 31 | 32 | local P = norns.crow.public 33 | 34 | 35 | function init() 36 | scripts = util.scandir(BOWERYPATH) 37 | if #scripts == 0 then 38 | print 'bowery not found. check install. git submodule init?' 39 | else 40 | scripts = boughs.filter( boughs.is_luafile, scripts ) -- remove README etc 41 | script_count = #scripts -- optimization 42 | end 43 | -- crow.receive = function(s) console = s; redraw() end -- capture plain crow responses to console 44 | function P.change() redraw() end 45 | function P.discovered() 46 | print'discovered!' 47 | if viewall then crow.public.view.all() end -- enable viewing of all CV levels 48 | redraw() 49 | end 50 | redraw() 51 | end 52 | 53 | 54 | function key(n,z) 55 | if selected_script > 0 then -- keys are *only* active if a script is selected 56 | if n==1 and z==1 then 57 | console = "" -- clear console 58 | if is_freeze and selected_script == current_script then 59 | P.freezescript(scripts[selected_script]) 60 | else 61 | norns.crow.loadscript(scripts[selected_script]) 62 | end 63 | current_script = selected_script 64 | redraw() 65 | end 66 | if n==2 then 67 | is_freeze = (z==1) and true or false 68 | redraw() 69 | end 70 | if n==3 then alt_param = (z==1) and true or false end 71 | if is_freeze and alt_param and z==1 then crow.public.view.all() end 72 | end 73 | end 74 | 75 | 76 | function enc(n,z) 77 | if n==1 then -- select script 78 | selected_script = util.clamp(selected_script + z, 1, script_count) 79 | -- show_description = true 80 | end 81 | if selected_script > 0 then -- keys 2+3 only active when a script is selected 82 | if n==2 then -- select param 83 | selected_param = util.wrap( selected_param + z, 1, P.get_count() ) 84 | elseif n==3 then -- set param 85 | P.delta(selected_param, z, alt_param) 86 | end 87 | end 88 | redraw() 89 | end 90 | 91 | 92 | function redraw() 93 | screen.clear() 94 | screen.line_width(1) 95 | 96 | draw.script_selection( scripts, selected_script, current_script, is_freeze ) 97 | -- FIXME disabled script preview as it's unhelpful without a big overhaul 98 | -- see: https://github.com/monome/norns/pull/1362#issuecomment-842592147 99 | -- if show_description then 100 | -- draw.script_describe( scripts[selected_script] ) 101 | -- show_description = false -- only display once 102 | -- end 103 | draw.public_params( P, selected_param ) 104 | draw.public_views( P.viewing ) 105 | if show_console then 106 | draw.console( console ) 107 | end 108 | screen.update() 109 | end 110 | -------------------------------------------------------------------------------- /lib/draw.lua: -------------------------------------------------------------------------------- 1 | --- draw 2 | -- functional display of UI elements 3 | -- no global data access here 4 | -- the only stateful actions allowed are to 'screen' 5 | 6 | local draw = {} 7 | 8 | 9 | function draw.script_selection( scripts, selected, current, is_freeze ) 10 | screen.move(128,8) 11 | screen.font_face(1) 12 | screen.font_size(8) 13 | screen.level( (selected==current) and 15 or 5 ) 14 | 15 | local name = "none" 16 | if selected > 0 then -- not none 17 | name = string.sub( scripts[selected],1,-5 ) 18 | if is_freeze and selected == current then 19 | name = "FREEZE " .. name 20 | end 21 | end 22 | screen.text_right(name) 23 | end 24 | 25 | 26 | function draw.script_describe( script ) 27 | local scriptpath = norns.state.path .. 'crow/' .. script 28 | if util.file_exists(scriptpath) then 29 | 30 | screen.move(0,8) 31 | screen.font_face(1) 32 | screen.font_size(8) 33 | screen.level(3) 34 | 35 | -- read first line of crow script 36 | local file = io.open(scriptpath,"r") 37 | local desc = file:read() -- grab first line 38 | file:close() 39 | 40 | -- trim comment markers and whitespace 41 | local start = string.find(desc,"[^%-%s]") -- find first non-comment, non-whitespace char 42 | desc = string.sub(desc,start) 43 | 44 | screen.text(desc) 45 | end 46 | end 47 | 48 | 49 | -- draw all the params 50 | function draw.public_params( pub, sel ) 51 | -- draw any kind of param 52 | local function dparam(p, sel, yoff) 53 | 54 | -- draw a slider 55 | local function dslider(p, sel, xoff, yoff, width) 56 | -- range strikethrough 57 | screen.line_width(1) 58 | screen.level(1) 59 | screen.move(xoff+1, yoff-2) 60 | screen.line_rel(width,0) 61 | screen.stroke() 62 | -- location 63 | local loc = width*(p.val - p.min)/p.range 64 | screen.move(xoff+1+loc, yoff) 65 | screen.level( sel and 15 or 5 ) 66 | screen.line_rel(0,-5) 67 | screen.stroke() 68 | end 69 | 70 | -- draw a table (with selection) 71 | local function dtable(p, sel, xoff, yoff) 72 | local spacing = 10 73 | -- draw step-select if index is non-zero 74 | if p.val.index then 75 | -- fill style 76 | screen.level(1) 77 | screen.rect(xoff-2 + (p.val.index-1)*spacing, yoff+1, 8, -7) 78 | screen.fill() 79 | -- underline style 80 | -- screen.move(xoff + (p.val.index-1)*spacing, yoff + 3) 81 | -- screen.level(15) 82 | -- screen.line_rel(4,0) 83 | -- screen.stroke() 84 | end 85 | -- draw table elements 86 | for k,v in ipairs(p.val) do 87 | screen.level( (sel and k==p.listix) and 15 or 5) 88 | screen.move(xoff+2 + (k-1)*spacing, yoff) 89 | screen.text_center(v) 90 | end 91 | end 92 | 93 | -- draw param name 94 | screen.level( sel and 15 or 5 ) 95 | screen.move(36, yoff) 96 | screen.text_right(p.name) 97 | local xoff = 45 98 | -- draw param value 99 | if p.type == 'slider' then 100 | dslider(p, sel, xoff, yoff, 64) 101 | elseif p.list then -- draw a list type 102 | dtable(p, sel, xoff, yoff) 103 | else -- draw number/string value 104 | screen.move(xoff, yoff) 105 | if type(p.val) == 'number' then 106 | screen.text(string.format('%.4g',p.val)) 107 | else 108 | screen.text(p.val) 109 | end 110 | end 111 | end 112 | 113 | local len = pub.get_count() 114 | if len > 0 then 115 | if len > 6 then len = 6 end -- limit length to viewable count 116 | screen.font_face(1) 117 | screen.font_size(8) 118 | for i=1,len do 119 | dparam(pub.get_index(i), i==sel, (i+1)*9 - 1) 120 | end 121 | end 122 | end 123 | 124 | 125 | -- draw viewable i/o 126 | function draw.public_views( vs ) 127 | local function vslide(x, val) 128 | val = -val*3.6 129 | if math.floor(val+0.5) == 0 then 130 | screen.level(1) 131 | screen.pixel(x-1, 43) -- pixel prints 1px to the right of line_rel 132 | screen.fill() 133 | else 134 | screen.level(5) 135 | screen.move(x, 44) 136 | screen.line_rel(0, val) 137 | screen.stroke() 138 | end 139 | end 140 | screen.line_width(1) 141 | for i=1,2 do if vs.input[i] ~= nil then vslide(1 + (i-1) * 4, vs.input[i]) end end 142 | for i=1,4 do if vs.output[i] ~= nil then vslide(115 + (i-1) * 4, vs.output[i]) end end 143 | end 144 | 145 | 146 | function draw.console(s) 147 | screen.move(0,62) 148 | screen.font_face(1) 149 | screen.font_size(8) 150 | screen.level(3) 151 | 152 | -- strip leading control chars 153 | local start = string.find(s,"%C") 154 | s = string.sub(s,start or 0) 155 | 156 | -- replace any control chars with spaces 157 | s = string.gsub(s,"%c"," ") 158 | 159 | -- only draw if there is a message to show 160 | if string.len(s) > 0 then screen.text("> "..s) end 161 | end 162 | 163 | return draw --------------------------------------------------------------------------------