Skip to content
Permalink
main
Go to file
 
 
Cannot retrieve contributors at this time
253 lines (222 sloc) 6.66 KB
-- dial marks notches in time
-- v0.1.0
-- Requires crow > 2.x
--
-- Generate pulses and shapes at voltage-controlled BPM.
-- Please see configure() below and change to taste.
--
-- In 1: BPM voltage input
-- Out 1-4: Pulses
--
-- TODO:
-- - do something with input 2
-- FEATURE CREEP
-- - split cycle functions (i.e. distribute phase) a la TC
-- ********************************************************************
-- Configuration
-- ********************************************************************
-- Ranges
-- --------------------------------------------------------------------
local bpm_min = 60
local bpm_max = 240
local bpm_quantize = 4 -- 1, 2, 4, or 10
local voltage_minimum = 0 -- Adjust based on voltage source
local voltage_maximum = 10
-- --------------------------------------------------------------------
-- Cycles (edit function body!)
-- --------------------------------------------------------------------
function configure()
local maj = { 0, 2/12, 4/12, 5/12, 7/12, 9/12, 11/12 }
return {
-- Polyrhythmic arps
{ 1/2, 0 , split(1/5,
duty(1/3, maj[5], maj[7]),
duty(1/3, maj[2], maj[4])
)
},
{ 2/3, 1/3, split(1/5,
duty(1/3, maj[7], maj[2]),
duty(1/3, maj[4], maj[5])
)
},
{ 2/5, 1/4, split(1/5,
duty(1/3, maj[2], maj[4]),
duty(1/3, maj[5], maj[7])
)
},
{ 4/7, 1/5, split(1/5,
duty(1/3, maj[4], maj[5]),
duty(1/3, maj[7], maj[2])
)
}
-- All fours
--1, 1, 1, 1
-- All fours, striated
-- { 1, 0/4 },
-- { 1, 3/4 },
-- { 1, 2/4 },
-- { 1, 1/4 }
-- All fours, striated, BPM-synced gates
--{ 1, 0/4, duty(1/4) },
--{ 1, 3/4, duty(1/4) },
--{ 1, 2/4, duty(1/4) },
--{ 1, 1/4, duty(1/4) }
-- Lab coat
--d(1/4, 0),
--d(1/2, 1/2),
--d(1, 1/2),
--d(2, 0)
-- Sort of funky (+ fatter pulses)
--d(1 , 0 , native(pulse(0.01))),
--d(1/4, 9/16, native(pulse(0.01))),
--d(1/2, 1/ 2, native(pulse(0.01))),
--d(1 , 1/ 4, native(pulse(0.01)))
-- Reich-esque
--{ 11/29, 1/ 3 },
--{ 13/31, 1/ 5 },
--{ 17/37, 1/ 7 },
--{ 19/41, 1/11 }
-- Barber pole
--d(1/4, 0/4, raw(0, 5)), -- default of raw is 0v - 5v
--d(1/4, 3/4, raw()),
--d(1/4, 2/4, raw()),
--d(1/4, 1/4, raw())
}
-- ********************************************************************
end
function pinch(e, min, max) return math.min(math.max(e, min), max) end
function native(action)
return function (o)
output[o].action = action
return function (new_phase, old_phase)
if new_phase < old_phase then
output[o]() -- threshold crossed
end
end
end
end
function raw(start, finish)
-- is validation a luxury we can afford?
start = pinch(start or 0, -10, 10)
finish = pinch(finish or 5, -10, 10)
local range = finish - start
return function (o)
return function (phase)
output[o].volts = start + (phase * range)
end
end
end
function duty(split, first, second)
-- is validation a luxury we can afford?
first = pinch(first or 5, -10, 10)
second = pinch(second or 0, -10, 10)
split = pinch(split or 0.5, 0, 1)
return function (o)
return function (phase)
output[o].volts = (phase < split) and first or second
end
end
end
function split(point, first, second)
return function (o)
first = first(o)
second = second(o)
return function(phase)
if (phase < point) then
first(point > 0 and (phase / point)
or 0)
else
second(point < 1 and ((phase - point) / (1 - point))
or 1)
end
end
end
end
function d(divisor, phase, make_reconcile)
make_reconcile = make_reconcile or native(pulse(0.001))
phase = phase or 0
divisor = divisor or 1
local start_phase = phase
return function (o)
-- init
local reconcile = make_reconcile(o)
reconcile(phase, phase)
return {
function (ms, bpm) -- do phase things
local phase_fraction = ms * bpm * divisor / 60000
local new_phase = (phase + phase_fraction) % 1
reconcile(new_phase, phase)
phase = new_phase
end,
function (reset_phase) -- reset
reconcile(reset_phase, phase)
phase = reset_phase
end
}
end
end
function make_update_time()
local last = time()
return function (new)
local delta = new - last
last = new
return delta
end
end
function v_to_bpm(v)
v = pinch(v, voltage_minimum, voltage_maximum)
local ratio = (v - voltage_minimum) / (voltage_maximum - voltage_minimum)
local new_bpm = bpm_min + (ratio * (bpm_max - bpm_min))
local frac = (new_bpm / bpm_quantize) % 1
new_bpm = new_bpm - (frac * bpm_quantize)
return new_bpm + ((frac < 0.5 and 0 or 1) * bpm_quantize)
end
function init()
-- "constants"/closured utils
local bpm_hysteresis = 1
local v_hysteresis = 0.005
local update_time = make_update_time()
bpm_quantize = (
bpm_quantize == 2 or
bpm_quantize == 4 or
bpm_quantize == 10
) and bpm_quantize or 1
-- state
local v_last = -20
local bpm = 9000
-- create the "engine"
local divisor = configure()
function for_divs(fn, arg2, arg3)
for o = 1, math.min(#divisor, 4), 1 do
fn(o, arg2, arg3)
end
end
-- setup input
input[1].stream = function (v)
-- check for new BPM
if math.abs(v - v_last) > v_hysteresis then
local new_bpm = v_to_bpm(v)
if math.abs(bpm - new_bpm) >= bpm_hysteresis then
bpm = new_bpm
v_last = v -- only if action taken (for now, BPM change)
end
end
for_divs(
function (o, ms) -- call phasors
divisor[o][1](ms, bpm)
end,
update_time(time())
)
end
input[1].mode("stream", 0.001)
-- replace phasor-makers with actual phasors
for_divs(function (o)
local div = divisor[o]
if type(div) == "number" then
div = d(div)
elseif type(divisor[o]) == "table" then
div = d(div[1], div[2], div[3])
end
divisor[o] = div(o)
end)
end