Permalink
Cannot retrieve contributors at this time
| -- 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 | |
| -- contributors: nf, okyeron | |
| local cs = require 'controlspec' | |
| local MusicUtil = require 'musicutil' | |
| engine.name = "PolyPerc" | |
| local m = midi.connect() | |
| local ii_options = {"off", "ii jf"} | |
| local balls = {} | |
| local cur_ball = 0 | |
| 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() | |
| cs.CLOCKDIV = cs.new(1/4,16,'lin',0.5,4,'') | |
| params:add_control("step_div", "clock division", cs.CLOCKDIV) | |
| params:add_option("ii", "ii", ii_options, 1) | |
| params:set_action("ii", function() crow.ii.pullup(true) crow.ii.jf.mode(1) end) | |
| 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() | |
| clock.run(pulse) | |
| end | |
| function pulse() | |
| while true do | |
| clock.sync(1/params:get("step_div")) | |
| play_notes() | |
| end | |
| 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}) | |
| if params:get("ii") == 2 then | |
| crow.ii.jf.play_note((n - 60) / 12, 5) | |
| end | |
| table.insert(note_off_queue, n) | |
| end | |
| end |