Norns: help

norns

#1080

Are new/current Norns the same as previous releases? I potentially have the option to buy an original one, I wonder if they are the same?

I’d love to buy new and support the project, but I fear they’d sell out by the time I have saved the extra.


#1081

Yes, the norns hardware hasn’t changed since the original release


#1082

carl is correct

they are the same
only the firmware/software has changed since initial release of each batch


#1083

Ahh cool, and that can be upgraded right? Lovely.


#1084

well it depends what you mean by that…
one upgrade you might have read about in discussions allows for expanded on-board memory

i’m not sure if other (hardware) expansions are possible


#1085

Apologies I wasnt clear. I mean the firmware.


#1086

ah cool
no worries btw

the firmware can easily be upgraded


#1087

And the beauty of it is also that the hardware is virtually immortal, as his processor and memory reside on a compute module that you can easily swap. Whenever a new compute module comes out, with more processing power or memory, you can have a whole new machine for pennies (provided that the firmware supports it, but I’m sure it will). This is one of the reasons I’m totally sucked in the platform: the initial cost might appear high, but once you are in, it won’t be obsoleted for a long time (at least until the compute module format gets eventually retired).


#1088

Where are the base functions located? Not the Lua wrappers, but the base functions that many of the Lua functions point to? Ex: in the ‘grid.lua’ file, the ‘rotation’ function uses a ‘grid_set_rotation()’ function. Where is that located? Thanks.


#1089

Github has a good search function.


#1090

Correct. Need to know location in the norns structure.

norns/matron/src/weaver.c

That contains helper functions. Not the function I’m looking for…


#1091

I’m not sure from your comment if you’re saying that you need to knwo the structure to be able to find the answer, but the ability that github provides to search the entire repo makes that less of a requirement.

Perhaps this is what you’re looking for?

or if you want to get deeper again:


#1092

Thanks. Yes, that’s closer to what I wanted to look at. Trying to find a way to fix to a zero-based array issue in the current law function.

Sorry, was working local – forgot all of this is on GitHub. :slight_smile:


#1093

Lua tables aren’t necessarily automatically numbered like arrays in other programming languages (they are ‘associative arrays’), so I think a key/value pair solution may be easier.

https://www.lua.org/pil/2.5.html


#1094

Have been working on this problem. Using this key/value assignment:

local grid_display_options = {["0"] = 0, ["90"] = 1, ["180"] = 2, ["270"] = 3}

… and this add_option/set_option assignment:

    params:add_option("grid_rotation", "Grid Rotation", grid_display_options, 0)
    params:set_action("grid_rotation", function(x) 
        grid_device:all(0)
        grid_device:rotation(x)
        grid_device:refresh()
    end)  

… maiden reports the following error:

### SCRIPT ERROR: init
/home/we/norns/lua/core/params/option.lua:17: bad argument #1 to 'pairs' (table expected, got nil)
stack traceback:
	/home/we/norns/lua/core/norns.lua:185: in function </home/we/norns/lua/core/norns.lua:185>
	[C]: in function 'pairs'
	/home/we/norns/lua/core/params/option.lua:17: in function 'core/params/option.new'
	/home/we/norns/lua/core/paramset.lua:67: in function 'core/paramset.add'

Is the problem in my code (highly possible) or is the problem with the ‘option.lua’ lines 17-19?:

  for k,v in pairs(options) do
    o.options[k] = v
  end

#1096

Do you need those brackets around the string values?


#1097

@carvingcode can you post the whole script? it looks like the table just isn’t initialized when the param is added.

@Jonny i agree that the brackets look weird, but they aren’t wrong. they let you use spaces in the string key if you want; or if you put an arbitrary lua expression it will be evaluated and the result used for the key.

[ed: and actually, i now think they are necessary for this inline initialization syntax.)


#1098

I know, they do look weird. This is what stack overflow responses said was correct.


#1099

FWIW - I was serious when I suggested this was my fault. I wrote/added the grid rotation function.

There’s are bit of code in weaver where this can be adjusted.

I can poke at this later tonight.

In the meantime - did you try manually adding 1 to the value you’re sending for rotation?


#1100

yes, thanks for looking.

