Feedback allows to use the image obtained in the previous frame in the current frame. This way, a pattern pile up with the slightly different versions of previous frames, often creating beautiful relationships and symmetries.
This guide has plenty of examples of feedback use everywhere. Here, we will see some specific ideas and techniques involving feedback.
There are two ways to use feedback in Punctual:
- By sending a number (between 0 and 1) to the
fdbk
output. - By using the function
fb
.
The fdbk
output is deprecated at this moment, so we will be using the second approach, much richer.
fb
fb
needs a graph as argument, which specifies the origin coordinates on the last frame image. For example, fb fxy
will return the last frame image without any modification, as each fragment is mapped to the same fragment on the last frame.
But something like:
iline [osc 0.01,0] [0, osc 0.011] 0.001 >> add;
fb [fx*0.96,fy+0.01] >> add;
will apply a horizontal shrinking effect and a downward movement, similar, but not identical, to this other way of coding this:
iline [osc 0.01,0] [0, osc 0.011] 0.001 >> add;
move [0,-0.01] $ zoom [0.96, 1] $ fb fxy >> add;
As an interesting application of this, we will look at some patterns created with the feedback read in polar coordinates: fb frt
.
NOTE: The outcome obtained when employing feedback is significantly influenced by the frame rate. For instance, an operation like
move [0, -0.01]
, as demonstrated in the last example, will result in the image moving at double speed when Punctual is executed at 60 frames per second, compared to when it runs at 30 frames per second.
Additive feedback
Additive feedback consists on simply adding an attenuated version of the last frame to the current one, without further modifications. Many dynamic patterns benefit from simple additive feedback.
Something as simple as two moving lines can generate beautiful patterns when using additive feedback:
l << hline (osc 0.03) 0.001;
fit 1 $ mono $ spin (saw [0.1,-0.1]) l >> add;
gate 0.1 $ 0.98 * fb fxy >> add;
When using a high amount of feedback, like in this last example, it’s often a good idea to use gate
or gatep
to remove ghost background images that are due to the fact that some small numbers, when multiplied by 0.98 (in this example), and rounded, never turn into 0.
To use additive feedback, you can just send the feedback to the output, or sum it up with the rest of the code, like in this example, which is equivalent to the last one:
l << fit 1 $ mono $ spin (saw [0.1,-0.1]) $ hline (osc 0.03) 0.001;
f << gate 0.1 $ 0.98 * fb fxy;
l +: f >> add;
A variant of the additive feedback consists on, instead of adding the current frame to the feedback, get the maximum of the two for each fragment. This technique avoids getting an oversaturated result (usually white), which is easy when using high amounts of feedback.
This example is taken from the Geometric transformations section, just adding feedback:
s << fit 1 $ mono $ spin (saw [-0.06,0.034]) $ spin (6*ft/(2*pi)) $ tile [6+15*(unipolar $ osc 0.07),1] $ vline 0 0.03;
0.1*s +: 0.95*(fb fxy) >> add;
Even though we are multiplying s
by 0.1, the resulting pattern gets too bright fast.
Now compare this with the next pattern, which uses the maximum value between the shape and the feedback instead of adding them:
s << fit 1 $ mono $ spin (saw [-0.06,0.034]) $ spin (6*ft/(2*pi)) $ tile [6+15*(unipolar $ osc 0.07),1] $ vline 0 0.03;
maxp (0.1*s) (0.95*fb fxy) >> add;
As s
is only one channel, it would be the same to use max
or maxp
on this case, but in case it had more channels, you would usually use maxp
rather than max
.
Color and feedback
Feedback always have three channels. We don’t need to attenuate all of the by the same amount. Instead, we can modify the feedback coloration to create interesting effects.
The next example follows up a pattern on the last section, but now we use a different amount for each component:
l << fit 1 $ mono $ spin (saw [0.1,-0.1]) $ hline (osc 0.03) 0.001;
f << gate 0.1 $ [0.98, 0.8, 0.95] *: (fb fxy);
l +: f >> add;
Going one step further, now we keep the whole feedback, but shift its color. We do this by converting it to the HSV color-space, adding some amount at the hue, and then converting it back to the RGB color-space:
l << fit 1 $ mono $ spin (saw [0.1,-0.1]) $ hline (osc 0.03) 0.001;
f << hsvrgb $ ([0.005, 0, 0] +: (rgbhsv $ fb fxy));
[0.1,0,0.08]*l +: f >> add;
This pattern will eventually end on a completely white screen, but it will run for several minutes before this happens.
Applying transformations to the feedback
One resource we’ve applied in several examples in this guide is to apply some coordinates transformation to the feedback.
Any transformation is possible, but keep in mind that this will be applied once per frame, so you will usually want to use very subtle changes between two consecutive frames.
In the next example, we create a rotating spiral, and add feedback with some transformations.
In the first line, r
is defined, essentially as the fragment’s angle for each fragment.
The angle is then rescaled (with linlin
) from a -π to π range to a 0 to 1 one, to adapt it to a color intensity. Then we add a saw oscillator (from 0 to 1) and finally keep only the fractional part (fract
).
If we draw r
the result is a kind of sonar effect, as the whiteness of a fragment only depends on the angle and the time.
r << fract $ (linlin [pi*(-1),pi] [0,1] $ ft) + unipolar (saw 0.3);
r >> add;
In the next step, between
is used to create the spiral shape. If the fractional part of a fragment’s radius is near enough to the previously computed r
for that fragment, the result is 1, otherwise is 0.
Next, we define e
as this spiral after applying a zooming effect depending on an oscillator to make the pattern more dynamic:
fit 1 $ between [r-4*px,r+4*px] (fract fr) >> add;
Finally, we use feedback to create the ending pattern. For each frame, we take the previous one, slightly zoom it out, and rotate it to create the final result:
r << ((linlin [pi*(-1),pi] [0,1] $ ft) + saw 0.3 )% 1;
e << zoom (0.2 ~~ 1 $ osc 0.03) $ fit 1 $ between [r-4*px,r+4*px] (fract fr);
e +: (spin (-0.01) $ zoom 0.99 $ 0.98 * fb fxy) >> add;
You can get really creative with the transformations applied to the feedback. For example, we can evolve the previous example by applying a non-uniform spin. Here, s
takes the range from -0.05 to 0.05 depending on fr
, creating more variation in the pattern:
r << ((linlin [pi*(-1),pi] [0,1] $ ft) + saw 0.3 )% 1;
e << zoom (0.2 ~~ 1 $ osc 0.03) $ fit 1 $ between [r-4*px,r+4*px] (fract fr);
s << 0.05-0.1*(smoothstep [0, 1] $ fr);
f << gate 0.1 $ spin s $ zoom 0.99 $ 0.98 * fb fxy;
e +: f >> add;
In this last variation, instead of using fr
, that is, the distance to (0,0)
, to calculate s
, we use the distance to a moving point:
r << ((linlin [pi*(-1),pi] [0,1] $ ft) + saw 0.3 )% 1;
e << zoom (0.2 ~~ 1 $ osc 0.03) $ fit 1 $ between [r-4*px,r+4*px] (fract fr);
p << [osc 0.13, osc 0.15];
s << 0.05-0.1*(smoothstep [0, 1] $ dist p);
f << gate 0.1 $ spin s $ zoom 0.99 $ 0.98 * fb fxy;
e +: f >> add;
Using polar coordinates with feedback
Another technique that can yield beautiful results involves using polar coordinates when capturing feedback.
In the following example, we begin by drawing some moving lines. Subsequently, we introduce feedback, which is acquired in polar coordinates. Pay attention to the utilization of 0
and 1
in the spin
function to create vertical symmetry:
tile [4,2] $ line (osc [0.1,0.2]) (osc [0.3,0.4]) 0.02 >> add;
spin [0,1] $ (fb frt) * 0.9 >> add;
It’s also possible to mix polar with Cartesian coordinates:
tile [4,2] $ line (osc [0.1,0.2]) (osc [0.3,0.4]) 0.02 >> add;
spin [0,1] $ (fb [fx,fr]) * 0.4 >> add;
In this variation, we return to polar coordinates and employ both spin
and setfx
to generate multiple copies of the feedback, resulting in a flower-like pattern:
setfxy frt $ tile [4,2] $ line (osc [0.1,0.2]) (osc [0.3,0.4]) 0.02 >> add;
setfx [abs fx] $ spin [-0.1,0.5,0.1,-0.5] $ (fb frt) * 0.9 >> add;
Now, we start spinning the resulting pattern, and add even a bit of additive feedback:
setfxy frt $ tile [4,2] $ line (osc [0.1,0.2]) (osc [0.3,0.4]) 0.02 >> add;
spin (saw 0.1) $ setfx [abs fx] $ spin [-0.1,0.5,0.1,-0.5] $ (fb frt) * 0.9 >> add;
0.3 * fb fxy >> add;
Here we add some color to the original lines, and duplicate the feedback pattern by using spin
again. Note that this duplicates the channel number, and we have to reduce the multiplicative factor of the feedback (from 0.9 to 0.18) to keep it under control:
setfxy frt $ tile [4,2] $ line (osc [0.1,0.2]) (osc [0.3,0.4]) 0.02 * [unipolar fx, 0, fr] >> add;
spin [[1,-1]*:saw 0.1] $ setfx [abs fx] $ spin [-0.1,0.5,0.1,-0.5] $ (fb frt) * 0.18 >> add;
Using transparency with feedback
As seen in the Output Notations section, when combining add
and blend
outputs, blending occurs using the alpha channel of the expression sent to blend
.
This feature can be leveraged to allow a high amount of feedback without oversaturating the screen.
In the following example, we employ this concept. Feedback is restricted to only 0.5, but it’s worth noting that it’s duplicated by the spin transformation, which could potentially lead to a result that is too bright.
The pattern itself comprises circles moving in a somewhat irregular manner. An important detail is the color definition in color
: the alpha is set to 1, yet even in this case, the color mixing results in a much softer effect compared to the direct addition we observed earlier. For instance, notice how the circles, when darkening, erase the background, a behavior that wouldn’t occur with additive feedback.
(zoom 0.9997 $ spin [0.003,-0.003] $ 0.5 * fb fxy) >> add;
c << tile [8,4] $ circle 0 0.1;
color << [unipolar $ osc 0.04, 0, unipolar $ osc 0.07, 1];
dx << fx * osc 0.1 * osc 0.15;
dy << fr * osc 0.17 * osc 0.05;
fit 1 $ spin (saw 0.013) $ move [dx,dy] $ c * color >> blend;
Non-additive feedback
While the most common method of utilizing feedback involves addition through various techniques (direct addition, taking the maximum between the pattern and feedback, or blending the pattern and feedback), there are cases where alternative operations can yield unique results.
Typically, these alternative operations lead to more unstable patterns, with small variations causing significant flickering.
In the following straightforward example, the primary pattern consists solely of rotating bands with a gradient applied to them. The expression 0.3 + fx % 0.3
creates vertical stripes, each ranging from 0.3 on the left side to 0.6 on the right side. Subsequently, the pattern undergoes slow rotation achieved by using spin
with a saw oscillator.
The key to the intriguing outcome lies in subtracting the previous frame. This subtraction results in complex patterns when color cancellation occurs in an irregular manner, as each frame is subtracted from a slightly rotated version of itself.
spin (saw 0.01) $ 0.3 + (fx % 0.3) -: fb fxy >> add;
Next is an example that divides the main pattern by the feedback.
Our main pattern, p
, is extremely simple: just a slowly moving gray gradient.
As the feedback is on the denominator, we need to make sure it’s high enough, because normal values from 0 to 1 will rapidly lead to a completely white screen. In this example, the feedback is double by the two-channels spin
, and multiplied by 1.2:
p << move [tri 0.013, 0] $ 0.5 * abs fx;
s << saw [0.02, -0.021];
f << (unrep 2 $ zoom 0.97 $ 1.2 * (spin s $ fb fxy));
p / f >> add;
By itself, the resulting pattern is interesting enough: dynamic and irregular patterns emerge in a difficult to predict way.
In the next iteration we add only two further modifications. zoom
adds only a bit more variation and creates some of the concentric circles that appear sometimes. But the key here is unrep 2
, which sums channels two by two. Feedback has 3 channels, that duplicate with spin
. After unrep
we have
p << move [tri 0.013, 0] $ 0.5 * abs fx;
s << saw [0.02, -0.021];
f << (unrep 2 $ zoom 0.97 $ 1.2 * (spin s $ fb fxy));
p / f >> add;
Using non-attenuated feedback to draw geometric patterns
When feedback is set to 100%, we can employ basic shapes as brushes in a photo editing program, combined with mathematical formulas, to craft geometrically interesting patterns. A previous example of this technique is demonstrated in the Audio reactive visuals section.
In the following example, we utilize r
, a rectangle that slowly moves up and down, as the fundamental shape. c
defines the color, with a red component dependent on the x-coordinate and changing over time, and a blue component that relies on the distance to the origin. The alpha is set to one, applying the transparency technique explained above.
Next, we apply an initial spin
to the rectangle to make it traverse the entire screen. Additionally, a transformation with setfx
is used to introduce some irregularity. Finally, we duplicate the rectangle and further spin it to create the final movement.
fb fxy >> add;
r << rect [0,0.5*osc 0.04] 0.3;
c << [unipolar $ osc 0.14 *: (cos $ fx*15), 0, fr ,1];
s << [saw 0.2, (-1)*saw 0.2];
fit 1 $ spin s $ setfx (fx/(1-2*fy)) $ spin (saw 0.03) $ r * c >> blend;
This pattern originated from one of the etime
examples. Initially, a flower-like design was created by dynamically changing polar coordinates and using them as the position of a pen:
x << 0.8*rtxy [sin' $ 0.5*pi*etime, etime];
circle x 0.01 >> add;
fb fxy >> add;
Building upon the same coordinates but employing a different approach, we use them as the horizontal position for vertical lines:
x << 0.8*rtxy [sin' $ 0.5*pi*etime, etime];
tile [6,1] $ vline x 0.01 >> add;
Since x
has two channels, it generates two sets of lines: red and cyan. The red lines follow the same movement as the pen’s x-coordinate in the previous example, while the cyan lines follow the y-coordinate.
The next iteration involves converting these vertical lines into circles and introducing some color. The base pattern is established as follows:
c << [0.3,0.3,1];
x << 0.8*rtxy [sin' $ 0.5*pi*etime, etime];
l << tile [6,1] $ vline x 0.001;
(fit 1 $ setfxy frt l) * c >> add;
Feedback is then applied in the following manner. Most of the time, the entire feedback is retained without any transformation. However, the variable s
introduces occasional changes. As the oscillator controlling s
operates at a very high frequency, s
intermittently reaches the value 2. When s
is 2, all the accumulated feedback is shifted to the left and tiled, creating a fractal-style pattern. These new images are also retained through feedback, contributing to the iterative and evolving nature of the pattern:
c << [0.3,0.3,1];
x << 0.8*rtxy [sin' $ 0.5*pi*etime, etime];
t << tile [6,1] $ vline x 0.001;
(fit 1 $ setfxy frt t) * c >> add;
s << step [0,0,0,0,0,0,0,0,0,2] $ saw 100;
tile [1+s,1+s] $ move [(-0.2)*s,0] $ fb fxy >> add;