Punctual include a whole lot of mathematical functions that can be used in different way in our expressions.
Range between two values
between
between [min1,max1...] expr
between
allows you to specify one or more ranges and any other expression. It returns 1 if the expression is between the specified ranges and 0 if not.
We’ve already seen some uses of between
in other sections, for example, the drawing of a radius 1 circumference:
between [fr-px, fr+px] 1 >> add;
Note that the expression fr==1 >> add;
won’t yield any results. There isn’t any fragment that has a radius of exactly one. Due to the division of the screen in a finite number of fragments, and thus not being a continuous space, some fragments will have a radius very near to 1 (such as 1.0001 or 0.99998), but not exactly 1.
These two expressions are equivalent:
between [0.5,0.7] fy >> add;
hline 0.6 0.1 >> add;
A 45º arc:
fit 1 $ (between [0.4,0.42] fr) * (between [0, pi/4] ft) >> add;
A lot of concentric circles:
r << fr%0.1;
fit 1 $ between [r-2*px, r+2*px] 0.1 >> add;
In the next example, we use between
to create a mask where only fragments whose green component is in a certain range are selected.
A second mask is used to discard fragments with a lot of blue component.
Then, these two masks filter the original image, so only the selected fragments are shown at full bright, and the others are dimmed.
i << img "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Half-timbered_mansion%2C_Zirkel%2C_East_view.jpg/1024px-Half-timbered_mansion%2C_Zirkel%2C_East_view.jpg";
o << 0.3*osc 0.015;
g<<between (o+[0.5,0.9]) $ rgbg i;
m << rgbb i < 0.6;
i*g*m >> add;
i*0.2 >> add;
betweenp
betweenp [min1,max1...] expr
: is the same thanbetween
, but if multiple limits and expressions are provided, they are combined in a pair-wise way, while withbetween
they are combined in a combinatorial way.
See the difference between these two expressions:
between [0.2,0.3,-0.2,-0.3] [fy,fx] >> add;
betweenp [0.2,0.3,-0.2,-0.3] [fy,fx] >> add;
The first one creates four stripes, as it combines [0.2,0.3] with fy
and fx
, and [-0.2,-0.3]
with fx
and fy
, while the second one creates only two stripes, combining [0.2, 0.3] with fy
, and [-0.2, -0.3] with fx
.
Sign of an expression
abs
abs graph
returns the absolute value of the provided graph, that is the same graph but ignoring the sign.abs (-1)
is 1 andabs 1
is 1.
There are a lot of examples of the use of abs
throughout this guide, as, alongside unipolar
, is a fast way to covert a -1 to 1 range to a 0 to 1 one.
abs fx >> add;
abs [1-fx, spin (saw 0.03) $ fx+osc 0.04, abs $ osc 0.13] >> add;
It is also useful to create symmetry:
setfx (abs fx) $ spin [0.2] $ tile [1,12] $ hline 0 0.01 >> add;
fit 1 $ setfxy (abs fxy) $ spin [0.2] $ tile [1,12] $ hline 0 0.01 >> add;
sign
sign graph
returns only the sign of the graph: 1 if it’s positive, -1 if it’s negative, 0 if it’s 0.
In this example, we use sign
as a threshold. For each color component, only fragments that have a certain amount of the component color are lit (but when they are, they are completely lit).
i2
is used as mask, so the brightest fragments are kept black.
Note that the same result could be achieved using a >0
comparison instead of sign
.
i << img "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Half-timbered_mansion%2C_Zirkel%2C_East_view.jpg/1024px-Half-timbered_mansion%2C_Zirkel%2C_East_view.jpg";
i2 << rgbv i < 0.7;
r << sign $ rgbr i - (0.3~~0.7 $ osc 0.13);
g << sign $ rgbg i - (0.3~~0.7 $ osc 0.15);
b << sign $ rgbb i - (0.3~~0.7 $ osc 0.09);
i2*[r, g, b] >> add;
Here, we explore the same idea, but use the HSV color space, oversaturate some fragments, and apply a gradual palette change to the entire image.
i << img "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Half-timbered_mansion%2C_Zirkel%2C_East_view.jpg/1024px-Half-timbered_mansion%2C_Zirkel%2C_East_view.jpg";
i2 << rgbv i < 0.6;
s << sign $ rgbs i - (0.3~~0.7 $ osc 0.013);
h << ((rgbh i)+(unipolar $ saw 0.01));
v << rgbv i;
hsvrgb $ i2*[h, s, v] >> add;
Here, sign
is applied to a bunch of oscillators. The resulting o
variable represents a series of -1, 0, or 1, depending on the sign of each oscillator at a given time.
When o
is used inside move
, we get a lot of moving lines, but that abruptly change their position whenever their associated oscillator changes its sign.
l << spin [fr*8] $ spin [saw 0.16] $ tile [1,8] $ hline 0 0.01;
o << sign $ osc [0.13,0.15..0.20];
fit 1 $ mono $ move [o*osc 0.03] l >> add;
0.9 * fb fxy >> add;
Rounding numbers
fract
fract
returns the fractional part of numbers.
Note that here “the fractional part” is defined as the difference between the number and the whole number immediately below it, so fract 2.3
is 0.3
, and fract (-1.2)
is 0.8
(-1.2 - (-2) = -1.2+2 = 0.8
):
l1 << vline (fract 2.3) 0.01;
l2 << vline (fract (-1.2)) 0.01;
[0,1,0]*l1 >> add;
[1,0,0]*l2 >> add;
Also note that fract x
is equivalent to x%1
for any x
.
In this example, the image is divided in 100 vertical slices, and each slice is distorted. Within each slice, each fragment is vertically displaced based on its position within the slice. The displacement goes from no displacement on the left side to maximum displacement on the right side, resulting in a distortion effect across the image:
i << img "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Peaceful_waterfall_%28Unsplash%29.jpg/1024px-Peaceful_waterfall_%28Unsplash%29.jpg";
move [0,((-0.5) ~~ (-0.01) $ sin 0.03)*fract (fx*100)] $ i >> add;
round
, ceil
, floor
, trunc
round
,ceil
,floor
andtrunc
are intimately related. All four convert a number to a near integer.
round
rounds the number to the nearest integer. round 3.2
is 3
, round 3.7
is 4, round (-3.2)
is -3
and round (-3.7)
is -4
. For a fractional part of exactly 0.5
, the number is rounded to the next higher integer.
ceil
rounds the number to the next higher integer. ceil 3.2
is 4
, ceil 3.7
is 4, ceil (-3.2)
is -3
and ceil (-3.7)
is -3
.
floor
rounds the number to the next lower integer. floor 3.2
is 3
, floor 3.7
is 3
, floor (-3.2)
is -4
and floor (-3.7)
is -4
.
trunc
truncates the number, in explain, it gets rid of the fractional part. trunc 3.2
is 3
, trunc 3.7
is 3
, trunc (-3.2)
is -3
and trunc (-3.7)
is -3
.
Amongst the useful applications of these functions, there is to apply a pixelation effect:
p << spin 0.25 $ unipolar fx;
setfxy [round (fxy*10)/10] p >> add;
In this example, the original pattern p
is pixelated, creating a grid of 10x10 squares, each one in a uniform color. The use of the other functions lead to slightly different results.
Next example is a reimplementation of Hydra’s function posterize, applied to an image:
i << img "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Sonoma_chipmunk_at_Samuel_P._Taylor_State_Park.jpg/1280px-Sonoma_chipmunk_at_Samuel_P._Taylor_State_Park.jpg";
bins << 3;
gamma << 0.6;
c2 << (floor $ i ** gamma * bins)/bins;
c2 ** (1/gamma) >> add;
Note how the same method as in the previous example is used (multiply by a number, rounding, and dividing by the same number), in this case to reduce to number of colors in the image.
Exponentials, logarithms, and roots
sqrt
sqrt <graph>
: Calculates the square root of the provided graph. It yields the positive square root for each value in the graph.
cbrt
cbrt <graph>
: Computes the cube root of the provided graph. The cube root is the number that, when multiplied by itself three times, results in the original number. It returns both positive and negative cube roots.
exp
exp <graph>
: Computes the exponential function, which raises the mathematical constant e to the power of the values in the provided graph. The exponential function grows rapidly for positive values and approaches zero for negative values.
log
log <graph>
: Computes the natural logarithm (base e) of the values in the provided graph. It is the inverse operation of the exponential function. It is only defined for positive numbers and grows faster than the binary and decimal logarithms.
log2
log2 <graph>
: Calculates the binary logarithm (base 2) of the values in the provided graph. It represents the power to which the base (2) must be raised to produce the values in the graph. The binary logarithm grows slower than the natural logarithm but faster than the decimal logarithm.
log10
log10 <graph>
: Computes the decimal logarithm (base 10) of the values in the provided graph. It represents the power to which the base (10) must be raised to produce the values in the graph. The decimal logarithm grows slower than both the natural and binary logarithms.
Let’s first see the graphs of these functions:
fit 1 $ zoom 0.5 $ circle [fx, [sqrt fx, cbrt fx, exp fx]] 0.01 >> add;
fit 1 $ zoom 0.5 $ circle [fx, [log fx, log2 fx, log10 fx]] 0.01 >> add;
Exponentials, logarithms, and roots examples
In the next example, we manipulate the graph of the square root function to create a symmetrical pattern reminiscent of wings.
First step is to create a four-way symmetry of the original graph. This is done by taking the absolute value of x
(so we have results for both positive and negative values), and then multiplying the square root by [-1, 1]
, so the graph is mirrored horizontally. The result is a four-way symmetry of the original graph:
s << [-1,1]*(sqrt $ abs fx);
mono $ zoom 0.5 $ circle [fx, s] 0.004 >> add;
To complete the example, an oscillator is added to the square root, so the graphs move up and down like wings. A second oscillator is added to curve the lines in response to the incoming audio signal.
Finally, we give the result some color and add a zooming feedback effect, which increase the wings resemblance:
o << unipolar $ saw 0.9;
s << [-1,1]*(sqrt $ o*abs fx);
g << mono $ zoom 0.5 $ circle [fx, s] 0.004;
g*[fr,0.5, 0.5+abs fy]>> add;
zoom 0.96 $ fb fxy >> add;
In this example, we use the cubic root function (cbrt
) to manipulate the parameters of a circle pattern, resulting in a visually dynamic and intricate design.
The core of the pattern is just a circle
, but the coordinates that define its center are defined by the x
and y
variables, that vary with fr
, fx
and fy
. Here is important to remember that circle
does not necessary define a proper circle: a fragment (fx, fy)
will be considered to be inside the circle if (x, y)
is at a distance of the fragment of 0.8 or less.
Let’s try first a simplified version:
d << fr;
x << d;
y << d;
move [0,-1] $ circle [x, y] 0.8 >> add;
This is just a distorted circle. Now, let’s add the different parts step by step:
d << fr;
x << d*(saw (0.1*fy));
y << d;
move [0,-1] $ circle [x, y] 0.8 >> add;
This is the main pattern. The circle moves horizontally, and the movement is controlled by fy
, slicing the circle in multiple parts.
d << fr;
x << d*(saw (0.1*fy));
y << d+(0.2*fract (fx*100));
move [0,-1] $ circle [x, y] 0.8 >> add;
In this step, the circle is sliced vertically. Note, for example, that fract (fx*10) >> add
would create a vertical stripe pattern.
To create the final pattern, we first add cbrt
to the d
calculation. This has the effect of compressing the pattern vertically. Finally, we add color to the circle using the HSV color space, with hue and saturation modulated by fx
and fy
, respectively:
d << cbrt fr;
x << d*(saw (0.1*fy));
y << d+(0.2*fract (fx*100));
c << move [0,-1] $ circle [x, y] 0.8;
co << hsvrgb [fx+saw 0.03,fy,0.8];
c*co >> add;
In this example, we use the three logarithm variants to create variation in the movement of six circles.
The pattern starts by defining a circle and applying a horizontal movement to it. As dx
is a 3-channel signal, three circles are created. Then they are duplicated again, creating the six circles, by using spin
with two oscillators. The modulo operator %1
is used to ensure that dx
values are within the interval [0,1]
. Because we are using three variants of the logarithm, the circles move in a similar but not identical way.
The circles are colored in the HSV color space, with the hue oscillating and the saturation and value fixed. The result is then converted to RGBA by adding an alpha channel.
Finally, feedback is applied to the image, and it is moved to the left to create a trail effect that increases the movement sensation.
c << circle 0 0.08;
et << etime;
dx << [log et, log2 et, log10 et]%1;
cs << mono $ spin (saw [0.2,-0.2]) $ move [dx, 0] c;
cs*(hsvrgb [saw 0.9, 0.6, 0.6]++1) >> blend;
move [-0.03,0] $ fb fxy >> add;
Trigonometric functions
pi
pi
: this is just the number pi, 3.14159…
sin'
, cos
, tan
- Basic trigonometric functions:
sin'
,cos
,tan
. These functions receive a graph in radians and return the sine, cosine, and tangent of that number, respectively. Note that the sine function is calledsin'
to avoid conflicts with thesin
function, which is a (deprecated) synonym forosc
.
fit 1 $ zoom 0.5 $ circle [fx, [sin' fx, cos fx, tan fx]] 0.01 >> add;
asin
, acos
, atan
- Inverse trigonometric functions:
asin
,acos
,atan
. These functions receive a number between -1 and 1 and return the corresponding angle in radians.
fit 1 $ zoom 0.5 $ circle [fx, [asin fx, acos fx, atan fx]] 0.01 >> add;
sinh
, cosh
, tanh
- Hyperbolic functions:
sinh
,cosh
,tanh
. These functions are the hyperbolic counterparts to the basic trigonometric functions. They are useful to create smooth curves and transitions.
fit 1 $ zoom 0.5 $ circle [fx, [sinh fx, cosh fx, tanh fx]] 0.01 >> add;
asinh
, acosh
, atanh
- Inverse hyperbolic functions:
asinh
,acosh
,atanh
. These functions are the hyperbolic counterparts to the inverse trigonometric functions.
fit 1 $ zoom 0.5 $ circle [fx, [asinh fx, acosh fx, atanh fx]] 0.01 >> add;
Trigonometric functions examples
These functions provide a range of mathematical tools to manipulate angles, curves, and transitions within your graphical patterns.
In this example, sinusoidal waves are generated from an image.
To assure a smooth transition, the image is first vertically reflected. The setfx
function in the second line is used to mirror the image and put the two copies side by side.
The tiled
variable is created by tiling the reflected image, so the two copies are repeated horizontally. Note that the tile
function is used with a 1
parameter, so there is no visible change at this point, but the image will now be repeated when moved.
The next step is to create a wave that will be used to move the image. The wave is created by combining a sine wave with a cosine wave. This way, two copies of the wave are created, but displaced by 90 degrees. The ~~
operator is used to reduce the displacement that will be applied to the image.
Finally, the image is moved by the wave, and the result is horizontally moved with a saw
oscillator to create the actual waves.
i << img "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Peaceful_waterfall_%28Unsplash%29.jpg/1024px-Peaceful_waterfall_%28Unsplash%29.jpg";
reflected << setfx [fx-1, (-1)*fx-1] i;
tiled << tile 1 reflected;
wave << (-0.5) ~~ 0 $ [sin' (fx*10), cos (fx*10)];
move [saw 0.02, wave] tiled >> add;
The next example utilize the tan
function to curve a horizontal line. The tangent argument, t
, is defined by the multiplication of two parts. The right part, (1-(abs fx))
, goes from 0 on the left and right sides of the screen, to 1 on the center. The left part, (etime%5)
, goes from 0 to 5 and back to 0 every 5 seconds.
After applying the tangent of t
to the line’s y coordinate, the line starts horizontal and bends from the center upwards, reappearing from the bottom after reaching the top. This line is then colored red and tiled.
The last bit of the example is the addition of the feedback in polar coordinates. Two copies are created, mirrored vertically, and then the result is duplicated again by rotating it 180 degrees.
t << (etime%5)*(1-(abs fx));
tile [8,4] $ [1,0,0]*(hline (tan t) 0.08) >> add;
zoom 1.4 $ fit 1 $ spin ([0,-1]) $ mono $ fb [fr, [1,-1]*ft] >> add;