Bodysoulspirit's picture

From a Facebook question from John Roger Smith about how to show flashing growing circles on an audio beat for example with opacity fading out but size increasing ?

2 Answers

MartinusMagneson's picture

Now for the "difficult" solution.

Snippet of music by Kiasmos - Blurred

Thinking about the problem, it can be boiled down to wanting a trigger of some sort to grow an arbitrary number of circles from 0 to 1 in size, while simultaneously reducing the color opacity from 1 to 0, and then get rid of it once it reaches its goal.

The middle part first, that is; the two values going from 0 to 1 and 1 to 0 is essentially the same. If you have a value going from 0 to 1, you can either scale the value using the scale node - or simply subtract the 0-1 value from 1. Realizing this will boil down the problem: Be able to trigger a 0, and then grow it to 1.

This again can be divided into its own two problems - followed by two solutions that can be combined. 1: Can we trigger a 0? 2: Can we grow it to 1 and then get rid of the 1?

Yes (to both). Lets start with the trigger.

Working with audio can be a bit ardous. A basic trigger is as simple as a boolean question (the answer is either true or false, more here: Wikipedia - Boolean Algebra), is the input (A) larger than a set value (B)? This question can be asked with the "Is Greater Than" node. For audio however, this will vary so rapidly that an unnoticeable sound can produce a trigger, producing unwanted results. Luckily Vuo has at least some basic filtering options like "Calculate Amplitude from Frequency" (although I'm too lazy to use it here) that can achieve a smaller base to compare against in the audio spectrum. There will still be a likelihood of triggering from tiny variations though, so we can use even more boolean questions to ask better questions to further narrow down our trigger conditions.

This is how I've done the triggering in this case. There are of course a lot of different ways to do this, and you should experiment with how you can filter this yourself. This can be used as a starting point for exploration - or just copied if you don't care (I would care, there are a lot of difference in the end result based on the values entered, and the nodes used). Taking it node for node:

  • Receive Live Audio - obvious if you want to use audio. It receives audio samples. Goes into:
  • Calculate Loudness - translates the received samples to real numbers for control/drawing purposes. Branches into:
  • (Top) Enqueue - collects the (in this case) 9 previous numbers from the Calculate Loudness node. Goes into:
  • (Top) Process List - runs a calculation on each of the 9 numbers provided by the (top) Enqueue node. Gets the calculation from:
  • (Bottom) Enqueue - collects the (in this case) 3 previous numbers from the Calculate Loudness node. Goes into:
  • (Bottom) Average - calculates the average of the 3 numbers in the (bottom) Enqueue node. Goes into:
  • (Bottom) Is Greater Than - Checks if each of the 9 numbers from the (top) Enqueue node is larger than the average of the 3 numbers from the (bottom) Enqueue node. Goes into:
  • (Top) Process List - now collects the result of each of the calculations and aggregates a list of 9 boolean values. Goes into:
  • Is One True - will give the boolean answer true if only one of the boolean inputs in the list (from the Process List) is true. Will otherwise produce false (if 0 or 2 or more are true). Goes into:
  • Hold Value - because audio moves fast, and we wish to limit the calculations to the framerate. I'm unsure if this is the wisest placement. Can probably be expanded with select latest or something similar to become even more effective.

So what does all this gibberish mean? Well, we now have a trigger that will only be true if only one of the last 9 samples of audio is louder than the previous 3. In essence, this cleans up the triggering so that we only have one trigger on average where it with the pure audio would have hundreds, if not thousands, cluttering the look, and bogging down the system. In short; preventing rapid retriggering. Be warned though,my example isn't particularly effective or pretty as it stands. Again, it should really be used as a base to explore from, and to see if you can produce better results either by swapping out the logic, or fine tuning the thresholds - or both!

On to the second part. How do we grow it to 1 and then get rid of the 1? Enter lists! (I realize they have already entered earlier, but ignoring that...).

Resetting a number to 0 is trivial (or should be at least), it will also just produce one circle at a time, so we will just jump straight to the list tools, and use them to grow and shrink themselves based on a few triggers. Adding to a list can be done with the "Add to List" node. Removing something from a list can be done with the "Take from List" node. But how do we get them to not constantly do so - resulting in a list in limbo?

Luckily there is a "Select List Input" node of the boolean variant. Since we have just produced a boolean trigger from audio, we can use it with this node to select the "Add to List"(uhh..?)-List only when the audio triggers it. Combining it with a "Hold List" and a "Take From List" node, we can produce a feedback loop that grows and shrinks based on its own contents and two triggers. We will get to the remove-trigger later, but for now, node for node starting with the "Hold List" node and then from left to right:

  • Hold List - keeps a list in memory and spits it out every time it's triggered by its event input port, effectively creating a feedback loop. Branches to:
  • Add to List - adds an item to a list. In this scenario it's a 0 at the end of the list. Goes in to:
  • (left) Select Input List - will select either the true or false list based on the boolean state of the selector. In this case it will spit out the unchanged "Hold List"-list (I guess it's official) if the selector is false. If the selector is true, it will select the "Add to List" input and add an item to the end of the list with a value of 0. Branches to:
  • Take form List - removes an item from a list. Here it will remove the first item in the input list. Goes into:
  • (right) Select Input List - same as above, but selects the "Take from List" if the selector is set to true (more about that later)

Note that the "Hold List" isn't connected this way in the actual composition, this is just for clarity in what happens within the loop. What is effectively happening here is that if there is no trigger, the "Hold List" will be on its merry way, keeping its size and enables acting on its values. If there should be a trigger active within one loop, it will either add or remove an item from the list - growing and shrinking it depending on which triggers it gets fed [insert millenial joke here (won't actually do that)]. What we now have is a list which we can modify the contents of. Time to stuff it into some Process/Build lists and make it do something!