-- 
-- Strum: plucky patterns
--
-- "Full moon shines so bright
--      Stars in its vicinity
--          All but disappear"
--
--
-- grid Controls
-- 
-- Tap grid pads to set/change pitch. 
-- Or randomize them with K2.
-- Double tap to add a rest.
-- 
-- norns Controls
-- 
-- Dividied into pages
-- 
-- ENC 1: chooses page
-- 
-- Page 1
-- 
-- ENC 2: adjusts direction
-- ENC 3: adjusts tempo
-- 
-- Page 2
-- 
-- ENC 2: sets scale
-- 
-- ENC 3: sets pattern length
-- 
-- KEY 2: randomizes pattern
-- 
-- KEY 3: pauses/restarts
-- 
-- MIDI
-- 
-- MIDI IN: clock sync
-- MIDI OUT: to external synths
-- 
-- Param Settings
-- 
-- Grid display: scatter or bar
-- Set grid and MIDI device
-- Set grid rotation
-- Set MIDI channel
-- Various clock settings
-- Synth params can be changed (beware the high ends!!!) -- Delay can be set using "cut1rate" (new)
-- 
-- 
-- Based on norns study #4
-- 
-- @carvingcode (Randy Brown)
-- v0.7d_0313 (for v2.x)
--


engine.name = 'KarplusRings'

local UI = require "ui"
local cs = require 'controlspec'
local music = require 'musicutil'
local beatclock = require 'beatclock'
local h = require 'halfsecond'

local playmode = {"Onward","Aft","Joy"}
local out_options = {"Audio", "MIDI", "Audio + MIDI"}

local grid_display_options = {["0"] = 0, ["90"] = 1, ["180"] = 2, ["270"] = 3}

-- vars for UI
local SCREEN_FRAMERATE = 15
local screen_refresh_metro
local screen_dirty = true
local pages

local name = ":: Strum :: "

-- pattern vars
local steps = {}
local playchoice = 1
local pattern_len = 16
local position = 1

local note_playing = nil
local prev_note = 0
local next_step = 0
local transpose = 0
local direction = 1
local k3_state = 0

-- device vars
local grid_device = grid.connect()
local midi_in_device = midi.connect()
local midi_out_device = midi.connect()
local midi_out_channel

-- scale vars
local mode = 5 -- set to dorian
local scale = music.generate_scale_of_length(60,music.SCALES[mode].name,8)

-- clock vars
local clk = beatclock.new()
local clk_midi = midi.connect()
clk_midi.event = clk.process_midi

----------------
-- stop notes --   TODO build this out
----------------
local function all_notes_kill()
  
  -- Audio engine out
  --engine.noteKillAll(). ??
  
  -- MIDI out
  if (params:get("output") == 2 or params:get("output") == 3) then
      midi_out_device:note_off(a, 96, midi_out_channel)
  end
  note_playing = nil
end

-------------------
-- reset pattern --	TODO finish
-------------------
local function reset_pattern()
    --playchoice = 1
    --position = 1
    --note_playing = nil
    clk:reset()
end

----------------------
-- handle each step --
----------------------
function handle_step()

    if playmode[playchoice] == "Onward" then
        position = (position % pattern_len) + 1
        
    elseif playmode[playchoice] == "Aft" then
        position = position - 1
        if position == 0 then
            position = pattern_len
        end
        
        -- 	TODO fix math
        
        --[[
    elseif playmode[playchoice] == "Sway" then
        if direction == 1 then
            position = (position % pattern_len) + 1
            if position == pattern_len then
                direction = 0
            end
        else
             if pattern_len > 1 then
                position = position - 1
            end
            if position == 1 then
                direction = 1
            end
        end
        --]]
    else -- random step position
        position = math.random(1,pattern_len)
    end

    if steps[position] ~= 0 then
        vel = math.random(1,100) / 100 -- random velocity values
        
          -- Audio engine out
        if params:get("output") == 1 or params:get("output") == 3 then
                engine.amp(vel)
                engine.hz(music.note_num_to_freq(scale[steps[position]] + transpose))
        end
        
            -- MIDI out
        if (params:get("output") == 2 or params:get("output") == 3) then
            if note_playing ~= nil then
                midi_out_device:note_off(note_playing,nil)
            end
            note_playing = music.freq_to_note_num(music.note_num_to_freq(scale[steps[position]] + transpose),1)
            midi_out_device:note_on(note_playing,vel*100)
        end
        
    end
    grid_redraw()
