Rescaling values is a common operation in Punctual. You’ll often want to relate coordinates to colors, oscillator outputs to color, distance from a point to a rotation transformation, etc.

### Common ranges

It may be useful to know the ranges of common variables and arguments in Punctual:

Variable/argument | Minimum | Maximum | Comments |
---|---|---|---|

`fx` ,`fy` | -1 | 1 | Can change if using for example `fit` . |

`fr` | 0 | √2~=1.414 | |

`ft` | -π | π | |

red, green, blue, alpha | 0 | 1 | |

hue, saturation, value | 0 | 1 | |

oscillator’s output | -1 | 1 | |

oscillator’s frequency | -∞ | ∞ | But usually low values, from -1 to 1 |

`spin` ’s argument | -1 | 1 | First lap, any other value is accepted |

`lo` , `mid` , `hi` , `ilo` , `imid` , `ihi` | 0 | 1 | |

`fft` /`ifft` argument | 0 | 1 | |

`fft` /`ifft` output | 0 | 1 |

Punctual offers a bunch of functions and operators specifically design to avoid doing the maths on the fly when transforming one range into another.

`unipolar`

, `bipolar`

`unipolar`

rescales a [-1, 1] range into [0,1]. This is equivalent to applying the formula `(x+1)/2`

to the input number `x`

.

`bipolar`

rescales a [0,1] range into [-1,1]. This is the same as applying the formula `2*x-1`

to the input number `x`

.

`~~`

`[min] ~~ [max] $ [input]`

The `~~`

operator rescales a bipolar signal to the specified range, specified as a `min`

and a `max`

value.

`~~:`

`[min] ~~: [max] $ [input]`

Same as `~~`

, but `~~`

works in a combinatorial way, while `~~:`

works in pair-wise way.

See the difference:

```
o << [0.1, 0.3] ~~ 0.8 $ osc [0.03, 0.07];
hline o 0.004 >> add;
```

```
o << [0.1, 0.3] ~~: 0.8 $ osc [0.03, 0.07];
hline o 0.004 >> add;
```

In the first version, there are four lines as the result of combining [0.1, 0.3] with [0.03, 0.07] in all possible ways. In the second version, there are only two lines, as 0.1 is matched with 0.03 and 0.3 with 0.07.

`+-`

`[centre] +- [offsetRatio] $ [input]`

The `+-`

operator rescales a bipolar signal to the specified range, just like `~~`

, but the range is specified as a `centre`

and an `offsetRatio`

.

The `offsetRatio`

indicates the proportion of variation from the center for the new values.

So, for example:

```
o << 0.4 +- 0.5 $ osc 0.1;
hline [0.2, 0.6, o] 0.004 >> add;
```

The blue line will move between the red (at 0.2) and the green (at 0.6) lines. This represents a variation of +-50% from 0.4.

`+-:`

`[center] +-: [offsetRatio] $ [input]`

This is the same as `+-:`

but in a pair-wise way.

`linlin`

`linlin [min1, max1] [min2, max2] [input]`

input graph is linearly scaled such that the range `(min1,max1)`

becomes the range `(min2,max2)`

. This is useful when you want to rescale a range that is not [0,1] or [-1,1].

In the next example, we use a combination of these functions and operators.

`co`

defines the color. Its red and blue components depend on the fragment’s distance to the origen, which takes values from 0 to approximately 1.4. We are using `linlin`

to rescale this value to the desired color component. Note how the red component is stronger as the fragment is closer to the origin, while the blue component is stronger when the fragment is far away from the origin.

`ci`

defines a set of three circles that are spinning around the center. All three circles come from a single one, whose center is defined as `[bipolar imid, 0]`

. That means that its x coordinate moves from -1 (when there is no sound), to 1 (when the middle frequencies are at their maximum). Its radius is defined as `linlin [0,1] [0.1,0.4] ilo`

. Here, we are rescaling the low frequencies intensity to the `[0,1,0.4]`

range.

The example also uses feedback. Each frame, the feedback is zoomed in or out, depending on the middle frequencies of the incoming sound. Here, we rescale the `[0,1]`

range from `imid`

first to `[-1,1]`

using `bipolar`

and then to `[0.8, 1.2]`

using the `+-`

operator.

```
co << [linlin [0,1.4] [1,0.2] fr,0,linlin [0,1.4] [0,1] fr,0.8];
ci << mono $ spin (saw [0.1,-0.2,0.3]) $ circle [bipolar imid, 0] (linlin [0,1] [0.1,0.4] ilo);
ci*co >> blend;
zoom (1 +- 0.2 $ bipolar imid) $ fb fxy >> add;
```

`linlinp`

`linlinp [min1, max1] [min2, max2] [input]`

: This function operates as the pairwise version of`linlin`

.

To illustrate the difference, consider these two expressions:

