Crow: how to mix output actions

I want to output two LFOs with different shapes, levels and frequencies simultaneously at one output, so that the signals of both LFOs are mixed.

Being a novice in programming, I got so far that I have to assign an LFO to an output action. But it looks I can not just add 2 LFOs in an output action, as I get an arithmetic error. Please, how can achieve this goal? Thanks in advance for any help!

EDIT
Here’s another example. I have a VCO with only one pitch CV input. I want to control it with a CV from my MIDI-CV interface, and I want to add a vibrato to it. While I ran out of LFOs, attenuators and unity gain mixers, I still have a crow. So I want to patch the pitch CV into crow input 1, create an LFO inside crow, mix the LFO with the pitch CV from input 1, and output the sum at crow’s output 1.

I can “pass thru” the CV at input 1 with the script below, but when I add an LFO action, its result get “overwritten” with the passed thru CV. Please, what can I do to mix both?

function init()
	input[1].mode( 'stream', 0.001 )
end

function stream_input_1_to_output_1()
 	output[1].volts = input[1].volts
end

input[1].stream = stream_input_1_to_output_1

Thanks much in advance!

1 Like

This is an interesting problem! I feel like this might be on the right track, but the problem here is that the LFO gets restarted on every stream event. I’m still trying to wrap my head around how to fix that problem, but maybe this will get you thinking?

--- vibrato
-- in1: pitch
-- out1: pitch with vibrato

function init()
    input[1].mode('stream', 0.5)
end

function vibrato( time, level, amount )
    local function add(pitch, amount) return asl.runtime( function(a) return a + amount end, pitch ) end
    return loop{ to( level, time/2 )
               , to( add(level, amount), time/2 )
               }
end

input[1].stream = function(volts)
    print(volts)
    output[1](vibrato(.2, volts, .1))
end
1 Like

Thanks @chenghiz, please pardon that I have not yet looked at your code, as I’ve stumbled upon a different solution literally half an hour ago. In my solution the vibrato LFO runs freely, but I “burn” one of crow’s outputs for it. I wrote “burn” because I don’t actually patch something into that output, but just needed something I can attach an LFO action to.

My solution works like this:
• The vibrato LFO action is attached to the unused output 4, and
• the LFO voltage at output 4 is “sampled” at each stream event, and
• added to the pitch control voltage at input 1, which is also sampled at each stream event, and
• the sum of both samples is sent to output 2.

Although this works, it feels like “there must be a more elegant way, one that omits having to tie up a complete output for this”.

BTW: How do you make your code quote look so nicely formatted? Mine looks not as friendly, although I just pasted it over from Sublime Text editor, in which it looks nicely colorful.

--- adds vibrato to externally patched pitch CV

    -- input 1: patch v/oct pitch CV into this
    -- input 2: unused
    -- output 1: pitch CV with vibrato
    -- output 2: unused
    -- output 3: unused
    -- output 4: vibrato LFO

    -- vibrato_freq: vibrato frequency in Hz
    -- vibrato_level: vibrato level with 1 being ±5 Volt swing

    function init()
    	input[1].mode( 'stream', 0.01 )
    	input[2].mode( 'stream', 0.01 )
    end

    vibrato_freq = 3.5
    vibrato_level = 0.04
    time = 1/vibrato_freq
    level = vibrato_level/2

    function mix_inputs_1_and_2_to_output_1()
     	output[1].volts = input[1].volts+output[4].volts
    end

    input[1].stream = mix_inputs_1_and_2_to_output_1

    output[4].action = lfo( time, level, sine )	
    output[4]()

EDIT
@chenghiz, I believe the restart behavior you see is to be expected. According to the actions section in the script reference, this…
output[1](vibrato(.2, volts, .1))
…is a “shortcut to set the action and start immediately”, so each time a stream event happens, it will set the LFO action and (re)start it.

EDIT2: updated code formatting

Wrap your code in triple backticks, as in the ‘code block’ example here: https://commonmark.org/help/ or in [code] tags (like old bbcode, if you have ever used it)

Yeah, I think the problem to be solved is how to update the level parameter after the lfo has been started.

1 Like

Currently this is not supported. The to values are only able to be changed at breakpoints, not while a slope is running, so you can’t do it directly from Lua.

The ASL actions are tied to the hardware for outputting them. I think the limitation of 1 action per output is sufficient for most cases, though we have discussed other options. We’re always open to other developers helping out with these things!

In the meantime, your solution seems good. Alternatively, you could use a stackable to add the LFO to the V/oct signal, or a precision-adder if you need tighter accuracy.

1 Like

Thank you for the answer, some follow-up question in-line below.

Thanks, as a newbie I am very unsure if “it works” is “good” or “right”.

Do you think that for a future crow version there might be a reasonable way to have multiple “virtual hardware outputs” to which ASL actions could be tied, so that we don’t have to tie up real hardware outputs for ASL actions?

My first approach for this was to feed the LFO at output 4 back into input 2 via a physical patchcord… :smiley:

There’s no space left for any other module, so crow has to act as the LFO, an attenuator and as a unity gain mixer. And that crow can now handle this makes me quite happy :smiley: Thanks for making crow!

With a reasonable high streaming frequency, this “sampling approach” works good enough for me.

What is the highest possible streaming rate? I am now down to 0.001, that is a 1 ms interval, right? Currently this makes druid eat up an unhealthy amount of CPU power (see here), but for crow such high input sampling rate are no problem, no?

EDIT: added link