The entire script is pretty minimal, bear in mind this is my very first attempt, trying to learn norns, lua, and a new sc engine at the same time, so I would not be surprised if I made an error somewhere.
-- arpcore
-- v0.01 by robotfunk
--
-- /////////////////////
-- / / / / / / / / / / / / /
-- / / / / / / / / / / /
-- / / / / / / / / / / /
-- / / / / / / / / / / /
engine.name = 'KarplusRings'
musicutil = require 'musicutil'
er = require 'er'
mode = 1
toniclist = musicutil.NOTE_NAMES
tonicnum = 1
mode_name = musicutil.SCALES[mode].name
m = midi.connect()
m:start()
function init()
generate_notes()
noteIndex = 1
length = 3
stride = 1
offset = 0
pulses = 1
End = 16
generate_gates()
--general params
--params:add{type = "option", id = "use_karplus_rings", name = "Use Karplus Rings", options = {'yes','no'}, default = 1}
params:add{type = "option", id = "send_midi", name = "Send MIDI Out", options = {'yes','no'}, default = 1}
params:add{type = "option", id = "send_crow", name = "Send Crow Out", options = {'yes','no'}, default = 2}
params:add{type = "option", id = "crow_outs", name = "Crow Outputs", options = {'1/2','3/4'}, default = 1}
-- params:add{type = "option", id = "send_osc", name = "Send OSC Out", options = {'yes','no'}, default = 2}
params:add_separator()
--KarplusRings params
--cs.AMP = cs.new(0,1,'lin',0,0.5,'')
params:add_control("amp","amp", controlspec.new(0,1,"lin",0,0.5,""))
params:set_action("amp", function(x) engine.amp(x) end)
--cs.BPF_FREQ = cs.new(100,10000,'lin',0,0.5,'')
params:add_control("bpf_freq","bpf_freq", controlspec.new(100,10000,"lin",0,0.5,""))
params:set_action("bpf_freq", function(x) engine.bpf_freq(x) end)
--cs.BPF_RES = cs.new(0,4,'lin',0,0.5,'')
params:add_control("bpf_res","bpf_res", controlspec.new(1,2,"lin",0,0.5,""))
params:set_action("bpf_res", function(x) engine.bpf_res(x) end)
--cs.COEF = cs.new(0,1,'lin',0,0.11,'')
params:add_control("coef","coef", controlspec.new(0,0.75,"lin",0,0.11,""))
params:set_action("coef", function(x) engine.coef(x) end)
-- cs.DECAY = cs.new(0.1,15,'lin',0,3.6,'s')
params:add_control("decay","decay", controlspec.new(0,15,"lin",0,3.6,"s"))
params:set_action("decay", function(x) engine.decay(x) end)
--cs.LPF_FREQ = cs.new(100,10000,'lin',0,4500,'')
params:add_control("lpf_freq","lpf_freq", controlspec.new(100,10000,"lin",0,4500,""))
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", controlspec.new(0,3.2,"lin",0,0.5,""))
params:set_action("lpf_gain", function(x) engine.lpf_gain(x) end)
--seq params
--length
params:add_control("length","length", controlspec.new(1,16,"lin",1,3,""))
params:set_action("length", function(x) length=x end)--numNotes
--stride
params:add_control("stride","stride", controlspec.new(-16,16,"lin",1,1,""))
params:set_action("stride", function(x) stride=x end)
--pulses
params:add_control("pulses","pulses", controlspec.new(1,16,"lin",1,1,""))
params:set_action("pulses", function(x) pulses=x end)
params:bang()
clk=clock.run(pulse)
end
function clock.transport.start()
clock.cancel(clk)
clk = clock.run(pulse)
end
function clock.transport.stop()
clock.cancel(id)
end
function pulse()
while true do
clock.sync(1/4)
step()
end
end
function step()
if (gates[noteIndex]) then
engine.hz((440 / 32) * (2 ^ ((notes[noteIndex] - 9) / 12)))
if (params:get("send_midi")==1) then
chan=1
note=notes[noteIndex]
m:note_on(note, 100, 1)
noteoff_delayed = metro.init()
noteoff_delayed.time = .05
noteoff_delayed.count = 1
noteoff_delayed.event =
function(chan, note)
m:note_off(note, 0, 1)
metro.free(noteoff_delayed.id)
end--function
noteoff_delayed:start()
end
end
noteIndex = noteIndex + stride
while (noteIndex < 1) do
noteIndex = noteIndex + length
end
while (noteIndex > length) do
noteIndex = noteIndex - length
end
redraw()
end
function key(n,z)
if ((n==2) and (z==1)) then
mode = mode + 1
if (mode>#musicutil.SCALES) then mode = 1 end
generate_notes()
end
if ((n==3) and (z==1)) then shuffle(notes) end
end
function enc(n,d)
if (n==1) then
params:delta("stride", d)
end
if (n==2) then
params:delta("length", d)
generate_gates()
end
if (n==3) then
params:delta("pulses", d)
generate_gates()
end
end
function get_value_by_shift(t, index, shift)
index = (index + shift - 1)%#t + 1
return t[index]
end
function redraw()
End = (pulses + length) -1
if (End>16) then End=End-16 end
screen.clear()
screen.level(15)
screen.line_width(1)
for i=1,16 do
screen.rect( (i-1)*8+1 , 19, 7, 8)
if (gates[i]) then screen.fill() else screen.stroke() end
end
screen.level(6)
for i=1,16 do
screen.rect( (i-1)*8+1 , 19, 7, 8)
if (i==(noteIndex)) then screen.fill() else screen.stroke() end
end
--screen.stroke()
--screen.move((pulses-1)*8+3, 25)
--screen.text(">")
--screen.move((End-1)*8+3, 25)
--screen.text("<")
screen.move(0,40)
screen.text(mode .. " " .. musicutil.SCALES[mode].name)
screen.move(0,8)
screen.text("stride: "..stride)
screen.move(0,60)
screen.text("length: "..length)
screen.move(92,60)
screen.text("pulses: "..pulses)
screen.update()
end
function cleanup()
-- deinitialization
end
function generate_notes()
notes = musicutil.generate_scale_of_length(36+(tonicnum-1),musicutil.SCALES[mode].name,16)
end
function generate_gates()
gates = er.gen(pulses, length)
end
function shuffle(tbl)
for i = #tbl, 2, -1 do
local j = math.random(i)
tbl[i], tbl[j] = tbl[j], tbl[i]
end
end
--copied from foulplay, not (yet?) used
local function rotate_pattern(t, rot, n, r)
-- rotate_pattern comes to us via okyeron and stackexchange
n, r = n or #t, {}
rot = rot % n
for i = 1, rot do
r[i] = t[n - rot + i]
end
for i = rot + 1, n do
r[i] = t[i - rot]
end
return r
end