```
dy << linlin [0,1,0.5,0.9] [2/5, 4/5, (-1)/5, 1/5] (ifft $ abs [fx, fx/3]);
hline dy 0.004 >> add;
```

```
dy << linlinp [0,1,0.5,0.9] [2/5, 4/5, (-1)/5, 1/5] (ifft $ abs [fx, fx/3]);
hline dy 0.004 >> add;
```

In the first expression using `linlin`

, there are two sets each of origin and destination ranges, along with two input signals. This results in 8 output channels. However, in the second expression using `linlinp`

, despite the same input structure, only two output channels are generated. This highlights the pairwise nature of `linlinp`

, where each input range pair corresponds to a single output pair, reducing the output channels to match the pairs in the input.

`clip`

`clip [min, max] [input]`

: clip input values into the specified range. If a value is less than`min`

it will become`min`

, and if it’s greater than`max`

it will become`max`

.

In the first example, the clip function is used to confine the `fx`

values within the range `[−0.2,0.2]`

. This ensures that the y coordinate of each fragment remains within this range, resulting in a line that follows the diagonal in the central part, but is constrained vertically on the sides:

```
x << clip [-0.2, 0.2] fx;
hline x 0.004 >> add;
```

In the second example, `clip`

is applied to mix two images. The resulting image takes the second image as a model, but each component of each fragment cannot exceed the corresponding component of the red channel of the first image. This effectively limits the intensity of each color channel in the second image to match or be lower than the intensity of the red channel in the first image:

```
i1 << img "https://upload.wikimedia.org/wikipedia/commons/a/a3/Neillia_affinis%2C_trosspirea._23-05-2022_%28actm.%29.jpg";
i2 << img "https://upload.wikimedia.org/wikipedia/commons/5/58/Indian_tightrope_girl_performing_folk_art_Baunsa_Rani_%28Crop_2%29.jpg";
clip [0, rgbr i1] i2 >> add;
```

Note that using `clip`

is a shorthand for using `max`

and `min`

. For example, the last sentence in the previous example could be rewritten as `max 0 (min (rgbr i1) i2) >> add;`

. In this specific example, since any pixel in the image already has a value greater than or equal to 0, the `max`

operation can be skipped. Therefore, the expression can be simplified to: `min (rgbr i1) i2 >> add;`

.

`clipp`

`clipp [min, max] [input]`

: pairwise version of`clip`

.

See the difference between the next two expressions. In the first expression, `clipp`

is used, which applies the specified ranges pairwise to each corresponding input value. As a result, `fx`

is confined within the range `[−0.2,0.2]`

and `fx+0.5`

within `[−0.4,0.4]`

. This results in two lines.

In the second expression, `clip`

is used, which applies all the specified ranges to all the inputs. Consequently, each input value is confined within its respective range, resulting in four lines.

```
y << clipp [-0.2,0.2,-0.4,0.4] [fx, fx+0.5];
hline y px >> add;
```

```
y << clip [-0.2,0.2,-0.4,0.4] [fx, fx+0.5];
hline y px >> add;
```

`smoothstep`

`smoothstep [lowedge, highedge] input`

: For input values below`lowedge`

, the function yields 0; for values above`highedge`

, it yields 1; and for values in between, it smoothly interpolates. Additionally,`smoothstep`

accepts the edges in descending order:`smoothstep [highedge, lowedge] input`

, in which case values below`lowedge`

yield 1, and values above`highedge`

yield 0.

In this example, when `fx`

is less than -0.5, `y`

is 0, when `fx`

is more than 0.5, `y`

is 1, and when `fx`

is between -0.5 and 0.5, `y`

goes from 0 to 1:

```
y << smoothstep [-0.5, 0.5] fx;
hline y px >> add;
```

Using `smoothstep`

provides precise control over transitions in patterns. In the following code snippet, multiple vertical white stripes are drawn across the display. These stripes gradually transition from darker on the left side of the screen to whiter on the right side:

```
f << unipolar $ sin' (fx*40);
s << smoothstep [-0.8,0.5] fx;
s*f >> add;
```

While a similar effect could be achieved using `(unipolar fx)*f >> add;`

on the last line, `smoothstep`

allows us to specify the range of coordinates where the transition occurs with greater precision.

Same idea, applied to the drawing of a mathematical function:

```
f << sin' (fx*4);
s << smoothstep [-3,2] fx;
fit 1 $ zoom 0.5 $ circle [fx, s*f] 0.02 >> add;
```

The range `[0,1]`

returned by `smoothstep`

can be easily adjusted to fulfill different requirements. In the following pattern, similar to the previous one, `s`

now varies from 0.1 to 0.7:

```
f << sin' (fx*4);
s << 0.1+0.6*smoothstep [-3,2] fx;
fit 1 $ zoom 0.5 $ circle [fx, s*f] 0.02 >> add;
```