Clusterducks are clusters of ducks moving seemingly erratically while looking quite messy. Unless eaten by a predator, they usually have some goal to their wandering about even though it might not seem that way. It's a great animal to have in your yard to transform snails into duck poop. Coincidentally so is this next bit (apart from the bit about snails and duck poop (phew!)). What we will do here is to take the list of 0s that the previous "Hold List" produces and add some number in a recursive matter. That way we will get a list of increasing numbers. One great thing to use an increasing number as, is a timer! This means that we can create a list of different timers that can then be used in company with a "Curve" node to interpolate between 0 and 1 (finally something relevant!).

This will make no sense on just a node for node basis all in all, so I'll try to explain the different parts more thoroughly. Time-base first.

The "Share Time" node (just a renamed "Share Value" node) gets it input from a "Requested Frame" port. This then goes into an "Enqueue" node that stores the current and previous time (Max Item Count = 2). Subtracting the previous time from the current time gives us the interval of time that has passed since the last frame. This then gets passed to the "Process List" loop.

The process list loop is what calculates the different timers. It gets it input from the previous "Hold List" node. Node for node:

  • Hold List - outputs the growing/shrinking list of initial 0s from the selectors in the previous part.
  • Process List - adds the timebase to each item from the "Hold List" via:
  • Hold Value - prevents unwanted synchronization duck poop from happening, goes into:
  • Add - adds the timebase to whatever the initial 0 from the "Hold List" feedback loop is. Goes into:
  • Process List - The edited input list where each item/number has grown by the time base. This goes back to the previous feedback loop at the "False" inputs of the "Select List" nodes and further on to the "Build List" node.

