Last active
October 9, 2018 01:05
-
-
Save j-flee/58d88182d910f55f7ed7792eafb6dac4 to your computer and use it in GitHub Desktop.
rebound sequencer and karplus rings engine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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