You are on page 1of 55

AVS Programming Guide

Version 1.1
Tom Young (A.K.A. PAK-9)

Contents: Introduction Part 1: The Fundamentals Part 2: The Superscope Part 3: Movements & Dynamic Movements Part 4: The Third Dimension Part 5: Registers Part 6: Megabuf and loops Part 7: Coding Effect Lists Part 8: Mouse Input Part 9: The Triangle APE Part 10: Advanced 3D Part 11: Tips & Tricks Part 12: Advanced Exercises

Note: The contents of this document are the intellectual property of Thomas Young; you may distribute this document freely provided it is not modified in any way. This document may not be included in any commercial product for profit or otherwise without the express permission of the author. The code contained in this document may be used freely with or without modification. Advanced Visualization Studio is copyrighted by Nullsoft.

Introduction Welcome to the AVS programming guide, a document written for novice AVS artists to help them learn the basics of the AVS programming language. This document assumes you have experimented at least a little bit with the effects of AVS but no prior programming experience is necessary. Note that this is not a reference document, so it is not a replacement for the expression help, which I recommend you use to familiarize yourself with the basics of syntax and operators. This is also not a maths document, many people think complex AVS presets require a lot of maths, however this is rarely the case. Having said that, we need to use maths sometimes to solve programming problems so be prepared for a little theory. The layout of the document is designed to introduce new topics at a steady pace, but if the early topics seem too basic, feel free to skip to later parts. The philosophy is learning by doing, so most parts are a brief description with an example or two, as well as exercises that I recommend you attempt to improve your understanding of the topic. I dont get much feedback about this guide so I can only assume that there is nothing wrong with it, if you disagree or have any suggestions for improvement (feedback of all kinds is appreciated) email me at: PAK.nine@gmail.com A learning philosophy I recommend you subscribe to: Read and forget Write and remember Do and understand

Part 1: The Fundamentals The AVS window is essentially a canvas onto which objects are placed and manipulated to produce effects. The main elements are split into two categories, Render elements and Trans elements. Render elements literally render to the window, that is, pixels are placed in the window (possibly blended with what is already there). Trans elements affect the existing pixels in a certain way, by shifting them or changing their value (remember, pixels are simply a red, green and blue value). The AVS window itself spans from -1 to 1 in the X and Y. The X is the horizontal axis, -1 being the far left and 1 being the far right. The Y is the vertical axis, -1 being the top and 1 being the bottom. Therefore it should be obvious that rendering a pixel at 0,0 will produce a dot in the exact centre of the AVS window. The programming in AVS is broken into 4 execution times (points at which the code is run), Init, Frame, Pixel (or Point) and Beat. Init stands for initialization, code in this box is executed when the preset is started (there is an exception to this but for now assume this is the case). Frame is executed once for every frame that AVS renders. The amount of times the Pixel code will execute will vary depending on the element, for example a superscope with n points will execute n times every frame. Often people make the mistake of putting code in the Pixel box which could go in the Frame box, which means the code is executed many more times than necessary, we will address this problem throughout the guide. Beat code is executed when there is a detected beat in the music. We will not go into great detail regarding the functions provided by AVS, as these are documented in the expression help; however it is a good idea to look at these for a quick idea of what is possible. Instead you should just ensure you are familiar with the following: Variables are created on assignment, that is MyVar=0 will create the variable MyVar. So MyVar=MyVar+1 is enough to create an incrementing variable Assignment is done using the = sign. So MyVar=1 will put the value of 1 into the variable MyVar. Functions return values, so MyVar=sin(2) is calling the sin function, and returning a value that is put into MyVar. This applies to most functions, although many simply return a 1 or 0. Every statement should end with a semicolon. So MyVar=0;

These are just reminders, look in the expression help for more detail about the basic syntax of AVS and some of the operators that are available.

Part 2: The Superscope Most people would agree that the superscope is the main render object in AVS, and it is extremely powerful. Some important variables are: n b i Represents the number of points to render Can only be read, 1 if there is a beat, otherwise 0 A value that changes from 0 to 1 according to the point being rendered, i can be defined as 1/n*p where p is the current point being rendered. The value of the waveform or spectrum (as specified) at the current The wave/spec data is a signal from 0 to 1, so for example a 3 point superscope will take data from 0, 0.5 and 1. The amount of red colour, from 0 to 1, this can be set in any box. As above with blue As above with green If rendered as lines, the line width, 0 to 255 If set above zero, the current point will not be rendered If non zero, will render lines, otherwise it will render dots.

v point. red blue green linesizeskip drawmode

Phew, well you dont need to remember all off those, they are just there as a reference. The effect of some of these variables can actually be replicated, for example v, which can be replicated with getspec() or getosc(). Okay enough of boring theory, lets get coding. The first thing we are going to make is a simple scope, so fire up AVS and create a new preset and add a superscope, ensure clear ever frame is ticked in main (this will clear the AVS window at the start of every frame). Then type the following into the code boxes:
Init n=800 Frame Beat Point X=2*i-1; Y=v*0.5;

Okay there we have our scope, now lets look at the code. First of all we have set n to 800 meaning we want 800 points for our superscope, if you change it to 8 you will notice how much more crude the scope is, because we are representing the scope with less points. We put this code in the init because we only want to tell AVS how many points there are once, that variable is set and we dont need to worry about it any more.

We then set X to 2*i-1 why? Because we want the scope to span the whole window, we cant just use i because it only spans 0 to 1, and we need -1 to 1. So we multiply it by 2 (now it spans 0 to 2) and subtract 1 (now -1 to 1). Finally we set Y to v*0.5, we could have used just v, but it looks a bit messy filling the whole screen, so we multiply it by 0.5, now rather than spanning -1 to 1 it spans -0.5 to 0.5 much nicer. Remember I said we could replicate v? Well lets give it a go, we need the oscilloscope data so we will use getosc(), the syntax is getosc(band,width,channel), where band is the point to sample from 0 to 1, width is the width of the sample from 0 to 1 and channel is the channel (left, right or centre). Dont worry if that doesnt mean too much to you, the important part is the band, width will be 0.1 and channel will be 0 (centre). Replace your point code with this:
Point X=2*i-1; Y=getosc(x,0.1,0);

Now we are feeding x into the getosc() function to retrieve scope data, but something is wrong, band should be a value from 0 to 1 and we are feeding it a value from -1 to 1. See if you can think of a solution before you continue. The solution is to multiply and shift the x to make it fall within 0 and 1, so how do we convert -1 to 1 to 0 to 1? Well first of all its 2 times too big, so x*0.5 is a good start, but now we have -0.5 to 0.5 so we need to shift it by 0.5 x*0.5+0.5
Point X=2*i-1; Y=getosc(x*0.5+0.5,0.1,0);

Tada! You just made your own version of v, congratulations. Exercises: Make a scope that is vertical instead of horizontal. Make a scope that spans from -0.5 to 0.5 in the X. (Exercise 2A) (Exercise 2B)

Okay lets move onto a different area, creating a superscope that draws a line from an arbitrary point to another arbitrary point. Seems like a simple problem, but we will look at some new functions in order to implement it. First of all we are going to drop i thats right, no i for this problem. Make a new preset, add a superscope and set n to 2, because we are only going to need 2 points, a start and a finish. Now remember that the point code is executed once for each point, so all we need is a way to determine which point is currently being rendered. Enter the following code:
Init n=2

Frame drawmode=1; CurPoint=0; Beat Point CurPoint=CurPoint+1;

Now each frame the following happens: CurPoint is set to 0 (frame code) CurPoint is set to 1 (point code) Rest of the Point code is executed CurPoint is set to 2 (point code) Rest of the Point code is executed It is important that you grasp this concept as is it key to the operation of a superscope, the frame code is executed, then the point code is executed twice (because n=2) and we are incrementing CurPoint in the pixel code to give ourselves a way of identifying what point is being rendered. Okay now that we have a way of knowing what point is being rendered we can specify where to render using an if statement
Point CurPoint=CurPoint+1; X=if(equal(CurPoint,1),-0.5,0.5); Y=0;

Here we are saying the X should be set to -0.5 if the value of CurPoint is 1, otherwise set it to 0.5. So if we just extend this idea to a slightly more complete solution:
Init n=2; X1=-0.5; Y1=0.25; X2=0.5; Y2=-0.25; Frame drawmode=1; CurPoint=0; Beat Point

CurPoint=CurPoint+1; X=if(equal(CurPoint,1),X1,X2); Y=if(equal(CurPoint,1),Y1,Y2);

Now we have a superscope that will draw a line from X1,Y1 to X2,Y2. Okay now lets make this solution a bit better, we can actually remove that equal() from each of the if statements because an if statement just checks if the first argument is a 0 or not in order to evaluate. Since we have a variable that is either 1 or 2, we can make a small adjustment to make it either 0 or 1 and pass it straight into the if statement.
Point X=if(CurPoint,X1,X2); Y=if(CurPoint,Y1,Y2); CurPoint=CurPoint+1;

By moving the addition to the last line, Curpoint will be 0 for the first point and 1 for the second point. Exercises: Make a superscope that draws between 3 arbitrary points. (Exercise 2C)

Okay lines are all very well and good, Im sure by now youve tried making a sine wave and maybe some other tricks by now, but what about solid objects? Im sure you have seen presets where people produce cubes and other geometric objects, well lets have a go ourselves. Well stick to 2D because you can apply the same principles in 3D and its covered in a later chapter. The key principle to solid superscopes is that they are made of lines, hopefully this isnt too much of a let down, but lines is all that a superscope can do (and dots of course). So the trick is to produce a series of lines that are so tightly packed that they have the appearance of a solid object. Lets try a solid square to start off with, make a new preset with a superscope and enter the following:
Init Frame n=w; drawmode=1; switch=0.5; Beat Point switch=-switch; x=0; y=switch;

Okay there are few things to note, first of all we have moved n into the frame box and set it to an expression rather than a constant. w is a variable that contains the width of the AVS window, so we have set the number of points to that value. This may seem like a lot of points but in order to make out object appear solid we need a lot of lines. It is in the frame box because we want it to remain w even if the user resizes the AVS window, if the code was in init it would not be executed after the start of the preset. Can you work out what the variable switch does? It changes from 0.5 to -0.5 for each alternate point. Okay, but wheres my solid square! I hear you cry, well hopefully you have worked out that currently the superscope is rendering lots of lines from 0.5 to -0.5 and back again, so if we change the x to i-0.5 what do we get? Thats right a lovely solid square! But it looks a bit bland so lets add some shading:
Point switch=-switch; x=i-0.5; y=switch; blue=cos(x*2); red=0; green=0;

I dont need to explain what the cos() function does, but notice that we have passed x as an argument so that the colour intensity will change with the x axis. Lets try changing the colour across the Y instead:
Point switch=-switch; x=i-0.5; y=switch; blue=cos(y*2); red=0; green=0;

Oh dear, that doesnt look very good at all, do you know why the colour isnt changing in the Y axis? Well we are drawing lines between Y=-0.5 and Y=0.5, and the colour is set for each point, so we are only getting the colurs blue=cos(-0.5*2) and cos(0.5*2). In other words, the colour your getting is approximately blue=0.54. One final note on optimizations, rather than have n=w, lets try n=w*0.5. Looks a bit liney doesnt it, well there is an easy fix, change the linesize to 2. This way the width of the lines will compensate for the gaps:
Frame n=w*0.5; linesize=2;

drawmode=1; switch=0.5;

We do this because superscope points are costly in terms of frame rate so we want to keep them to a minimum. This solid superscope is also a good situation in which to illustrate the skip variable, lets cut off the right hand side of the solid square:
Point switch=-switch; x=i-0.5; y=switch; skip=above(x,0); blue=cos(x*2+col); red=0; green=0;

Here we are setting skip to 1 when x is greater than zero using the above() function, try changing it to below(), you can probably guess the effect it will have. Okay now lets cut out a chunk from the middle:
Point switch=-switch; x=i-0.5; y=switch; skip=band(above(x,-0.25),below(x,0.25)); blue=cos(x*2+col); red=0; green=0;

Here we are using the band() function, which means it will return 1 if x is above -0.25 AND below 0.25. Remember, we could not apply this to the Y axis because we can only skip points, we cannot chop lines up into pieces. Exercises: Create a solid square with shading along the Y axis. Use linesize to make a solid square with a 2 point superscope. (Exercise 2D) (Exercise 2E)

10

A solid superscope that we can colour in both axis is our next challenge, for this we will need to divide each of our lines into pieces. How many pieces we divide each line into is a matter of personal preference, although we do not want to pick a number too large or our frame rate will take a big hit. We should also have a fixed number of points (rather than n=w*0.5 or something) so that we dont encounter problems calculating the dimensions of our solid object. It can be done with a dynamic number of points, but

Etc

Skip these solid lines

the code is significantly more complex. As you might have guessed, we are going to ditch i again, because we need a little more control over our point positions. Before we start we need to do some calculations, lets say we are going to have 101 lines, each one divided into 11 points per line (these values may not seem very round, but later you will see the logic), what should are n value be? Ill let you think about it while we get on with the implementation Right, onto the coding, create a new preset, clear every frame and add a Superscope.
Init n=800; Frame drawmode=1; line=-1; linepos=-1; Point x=line; y=linepos;

11

That n value is wrong at the moment but we will change it later, for now, try to see why we have the variables line and linepos. The line will be which vertical line we are dealing with and linepos will be the chunk of the line we are currently drawing. Because we have set line and linepos to -1 you should be able to see we are going to draw from the top left to the bottom right. Now add this code to the bottom of the point box:
line=if(equal(linepos,1),line+0.02,line); linepos=if(equal(linepos,1),-1,linepos+0.2);

Hopefully you can follow this code, what we are essentially doing is adding to linepos 0.2 each time, thereby putting a point further down the screen, until we reach the bottom of the screen where we set it back to -1 so that we can start a new line. The line itself only moves along when the last line has been finished, i.e. linepos =1. Only one problem remains, we are still drawing the diagonal connecting lines as shown in grey in the diagram above. We need to skip these, so we replace the code with:
skip=equal(linepos,-1); line=if(equal(linepos,1),line+0.02,line); linepos=if(equal(linepos,1),-1,linepos+0.2);

Note that the order in which we put these commands is very important; if the skip was at the end we would be skipping the wrong chunk of the line. Okay we are essentially finished, but you have probably noticed your solid object is only partly drawn, so we need to calculate the correct n value. Notice that because we have 101 lines and 11 points per line, we can use nice round numbers like 0.02 and 0.2 in our code remember, a 10 segment line needs 11 points to draw. You are probably used to n values being a matter of preference or using a little guesswork, but with a problem like this the number of points is a non-trivial problem, if we have to few or too many we will get overlap or under-run. In this particular case we can work out the points needed fairly simply because we have a nice clean algorithm (even if I do say so myself ;) ) it is simply the number of lines times the number of line points 101*11=1111. So lets add a little tidying up, that equal(linepos,1) would be a bit nicer as a separate variable, we need to change the linesize to fill in the holes, and we should scale the square to a more visible size. The final version looks like this:
Init n=1111; Frame drawmode=1; linesize=w*0.01; line=-1; linepos=-1; Point

12

x=line; y=linepos; newline=equal(linepos,1); skip=equal(linepos,-1); line=if(newline,line+0.02,line); linepos=if(newline,-1,linepos+0.2); x=x*0.75; y=y*0.75;

There, nice and tidy. Now lets add some shading to the end of the point box
red=sin(x*2); blue=sin(y+0.5); green=sin(x-0.6)*sin(y-0.5);

You can tell that the shading in the y axis is pretty blocky, even though we have a superscope with over 1000 points. In other words, high quality solid superscopes are very expensive in terms of fps. Of course you could always increase the number of line divisions Exercises: Modify the solid superscope to have 21 points per line (Exercise 2F) Approximate a solid superscope with w*0.5 lines and h*0.5 points per line (Exercise 2G)

13

Part 3: Movements & Dynamic Movements Movements and Dynamic Movements (DMs) are two of the most powerful trans objects that exist in AVS, we will start by looking at movement and then extend into DMs. The main difference between a movement and a DM is that you cannot use changing variables in a movement, the code is compiled once and then executed for each pixel each frame. A movement uses the following variables: d r x y This value is the depth of each pixel This value is the rotation of each pixel The x value of each pixel The y value of each pixel

The d and r values can only be used if the rectangular coordinates checkbox is unchecked and the x and y can only be used if it is checked. This is because a movement has essentially 2 modes, to make certain operations easier (such as rotation); To understand these different modes of operation we need to take a look at coordinate systems, if you are already familiar with rectangular and polar coordinate systems you can skip this brief introduction. A brief look at coordinate systems You should already be familiar with the rectangular coordinate system since it is the standard coordinate system of AVS, in order to reference a point with this method we specify an x and a y component, from 0 to 1 in each case. This is a typical and hopefully very intuitive coordinate system, you may also hear it referred to as a Cartesian coordinate system. You may be curious as to why the y axis considers the top of the window -1 and the bottom 1 rather than vice versa, well in practical terms it makes no real difference, it is referred to as a right handed coordinate system (more on this in part 4) and it is simply a design choice that was taken by the makers of AVS.

14

However another common coordinate system is the polar coordinate system, where a point is defined by the distance from the origin (d) and the rotation (r). At the centre of the window, d=0 since that is the origin, at the points defined by a circle touching the edge of the window, d=1. The value of r ranges from 0 to defining the rotation in radians (r increases anticlockwise and decreases clockwise), where r= for example is a 180 degree rotation. There is an equation to convert polar to rectangular coordinates (and back) however I would rather not introduce it at this point because understanding the concept is more important. Here are a few examples of equivalent values in the two coordinate systems, try to see intuitively (use the diagrams to help) why they are equivalent. Remember that r=0 is the same as the negative y axis (another AVS design choice, r=0 is more commonly the same as the positive x axis)