So far, we’ve used `smoothstep`

to gradually introduce a pattern. It’s also well-suited for creating transitions between two different patterns. In the following example, an image is displayed on the left side, and another image on the right side. The section in the middle transitions from one image to the other. This is achieved using the linear interpolation formula in the last line:

```
i1 << img "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Peaceful_waterfall_%28Unsplash%29.jpg/1024px-Peaceful_waterfall_%28Unsplash%29.jpg";
i2 << 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";
s << smoothstep [-0.25, 0.25] fx;
i1*(1-s)+:i2*s >> add;
```

This same idea can be used to blend any two different patterns:

```
a << spin (saw 0.1 + fy) $ tile 4 $ hline (cbrt fx) 0.2;
b << spin [saw [-0.01,0.013]] $ tile [8*(1-abs fx),4*fx] $ spin 0.25 $ rect 0 0.6;
s << smoothstep [0.3, 1.3] (fit 1 $ fr);
s*a+(1-s)*b >> add;
```

In this example, `a`

represents a pattern based on white moving lines, while `b`

is based on colored rectangles. Notice how the two patterns blend: `b`

dominates the center, `a`

dominates the sides, and between a radius of 0.3 and 0.6, the two patterns mix together.

The concept of mixing multiple patterns can be expanded. In the following example, three sections are blended together. The entire pattern consists of a wide horizontal line that reacts to input frequencies. On the left side, it responds to low frequencies, in the center to mid-range frequencies, and on the right side to high frequencies.

A sharp version of this idea can be achieved by filtering the analyzed frequency depending on the x coordinate of the fragment:

```
a << ilo*(fx<(-1)/3);
b << ihi*(fx>1/3);
c << imid*((fx>=(-1)/3)*(fx<=1/3));
hline (-1) (2*(a+b+c)) >> add;
```

Here, `a`

is `ilo`

if `fx`

is less than -1/3, and 0 otherwise. Similarly, `b`

is `ihi`

only for `fx`

greater than 1/3, and `c`

is `imid`

only for the middle section.

Now, we aim to adapt this pattern to smoothly transition from one section to another, ensuring continuity in the line.

To determine the transition coordinates for dividing the display into three equal parts, we’ll add 1/6 on each side of the previous transition points (-1/3 and 1/3). This results in the first transition occurring between -1/2 (-1/3-1/6) and -1/6 (-1/3+1/6), and the second transition occurring between 1/6 (1/3-1/6) and 1/2 (1/3+1/6).

We define `ab`

and `bc`

as the transitions from `a`

to `b`

and from `b`

to `c`

, respectively. In the first line, we use `smoothstep`

to define `ab`

, which will be 1 when `fx`

is less than -1/2 and 1 when `fx`

is greater than -1/6. Similarly, we define `bc`

in the second line.

Extending the linear interpolation formula to accommodate three sections is the tricky part. From how we’ve defined `ab`

and `bc`

, it’s easy to see that `ilo*ab`

defines the left part, and `ihi*bc`

defines the right part. However, defining the middle part requires considering the complement of the other two. Therefore, the result is `imid*(1-ab)*(1-bc)`

.

Finally, all parts are summed together in `s`

, and this is applied to the line’s width:

```
ab << smoothstep [(-1)/6, (-1)/2] fx;
bc << smoothstep [1/6, 1/2] fx;
s << ilo*ab+ihi*bc+imid*(1-ab)*(1-bc);
hline (-1) (s*2) >> add;
```

`smoothstepp`

`smoothstepp [lowedge, highedge] input`

: the pair-wise version of`smoothstep`

.

See the difference between the two functions in these examples:

```
s << (smoothstep [0.1,0.4,0.8,1.2] [fr, fr/2])/10000;
fit 1 $ zoom 0.5 $ spin (saw s) $ hline 0 0.05 >> add;
```

```
s << (smoothstepp [0.1,0.4,0.8,1.2] [fr, fr/2])/10000;
fit 1 $ zoom 0.5 $ spin (saw s) $ hline 0 0.05 >> add;
```

In the first one, there are a total of four lines:

- Red line, spiraling when
`fr`

is between 0.1 and 0.4. - Green line, spiraling when
`fr/2`

is between 0.1 and 0.4, corresponding to`fr`

being between 0.2 and 0.8. - Blue line, spiraling when
`fr`

is between 0.8 and 1.2. - White line, spiraling when
`fr/2`

is between 0.8 and 1.2, corresponding to`fr`

being between 1.6 and 2.4.

In the second one, there are only two lines:

- Red line, spiraling when
`fr`

is between 0.1 and 0.4. - Cyan line, spiraling when
`fr/2`

is between 0.8 and 1.2, corresponding to`fr`

being between 1.6 and 2.4.