Created
December 1, 2024 23:12
-
-
Save ylmrx/da3f9f356074be7a8923abd268481bcb to your computer and use it in GitHub Desktop.
Awake Passersby, w/ arc
This file contains hidden or 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
-- awake: time changes | |
-- 2.3 @tehn | |
-- llllllll.co/t/21022 | |
-- | |
-- top loop plays notes | |
-- transposed by bottom loop | |
-- | |
-- (grid optional) | |
-- | |
-- E1 changes modes: | |
-- STEP/LOOP/SOUND/OPTION | |
-- | |
-- K1 held is alt * | |
-- | |
-- STEP | |
-- E2/E3 move/change | |
-- K2 toggle *clear | |
-- K3 morph *rand | |
-- | |
-- LOOP | |
-- E2/E3 loop length | |
-- K2 reset position | |
-- K3 jump position | |
-- | |
-- SOUND | |
-- K2/K3 selects | |
-- E2/E3 changes | |
-- | |
-- OPTION | |
-- *toggle | |
-- E2/E3 changes | |
engine.name = "Passersby" | |
passersby = include "passersby/lib/passersby_engine" | |
local hs = include('lib/halfsecond') | |
local MusicUtil = require "musicutil" | |
local options = {} | |
options.OUTPUT = {"audio", "midi", "audio + midi", "crow out 1+2", "crow ii JF", "crow ii JF + cv"} | |
options.STEP_LENGTH_NAMES = {"1 bar", "1/2", "1/3", "1/4", "1/6", "1/8", "1/12", "1/16", "1/24", "1/32", "1/48", "1/64"} | |
options.STEP_LENGTH_DIVIDERS = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64} | |
local grid = util.file_exists(_path.code.."toga") and include "toga/lib/togagrid" or grid | |
local arc = util.file_exists(_path.code.."toga") and include "toga/lib/togaarc" or arc | |
g = grid.connect() | |
a = arc.connect() | |
local alt = false | |
local mode = 1 | |
local mode_names = {"STEP","LOOP","SOUND","OPTION"} | |
local one = { | |
pos = 0, | |
length = 8, | |
start = 1, | |
data = {1,0,3,5,6,7,8,7,0,0,0,0,0,0,0,0} | |
} | |
local two = { | |
pos = 0, | |
length = 7, | |
start = 1, | |
data = {5,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0} | |
} | |
function add_pattern_params() | |
params:add_separator() | |
params:add{type = "number", id = "one_length", name = "<one> length]", min=1, max=16, | |
default = one.length, | |
action=function(x) one.length = x end } | |
params:add{type = "number", id = "one_start", name = "<one> start]", min=1, max=16, | |
default=one.start, | |
action=function(x) one.start = x end } | |
for i=1,16 do | |
params:add{type = "number", id= ("one_data_"..i), name = ("<one> data "..i), min=0, max=8, | |
default = one.data[i], | |
action=function(x)one.data[i] = x end } | |
end | |
params:add_separator() | |
params:add{type = "number", id = "two_length", name = "<two> length]", min=1, max=16, | |
default = two.length, | |
action=function(x)two.length = x end} | |
params:add{type = "number", id = "two_start", name = "<two> start]", min=1, max=16, | |
default = two.start, | |
action=function(x)two.start = x end } | |
for i=1,16 do | |
params:add{type = "number", id= "two_data_"..i, name = "<two> data "..i, min=0, max=8, | |
default = two.data[i], | |
action=function(x) two.data[i] = x end } | |
end | |
params:add_separator() | |
end | |
local set_loop_data = function(which, step, val) | |
params:set(which.."_data_"..step, val) | |
end | |
local midi_out_device | |
local midi_out_channel | |
local scale_names = {} | |
local notes = {} | |
local active_notes = {} | |
local edit_ch = 1 | |
local edit_pos = 1 | |
snd_sel = 1 | |
local snd_names = {"peak", "decay", "fm low", "rev", "fb", "rate"} | |
local snd_params = {"peak", "decay", "fm_low_amount", "reverb_mix", "delay_feedback", "delay_rate"} | |
local NUM_SND_PARAMS = #snd_params | |
local BeatClock = include 'lib/beatclock-crow' | |
local clk = BeatClock.new() | |
local clk_midi = midi.connect() | |
clk_midi.event = function(data) | |
clk:process_midi(data) | |
end | |
local notes_off_metro = metro.init() | |
local arc_redraw_metro = metro.init() | |
function build_scale() | |
notes = MusicUtil.generate_scale_of_length(params:get("root_note"), params:get("scale_mode"), 16) | |
local num_to_add = 16 - #notes | |
for i = 1, num_to_add do | |
table.insert(notes, notes[16 - num_to_add]) | |
end | |
end | |
local function all_notes_off() | |
if (params:get("output") == 2 or params:get("output") == 3) then | |
for _, a in pairs(active_notes) do | |
midi_out_device:note_off(a, nil, midi_out_channel) | |
end | |
end | |
active_notes = {} | |
end | |
local function morph(loop, which) | |
for i=1,loop.length do | |
if loop.data[i] > 0 then | |
set_loop_data(which, i, util.clamp(loop.data[i]+math.floor(math.random()*3)-1,1,8)) | |
end | |
end | |
end | |
local function random() | |
for i=1,one.length do set_loop_data("one", i, math.floor(math.random()*9)) end | |
for i=1,two.length do set_loop_data("two", i, math.floor(math.random()*9)) end | |
end | |
local function step() | |
all_notes_off() | |
one.pos = one.pos + 1 | |
if one.pos > one.length then one.pos = 1 end | |
two.pos = two.pos + 1 | |
if two.pos > two.length then two.pos = 1 end | |
if one.data[one.pos] > 0 then | |
local note_num = notes[one.data[one.pos]+two.data[two.pos]] | |
local freq = MusicUtil.note_num_to_freq(note_num) | |
-- Trig Probablility | |
if math.random(100) <= params:get("probability") then | |
-- Audio engine out | |
if params:get("output") == 1 or params:get("output") == 3 then | |
engine.noteOn(1, freq, vel) | |
elseif params:get("output") == 4 then | |
crow.output[1].volts = (note_num-60)/12 | |
crow.output[2]() | |
elseif params:get("output") == 5 then | |
crow.ii.jf.play_note((note_num-60)/12,5) | |
elseif params:get("output") == 6 then | |
crow.output[1].volts = (note_num-60)/12 | |
crow.output[2]() | |
crow.ii.jf.play_note((note_num-60)/12,5) | |
end | |
-- MIDI out | |
if (params:get("output") == 2 or params:get("output") == 3) then | |
midi_out_device:note_on(note_num, 96, midi_out_channel) | |
table.insert(active_notes, note_num) | |
-- Note off timeout | |
if params:get("note_length") < 4 then | |
notes_off_metro:start((60 / clk.bpm / clk.steps_per_beat / 4) * params:get("note_length"), 1) | |
end | |
end | |
end | |
end | |
if params:get("crow_clock_out") == 2 then crow.output[4]() end | |
if g then | |
gridredraw() | |
end | |
redraw() | |
end | |
local function stop() | |
all_notes_off() | |
end | |
local function crow_init() | |
local crow_tap = 0 | |
local crow_deltatap = 1 | |
crow.input[1].mode("change", 1, 0.05, "rising") | |
crow.input[1].change = function(s) | |
if params:get("crow_clock_input") ~= 2 then | |
morph(one, "one") | |
morph(two, "two") | |
else | |
step() | |
local crow_tap1 = util.time() | |
crow_deltatap = crow_tap1 - crow_tap | |
crow_tap = crow_tap1 | |
local crow_tap_tempo = (60/crow_deltatap)/4 | |
params:set("bpm",math.floor(crow_tap_tempo+0.5)) | |
end | |
end | |
crow.input[2].mode("change", 1, 0.05, "rising") | |
crow.input[2].change = function() | |
if params:get("crow_clock_input") ~= 3 then | |
random() | |
else | |
step() | |
end | |
end | |
if params:get("output") == 4 then | |
crow.output[2].action = "{to(5,0),to(0,0.25)}" | |
end | |
end | |
norns.crow.add = function() | |
norns.crow.init() -- initialize norns system & clear crow | |
crow_init() -- initialize script settings for crow | |
end | |
function init() | |
for i = 1, #MusicUtil.SCALES do | |
table.insert(scale_names, string.lower(MusicUtil.SCALES[i].name)) | |
end | |
midi_out_device = midi.connect(1) | |
--midi_out_device.event = function() end | |
clk.on_step = step | |
clk.on_stop = stop | |
clk.on_select_internal = function() | |
clk:start() | |
params:set("crow_clock_input",1) | |
end | |
clk.on_select_midi = function() | |
one.pos = 0 | |
two.pos = 0 | |
params:set("crow_clock_input",1) | |
end | |
clk.on_select_crow = function() | |
params:set("crow_clock_input",2) | |
end | |
clk:add_clock_params() | |
params:set("bpm", 91) | |
params:add{type = "trigger", id = "clear_crow", name = "reset/clear crow [K3]", action = function() | |
norns.crow.init() -- resets norns.crow & triggers crow.reset() | |
end} | |
notes_off_metro.event = all_notes_off | |
arc_redraw_metro.event = arcredraw | |
arc_redraw_metro:start(1 / 60) | |
params:add{type = "option", id = "output", name = "output", | |
options = options.OUTPUT, | |
action = function(value) | |
all_notes_off() | |
if value == 4 then crow.output[2].action = "{to(5,0),to(0,0.25)}" | |
elseif value == 5 or value == 6 then | |
crow.ii.pullup(true) | |
crow.ii.jf.mode(1) | |
end | |
end} | |
params:add{type = "trigger", id = "reset_jf_ii", name = "reset JF [K3]", action = function() | |
crow.ii.jf.mode(0) | |
end} | |
params:add{type = "number", id = "midi_out_device", name = "midi out device", | |
min = 1, max = 4, default = 1, | |
action = function(value) midi_out_device = midi.connect(value) end} | |
params:add{type = "number", id = "midi_out_channel", name = "midi out channel", | |
min = 1, max = 16, default = 1, | |
action = function(value) | |
all_notes_off() | |
midi_out_channel = value | |
end} | |
params:add_separator() | |
params:add{type = "option", id = "step_length", name = "step length", options = options.STEP_LENGTH_NAMES, default = 8, | |
action = function(value) | |
clk.ticks_per_step = 96 / options.STEP_LENGTH_DIVIDERS[value] | |
clk.steps_per_beat = options.STEP_LENGTH_DIVIDERS[value] / 4 | |
clk:bpm_change(clk.bpm) | |
end} | |
params:add{type = "option", id = "note_length", name = "note length", | |
options = {"25%", "50%", "75%", "100%"}, | |
default = 4} | |
params:add{type = "option", id = "scale_mode", name = "scale mode", | |
options = scale_names, default = 5, | |
action = function() build_scale() end} | |
params:add{type = "number", id = "root_note", name = "root note", | |
min = 0, max = 127, default = 60, formatter = function(param) return MusicUtil.note_num_to_name(param:get(), true) end, | |
action = function() build_scale() end} | |
params:add{type = "number", id = "probability", name = "probability", | |
min = 0, max = 100, default = 100,} | |
params:add_separator() | |
passersby.add_params() | |
crow_init() | |
clk:start() | |
hs.init() | |
add_pattern_params() | |
params:default() | |
end | |
function g.key(x, y, z) | |
local grid_h = g.rows | |
if z > 0 then | |
if (grid_h == 8 and edit_ch == 1) or (grid_h == 16 and y <= 8) then | |
if one.data[x] == 9-y then | |
set_loop_data("one", x, 0) | |
else | |
set_loop_data("one", x, 9-y) | |
end | |
end | |
if (grid_h == 8 and edit_ch == 2) or (grid_h == 16 and y > 8) then | |
if grid_h == 16 then y = y - 8 end | |
if two.data[x] == 9-y then | |
set_loop_data("two", x, 0) | |
else | |
set_loop_data("two", x, 9-y) | |
end | |
end | |
gridredraw() | |
redraw() | |
end | |
end | |
function gridredraw() | |
if not g then | |
return | |
end | |
local grid_h = g.rows | |
g:all(0) | |
if edit_ch == 1 or grid_h == 16 then | |
for x = 1, 16 do | |
if one.data[x] > 0 then g:led(x, 9-one.data[x], 5) end | |
end | |
if one.pos > 0 and one.data[one.pos] > 0 then | |
g:led(one.pos, 9-one.data[one.pos], 15) | |
else | |
g:led(one.pos, 1, 3) | |
end | |
end | |
if edit_ch == 2 or grid_h == 16 then | |
local y_offset = 0 | |
if grid_h == 16 then y_offset = 8 end | |
for x = 1, 16 do | |
if two.data[x] > 0 then g:led(x, 9-two.data[x] + y_offset, 5) end | |
end | |
if two.pos > 0 and two.data[two.pos] > 0 then | |
g:led(two.pos, 9-two.data[two.pos] + y_offset, 15) | |
else | |
g:led(two.pos, 1 + y_offset, 3) | |
end | |
end | |
g:refresh() | |
end | |
function a.delta(n, d) | |
-- "peak", "decay", "fm low", | |
if n == 1 then | |
params:delta("decay", 0.2 * d) | |
elseif n == 2 then | |
-- cutoff is exponential; we use the controlspec | |
-- for adjustments to feel natural | |
local now = passersby.specs.PEAK:unmap(params:get("peak")) | |
local delta = 0.002 | |
params:set("peak", passersby.specs.PEAK:map(now + delta * d)) | |
elseif n == 3 then | |
params:delta("fm_low_amount", 0.2 * d) | |
elseif n == 4 then | |
params:delta("reverb_mix", 0.2 * d) | |
end | |
end | |
function arcredraw() | |
if not a then | |
return | |
end | |
a:all(0) | |
a:led(1, 1, 4) | |
a:led(2, 1, 4) | |
a:led(3, 1, 4) | |
a:led(4, 1, 4) | |
-- ARC 1 | |
local percentage = 64 * passersby.specs.DECAY:unmap(params:get("decay")) | |
for i=1,64 do | |
if percentage >= i then | |
a:led(1, (i-1) % 64 + 1, 15) | |
end | |
end | |
-- ARC 2 | |
percentage = 64 * passersby.specs.PEAK:unmap(params:get("peak")) | |
for i=1,64 do | |
if percentage >= i then | |
a:led(2, (i-1) % 64 + 1, 15) | |
end | |
end | |
-- ARC 3 | |
percentage = 64 * passersby.specs.FM_LOW_AMOUNT:unmap(params:get("fm_low_amount")) | |
for i=1,64 do | |
if percentage >= i then | |
a:led(3, (i-1) % 64 + 1, 15) | |
end | |
end | |
-- ARC 4 | |
percentage = 64 * passersby.specs.REVERB_MIX:unmap(params:get("reverb_mix")) | |
for i=1,64 do | |
if percentage >= i then | |
a:led(4, (i-1) % 64 + 1, 15) | |
end | |
end | |
a:refresh() | |
end | |
function enc(n, delta) | |
if n==1 then | |
mode = util.clamp(mode+delta,1,4) | |
elseif mode == 1 then --step | |
if n==2 then | |
if alt then | |
params:delta("probability", delta) | |
else | |
local p = (edit_ch == 1) and one.length or two.length | |
edit_pos = util.clamp(edit_pos+delta,1,p) | |
end | |
elseif n==3 then | |
if edit_ch == 1 then | |
params:delta("one_data_"..edit_pos, delta) | |
else | |
params:delta("two_data_"..edit_pos, delta) | |
end | |
end | |
elseif mode == 2 then --loop | |
if n==2 then | |
params:delta("one_length", delta) | |
elseif n==3 then | |
params:delta("two_length", delta) | |
end | |
elseif mode == 3 then --sound | |
if n==2 then | |
params:delta(snd_params[snd_sel], delta) | |
elseif n==3 then | |
params:delta(snd_params[snd_sel+1], delta) | |
end | |
elseif mode == 4 then --option | |
if n==2 then | |
if alt==false then | |
params:delta("bpm", delta) | |
else | |
params:delta("step_length",delta) | |
end | |
elseif n==3 then | |
if alt==false then | |
params:delta("root_note", delta) | |
else | |
params:delta("scale_mode", delta) | |
end | |
end | |
end | |
redraw() | |
end | |
function key(n,z) | |
if n==1 then | |
alt = z==1 | |
elseif mode == 1 then --step | |
if n==2 and z==1 then | |
if not alt==true then | |
-- toggle edit | |
if edit_ch == 1 then | |
edit_ch = 2 | |
if edit_pos > two.length then edit_pos = two.length end | |
else | |
edit_ch = 1 | |
if edit_pos > one.length then edit_pos = one.length end | |
end | |
else | |
-- clear | |
for i=1,one.length do params:set("one_data_"..i, 0) end | |
for i=1,two.length do params:set("two_data_"..i, 0) end | |
end | |
elseif n==3 and z==1 then | |
if not alt==true then | |
-- morph | |
if edit_ch == 1 then morph(one, "one") else morph(two, "two") end | |
else | |
-- random | |
random() | |
gridredraw() | |
end | |
end | |
elseif mode == 2 then --loop | |
if n==2 and z==1 then | |
one.pos = 0 | |
two.pos = 0 | |
if alt==true then clk:reset() end | |
elseif n==3 and z==1 then | |
one.pos = math.floor(math.random()*one.length) | |
two.pos = math.floor(math.random()*two.length) | |
end | |
elseif mode == 3 then --sound | |
if n==2 and z==1 then | |
snd_sel = util.clamp(snd_sel - 2,1,NUM_SND_PARAMS-1) | |
elseif n==3 and z==1 then | |
snd_sel = util.clamp(snd_sel + 2,1,NUM_SND_PARAMS-1) | |
end | |
elseif mode == 4 then --option | |
if n==2 then | |
elseif n==3 then | |
end | |
end | |
redraw() | |
end | |
function redraw() | |
screen.clear() | |
screen.line_width(1) | |
screen.aa(0) | |
-- edit point | |
if mode==1 then | |
screen.move(26 + edit_pos*6, edit_ch==1 and 33 or 63) | |
screen.line_rel(4,0) | |
screen.level(15) | |
if alt then | |
screen.move(0, 30) | |
screen.level(1) | |
screen.text("prob") | |
screen.move(0, 45) | |
screen.level(15) | |
screen.text(params:get("probability")) | |
end | |
screen.stroke() | |
end | |
-- loop lengths | |
screen.move(32,30) | |
screen.line_rel(one.length*6-2,0) | |
screen.move(32,60) | |
screen.line_rel(two.length*6-2,0) | |
screen.level(mode==2 and 6 or 1) | |
screen.stroke() | |
-- steps | |
for i=1,one.length do | |
screen.move(26 + i*6, 30 - one.data[i]*3) | |
screen.line_rel(4,0) | |
screen.level(i == one.pos and 15 or ((edit_ch == 1 and one.data[i] > 0) and 4 or (mode==2 and 6 or 1))) | |
screen.stroke() | |
end | |
for i=1,two.length do | |
screen.move(26 + i*6, 60 - two.data[i]*3) | |
screen.line_rel(4,0) | |
screen.level(i == two.pos and 15 or ((edit_ch == 2 and two.data[i] > 0) and 4 or (mode==2 and 6 or 1))) | |
screen.stroke() | |
end | |
screen.level(4) | |
screen.move(0,10) | |
screen.text(mode_names[mode]) | |
if mode==3 then | |
screen.level(1) | |
screen.move(0,30) | |
screen.text(snd_names[snd_sel]) | |
screen.level(15) | |
screen.move(0,40) | |
screen.text(params:string(snd_params[snd_sel])) | |
screen.level(1) | |
screen.move(0,50) | |
screen.text(snd_names[snd_sel+1]) | |
screen.level(15) | |
screen.move(0,60) | |
screen.text(params:string(snd_params[snd_sel+1])) | |
elseif mode==4 then | |
screen.level(1) | |
screen.move(0,30) | |
screen.text(alt==false and "bpm" or "div") | |
screen.level(15) | |
screen.move(0,40) | |
screen.text(alt==false and params:get("bpm") or params:string("step_length")) | |
screen.level(1) | |
screen.move(0,50) | |
screen.text(alt==false and "root" or "scale") | |
screen.level(15) | |
screen.move(0,60) | |
screen.text(alt==false and params:string("root_note") or params:string("scale_mode")) | |
end | |
screen.update() | |
end | |
function cleanup() | |
clk:stop() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment