Skip to content

Commit

Permalink
Merge pull request #8 from monome/snippets
Browse files Browse the repository at this point in the history
Snippets & Public
  • Loading branch information
trentgill committed Jun 30, 2021
2 parents 64b4156 + 8aa5f4c commit eed1d75
Show file tree
Hide file tree
Showing 33 changed files with 1,304 additions and 329 deletions.
91 changes: 41 additions & 50 deletions boids.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@
-- in2: influence acceleration
-- out1-4: slewed voltage

-- params
follow = 0.75 -- input=1, free=0
pull = 0.5 -- to centre
avoid = 0.1 -- V threshold
sync = 1/20 -- dir attraction
limit = 0.05 -- max volts per timestep

timing = 0.02 -- calc speed
-- TODO refine ranges & apply 'expo' where appropriate
public.add('follow', 0.75, {0, 1}) -- input=1, free=0. influence of input[1] over centre of mass
public.add('pull', 0.5, {0, 1}) -- pull boids toward their centre of mass
public.add('avoid', 0.1, {0, 1}) -- voltage difference under which boids repel
public.add('sync', 1/20, {0, 1}) -- amount by which boids copy each other's direction
public.add('limit', 0.05, {0, 0.3}) -- limit boids instantaneous movement to reduce over-correction
public.add('timing', 0.02, {0.005, 0.2} -- timestep for simulation
, function(v) for n=1,4 do output[n].slew = n*2 end end) -- calc speed TODO use Hz not seconds?

boids = {}
count = 4
COUNT = 4 -- only first 4 are output

-- artificially provide a 'centre-of-mass'
function centring(b,c)
return (c - b.p) * pull
return (c - b.p) * public.pull
end

function avoidance(bs,b)
v = 0
for n=1,count do
local v = 0
for n=1,COUNT do
if bs[n] ~= b then -- ignore self
d = bs[n].p - b.p
if math.abs(d) < avoid then
local d = bs[n].p - b.p
if math.abs(d) < public.avoid then
v = v - d/2
end
end
Expand All @@ -36,67 +36,58 @@ function avoidance(bs,b)
end

function syncing(bs,b)
v = 0
for n=1,count do
local v = 0
for n=1,COUNT do
if bs[n] ~= b then -- ignore self
v = v + bs[n].v
end
end
v = v / (count-1)
return (v - b.v) * sync
v = v / (COUNT-1)
return (v - b.v) * public.sync
end

function findcentre(bs,c)
m = 0
for n=1,count do
local m = 0
for n=1,COUNT do
m = m + bs[n].p
end
m = m/count
return m + follow*(c-m)
m = m/COUNT
return m + public.follow*(c-m)
end

function move( bs, n, c, v )
mass = findcentre(bs,c)
b = bs[n]
v1 = centring(b,mass)
v2 = avoidance(bs,b)
v3 = syncing(bs,b)
b.v = b.v + v1 + v2 + v3
if b.v > limit then b.v = limit
elseif b.v < -limit then b.v = -limit end
local b = bs[n]
b.v = b.v + centring(b, findcentre(bs, c))
+ avoidance(bs, b)
+ syncing(bs, b)
if b.v > public.limit then b.v = public.limit
elseif b.v < -public.limit then b.v = -public.limit end
b.v = b.v * v
b.p = b.p + b.v
return b
end

function draw( b, n, v )
output[n].slew = v*1.1
output[n].volts = b.p
end

-- round-robin calculation
function step(c)
c = (c % count)+1
accel = input[2].volts
draw( boids[c], c, timing )
boids[c] = move( boids, c, input[1].volts, (accel+5.0)/5.0 )
end

function init_boids()
local bs = {}
for n=1,count do
for n=1,COUNT do
bs[n] = { p = math.random()*3.0 - 1.0
, v = 0
}
end
return bs
end

function boids_run(c)
local boids = init_boids()
while true do
c = (c % COUNT)+1 -- round-robin calculation
boids[c] = move( boids, c, input[1].volts, (input[2].volts+5.0)/5.0 )
if c <= 4 then output[c].volts = boids[c].p end -- apply updated voltage to output
clock.sleep(public.timing / COUNT) -- TODO try sleep(0) for maximum speed?
end
end

function init()
boids = init_boids()
mover = metro.init{ event = step
, time = timing/count
, count = -1
}
mover:start()
for n=1,4 do public.view.output[n]() end -- visualize location of all 4 boids
clock.run(boids_run)
end
42 changes: 42 additions & 0 deletions booleans.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--- boolean logic
-- output logic transfer functions are dynamically selected per channel
-- a state change on any input triggers update of all outputs

-- enumeration of the fnlist. least to most density of transfer
fenum = {'~|','>','<','&','~^','^','~&','<=','>=','|'}

public.add('f1','&',fenum)
public.add('f2','|',fenum)
public.add('f3','^',fenum)
public.add('f4','~^',fenum)

-- all the dynamic transfer fns between 2 inputs (ordering doesn't matter)
fnlist =
{ '~|' = function(a,b) return not (a or b) end
, '>' = function(a,b) return a>b end
, '<' = function(a,b) return a<b end
, '&' = function(a,b) return (a and b) end
, '~^' = function(a,b) return a==b end
, '^' = function(a,b) return a~=b end
, '~&' = function(a,b) return not (a and b) end
, '<=' = function(a,b) return a<=b end
, '>=' = function(a,b) return a>=b end
, '|' = function(a,b) return (a or b) end
}

function q(n) return input[n].volts > 1.0 end
function apply()
local a, b = q(1), q(2)
output[1].volts = fnlist[public.f1](a, b)
output[2].volts = fnlist[public.f2](a, b)
output[3].volts = fnlist[public.f3](a, b)
output[4].volts = fnlist[public.f4](a, b)
end

function init()
for n=1,2 do
input[n].mode('change')
input[n].change = apply -- update all outs on any state change
end
apply() -- initialize to current state
end
43 changes: 20 additions & 23 deletions clockdiv.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,29 @@
-- in2: division selector (see divs)
-- out1-4: divided outputs

function newdiv(tab)
for n=1,4 do
output[n].clock_div = tab[n]
end
end

-- choose your clock divisions
divs = { {5,7,11,13} -- -5V
, {3,5,7,11}
, {2,3,5,7} -- 0V
, {2,4,8,16}
, {4,8,16,32} -- +5V
}
public.add('win1', {5,7,11,13}, newdiv)
public.add('win2', {3,5,7,11}, newdiv)
public.add('win3', {2,3,5,7}, newdiv)
public.add('win4', {2,4,8,16}, newdiv)
public.add('win5', {4,8,16,32}, newdiv)

-- private vars
count = {1,1,1,1}
function init()
input[1].mode('change')
input[2].mode('none')
for n=1,4 do
output[n].slew = 0.001
end
WINDOWS = {public.win1, public.win2, public.win3, public.win4, public.win5}

function init()
input[1].mode('clock',1/4)
input[2].mode('window', {-3,-1,1,3})
for n=1,4 do
output[n]:clock(public.win3[n])
end
end

input[1].change = function(s)
sel = math.floor(input[2].volts/2 + 3.5)
if sel > 5 then sel = 5 elseif sel < 1 then sel = 1 end
if s then
for n=1,4 do
count[n] = (count[n] % divs[sel][n])+1
output[n].volts = count[n] <= (divs[sel][n]/2) and 5 or 0
end
end
input[2].window = function(win, dir)
newdiv(WINDOWS[win])
end
48 changes: 29 additions & 19 deletions cvdelay.lua
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
--- control voltage delay
-- input1: CV to delay
-- input2: 0v = capture, 5v = loop
-- input2: 0v = capture, 5v = loop (continuous)
-- output1-4: delay equaly spaced delay taps

LENGTH = 100 -- sets loop time
-- closer to LENGTH is shorter, closer to 0 is longer
taps = { 1
, 26
, 51
, 76
}
LENGTH = 1000 -- max loop time. MUST BE CONSTANT

public.add('tap1', 250, {1,LENGTH,'slider'})
public.add('tap2', 500, {1,LENGTH,'slider'})
public.add('tap3', 750, {1,LENGTH,'slider'})
public.add('tap4', LENGTH, {1,LENGTH,'slider'})
public.add('loop', 0.0, {0,1,'slider'})

--private vars
bucket = {}
write = 1
cv_mode = 0

function init()
input[1].mode( 'stream', 0.01 ) -- 100Hz fastest stable
for n=1,4 do output[n].slew = 0.01 end
for n=1,LENGTH do bucket[n] = 0 end
input[1].mode('stream', 0.001) -- 1kHz
for n=1,4 do output[n].slew = 0.002 end -- smoothing at nyquist
for n=1,LENGTH do bucket[n] = 0 end -- clear the buffer
end

input[1].stream = function(v)
c = input[2].volts / 4.5
function peek(tap)
local ix = (math.floor(write - tap - 1) % LENGTH) + 1
return bucket[ix]
end

function poke(v, ix)
local c = (input[2].volts / 4.5) + public.loop
c = (c < 0) and 0 or c
c = (c > 1) and 1 or c
bucket[write] = v + c*(bucket[write] - v)
bucket[ix] = v + c*(bucket[ix] - v)
end

input[1].stream = function(v)
output[1].volts = peek(public.tap1)
output[2].volts = peek(public.tap2)
output[3].volts = peek(public.tap3)
output[4].volts = peek(public.tap4)
poke(v, write)
write = (write % LENGTH) + 1
for n=1,4 do
taps[n] = (taps[n] % LENGTH) + 1
output[n].volts = bucket[math.floor(taps[n])]
end
end
62 changes: 32 additions & 30 deletions euclidean.lua
Original file line number Diff line number Diff line change
@@ -1,67 +1,69 @@
--- euclidean rhythms
--- 4 euclidean rhythm generators
-- sam wolk 2019.10.21
-- Create 4 euclidean rhythm generators
-- in1: clock
-- in2: reset
-- outs: euclidean rhythms

-- ER parameters for each channel.
lengths = {16,16,16,16}
fills = {4,5,9,12}
offsets = {0,0,0,0}
public.add('lengths', {16,16,16,16})
public.add('fills', {4,5,9,12})
public.add('offsets', {0,0,0,0})

-- Non-User Variables
locations = {-1,-1,-1,-1} --
-- private state
locations = {-1,-1,-1,-1}

-- ER function adapted from https://gist.github.com/vrld/b1e6f4cce7a8d15e00e4
-- k > Euclidean Fill
-- n > Euclidean Length
-- s > current step (0-indexed)
function er(k,n,s)
function er(fill, length, ix)
local r = {}
for i = 1,n do
r[i] = {i <= k}
-- place all filled slots at the start of the rhythm
for i=1,length do
r[i] = {i <= fill}
end
local function cat(i,k)
for _,v in ipairs(r[k]) do
r[i][#r[i]+1] = v
-- each element is now a table with either true or false

local function cat(t, dst, src)
-- copy all elements of t[src] to the end of t[dst] and remove t[src]
for _,v in ipairs(t[src]) do
table.insert(t[dst], v)
end
r[k] = nil
t[src] = nil
end

while #r > k do
for i = 1,math.min(k, #r-k) do
cat(i, #r)

-- interleave the empty slots until they are spread out evenly
while #r > fill do
for i=1,math.min(fill, #r-fill) do
cat(r, i, #r)
end
end

-- fold all lists down to a single one
while #r > 1 do
cat(#r-1, #r)
cat(r, #r-1, #r)
end

return r[1][s+1]
-- return boolean (and discard table)
return r[1][ix]
end

-- Use a trigger to advance ER counters and activate ASLs on hits
input[1].change = function(state)
for i=1,4 do
--- increment counters
locations[i] = ((locations[i]+1) % lengths[i])
locations[i] = (locations[i] + 1) % lengths[i]

-- get current location
local index = ((locations[i]+offsets[i]) % lengths[i])
-- fire asl if there is a hit
if er(fills[i],lengths[i],index) then
local index = (locations[i] + offsets[i]) % lengths[i]

-- create pulse if there is an event
if er(fills[i], lengths[i], index+1) then
output[i]()
end
end
end

-- Use a trigger to reset locations
input[2].change = function(state)
for i=1,4 do
locations[i]=-1
locations[i] = -1 -- reset locations
end
end

Expand Down
Loading

0 comments on commit eed1d75

Please sign in to comment.