The "Build List" node then takes the timers from the feedback loop (Hold/Process List loop) and use them for as a timebase for the "curve" node. This is set here with a "Start Position" and "End Position" fed from Published Inputs. The "End Position" published port goes via a "Share Value" node which also feeds a boolean comparator "Are Equal". The "Are Equal" node compares the "End Position" to the current output of the "Curve" node - and is the last piece of the puzzle as it triggers the "Take from List" node in the feedback timer loop. This means that whenever a value reaches 1, the feedback loop will remove the oldest timer in the loop which should be the one that has reached 1. Node for node:

  • Build List - takes the amount of items from the "Hold/Process List"-list (looking forward to list of lists -lists) and generates a new list. Goes into:
  • Get Item from List - gets the corresponding item from the timer loop to the item being created. Goes Into:
  • Curve - generates an interpolated value from "Start Position" (published port, 0), "End Position" (published port, 1) and "Time" (timer loop). Branches to:
  • Build List - outputs a list of numbers from "Start Position" (0) to "End Position" (1) based on individual timers.
  • Are Equal - compares the output of the "Curve" node to the "End Position" and sends the value "True" to the "Take from List" node if it has reached its end position.

We now have our growing and shrinking list of numbers, and can start putting it into context. I've opted for the layers as that was what the original question asked for, but it can easily be adopted to 3D Objects, images, serial data, numerical data and whatever else you might figure out it could be used for.

The output from the "Build List" can now be used in a new "Process List" which applies the 0-1, or scale to the width and height of an Oval layer. In addition, the value is inverted through a "Subtract" node also connected to the "End Position" value (via a "Hold" node to sychronize external events) and used to define the opacity of a "Make HSL Color" node. The rest is basic stuff. Try to build it yourself, or implement it into something nice (or horrible - that could also be interesting)! The composition is in the attached files.

Cheers, Magneson!

Magneson (@MartinusMagneson),

jstrecker's picture
Submitted by

Magneson (@MartinusMagneson), your explanations are always enjoyable to read :)

Now that you've done the hard parts (like breaking down the problem into well-defined small pieces), I will just add a couple of suggestions to sidestep some of the duck yuck.

One is that you can form a list of 0s and 1s using Enqueue and turn those into fading-out opacities by multiplying by descending values. (Actually since the Enqueue output is oldest to newest I'm multiplying by ascending values.) (See ConcentricCircles1.vuo, which by the way is not very efficient.)

The other is that you can get times relative to the timebase (or a timebase? not sure if this is the same as what you were doing) by subtracting them from the current time, again using list math nodes. (See ConcentricCircles2.vuo.)

Thanks for the input Jaymie (

MartinusMagneson's picture
Submitted by

Thanks for the input Jaymie (@jstrecker)! Brilliantly simple solution with the enqueue-lists!

I found the auto-add/-remove thingy so intrigueing that I didn't even consider the option of just letting all the items exist all the time. I also tried a simpler solution, where I was just using a hold-list node to add the timebase to. This didn't work as the hold-list, when getting a new item, would inherit the value from the first item (current time) instead of starting at the initial value (0). I might have done some triggering errors in that - or it's the way the hold list node works. I could at least not figure it out.

It seems like the calculation of the timebase should be about the same (produce the same output), but the other way around. The way I did it, I get the time between the frames by subtracting the previous frame time from the current frame time. This result then gets added to itself in a feedback loop producing an increasing value that should follow changes in framerate, but keep the time-scale.

If this gets hard to follow for anyone else reading, the difference between our approaches is that Jaymies way will create an enqueued list of n-items, and the animation is a result of the different items in the list. The way I do it, the animation is a result of the item in the list itself. This means that when a circle grows and fades in Jaymies composition, it is actually the next item (circle) in the list that gets displayed with those values (think stop-motion). The example I show is the item (circle) getting updated with a different value (think melting ice). Jaymies way will probably be a lot easier to get your head around, and is also probably way easier to deal with, but the time-scale and step-size between the items (circles) will be dependent on the size (amount) of items in the list.

Bodysoulspirit's picture

Here is a method using Blend Image With Feedback as suggested by Martinus Magneson

Joining the composition below for those who might be interested.