Norns: code review

norns
#45

In regards to variable scoping yes, instrument and triggered are only valid inside this for loop, and will be replaced each iteration of the loop.

To be honest I haven’t totally grokked variable scoping in lua yet :sweat_smile:

0 Likes

#46

update on traffic.lua, my reworking of loom to decouple a sequence from its harmonic content.

the recent changes to the app have produced some music that’s got me feeling introspective so this will be needlessly long.

awhile ago i read that when pt anderson decided to adapt pynchon’s ‘inherent vice’, the first thing he did was type up the entire book, line by line, in screenplay format, to get a feel of what it had felt like to write the book. i thought this seemed like a good idea for my next version of loom, to teach myself vim and “truly understand” how the script worked.

this in retrospect was probably not a good use of my time because i almost immediately forgot how the app worked after i was done and i also introduced a bunch of typos that took forever to find.

but i’ve got 98% of the kinks worked out with my scale changing functions. i’ve added in tehn’s pattern recording to allow for the repetition of a sequence of scales of transpositions of arbitrary length, and i’ve added support for transposing within the key via midi.

i also tried to decouple the triggers from the notes but this proved to be both a lot harder and less useful than i thought, so i only wound up including 4 modes for trigger speed: normal, slower, paused, reverse.

i have gotten to the point where i don’t actually know where to go next with this code, aside from tweaking some edge case bugs as they pop up i am probably done with this idea in its current form.

the impulse with this project was to handle changing chords and modulations in a more dynamic way without interrupting flow (eg during a performance). but i’m not a performer, and what i find most handy about the script is its ability to quickly generate motifs and then refine them using harmony until until they’re something interesting. so for my next exploration, i want push the refinement possibilities further without losing the immediacy of the generative aspects.

some broad design goals:
—design a new grid sequencer that facilitates easy reassignment of individual notes and triggers without breaking a chord tone.
—bring wacom integration to sequencer. if the information can be processed without too big of a cpu hit this seems like an ideal way of refining sequences. map grid to tablet 1:1, select notes with pen, process with arc…
—if you hear a happy accident it should be possible to get back to it. on the norns screen, some abstraction of a timeline with options to set loop points within the previous events, ala the party van. maybe should be able to set sequencer to rewind mode to undo changes.
—should be a tab on norns’ display for synth paramaters.
—synth scenes that can be morphed between over a defined period of time.
—never use the brightest LED level for more than, say, 8 LEDs at a time. LED noise on this was a constant battle but too closely entwined with the sequencer for me to wrap my head around.

anyway here’s the script!

if you try it out and something isn’t working or is unclear please let me know! thanks again everyone.

18 Likes

#47

Great music - looking forward to trying out the code :thinking:

1 Like

#48

Will this be released into the dust repository or whatever its successor might be? I’d love to give it a spin.

0 Likes

#49

not 100% sure this was directed at me but just in case—i am still too github impaired to figure out how to do it, and have sort have been waiting to figure out what actually is going to happen to dust before i invest any time in it.

even though i called it done, after having used it for a couple of weeks i already have some more “small tweaks” i want to change. in the process i’ve discover the fundamental problem with this hobby:

—it takes time to build an instrument
—you need to play an instrument to know what it needs
—it takes time to learn to play an instrument
—doing one causes you to forget the other

it should “just work” if you copy/paste it into maiden though unless i’ve done something really wrong

4 Likes

#50

Just coming across this now. Great mind map you left for us to read/learn from. Thanks!

1 Like

#51

Quite an interesting instrument you’ve built. First play with it this evening. Really like the harmonic constraints you built in. Some may want more dissonances, but IMB one can’t go wrong with he Circle of 5ths. Looking forward to spending more time.

1 Like

#52

thanks so much! to be honest, it’s not enough dissonance for me. have been trying to think of more playable ways to do key changes with the midi keyboard. the simplest would be to make the black keys something like the secondary dominate of of the white keys, but it doesn’t seem like it would be very intuitive. if you want to experiment, i tried to set up the function for key_change to support modulation by any number of semitones! (the code is looking so janky to me now, really need to find some time to start the next version…)

0 Likes

#53

Here’s my first addition to the review. It should work fine on your norns.

Suggestions would be appreciated. Still studying though the many examples you all have provided in dust. Much to learn.

-- strum v0.52
--
-- tap grid pads to change pitch
-- double tap to add a rest
--
-- ENC 1: adjusts direction
-- ENC 2: adjusts tempo
-- ENC 3: sets scale
-- KEY 3: pauses/restarts
-- MIDI : transposes key
--
-- synth params can be changed
--
-- based on norns study #4
--
-- @carvingcode (Randy Brown)
--

engine.name = 'KarplusRings'

local cs = require 'controlspec'
music = require 'mark_eats/musicutil'
beatclock = require 'beatclock'

version = "v0.5"
name = ":: strum :: "

steps = {}
playmode = {"Onward","Aft","Sway","Joy"}
playchoice = 1
position = 1
transpose = 0
direction = 1
k3_state = 0

mode = math.random(#music.SCALES)
scale = music.generate_scale_of_length(60,music.SCALES[mode].name,8)

clk = beatclock.new()
clk_midi = midi.connect()
clk_midi.event = clk.process_midi

--
-- setup
--
function init()

    for i=1,16 do
        table.insert(steps,1)
    end
    grid_redraw()
    redraw()

    clk.on_step = handle_step
    clk.on_select_internal = function() clk:start() end
    clk.on_select_external = function() print("external") end
    clk:add_clock_params()

    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()

    clk:start()

end

--
-- each step
--
function handle_step()
    --print(playmode[playchoice])
    if playmode[playchoice] == "Onward" then
        position = (position % 16) + 1
    elseif playmode[playchoice] == "Aft" then
        position = position - 1
        if position == 0 then
            position = 16
        end
    elseif playmode[playchoice] == "Sway" then
        if direction == 1 then
            position = (position % 16) + 1
            if position == 16 then
                direction = 0
            end
        else
            position = position - 1
            if position == 1 then
                direction = 1
            end
        end
    else
        position = math.random(1,16)
    end

    if steps[position] ~= 0 then
        vel = math.random(1,100) / 100 -- random velocity values
        --print(vel)
        engine.amp(vel)
        engine.hz(music.note_num_to_freq(scale[steps[position]] + transpose))
    end
    grid_redraw()
end

--
-- norns keys
--
function key(n,z)
    --if n == 2 and z == 1 then
    --prompt = "key 2 pressed"
    --end
    if n == 3 and z == 1 then
        --prompt = "key 3 pressed"
        if k3_state == 0 then
            clk:stop()
            g.all(0)
            g.refresh()
            k3_state = 1
        else
            clk:start()
            k3_state = 0
        end
    end
    --if z == 0 then
    --prompt = "key released"
    --end
    redraw()
end

--
-- norns encoders
--
function enc(n,d)
    if n == 1 then          -- sequence direction
        playchoice = util.clamp(playchoice + d, 1, #playmode)
        print (playchoice)
    elseif n == 2 then      -- tempo
        params:delta("bpm",d)
    elseif n == 3 then      -- scale
        mode = util.clamp(mode + d, 1, #music.SCALES)
        scale = music.generate_scale_of_length(60,music.SCALES[mode].name,8)
    end
    redraw()
end

--
-- norns screen display
--
function redraw()
    screen.clear()
    screen.move(44,10)
    screen.level(5)
    screen.text(name)
    screen.move(0,20)
    screen.text("---------------------------------")
    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")
    screen.move(0,50)
    screen.level(5)
    screen.text("Scale: ")
    screen.move(30,50)
    screen.level(15)
    screen.text(music.SCALES[mode].name)
    --screen.move(64,60)
    --screen.level(2)
    --screen.text(":: carvingcode ::")
    screen.update()
end

--
-- grid functions
--
g = grid.connect()

g.event = function(x,y,z)
    --print(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

function grid_redraw()
    g.all(0)
    for i=1,16 do
        if steps[i] ~= 0 then
            for j=0,7 do
                g.led(i,steps[i]+j,i==position and 12 or (2+j))
            end
        end
    end
    g.refresh()
end

--
-- midi functions
--
k = midi.connect(1)
k.event = function(data)
    local d = midi.to_msg(data)
    if d.type == "note_on" then
        transpose = d.note - 60
    end
end
5 Likes

#54

Hello everyone… I’m trying to get my head around norns engines.

First off - I should say I HAVE NO IDEA WHAT I’M DOING
But… my usual technique of “copy-and-paste, then rewrite stuff until it works” seems to be working ok so far. :grin:

So - here’s an engine based on one of the f0plugins Ugens by fredrik olofsson. I’ve done one for SN76489 and Atari2600 so far. Atari2600 engine copied here for review.

EDIT - I should mention - I used PolyPerc as a template and changed a couple things around.

Would anyone with more sc/norns-engine experience give me comments on how this looks so far?

if you want to try this I have compiled the f0plugin ugens here.

6 Likes

#55

This is great - all working good here.

Some ideas

  • Not sure how to transpose works but via the parameters section so I can assign midi CC would be great. Being able to use a slider to switch transpose would be handy
  • Some sort of pattern saving so you can manually switch between patterns - I would look at the takt app in the bedtime folder as that has pattern chaining and a song mode in there on different pages. Or better still, check the Kria app in the Junklight folder, which is 4 channels with different pages of sequences and patterns. This has individual transpose per step not global. Both use a pattern selection across the top row.
  • midi out would be useful also.
  • I would clamp the upper range on bpf-freq and mainly the bpf-res to avoid it self oscillating - you get a nasty clicks that can blow your speakers when both are set high together and the audio cuts out all together
1 Like

#56

Thanks for feedback. Will look into these. Especially clamp to avoid speaker damage. KarplusRings is not my work. Will need to investigate.

Edit:

Per suggestions, I made adjustments to the parameters to be less extreme. See below, but I also updated them in the full script posted above. Please let me know your thoughts.

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)
0 Likes

#57

Hi! This is my first Norns script - it’s called “Bletchley Park.”

This script started out as a straightforward Turing Machine clone in Max4Live about 8 months ago, then gradually evolved as I learned Marbles and ported the script to Norns. Now, it’s a 2-voice, 16-step shift register, with adjustable loop length and repetition, and independent probabilistic triggers per voice.

I put together a quick demo on IG:


The big design difference vs Marbles are:

  1. It’s a true shift register, which adds a smoothness to the generated notes
  2. The registers are displayed in their entirety at all times, no hidden state
  3. The 2 voices have fully independent controls

The quirky/different thing is that it uses 3-bit integer math throughout, which maps very well onto a 16 x 8 grid - each step holds a value between 1 and 8, which can be displayed in 8 rows or in compactly in 3. I mostly use MIDI out with CVpal but it does have an internal PolyPerc engine also. More details on how it works are in the code comments.

I’m feeling OK about the code, although I’m not sure how much I’ll need to change for Norns 2.0 - the thing I’m most confused about is pattern/preset storage, but I’d appreciate any style tips, totally new to Lua and I haven’t worked in a dynamic language in a while.

Most of all, I’d be most interested in a “design review” - I’m really new to the Monome ecosystem, and I’m not sure what obvious best practices I’m missing. In particular, I’m not sure if the horizontal-fader UI approach makes sense, is legible at all, or is too dense. I’d actually like to add a lot more features, but don’t know how to fit them in - more pages, contextual/button combos, something else?

Also really curious how other folks are handling MIDI vs built-in sounds - is it worth it to support both, or better to decide on one or the other?

Finally, huge thanks to @Dan_Derks and @andrew for giving me early feedback and moral support on this, as well as for being inspirational artists and instrument designers.

15 Likes

#58

agh thanks R ! it’s sounding really lovely so far. this generative bit math stuff goes way over my head but I can’t wait to try it out someday

2 Likes

#59

I’ve followed your Insta posts for the past several days and have been intrigued. Will explore your code over the next couple.

You mention wanting to explore some ‘how to best do things’ ideas: I might suggest taking a look at the “loom” script in the 'mark_eats" folder. He handles saving/loading presets, paging, and a lot of other interesting things which may be of interest.

Great work.

1 Like

#60

You can sneak a look at syntax changes here. mostly it’s quick/easy changes to metro and grid functions.

4 Likes

#61

thanks

missed this in another thread

0 Likes

#62

anybody willing to glance at my minor tweak of a script by @Tyler ??
(hopefully not stepping on their toes…just wanted to try updating a small app for 2.0)

4 Likes

#63

will check this out later, I love ekombi!

0 Likes

#64

Love cranes still! Any idea how difficult it would be to add a display of the current time of the buffer? Say start is at 2 seconds and end is at 8. It would be cool to know where in that span the play head is at.

edit : not sure where is the best place to post this, please move as needed

0 Likes