├── legacy ├── samplehold-basic.lua ├── samplehold.lua ├── clockdiv.lua ├── seqswitch.lua ├── gingerbread.lua ├── booleanlogic.lua ├── shiftregister.lua ├── euclidean.lua ├── quantizer.lua ├── boids.lua └── lorenz.lua ├── twigs └── README.md ├── samplehold.lua ├── seqswitch.lua ├── snippets ├── sample_hold.lua ├── clock_divider.lua ├── acquaintances.lua ├── mixing.lua ├── random_sample_hold.lua ├── transpose.lua ├── quantize.lua ├── sequins.lua └── asr.lua ├── gingerbread.lua ├── clockdiv.lua ├── jfstrum.lua ├── shiftregister.lua ├── cvdelay.lua ├── wsyn_patch.lua ├── quantizer.lua ├── README.md ├── booleans.lua ├── timeline.lua ├── lorenz.lua ├── euclidean.lua ├── boids.lua ├── alphabetsequencer.lua ├── krahenlied.lua └── cali.lua /legacy/samplehold-basic.lua: -------------------------------------------------------------------------------- 1 | --- sample & hold 2 | -- in1: sampling clock 3 | -- out1: random sample 4 | 5 | function init() 6 | input[1].mode('change',1,0.1,'rising') 7 | end 8 | 9 | input[1].change = function() 10 | output[1].volts = math.random() * 10.0 - 5.0 11 | end 12 | -------------------------------------------------------------------------------- /twigs/README.md: -------------------------------------------------------------------------------- 1 | # twigs 2 | #### *collect twigs, sticks and branches, to build your nest.* 3 | 4 | --- 5 | 6 | this is a collection of scripts that demonstrates and documents crow's various features. 7 | examples are given simply, to both aid in understanding of syntax and give the ability to copy-paste boilerplate into whatever your script needs. 8 | 9 | -------------------------------------------------------------------------------- /samplehold.lua: -------------------------------------------------------------------------------- 1 | --- sample & hold 2 | -- in1: sampling clock 3 | -- in2: input to sample 4 | -- out1: random sample 5 | -- out2: sampled input 6 | 7 | function init() 8 | input[1].mode('change',1,0.1,'rising') 9 | end 10 | 11 | input[1].change = function() 12 | output[1].volts = math.random() * 10.0 - 5.0 13 | output[2].volts = input[2].volts 14 | end 15 | -------------------------------------------------------------------------------- /legacy/samplehold.lua: -------------------------------------------------------------------------------- 1 | --- sample & hold 2 | -- in1: sampling clock 3 | -- in2: input voltage 4 | -- out1: s&h random 5 | -- out2: s&h input 6 | -- out3: s&h input (quantized chromatically) 7 | -- out4: s&h input+random nudge (quantized chromatically) 8 | 9 | function init() 10 | input[1]{ mode = 'change' 11 | , direction = 'rising' 12 | } 13 | end 14 | 15 | input[1].change = function() 16 | r = math.random() * 10.0 - 5.0 17 | v = input[2].volts 18 | output[1].volts = r 19 | output[2].volts = v 20 | output[3].volts = math.floor(v*12)/12 21 | output[4].volts = math.floor(r + v*12)/12 22 | end 23 | -------------------------------------------------------------------------------- /legacy/clockdiv.lua: -------------------------------------------------------------------------------- 1 | --- clock divider 2 | -- in1: clock input 3 | -- in2: division selector (see divs) 4 | -- out1-4: divided outputs 5 | 6 | -- choose your clock divisions 7 | divs = { {5,7,11,13} -- -5V 8 | , {3,5,7,11} 9 | , {2,3,5,7} -- 0V 10 | , {2,4,8,16} 11 | , {4,8,16,32} -- +5V 12 | } 13 | 14 | -- private vars 15 | count = {1,1,1,1} 16 | function init() 17 | input[1].mode('change') 18 | end 19 | 20 | input[1].change = function(s) 21 | sel = math.floor(input[2].volts/2 + 3.5) 22 | if sel > 5 then sel = 5 elseif sel < 1 then sel = 1 end 23 | if s then 24 | for n=1,4 do 25 | count[n] = (count[n] % divs[sel][n])+1 26 | output[n].volts = count[n] <= (divs[sel][n]/2) and 5 or 0 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /seqswitch.lua: -------------------------------------------------------------------------------- 1 | --- sequential switch 2 | -- in1: signal 3 | -- in2: switch 4 | -- out1-4: i1 sent through sequentially 5 | 6 | -- user settings 7 | public{mode = 'hold'}:options{'hold','zero'} -- when switch closes, should hold last value? 8 | 9 | -- private 10 | dest = 1 11 | 12 | function init() 13 | input[1]{ mode = 'stream' 14 | , time = 0.001 15 | } 16 | input[2]{ mode = 'change' 17 | , direction = 'rising' 18 | } 19 | for n=1,4 do 20 | output[n].slew = 0.001 -- interpolate at the stream rate 21 | end 22 | end 23 | 24 | input[1].stream = function(v) 25 | output[dest].volts = v 26 | end 27 | 28 | input[2].change = function() 29 | if public.mode == 'zero' then output[dest].volts = 0 end 30 | dest = (dest % 4)+1 -- rotate channel 31 | end 32 | -------------------------------------------------------------------------------- /legacy/seqswitch.lua: -------------------------------------------------------------------------------- 1 | --- sequential switch 2 | -- input 1 is signal, input 2 is switch 3 | -- input 1 is sequentially sent to each output 4 | 5 | -- user settings 6 | hold = true -- when switch closes, should hold last value? 7 | samplerate = 0.05 -- 1ms 8 | 9 | -- private 10 | dest = 1 11 | 12 | function init() 13 | input[1]{ mode = 'stream' 14 | , time = samplerate 15 | } 16 | input[2]{ mode = 'change' 17 | , direction = 'rising' 18 | } 19 | for n=1,4 do 20 | output[n].slew = samplerate 21 | output[n].volts = 0 22 | end 23 | end 24 | 25 | input[1].stream = function(v) 26 | output[dest].volts = v 27 | end 28 | 29 | input[2].change = function() 30 | old = dest 31 | dest = (dest % 4)+1 -- rotate channel 32 | if not hold then output[old].volts = 0 end 33 | end 34 | -------------------------------------------------------------------------------- /snippets/sample_hold.lua: -------------------------------------------------------------------------------- 1 | --- sample & hold 2 | -- in1: clock pulse 3 | -- in2: input voltage to sample 4 | -- out1: input2 voltage sampled on each input1 pulse 5 | 6 | function in2_to_out1() 7 | output[1].volts = input[2].volts 8 | end 9 | 10 | function init() 11 | -- turn on 'change' mode for input 1 12 | input[1].mode('change') 13 | 14 | -- assign input[1]'s 'change' handler 15 | -- the function in1_to_out1 will be called on each input pulse 16 | input[1].change = in2_to_out1 17 | end 18 | 19 | 20 | 21 | --- simplified form 22 | -- note how input[1].mode is called without parens. this is standard lua syntax for string literal fn args 23 | -- second we inline in2_to_out1 to set the .change event directly 24 | function init() 25 | input[1].mode 'change' 26 | input[1].change = function() output[1].volts = input[2].volts end 27 | end 28 | -------------------------------------------------------------------------------- /legacy/gingerbread.lua: -------------------------------------------------------------------------------- 1 | --- gingerbread man chaotic attractor 2 | -- input1: clock 3 | -- input2: offset 4 | -- output1: X 5 | -- output2: Y 6 | 7 | function init() 8 | input[1]{ mode = 'change' 9 | , direction = 'rising' 10 | } 11 | end 12 | 13 | -- two instances 14 | gs = { {x=0,y=0} 15 | , {x=0,y=0} 16 | } 17 | 18 | function make_bread(g, n) 19 | if n==1 then 20 | g.x = g.x + input[2].volts 21 | else 22 | g.y = g.y + input[2].volts 23 | end 24 | local x1 = g.x 25 | g.x = 0.5 - g.y + math.abs(x1) 26 | g.y = x1 27 | if g.x > 5 then g.x = 5.0 end 28 | if g.y > 5 then g.y = 5.0 end 29 | end 30 | 31 | input[1].change = function() 32 | for n=1,2 do 33 | make_bread(gs[n], n) 34 | output[n*2-1].volts = gs[n].x 35 | output[n*2].volts = gs[n].y 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /legacy/booleanlogic.lua: -------------------------------------------------------------------------------- 1 | --- boolean logic 2 | -- out1: 1 AND 2 3 | -- out2: 1 OR 2 4 | -- out3: 1 XOR 2 5 | -- out4: true on 1rising, false on 2rising 6 | 7 | g = {false, false} -- state of the input gates 8 | flipflop = false -- state of the flipflop 9 | function init() 10 | for n=1,2 do 11 | input[n].mode('change') 12 | g[n] = input[n].volts > 1.0 13 | end 14 | end 15 | 16 | function logic( chan, state ) 17 | g[chan] = state 18 | if state then flipflop = (chan == 1) and true or false end 19 | 20 | output[1].volts = (g[1] and g[2]) and 5 or 0 21 | output[2].volts = (g[1] or g[2]) and 5 or 0 22 | output[3].volts = (g[1] ~= g[2]) and 5 or 0 -- XOR is the same as not-equal 23 | output[4].volts = flipflop and 5 or 0 24 | end 25 | 26 | input[1].change = function(s) 27 | logic(1,s) 28 | end 29 | input[2].change = function(s) 30 | logic(2,s) 31 | end 32 | -------------------------------------------------------------------------------- /gingerbread.lua: -------------------------------------------------------------------------------- 1 | --- gingerbread man chaotic attractor 2 | -- input1: clock 3 | -- input2: offset 4 | -- output1: X1 5 | -- output2: Y1 6 | -- output3: X2 7 | -- output4: Y2 8 | 9 | -- TODO view in x-y mode 10 | function init() 11 | input[1]{ mode = 'change' 12 | , direction = 'rising' 13 | } 14 | end 15 | 16 | -- two instances 17 | gs = { {x=0,y=0} 18 | , {x=0,y=0} 19 | } 20 | 21 | function make_bread(g, xoff, yoff) 22 | local x1 = g.x 23 | g.x = 0.5 - g.y + math.abs(x1) + xoff 24 | g.y = x1 + yoff 25 | if g.x > 5 then g.x = 5.0 end 26 | if g.y > 5 then g.y = 5.0 end 27 | end 28 | 29 | input[1].change = function() 30 | make_bread(gs[1], input[2].volts, 0) 31 | make_bread(gs[2], 0, input[2].volts) 32 | for n=1,2 do 33 | output[n*2-1].volts = gs[n].x 34 | output[n*2].volts = gs[n].y 35 | end 36 | for n=1,4 do public.view.output[n]() end 37 | end 38 | -------------------------------------------------------------------------------- /clockdiv.lua: -------------------------------------------------------------------------------- 1 | --- clock divider 2 | -- in1: clock input 3 | -- in2: division selector (see divs) 4 | -- out1-4: divided outputs 5 | 6 | function newdiv() 7 | for n = 1, 4 do 8 | output[n].clock_div = windows[win_ix].v[n] -- changed line 9 | end 10 | end 11 | 12 | -- choose your clock divisions 13 | windows = { 14 | public({ win1 = { 5, 7, 11, 13 } }):action(newdiv), 15 | public({ win2 = { 3, 5, 7, 11 } }):action(newdiv), 16 | public({ win3 = { 2, 3, 5, 7 } }):action(newdiv), 17 | public({ win4 = { 2, 4, 8, 16 } }):action(newdiv), 18 | public({ win5 = { 4, 8, 16, 32 } }):action(newdiv), 19 | } 20 | win_ix = 3 21 | 22 | function init() 23 | input[1].mode("clock", 1 / 4) 24 | input[2].mode("window", { -3, -1, 1, 3 }) 25 | for n = 1, 4 do 26 | output[n]:clock(public.win3[n]) 27 | end 28 | end 29 | 30 | input[2].window = function(win, dir) 31 | win_ix = win 32 | newdiv(windows[win]) 33 | end 34 | -------------------------------------------------------------------------------- /snippets/clock_divider.lua: -------------------------------------------------------------------------------- 1 | --- clock divider 2 | -- in1: input clock 3 | -- out1: clock divided output 4 | 5 | function init() 6 | -- set input1 to clock mode to drive the internal 'clock' system 7 | -- the second argument '1' means that each trigger pulse equals 1 beat 8 | input[1].mode('clock', 1) 9 | 10 | -- set output1 to clock output mode 11 | -- the argument '4' will output a clock pulse once every 4 beats 12 | -- note: this value can be a fraction (eg 1/4) to set the output clock faster than input 13 | -- the ':' represents a 'method' call. for now just remember that clock needs this 14 | output[1]:clock(4) 15 | end 16 | 17 | 18 | --- REPL interaction 19 | -- update the output clock-division by setting the output's .clock_div key 20 | -- output[1].clock_div = 1/4 21 | 22 | 23 | --- simplified form 24 | function init() 25 | input[1].mode('clock', 1) 26 | output[1]:clock(4) 27 | end 28 | -------------------------------------------------------------------------------- /snippets/acquaintances.lua: -------------------------------------------------------------------------------- 1 | --- just friends for crow 2 | 3 | -- in1: frequency 4 | -- in2: intone 5 | -- out1-4: lfos 6 | 7 | 8 | iratio = {1,3,5,7} -- harmonic indices for intone control 9 | SHAPE = 'sine' -- waveshape of oscillators 10 | offset = 0 -- octave offset of frequency. 0 means 0V = 1second 11 | 12 | function init() 13 | input[1].mode('stream',0.001) 14 | input[1].stream = update_speeds 15 | for n=1,4 do 16 | output[n]( lfo( dyn{freq=1}, 5, SHAPE )) -- start all at 1hz, immediately updated by stream 17 | end 18 | end 19 | 20 | function update_speeds(v) 21 | local freq = 2^(-(v+offset)) 22 | local intone = math.min(5, math.max(-5, input[2].volts / 5)) 23 | if intone >= 0 then 24 | for n=1,4 do 25 | output[n].dyn.freq = freq/(1 + intone*(iratio[n]-1)) 26 | end 27 | else 28 | for n=1,4 do 29 | output[n].dyn.freq = freq*(1 - intone*(iratio[n]-1)) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /legacy/shiftregister.lua: -------------------------------------------------------------------------------- 1 | --- digital analog shift register 2 | -- four stages of delay for an incoming cv 3 | -- input 1: cv to delay 4 | -- input 2: trigger to capture input & shift 5 | -- output 1-4: newest to oldest output 6 | -- ii: 6 stages of ASR via just friends 7 | 8 | -- public params 9 | jf_active = true 10 | jf_velocity = 2 11 | 12 | -- global state 13 | reg = {0,0,0,0,0,0} 14 | 15 | function init() 16 | input[2]{ mode = 'change' 17 | , direction = 'rising' 18 | } 19 | if jf_active then 20 | ii.jf.mode(1) 21 | end 22 | end 23 | 24 | function update(r) 25 | for n=1,4 do 26 | output[n].volts = r[n] 27 | end 28 | if jf_active then 29 | for n=1,6 do 30 | ii.jf.play_note(r[n], jf_velocity) 31 | end 32 | end 33 | end 34 | 35 | input[2].change = function() 36 | table.remove(reg) 37 | table.insert(reg, 1, input[1].volts) -- insert at start to force rotation 38 | update(reg) 39 | end 40 | -------------------------------------------------------------------------------- /snippets/mixing.lua: -------------------------------------------------------------------------------- 1 | --- mixing 2 | -- in1: signal 1 3 | -- in2: signal 2 4 | -- out1: signal 1 transposed by signal 2 5 | 6 | -- note the argument 'v' which will represent input[1]'s voltage 7 | function add_ins_to_out1(v) 8 | -- here we assign the output 1 voltage to the addition of input 1 and 2 9 | output[1].volts = v + input[2].volts 10 | 11 | -- note: we could use `input[1].volts` instead of 'v' above 12 | end 13 | 14 | function init() 15 | -- turn on 'stream' mode for input 1 16 | -- 0.001 sets the sample-rate of the input. 0.001s is 1ms or 1kHz 17 | -- this is the fastest supported stream rate 18 | input[1].mode('stream', 0.001) 19 | 20 | -- assign input[1]'s 'stream' handler 21 | -- the function add_ins_to_out1 will be called every sample 22 | input[1].stream = add_ins_to_out1 23 | end 24 | 25 | 26 | 27 | --- simplified form 28 | function init() 29 | input[1].mode('stream', 0.001) 30 | input[1].stream = function(v) output[1].volts = v + input[2].volts end 31 | end 32 | -------------------------------------------------------------------------------- /snippets/random_sample_hold.lua: -------------------------------------------------------------------------------- 1 | --- random sample & hold 2 | -- in1: clock pulse 3 | -- out1: random voltage updated on each clock pulse 4 | 5 | function random_volts() 6 | -- generate a random voltage between 0 and 1 7 | r = math.random() 8 | 9 | -- increase range from (0 .. 1) up to (0 .. 10) 10 | rr = r * 10 11 | 12 | -- shift range downward to centre around 0V 13 | rrr = rr - 5 14 | 15 | -- set output[1] to the random voltage (-5V .. +5V) 16 | output[1].volts = rrr 17 | end 18 | 19 | function init() 20 | -- turn on 'change' mode for input 1 21 | input[1].mode('change') 22 | 23 | -- assign input[1]'s 'change' handler 24 | -- the function random_volts will be called on each input pulse 25 | input[1].change = random_volts 26 | end 27 | 28 | 29 | 30 | --- simplified form 31 | -- note how input[1].mode is called without parens. this is standard lua syntax for string literal fn args 32 | -- second we inline random_volts by using an anonymous function 33 | function init() 34 | input[1].mode 'change' 35 | input[1].change = function() output[1].volts = math.random() * 10 - 5 end 36 | end 37 | -------------------------------------------------------------------------------- /jfstrum.lua: -------------------------------------------------------------------------------- 1 | ---strum sequencer for just friends, w/syn, and crow 2 | 3 | s = sequins 4 | 5 | a = s{0,3,8,5,12,15,s{19,24,31}} 6 | b = s{1,2,3,-1} 7 | c = s{0,3,8,s{3,5,0,5}} 8 | 9 | output[1].slew = .1 10 | output[2].action = ar(1.25,1.5,5) 11 | 12 | ii.wsyn.patch(1,1) 13 | ii.wsyn.patch(2,2) 14 | 15 | note = function() 16 | ii.jf.play_note(a()/12, 3) 17 | end 18 | 19 | withnote = function() 20 | ii.wsyn.play_note(a:step(b())()/12, 1) 21 | end 22 | 23 | strum = function() 24 | t = 0.1 25 | tm = 1.15 26 | for i=1,9 do 27 | note() 28 | clock.sleep(t) 29 | t = t * tm 30 | end 31 | end 32 | 33 | with = function() 34 | wt = 0.1 35 | wtm = 1.2 36 | for i=1,3 do 37 | withnote() 38 | clock.sleep(t) 39 | t = wt * wtm 40 | end 41 | end 42 | 43 | bass = function() 44 | output[1].volts = c()/12 45 | output[2]() 46 | end 47 | 48 | rep = function() 49 | while true do 50 | clock.run(strum) 51 | clock.run(with) 52 | clock.run(bass) 53 | clock.sleep(5) 54 | end 55 | end 56 | 57 | clock.run(rep) 58 | -------------------------------------------------------------------------------- /shiftregister.lua: -------------------------------------------------------------------------------- 1 | --- digital analog shift register 2 | -- four stages of delay for an incoming cv 3 | -- in1: cv to delay 4 | -- in2: trigger to capture input & shift 5 | -- out1-4: newest to oldest output 6 | -- ii: 6 stages of ASR via just friends 7 | 8 | -- public params 9 | public{slew = 0.001}:xrange(0.001, 0.5):action(function(v) for n=1,4 do output[n].slew = v end end) 10 | public{ii_dest = 'none'}:options{'none','jf','wsyn'} 11 | :action(function(e) if e=='jf' then ii.jf.mode(1) end end) -- enable synthesis mode 12 | public{ii_velo = 2}:range(0.1, 5) 13 | 14 | -- global state 15 | reg = {0,0,0,0,0,0} 16 | 17 | function make_notes(r) 18 | for n=1,4 do 19 | output[n].volts = r[n] 20 | end 21 | if public.ii_dest ~= 'none' then 22 | local count = (public.ii_dest == 'jf') and 6 or 4 23 | for n=1,count do 24 | ii[public.ii_dest].play_note(r[n], public.ii_velo) 25 | end 26 | end 27 | end 28 | 29 | input[2].change = function() 30 | make_notes(reg) -- send old values first 31 | table.remove(reg) 32 | table.insert(reg, 1, input[1].volts) -- insert at start to force rotation 33 | end 34 | 35 | function init() 36 | input[2]{ mode = 'change' 37 | , direction = 'rising' 38 | } 39 | end 40 | -------------------------------------------------------------------------------- /cvdelay.lua: -------------------------------------------------------------------------------- 1 | --- control voltage delay 2 | -- input1: CV to delay 3 | -- input2: 0v = capture, 5v = loop (continuous) 4 | -- output1-4: delay equaly spaced delay taps 5 | 6 | LENGTH = 1000 -- max loop time. MUST BE CONSTANT 7 | 8 | public{tap1 = 250}:range(1,LENGTH):type'slider' 9 | public{tap2 = 500}:range(1,LENGTH):type'slider' 10 | public{tap3 = 750}:range(1,LENGTH):type'slider' 11 | public{tap4 = 1000}:range(1,LENGTH):type'slider' 12 | public{loop = 0.01}:range(0,1):type'slider' 13 | 14 | bucket = {} 15 | write = 1 16 | cv_mode = 0 17 | 18 | function init() 19 | input[1].mode('stream', 0.001) -- 1kHz 20 | for n=1,4 do output[n].slew = 0.002 end -- smoothing at nyquist 21 | for n=1,LENGTH do bucket[n] = 0 end -- clear the buffer 22 | end 23 | 24 | function peek(tap) 25 | local ix = (math.floor(write - tap - 1) % LENGTH) + 1 26 | return bucket[ix] 27 | end 28 | 29 | function poke(v, ix) 30 | local c = (input[2].volts / 4.5) + public.loop 31 | c = (c < 0) and 0 or c 32 | c = (c > 1) and 1 or c 33 | bucket[ix] = v + c*(bucket[ix] - v) 34 | end 35 | 36 | input[1].stream = function(v) 37 | output[1].volts = peek(public.tap1) 38 | output[2].volts = peek(public.tap2) 39 | output[3].volts = peek(public.tap3) 40 | output[4].volts = peek(public.tap4) 41 | poke(v, write) 42 | write = (write % LENGTH) + 1 43 | end 44 | -------------------------------------------------------------------------------- /wsyn_patch.lua: -------------------------------------------------------------------------------- 1 | --- wsyn example sequencer 2 | 3 | w = ii.wsyn 4 | 5 | function init() 6 | metro[1].event = notes 7 | metro[1].time = 0.3 8 | metro[1]:start() 9 | metro[2].event = inotes 10 | metro[2].time = 0.37 11 | metro[2]:start() 12 | 13 | w.ar_mode(1) 14 | w.fm_index(0.0) 15 | w.fm_ratio(3,2) 16 | w.fm_env(0) 17 | w.patch(3,0) 18 | w.ramp(0) 19 | w.curve(1) 20 | w.lpg_time(-3) 21 | 22 | output[2].action = { to(5,0), to(5,0.02), to(0,0) } 23 | end 24 | 25 | -- some scales 26 | FLAT = {-10,3,10,14,20} 27 | TH = {-12,4,11,14,21} 28 | THUN = {-12,4,6,14,23} 29 | CHOO = {-5,6,17,18} 30 | 31 | nns = TH -- assign a scale 32 | 33 | octave = 0 34 | cycle = 1 35 | which = 1 36 | WHICH_LEN = #nns 37 | function notes() 38 | for n=1,1 do -- play 2 notes at once 39 | which = (which+1) % #nns + 1 40 | --which = which % #nns; which = which + 1 41 | --ii.wsyn.play_note(nns[which]/12,0.5) 42 | note = -2 + octave + nns[which]/12 43 | output[1].volts = note 44 | output[3].volts = note 45 | output[2]() 46 | end 47 | cycle = cycle + 1 48 | if cycle > 13 then 49 | cycle = 1 50 | octave = 1-octave 51 | end 52 | end 53 | 54 | function inotes() 55 | which = (which+3) % #nns + 1 56 | output[1].volts = nns[which]/12 -1 57 | output[2]() 58 | 59 | for n=1,2 do -- play 2 notes at once 60 | --which = (which+3) % #nns + 1 61 | --ii.wsyn.play_note(0,0.5) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /quantizer.lua: -------------------------------------------------------------------------------- 1 | --- quantizer 2 | -- sam wolk 2019.10.15 3 | -- updated by whimsicalraps 2021 4 | -- in1: clock 5 | -- in2: voltage to quantize 6 | -- out1: in2 quantized to scale1 on clock pulses 7 | -- out2: in2 quantized to scale2 on clock pulses 8 | -- out3: in2 quantized to scale3 continuously 9 | -- out4: trigger pulses when out3 changes 10 | 11 | snum = {'octave','chroma','major','harMin','dorian','majTri','dom7th','wholet'} 12 | 13 | public.add('scale1','major',snum 14 | , function(s) output[1].scale = scales[s] end) 15 | public.add('scale2','harMin',snum 16 | , function(s) output[2].scale = scales[s] end) 17 | public.add('scale3','majTri',snum 18 | , function(s) input[2].mode('scale',scales[s]) end) 19 | 20 | scales = 21 | { octave = {0} 22 | , chroma = {} -- this is a shortcut 23 | , major = {0,2,4,5,7,9,11} 24 | , harMin = {0,2,3,5,7,8,10} 25 | , dorian = {0,2,3,5,7,9,10} 26 | , majTri = {0,4,7} 27 | , dom7th = {0,4,7,10} 28 | , wholet = {0,2,4,6,8,10} 29 | } 30 | 31 | -- update clocked outputs 32 | input[1].change = function(state) 33 | output[1].volts = input[2].volts 34 | output[2].volts = input[2].volts 35 | end 36 | 37 | -- update continuous quantizer 38 | input[2].scale = function(s) 39 | output[3].volts = s.volts 40 | output[4]() 41 | end 42 | 43 | function init() 44 | input[1].mode('change',1,0.1,'rising') 45 | input[2].mode('scale',scales[public.scale3]) 46 | output[1].scale(scales[public.scale1]) 47 | output[2].scale(scales[public.scale2]) 48 | output[4].action = pulse(0.01, 8) 49 | end 50 | 51 | -------------------------------------------------------------------------------- /snippets/transpose.lua: -------------------------------------------------------------------------------- 1 | --- transpose 2 | -- in1: input signal to transpose 3 | -- out1: in1 transposed by 2 octaves 4 | 5 | -- this is a global variable, representing our transposition amount 6 | -- the number '2' means 2V in crow, which will translate to 2 octaves 7 | -- try changing it from the druid REPL 8 | transpose = 2 9 | 10 | -- note the argument 'v' which will represent input[1]'s voltage 11 | function transpose_in1_to_out1(v) 12 | -- transposing is as simple as adding the 'transpose' value to our input voltage 13 | -- note how we store the result of the addition back into v, overwriting it's value 14 | v = v + transpose 15 | 16 | -- apply the transposed voltage to output1 17 | output[1].volts = v 18 | end 19 | 20 | function init() 21 | -- turn on 'stream' mode for input 1 22 | -- 0.001 sets the sample-rate of the input. 0.001s is 1ms or 1kHz 23 | -- this is the fastest supported stream rate 24 | input[1].mode('stream', 0.001) 25 | 26 | -- assign input[1]'s 'stream' handler 27 | -- the function transpose_in1_to_out1 will be called every sample 28 | input[1].stream = transpose_in1_to_out1 29 | end 30 | 31 | 32 | 33 | --- REPL interaction 34 | -- the transpose amount can be changed by updating the 'transpose' variable 35 | -- transpose = 1 -- set to 1V up 36 | -- transpose = -1 -- set to 1V down 37 | -- transpose = 3/12 -- transpose up by 3 semitones (note: an octave is 12 semitones) 38 | 39 | 40 | 41 | --- simplified form 42 | function init() 43 | input[1].mode('stream', 0.001) 44 | input[1].stream = function(v) output[1].volts = v + transpose end 45 | end 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bowery 2 | [druid](https://github.com/monome/druid) script collection 3 | 4 | - [alphabetsequencer.lua](alphabetsequencer.lua): sequence synth voices with sequins 5 | - [boids.lua](boids.lua) (*): four simulated birds that fly around your input 6 | - [booleans.lua](booleans.lua) (*): logic gates determined by two input gates 7 | - [cali.lua](cali.lua): utility for calibrating crow's CV i/o 8 | - [clockdiv.lua](clockdiv.lua) (*): four configurable clock divisions of the input clock 9 | - [cvdelay.lua](cvdelay.lua) (*): a control voltage delay with four taps & looping option 10 | - [euclidean.lua](euclidean.lua) (*): a euclidean rhythm generator 11 | - [gingerbread.lua](gingerbread.lua): clocked chaos generators 12 | - [jfstrum.lua](jfstrum.lua): i2c strum sequencer for just friends and w/syn 13 | - [krahenlied.lua](krahenlied.lua): sequence synth voices with poetry 14 | - [lorenz.lua](lorenz.lua): lorenz attractor chaotic lfos 15 | - [quantizer.lua](quantizer.lua) (*): a continuous and clocked quantizer demo 16 | - [samplehold.lua](samplehold.lua) (*): sample and hold basics for scripting tutorial 17 | - [seqswitch.lua](seqswitch.lua) (*): route an input to 1 of 4 outputs with optional 'hold' 18 | - [shiftregister.lua](shiftregister.lua) (*): output the last 4 captured voltages & play just friends 19 | - [timeline.lua](timeline.lua): timeline sequencer 20 | - [wsyn_patch.lua](wsyn_patch.lua): wsyn example sequencer 21 | 22 | scripts with (*) have optional ui when connected to norns 23 | 24 | learn how to upload scripts to crow using [***stage one*** of the crow scripting tutorial](https://monome.org/docs/crow/scripting) 25 | -------------------------------------------------------------------------------- /booleans.lua: -------------------------------------------------------------------------------- 1 | --- boolean logic 2 | -- output logic transfer functions are dynamically selected per channel 3 | -- a state change on any input triggers update of all outputs 4 | 5 | -- enumeration of the fnlist. least to most density of transfer 6 | fenum = {'~|','>','<','&','~^','^','~&','<=','>=','|'} 7 | 8 | public{f1 = '&' }:options(fenum):action(function() apply() end) 9 | public{f2 = '|' }:options(fenum):action(function() apply() end) 10 | public{f3 = '^' }:options(fenum):action(function() apply() end) 11 | public{f4 = '~^'}:options(fenum):action(function() apply() end) 12 | 13 | -- all the dynamic transfer fns between 2 inputs (ordering doesn't matter) 14 | fnlist = 15 | { ['~|'] = function(a,b) return not (a or b) end 16 | , ['>' ] = function(a,b) return a and not b end 17 | , ['<' ] = function(a,b) return b and not a end 18 | , ['&' ] = function(a,b) return (a and b) end 19 | , ['~^'] = function(a,b) return a==b end 20 | , ['^' ] = function(a,b) return a~=b end 21 | , ['~&'] = function(a,b) return not (a and b) end 22 | , ['<='] = function(a,b) return not (a and not b) end 23 | , ['>='] = function(a,b) return not (b and not a) end 24 | , ['|' ] = function(a,b) return (a or b) end 25 | } 26 | 27 | function q(n) return input[n].volts > 1.0 end 28 | function apply() 29 | print'apply' 30 | local a, b = q(1), q(2) 31 | output[1].volts = fnlist[public.f1](a, b) and 5 or 0 32 | output[2].volts = fnlist[public.f2](a, b) and 5 or 0 33 | output[3].volts = fnlist[public.f3](a, b) and 5 or 0 34 | output[4].volts = fnlist[public.f4](a, b) and 5 or 0 35 | end 36 | 37 | function init() 38 | for n=1,2 do 39 | input[n].mode('change') 40 | input[n].change = apply -- update all outs on any state change 41 | end 42 | apply() -- initialize to current state 43 | end 44 | -------------------------------------------------------------------------------- /snippets/quantize.lua: -------------------------------------------------------------------------------- 1 | --- quantize 2 | -- in1: input signal to quantize 3 | -- out1: in1 quantized to my_scale 4 | -- out2: generates a trigger pulse every time a new note occurs 5 | 6 | -- my_scale is a global variable, representing our chosen scale 7 | -- the curly braces {} create a table (aka list) of values 8 | -- numbers in the table represent semitones 9 | my_scale = {0,2,4,7,9} -- this is a major pentatonic scale 10 | 11 | function new_note(s) 12 | -- s is a table representing the new quantized note that has occured 13 | -- the simplest way to use the table is with the .volts key 14 | -- this gives a voltage that can be sent directly to an output as a voltage 15 | output[1].volts = s.volts 16 | 17 | -- call output[2]'s assigned action 18 | -- this will use the 'pulse' function that was assigned in init 19 | output[2]() 20 | end 21 | 22 | function init() 23 | -- turn on 'scale' mode for input 1 24 | -- the second argument is a table of 'notes' to quantize to 25 | input[1].mode('scale', my_scale) 26 | 27 | -- assign input[1]'s 'scale' handler 28 | -- the function new_note will be called every time a new note occurs 29 | input[1].scale = new_note 30 | 31 | -- configure output[2] such that when it is called, it will generate a trigger pulse 32 | output[2].action = pulse() 33 | end 34 | 35 | 36 | 37 | --- REPL interaction 38 | -- to update the scale you must reconfigure the input mode: 39 | -- input[1].mode('scale', {0,3,6,9}) -- set to a diminished7th arpeggio 40 | 41 | 42 | 43 | --- simplified form 44 | function init() 45 | input[1].mode('scale', {0,2,4,7,9}) 46 | 47 | input[1].scale = function(s) 48 | output[1].volts = s.volts 49 | output[2]() 50 | end 51 | 52 | output[2].action = pulse() 53 | end 54 | -------------------------------------------------------------------------------- /legacy/euclidean.lua: -------------------------------------------------------------------------------- 1 | --- Euclidean Rhythms 2 | -- sam wolk 2019.10.21 3 | -- Create 4 euclidean rhythm generators 4 | -- in1: clock 5 | -- in2: reset 6 | -- outs: euclidean rhythms 7 | 8 | -- ER parameters for each channel. 9 | lengths = {16,16,16,16} 10 | fills = {4,5,9,12} 11 | offsets = {0,0,0,0} 12 | 13 | -- Non-User Variables 14 | locations = {-1,-1,-1,-1} -- 15 | 16 | -- ER function adapted from https://gist.github.com/vrld/b1e6f4cce7a8d15e00e4 17 | -- k > Euclidean Fill 18 | -- n > Euclidean Length 19 | -- s > current step (0-indexed) 20 | function er(k,n,s) 21 | local r = {} 22 | for i = 1,n do 23 | r[i] = {i <= k} 24 | end 25 | local function cat(i,k) 26 | for _,v in ipairs(r[k]) do 27 | r[i][#r[i]+1] = v 28 | end 29 | r[k] = nil 30 | end 31 | 32 | while #r > k do 33 | for i = 1,math.min(k, #r-k) do 34 | cat(i, #r) 35 | end 36 | end 37 | 38 | while #r > 1 do 39 | cat(#r-1, #r) 40 | end 41 | 42 | return r[1][s+1] 43 | end 44 | 45 | -- Use a trigger to advance ER counters and activate ASLs on hits 46 | input[1].change = function(state) 47 | for i=1,4 do 48 | --- increment counters 49 | locations[i] = ((locations[i]+1) % lengths[i]) 50 | 51 | -- get current location 52 | local index = ((locations[i]+offsets[i]) % lengths[i]) 53 | 54 | -- fire asl if there is a hit 55 | if er(fills[i],lengths[i],index) then 56 | output[i]() 57 | end 58 | end 59 | end 60 | 61 | -- Use a trigger to reset locations 62 | input[2].change = function(state) 63 | for i=1,4 do 64 | locations[i]=-1 65 | end 66 | end 67 | 68 | function init() 69 | input[1].mode('change',1,0.1,'rising') 70 | input[2].mode('change',1,0.1,'rising') 71 | 72 | for i=1,4 do 73 | output[i].action = pulse(0.02,8) 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /timeline.lua: -------------------------------------------------------------------------------- 1 | --- timeline sequencer 2 | -- aka 'function tracker' 3 | 4 | -- out1: pitch 5 | -- out2: volume 6 | -- out3: filter cf 7 | 8 | seq = sequins{0,6,11,-12,-1,4,0,0,7,6,4,1} 9 | 10 | function make_note() 11 | output[1].volts = seq() 12 | output[2]( ar(0.008, 1) ) 13 | end 14 | function shift_pitch() 15 | output[1].slew = 0.1 16 | output[1].volts = seq() 17 | end 18 | function fsweepup_note() 19 | output[3].slew = 0.5 20 | output[3].volts = 5 21 | make_note() 22 | end 23 | function fsweepdown() 24 | output[3].slew = 2.0 25 | output[3].volts = 0 26 | end 27 | function wobble_glissando() 28 | output[3]( lfo() ) 29 | output[1].slew = 0.1 30 | output[1].volts = seq() 31 | end 32 | function ratchet3() 33 | output[2]( times( 3, { to(5,0.07), to(0,0.02) })) 34 | end 35 | function rapid_notes() 36 | output[1].slew = 0.005 37 | output[1]( loop{ to( 2, 0.1 ) } ) 38 | end 39 | 40 | function init() 41 | output[3].slew = 2 42 | ii.jf.mode(1) 43 | ii.jf.run_mode(1) 44 | 45 | clock.run(run_timeline) -- call instantly 46 | end 47 | 48 | -- timeline runtime 49 | line = 1 -- track which line is active 50 | function run_timeline() 51 | local dur = 0 52 | while timeline[line] do 53 | dur = timeline[line][1] - dur -- compare timestamp delta 54 | if dur > 0 then clock.sleep(dur) end 55 | timeline[line][2]() -- call the action! 56 | dur = timeline[line][1] -- grab current timestamp 57 | line = line +1 -- next line 58 | end 59 | end 60 | 61 | -- time -- action 62 | timeline = 63 | { { 0 , function() end } -- start point (do nothing) 64 | , { 0 , make_note } 65 | , { 0.4, fsweepdown } 66 | , { 1.3, fsweepup_note } 67 | , { 1.8, wobble_glissando } 68 | , { 2.1, shift_pitch } 69 | , { 2.4, ratchet3 } 70 | , { 2.7, wobble_glissando } 71 | , { 3.5, rapid_notes } 72 | , { 4.7, wobble_glissando } 73 | , { 5 , function() line = 1 end } -- jump to start 74 | } 75 | -------------------------------------------------------------------------------- /lorenz.lua: -------------------------------------------------------------------------------- 1 | --- lorenz attractor 2 | -- sam wolk 2019.10.13 3 | -- in1 resets the attractor to the {x,y,z} coordinates stored in the Lorenz.origin table 4 | -- in2 controls the speed of the attractor 5 | -- out1 is the x-coordinate (by default) 6 | -- out2 is the y-coordinate (by default) 7 | -- out3 is the z-coordinate (by default) 8 | -- out4 is a weighted sum of x and y (by default) 9 | -- the weights table allows you to specify the weight of each axis for each output. 10 | 11 | weights = {{1,0,0}, {0,1,0}, {0,0,1}, {0.33,0.33,0}} 12 | 13 | Lorenz = { 14 | origin = {.01,0,0}, 15 | sigma = 10, 16 | rho = 28, 17 | beta = 8/3, 18 | state = {.01,0,0}, 19 | steps = 1, 20 | dt = 0.001, 21 | } 22 | 23 | function Lorenz:process(steps,dt) 24 | steps = steps or self.steps 25 | dt = dt or self.dt 26 | for i=1,steps do 27 | local dx = self.sigma*(self.state[2]-self.state[1]) 28 | local dy = self.state[1]*(self.rho-self.state[3])-self.state[2] 29 | local dz = self.state[1]*self.state[2]-self.beta*self.state[3] 30 | self.state[1] = self.state[1]+dx*dt 31 | self.state[2] = self.state[2]+dy*dt 32 | self.state[3] = self.state[3]+dz*dt 33 | end 34 | end 35 | 36 | function Lorenz:reset() 37 | for i=1,3 do self.state[i] = self.origin[i] end 38 | end 39 | 40 | updateOutputs = function() 41 | for i=1,4 do 42 | local sum = 0 43 | for j=1,3 do 44 | sum = sum+weights[i][j]*Lorenz.state[j] 45 | end 46 | output[i].volts = 10*(sum+25)/80 - 5 47 | end 48 | end 49 | 50 | input[1].change = function(s) 51 | Lorenz:reset() 52 | end 53 | 54 | input[2].stream = function(volts) 55 | Lorenz.dt = math.exp((volts-1)/3)/1000-0.00005 56 | end 57 | 58 | function init() 59 | Lorenz:reset() 60 | input[1].mode('change', 1,0.1,'rising') 61 | input[2].mode('stream',0.001) 62 | clock.run( function() 63 | while true do 64 | Lorenz:process() 65 | updateOutputs() 66 | clock.sleep(0.001) 67 | end 68 | end) 69 | end 70 | 71 | -------------------------------------------------------------------------------- /euclidean.lua: -------------------------------------------------------------------------------- 1 | --- 4 euclidean rhythm generators 2 | -- sam wolk 2019.10.21 3 | -- in1: clock 4 | -- in2: reset 5 | -- outs: euclidean rhythms 6 | 7 | -- ER parameters for each channel. 8 | public{lengths = {16,16,16,16}} 9 | public{fills = {4,5,9,12}}:range(1,64) 10 | public{offsets = {0,0,0,0}} 11 | 12 | -- private state 13 | locations = {-1,-1,-1,-1} 14 | 15 | -- ER function adapted from https://gist.github.com/vrld/b1e6f4cce7a8d15e00e4 16 | function er(fill, length, ix) 17 | local r = {} 18 | -- place all filled slots at the start of the rhythm 19 | for i=1,length do 20 | r[i] = {i <= fill} 21 | end 22 | -- each element is now a table with either true or false 23 | 24 | local function cat(t, dst, src) 25 | -- copy all elements of t[src] to the end of t[dst] and remove t[src] 26 | for _,v in ipairs(t[src]) do 27 | table.insert(t[dst], v) 28 | end 29 | t[src] = nil 30 | end 31 | 32 | -- interleave the empty slots until they are spread out evenly 33 | while #r > fill do 34 | for i=1,math.min(fill, #r-fill) do 35 | cat(r, i, #r) 36 | end 37 | end 38 | 39 | -- fold all lists down to a single one 40 | while #r > 1 do 41 | cat(r, #r-1, #r) 42 | end 43 | 44 | -- return boolean (and discard table) 45 | return r[1][ix] 46 | end 47 | 48 | -- Use a trigger to advance ER counters and activate ASLs on hits 49 | input[1].change = function(state) 50 | for i=1,4 do 51 | --- increment counters 52 | locations[i] = (locations[i] + 1) % public.lengths[i] 53 | 54 | -- get current location 55 | local index = (locations[i] + public.offsets[i]) % public.lengths[i] 56 | 57 | -- create pulse if there is an event 58 | if er(public.fills[i], public.lengths[i], index+1) then 59 | output[i]() 60 | end 61 | end 62 | end 63 | 64 | input[2].change = function(state) 65 | for i=1,4 do 66 | locations[i] = -1 -- reset locations 67 | end 68 | end 69 | 70 | function init() 71 | input[1].mode('change',1,0.1,'rising') 72 | input[2].mode('change',1,0.1,'rising') 73 | 74 | for i=1,4 do 75 | output[i].action = pulse(0.02,8) 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /legacy/quantizer.lua: -------------------------------------------------------------------------------- 1 | --- quantizer 2 | -- sam wolk 2019.10.15 3 | -- in1: clock 4 | -- in2: voltage to quantize 5 | -- out1: in2 quantized to scale1 on clock pulses 6 | -- out2: in2 quantized to scale2 on clock pulses 7 | -- out3: in2 quantized to scale3 continuously 8 | -- out4: trigger pulses when out3 changes 9 | 10 | -- nb: scales should be written as semitones (cents optional) in ascending order 11 | octaves = {0,12} 12 | major = {0,2,4,5,7,9,11,12} 13 | harmonicMinor = {0,2,3,5,7,8,10,12} 14 | dorian = {0,2,3,5,7,9,10,12} 15 | majorTriad = {0,4,7,12} 16 | dominant7th = {0,4,7,10,12} 17 | 18 | -- try re-assigning scale1/2/3 to change your quantizer! 19 | scale1 = major 20 | scale2 = harmonicMinor 21 | scale3 = majorTriad 22 | 23 | 24 | function quantize(volts,scale) 25 | local octave = math.floor(volts) 26 | local interval = volts - octave 27 | local semitones = interval / 12 28 | local degree = 1 29 | while degree < #scale and semitones > scale[degree+1] do 30 | degree = degree + 1 31 | end 32 | local above = scale[degree+1] - semitones 33 | local below = semitones - scale[degree] 34 | if below > above then 35 | degree = degree +1 36 | end 37 | local note = scale[degree] 38 | note = note + 12*octave 39 | return note 40 | end 41 | 42 | -- sample & hold handler; sets out1 & out2 43 | input[1].change = function(state) 44 | -- sample input[2] 45 | local v = input[2].volts 46 | 47 | -- quantize voltage to scale 48 | local note1 = quantize(v,scale1) 49 | local note2 = quantize(v,scale2) 50 | 51 | -- convert semitones to volts and update out1 & out2 52 | output[1].volts = note1/12 53 | output[2].volts = note2/12 54 | end 55 | 56 | -- streaming handler; sets out3 & out4 57 | input[2].stream = function(volts) 58 | -- find current quantized note 59 | local newNote = quantize(volts,scale3) 60 | 61 | -- check if quantized voltage is equal to current voltage 62 | if newNote/12 ~= output[3].volts then 63 | -- if not, update out3 to new voltage and pulse out4 64 | output[3].volts = newNote/12 65 | output[4](pulse(0.01,8)) 66 | end 67 | end 68 | 69 | 70 | function init() 71 | input[1].mode('change',1,0.1,'rising') 72 | input[2].mode('stream',0.05) 73 | print('quantizer loaded') 74 | end 75 | 76 | -------------------------------------------------------------------------------- /snippets/sequins.lua: -------------------------------------------------------------------------------- 1 | --- sequencing with sequins 2 | -- in1: clock input 3 | -- out1: next note 4 | 5 | -- first we make a table representing the chromatic scale as semitones 6 | chrom_scale = {0,1,2,3,4,5,6,7,8,9,10,11} 7 | 8 | -- make a new sequins from chrom_scale by passing it as the argument to the 'sequins' function 9 | -- this makes a copy of chrom_scale, which can be 'called' like a function, to get a new note 10 | chrom_sequins = sequins(chrom_scale) 11 | 12 | -- by default chrom_sequins() would generate a chromatic scale, moving up by one note at a time 13 | -- we can change this default behaviour by applying the 'step' method to the sequins 14 | -- here :step(7) means each new value will move forward by 7 elements, resulting in a musical 'fifth' 15 | -- when the selection goes out of range, it will wrap around 16 | cycle_of_fifths = chrom_sequins:step(7) 17 | 18 | -- next_note will be called on every clock pulse at input1 19 | function next_note() 20 | -- get the next note from the cycle_of_fifths sequins, and store it in 'n' 21 | n = cycle_of_fifths() 22 | 23 | -- because chrom_scale above is written in semitones, we must convert to volts 24 | -- divide-by-12 will convert semitones to volts, as there are 12 semitones in 1 octave (aka 1 volt) 25 | n = n / 12 26 | 27 | -- set output1's voltage to our calculated value 'n' 28 | output[1].volts = n 29 | end 30 | 31 | function init() 32 | input[1]{mode = 'change', direction = 'rising'} 33 | input[1].change = next_note 34 | end 35 | 36 | 37 | 38 | --- REPL interaction 39 | -- interesting things happen when sequencing by steps other than fifths 40 | -- cycle_of_fifths:step(5) -- step by fourths (moving backwards around the cycle) 41 | -- cycle_of_fifths:step(4) -- step by major thirds (augmented triads) 42 | -- cycle_of_fifths:step(3) -- step by minor thirds (diminished triads) 43 | 44 | 45 | 46 | --- simplified form 47 | -- it is idiomatic to alias 'sequins' to the letter 's' for more concise scripts 48 | s = sequins 49 | -- wrap the chromatic scale in a sequins directly, and set it's step to 7 50 | cycle = s{0,1,2,3,4,5,6,7,8,9,10,11}:step(7) 51 | function init() 52 | input[1]{mode = 'change', direction = 'rising'} 53 | input[1].change = function() output[1].volts = cycle()/12 end 54 | end 55 | -------------------------------------------------------------------------------- /snippets/asr.lua: -------------------------------------------------------------------------------- 1 | --- analog shift register 2 | -- in1: clock input 3 | -- in2: signal to be sampled 4 | -- out1-4: 4 most recent sampled values 5 | 6 | -- this is a table where we store the most recent sampled values 7 | -- initialize it to 0V for all 4 channels 8 | register = {0,0,0,0} 9 | 10 | function shift_reg() 11 | -- before we read in the new value, we output the currently stored values 12 | -- 'for' will iterate over all values between 1 and 4 13 | -- each time the for loop repeats, the value 'n' will be set to the iteration number 14 | for n=1,4 do 15 | -- this line will be called 4 times, with n set to 1, then 2, then 3, then 4 16 | -- we grab the nth element of the 'register' table, and set the nth output to that voltage 17 | output[n].volts = register[n] 18 | end 19 | 20 | -- now we rotate the shift-register and insert the new value 21 | -- table.remove will drop the last element of the table (the oldest value) 22 | table.remove(register) 23 | 24 | -- now we insert the current value of input[2] 25 | -- the 2nd argument '1' means that the new value will be put on the front of the list 26 | -- this causes all other values to be 'pushed' down the line 27 | table.insert(register, 1, input[2].volts) 28 | end 29 | 30 | function init() 31 | -- configure input1 into 'change' mode 32 | -- the 2nd & 3rd arguments are 'threshold' and 'hysteresis', both of which are default values 33 | -- the last argument 'rising' means the .change event will only occur when input1 rises 34 | -- using 'rising' is typical when the input is a trigger, while 'both' is used for gates 35 | input[1].mode('change', 1.0, 0.1, 'rising') 36 | 37 | -- every time a rising-edge is detected on input1, shift_reg will be called 38 | input[1].change = shift_reg 39 | end 40 | 41 | 42 | 43 | --- simplified form 44 | -- in lua, if calling a function with a single table argument, you can omit the surrounding parens 45 | -- calling input[1] with a literal table allows setting the 'mode' with named variables 46 | reg = {0,0,0,0} 47 | function init() 48 | input[1]{ mode = 'change', direction = 'rising' } 49 | input[1].change = function() 50 | for n=1,4 do output[n].volts = reg[n] end 51 | table.remove(reg) 52 | table.insert(reg, 1, input[2].volts) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /legacy/boids.lua: -------------------------------------------------------------------------------- 1 | -- boids 2 | -- t gill 190925 3 | -- inspired by http://www.vergenet.net/~conrad/boids/pseudocode.html 4 | 5 | -- params 6 | follow = 0.75 -- input=1, free=0 7 | pull = 0.5 -- to centre 8 | avoid = 0.1 -- V threshold 9 | sync = 1/20 -- dir attraction 10 | limit = 0.05 -- max volts per timestep 11 | 12 | timing = 0.02 -- calc speed 13 | 14 | boids = {} 15 | count = 4 16 | 17 | -- artificially provide a 'centre-of-mass' 18 | function centring(b,c) 19 | return (c - b.p) * pull 20 | end 21 | 22 | function avoidance(bs,b) 23 | v = 0 24 | for n=1,count do 25 | if bs[n] ~= b then -- ignore self 26 | d = bs[n].p - b.p 27 | if math.abs(d) < avoid then 28 | v = v - d/2 29 | end 30 | end 31 | end 32 | return v 33 | end 34 | 35 | function syncing(bs,b) 36 | v = 0 37 | for n=1,count do 38 | if bs[n] ~= b then -- ignore self 39 | v = v + bs[n].v 40 | end 41 | end 42 | v = v / (count-1) 43 | return (v - b.v) * sync 44 | end 45 | 46 | function findcentre(bs,c) 47 | m = 0 48 | for n=1,count do 49 | m = m + bs[n].p 50 | end 51 | m = m/count 52 | return m + follow*(c-m) 53 | end 54 | 55 | function move( bs, n, c, v ) 56 | mass = findcentre(bs,c) 57 | b = bs[n] 58 | v1 = centring(b,mass) 59 | v2 = avoidance(bs,b) 60 | v3 = syncing(bs,b) 61 | b.v = b.v + v1 + v2 + v3 62 | if b.v > limit then b.v = limit 63 | elseif b.v < -limit then b.v = -limit end 64 | b.v = b.v * v 65 | b.p = b.p + b.v 66 | return b 67 | end 68 | 69 | function draw( b, n, v ) 70 | output[n].slew = v*1.1 71 | output[n].volts = b.p 72 | end 73 | 74 | -- round-robin calculation 75 | function step(c) 76 | c = (c % count)+1 77 | accel = input[2].volts 78 | draw( boids[c], c, timing ) 79 | boids[c] = move( boids, c, input[1].volts, (accel+5.0)/5.0 ) 80 | end 81 | 82 | function init_boids() 83 | local bs = {} 84 | for n=1,count do 85 | bs[n] = { p = math.random()*3.0 - 1.0 86 | , v = 0 87 | } 88 | end 89 | return bs 90 | end 91 | 92 | function init() 93 | boids = init_boids() 94 | mover = metro.init{ event = step 95 | , time = timing/count 96 | , count = -1 97 | } 98 | mover:start() 99 | end 100 | -------------------------------------------------------------------------------- /legacy/lorenz.lua: -------------------------------------------------------------------------------- 1 | --- lorenz attractor 2 | -- sam wolk 2019.10.13 3 | -- in1 resets the attractor to the {x,y,z} coordinates stored in the Lorenz.origin table 4 | -- in2 controls the speed of the attractor 5 | -- out1 is the x-coordinate (by default) 6 | -- out2 is the y-coordinate (by default) 7 | -- out3 is the z-coordinate (by default) 8 | -- out4 is a weighted sum of x and y (by default) 9 | -- the config table allows you to deactivate the attractor for outputs and take direct control. 10 | -- the weights table allows you to specify the weight of each variable for each output. 11 | 12 | config = {'on','on','on','on'} 13 | weights = {{1,0,0},{0,1,0},{0,0,1},{0.33,0.33,0}} 14 | Lorenz = {} 15 | Lorenz.origin = {.01,0,0} 16 | Lorenz.sigma = 10 17 | Lorenz.rho = 28 18 | Lorenz.beta = 8/3 19 | Lorenz.state = {.01,0,0} 20 | Lorenz.steps = 1 21 | Lorenz.dt = 0.001 22 | 23 | function Lorenz:process(steps,dt) 24 | steps = steps or self.steps 25 | dt = dt or self.dt 26 | for i=1,steps do 27 | local dx = self.sigma*(self.state[2]-self.state[1]) 28 | local dy = self.state[1]*(self.rho-self.state[3])-self.state[2] 29 | local dz = self.state[1]*self.state[2]-self.beta*self.state[3] 30 | self.state[1] = self.state[1]+dx*dt 31 | self.state[2] = self.state[2]+dy*dt 32 | self.state[3] = self.state[3]+dz*dt 33 | end 34 | end 35 | 36 | function Lorenz:start() 37 | self.metro:start() 38 | end 39 | 40 | function Lorenz:stop() 41 | self.metro:stop() 42 | end 43 | 44 | function Lorenz:reset() 45 | for i = 1,3 do self.state[i] = self.origin[i] end 46 | end 47 | 48 | Lorenz.metro = metro.init{event = function(c) Lorenz:process() end, count = -1, time = 0.001} 49 | 50 | updateEvent = function(c) 51 | for i=1,4 do 52 | local sum = 0 53 | for j=1,3 do 54 | sum = sum+weights[i][j]*Lorenz.state[j] 55 | end 56 | local scaledSum = 10*(sum+25)/80 - 5 57 | if config[i] == 'on' then output[i].volts = scaledSum end 58 | 59 | end 60 | end 61 | 62 | AttractorsMetro = metro.init{event = updateEvent, count = -1, time =0.001} 63 | 64 | input[1].change = function(s) 65 | Lorenz:reset() 66 | end 67 | 68 | input[2].stream = function(volts) 69 | Lorenz.dt = math.exp((volts-1)/3)/1000-0.00005 70 | end 71 | 72 | function init() 73 | Lorenz:reset() 74 | Lorenz:start() 75 | AttractorsMetro:start() 76 | input[1].mode('change', 1,0.1,'rising') 77 | input[2].mode('stream',0.001) 78 | print('Lorenz Attractor loaded') 79 | end 80 | 81 | -------------------------------------------------------------------------------- /boids.lua: -------------------------------------------------------------------------------- 1 | --- boids 2 | -- t gill 190925 3 | -- inspired by http://www.vergenet.net/~conrad/boids/pseudocode.html 4 | -- in1: influence center 5 | -- in2: influence acceleration 6 | -- out1-4: slewed voltage 7 | 8 | -- TODO refine ranges & apply 'expo' where appropriate 9 | public{follow = 0.75}:range(0, 1) -- input=1, free=0. influence of input[1] over centre of mass 10 | public{pull = 0.5}:range(0, 1) -- pull boids toward their centre of mass 11 | public{avoid = 0.1}:range(0, 1) -- voltage difference under which boids repel 12 | public{sync = 1/20}:range(0, 1) -- amount by which boids copy each other's direction 13 | public{limit = 0.05}:range(0, 0.3) -- limit boids instantaneous movement to reduce over-correction 14 | public{timing = 0.02}:xrange(0.001, 0.2) -- timestep for simulation 15 | :action(function(v) for n=1,4 do output[n].slew = v*2 end end) -- calc speed TODO use Hz not seconds? 16 | 17 | boids = {} 18 | COUNT = 4 -- only first 4 are output 19 | 20 | -- artificially provide a 'centre-of-mass' 21 | function centring(b,c) 22 | return (c - b.p) * public.pull 23 | end 24 | 25 | function avoidance(bs,b) 26 | local v = 0 27 | for n=1,COUNT do 28 | if bs[n] ~= b then -- ignore self 29 | local d = bs[n].p - b.p 30 | if math.abs(d) < public.avoid then 31 | v = v - d/2 32 | end 33 | end 34 | end 35 | return v 36 | end 37 | 38 | function syncing(bs,b) 39 | local v = 0 40 | for n=1,COUNT do 41 | if bs[n] ~= b then -- ignore self 42 | v = v + bs[n].v 43 | end 44 | end 45 | v = v / (COUNT-1) 46 | return (v - b.v) * public.sync 47 | end 48 | 49 | function findcentre(bs,c) 50 | local m = 0 51 | for n=1,COUNT do 52 | m = m + bs[n].p 53 | end 54 | m = m/COUNT 55 | return m + public.follow*(c-m) 56 | end 57 | 58 | function move( bs, n, c, v ) 59 | local b = bs[n] 60 | b.v = b.v + centring(b, findcentre(bs, c)) 61 | + avoidance(bs, b) 62 | + syncing(bs, b) 63 | if b.v > public.limit then b.v = public.limit 64 | elseif b.v < -public.limit then b.v = -public.limit end 65 | b.v = b.v * v 66 | b.p = b.p + b.v 67 | return b 68 | end 69 | 70 | function init_boids() 71 | local bs = {} 72 | for n=1,COUNT do 73 | bs[n] = { p = math.random()*3.0 - 1.0 74 | , v = 0 75 | } 76 | end 77 | return bs 78 | end 79 | 80 | function boids_run(c) 81 | local boids = init_boids() 82 | local c = 0 83 | while true do 84 | c = (c % COUNT)+1 -- round-robin calculation 85 | boids[c] = move( boids, c, input[1].volts, (input[2].volts+5.0)/5.0 ) 86 | if c <= 4 then output[c].volts = boids[c].p end -- apply updated voltage to output 87 | clock.sleep(public.timing / COUNT) -- TODO try sleep(0) for maximum speed? 88 | end 89 | end 90 | 91 | function init() 92 | for n=1,4 do public.view.output[n]() end -- visualize location of all 4 boids 93 | clock.run(boids_run) 94 | end 95 | -------------------------------------------------------------------------------- /alphabetsequencer.lua: -------------------------------------------------------------------------------- 1 | ---alphabet sequencer 2 | --start playback for outputs 1-4 with start_playing() 3 | --start playback on just friends with start_jf() 4 | --start playback on w/syn with start_with() 5 | --or start eveything at once with start_everything() 6 | --stop playback on outputs 1-4 with stop_playing() 7 | --stop playback on just friends with stop_jf() 8 | --stop playback on w/syn with stop_with 9 | --or stop everything at once with stop_everything() 10 | --try updating the sequins! 11 | --see comments below for which sequins do what 12 | s = sequins 13 | a = s{4, 6, 4, s{6, 8, 1, 11}} -- voice 1 pitch 14 | b = s{2, 2, 2, 2, 2, 2} -- voice 1 timing 15 | c = s{4, 1, 6, 1, 6} -- voice 2 pitch 16 | d = s{2, 2, 2, 2, 2, 2} -- voice 2 timing 17 | e = s{4, 6, 4, s{11, 1, 8}} -- just friends voice 1 pitch 18 | f = s{1/16, 1/16, 1/4, 1/4} -- just friends voice 1 timing 19 | g = s{2, 3, 2, 2, 3, 2, 3} -- just friends voice 1 level 20 | h = s{4, 6, 4, s{8, 1, 11}} -- just friends voice 2 pitch 21 | i = s{1/4, 1/4, 1/8, 1/16} -- just friends voice 2 timing 22 | j = s{2, 2, 3, 2, 2, 3, 2} -- just friends voice 2 level 23 | k = s{6, 4, 6, 11, 8, 11} -- just friends voice 3 pitch 24 | l = s{1/2, 1/4, 1/2, 1/16} -- just friends voice 3 timing 25 | m = s{2, 3, 2, 2, 3, 3, 2} -- just friends voice 3 level 26 | x = s{4, 6, 1, s{11, 6, 11, 8, 6, 11, 8, 6}} -- w/syn pitch 27 | y = s{1/2, 1/2, 2, 1.5, 1/2} -- w/syn timing 28 | z = s{2, 2, 3, 2, 2, 3, 2, 2} -- w/syn level 29 | function init() 30 | input[1].mode('clock') 31 | bpm = clock.tempo 32 | ii.jf.mode(1) 33 | ii.jf.run_mode(1) 34 | ii.jf.tick(bpm) 35 | ii.wsyn.ar_mode(1) 36 | ii.wsyn.voices(4) 37 | end 38 | function start_playing() 39 | coro_1 = clock.run(notes_event) 40 | coro_2 = clock.run(other_event) 41 | end 42 | function start_jf() 43 | coro_3 = clock.run(jfa_event) 44 | coro_4 = clock.run(jfb_event) 45 | coro_5 = clock.run(jfc_event) 46 | end 47 | function start_with() 48 | coro_6 = clock.run(with_event) 49 | end 50 | function start_everything() 51 | coro_1 = clock.run(notes_event) 52 | coro_2 = clock.run(other_event) 53 | coro_3 = clock.run(jfa_event) 54 | coro_4 = clock.run(jfb_event) 55 | coro_5 = clock.run(jfc_event) 56 | coro_6 = clock.run(with_event) 57 | end 58 | function stop_playing() 59 | clock.cancel(coro_1) 60 | clock.cancel(coro_2) 61 | end 62 | function stop_jf() 63 | clock.cancel(coro_3) 64 | clock.cancel(coro_4) 65 | clock.cancel(coro_5) 66 | end 67 | function stop_with() 68 | clock.cancel(coro_6) 69 | end 70 | function stop_everything() 71 | clock.cancel(coro_1) 72 | clock.cancel(coro_2) 73 | clock.cancel(coro_3) 74 | clock.cancel(coro_4) 75 | clock.cancel(coro_5) 76 | clock.cancel(coro_6) 77 | end 78 | function notes_event() 79 | while true do 80 | clock.sync(b()) 81 | output[1].volts = a()/12 82 | output[1].slew = .1 83 | output[2].action = ar(1, 1, 5, 'lin') 84 | output[2]() 85 | end 86 | end 87 | function other_event() 88 | while true do 89 | clock.sync(d()) 90 | output[3].volts = c()/12 91 | output[3].slew = .1 92 | output[4].action = ar(1, 1, 5, 'lin') 93 | output[4]() 94 | end 95 | end 96 | function jfa_event() 97 | while true do 98 | clock.sync(f()) 99 | ii.jf.play_note(e()/12, g()) 100 | end 101 | end 102 | function jfb_event() 103 | while true do 104 | clock.sync(i()) 105 | ii.jf.play_note(h()/12, j()) 106 | end 107 | end 108 | function jfc_event() 109 | while true do 110 | clock.sync(l()) 111 | ii.jf.play_note(k()/12, m()) 112 | end 113 | end 114 | function with_event() 115 | while true do 116 | clock.sync(y()) 117 | ii.wsyn.play_note(x()/12, z()) 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /krahenlied.lua: -------------------------------------------------------------------------------- 1 | ---krahenlied 2 | --a poetry sequencer for crow, just friends, and w/tape 3 | --input 1: clock 4 | --outputs 1 & 3: v/8 5 | --outputs 2 & 4: AR envelopes 6 | --begin by giving your poem a title in druid using the title function (i.e., typing title followed by your title in quotes), like so: 7 | --title "Christabel" 8 | --this will start the clocks running and create an initial sequence 9 | --continue by updating the sequence in druid by typing text followed by a new line of poetry in quotes — e.g., 10 | --text "'Tis the middle of night by the castle clock," 11 | --note: it's probably best to reset crow after using this script and before using another one or using this one again. Not doing so can lead to crashes 12 | text_string = "aaaaaa" 13 | function remap(ascii) 14 | ascii = ascii % 32 + 1 15 | return ascii 16 | end 17 | function processString(s) 18 | local tempScalar = {} 19 | for i = 1, #s do 20 | table.insert(tempScalar,remap(s:byte(i))) 21 | end 22 | return tempScalar 23 | end 24 | function jfmap(ascii) 25 | ascii = ascii % 5 + 1 26 | return ascii 27 | end 28 | function jfscaling(j) 29 | local tempScalar = {} 30 | for i = 1, #j do 31 | table.insert(tempScalar,jfmap(j:byte(i))) 32 | end 33 | return tempScalar 34 | end 35 | s = sequins(processString(text_string)) 36 | j = sequins(jfscaling(text_string)) 37 | function set() 38 | s:settable(processString(text_string)) 39 | j:settable(jfscaling(text_string)) 40 | end 41 | function title(str) 42 | text_string = str 43 | set() 44 | coro_id = clock.run(notes_event) 45 | clock.run(other_event) 46 | clock.run(jfa_event) 47 | clock.run(jfb_event) 48 | clock.run(jfc_event) 49 | clock.run(jfd_event) 50 | clock.run(jfe_event) 51 | clock.run(jff_event) 52 | clock.run(run_event) 53 | clock.run(quantize_event) 54 | clock.run(with_event) 55 | clock.run(rev_event) 56 | clock.run(looper) 57 | end 58 | function text(str) 59 | text_string = str 60 | set() 61 | end 62 | function init() 63 | input[1].mode('clock') 64 | bpm = clock.tempo 65 | ii.jf.mode(1) 66 | ii.jf.run_mode(1) 67 | ii.jf.tick(bpm) 68 | ii.wtape.timestamp(1) 69 | ii.wtape.freq(0) 70 | ii.wtape.play(1) 71 | end 72 | function notes_event() 73 | while true do 74 | clock.sync(s()/s:step(2)()) 75 | output[1].volts = s:step(3)()/12 76 | output[1].slew = s:step(4)()/300 77 | output[2].action = ar(s:step(5)()/20, s:step(6)()/20, j:step(7)(), 'linear') 78 | output[2]() 79 | end 80 | end 81 | function other_event() 82 | while true do 83 | clock.sync(s:step(8)()/s:step(9)()) 84 | output[3].volts = s:step(10)()/12 85 | output[3].slew = s:step(11)()/300 86 | output[4].action = ar(s:step(12)()/20, s:step(13)()/20, j:step(14)(), 'linear') 87 | output[4]() 88 | end 89 | end 90 | function jfa_event() 91 | while true do 92 | clock.sync(s:step(15)()/s:step(16)()) 93 | ii.jf.play_voice(1, s:step(17)()/12, j:step(18)()) 94 | end 95 | end 96 | function jfb_event() 97 | while true do 98 | clock.sync(s:step(19)()/s:step(20)()) 99 | ii.jf.play_voice(2, s:step(21)()/12, j:step(22)()) 100 | end 101 | end 102 | function jfc_event() 103 | while true do 104 | clock.sync(s:step(23)()/s:step(24)()) 105 | ii.jf.play_voice(3, s:step(25)()/12, j:step(26)()) 106 | end 107 | end 108 | function jfd_event() 109 | while true do 110 | clock.sync(s:step(27)()/s:step(28)()) 111 | ii.jf.play_voice(4, s:step(29)()/12, j:step(30)()) 112 | end 113 | end 114 | function jfe_event() 115 | while true do 116 | clock.sync(s:step(31)()/s:step(32)()) 117 | ii.jf.play_voice(5, s:step(33)()/12, j:step(34)()) 118 | end 119 | end 120 | function jff_event() 121 | while true do 122 | clock.sync(s:step(35)()/s:step(36)()) 123 | ii.jf.play_voice(6, s:step(37)()/12, j:step(38)()) 124 | end 125 | end 126 | function run_event() 127 | while true do 128 | clock.sync(s:step(39)()/s:step(40)()) 129 | ii.jf.run(j:step(41)()) 130 | end 131 | end 132 | function quantize_event() 133 | while true do 134 | clock.sync(s:step(42)()/s:step(43)()) 135 | ii.jf.quantize(s:step(44)()) 136 | end 137 | end 138 | function with_event() 139 | while true do 140 | clock.sync(s:step(45)()/s:step(46)()) 141 | ii.wtape.speed(s:step(47)(), s:step(48)()) 142 | end 143 | end 144 | function rev_event() 145 | while true do 146 | clock.sync(s:step(49)()/s:step(50)()) 147 | ii.wtape.reverse() 148 | end 149 | end 150 | function looper() 151 | while true do 152 | clock.sync(s:step(51)()/s:step(52)()) 153 | ii.wtape.loop_start() 154 | clock.sync(s:step(53)()/s:step(54)()) 155 | ii.wtape.loop_end() 156 | if s:step(55)() < 17 then 157 | for i = 1,j:step(56)() do 158 | clock.sync(s:step(57)()/s:step(58)()) 159 | ii.wtape.loop_scale(s:step(59)()/s:step(60)()) 160 | for i = 1,j:step(61)() do 161 | clock.sync(s:step(62)()/s:step(63)()) 162 | ii.wtape.loop_next(s:step(64)()-s:step(65)()) 163 | end 164 | end 165 | elseif s:step(55)() >= 17 then 166 | for i = 1,j:step(66)() do 167 | clock.sync(s:step(67)()/s:step(68)()) 168 | ii.wtape.loop_next(s:step(69)()-s:step(70)()) 169 | for i = 1,j:step(71)() do 170 | clock.sync(s:step(72)()/s:step(73)()) 171 | ii.wtape.loop_scale(s:step(74)()/s:step(75)()) 172 | end 173 | end 174 | end 175 | clock.sync(s:step(76)()/s:step(77)()) 176 | ii.wtape.loop_active(0) 177 | for i = 1,s:step(78)() do 178 | clock.sync(s:step(79)()/s:step(80)()) 179 | ii.wtape.seek((s:step(81)()*300)-(s:step(82)()*300)) 180 | end 181 | for i = 1,j:step(83)() do 182 | clock.sync(s:step(84)()/s:step(85)()) 183 | ii.wtape.loop_active(1) 184 | if s:step(86)() < 17 then 185 | for i = 1,j:step(87)() do 186 | clock.sync(s:step(88)()/s:step(89)()) 187 | ii.wtape.loop_scale(s:step(90)()/s:step(91)()) 188 | for i = 1,j:step(92)() do 189 | clock.sync(s:step(93)()/s:step(94)()) 190 | ii.wtape.loop_next(s:step(95)()-s:step(96)()) 191 | end 192 | end 193 | elseif s:step(86)() >= 17 then 194 | for i = 1,j:step(97)() do 195 | clock.sync(s:step(98)()/s:step(99)()) 196 | ii.wtape.loop_next(s:step(100)()-s:step(101)()) 197 | for i = 1,j:step(102)() do 198 | clock.sync(s:step(103)()/s:step(104)()) 199 | ii.wtape.loop_scale(s:step(105)()/s:step(106)()) 200 | end 201 | end 202 | end 203 | clock.sync(s:step(107)()/s:step(108)()) 204 | ii.wtape.loop_active(0) 205 | for i = 1,s:step(109)() do 206 | clock.sync(s:step(110)()/s:step(111)()) 207 | ii.wtape.seek((s:step(112)()*300)-(s:step(113)()*300)) 208 | end 209 | end 210 | end 211 | end 212 | -------------------------------------------------------------------------------- /cali.lua: -------------------------------------------------------------------------------- 1 | --- cali 2 | -- utility for calibrating crow's CV i/o 3 | -- 4 | -- keep druid open while running! 5 | -- 6 | -- when the script starts, it will run the calibration automatically 7 | -- input[1] and all outputs will be calibrated automatically 8 | -- then you'll be prompted to add a patch cable 9 | -- connect output[1] to input[2] 10 | -- now the calibration will complete 11 | -- if ALWAYS_TEST is true, you'll be prompted to remove the patch cable 12 | -- test results will appear gradually 13 | -- 14 | -- re-run the calibration with: calibrate() 15 | -- run the test-suite with: test() 16 | -- print the raw offsets & scalars with: raw() 17 | 18 | 19 | -- settings 20 | local AUTOSTART = true -- if false, you can calibrate by typing: calibrate() 21 | local ALWAYS_TEST = true -- if true, the test() function will autorun after calibrate() 22 | 23 | -- debug: use when working on this script 24 | local AUTOSAVE = true -- set false while debugging to reduce flash write cycles 25 | local ALWAYS_RAW = false -- set true to print raw offset & scale values after calibrate() 26 | local ITERATIONS = 1 -- set higher than 1 to show that you have found the *correct* solution 27 | 28 | 29 | --- these functions are generally unused 30 | -- values seem to be accurate enough by simply measuring & calculating directly 31 | -- no need to measure, update, re-measure (etc) 32 | 33 | function lerp(from, to, coeff) 34 | return from + coeff*(to - from) 35 | end 36 | 37 | -- diff_multiplier is 100 ~ 2000 or so. sets convergence rate / sensitivity 38 | function adaptive_filter(past, new, diff_multiplier) 39 | local coeff = math.min(1, diff_multiplier * math.abs(past - new)) 40 | local dest = lerp(past, new, coeff) 41 | return dest 42 | end 43 | 44 | 45 | -- specifically tuned for minimal standard deviation across multiple calls 46 | -- see: util/adda-measurement.lua for methodology 47 | -- settling time increased for out[3]. limit ~12ms. 20ms for safety 48 | function read_avg(chan) 49 | local reps = 256 50 | local sum = 0 51 | clock.sleep(0.02) -- input & output settling time 52 | for i=1,reps do 53 | sum = sum + input[chan].volts -- accumulate readings 54 | clock.sleep(0.001) 55 | end 56 | return sum / reps -- average 57 | end 58 | 59 | function xvolts(ch, v) 60 | for n=1,4 do 61 | output[n].volts = n==ch and v or 0 62 | end 63 | end 64 | 65 | function sample(t) 66 | if t.volts then xvolts(t.source, t.volts) end -- set source voltage exclusively 67 | cal.source(t.source) -- select source 68 | return read_avg(t.input) 69 | end 70 | 71 | function input_scale(n) 72 | local gnd, vref 73 | if n==1 then 74 | gnd = sample{input=n, source='gnd'} 75 | vref = sample{input=n, source='2v5'} 76 | else -- n==2 77 | gnd = sample{input=n, source=1, volts=0} 78 | vref = sample{input=n, source=1, volts=2.5} 79 | end 80 | 81 | local past = cal.input[n].scale -- capture past scalar 82 | local new = 2.5/(vref-gnd) -- determine new scalar 83 | new = new * past -- account for past scalar 84 | 85 | -- move toward solution 86 | cal.input[n].scale = adaptive_filter(past, new, 1000) 87 | 88 | print(string.format('input[%i].scale = % 6.5f (% 6.5f, % 6.5f)',n,cal.input[n].scale,gnd,vref)) 89 | end 90 | 91 | function input_offset(n) 92 | local gnd 93 | if n==1 then 94 | gnd = sample{input=n, source='gnd'} 95 | else -- n==2 96 | gnd = sample{input=n, source=1, volts=0} 97 | end 98 | 99 | local past = cal.input[n].offset -- capture past offset 100 | local new = gnd -- determine new offset 101 | new = past - new -- account for past offset 102 | 103 | -- move toward solution 104 | cal.input[n].offset = adaptive_filter(past, new, 300) 105 | 106 | print(string.format('input[%i].offset = % 6.5f (% 6.5f)',n,cal.input[n].offset,gnd)) 107 | end 108 | 109 | function output_scale(n) 110 | local gnd = sample{input=1, source=n, volts=0} 111 | local vref = sample{input=1, source=n, volts=2.5} 112 | 113 | local past = cal.output[n].scale -- capture past scalar 114 | local new = 2.5/(vref-gnd) -- determine new scalar 115 | new = new * past -- account for past scalar 116 | 117 | -- move toward solution 118 | cal.output[n].scale = adaptive_filter(past, new, 1000) 119 | 120 | print(string.format('output[%i].scale = % 6.5f (% 6.5f, % 6.5f)',n,cal.output[n].scale,gnd,vref)) 121 | end 122 | 123 | function output_offset(n) 124 | local gnd = sample{input=1, source=n, volts=0} 125 | 126 | local past = cal.output[n].offset -- capture past offset 127 | local new = gnd -- determine new offset 128 | new = new / cal.output[n].scale -- account for scale being applied already 129 | new = past - new -- account for past offset 130 | 131 | -- move toward solution 132 | cal.output[n].offset = adaptive_filter(past, new, 300) 133 | 134 | print(string.format('output[%i].offset = % 6.5f (% 6.5f)',n,cal.output[n].offset,gnd)) 135 | end 136 | 137 | function await_input(n) 138 | if sample{input=n, source=1, volts=-3} > -2 then 139 | if n == 1 then print '\ndisconnect all patch cables' 140 | else print '\nconnect output[1] -> input[2]' end 141 | 142 | while sample{input=n, source=1, volts=-3} > -2 do 143 | tell('stream',n,input[n].volts) 144 | clock.sleep(0.1) 145 | end 146 | print'ok!' 147 | clock.sleep(1) -- wait 1 second to ensure both ends of cable are disconnected 148 | end 149 | end 150 | 151 | function clear_cal_settings() 152 | for n=1,2 do 153 | cal.input[n].offset = 0 154 | cal.input[n].scale = 1.0 155 | end 156 | for n=1,4 do 157 | cal.output[n].offset = 0 158 | cal.output[n].scale = 1.0 159 | end 160 | end 161 | 162 | function find_error_in_cents(result, expected) 163 | return math.floor(0.5 + 1200*(result - expected)) 164 | end 165 | 166 | function test() 167 | clock.run(function() 168 | -- ensure there's no cable remaining from calibration 169 | await_input(1) 170 | 171 | local pass = true -- assume true unless we see an error 172 | local failmsgs = {} 173 | -- 30, 3, 5, 12, 30 174 | 175 | print'\nvoltage listed to the nearest millivolt' 176 | print'error amounts listed in cents beneath the readings' 177 | print' expect: -2.5 0.0 2.5 5.0 7.5\n' 178 | 179 | -- input 1 180 | local gnd = sample{input=1, source='gnd'} 181 | local vref = sample{input=1, source='2v5'} 182 | local gnd_err = find_error_in_cents(gnd, 0) 183 | local vref_err = find_error_in_cents(vref, 2.5) 184 | print(string.format('input[1] % 01.3f % 01.3f',gnd, vref)) 185 | print(string.format(' % 7d % 7d',gnd_err,vref_err)) 186 | if math.abs(gnd_err) > 3 then pass = false end 187 | if math.abs(vref_err) > 3 then pass = false end 188 | 189 | -- input 2 190 | gnd = sample{input=2, source='gnd'} 191 | gnd_err = find_error_in_cents(gnd, 0) 192 | print(string.format('input[2] % 01.3f', gnd)) 193 | print(string.format(' % 7d',gnd_err)) 194 | if math.abs(gnd_err) > 3 then pass = false end 195 | 196 | for n=1,4 do 197 | local V = {-2.5, 0, 2.5, 5, 7.5} -- test voltages 198 | local E = { 30, 3, 5, 12, 30} -- allowable cents error at each voltage 199 | local t = {} -- test results 200 | local e = {} -- error calculation in cents 201 | for k,v in ipairs(V) do 202 | t[k] = sample{input=1, source=n, volts = v} 203 | e[k] = find_error_in_cents(t[k], v) 204 | if math.abs(e[k]) > E[k] then 205 | pass = false 206 | table.insert(failmsgs, 'output['..n..'] @'..v..'V out of range') 207 | end 208 | end 209 | print(string.format('output[%i] % 01.3f % 01.3f % 01.3f % 01.3f % 01.3f',n 210 | ,t[1],t[2],t[3],t[4],t[5])) 211 | print(string.format(' % 7d % 7d % 7d % 7d % 7d' 212 | ,e[1],e[2],e[3],e[4],e[5])) 213 | end 214 | if pass then 215 | print('\n---- PASS ----') 216 | else 217 | print('\n!!!! FAIL !!!!') 218 | print(table.concat(failmsgs, ", ")) 219 | end 220 | end) 221 | end 222 | 223 | function raw() 224 | local function pprintset(key) 225 | print(key..'s: ') 226 | for k,v in ipairs(cal[key]) do 227 | print(string.format(' %i offset: % 01.4f, scale:% 01.4f', k, v.offset, v.scale)) 228 | end 229 | end 230 | print' offset should be near 0' 231 | print' scale should be near 1' 232 | pprintset'input' 233 | pprintset'output' 234 | end 235 | 236 | 237 | function calibrate() 238 | clock.run( function() 239 | clear_cal_settings() -- clear everything before we start 240 | 241 | -- input 1 242 | await_input(1) 243 | for i=1,ITERATIONS do 244 | input_scale(1) 245 | input_offset(1) 246 | end 247 | 248 | -- outputs 249 | for i=1,ITERATIONS do 250 | for n=1,4 do 251 | output_scale(n) 252 | output_offset(n) 253 | end 254 | end 255 | 256 | -- input 2 257 | await_input(2) 258 | for i=1,ITERATIONS do 259 | input_scale(2) 260 | input_offset(2) 261 | end 262 | 263 | if AUTOSAVE then cal.save() end 264 | if ALWAYS_RAW then raw() end 265 | if ALWAYS_TEST then test() end 266 | end) 267 | end 268 | 269 | function init() 270 | print('///////////////////////////') 271 | if AUTOSTART then calibrate() end 272 | end --------------------------------------------------------------------------------