Skip to content

Instantly share code, notes, and snippets.

@justmat
Forked from phonk/Rebound 2.0.lua
Created June 13, 2019 13:31
Show Gist options
  • Save justmat/627d9c7a2085ea9f18d0eb54eff4f2cb to your computer and use it in GitHub Desktop.
Save justmat/627d9c7a2085ea9f18d0eb54eff4f2cb to your computer and use it in GitHub Desktop.
-- 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
-- TODO:
-- - ack-based version
local cs = require 'controlspec'
local Midi = require 'midi'
local MusicUtil = require 'musicutil'
engine.name = "PolyPerc"
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 shift = false
local info_note_name = ""
local info_visible = false
local info_timer = metro.init()
info_timer.event = 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.init()
u.time = 1/60
u.count = -1
u.event = 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.5,'')
params:add_control("amp", "amp", cs.AMP)
params:set_action("amp",
function(x) engine.amp(x) end)
cs.PW = cs.new(0,100,'lin',0,80,'%')
params:add_control("pw", "pw", cs.PW)
params:set_action("pw",
function(x) engine.pw(x/100) end)
cs.REL = cs.new(0.1,3.2,'lin',0,0.2,'s')
params:add_control("release", "release", cs.REL)
params:set_action("release",
function(x) engine.release(x) end)
cs.CUT = cs.new(50,5000,'exp',0,555,'hz')
params:add_control("cutoff", "cutoff", cs.CUT)
params:set_action("cutoff",
function(x) engine.cutoff(x) end)
cs.GAIN = cs.new(0,4,'lin',0,1,'')
params:add_control("gain", "gain", cs.GAIN)
params:set_action("gain",
function(x) engine.gain(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