Rectangular (Cartesian) x=0 x=0 x=0 x=0 x=1 x=y=0 y=-1 y=-0.5 y=1 y=0

Polar d=0 d=1 d=0.5 d=1 d=1 r= r=0 r=0 r=0 r= r=-

y=-

d=1

If you wish to see this in action more closely open the AVS preset Demonstration Coordinate Systems in the AVS programming guide folder. Okay thats the end of coordinate systems theory, remember this isnt a maths document so if you are having trouble understanding this concept I suggest you look elsewhere for a better description. On to the coding! Lets start with a new preset, clear every frame, add a superscope with the following code:
Init n=5 Frame drawmode=1; linesize=10; point=0; Beat Point x=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3); y=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3);

15

x=x*0.5; y=y*0.5; point=if(equal(point,3),0,point+1);

Hopefully you can see that this will draw a square, remember trans elements only affect what is already rendered so a movement on its own will do nothing. Now add a movement, select (user defined) from the list and enter the following code:
User defined code: d=d*2;

Notice that the square is now half the size. Why? Well we are saying that for each pixel (in polar coordinates) we take the pixel which is two times as far away from the origin, so for each pixel where d=1 we take the pixel at d=2 (of course there are no pixels at d=2 though so we just take a black pixel). The same effect can be achieved by checking the rectangular coordinates box and entering:
User defined code: x=x*2; y=y*2;

Here we are doing something very similar except in rectangular coordinates, i.e. for each pixel, take the pixel that is twice as far away from the origin in the x and the y. This can be a little counter-intuitive at first because people often think Well if I multiply each position by two it will be twice as big however remember you are assigning a new pixel position based on the current coordinates. So based on what I have just said, in which direction will this code move the pixels?
User defined code: x=x+0.5;

The answer is left, because we assign each pixel the value of a pixel 0.5 to the right of it. Remember that 0.5 is a coordinate value, not a number of pixels, so x=x+1 will shift everything completely off the screen. But I want to move everything n pixels you say, well to move a fixed number of pixels you first need to calculate the size of one pixel in terms of the coordinate system, Ill let you try to solve that problem yourself (its part of one of the final exercises in part 12). Now uncheck the rectangular coordinates box and enter:
User defined code: r=r+$pi*0.25;

Here we are rotating the screen by 45 degrees, the value $pi is a constant for pi that we can use instead of typing it out. Why cant we say r = r + 45 ? Because remember the polar coordinate system uses radians rather than degrees, if you need to convert an

16

angle from degrees to radians, just multiply the value by pi/180. And yes, 45 * pi/180 = pi*0.25. Lets look at a very useful (and commonly used) feature of movements, creating full screen gradients. Create a new superscope:
Init n=800 Frame drawmode=1; linesize=1; Beat Point x=2*i-1; y=0; red=cos(x*$pi+$pi*0.5); green=cos(x*$pi); blue=cos(x*$pi-$pi*0.5);

There we have a simple line superscope that produces a gradient of red, green and blue; however we would like it to fill the whole screen rather than just be a line. Let us try to think about the problem from the perspective of a movement (i.e. what coordinates do we want to assign each pixel based on their current coordinates?). Well for each pixel, we want the x value to remain the same, but we want the y value of each pixel to equal a point on the line superscope. Since the line superscope is sitting on y=0, we can say for each pixel x=x and y=0, but of course that x=x is superfluous so we can just say y=0. That was simple, go ahead and add a movement after the superscope, check rectangular coordinates and add the code:
User defined code: y=0;

There we have it, this type of effect is very useful because it fills the whole screen with just a few lines of code, and it runs extremely fast. So as a background to a preset, a gradient is a good choice (dont use a nasty RGB gradient though, experiment to find something more subtle.) Lets expand this idea to make a circular gradient, that is, where the colour varies uniformly from the centre to the edge of the window. Hopefully you can see that it will be a lot simpler using polar coordinates, but you will probably find it harder to envisage the solution. You may have also guessed already that we are going to use r=0, but lets step through the problem logically. If you imagine the polar coordinate system in your mind, the value of d and r is different for every pixel, now imagine what happens if the value of r is a constant. In

17

this case we are saying the value of d can vary, so the pixel colour can vary based on its distance from the origin, but the pixel colour will be the same for each separate value of d. If we want a gradient using movement of r=0, then we need to create a superscope from d=0 to d=1 where r=0, where is this? Well looking back at the diagram:

So it should be pretty clear then that we need a superscope from rectangular coordinates 0,0 to 0,-1. No problem, make a superscope and add the following code:
Init n=800 Frame drawmode=1; linesize=1; Beat Point y=-i; x=0; red=cos(y*$pi*2+$pi*0.5+$pi); green=cos(y*$pi*2+$pi); blue=cos(y*$pi*2-$pi*0.5+$pi);

Then afterwards add our movement with the code:


User defined code: y=0;

Hooray, a lovely circular gradient. Thats all we are going to look at for movements, I realize I labored the rectangular/polar coordinates issue but it really is important for understanding the mechanics of movements and DMs.

18

Right, its state-the-obvious time: A Dynamic Movement is a movement, except it is dynamic Why is this important? Because if you can do it in a movement, you can do it in a DM, therefore everything we just covered regarding movement is still valid here. The big difference is that now we can have variables which change, meaning the way in which pixels are affected can change dynamically too. Something worth noting is that although you can use the same (static movement) code in movements and DMs, the quality will be worse in a DM; this is because the DM breaks the screen down into blocks for efficiency. Each block is approximated rather than exactly calculated; however you can improve the accuracy by increasing the grid size. If you increase the grid size it is a good idea to use multiples of 2 because computers are faster at working with multiples of 2 than arbitrary numbers. DMs are compiled in similar stages to a superscope, on init, frame, beat and pixel, meaning you have a lot of control over the movement of pixels. Start a new preset with a superscope:
Init n=5 Frame drawmode=1; linesize=10; point=0; Beat Point x=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3); y=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3); x=x*0.5; y=y*0.5; point=if(equal(point,3),0,point+1);

This is just our superscope square, now add a Dynamic Movement after it with the following code:
Init Frame move=move+0.01; Beat Pixel x=x+move;

19

Now turn on rectangular coordinates and wrap. Notice how the square moves across the screen and wraps around the screen when it leaves one side. We can also rotate an image using the r variable, try un-checking rectangular coordinates and entering:
Init Frame move=move+0.01; Beat Pixel r=r+move;

This should all be fairly familiar because we are simply applying similar effects as we tried in the movement except with variables that are changing. Try checking the blend box and notice how the image is blended with the original. This is actually a very powerful function as we can use it to blend certain parts of the image using the alpha variable. The alpha is set to a value from 0 to 1, 1 being 100% opacity of the new image and 0 being 100% opacity of the old image. Replace the pixel code with this:
Pixel x=x+move; alpha=sin(move);

Now we are fading between the superscope square, and our modified (or moved) pixels. Now lets try this:
Pixel x=x+move; alpha=above(y,0);

Here we are saying, if y is greater than zero, alpha is 1 otherwise it is zero, so we have split the screen into halves. Alpha blending can also use a buffer rather than whatever came before it, start a new preset arranged like so:

20

Set the Clear screen to white, check blend in the dynamic movement and set the source combo box to Buffer 1. What we are doing is using whatever is in the buffer as the data to be blended with, so unchanged we are blending 50% black (the buffer) with 50% white (the clear screen), i.e. grey. So hopefully you have guessed that this:
Pixel alpha=0;

Will give us white; and this


Pixel alpha=1;

will give us black. What about:


Pixel alpha=d;

Well, we know that d varies from 0 at the centre to 1 at the edges of the window, so we get a nice gradient from white to black. Now lets try:
Pixel alpha=above(d,0.5);

Yuck, thats supposed to be a filled circle (if you dont see why go back to a brief look a coordinate systems), looks pretty horrible right? This is often referred to as the notorious ugly edge syndrome associated with DMs. It occurs when you have a very high frequency change in colour, since the DM is only approximating the value of each grid square. We can reduce the effect by changing our grid size, try 32x32, looks a bit better but its still pretty jagged. Try 256x256, ah yes thats much better now look at your frame rate counter. A drop in frame rate of that magnitude is unacceptable, you may think its not that bad, but remember that the code in our pixel box is now being calculated exponentially more times. To illustrate the point try changing your pixel code to:
Pixel loop(10, assign(a,sin(rand(100))) ); alpha=above(d,0.5);

Dont worry about the syntax, all the loop code does is 10 arbitrary sine operations, but look at your frame rate now. Ill wager its under 10fps, and thats just from adding 10 sine calculations. The point I am trying to make is that you have to keep your grid size as small as possible, but it is a tradeoff between quality and speed. I would suggest you never need a gridsize above 128x128, and for many applications very low values like 4x4 or 8x8 are perfectly acceptable.

