^^ crow: druid scripts

This is perfect. Thanks @Justmat

1 Like

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

4 Likes

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).

13 Likes

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 :slight_smile:

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)

10 Likes

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
5 Likes

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?

2 Likes

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,

2 Likes

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.

3 Likes

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.

2 Likes

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.

11 Likes

Thanks @Galapagoose for a wonderful answer!

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.)

8 Likes

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?

1 Like

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() calls quadrant(). 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 handle stream on both inputs. You could get away with only one input, because as written quadrant() doesn’t actually pay attention to the stream 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 examine input[1] and input[2]. You do not distinguish between how quadrant() behaves when called from input[1] or from input[2], so you do not need to drive it from both events. In fact, you don’t really need to use stream at all. You could as easily drive it from metro or clock.)
  • You don’t need to recalculate the outputs unless input[1] or input[2] changes significantly, so you could either store the last-observed values for the inputs and return from quadrants() immediately if hte new values are close to the old ones, or try to use window instead of stream so that you only call quadrant() when the voltages change value. (I’m not sure how well the window approach will work, because you will probably need a lot of windows.) At any rate, the less work you do every time you run quadrant(), or the less frequently it gets called at all, the easier it will be for crow to keep up.
3 Likes

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.

3 Likes

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!

1 Like