This is perfect. Thanks @Justmat
Hi there,
Just managed to get a crow and am in the middle of writing my first script (I have no lua skills, just some teletype experience). The concept is that on a trigger (input1), the crow outputs a total of 5V (could be any value tbh) randomly across the 4 outputs. These outputs control the levels of 4 channels on my mixer. On the next trigger, the 5volts are re-distributed (with a lot of slew). So it’s a kind of auto-mixing script (the total volume never goes above a certain level, so if channel one is loud the other three channels must be quieter etc.)
I have this working but I would like a trigger on input2 to randomise the output order. At the moment, my script is picking a random voltage for output1 first then 2, 3 then 4. What this means is the voltage tends to be lowest in the latter outputs as there is less of the 5V to use by the time you get to output 4. And channel 1 has the highest chance of being loudest.
What I wanted to try was a simple array (or sequins?) that has the values 1 - 4 but that I can randomise the order but without repeating any values (until it all resets on the next trig1). I have done a similar thing in teletype and used the tracker with the P.SHUF op.
I can’t attach the script as I’m too new to the forum so here’s a c+p - any pointers would be welcome (I’m sure there’s better ways to do what I’m trying to do!)
— WMD 4 channel random levels
– in1: VCA change trigger
– in2: randomise output order
totalv = 0
function init()
input[1].mode(‘change’,1.0,0.1,‘rising’)
input[2].mode(‘change’,1.0,0.1,‘rising’)
for n = 1,4 do
output[n].slew = 10
output[n].volts = 0
end
end
input[1].change = function(state)
rand1 = math.random() * 5
output[1].volts = rand1
totalv = rand1
rand2 = math.random() * (5 - totalv)
output[2].volts = rand2
totalv = totalv + rand2
rand3 = math.random() * (5 - totalv)
output[3].volts = rand3
totalv = totalv + rand3
rand4 = 5 % totalv
output[4].volts = rand4
totalv = totalv + rand4
end
input[2].change = function(state2)
– randomise output order using arrays or seqs
end
cool script idea!
quick moderation note: you can wrap your code in three backticks (```) at top and bottom of your script so that it’s nicely formatted.
to answer your question, i’d probably create an array to hold the output mappings: chans = {1, 2, 3, 4}
then in the input[2].change
event, you’ll need to rotate the elements of chans
. this is easiest by using lua’s table library:
first = table.remove(chans,1)
will remove the first element of the list.
table.insert(chans, -1, first)
will place the removed value at the end (negative indices count backwards from the end).
then you just need to update the input[1].change
event wherever you touch the output from output[1].volts
to output[chans[1]].volts
etc.
alternatively, if you wanted a solution that evenly weighted all the channels, i’d start by generating 4 random values, finding their sum, then dividing all 4 random values by the sum resulting in a total of 1
across all elements.
that said, i think your solution might be more interesting, having a “focus” channel. you could also try another mode where the continuous voltage at input[2] selects which channel is “focussed” (the "window"
input mode would be useful here).
Awesome. Thanks for your help, that gives me enough to go on for sure. I like the alt idea for the input[2] selecting the focussed channel. Would allow some shaping or control of the randomness which could be useful. Could also be interesting to mult one of the outputs and use that so there’s some feedback. I’ll post the next version when I’ve done it.
Cheers!
EDIT:
Got it working now - thanks for the pointers. And ended up with three different flavours
All three scripts designed for 4 channel VCA ‘auto-mix’ control. Slew (min and max), fade type and max output voltage (default is 8V, though would sometimes be better at 5V) all user definable at the top of the scripts.
Ver1 is input[1] random VCA order + levels (trigger), input[2] randomises slew time (trigger)
(allows for more experimental variable slew level changes, perhaps more useful for faster triggering)
--- WMD 4 channel random levels V1
-- in1: VCA change trigger
-- in2: randomise output slew trigger
chans = {1, 2, 3, 4}
totalv = 0
maxv = 5 -- put max total VCA voltage here
slmin = 0 -- put min slew time here
slmax = 30 -- put max slew time here
function init()
input[1].mode('change',1.0,0.1,'rising')
input[2].mode('change',1.0,0.1,'rising')
sl = math.random(slmin,slmax)
for n = 1,4 do
output[n].shape = 'logarithmic' -- set fade type here
output[n].slew = sl
output[n].volts = 0
end
end
input[1].change = function(randvca)
rand1 = math.random() * maxv
outsel = math.random(4)
output[chans[outsel]].volts = rand1
totalv = rand1
table.remove (chans, outsel)
rand2 = math.random() * (maxv - totalv)
outsel = math.random(3)
output[chans[outsel]].volts = rand2
totalv = totalv + rand2
table.remove (chans, outsel)
rand3 = math.random() * (maxv - totalv)
outsel = math.random(2)
output[chans[outsel]].volts = rand3
totalv = totalv + rand3
table.remove (chans, outsel)
rand4 = maxv % totalv
output[chans[1]].volts = rand4
totalv = totalv + rand4
chans = {1,2,3,4}
end
input[2].change = function(slew)
sl = math.random(slmin,slmax)
for i = 1, 4 do
output[i].slew = sl
end
end
Ver2 is input[1] random VCA levels (trigger), input[2] randomises the output order (trigger)
(this allows for less random mixing when using less input[2] triggers)
--- WMD 4 channel random levels V2
-- in1: VCA change trigger
-- in2: randomise output order trigger
chans = {1, 2, 3, 4}
totalv = 0
maxv = 8 -- put max total VCA voltage here
sl = 10 -- put slew time here
function init()
input[1].mode('change',1.0,0.1,'rising')
input[2].mode('change',1.0,0.1,'rising')
for n = 1,4 do
output[n].shape = 'logarithmic' -- set fade type here
output[n].slew = sl
output[n].volts = 0
end
end
input[1].change = function(randvca)
rand1 = math.random() * maxv
output[chans[1]].volts = rand1
totalv = rand1
rand2 = math.random() * (maxv - totalv)
output[chans[2]].volts = rand2
totalv = totalv + rand2
rand3 = math.random() * (maxv - totalv)
output[chans[3]].volts = rand3
totalv = totalv + rand3
rand4 = maxv % totalv
output[chans[4]].volts = rand4
totalv = totalv + rand4
end
input[2].change = function(randout)
for i = 1, 4 do
outsel = table.remove (chans, math.random(5 - i))
table.insert (chans, outsel)
end
end
Ver3 is input[1] random VCA levels (trigger), input[2] selects the dominant channel (0-5V)
(useful perhaps for a more controlled mix)
--- WMD 4 channel random levels V3
-- in1: VCA change trigger
-- in2: select lead output cont. voltage (0-5V)
chans = {1, 2, 3, 4}
totalv = 0
maxv = 8 -- put max total VCA voltage here
sl = 10 -- put slew time here
function init()
input[1].mode('change',1.0,0.1,'rising')
input[2].mode( 'window', {0,1.25,2.5,3.75,5}, 0.1)
for n = 1,4 do
output[n].shape = 'logarithmic' -- set fade type here
output[n].slew = sl
output[n].volts = 0
end
end
input[1].change = function(randvca)
rand1 = math.random() * maxv
output[chans[1]].volts = rand1
totalv = rand1
rand2 = math.random() * (maxv - totalv)
output[chans[2]].volts = rand2
totalv = totalv + rand2
rand3 = math.random() * (maxv - totalv)
output[chans[3]].volts = rand3
totalv = totalv + rand3
rand4 = maxv % totalv
output[chans[4]].volts = rand4
totalv = totalv + rand4
end
input[2].window = function(state)
outsel = state - 1
for i = 1, 4 do
if chans[i] == outsel then
outsel2 = table.remove (chans, i)
table.insert (chans, 1, outsel2)
else
-- do nothing
end
end
end
WMD rand levels V3.lua (1.3 KB)
WMD rand levels V1.lua (1.5 KB)
WMD rand levels V2.lua (1.2 KB)
I want to make a script with something like “when nothing is patched to input 1, normalised to a random voltage.”
if input[1].stream == nil then
x = math.rand() else
x = input[1].volts
end
Any suggestions is welcome!
Hmmm. I don’t think there’s a way to detect if a voltage is plugged into the input. However, the input accepts down to -5V input and up to +10V, and when the input is unplugged, the input voltage will be around 0V.
You could do something similar: if the input voltage (v) is below 0.01V (around zero) it will set x to math.rand(), otherwise it will set x to v. Or set this to < 0V if you’ve got access to a negative voltage maybe.
input[1].stream = function(v)
x = (v <= 0.01) and math.rand() or v
end
I am having trouble with several fundamental concept on Lua and basic programming knowledge in general.
Is there any different between this-
input[1].stream = function(x)
x = math.min(input[1].volts, 5)
end
-- to me it looks like "when input 1 is receiving value(voltage), do the math and assign its answer to variable x
and this-
x = math.min(input[1].volts, 5)
-- to me it looks like plainly "do the math and assign its answer to variable x"
Another question: Quoted from script reference section-
input[1].stream = function(volts) <your_inline_function> end
or
input[n]{ mode = 'stream'
, time = 0.2
, stream = function(v) print(v) end -- assign the handler inline
}
Where are volts and v come from? Can I interpret that those are global variables from the system under the hood? If so, are they interchangeable?
This!
For me it was difficult to figure out these kind of “too obvious to explain”-things. Some things can be pieced together from the reference, examples, threads and github, but I think putting it together is a pretty big cognitive load. Some things I had trouble with (not asking for help now btw) are event-handlers, responses from get via ii, and clocks. Other questions I had were, as above, why “v”, where does that come from and a general confusion as to what are valid arguments for a lot of the premade functions.
Having a technical map-level manual/reference for crow would be a dream,
Totally agreed. It makes Crow a bit difficult to get into, and can be confusing when you’re new to Lua (like me). Good to know I’m not the only one struggling with this. The TT language is in that sense a lot ‘clearer’, as theres less ‘hidden’. Or at least it feels like that. Still enjoy both though, but my music making brain prefers TT for sure.
As far as my limited programming knowledge goes I‘d say volts and v are arbitrary. In this context it just makes sense, and helps readability to use v or volts but you could also use something like foo. Basically you‘re assigning whatever the functions returns to the variable.
I think the difficulty here is that the crow docs aren’t trying to teach you how to program in lua, they assume a certain base level of knowledge. I would love to have a “learn lua by coding for crow” set of documentation, but it would be a novel in size – something we simply don’t have the resources to create.
To focus on this specific issue, the key to understanding it is that the crow system will be calling into your script’s code. This is called a “callback” or an “event handler”, and for the input stream
mode, it will call input[1].stream
(for input 1) function.
This event will deliver a single argument (the x
in function(x)
above), which is already set to the value of the input. The event is giving you a piece of information that you can use in your script. In the example above, you call input[1].volts
which actually grabs the exact same value that is already in the variable x
. The choice of x
here is arbitrary, and can be swapped to any other variable name.
Lastly, you will need to do something with x
other than assigning to it (ie. overwriting it). Typically this would be calling an output
method, sending an ii
message, or calling some other function that does those things. Note that the value in x
is discarded once the function completes.
I might suggest watching a couple Lua tutorials to learn a bit more about how functions & variables work. Once you get over those hurdles, I believe the code will feel far more intuitive and less arcane.
Another thing that might be useful to think about, if you are not used to event-based programming, is that there are actually two ways that your code gets executed in a crow script.
First, whenever you type a line in druid or load a script into crow, each line gets executed right there. That’s what is happening in the x = ...
statements in the original question.
Second, there are also events that occur outside of your code, such as when a metronome ticks over, or the voltage at one of the inputs changes. You can define bits of code that are executed when these events occur. That’s what is happening in the input[1].stream = function(v) ...
statements. You are defining what will happen every time input 1 reads the voltage on the jack. The code inside the function will be executed with v
set to the jack’s voltage each time the “stream” event occurs, but nothing happens at all at the moment that you type in the input[1].stream = ...
statement.
Teletype actually works the same way, but it is easier to understand because there are fewer events (mostly the trigger inputs and the metronome) and there are separate screens for the “live” input and for each of the event handlers (scripts). Think of input[1].stream = ...
as similar to filling in the screen for script 1 in Teletype.
On crow, you mix the live input and the event handlers together any way you like, and they all look more or less the same, so it is harder at first to see the difference.
(For the programmers out there: I’m writing this off-the-cuff and yes the terminology is all over the place. Corrections are welcome, but remember we aren’t the audience.)
Thanks @Galapagoose and @3-foot-1! I think I now understand more about what callback (aka event handler) actually means.
I’m making a script but it still doesn’t work
1 --- coordinates cv generator
2 -- vinc
3 -- in1: x-axis
4 -- in2: y-axis
5 -- out1-4: magnitude in corresponding quadrants
6
7 function quadrants()
8 x = math.min(input[1].volts, 5)
9 y = math.min(input[2].volts, 5)
10 h = math.sqrt(math.pow(x, 2) + math.pow(y, 2)) -- hypotenuse
11 r = math.atan2(y, x) -- radian
12
13 mainQuart = h * ((math.pi/2) - math.abs(math.pi/4 - r)) / (math.pi/2)
14 adjacentL = h * math.max(((math.pi/2) - math.abs(math.pi*3/4 - r)) / (math.pi/2), 0)
15 adjacentR = h * math.max(((math.pi/4) - r) / (math.pi/2), 0)
16
17 if x > 0 and y > 0 then -- quadrant i
18 output[1].volts = mainQuart
19 output[2].volts = adjacentL
20 output[3].volts = 0
21 output[4].volts = adjacentR
22
23 elseif x < 0 and y > 0 then -- quadrant ii
24 output[2].volts = mainQuart
25 output[3].volts = adjacentL
26 output[4].volts = 0
27 output[1].volts = adjacentR
28
29 elseif x < 0 and y < 0 then -- quadrant iii
30 output[3].volts = mainQuart
31 output[4].volts = adjacentL
32 output[1].volts = 0
33 output[2].volts = adjacentR
34
35 elseif x > 0 and y < 0 then -- quadrant iv
36 output[4].volts = mainQuart
37 output[1].volts = adjacentL
38 output[2].volts = 0
39 output[3].volts = adjacentR
40
41 elseif x == 0 and y > 0 then -- positive y-axis
42 output[1].volts = h * 0.5
43 output[2].volts = h * 0.5
44 output[3].volts = 0
45 output[4].volts = 0
46
47 elseif x < 0 and y == 0 then -- negative x-axis
48 output[2].volts = h * 0.5
49 output[3].volts = h * 0.5
50 output[4].volts = 0
51 output[1].volts = 0
52
53 elseif x == 0 and y < 0 then -- negative y-axis
54 output[3].volts = h * 0.5
55 output[4].volts = h * 0.5
56 output[1].volts = 0
57 output[2].volts = 0
58
59 elseif x > 0 and y == 0 then -- positive x-axis
60 output[4].volts = h * 0.5
61 output[1].volts = h * 0.5
62 output[2].volts = 0
63 output[3].volts = 0
64
65 else -- origin
66 output[1].volts = 0
67 output[2].volts = 0
68 output[3].volts = 0
69 output[4].volts = 0
70 end
71 end
72
73 function int()
74 for n = 1, 2 do
75 input[n]{ mode = 'stream'
76 , time = 0.001
77 , stream = quadrants
78 }
79 end
80 for n = 1, 4 do
81 output[n].slew = 0.002
82 end
83 end
When I execute quadrants()
in druid it seems to react for just ‘one shot’, but what I wanted is it runs continuously when the inputs stream.
away from crow at the moment but caught this at line 73:
since i don’t see int()
called anywhere else to establish the 'stream'
behavior, i’m curious if renaming this to function init()
(which is automatically called at script start / initialization) solves the trouble?
Thanks @dan_derks ! Now it’s runnung as it seems. However druid keep saying event queue full!
, may I know what does that mean?
If I understand correctly, you are entering these scripts in druid and then perhaps calling init()
by hand? I.e., something that looks like this in the druid screen?
> function quadrant()
> ...
> end
> function init()
> ...
> end
> init()
If you do that, then once you have executed the init()
line, quadrant()
will be registered as the event handler for stream
on both input 1 and input 2. So quadrant()
will be called twice every millisecond. @Galapagoose can confirm, but I would not be surprised if that is more work than crow can actually do in such a short period of time. (I expect the call to math.atan2
is pretty expensive.)
Each millisecond, two calls to quadrant()
are put on the event queue, waiting to be run by crow. If crow can’t get through both calls in less than one millisecond, then two more calls will be put on the queue before it is done, and the queue will slowly grow, until it is full. That, I think, is why you see the error message.
There are few things you can do.
- First of all, you can slow down the rate at which
stream()
callsquadrant()
. You probably don’t need to do this every millisecond. I bet 10 milliseconds would be just fine. - Second, you don’t really need to register
quadrant()
to handlestream
on both inputs. You could get away with only one input, because as writtenquadrant()
doesn’t actually pay attention to thestream
event. That would cut the amount of work crow needs to do in half. (In more detail:stream
expects a function that takes one argument: the voltage at the input that generates the stream event. So,input[1]{ mode='stream', time=0.1, stream=function(v) print(v) end }
would print out the voltage at input 1 ten times per second.quadrant()
doesn’t take an argument at all; instead you directly examineinput[1]
andinput[2]
. You do not distinguish between howquadrant()
behaves when called frominput[1]
or frominput[2]
, so you do not need to drive it from both events. In fact, you don’t really need to usestream
at all. You could as easily drive it frommetro
orclock
.) - You don’t need to recalculate the outputs unless
input[1]
orinput[2]
changes significantly, so you could either store the last-observed values for the inputs andreturn
fromquadrants()
immediately if hte new values are close to the old ones, or try to usewindow
instead ofstream
so that you only callquadrant()
when the voltages change value. (I’m not sure how well thewindow
approach will work, because you will probably need a lot of windows.) At any rate, the less work you do every time you runquadrant()
, or the less frequently it gets called at all, the easier it will be for crow to keep up.
Thank @3-foot-1 for your detailed explanation! I now use metro[1].event = quadrants
and metro[1].time = 0.01
instead. Working as intended!
Read my full script here if anyone interested.
I’m trying to script a dynamic envelope follower for Crow. The idea is that the loudness of the signal would alter the envelope length. I suppose I should use input mode ‘change’ for this, though amplitude following would be better suited for ‘volume’, I presume.
If I were to use ‘change’, how could I measure the incoming signal and then use it for changing the release length of ar? Additionally, if I were to use output 2 for negative envelope (same envelope but -CV), how would I write the ar function for negative values?
function init()
end
input[1]{mode = 'change', threshold = 0,3, hysteresis = 0,1, direction = 'rising'}
input[1].change = function(level)
output[1].action = ar(0.01,0.2,4,linear)
output[1]()
end
I believe input[n].volume
might be more suitable for typical envelope follower application.
For negative envelope you can do something like this:
output[1].volts = volume * -0.5
or
output[1].volts = math.min(volume * -1, -5)
crow’s voltage output range is -5,10v so you need to scale down the original output voltage in half, and use * -1
to make it negative.
I’m still learning so my answer might not be correct. Happy to see more discussions about crow scripting!