Skip to content
Permalink
e22d542d59
Go to file
 
 
Cannot retrieve contributors at this time
661 lines (623 sloc) 18.2 KB
-- less concepts:
-- 1d cellular automata sequencer
-- v1.0.0 @dan_derks
-- llllllll.co/t/less-concepts/
--
-- hold key 1: start/stop seq.
-- enc 1: scale
-- (hold key 2) enc 2: seed
-- (hold key 2) enc 3: rule
--
-- press key 3: menu nav.
-- enc 2 + 3: change menu val.
-- params: midi, +/- st, timbre
--
-- plug in grid
-- (1,1) to (8,2): bits
-- (10,1) to (16,2): octaves
-- (1,3) to (16,3): randomize
-- (1,4) to (16,5): low
-- (1,6) to (16,7): high
-- (16,8): take snapshot
-- (15,8): clear snapshots
-- (1,8) to (8,8): snapshots
--
-- seek.
-- think.
-- discover.
local seed = 0
local rule = 0
local next_seed = nil
local new_low = 1
local new_high = 14
local coll = 1
local new_seed = seed
local new_rule = rule
local automatic = 0
local KEY2 = false
local KEY3 = false
local KEY3_toggle = 0
local v1_bit = 0
local v2_bit = 0
local v1_octave = 0
local v2_octave = 0
local ch_1 = 1
local ch_2 = 1
local semi = 0
local presets = {}
local preset_count = 0
local active_notes_v1 = {}
local active_notes_v2 = {}
local beatclock = require 'beatclock'
local clk = beatclock.new()
clk_midi = midi.connect()
clk_midi.event = clk.process_midi
engine.name = "Passersby"
passersby = include "passersby/lib/passersby_engine"
-- this section is all maths + computational events
-- maths: translate the seed integer to binary
local function seed_to_binary()
seed_as_binary = {}
for i = 0,7 do
table.insert(seed_as_binary, (seed & (2 ^ i)) >> i)
end
end
-- maths: translate the rule integer to binary
local function rule_to_binary()
rule_as_binary = {}
for i = 0,7 do
table.insert(rule_as_binary, (rule & (2 ^ i)) >> i)
end
end
-- maths: basic compare function, used in bang()
local function compare (s, n)
if type(s) == type(n) then
if type(s) == "table" then
for loop=1, 3 do
if compare (s[loop], n[loop]) == false then
return false
end
end
return true
else
return s == n
end
end
return false
end
-- maths: scale seeds to the note pool + range selected
local function scale(lo, hi, received)
scaled = math.floor(((((received-1) / (256-1)) * (hi - lo) + lo)))
end
-- pack the seeds into clusters, compare these against neighborhoods to determine gates in iterate()
local function bang()
redraw()
seed_to_binary()
rule_to_binary()
seed_pack1 = {seed_as_binary[1], seed_as_binary[8], seed_as_binary[7]}
seed_pack2 = {seed_as_binary[8], seed_as_binary[7], seed_as_binary[6]}
seed_pack3 = {seed_as_binary[7], seed_as_binary[6], seed_as_binary[5]}
seed_pack4 = {seed_as_binary[6], seed_as_binary[5], seed_as_binary[4]}
seed_pack5 = {seed_as_binary[5], seed_as_binary[4], seed_as_binary[3]}
seed_pack6 = {seed_as_binary[4], seed_as_binary[3], seed_as_binary[2]}
seed_pack7 = {seed_as_binary[3], seed_as_binary[2], seed_as_binary[1]}
seed_pack8 = {seed_as_binary[2], seed_as_binary[1], seed_as_binary[8]}
neighborhoods1 = {1,1,1}
neighborhoods2 = {1,1,0}
neighborhoods3 = {1,0,1}
neighborhoods4 = {1,0,0}
neighborhoods5 = {0,1,1}
neighborhoods6 = {0,1,0}
neighborhoods7 = {0,0,1}
neighborhoods8 = {0,0,0}
local function com (seed_packN, lshift, mask)
if compare (seed_packN,neighborhoods1) then
return (rule_as_binary[8] << lshift) & mask
elseif compare (seed_packN, neighborhoods2) then
return (rule_as_binary[7] << lshift) & mask
elseif compare (seed_packN, neighborhoods3) then
return (rule_as_binary[6] << lshift) & mask
elseif compare (seed_packN, neighborhoods4) then
return (rule_as_binary[5] << lshift) & mask
elseif compare (seed_packN, neighborhoods5) then
return (rule_as_binary[4] << lshift) & mask
elseif compare (seed_packN, neighborhoods6) then
return (rule_as_binary[3] << lshift) & mask
elseif compare (seed_packN, neighborhoods7) then
return (rule_as_binary[2] << lshift) & mask
elseif compare (seed_packN, neighborhoods8) then
return (rule_as_binary[1] << lshift) & mask
else return (0 << lshift) & mask
end
end
out1 = com(seed_pack1, 7, 128)
out2 = com(seed_pack2, 6, 64)
out3 = com(seed_pack3, 5, 32)
out4 = com(seed_pack4, 4, 16)
out5 = com(seed_pack5, 3, 8)
out6 = com(seed_pack6, 2, 4)
out7 = com(seed_pack7, 1, 2)
out8 = com(seed_pack8, 0, 1)
next_seed = out1+out2+out3+out4+out5+out6+out7+out8
end
local function notes_off_v1()
for i=1,#active_notes_v1 do
m:note_off(active_notes_v1[i],0,ch_1)
end
active_notes_v1 = {}
end
local function notes_off_v2()
for i=1,#active_notes_v2 do
m:note_off(active_notes_v2[i],0,ch_2)
end
active_notes_v2 = {}
end
-- if user-defined bit in the binary version of a seed equals 1, then note event [aka, bit-wise gating]
local function iterate()
notes_off_v1()
notes_off_v2()
seed = next_seed
bang()
scale(new_low,new_high,seed)
if seed_as_binary[v1_bit] == 1 then
engine.noteOn(1,midi_to_hz((notes[coll][scaled])+(48+(v1_octave * 12)+semi)),127)
m:note_on((notes[coll][scaled])+(36+(v1_octave*12)+semi),127,ch_1)
table.insert(active_notes_v1,(notes[coll][scaled])+(36+(v1_octave*12)+semi))
end
if seed_as_binary[v2_bit] == 1 then
engine.noteOn(2,midi_to_hz((notes[coll][scaled])+(48+(v2_octave * 12)+semi)),127)
m:note_on((notes[coll][scaled])+(36+(v2_octave*12)+semi),127,ch_2)
table.insert(active_notes_v2,(notes[coll][scaled])+(36+(v2_octave*12)+semi))
end
redraw()
grid_redraw()
end
-- convert midi note to hz for Passersby engine
function midi_to_hz(note)
return (440 / 32) * (2 ^ ((note - 9) / 12))
end
-- allow user to define the MIDI channel voice 1 sends on
local function midi_vox_1(channel)
ch_1 = channel
end
-- allow user to define the MIDI channel voice 2 sends on
local function midi_vox_2(channel)
ch_2 = channel
end
-- allow user to define the transposition of voice 1 and voice 2, simultaneous changes to MIDI and Passersby engine
local function transpose(semitone)
semi = semitone
end
-- everything that happens when the script is first loaded
function init()
math.randomseed(os.time())
math.random(); math.random(); math.random()
seed_to_binary()
rule_to_binary()
g = grid.connect()
g:led(new_low,4,15)
g:led(new_high,6,15)
g:led(v1_octave+13,1,15)
g:led(v2_octave+13,2,15)
grid_redraw()
g:refresh()
m = midi.connect()
clk.on_step = function() iterate() end
clk.on_select_internal = function() clk:start() end
clk.on_select_external = function() print("external") end
clk:add_clock_params()
params:add_number("midi ch vox 1", "midi ch vox 1", 1,16,1)
params:set_action("midi ch vox 1", function (x) midi_vox_1(x) end)
params:add_number("midi ch vox 2", "midi ch vox 2", 1,16,1)
params:set_action("midi ch vox 2", function (x) midi_vox_2(x) end)
params:add_number("global transpose", "global transpose", -24,24,0)
params:set_action("global transpose", function (x) transpose(x) end)
params:add_separator()
passersby.add_params()
bang()
notes = { {0,2,4,5,7,9,11,12,14,16,17,19,21,23,24,26,28,29,31,33,35,36,38,40,41,43,45,47,48},
{0,2,3,5,7,8,10,12,14,15,17,19,20,22,24,26,27,29,31,32,34,36,38,39,41,43,44,46,48},
{0,2,3,5,7,9,10,12,14,15,17,19,21,22,24,26,27,29,31,33,34,36,38,39,41,43,45,46,48},
{0,1,3,5,7,8,10,12,13,15,17,19,20,22,24,25,27,29,31,32,34,36,37,39,41,43,44,46,48},
{0,2,4,6,7,9,11,12,14,16,18,19,21,23,24,26,28,30,31,33,35,36,38,40,42,43,45,47,48},
{0,2,4,5,7,9,10,12,14,16,17,19,21,22,24,26,28,29,31,33,34,36,38,40,41,43,45,46,48},
{0,3,5,7,10,12,15,17,19,22,24,27,29,31,34,36,39,41,43,46,48,51,53,55,58,60,63,65,67},
{0,2,4,7,9,12,14,16,19,21,24,26,28,31,33,36,38,40,43,45,48,50,52,55,57,60,62,64,67},
{0,2,5,7,10,12,14,17,19,22,24,26,29,31,34,36,38,41,43,46,48,50,53,55,58,60,62,65,67},
{0,3,5,8,10,12,15,17,20,22,24,27,29,32,34,36,39,41,44,46,48,51,53,56,58,60,63,65,68},
{0,2,5,7,9,12,14,17,19,21,24,26,29,31,33,36,38,41,43,45,48,50,53,55,57,60,62,65,67},
{0,1,3,6,7,8,11,12,13,15,18,19,20,23,24,25,27,30,31,32,35,36,37,39,42,43,44,47,48},
{0,1,4,6,7,8,11,12,13,16,18,19,20,23,24,25,28,30,31,32,35,36,37,40,42,43,44,47,48},
{0,1,4,6,7,9,11,12,13,16,18,19,21,23,24,25,28,30,31,33,35,36,37,40,42,43,45,47,48},
{0,1,4,5,7,8,11,12,13,16,17,19,20,23,24,25,28,29,31,32,35,36,37,40,41,43,44,47,48},
{0,1,4,5,7,9,10,12,13,16,17,19,21,22,24,25,28,29,31,33,35,36,37,40,41,43,45,47,48},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28} }
names = {"ionian","aeolian", "dorian", "phrygian", "lydian", "mixolydian", "major_pent", "minor_pent", "shang", "jiao", "zhi", "todi", "purvi", "marva", "bhairav", "ahirbhairav", "chromatic"}
clk:stop()
end
-- this section is all hardware stuff
-- hardware: key interaction
function key(n,z)
if n == 1 and z == 1 then
automatic = automatic + 1
if automatic % 2 == 1 then
clk:start()
elseif automatic % 2 == 0 then
clk:stop()
end
end
if n == 2 and z == 1 then
KEY2 = true
bang()
redraw()
elseif n == 2 and z == 0 then
KEY2 = false
seed = new_seed
rule = new_rule
bang()
redraw()
end
if n == 3 and z == 1 then
KEY3 = true
KEY3_toggle = KEY3_toggle + 1
bang()
redraw()
elseif n == 3 and z == 0 then
KEY3 = false
bang()
redraw()
end
end
-- hardware: encoder interaction
function enc(n,d)
if n == 1 and KEY3 == false and KEY2 == false then
coll = math.min(17,(math.max(coll + d,1)))
end
if n == 2 and KEY3 == false and KEY2 == false and KEY3_toggle % 3 == 1 then
new_low = math.min(29,(math.max(new_low + d,1)))
for i=1,16 do
g:led(i,4,0)
g:led(i,5,0)
if new_low < 17 then
g:led(new_low,4,15)
elseif new_low > 16 then
g:led(new_low-16,5,15)
end
g:refresh()
end
elseif n == 2 and KEY3 == false and KEY2 == false and KEY3_toggle % 3 == 2 then
v1_octave = math.min(3,(math.max(v1_octave + d,-3)))
for i=10,16 do
g:led(i,1,0)
g:led(v1_octave+13,1,15)
g:refresh()
end
elseif n == 2 and KEY2 == false and KEY3_toggle %3 == 0 then
v1_bit = math.min(8,(math.max(v1_bit + d,0)))
elseif n == 2 and KEY2 then
new_seed = math.min(255,(math.max(new_seed + d,0)))
end
if n == 3 and KEY3 == false and KEY2 == false and KEY3_toggle % 3 == 1 then
new_high = math.min(29,(math.max(new_high + d,1)))
for i=1,16 do
g:led(i,6,0)
g:led(i,7,0)
if new_high < 17 then
g:led(new_high,6,15)
elseif new_high > 16 then
g:led(new_high-16,7,15)
end
g:refresh()
end
elseif n == 3 and KEY3 == false and KEY2 == false and KEY3_toggle % 3 == 2 then
v2_octave = math.min(3,(math.max(v2_octave + d,-3)))
for i=10,16 do
g:led(i,2,0)
g:led(v2_octave+13,2,15)
g:refresh()
end
elseif n == 3 and KEY2 == false and KEY3_toggle %3 == 0 then
v2_bit = math.min(8,(math.max(v2_bit + d,0)))
elseif n == 3 and KEY2 then
new_rule = math.min(255,(math.max(new_rule + d,0)))
end
redraw()
end
-- hardware: screen redraw
function redraw()
screen.clear()
screen.level(15)
screen.move(0,10)
screen.text("seed: "..new_seed.." // rule: "..new_rule)
screen.move(0,20)
if KEY3_toggle % 3 == 0 then
screen.level(15)
screen.text("vox 1 bit: "..v1_bit.." // vox 2 bit: "..v2_bit)
elseif KEY3_toggle % 3 == 1 or 2 then
screen.level(1)
screen.text("vox 1 bit: "..v1_bit.." // vox 2 bit: "..v2_bit)
end
screen.move(0,30)
if KEY3_toggle % 3 == 1 then
screen.level(15)
screen.text("low: "..new_low.." // high: "..new_high)
elseif KEY3_toggle % 3 == 2 or 0 then
screen.level(1)
screen.text("low: "..new_low.." // high: "..new_high)
end
screen.move(0,40)
if KEY3_toggle % 3 == 2 then
screen.level(15)
screen.text("vox 1 oct: "..v1_octave)
screen.move(57,40)
screen.text("// vox 2 oct: "..v2_octave)
elseif KEY3_toggle % 3 == 0 or 1 then
screen.level(1)
screen.text("vox 1 oct: "..v1_octave)
screen.move(57,40)
screen.text("// vox 2 oct: "..v2_octave)
end
screen.level(15)
screen.move(0,50)
screen.text("scale: "..names[coll])
screen.move(0,60)
screen.text("current: "..seed)
screen.move(60,60)
screen.text("next: "..next_seed)
screen.update()
end
-- hardware: grid connect
g = grid.connect()
-- hardware: grid event (eg 'what happens when a button is pressed')
g.key = function(x,y,z)
if y == 1 and x < 9 then
g:led(x,y,z*15)
g:refresh()
v1_bit = x
bang()
redraw()
end
if y == 1 and x > 9 and z == 1 then
for i=10,16 do
g:led(i,1,0)
end
g:led(x,y,z*15)
v1_octave = x-13
redraw()
g:refresh()
end
if y == 2 and x < 9 then
g:led(x,y,z*15)
g:refresh()
v2_bit = x
bang()
redraw()
end
if y == 2 and x > 9 and z == 1 then
for i=10,16 do
g:led(i,2,0)
end
g:led(x,y,z*15)
v2_octave = x-13
redraw()
g:refresh()
end
if y == 4 and z == 1 then
for i=1,16 do
g:led(i,4,0)
g:led(i,5,0)
end
g:led(x,y,z*15)
new_low = x
redraw()
g:refresh()
end
if y == 5 and z == 1 then
for i=1,16 do
g:led(i,4,0)
g:led(i,5,0)
end
g:led(x,y,z*15)
new_low = x+16
redraw()
g:refresh()
end
if y == 6 and z == 1 then
for i=1,16 do
g:led(i,6,0)
g:led(i,7,0)
end
g:led(x,y,z*15)
new_high = x
redraw()
g:refresh()
end
if y == 7 and z == 1 then
for i=1,16 do
g:led(i,6,0)
g:led(i,7,0)
end
g:led(x,y,z*15)
new_high = x+16
redraw()
g:refresh()
end
if y == 3 and z == 1 then
if x == 1 then
seed = math.random(0,255)
new_seed = seed
elseif x == 2 then
rule = math.random(0,255)
new_rule = rule
elseif x == 4 then
v1_bit = math.random(0,8)
elseif x == 5 then
v2_bit = math.random(0,8)
elseif x == 7 or x == 8 or x == 10 or x == 11 then
if x == 7 then
new_low = math.random(1,29)
end
if x == 8 then
new_high = math.random(1,29)
end
if x == 10 then
v1_octave = math.random(-2,2)
end
if x == 11 then
v2_octave = math.random(-2,2)
end
g:all(0)
g:led(v1_octave+13,1,15)
g:led(v2_octave+13,2,15)
if new_low < 17 then
g:led(new_low,4,15)
else
g:led(new_low-16,5,15)
end
if new_high < 17 then
g:led(new_high,6,15)
else
g:led(new_high-16,7,15)
end
elseif x == 10 then
v1_octave = math.random(-2,2)
elseif x == 11 then
v2_octave = math.random(-2,2)
elseif x == 16 then
randomize_all()
end
bang()
redraw()
grid_redraw()
g:refresh()
end
if y == 8 and z == 1 then
if x < 9 and x < preset_count+1 then
preset_unpack(x)
elseif x == 15 then
presets = {}
preset_pool = {}
preset_count = 0
for i=1,8 do
g:led(i,8,0)
end
grid_redraw()
elseif x == 16 then
preset_pack()
if preset_count < 8 then
preset_count = preset_count + 1
grid_redraw()
end
end
end
end
-- hardware: grid redraw
function grid_redraw()
for i=1,8 do
g:led(i,1,0)
g:led(i,2,0)
end
if seed_as_binary[v1_bit] == 1 then
g:led(v1_bit,1,15)
end
if seed_as_binary[v2_bit] == 1 then
g:led(v2_bit,2,15)
end
g:led(1,3,4)
g:led(2,3,4)
g:led(4,3,4)
g:led(5,3,4)
g:led(7,3,4)
g:led(8,3,4)
g:led(10,3,4)
g:led(11,3,4)
g:led(16,3,4)
for i=1,preset_count do
g:led(i,8,6)
end
g:led(15,8,2)
g:led(16,8,6)
g:led(v1_octave+13,1,15)
g:led(v2_octave+13,2,15)
g:refresh()
end
-- this section is all performative stuff
-- randomize all maths paramaters (does not affect scale or engine, for ease of use)
function randomize_all()
seed = math.random(0,255)
new_seed = seed
rule = math.random(0,255)
new_rule = rule
v1_bit = math.random(0,8)
v2_bit = math.random(0,8)
new_low = math.random(1,29)
new_high = math.random(1,29)
v1_octave = math.random(-2,2)
v2_octave = math.random(-2,2)
bang()
redraw()
g:all(0)
g:led(v1_octave+13,1,15)
g:led(v2_octave+13,2,15)
if new_low < 17 then
g:led(new_low,4,15)
elseif new_low > 16 then
g:led(new_low-16,5,15)
end
if new_high < 17 then
g:led(new_high,6,15)
elseif new_high > 16 then
g:led(new_high-16,7,15)
end
grid_redraw()
g:refresh()
end
-- pack all maths parameters into a volatile preset
function preset_pack()
table.insert(presets, new_seed)
table.insert(presets, new_rule)
table.insert(presets, v1_bit)
table.insert(presets, v2_bit)
table.insert(presets, new_low)
table.insert(presets, new_high)
table.insert(presets, v1_octave)
table.insert(presets, v2_octave)
preset_pool = { {presets[1],presets[2],presets[3],presets[4],presets[5],presets[6],presets[7],presets[8]},
{presets[9],presets[10],presets[11],presets[12],presets[13],presets[14],presets[15],presets[16]},
{presets[17],presets[18],presets[19],presets[20],presets[21],presets[22],presets[23],presets[24]},
{presets[25],presets[26],presets[27],presets[28],presets[29],presets[30],presets[31],presets[32]},
{presets[33],presets[34],presets[35],presets[36],presets[37],presets[38],presets[39],presets[40]},
{presets[41],presets[42],presets[43],presets[44],presets[45],presets[46],presets[47],presets[48]},
{presets[49],presets[50],presets[51],presets[52],presets[53],presets[54],presets[55],presets[56]},
{presets[57],presets[58],presets[59],presets[60],presets[61],presets[62],presets[63],presets[64]} }
end
-- switch all current maths parameters to a volatile preset
function preset_unpack(set)
seed = preset_pool[set][1]
new_seed = seed
rule = preset_pool[set][2]
new_rule = rule
v1_bit = preset_pool[set][3]
v2_bit = preset_pool[set][4]
new_low = preset_pool[set][5]
new_high = preset_pool[set][6]
v1_octave = preset_pool[set][7]
v2_octave = preset_pool[set][8]
bang()
redraw()
g:all(0)
g:led(v1_octave+13,1,15)
g:led(v2_octave+13,2,15)
if new_low < 17 then
g:led(new_low,4,15)
elseif new_low > 16 then
g:led(new_low-16,5,15)
end
if new_high < 17 then
g:led(new_high,6,15)
elseif new_high > 16 then
g:led(new_high-16,7,15)
end
grid_redraw()
g:refresh()
end