Combining channels

Different ways how multiple channels can be combined.

When working with Punctual, since many functions and operators have a combinatorial nature, expressions often result in signals with a high number of channels. While this can be desirable in certain cases, it is often not ideal. More channels can make computations more challenging, potentially slowing down or even freezing your environment. It’s relatively common to encounter the need to exit and reenter an ensemble in Estuary due to overly complex patterns, often associated with a high channel count. Additionally, when mixing channels into add or blend, the default color treatment may not always align with your intended result.

Fortunately, there are functions available for combining or mixing different channels, allowing you to reduce complexity when needed.


  • mono: this is one of the simplest but most useful functions to combine channels. It simply takes a signal and combine all channels into one. Very useful when we want multiple shapes in our pattern, but don’t need to have them split in different channels.

Three white circle at different heights:

y << [(-0.5),0,0.5];
mono $ circle [0, y] 0.1 >> add;

Combining channels example 1

Some rotating lines intersecting at the center of the screen:

x << [0.5,0.4..(-0.5)];
y << (x+saw 0.01);
mono $ iline 0 (zip x y) 0.002 >> add;

Combining channels example 2

In order to mix channels, mono simply adds them. This is acceptable when each channel represents a shape, like in the previous examples, but can be a problem in other situations. For example, mono [0.5, 0.5] >> add; evaluates to 1 >> add;, resulting in a completely white screen.


  • blend is intended to mix channels into a group of four, to be used with >> blend. For every two groups of 4 channels, they are mixed by using the alpha channel of the second group to interpolate between the two.

For example:

c1 << [1, 0, 0.7, 1];
c2 << [0.5, 1, 0.3, 0.2];
blend $ c1++c2 >> blend;

Combining channels example 3

The resulting color is [1*0.8+0.5*0.2, 0*0.8+1*0.2, 0.7*0.8+0.3*0.2, 1*0.8+0.2*0.2] = [0.9, 0.2, 0.62, 0.84].


  • rep takes an integer n and a graph g as arguments. It replicates the channels in g n times. So, if for example g has 3 channels the result of applying rep 2 will have 6.

NOTE: This function is currently undocumented.

rep 3 (unipolar $ osc 0.1) >> add;

Combining channels example 4

This is equivalent to:

o << unipolar $ osc 0.1;
[o,o,o] >> add;


  • unrep takes an integer n and a graph g as arguments. It mixes every n consecutive channels in g into 1. So, if for example g has 12 channels, the result of applying unrep 3 will have 4.

NOTE: This function is currently undocumented.

l << hline [0.9,0.8..(-0.9)] 0.001;
unrep 4 l >> add;

Combining channels example 5

Here, l has a total of 19 channels. With unrep they are grouped 4 by 4, with the exception of the last group that only joins 3 channels.

It’s important to note that the number of channels must be constant and known to Punctual when an expression is executed. That means that expressions like unrep (step [2,3] $ saw 0.3) l are incorrect, as the resulting number of channels would vary through time.

You can still simulate this behavior by building both signals and showing only one of them at any moment:

l << hline [0.9,0.8..(-0.9)] 0.001;
u2 << unrep 2 l;
u3 << unrep 3 l;
s << step [0,1] $ saw 0.3;
u2*s >> add;
u3*(1-s) >> add;

Combining channels example 6

Let’s see some creative ideas around unrep. In this first example, we start by creating 10 horizontal lines. Using the unrep function, we then consolidate these 10 channels into 2 groups of 5 channels each.

Next, we define two colors, c1 and c2. The objective is to assign one color to the first group of lines and the other color to the second group. To achieve this, we use the zip function to pair the corresponding red, green, and blue components of the two colors.

The *: operator multiplies each group of lines by its associated set of color components. However, a challenge arises as the color components are paired (two red, two green, and two blue), which would result in an interpretation of rgbrgb rather than the desired rrggbb when sent to the rgb output. To resolve this, we employ the unrep function once again, grouping the channels into pairs.

l << unrep 5 $ hline [0,0.1..0.9] 0.02;
c1 << [1,0.5,0.8];
c2 << [1,0.4,0];
unrep 2 $ l *: (zip c1 c2) >> add;

Combining channels example 7

Now that we understand how to assign different colors to distinct sets of elements, let’s apply this concept to create a more intricate pattern.

In this example, we start by forming a quadrilateral with dynamically moving vertices. To achieve this, we define coordinates using x and y, combining them with zip. As a result, we obtain two points for each argument in the line function, which, when combined combinatorially, give us four lines.

Building on the previous idea, we color two of the lines with c1 and the remaining two with c2.

Finally, with the application of a substantial amount of feedback, captivating line patterns emerge.

x << osc [0.11,0.23];
y << osc [0.13,0.17];
l << unrep 2 $ spin (saw 0.03) $ line (zip x y) (zip y x) 0.003;
c1 << [0.5,0.25,0.4];
c2 << [0.5,0.2,0];
fit 1 $ unrep 2 $ l *: (zip c1 c2) >> add;
gatep 0.2 $ 0.99*fb fxy >> add;

Combining channels example 8

In this last example, unrep is applied to feedback.

Initially, a straightforward pattern of moving lines is created. The feedback is then taken in polar coordinates (refer to Playing with feedback), and it is duplicated using spin, resulting in a 3-way symmetry.

Since the feedback is the last frame image, it always has three channels. After applying spin, the signal expands to a total of nine channels. The unrep 3 bit collapses each copy of the feedback into a single channel. When sent to rgb, this is interpreted as red, green, and blue, respectively."

tile [4,2] $ line (osc [0.1,0.2]) (osc [0.3,0.4]) 0.02 >> add;
unrep 3 $ spin [0,2/3,4/3] $ (fb frt) * 0.9 >> add;

Combining channels example 9