21

Finally here are the equations for converting polar to rectangular coordinates and vice versa, they are written as AVS code rather than mathematical equations: Rectangular to polar:
r=atan2(-x,-y); d=sqrt(sqr(x)+sqr(y);

Polar to rectangular:
x=-d*sin(r); y=-d*cos(r);

22

Part 4: The Third Dimension I can imagine a lot of people skipping to this section, because 3D is a very popular AVS technique that people often have difficulty with. Ive stressed that this is a programming guide and not a maths document but Im afraid we will need to delve into some maths for this chapter. First of all, lets learn how to rotate something in 2D, thats easy I hear you say just use r r = r + 1, all done! bzzzpt wrong, sorry. We need to work with rectangular coordinates here so that when we extend into the third dimension we dont encounter problems expressing our 3D projection. The key to rotation is rotation matrices, that is, a matrix to which we can pass our x, y and rotation amount and retrieve the translated pixels. The first rotation matrix is: x=x*cos(rot)-y*sin(rot); y=x*sin(rot)+y*cos(rot); Where rot is the angle of rotation in radians. Dont worry about understanding the maths behind this particularly, just accept that this is how you rotate things. Okay lets implement this into a preset, make a new preset, clear every frame and add a superscope with the following:
Init n=5; Frame Rot=0; Point=0; Beat Point x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3); y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3); x1=x1*0.5; y1=y1*0.5; x=x1*cos(rot)-y1*sin(rot); y=x1*sin(rot)+y1*cos(rot); point=if(equal(point,3),0,point+1);

Okay dont be put off if it seems like a lot of code, lets just look through it a step at a time. The first three lines of the pixel box are defining a square (if you dont see how go back to part 2), and the next two lines simply resize it to be a bit smaller. Notice that we are now using x1 and y1 instead of just x and y, this isnt strictly necessary in this case, but it is good practice to reference variables rather than the pixels

23

themselves as you will see later on. The last two lines are the rotations themselves, they use x1 and y1 and the variable rot that is defined in the frame box. Try changing the value of rot to something other than 0, now try rot=rot+0.01; There we have our rotating square, it took a little work but Im sure you followed it. Now lets try it in a DM, set rot=0 in that superscope to make it just a plain square again, then add a DM after it, rectangular coordinates and add the following code:
Init Frame rot=rot+0.01 Beat Point x1=x; y1=y; x=x1*cos(rot)-y1*sin(rot); y=x1*sin(rot)+y1*cos(rot);

Note that in this case we DO need to place the x and y into variables, because we are affecting the pixels as we are processing them. Try:
Point x=x*cos(rot)-y*sin(rot); y=x*sin(rot)+y*cos(rot);

Notice the zany results. Exercises: Create a superscope square that allows you to specify its rotation in degrees. (Exercise 4A) Create a DM that resizes the screen to 50% and rotates it by pi/5 radians (rect cords) (Exercise 4B)

24

Now that we have mastered rotation in 2D we can move onto 3D, for this we need two more rotation matrices, so that we have 3. The following are the rotation matrices:
X1=x; Y1=y; Z1=z; x2=x1*cos(rotz)-y1*sin(rotz); y2=x1*sin(rotz)+y1*cos(rotz); z2=z1; x3=x2*cos(roty)+z2*sin(roty); y3=y2; z3=-x2*sin(roty)+z2*cos(roty); x4=x3; y4=y3*cos(rotx)-z3*sin(rotx); z4=y3*sin(rotx)+z3*cos(rotx);

Seems like a lot of code, but you can see how it is the some principle as our one rotation matrix, just applied twice more. Note that the input for the rotation matrices is the result of the previous one. Okay, hold out your left hand so that your palm is perpendicular to you; make your thumb face up, your index finger face away from you and your forefinger point right (perpendicular to your palm) and clasp your remaining two fingers to your palm. You should be making a gesture like this: Thumb (y axis) Index finger (z axis)

Fore finger (x axis)

The variables rotx, roty and rotz are the amount of rotation around those axis. So rotation around the y axis (changing variable roty) is like keeping your thumb pointing up and rotating your hand. Rotating around z (changing rotz) is like keeping your index finger facing away from you and rotating your hand etc

25

One last step is needed before we can implement this, once we have passed our values through the rotation matrices we need to project the pixels onto our 2D area, this is a little like interpreting the result to fit our window. The projection is:
x=x4/(z4+2); y=y4/(z4+2);

The value 2 is actually a constant that represents the scaling of the final result, so a value of 1 would be acceptable but would result in the image being larger (as we are dividing our final x and y values by it). Alright thats enough theory, lets implement already! Make a new preset, clear every frame and add a superscope with the code:
Init n=5 Frame linesize=2; drawmode=2; rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03; point=0; Beat Point x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3); y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3); z1=0; x2=x1*cos(rotz)-y1*sin(rotz); y2=x1*sin(rotz)+y1*cos(rotz); z2=z1; x3=x2*cos(roty)+z2*sin(roty); y3=y2; z3=-x2*sin(roty)+z2*cos(roty); x4=x3; y4=y3*cos(rotx)-z3*sin(rotx); z4=y3*sin(rotx)+z3*cos(rotx); x=x4/(z4+2); y=y4/(z4+2); point=if(equal(point,3),0,point+1);

And there we have our rotating square, not too bad. But this code is rather inefficient, so were going to have to tidy it up. Note these optimizations are optional so dont worry if you dont understand them. First of all those sines and cosines do not need

26

to be calculated every pixel, as our rotation only changes every frame, so we can calculate the values in the pixel box.
Init n=5 Frame linesize=2; drawmode=2; rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03; crotx=cos(rotx); srotx=sin(rotx); croty=cos(roty); sroty=sin(roty); crotz=cos(rotz); srotz=sin(rotz); point=0; Beat Point x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3); y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3); z1=0; x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz; z2=z1; x3=x2*croty+z2*sroty; y3=y2; z3=-x2*sroty+z2*croty; x4=x3; y4=y3*crotx-z3*srotx; z4=y3*srotx+z3*crotx; x=x4/(z4+2); y=y4/(z4+2); point=if(equal(point,3),0,point+1);

Also, divisions are costly on the frame rate so it is a good idea to minimize them, so we can replace
x=x4/(z4+2); y=y4/(z4+2);

with

27

z5=1/(z4+2); x=x4*z5 y=y4*z5;

Although this is more lines of code it is actually marginally more efficient because we have removed a division and replaced it with a multiplication. Finally, the redundant variable assignments such as x4=x3 can be removed, and we can re-use variables such as x1 rather than creating a new one each time. Leaving us with a much tidier:
Init n=5 Frame linesize=2; drawmode=2; rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03; crotx=cos(rotx); srotx=sin(rotx); croty=cos(roty); sroty=sin(roty); crotz=cos(rotz); srotz=sin(rotz); point=0; Beat Point x1=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3); y1=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3); z1=0; x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz; x3=x2*croty+z1*sroty; z3=-x2*sroty+z1*croty; y1=y2*crotx-z3*srotx; z1=y2*srotx+z3*crotx; z2=1/(z1+2); x=x3*z2; y=y1*z2; point=if(equal(point,3),0,point+1);

28

A little harder to read perhaps, but once you grasp the basics, you can follow the logic. It is a good idea to write your own code using this as a reference in order to ensure you understand the principles involved. If we want to produce a 3D DM, the process is slightly different, we still need our rotation matrices but because we are working with every pixel, we need to pass each pixel into the matrices. So rather than defining a shape with X1,Y1 and Z1, we pass in the x and y of the current pixel, and a constant for z. Create a new preset, clear every frame, add a picture (any picture will do) and a DM. Set the DM to rectangular coordinates and wrap, then enter the following:
Init Frame ox=0; oy=0; oz=-1; rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03; crotx=cos(rotx); srotx=sin(rotx); croty=cos(roty); sroty=sin(roty); crotz=cos(rotz); srotz=sin(rotz); Beat Pixel x1=x; y1=y; z1=1; x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz; x3=x2*croty+z1*sroty; z3=-x2*sroty+z1*croty; y1=y2*crotx-z3*srotx; z1=y2*srotx+z3*crotx;

The key concept when dealing with a 3D DM is that we are reshaping the window to our own shape, imagine the window is a mesh, we can distort it in order to form shapes and projections. Imagine a mesh that you push with your finger to form a shape, your finger is the ray that tells the mesh where to be at a given point. This is a rather poor analogy for ray-tracing which is essentially what we are doing to define a 3D DM. The three variables we have added, ox, oy and oz are the origin of our ray trace, in other words the position of the camera in our 3D world. The last stage of the

29

ray trace is missing in our DM, because we have not decided what we want to show yet, lets try a plane because it is easy. The simplest plane is defined by:
t=-oz/z1; x=x3*t-ox; y=y1*t-oy;

Where t is whats know as the ray trace parameter, add this code to the end of the DM and notice the result. It may appear that there are two planes but one is actually a ghost of the real plane. Notice that oz is set to -1 because our plane is z=0 and so if oz were 0 our camera would be embedded in the plane. In order to remove the ghost plane, and remove the distortion as the plane extends to infinity, we need to add some distance fogging. Add a clear screen and a buffer save to your preset like so:

Change your DM source to Buffer 1, check blend and add the following code to the end of your pixel box:
alpha=z1;

The DM should now look a lot nicer. What we have done is blended the DM with a clear screen to give the impression of distance fog; the picture is saved into the buffer, then the screen is cleared, then the DM renders the plane but blends out everything at a rate of z1. Because z1 is the last value of z we get from our rotation matrices, it represents the distance away from the camera. Hold out your hand and point your three fingers as I showed you before, now keep your index finger facing away from you and rotate your body, your index finger is representing the final z1. So how do you create other shapes? Well you need to sit down with a pencil and paper, get the mathematical equation for the shape you want to ray trace and solve it to find your ray trace parameter (t). I realize this isnt much help, but you will need to look elsewhere for a more comprehensive ray tracing guide. Exercises: Make a solid square 3D superscope Make a DM plane where x=0 (Exercise 4C) (Exercise 4D)

30

Part 5: Registers Sometimes it is necessary to have a variable which is global, that is a variable which can be read by any element of your AVS preset. For this we use registers, a series of variables from reg00 to reg99 that we treat exactly like standard variables. Lets start a new preset, clear every frame and add a Texer2. Texer2 is very similar to a dot superscope, except that we can pick the picture to display instead of just a dot. Enter the following into the Texer2:
Init n=1; Frame pos1=pos1+speed1; pos2=pos2+speed2; Xpos=sin(pos1); Ypos=sin(pos2); Beat speed1=rand(10)/100; speed2=rand(10)/100; Point X=Xpos; Y=Ypos;

Now we have a blob with rather erratic movement, mainly because we are changing the speed on beat. Now if we want to make a superscope that draws a vertical line through the blob we will need to use registers, to store the x position of the blob. Change the frame code of the Texer2 to:
Frame pos1=pos1+speed1; pos2=pos2+speed2; Xpos=sin(pos1); Ypos=sin(pos2); Reg00=Xpos;

Now if we open the debug window (Settings > Debug Window) and look at register 0 we can see that it is updating with the X position of our dot. Now if we add a superscope and enter the following code:
Init n=2; Frame

31

Xpos=reg00; Drawmode=2; Beat Point X=Xpos; Y=2*i-1;

There we have a synchronized Texer 2 and superscope, easy. A common use for registers is to have a control scope, that is a superscope that does not render anything but instead contains code that affects the preset. For example pre-calculating the sines and cosines of rotations for 3D presets and storing them in registers.
rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03; crotx=cos(rotx); srotx=sin(rotx); croty=cos(roty); sroty=sin(roty); crotz=cos(rotz); srotz=sin(rotz); reg01=crotx; reg02=srotx; reg03=croty; reg04=sroty; reg05=crotz; reg06=srotz;

This way the sines and cosines are only calculated once per frame. Exercises: Make a control scope for a 3D square. (Exercise 5A)

32

Part 6: Megabuf and loops The megabuf is a million element array, which allows you to store large volumes of data and, importantly, process it with loops. Unfortunately the syntax of the megabuf is a little different to the standard AVS: Retrieving data: MyVar=megabuf(index); Entering data: assign(megabuf(index),MyVar); Where index is the megabuf item (1 to 1 million) to retrieve or enter data into. We will look at loops now as well because the megabuf is pretty pointless if you dont use loops. The syntax of loops is: loop(count, statement) However because we are likely to want to execute more than one statement in a loop, we also need to use: exec2(parm1, parm2) This evaluates parm1, then parm2, and returns the value of parm2. This may seem like a lot of new functions to get to grips with all in one go, but unfortunately they are all necessary. Lets work through an example to help understand how they all fit together We are going to make a 100 point superscope that stores the value of the spectrum at one point over time, so that we can have a scope that scrolls from left to right. The first thing we need to do is put the value of the spectrum into megabuf(1), which we do like so:
assign(megabuf(1),-getspec(0.1,0.1,0));

Now we need to loop through 100 elements of the megabuf and fill each one with the previous element, that is, megabuf(i)=megabuf(i-1). We have to loop from the 100th element to the 2nd rather than vice versa otherwise we will be overwriting ourselves as we go. Here is the code:
index=101; loop(99, exec2( assign(index,index-1), assign(megabuf(index),megabuf(index-1)) ) );

33

Okay dont panic! It looks confusing but lets follow it logically. First we have defined a variable called index, this will be the current element of the megabuf we are working with, so to start off we set it to 101. Then we begin our loop, we set the first argument to 99, because we want to execute the next part 99 times, for elements 100 to 2, we dont need to do element 1 because we are setting that to the value of the spectrum. Next we have an exec2, because we want to perform 2 actions decrementing the index, and setting the megabuf item. Remember I said we are always likely to want to do more than one statement in a loop? The reason for this is that we generally need a way of identifying the current element we are working with, in this case the index variable, which needs to be incremented or decremented. Unfortunately we cant just say index=index-1, we have to use assign(index,index1) because we are inside an exec2 function. Finally the operation itself assign(megabuf(index),megabuf(index-1)), this shifts each megabuf element along by one. Phew! But were not finished yet Im afraid, we need to output the megabuf elements, so in the point box we put:
Point: index=index+1; x=2*i-1; y=megabuf(index);

But we need to set the index to 0 before this code gets run, so at the end of the frame box we add index=0. The final superscope code looks like this:
Init n=100; Frame drawmode=1; assign(megabuf(1),-getspec(0.1,0.1,0)); index=101; loop(99, exec2( assign(index,index-1), assign(megabuf(index),megabuf(index-1)) ) ); index=0; Beat Point index=index+1; x=2*i-1; y=megabuf(index);

Now for some closing points of interest: There is also a gmegabuf, that is, a global megabuf; the only difference between the two is that the gmegabuf can be accessed from any AVS element, in the same way in which a register can. Remember that often

34

a control scope is used in presets to store all of the complex code and keep important code elements in one place, frequently it is tidier to use the gmegabuf for complicated operations and place the code in a control scope for easy access. One might argue that registers are essentially redundant with a global million item array available, however registers generally make for easier to read code (and do not require ugly assign statements). Exercises: Make a 100 point Texer2 that fills the megabuf with 200 random values from -1 to 1 and uses each pair of numbers as the x and y for a point. (Exercise 6A)

35

Part 7: Coding Effect Lists A relatively recent addition to AVS is the ability to code effect lists, giving the author the ability to control the activation of an effect list and (to some extent) its input and output. Lets jump straight into an example, create a new preset, clear every frame, add an effect list, uncheck enabled, check clear every frame and check use evaluation override. Use evaluation override tells AVS that we want to put code into the boxes, if you do not tick it your code will be ignored. Now add a text object inside the effect list and type Inside effect list, move it up a little from the centre so we have some room for more text. Now the effect list is currently disabled, so lets add some code that will enable it occasionally, type the following into the effect list:
Frame counter=counter+0.1; enabled=above(sin(counter),0);

The variable enabled is a reserved word we use to tell AVS whether or not to enable the effect list, by setting it to 0 or 1. The code we entered simply produces a sine wave from the variable counter, and enables the effect list when the sine wave is greater than 0. Now replace the code with:
Frame counter=if(equal(beat,1),1,counter-0.05); enabled=above(counter,0);

Here we are setting the counter to be 1 when there is a beat (beat is another reserved word that AVS sets to 1 when there is a beat), otherwise we reduce it by 0.05. The effect list is enabled when counter is greater than zero, the net effect is that the effect list is enabled for a little while each time there is a beat. Right, now we can enable and disable effect lists as we want, lets move onto blending. There are two important variables when dealing with blending effect lists: Alphain Alphaout This is the amount of the incoming data to blend with the contents of the effect list. This variable only has an effect when the input blending is set to Adjustable. This is the amount of the contents of the effect list to blend with the data outside of the effect list. This variable only has an effect when the output blending is set to Adjustable.

These descriptions are rather dry, and it is difficult to envisage the results of the blend operations until you have experimented with them a bit. Lets add another text object outside of our effect list so we can get an idea of how the blending works. Add a text object before the effect list with the text Outside effect list and move it down from the centre a little. Set the output of the effect list to adjustable and replace the effect list code with:

36

Frame enabled=1; counter=counter+0.01; alphaout=abs(sin(counter));

Notice how the texts fade in and out because we are blending the effect list in and out. Also note in the code we have an abs() function, which returns the absolute value of its argument (that is, negative numbers are changed to positive), this is to ensure our alphaout stays between 0 and 1. Now try changing the input of the effect list to maximum. Notice that the data outside the effect list is always visible, why is this? It is because when the effect list is not visible, the text will be visible as it is outside the effect list; and when the effect list is visible, it contains the text from outside (maximum blended) with the text inside the effect list. Okay, now set the input blending to adjustable and the output to replace and enter the following code into the effect list:
Frame enabled=1; counter=counter+0.01; alphain=abs(sin(counter));

Here we are keeping the data inside the effect list visible and just fading in and out the data from outside, because our output is set to replace, the outside text is overwritten with black pixels when not blended into the effect list.. You may have already spotted that there is sometimes more than one way to achieve the same effect, but in more complex arrangements of effect lists, this becomes less and less true. Exercises: Make three effect lists which blend into each other sequentially, such that 1 blends into 2, then 2 blends into 3, then 3 blends into 1 etc(Hint: registers) (Exercise 7A)

37

Part 8: Mouse Input Mouse input is a powerful feature of AVS, it allows artists to add user interactivity to their presets and introduces the possibility of menus and other window like functionality. We arent concerned here with what the correct and incorrect uses are for mouse input (but you would be wise to consider what mouse control brings to a preset) instead we will look at how to code for mouse input. The main function we require is Getkbmouse(), depending on the argument we pass to it, we will receive a different value: Getkbmouse(1) Getkbmouse(2) Getkbmouse(3) Getkbmouse(4) Getkbmouse(5) Returns the X position of the cursor Returns the Y position of the cursor Returns the mouse left button state (0 up, 1 down) Returns the mouse right button state (0 up, 1 down) Returns the shift state (0 up, 1 down) [NOT middle mouse as incorrectly stated in the expression help]

So lets jump into an example, make a new preset, clear every frame and add a Texer2 with the following code:
Init N=1; Frame Beat Point x=getkbmouse(1); y=getkbmouse(2);

Gosh, that was simple, a mouse controllable dot. But to be honest a dot that you can move around with your cursor isnt particularly useful, so lets try something we can usea button! Create a new preset, clear every frame, and add a superscope with the following:
Init N=5; Frame drawmode=1; point=1; Beat Point

38

point=(point+1)%4; x=-equal(point,0)+equal(point,1)+equal(point,2)-equal(point,3); y=equal(point,0)+equal(point,1)-equal(point,2)-equal(point,3); x=x*0.4; y=y*0.2;

There we have our button. Notice we are using the modulus operator to create a counter instead of the usual if statement, just to show that there is more than one way to approach most problems. Now we are going to need some way of identifying if it has been pressed, so add another superscope with the following code:
Init //button fill superscope N=2; Frame drawmode=1; linesize=h*0.2; Beat Point point=bnot(point); x=if(point,-0.4,0.4); y=0; red=0.3; blue=0.1; green=0.1;

Here we are using linesize to make a solid superscope and filling in our button area, ensure that this scope is before the previous one or we will overdraw our button outline. Now finally we need a control scope, it is always a good idea to have a control scope when dealing with mouse control because mouse input code is usually associated with various parts of the preset and you dont want to lose track of it. So add a superscope and erase all of the boxes to leave an empty superscope we can fill in. Right, now we can get coding, the first thing we need to do is detect if the mouse is inside the button area, so add the following to the control superscope:
Frame inbox= band( band(above(getkbmouse(1),-0.4),below(getkbmouse(1),0.4)), band(above(getkbmouse(2),-0.2),below(getkbmouse(2),0.2)) );

We are using band() a lot here, because we want to check that lots of conditions are occurring at once, so the mouse x must be above -0.4 and the mouse x must be below 0.4 and the mouse y etc The net result of all our ands is that our variable inbox will be 1 if all the conditions are true and 0 if any of the conditions are false. Now we

39

can use this one variable to produce an effect already, add the following after the above code:
reg01=inbox;

If you wish you can look in the debug window and see it change from 0 to 1 when your mouse is in the area, but for a much easier indication simply change the button fill scope code to the following:
Point point=bnot(point); x=if(point,-0.4,0.4); y=0; red=0.3+reg01*0.2; blue=0.1; green=0.1;

Here we are (indirectly) adding the value of our inbox variable to the colour to get a pleasant mouse over effect. The only thing left to add now is the actual pressing of the button. For this we will make it so that the button changes colour when pressed, so modify the control scope to be:
Init Bstate=0; Frame inbox= band( band(above(getkbmouse(1),-0.4),below(getkbmouse(1),0.4)), band(above(getkbmouse(2),-0.2),below(getkbmouse(2),0.2)) ); reg01=inbox; Pressing=band(getkbmouse(3),inbox); bstate=if(pressing,bnot(bstate),bstate); reg02=bstate;

We have two new variables now, pressing is a variable that will be set to 1 when the user is pressing the button (that is, when the left mouse button is down and the mouse is in the button area). Bstate is the state of the button, either 1 or 0, we are saying if the button is being pressed, change the state to not the current state, so if it is 0 it changes to 1 and vice versa. We are saving the button state in a register because we are going to use it in the button fill superscope, change the button fill superscope to become:
Point point=bnot(point); x=if(point,-0.4,0.4); y=0; red=0.3+reg01*0.2;

40

blue=0.1+reg02; green=0.1;

Now our button will change colour when it is pressed. Have you noticed a problem though? Our button flickers when we press it and seems to change to a random state, can you tell why this is? It is because of the following line in our control scope:
bstate=if(pressing,bnot(bstate),bstate);

Because the code is executed every frame, it is switching bstate to bnot(bstate) every frame we hold down the left mouse button in the area. We need some way of making the state only change once per press of the mouse key, there are various ways of achieving this, change the control scope code to the following:
Frame inbox= band( band(above(getkbmouse(1),-0.4),below(getkbmouse(1),0.4)), band(above(getkbmouse(2),-0.2),below(getkbmouse(2),0.2)) ); reg01=inbox; Pressing=band(getkbmouse(3),inbox); bstate=if(band(pressing,unlocked),bnot(bstate),bstate); reg02=bstate; unlocked=bnot(getkbmouse(3));

The new lines are highlighted in red, what we have done is introduced a variable which is set at the end of the frame, and equals 1 if the mouse is up, 0 if the mouse is down. Now we only change our state if the variable pressed is 1 and our unlocked variable is 1. If you are having trouble seeing how the unlocked variable works, follow the code through from the user not pressing the button, to the user pressing it for a few frames, then not pressing it. Remember, the fact that it is at the end of the code is very important. Well that is our button completed, just to show a potential use, lets add an effect list which is enabled and disabled according to the button state. Add an effect list, uncheck enabled, check clear every frame, check use evaluation override, set the input and output blending to maximum and add the following code:
Frame Enabled=reg02;

Now add a text object inside the effect list with the text enabled and try out your button. Simple or what? Because we used a control scope and registers, we simply have to reference a register to get the button state. Effect lists and mouse input are a very powerful combination, experiment with them to see what you can produce.

41

Part 9: The Triangle APE The triangle APE was created by TomyLobo as a way to render triangles in AVS, given our previous discussion on solid superscopes the usefulness of such a feature should be obvious. The way in which we render triangles to the screen is slightly different to a superscope or Texer2 because we have three coordinates to work with for each triangle; so we reference x1,y1,x2,y2 and x3,y3 to define the corners of our triangle. For example, start a new preset, clear every frame and add a Render Triangle with the following code:
Init n=1; Triangle x1=-0.5; y1=-0.5; x2=0.5; y2=-0.5; x3=0; y3=0.5;

Note that in the triangle ape n represents the number of triangles we want to render, not the number of points. Here we have described a simple triangle, however explicitly writing the coordinates of a triangle has limited uses, as we probably want to render a lot of them. Because of the nature of triangles and defining three coordinates the code tends to be more complicated than superscopes or Texer2s and frequently requires loops. Dont be frightened though, we have already covered all of the concepts and syntax involved, we simply have to apply it to a more sophisticated problem. Let us try to solve a simple problem: rendering a line of triangles. For most presets involving triangles you would be wise to sit down with a pen and paper and roughly calculate where you want triangles to be, but this is a simple problem so we will just jump right in We need a row of triangles, so the logical approach would be to define a single triangle, shift it, render it, shift it, render it etc So lets define our single triangle, something small and offset to start our row, modify your preset to:
Triangle x1=-0.9; y1=0.1; x2=-0.8; y2=0.1; x3=-0.85; y3=-0.1;

There we go, a little triangle, now if we add a variable that increases each pass through the triangle code, we can add that to the x values to perform our shift

42

Frame shift=0; Triangle x1=-0.9+shift; y1=0.1; x2=-0.8+shift; y2=0.1; x3=-0.85+shift; y3=-0.1; shift=shift+0.1;

Here we are resetting shift to zero each frame, then for each time the triangle code is executed (once per triangle), the value of shift is increased by 0.1. Now change the value of n in the init box to 18 (18 triangles) and there we have our row! And for a bit of fun lets make it respond to the music by changing the value of y3
Triangle x1=-0.9+shift; y1=0.1; x2=-0.8+shift; y2=0.1; x3=-0.85+shift; y3=getosc(x1*0.5+0.5,0.1,0); shift=shift+0.1;

We can also add colour coding to the triangles, however we use red1, blue1 and green1 (bonus points if you can work out why):
Triangle x1=-0.9+shift; y1=0.1; x2=-0.8+shift; y2=0.1; x3=-0.85+shift; y3=getosc(x1*0.5+0.5,0.1,0); shift=shift+0.1; red1=abs(x1); blue1=1-x1; green1=0.6;

43

Part 10: Advanced 3D Well back in Part 4 we covered 3D, but that was only the basics, now we are going to look at some of the more sophisticated elements of working in the third dimension. This is the most advanced section of the programming guide, if you do not feel comfortable tackling the topics here it is best to spend more time familiarizing yourself with the basics; theres no point struggling through and not learning anything. A lot of this part is theory, which you may find a little disappointing; however the main aim here is to give you the tools to solve programming tasks that you are likely to encounter. When working in 3D it is crucial that you fully understand the environment you are creating, we have already covered rotation matrices and projections, so you should be at ease with the idea of your three axes x, y and z defining your world. One of the most useful mathematical tools at our disposal is vectors, you may or may not be aware of what a vector is, essentially it is a way to describe a position and a direction. But in our graphics programming we tend to be less interested in the position, so we usually just use vectors to describe a direction, for example: MyVector = [0 x ,0 y ,1z ] There are lots of notations for vectors, here I am saying the x component of my vector is 0, the y component is 0 and the z component is 1 (this is a mathematical notation not AVS code). If you imagine a 3D space, the vector MyVector is describing a direction of positive z, i.e. pointing down the z axis. MyVector = [0 x ,1 y ,1z ] Now MyVector is describing a direction 45 degrees between the y and z axes.

Hopefully this concept is fairly straightforward; now, one thing we might want to do with a vector is calculate its length. We do this using Pythagoras theorem which you may be familiar with; I dont want to go into much detail about it, rather I will just state the equation:

44

L (v ) = v x + v y + v z

Aaah! The equations have started! I hear you cry. Dont worry its very easy; it simply says the length of a vector equals the square root of the sum of the squares of the components. In AVS this would be:
lengthv=sqrt(sqr(vx)+sqr(vy)+sqr(vz));

Where your vector is defined by vx, vy, vz. Now when we do our calculations with vectors it is rather nice to have them normalized, what does that mean? It means that the x, y and z add up to 1 (a vector of length 1 is known as a unit vector), the way we achieve this is by dividing each component by the length of the vector. So in AVS code that would just be:
vx=vx/lengthv; vy=vy/lengthv; vz=vz/lengthv;

Right, now we know a little about vectors we can start looking at some uses for them; so lets move onto normals. What is a normal? A normal is related to a face, if you have a face, the normal of that face is a vector which defines the direction it is facing. For example, hold out your hand with your palm flat, if you imagine your palm is a face (such as a triangle or square) the normal to your palm is a vector that describes the direction away from your hand. Normals are very useful for a variety of reasons; imagine, for example you want to light a cube, if you only know the location of all the faces on the cube you dont really have enough data to produce any decent lighting. If you know the direction of each face, you can tell whether or not each side is facing the light or not. Lets make a preset that renders a solid 3D square and shows its normal as a line, start a new preset, and add a superscope with the following code:
Init Frame rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03; reg00=rotx; reg01=roty; reg02=rotz; Beat Point

45

This is a very simple control scope that we are using to store our rotation values, we need this because the values will be used in more than one superscope (go back to part 5 if you are unfamiliar with registers). Now add another superscope to render a square:
Init n=1000; Frame rotx=reg00; roty=reg01; rotz=reg02; linesize=2; drawmode=2; crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty); sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz); switch=0.5; Beat Point switch=-switch; y1=i-0.5; x1=switch; z1=0; x2=x1*crotz-y1*srotz;y2=x1*srotz+y1*crotz; x3=x2*croty+z1*sroty;z3=-x2*sroty+z1*croty; y1=y2*crotx-z3*srotx;z1=y2*srotx+z3*crotx; z2=1/(z1+2); x=x3*z2; y=y1*z2;

You should have no problem understanding this code, if you do, go back to part 4 and refresh your memory on the basics of 3D (this is actually the solution to exercise 4C and 5A). You may have noticed that my rotation matrices are a little crushed together, this is just to save space and make the code a bit smaller. Okay so we have a rotating square, now we need to define the normal to that face. Because our face is flat to the z axis (z1=0) we actually already know our normal, it is z=1 or z=-1. Why can it be two values? Because it is just a square on its own it can be facing either up or down it doesnt really matter, we just have to make a decision and stick to it. If the square was part of an object such as a cube we would want it to face away from the centre of the cube, and we would assign its normal based on that. So let us say arbitrarily that the normal to this face is z=1, in order to display that as a line we need to put the normal through rotation matrices with the same rotation as the face. This is because as the face rotates the normal is also rotating, so we need to recalculate it.

46

Add a new superscope with the following code:


Init n=2; Frame linesize=2; drawmode=2; rotx=reg00; roty=reg01; rotz=reg02; crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty); sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz); Beat Point y1=0; x1=0; z1=0; x2=x1*crotz-y1*srotz;y2=x1*srotz+y1*crotz; x3=x2*croty+z1*sroty;z3=-x2*sroty+z1*croty; y1=y2*crotx-z3*srotx;z1=y2*srotx+z3*crotx; z2=1/(z1+2); x=x3*z2; y=y1*z2;

This second superscope does not do anything yet, but 90% of our work is done, because all we have to do is add the little bit of code that draws a line from the origin to the point defined by the normal, i.e. from 0,0,0 to 0,0,1, replace the code with:
Init n=2; Frame linesize=2; drawmode=2; rotx=reg00; roty=reg01; rotz=reg02; crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty); sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz); point=0; Beat

47

Point y1=0; x1=0; z1=point; point=bnot(point); x2=x1*crotz-y1*srotz;y2=x1*srotz+y1*crotz; x3=x2*croty+z1*sroty;z3=-x2*sroty+z1*croty; y1=y2*crotx-z3*srotx;z1=y2*srotx+z3*crotx; z2=1/(z1+2); x=x3*z2; y=y1*z2; red=1; blue=0; green=0;

There we have it, our face and normal. This was a rather simple case because we know our normal, plus it was centred at the origin, however I hope you now understand the concept a little better. In the example we just tried, we already knew the normal of our face, it was obvious, this is often the case if you are making simple geometric shapes; for example a cube will have faces where the normals are x=1, x=-1, y=1 etc unless you have defined it without the faces aligned to the axis. However what can you do if cant just guess the normal of a face? The answer is a rather nifty formula/equation known as the cross product. If you have a face, you can define two vectors which lie on that face and calculate the cross product to give you the normal. The cross product is defined as: Cx = v1(y) * v2(z) - v1(z) * v2(y) Cy = v1(z) * v2(x) - v1(x) * v2(z) Cz = v1(x) * v2(y) - v1(y) * v2(x) Where you provide the vectors v1 and v2, and the result is the vector Cx,Cy,Cz (a vector which is perpendicular to v1 and v2). So how do you define two vectors that lie on the face? Well its really quite easy, you simply take two points which are on the face (you might as well use vertices since you already have them) and subtract them to get a vector which lies on the face. For example, we have a triangle which is the face whose normal we wish to calculate with vertices: x1 x2 x3 y1 y2 y3 z1 z2 z3 (Vertex 1) (Vertex 2) (Vertex 3)

(A vertex simply means a corner just in case you didnt know) There is our triangle in 3D, to calculate the normal we make 2 vectors from vertex 1 to 2 and vertex 1 to 3:

48

v1x=x2-x1; v1y=y2-y1; v1z=z2-z1; v2x=x3-x1; v2y=y3-y1; v2z=z3-z1;

So we have our two vectors, which we found by subtracting some of the vertices from each other, we can chose a different combination of vertices as long as we end up defining two vectors that lie on the face. Now we plug those vectors into the cross product formula to find the normal:
nx = v1y*v2z - v1z*v2y; ny = v1z*v2x - v1x*v2z; nz = v1x*v2y - v1y*v2x;

There is our normal, defined by nx,ny,nz; although at this point it would be a good idea to normalize it because it may be very large or small depending on the size of the triangle; we already covered that though so I wont repeat it here. If you want to see this in action, you can load the preset Demonstration Arbitrary Normal in your AVS Programming Guide directory. The preset creates a random triangle and shows its normal, you will notice that the line for the normal is based at the origin not on the triangle; this is because, if you remember, we are interested in using the normal to define a direction, we are not interested in the location. You should be able to read through the code and follow the steps we have just been through. The final topic we are going to explore is the dot product, a formula that will tell us the angle between two vectors. We already know the direction of a face, but currently we only have it expressed as a vector; in a realistic programming context this isnt very useful because we almost always want to know the angle between the normal of that face and something else. For example, if we want to know if a face is pointing away from the camera (so that we can avoid rendering it for speed) want we need is the angle between the face normal and a vector from the camera to the face. The formula for the dot product of two vectors is: Dot=(v1x*v2x+v1y*v2y+v1z*v1z) In other words multiply each vector component together and sum the result, the value that you get as a result is the cosine of the angle between the vectors (multiplied by their lengths but that isnt important for our needs). So in laymens terms the result is a value between -1 and 1 where 1 means they are facing each other, -1 means they are facing the same direction and 0 means they are at 90 degrees to each other (at least thats all we really care about). This is another situation where normalizing the vectors is important, otherwise the result you get will not fall between -1 and 1. Have you ever heard of Lambert's cosine law? Probably not, but it states that the apparent brightness of a face is proportional to the cosine of the angle between the normal of the face and the direction of the light. Gosh, thats convenient isnt it, so if we define a light as a vector (the way the light is pointing) and set the colour of our

49

face to the dot product of the face normal and light vector, the face will be lit pretty accurately without any extra work on our part. Lets make a rotating square with some lighting to demonstrate this idea, make a new preset and add a superscope with the following code:
Init Frame n=1000; linesize=2; drawmode=2; rotx=rotx+0.01; roty=roty+0.02; rotz=rotz+0.03; crotx=cos(rotx);srotx=sin(rotx);croty=cos(roty); sroty=sin(roty);crotz=cos(rotz);srotz=sin(rotz); switch=0.5; Beat Point switch=-switch; y1=i-0.5; x1=switch; z1=0; x2=x1*crotz-y1*srotz; y2=x1*srotz+y1*crotz; x3=x2*croty+z1*sroty; z3=-x2*sroty+z1*croty; y1=y2*crotx-z3*srotx; z1=y2*srotx+z3*crotx; z2=1/(z1+2); x=x3*z2; y=y1*z2;

There is a simple rotating square, now the first thing we need to add to do some lighting is calculate the normal, we already know how to do this, so add the following code to the end of the frame box:
//define the normal nx=0;ny=0;nz=1; //rotate the normal x1=nx;y1=ny;z1=nz; x2=x1*crotz-y1*srotz;

50

y2=x1*srotz+y1*crotz; x3=x2*croty+z1*sroty; z3=-x2*sroty+z1*croty; y1=y2*crotx-z3*srotx; z1=y2*srotx+z3*crotx;

So now we can access the rotated normal as a vector (x3,y1,z1), again we specified the normal to be z=1 however z=-1 would have been fine too. Next we need to define our light as a vector, we will define it and then normalize it; add the following code to the frame box:
//define the light lx=1; ly=0; lz=0; //normalise it length=sqrt(sqr(lx)+sqr(ly)+sqr(lz)); lx=lx/length; ly=ly/length; lz=lz/length;

So this is our light, a vector that points along the positive x axis. Now you can hopefully see that we dont really need to normalize this vector because the sum of its components is already 1. The reason we are putting the code in is so that you can fiddle with the values of the light vector, for example we could make the light point at a 45 degree angle between the x and y axes by setting lx and ly to 1, the normalization code would turn it into a unit vector. Notice that we did not rotate the light vector, this is because we want the light to be stationary and shine on the rotating face, if we rotated the light vector with the same rotation as the face (like we did for the normal) it would be as if the light was attached to the face, so the lighting would not change. Now the final stage is to dot product the light vector with the normal of the face to find out the angle between them, so add the following code to the end of the frame box:
//dot product the light and the normal dot=x3*lx+y1*ly+z1*lz;

This should be nothing too surprising, we are simply plugging the face normal and light vectors into the dot product formula presented earlier. So how do we use this value? Well we can simply assign the colour values of the superscope directly to that variable, since dot will by 1 when facing the light and <0 when facing away from it. Add the following code to the end of the point box:
red=dot; blue=dot; green=dot;

All done! Notice how the face appears to be lit as it faces our virtual light, you can fiddle with the values to see the effect they have, setting the light to 0,0,1 (a vector pointing down the positive z axis) will have the effect of lighting in the direction of the camera which may help you to see the effect.

51

Well there is your introduction to lighting; here we have seen one type of simple lighting, the best analogy for the effect we have created is a sun, because our faces are lit uniformly depending on rotation. In other words the location of our face isnt important, as if the light were very far away, but very bright. A more realistic lighting system would account for the distance between the light and the face, something you already have the ability to do because of what we have covered regarding vectors. One method for finding the distance between the light and the face would be: Define the light location (a) Define the face location (b) Subtract (a) from (b) to create a vector (c) Find the length of (c) using Pythagoras

That length value is the distance between the light and the face, you would use this value to modify the colour of your face. We wont go into any more detail about this though, since it is up to you to experiment.

52

Part 11: Tips & Tricks This section contains some useful coding tips and tricks that do not apply to any specific object in AVS, but are handy for polishing your presets and solving certain problems. Sine Waves Sinusoidal effects are very useful in AVS; sinusoidal motion is very aesthetically pleasing to the human eye and occurs regularly in nature. It is important to understand the basics of sin and cos to use them effectively in presets, the (basic) equation of a sine wave is: y=sin(2fx) That is, amplitude times sin of 2 times pi times the frequency times x. So to produce a sine wave of amplitude 0.5 and frequency 2 we can make a superscope containing:
Point: x=2*i-1; y=0.5*sin(2*$pi*2*x);

Remember that the x axis in AVS spans from -1 to 1 so you will see 4 periods rather than 2 as you might expect. Experiment a little with sine waves and sinusoidal motion so that you are able to plan and implement the effect you want, rather than throwing sins and coss into code randomly. Aspect ratio correction To aspect ratio correct a preset, for example a DM or superscope, the simplest method is:
Frame: asp=h/w; Point/Pixel: x=x*asp;

Where x is your final x after translation, rotation etc However this relies on the AVS window being wider than it is tall, otherwise the data will probably spill off the edge of the screen. You can apply more complex functions to create an aspect ratio correction for any size window:
Point/Pixel: x=x*if(above(w,h),h/w,1); y=y*if(above(h,w),w/h,1);

53

Frame rate independent movement To avoid your preset moving too fast on fast computers you can limit the movement of objects speed by basing it on time rather than the frame rate.
Frame: passed=gettime(0)-last; MyVar=MyVar+passed; last=gettime(0);

Where your movement is based on MyVar. To have different elements moving at different speeds you can scale this value passed, for example multiplying it by a random scale changed on beat. Inactive effect lists Effect lists that fade in and out or have changing alphaout of any kind should be disabled once the alphaout becomes unnoticeably small, thus improving the frame rate:
Frame: enabled=above(alpha,0.01); alphaout=alpha;

Caching static data Static data, for example a gradient background to a preset can be cached into a buffer at the start of a preset to improve the frame rate. Simply put the effects to be cached into an effect list with a buffer save at the end, then use the following code in the effect list:
Init hi=0; wi=0; Frame: enabled=bor(1-equal(hi,h),1-equal(wi,w)); hi=h; wi=w;

This will enable the preset when the preset starts and (more importantly) when the AVS window is resized (so that the data can be recalculated which is probably necessary). You can use a buffer save with restore to get your saved data at any point.

54

Part 12: Advanced Exercises It is expected that by this chapter you have gained a fairly solid understanding of AVS programming and are able to translate most of your ideas into code. After all, the reason for learning the AVS language is to allow you to better implement your ideas and reproduce what you envisage in your mind. One of the greatest skills any programmer needs is problem solving, given a problem you need to be able to come up with a creative and graceful solution, that is the real joy of programming. This final chapter contains some exercises that you should find challenging, read the tips if you struggle, but try to work it out on your own first. Also there is usually more than one solution to a problem, so do not think your solution must match mine, provided it works. Hopefully this will encourage you to think creatively and develop your own approach to solving AVS problems. And when in doubt use the expression help! Exercises: Create a dot superscope that produces a solid 10 pixel by 10 pixel square in the centre of the AVS window. The square should remain 10x10 regardless of the AVS window size. (Exercise 12A) Create a 10,000 point superscope that displays the oscilloscope data, but only updates once a second. (Exercise 12B) Tips for Exercise 12A: First decide how to define 1 pixel, it will be related to the height and width of the AVS window. Try to produce something of a fixed width before you tackle 2 dimensions. Use the solid superscope base from chapter 2, but rearrange it to your needs An ideal solution will have n=100, does yours?

Tips for Exercise 12B: Break the problem into smaller problems can you make a variable equal 1 when a second has elapsed? You need to use the megabuf to store the oscilloscope values What is the maximum limit for a loop (Ill give you a clue, its below 10,000!)? If you cant use one loop, maybe you can use two. Sample the oscilloscope data using getosc(), divide your loop counter by 10,000 for the band.

55

You might also like