Oscillators
An oscillator is a device used to create oscillations:
the repetitive or periodic variation, typically in time, of some measure about a central value (often a point of equilibrium) or between two or more different states". — https://en.wikipedia.org/wiki/Oscillation
In Punctual, an oscillator is a function that returns values in the range from -1 to 1, varying through time.
All oscillators receive a single argument, which is the oscillation frequency in hertzs, that is full cycles per second.
osc
, tri
, saw
, sqr
There are four oscillators in Punctual, each with a different pattern:
In the next example, you can see all four oscillators in action. There are four balls, each one moving according to one of the oscillator types along the y axis, and using the same colors than in the previous representation. Each cycle lasts 4 seconds instead of 1:
fit 1 $ (circle [-0.6, osc 0.25] 0.1 * [1,0,0] +:
circle [-0.2, tri 0.25] 0.1 * [0,0,1] +:
circle [0.2, saw 0.25] 0.1 * [1,0,1] +:
circle [0.6, sqr 0.25] 0.1 * [0,1,0]) >> add;
The first circle, in red, uses the osc
function and follows a sinusoidal wave. The second one, in blue, uses tri
and follows a triangle waveform. The third one, in magenta, uses saw
and follows a saw wave, that is. it increases its value from -1 to 1, and then jumps and starts again at -1. The last circle, in green, uses sqr
and follows a square wave, taking only the values -1 and 1 for half a cycle each one.
Oscillators are such a fundamental tool in Punctual that you can find examples of them being used across all the sections in this guide.
Summarizing, oscillators can be used to modulate:
- Coordinates
x1 << osc 0.13;
y1 << osc 0.15;
x2 << osc 0.09;
y2 << osc 0.11;
line [x1,y1] [x2,y2] 0.001 >> add;
fit 1 $ circle [x1*x2, y1*y2] 0.2 >> add;
line [tri fx, tri fr] [tri fy, tri fr] 1 >> add;
- Transformations
l << [0.05,0.04..(-0.06)];
mono $ spin (saw l) $ hline 0 0.001 >> add;
- Colors
fit 1 $ circle 0 1 * (0.5 ~~ 1 $ sqr [0.73, 0.81, 0.65]) >> add;
- Sizes
fit 1 $ circle 0 (2*:(unipolar $ sqr (0.5+fx*fy*5))) >> add;
hline 0 (0.2*(abs $ osc (0.2*(abs $ fx)))) >> add;
- Other oscillator frequencies
f<<[0.1,0.11..0.2];
(mono $ circle [osc (0.001*:osc f), osc (0.001*:osc f)] 0.1) * [1.2-fr,0,0.6,0.7] >> blend;
0.99 * fb fxy >> add;
- Between a set of values (see
step
bellow)
Changing the phase of an oscillator
The phase is the position on the cycle of an oscillator where it begins its movement. Two oscillators can have the same frequency and amplitude, but different phase, and then one is displaced respect the other:
At the moment, there is no an immediate way to change the phase of an oscillators, but we can do it by rewriting the oscillators as mathematical functions.
osc 0.1
is equivalent to sin' (0.1*time*2*pi)
, so we can add some value inside the sine to change the phase:
(between [-1,0] $ fx) * osc 0.1 >> add;
(between [0,1] $ fx) * sin' (pi+0.1*time*2*pi) >> add;
tri 0.1
is equivalent to 4*abs (((0.1*time-0.5)%1)-0.5)-1
:
(between [-1,0] $ fx) * tri 0.1 >> add;
(between [0,1] $ fx) * (4*abs (((0.1*time)%1)-0.5)-1) >> add;
saw 0.1
is 2*((0.1*time-0.5)-floor (0.1*time))
:
(between [-1,0] $ fx) * saw 0.1 >> add;
(between [0,1] $ fx) * (2*((0.1*time)-floor (0.1*time+0.5))) >> add;
sqr 0.1
is (-1) * (sign $ sin' $ 2*pi*time*0.1)
:
(between [-1,0] $ fx) * sqr 0.1 >> add;
(between [0,1] $ fx) * ((-1) * (sign $ sin' $ 2*pi*(time+5)*0.1)) >> add;
step
step
chooses a value from a set based on an expression, usually (but not necessarily) an oscillator:
vline (step [-0.5,0,0.5] $ saw 0.3) 0.001 >> add;
Here, the vertical line position changes regularly, taking the values in the list by turns.
Next, there is a more complex example. x
and y
define the center coordinates of a circle. Due to the fast oscillators controlling step
, the circle jumps between four moving points in the screen. r
is the circle radius, which changes over time between three values. c
is the circle’s color, which is changing over a reduced set of possibilities, due to the three step
functions using different frequency oscillators. Note how the alpha channel is set to 0.7. This, with the feedback set to a full 1, allow the circles in each frame to accumulate on the screen without saturating the color (this technique is explained in the color section), creating the effect that there are four circles moving:
x << osc 0.17*osc 0.19*(step [-1,1] $ saw 5.1);
y << osc 0.16*osc 0.18*(step [-1,1] $ saw 5.3);
r << step [0.1,0.2,0.3] $ osc 0.17;
c << [step [0.5,1] $ tri 5, step [0,0.5,1] $ tri 54, step [0,0.5,1] $ tri 57, 0.7];
fit 1 $ circle [x,y] r * c >> blend;
fb fxy >> add;
You can use step
with any other expression in addition to oscillators.
Evolving from the last example, we can now start to apply transformations to the feedback. Here, we use step
once again to apply a spinning effect. But this time, the amount of spinning applied depends on the distance from the center (fr
), so fragments on the center rotate to the right, fragments a bit more distant rotate slower to the left, more distance ones to the right again, and the most distant don’t rotate at all:
x << osc 0.17*osc 0.19*(step [-1,1] $ saw 5.1);
y << osc 0.16*osc 0.18*(step [-1,1] $ saw 5.3);
r << step [0.1,0.2,0.3] $ osc 0.17;
c << [step [0.5,1] $ tri 5, step [0,0.5,1] $ tri 54, step [0,0.5,1] $ tri 57, step [0.1,1,0.3] $ saw 0.59];
fit 1 $ circle [x,y] r * c >> blend;
zoom 1.003 $ spin [step [0.03,-0.005,0.01,0] $ (bipolar $ fr)] $ fb fxy >> add;
As you can see from these examples, step
is a very useful function and can be used in a lot of different contexts. However, step
has some strong limitations:
step
doesn’t currently support multi-channel signals in any meaningful way.
While next example will work as expected,
c << fit 1 $ circle 0 0.5;
c2 << step [zoom 2 c, move [0.5,0] c] $ saw 0.2;
c2 * [0.3, 0.6, 0.8] >> add;
any attempt to pass a multichannel signal to step
will result in strange behavior, as step
iterates through the channels instead of taking them as a whole:
c << fit 1 $ circle 0 0.5 * [0.3, 0.6, 0.8];
c2 << step [zoom 2 c, move [0.5,0] c] $ saw 0.2;
c2 >> add;
In this last example, we can skip this limitation by applying color as the last step of the expression, but let’s suppose we want to use step
to change between two images, which inherently have 3 channels:
i1 << img "https://upload.wikimedia.org/wikipedia/commons/b/b4/Vaporwave_for_China.jpg";
i2 << img "https://upload.wikimedia.org/wikipedia/commons/4/48/Mao_Tse_tung_in_1965_Color.png";
fit (step [1,0.66] $ saw 0.3) $ step [i1,i2] $ saw 0.3 >> add;
The result is quite cool but not necessarily what we expected. Sometimes, there are workarounds that we can use to get to the desired result:
i1 << img "https://upload.wikimedia.org/wikipedia/commons/b/b4/Vaporwave_for_China.jpg";
i2 << img "https://upload.wikimedia.org/wikipedia/commons/4/48/Mao_Tse_tung_in_1965_Color.png";
s << step [1,0] $ saw 0.3;
i << i1 * s +: i2 * (1-s);
fit (step [1,0.66] $ saw 0.3) $ i >> add;
Here, s
is used to choose one of the two images, through some arithmetics.
What if we want to iterate over three or more images? Here is a scalable workaround:
i1 << (step [1,0,0] $ saw 0.3) * img "https://upload.wikimedia.org/wikipedia/commons/b/b4/Vaporwave_for_China.jpg";
i2 << (step [0,1,0] $ saw 0.3) * img "https://upload.wikimedia.org/wikipedia/commons/4/48/Mao_Tse_tung_in_1965_Color.png";
i3 << (step [0,0,1] $ saw 0.3) * img "https://upload.wikimedia.org/wikipedia/commons/b/b6/Mao_Zedong_in_front_of_crowd.jpg";
fit (step [1,0.66,0.86] $ saw 0.3) $ (i1+:i2+:i3) >> add;
Also:
i1 << img "https://upload.wikimedia.org/wikipedia/commons/b/b4/Vaporwave_for_China.jpg";
i2 << img "https://upload.wikimedia.org/wikipedia/commons/4/48/Mao_Tse_tung_in_1965_Color.png";
i3 << img "https://upload.wikimedia.org/wikipedia/commons/b/b6/Mao_Zedong_in_front_of_crowd.jpg";
n << step [0,1,2] $ saw 0.3;
fit (step [1,0.66,0.86] $ saw 0.3) $ ((n==0)*i1)+:((n==1)*i2)+:((n==2)*i3) >> add;
- List expansions aren’t supported inside
step
.
circle 0 (step [0,0.1..0.6] $ saw 0.1) >> add; -- error