Skip to content

Instantly share code, notes, and snippets.

@j-flee
Last active October 9, 2018 01:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save j-flee/58d88182d910f55f7ed7792eafb6dac4 to your computer and use it in GitHub Desktop.
Save j-flee/58d88182d910f55f7ed7792eafb6dac4 to your computer and use it in GitHub Desktop.
rebound sequencer and karplus rings engine
-- rebound: a kinetic sequencer
--
-- key1: shift^
-- key2: add/^remove orb
-- key3: select next orb
-- enc1: change orb note
-- enc2: rotate orb^s
-- enc3: accelerate orb^s
-- written by nf in august 2018
-- params and scales taken from tehn/awake, thanks
--
-- @burns karplusrings engine added
-- by Johnathan F. Lee
-- TODO:
-- - ack-based version
local cs = require 'controlspec'
local Midi = require 'midi'
local MusicUtil = require 'mark_eats/musicutil'
engine.name = "KarplusRings"
local m = midi.connect()
local balls = {}
local cur_ball = 0
local BeatClock = require 'beatclock'
local clk = BeatClock.new()
local scale_notes = {}
local note_queue = {}
local note_off_queue = {}
local min_note = 0
local max_note = 127
local min_rand_note = min_note+24
local max_rand_note = max_note-24
local freq_queue = {}
local shift = false
local info_note_name = ""
local info_visible = false
local info_timer = metro.alloc()
info_timer.callback = function() info_visible = false end
function show_info()
info_visible = true
info_timer:start(1, 1)
local b = balls[cur_ball]
local n = MusicUtil.snap_note_to_array(b.n, scale_notes)
info_note_name = MusicUtil.note_num_to_name(n, true)
end
function init()
screen.aa(1)
local u = metro.alloc()
u.time = 1/60
u.count = -1
u.callback = update
u:start()
clk.on_step = play_notes
clk.on_select_internal = function() clk:start() end
clk:add_clock_params()
params:add_separator()
local scales = {}
for i=1,#MusicUtil.SCALES do
scales[i] = MusicUtil.SCALES[i].name
end
params:add_option("scale", "scale", scales)
params:set_action("scale", build_scale)
params:add_option("root", "root", MusicUtil.NOTE_NAMES)
params:set_action("root", build_scale)
params:add_separator()
cs.AMP = cs.new(0,1,'lin',0,0.75,'')
params:add_control("amp", "amp", cs.AMP)
params:set_action("amp",
function(x) engine.amp(x) end)
engine.amp(0.75)
cs.DECAY = cs.new(0.1,5,'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,1,'lin',0,0.11,'')
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,3600,'')
params:add_control("lpf_freq", "lpf_freq", cs.LPF_FREQ)
params:set_action("lpf_freq",
function(x) engine.lpf_freq(x) end)
engine.lpf_freq(3600.0)
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,9000,'lin',0,1200,'')
params:add_control("bpf_freq", "bpf_freq", cs.BPF_FREQ)
params:set_action("bpf_freq",
function(x) engine.bpf_freq(x) end)
engine.bpf_freq(1200.0)
cs.BPF_RES = cs.new(0.05,2.5,'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()
end
function build_scale()
scale_notes = MusicUtil.generate_scale(params:get("root") - 1, params:get("scale"), 9)
end
function redraw()
screen.clear()
if shift then
screen.level(5)
screen.line_width(1)
screen.rect(1,1,126,62)
screen.stroke()
end
for i=1,#balls do
drawball(balls[i], i == cur_ball)
end
if info_visible and cur_ball > 0 then
screen.level(15)
screen.font_face(3)
screen.font_size(16)
screen.move(8,52)
screen.text(cur_ball)
screen.move(32,52)
screen.text(info_note_name)
end
screen.update()
end
function update()
for i=1,#balls do
updateball(balls[i])
end
redraw()
end
function enc(n, d)
if n == 1 and not shift and cur_ball > 0 then
-- note
balls[cur_ball].n = math.min(math.max(balls[cur_ball].n+d, min_note), max_note)
show_info()
elseif n == 2 then
-- rotate
for i=1,#balls do
if shift or i == cur_ball then
balls[i].a = balls[i].a - d/10
end
end
elseif n == 3 then
-- accelerate
for i=1,#balls do
if shift or i == cur_ball then
balls[i].v = balls[i].v + d/10
end
end
end
end
function key(n, z)
if n == 1 then
-- shift
shift = z == 1
elseif n == 2 and z == 1 then
if shift then
-- remove ball
table.remove(balls, cur_ball)
if cur_ball > #balls then
cur_ball = #balls
end
else
-- add ball
table.insert(balls, newball())
cur_ball = #balls
end
show_info()
elseif n == 3 and z == 1 and not shift and #balls > 0 then
-- select next ball
cur_ball = cur_ball%#balls+1
show_info()
end
end
function newball()
return {
x = 64,
y = 32,
v = 0.5*math.random()+0.5,
a = math.random()*2*math.pi,
n = math.floor(math.random()*(max_rand_note-min_rand_note)+min_rand_note),
}
end
function drawball(b, hilite)
screen.level(hilite and 15 or 5)
screen.circle(b.x, b.y, hilite and 2 or 1.5)
screen.fill()
end
function updateball(b)
b.x = b.x + math.sin(b.a)*b.v
b.y = b.y + math.cos(b.a)*b.v
local minx = 2
local miny = 2
local maxx = 126
local maxy = 62
if b.x >= maxx then
b.x = maxx
b.a = 2*math.pi - b.a
enqueue_note(b, 0)
elseif b.x <= minx then
b.x = minx
b.a = 2*math.pi - b.a
enqueue_note(b, 1)
elseif b.y >= maxy then
b.y = maxy
b.a = math.pi - b.a
enqueue_note(b, 2)
elseif b.y <= miny then
b.y = miny
b.a = math.pi - b.a
enqueue_note(b, 3)
end
end
function enqueue_note(b, z)
local n = b.n
if z == 0 then
n = n + 12
elseif z == 1 then
n = n - 12
end
n = math.max(min_note, math.min(max_note, n))
table.insert(note_queue, n)
end
function play_notes()
-- send note off for previously played notes
while #note_off_queue > 0 do
m.send({type='note_off', note=table.remove(note_off_queue)})
end
-- play queued notes
while #note_queue > 0 do
local n = table.remove(note_queue)
n = MusicUtil.snap_note_to_array(n, scale_notes)
engine.hz(MusicUtil.note_num_to_freq(n))
m.send({type='note_on', note=n})
table.insert(note_off_queue, n)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment