This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Tutorial

An introductory self-contained tutorial to Punctual.

    This tutorial is intended to be a smoother introduction to Puntual for those who have never used it before. Each section links with the corresponding sections in the guide.

    Punctual is a language for live coding audio and visuals. It enables users to construct and modify networks of signal processors such as oscillators and filters on the fly.

    Punctual is compatible with all major web browsers. Throughout this tutorial, we will utilize Punctual within Estuary, a collaborative live coding platform supporting various live-coding languages.

    Setup

    To follow along the examples in the tutorial:

    1. Open a new web browser tab and navigate to https://estuary.mcmaster.ca/. Select Solo Mode.

      Setup 1

    2. In the Terminal/Chat:, type !presetview twocolumns and press enter.

      Setup 2

    3. Use the dropdown in each cell to select the language for that cell. Choose Punctual for the left cell and MiniTidal for the right cell (which we will be using in the later examples).

      Setup 3

    4. If needed, click the ? marker and select Settings. Here, you can adjust settings like Resolution (which I personally like to set to QHD), Frames per Second (FPS), or Brightness to match your preferences. Click again on the ? to hide the Settings.

      Setup 4

    5. You can also change the Theme to Dark on the upper right dropdown list to increase the contrast between the code and the visuals.

      Setup 5

    6. Other elements in the Estuary interface can be hidden as well. Click on the Estuary title to hide the upper text, on the bottom left to hide the Terminal, and on the bottom right to hide the footer. Clicking again in the same areas will make these elements visible again.

      Setup 6

    Shapes

    Let’s start by drawing some shapes:

    circle [0,0] 0.25 >> add;
    

    Shapes 1

    This code snippet draws a circle with its center at (0, 0) and a radius of 0.25. Notice how (0, 0) represents the center of the screen. You can change either coordinate to move the circle around:

    circle [0.5, 0] 0.25 >> add;
    

    Shapes 2

    circle [-0.5, 0] 0.25 >> add;
    

    Shapes 3

    circle [0, 0.5] 0.25 >> add;
    

    Shapes 4

    circle [0, -0.5] 0.25 >> add;
    

    Shapes 5

    Note that moving more than 1 unit in any direction will position the center of the circle outside the screen.

    More information on coordinate system and coordinates sections.

    You can also change the radius to make the circle bigger or smaller:

    circle [0.3, -0.5] 0.5 >> add;
    

    Shapes 6

    circle [0.3, -0.5] 0.1 >> add;
    

    Shapes 7

    Adding more coordinates in a single circle instruction allows you to draw multiple circles at different positions:

    circle [0.5,-0.8,0.2,0.3] 0.25 >> add;
    

    Shapes 8

    In this code, we have a circle centered at (0.5, -0.8) and another one at (0.2, 0.3). We’ll understand why they are of different colors shortly.

    Feel free to experiment by adding as many coordinates to the set as you like. Each additional pair of coordinates will create another circle at the specified position.

    More shapes

    There are some more shapes that can be directly drawn with Punctual.

    Drawing vertical and horizontal lines is quite straightforward:

    vline 0.2 0.01 >> add;
    

    More shapes 1

    This code draws a vertical line at position x=0.2 with a width of 0.01.

    hline (-0.3) 0.01 >> add;
    

    More shapes 2

    This code draws a horizontal line at position y=−0.3 with a width of 0.01.

    The first number in each instruction represents the position along the respective axis, and the second number represents the width of the line.

    As with circles, you can add more numbers to a list to create more lines with a single instruction:

    vline [-0.3,0.2,0.6] 0.1 >> add;
    

    More shapes 3

    Let’s draw now some rectangles:

    rect [-0.1,0.3] [0.4,0.1] >> add;
    

    More shapes 4

    Here, we draw a rectangle with its center at (-0.1, 0.3), a width of 0.4 and a height of 0.1.

    rect [-0.5,0,0.5,0] 0.4 >> add;
    

    More shapes 5

    More information in the Shapes and textures section.

    The add output

    We’ve been using add to draw our shapes. add allows to keep adding different shapes to the screen:

    circle [0.3, -0.5] 0.1 >> add;
    hline [0, 0.5, 0.8] 0.01 >> add;
    rect [0.1,0.3,-0.5,-0.9] [0.3,0.2] >> add;
    

    Add output 1

    In this code, we draw a circle, three horizontal lines, and two rectangles, and they all stay visible on the screen simultaneously because of the add ouput. This feature allows us to create complex compositions by combining different shapes and patterns.

    When drawing many shapes in a single instruction (like the horizontal lines or the rectangles above), each one get a different color.

    In Punctual, a signal can have many channels. In our last example, the circle has one channel, the hline has 3 channels, corresponding to the 3 lines, and the rect has 2 channels, corresponding to the 2 rectangles.

    The add output also has channels. Specifically, it has 3 channels representing red, green, and blue components.

    When sending a signal to the add output, each channel of the signal is matched with the corresponding channel of the output. So, for the hline instruction, the first line goes to the first channel (red), the second line to the second channel (green), and the third line to the third channel (blue).

    When the number of channels of the signal and the output is not the same, the last channel of the signal is repeated to make them match. So, the first rectangle goes to red, and the second rectangle goes to green and blue, which results in cyan.

    The circle has only one channel, so it is repeated for the red, green, and blue output channels, resulting in a white circle.

    More information in the Output notations section.

    Comments

    Using comments in code is a great way to temporarily disable certain instructions without deleting them. In Punctual, lines starting with -- are treated as comments and are ignored during execution.

    For example, consider the following code snippet:

    circle 0 0.1 >> add;
    -- hline 0.5 0.1 >> add;
    

    Comments 1

    In this code, only the circle instruction will be executed because the hline instruction is commented out.

    More information in the Getting Started section.

    Variables

    Using variables can help simplify code and make it more readable. The << operator is used to assign the result of an expression to a variable. Here’s an example:

    c << circle 0 0.1;
    c >> add;
    

    Comments 1

    This code is equivalent to:

    circle 0 0.1 >> add;
    

    Storing the circle expression in the c variable allows you to reuse it multiple times if needed. This not only reduces repetition but also makes the code more concise and easier to understand. Additionally, it can reduce the need for parentheses in complex expressions.

    More information in the Bindings section.

    Colors

    As seen before, color is determined by the signal received by each channel in the add output, being the first chanel the red component, the second the green, and the third the blue.

    We can create any color this way, just by multiplying an expression by the desired color:

    c << circle 0 0.1;
    c * [0.5, 0.2, 0.8] >> add;
    

    Colors 1

    Each color component can take a value from 0 to 1.

    The mono function in Punctual is quite handy for converting a multi-channel signal into a single-channel one. This is particularly useful when you want to avoid coloring a set of shapes when sending them to add.

    For instance, consider these examples:

    l << vline [-0.3,0.2,0.6] 0.1;
    l >> add;
    

    Colors 2

    In this case, each line in the vline command will have a different color assigned. However, by using mono, you can ensure they all are white:

    l << vline [-0.3,0.2,0.6] 0.1;
    mono l >> add;
    

    Colors 3

    Additionally, you can send a color directly to the output without using any shape:

    [0.8, 0.4, 0] >> add;
    

    Colors 4

    0.5 >> add;
    

    Colors 5

    Since add adds colors, you can leverage this behavior to create dark shapes over a colored background:

    c << circle 0 0.8;
    [0.5,0,0] >> add;
    c*(-1) >> add;
    

    Colors 6

    In the last example, the first add command tints the background with a red color, and the second one creates a dark circle by multiplying the circle shape c by -1.

    More information in the Colors and in the Combining channels sections.

    Parenthesis and dollars

    Let’s study for a moment the previous mono example:

    l << vline [-0.3,0.2,0.6] 0.1;
    mono l >> add;
    

    Colors 3

    Here, mono is applied to the whole content of l, the three lines. If we weren’t using a variable, we could have writen an equivalent expression like so:

    mono (vline [-0.3,0.2,0.6] 0.1) >> add;
    

    The parenthesis are necessary, as otherwise Punctual wouldn’t know that [-0.3,0.2,0.6] and 0.1 are arguments for vline and not mono.

    Another equivalent way to write this is by using a dollar sign instead of parenthesis:

    mono $ vline [-0.3,0.2,0.6] 0.1 >> add;
    

    A dollar sign means that all what follows is to be treated as a unit, in this case, as a single parameter for mono. Using $ is more concise, as it reduces the need for additional parentheses, especially in longer or more complex expressions.

    See the Notes on Haskell section for more information.

    Correcting distorsion with fit

    When drawing shapes, you have observed that our circles are not visually circular, and our squares are not visually square. As Punctual uses a coordinate system where the visible screen gets a range from -1 to 1 in each axis, and our browser windows isn’t usually square, our fragments or points in the image are not square, they usually are wider than higher.

    fit allows us to change the aspect ratio of our fragments. Concretely, fit 1 will force fragments to be completely squared, making our shapes visually correct:

    fit 1 $ circle 0 0.2 >> add;
    fit 1 $ rect 0.5 0.2 >> add;
    

    Fit 1

    Note that when applying fit 1, we can’t garantee any more that our visible coordinates are from -1 to 1 on each axis:

    fit 1 $ vline 1.5 0.01 >> add;
    

    Fit 2

    See the Coordinates section for more information on fit.

    Fragment coordinates

    So far we have used fixed numbers for our expressions. There are some expressions that are fragment-dependent, that is, that has a different value for each point in the screen.

    fx is the horizontal coordinate of the fragment, while fy is the vertical coordinate, and fr is the radius, the distance from the [0,0].

    These functions have a lot of useful applications, amongst them the creation of variation in colors:

    fx >> add;
    

    Fragment coordinates 1

    [fr, 0, 0] >> add;
    

    Fragment coordinates 2

    [fy, 1-fx, fr] >> add;
    

    Fragment coordinates 3

    c << circle 0 1;
    c * [0.3, 0, fr] >> add;
    

    Fragment coordinates 4

    See the Fragments and Coordinates sections for more information on fragment’s coordinates.

    Scaling values

    Let’s get back to this gradient example:

    fx >> add;
    

    Fragment coordinates 1

    You’ll notice that the left half of the screen is completely black, and the white gradient affects only the right half. This is because fx takes values from -1 to 1, while color takes from 0 to 1.

    As this is a common situation, we have two functions in Punctual that convert from the -1 to 1 range to the 0 and 1 range and viceversa.

    unipolar reescales a number from [-1,1] to [0,1], while bipolar reescales it from [0,1] to [-1,1]:

    unipolar fx >> add;
    

    Scaling values 1

    See the Scaling values section for more ways to reescale values.

    Oscillators

    Our patterns have been static so far. One straightforward way to animate them is by using oscillators.

    Oscillators are functions that generate varying values over time. In Punctual, oscillators operate within a range from -1 to 1. When using an oscillator, you specify its frequency, which determines how many cycles it completes per second.

    For instance, osc 1 creates an oscillator that oscillates from -1 to 1 and back to -1 once every second. Typically, we use low frequencies for our oscillators. For example, osc 0.1 takes 10 seconds to complete one full cycle.

    We can use an oscillator at any place where a number is expected:

    vline (osc 0.2) 0.02 >> add;
    

    Oscillators 1

    This code creates a vertical line that oscillates in position with a frequency of 0.2.

    vline (osc [0.11,0.15,0.19]) 0.02 >> add;
    

    Oscillators 2

    Three vertical lines oscillate at frequencies of 0.11, 0.15, and 0.19, respectively.

    circle 0 (unipolar $ osc 0.13) >> add;
    

    Oscillators 3

    The radius of the circle oscillates from 0 to 1 at a frequency of 0.13.

    circle [0,0.3,0.3,-0.3,-0.3,-0.3] (unipolar $ osc 0.13) >> add;
    

    Oscillators 4

    Three circles with different positions have their radius oscillate from 0 to 1 at a frequency of 0.13.

    [unipolar $ osc 0.13, 0, unipolar $ osc 0.15] >> add;
    

    Oscillators 5

    This code creates a varying color. As the frequencies of the two oscillators are different, this will produce a lot of different colors over time.

    osc fr >> add;
    

    Oscillators 6

    The amount of white color applied to each fragment oscillates at a different frequency. This creates an interesting and evolving pattern.

    hline (osc $ fx/10) 0.01 >> add;
    

    Oscillators 7

    Here, each point of a horizontal line moves up and down at a frequency that depends of its horizontal position. This creates a vertically symmetric pattern of moving little lines.

    [0,0.3,0.1]*hline 0 (0.01*osc fr) >> add;
    

    Oscillators 8

    This code follows a similar idea, but now is the width of the line at each point that oscillates at a frequency that depends on its distance to the origin.

    More information and other types of oscillators on the Oscillators section.

    Transformations

    There are four main transformations you can apply to any pattern in Punctual: move, spin, tile and zoom.

    move

    move needs the displacement on the horizontal and on the vertical axis:

    move [0.1, -0.3] $ rect 0 0.1 >> add;
    

    Move 1

    Moves a rectangle 0.1 units to the right and 0.3 units upwards.

    move 0.1 $ rect 0 0.1 >> add;
    

    Move 2

    Moves a rectangle 0.1 units both to the right and upwards.

    r << rect 0 0.1;
    move [0, 0, 0.1, -0.3, -0.8, 0.7] r >> add;
    

    Move 3

    Applies multiple displacements to a rectangle, creating three copies of the original at different positions.

    r << rect 0 0.1;
    move (osc [0.19,0.17]) r >> add;
    

    Move 4

    Oscillates the position of a rectangle horizontally and vertically.

    r << rect 0 0.1;
    o << osc [0.19, 0.17]*osc [0.14, 0.15];
    move o r >> add;
    

    Move 5

    Uses combined oscillators to create complex oscillating movements for a rectangle.

    r << rect 0 0.4;
    move [fy,0] r >> add;
    

    Move 6

    Moves a rectangle horizontally based on the fragment’s vertical coordinate. This effectively skews the rectangle.

    r << rect (0.5*osc [0.08, 0.04]) 0.4;
    move [fy/fr,0] r >> add;
    

    Move 7

    The original moving rectangle is deformed by a move operation that displaces each fragment by a different amount, based on its vertical coordinate and radius.

    spin

    spin receives the rotation amount, being 2 a whole turn.

    h << hline 0.5 0.01;
    spin 0.5 h >> add;
    

    Spin 1

    Rotates a horizontal line by 180 degrees, clockwise.

    h << hline 0 0.01;
    spin [0, 1/3, 2/3] h >> add;
    

    Spin 2

    Rotates three horizontal lines by 0, 120, and 240 degrees respectively.

    h << hline 0 0.01;
    o << osc $ [3,7,9]/100;
    spin o h >> add;
    

    Spin 3

    Rotates three horizontal lines at different frequencies, creating a spinning effect.

    r << spin (osc 0.019) $ unipolar fx;
    b << unipolar $ osc 0.013;
    [r, 0, b] >> add;
    

    Spin 4

    Creates a color pattern combining a rotating red gradient and a pulsating blue.

    c << circle (osc [0.18,0.16]) 0.1;
    spin [0, 1/2, 1, 3/2] c >> add;
    

    Spin 5

    In the first line, a moving circle is creating. Then, in the second line, four copies of the original are created, each one rotated by a different amount.

    l << hline 0 0.1;
    spin fr l >> add;
    

    Spin 6

    Spins a horizontal line based radius of each segment, curving the line.

    tile

    tile repeats the pattern the specified number of times on the x axis and on the y axis.

    r << rect 0 0.3;
    tile [5,3] r >> add;
    

    Tile 1

    Tiles a rectangle 5 times along the horizontal axis and 3 times along the vertical axis.

    r << rect 0 0.3;
    tile 4 r >> add;
    

    Tile 2

    Tile a rectangle 4 times in each axis, creating a 4x4 grid.

    c << circle 0 0.5;
    fit 1 $ move [2*osc 0.1,0] $ tile 1 c >> add;
    

    Tile 3

    The circle moves horizontally following an oscillator that oscillates between -2 and 2. tile 1 tiles the pattern, causing the circle to appear on the other side of the screen when it reaches the -1 or 1 coordinate.

    r << rect 0 0.3;
    tile (4+osc [0.13, 0.19]) r >> add;
    

    Tile 4

    The rectangle is now repeated a variable number of times over time, between 3 and 5 in each axis.

    r << rect 0 0.3;
    tile (8*unipolar fy) r >> add;
    

    Tile 5

    By making tile depend on the vertical coordinate of each fragment, a kind of 3D effect is created.

    tile [8,4] $ [unipolar fx, unipolar fy, 0] >> add;
    

    Tile 6

    A color gradient is repeated in a grid.

    r << rect 0 [0.3, 0.3, 0.6, 0.6];
    tile [4,2,8,4,2,1] r >> add;
    

    Tile 7

    In the first line, two squares are created, one inside the other. Then, in the second line, three copies of the pair of rectangles are generated, each one tiled by a different amount, creating a pattern.

    r << rect 0 [0.3, 0.3, 0.6, 0.6];
    s << spin (osc [0.1,-0.1]) r ;
    tile [4,2,8,4,2,1] s >> add;
    

    Tile 8

    Here, the last pattern is modified, by spinning the original rectangle. As two oscillators are used for the spin, the rectangles are duplicated.

    zoom

    zoom zooms in or out the pattern. It needs the zooming amount in the x axis and in the y axis, being 1 the original size.

    c << circle 0 0.4;
    zoom [0.6, 2] c >> add;
    

    Zoom 1

    Zooms the circle, making it narrower along the x-axis and taller along the y-axis.

    c << circle 0 0.4;
    zoom 2 c >> add;
    

    Zoom 2

    Zooms the circle, making it twice as large in both dimensions.

    c << circle 0 1;
    zoom (osc 0.03) c >> add;
    

    Zoom 3

    Creates a pulsating effect on the circle’s size using an oscillator.

    c << circle 0 1;
    zoom [1,1,1/2,1/2] c >> add;
    

    Zoom 4

    Zooms the circle by distinct amounts, creating two copies of it.

    c << circle 0 1;
    o << osc [0.13, 0.13, 0.18, 0.18];
    zoom o c >> add;
    

    Zoom 5

    A variant of the last example, now each circle’s copy is zoomed in and out periodically.

    See Geometric Transformations for more examples and transformations.

    Feedback

    Feedback consists on using the last image frame to build the current one. There are many ways to use feedback, but a simple and very effective one is to add a sligthly attenuated version of the last frame to a changing pattern.

    fb fxy is the last frame without any other changes.

    o << osc [0.17, 0.19];
    circle o 0.2 >> add;
    0.97*fb fxy >> add;
    

    Feedback 1

    Using feedback this way can create interesting trails and persistence-of-vision effects in animations. Feel free to explore this concept with other patterns or combinations!

    See Playing with feedback for other ways to use feedback in your patterns.

    Audio reactive visuals

    The easiest way to create audio-reactive visuals in Punctual is by using the lo, mid, and hi functions. They respectively represent the power of the low, middle, and high frequencies that are playing at every moment inside Estuary.

    Their counterparts, ilo, imid, and ihi, work in the same way but use external audio captured by the computer microphone as their input.

    To experiment with the first group, you’ll need to create an audio pattern inside Estuary. In the second cell in Estuary, select MiniTidal as the language, and then write and execute the following code:

    s "bass arpy linnhats:3"
    

    This code will repeatedly play three sounds: one bass sound composed mainly of low frequencies, one note composed of middle frequencies, and a hi-hat sound composed of high frequencies.

    In the Punctual cell, let’s explore how we can use lo, mid, and hi:

    circle [-0.5, lo] 0.1 >> add;
    circle [0, mid] 0.1 >> add;
    circle [0.5, hi] 0.1 >> add;
    

    Audio 1

    Moves circles vertically based on the power of low, middle, and high frequencies in the audio.

    c << circle [0.5, 0] 0.2;
    spin [lo,mid,hi] c >> add;
    

    Audio 2

    Spins circles based on the power of low, middle, and high frequencies in the audio.

    vline (bipolar lo) 0.01 >> add;
    

    Audio 3

    Moves a vertical line left and right based on the power of low frequencies in the audio.

    l << hline 0 0.1;
    tile 4 $ spin (fr*mid) $ tile 4 l >> add;
    0.9 * fb fxy >> add;
    

    Audio 4

    Creates an animated grid of horizontal lines that spin based on the power of middle frequencies in the audio, with feedback applied for visual continuity.

    See the Audio reactive visuals section to learn about other ways to synchronize audio and visuals.

    Conclusion

    “How do you create cute patterns with Punctual?” a friend of mine asked me once.

    In this guide you’ll find a lot of advanced techniques and creative ideas I’ve been exploring for more than a year.

    But there is no need to learn all of it at once. In fact, you can already create awesome patterns with just the information presented in this tutorial.

    This is a list of ingredients I find useful when creating patterns with Punctual:

    1. Start simple. Tt’s usually enough to begin with a circle or a line. Complexity arises through composition of multiple simple steps. And you always can go back and add complexity to an existing shape later.
    2. Build a pattern step by step. Specially when you are starting to use Punctual, it’s better to organize your pattern in little steps and make sure they are well written. This way, it’s easier to detect a syntax error in the code.
    3. Use variables. You can build a pattern by creating long line of code, but variables make it easier to modify, understand and fix. I often lose some seconds in the middle of my performances just to rearrange my code and simplify things before going ahead.
    4. Add movement. This is one of the first things you’d want to do in any pattern. Even if it’s just a simple circle moving through the screen, it’s already much more interesting than a static scene.
    5. Use symmetry and repetition. That’s why they are called patterns. Punctual makes this simple, as adding numbers to any list will create copies of the existing shapes.
    6. Create irregularity. Symmetry and repetition are cool, but adding irregularity on top of those is what makes a pattern really unique. Use any transformation that depends on one of the fragment’s coordinates (fx, fy, fr) to break the homogenity of a pattern.
    7. Use color. Using mono and multiplying by a color is very easy to tint your patterns. Just by changing a color you can completely modify a pattern’s vibe.
    8. Feedback is your friend. Most patterns benefit from adding a good amount of feedback. Just keep in mind that feedback can result in very bright results, so add it smoothly and be ready to darken your pattern when necessary.

    Next is a beautiful pattern build with these ingredients in mind. It only uses the functions and ideas explained in this tutorial.

    Step 1: Start simple

    hline 0.3 0.002 >> add;
    

    Conclusion 1

    Step 2: Add movement

    l << hline 0.3 0.002;
    spin (saw 0.2) l >> add;
    

    Conclusion 2

    Step 3: Add symmetry

    l << hline 0.3 0.002;
    sp << spin (saw 0.2) l;
    mono $ spin [0,0.5,1,1.5] sp >> add;
    

    Conclusion 3

    Step 4: Feedback is your friend

    l << hline 0.3 0.002;
    sp << spin (saw 0.2) l;
    mono $ spin [0,0.5,1,1.5] sp >> add;
    0.97 * fb fxy >> add;
    

    Conclusion 4

    Step 5: Use color

    l << hline 0.3 0.002;
    sp << spin (saw 0.2) l;
    pat << mono $ spin [0,0.5,1,1.5] sp;
    color << [1, 0.1, 0];
    color * pat >> add;
    0.97 * fb fxy >> add;
    

    Conclusion 5

    Step 6: Create irregularity

    l << hline 0.3 0.002;
    sp << spin (saw 0.2) l;
    mv << move fr $ tile 1 sp;
    pat << mono $ spin [0,0.5,1,1.5] mv;
    color << [1, 0.1, 0];
    color * pat >> add;
    0.97 * fb fxy >> add;
    

    Conclusion 6

    Note the addition of tile 1 before applying move fr in this step. Without it, the lines get out of the screen from time to time. As explained in a prior example, tile 1 tiles a pattern, so when the line leaves the screen for one side, it appears at the opposite site.

    Step 7: Add repetition

    l << hline 0.3 0.002;
    sp << spin (saw 0.2) l;
    mv << move fr $ tile 1 sp;
    t << tile [8, 4] mv;
    pat << mono $ spin [0,0.5,1,1.5] t;
    color << [1, 0.1, 0];
    color * pat >> add;
    0.97 * fb fxy >> add;
    

    Conclusion 7

    Step 8: Small adjustments

    l << hline 0.3 0.02;
    sp << spin (saw 0.2) l;
    mv << move fr $ tile 1 sp;
    t << tile [8, 4] mv;
    pat << mono $ spin [0,0.5,1,1.5] t;
    color << [0.2, 0.02, 0];
    color * pat >> add;
    0.97 * fb fxy >> add;
    

    Conclusion 8

    Here, we increase the line width to make it more visible, but divide the color by 5 to compensate for the high feedback, while mantaining the same red-orange color palette from before.