end

-----------
-- setup --
-----------
function init()

	screen.aa(1)
  
	-- Init UI
  	pages = UI.Pages.new(1, 3)
  
  	-- Start drawing to screen
  	screen_refresh_metro = metro.init()
  	screen_refresh_metro.event = function()
  		if screen_dirty then
      		screen_dirty = false
	  		redraw()
    	end
    end
	screen_refresh_metro:start(1 / SCREEN_FRAMERATE)
  
	-- initialize pattern with random notes
    for i=1,16 do
        table.insert(steps,math.random(0,8))
    end

	-- set clock functions
    clk.on_step = handle_step
    clk.on_stop = reset_pattern
    clk.on_select_internal = function() clk:start() end
    clk.on_select_external = reset_pattern

	-- set up parameter menu

    params:add_number("bpm", "BPM", 1, 480, clk.bpm)
    params:set_action("bpm", function(x) clk:bpm_change(x) end)
    params:set("bpm", 72)
    
    params:add_separator()
    
    params:add{type = "number", id = "grid_device", name = "Grid Device", min = 1, max = 4, default = 1, 
        action = function(value)
        grid_device:all(0)
        grid_device:refresh()
        grid_device = grid.connect(value)
    end}
    
    params:add_option("grid_display", "Grid Display", { "Bar", "Scatter" }, grid_display or 1 and 2)
    params:set_action("grid_display", function(x) if x == 1 then grid_display = 1 else grid_display = 2 end end)
  

    params:add_option("grid_rotation", "Grid Rotation", grid_display_options, 0)
    params:set_action("grid_rotation", function(x) 
        grid_device:all(0)
        grid_device:rotation(x)
        grid_device:refresh()
    end)       

--[[
	-- working, not ideal
    params:add{type = "number", id = "grid_rotation", name = "Grid Rotation", min = 0, max = 3, default = 0, 
		action = function(value)
		grid_device:all(0)
		grid_device:rotation(value)
		grid_device:refresh()
    end}
--]]
    params:add_separator()
    
    params:add{type = "option", id = "output", name = "Output", options = out_options, 
        action = all_notes_kill}
        
    params:add{type = "number", id = "midi_out_device", name = "MIDI Out Device", min = 1, max = 4, default = 1,
        action = function(value)
        midi_out_device = midi.connect(value)
    end}
  
    params:add{type = "number", id = "midi_out_channel", name = "MIDI Out Channel", min = 1, max = 16, default = 1,
        action = function(value)
        all_notes_kill()
        midi_out_channel = value
    end}
    
	params:add{type = "number", id = "clock_midi_in_device", name = "Clock MIDI In Device", min = 1, max = 4, default = 1,
    	action = function(value)
		midi_in_device = midi.connect(value)
    end}
  
	params:add{type = "option", id = "clock_out", name = "Clock Out", options = {"Off", "On"}, default = clk.send or 2 and 1,
    	action = function(value)
		if value == 1 then clk.send = false
		else clk.send = true end
    end}
  
    params:add_separator()

    cs.AMP = cs.new(0,1,'lin',0,0.5,'')
    params:add_control("amp", "Amp", cs.AMP)
    params:set_action("amp",
        function(x) engine.amp(x) end)

    cs.DECAY = cs.new(0.1,15,'lin',0,3.6,'s')
    params:add_control("damping", "Damping", cs.DECAY)
    params:set_action("damping",
        function(x) engine.decay(x) end)

    cs.COEF = cs.new(0.2,0.9,'lin',0,0.2,'')
    params:add_control("brightness", "Brightness", cs.COEF)
    params:set_action("brightness",
        function(x) engine.coef(x) end)

    cs.LPF_FREQ = cs.new(100,10000,'lin',0,4500,'')
    params:add_control("lpf_freq", "LPF Freq", cs.LPF_FREQ)
    params:set_action("lpf_freq",
        function(x) engine.lpf_freq(x) end)

    cs.LPF_GAIN = cs.new(0,3.2,'lin',0,0.5,'')
    params:add_control("lpf_gain", "LPF Gain", cs.LPF_GAIN)
    params:set_action("lpf_gain",
        function(x) engine.lpf_gain(x) end)

    cs.BPF_FREQ = cs.new(100,4000,'lin',0,0.5,'')
    params:add_control("bpf_freq", "BPF Freq", cs.BPF_FREQ)
    params:set_action("bpf_freq",
        function(x) engine.bpf_freq(x) end)

    cs.BPF_RES = cs.new(0,3,'lin',0,0.5,'')
    params:add_control("bpf_res", "BPF Res", cs.BPF_RES)
    params:set_action("bpf_res",
        function(x) engine.bpf_res(x) end)

    params:bang()

	-- set up MIDI in
    midi_in_device.event = function(data)
    	clk:process_midi(data)
		if not clk.playing then
			screen_dirty = true
    	end
	end
    
	clk:start()
        
    --h.init()
end


-------------------------
-- handle grid presses --
-------------------------
grid_device.key = function(x,y,z)

    if z == 1 then
        if steps[x] == y then
            steps[x] = 0
        else
            steps[x] = y
        end
        grid_redraw()
    end
    redraw()
end

---------------------
-- redraw the grid --
---------------------
function grid_redraw()
	
    grid_device:all(0)
    for i=1,pattern_len do
         if grid_display == 1 then
            if steps[i] ~= 0 then
                for j=0,7 do
                    grid_device:led(i,steps[i]+j,i==position and 12 or (2+j))
                end
            end
        else
            grid_device:led(i,steps[i],i==position and 12 or 4)
        end
    end
    grid_device:refresh()
end

-----------------------
-- handle norns keys --
-----------------------
function key(n,z)
	
    if n == 2 and z == 1 then
         steps = {}
        for i=1,16 do
            table.insert(steps,math.random(0,8))
        end
    end
    if n == 3 and z == 1 then
        if k3_state == 0 then
            clk:stop()
            k3_state = 1
            
            -- MIDI out
            if (params:get("output") == 2 or params:get("output") == 3) then
                all_notes_kill()
            end
            -- clear grid lights
            grid_device:all(0)
            grid_device:refresh()
        else
            reset_pattern()
            clk:start()
            k3_state = 0
        end
    end

    redraw()
end

---------------------------
-- handle norns encoders --
---------------------------
function enc(n,delta)
	
    -- handle UI paging
    if n == 1 then
    -- Page scroll
        pages:set_index_delta(util.clamp(delta, -1, 1), false)
    end
  
    if pages.index == 1 then
        
        if n == 2 then           -- sequence direction
            playchoice = util.clamp(playchoice + delta, 1, #playmode)
        elseif n == 3 then      -- tempo
            params:delta("bpm",delta)
        end
        
    elseif pages.index == 2 then

        if n == 2 then       -- scale
            mode = util.clamp(mode + delta, 1, #music.SCALES)
            scale = music.generate_scale_of_length(60,music.SCALES[mode].name,8)
        elseif n == 3 then
            pattern_len = util.clamp(pattern_len + delta, 2, 16)
        end
        
    end
    redraw()
    
    screen_dirty = true
end

-------------------------
-- handle norns screen --
-------------------------
function redraw()
	
    screen.clear()
    
    pages:redraw()
    
    screen.aa(1)
    screen.line_width(1)
    screen.move(44,10)
    screen.level(5)
    screen.text(name)
    screen.move(0,15)
    screen.line(127,15)
    screen.stroke()
    
    if pages.index == 1 then
	    
        screen.move(0,30)
        screen.level(5)
        screen.text("Path: ")
        screen.move(30,30)
        screen.level(15)
        screen.text(playmode[playchoice])
        screen.move(0,40)
        screen.level(5)
        screen.text("Tempo: ")
        screen.move(30,40)
        screen.level(15)
        screen.text(params:get("bpm").." bpm")
        
    elseif pages.index == 2 then
        screen.move(0,30)
        screen.level(5)
        screen.text("Scale: ")
        screen.move(30,30)
        screen.level(15)
        screen.text(music.SCALES[mode].name)
        screen.move(0,40)
        screen.level(5)
        screen.text("Len: ")
        screen.move(30,40)
        screen.level(15)
        screen.text(pattern_len)
        
    elseif pages.index == 3 then
         screen.move(0,30)
         screen.text(":: you are here ::")
    end
    
    screen.update()
end


function cleanup ()
  clk:stop()
end