Line by Line through the Random Envelope Macro by Grant Boucher One of the most powerful features of the Amiga operating system is the ARexx programming language. Essentially, ARexx lets you write your own programs that control other programs according to your needs. For example, you can use ARexx to write programs that will convert large numbers of image files from one file format to another. With the new Modeler Arexx and Macro functions, we can program our own custom LightWave and Modeler effects without having to know how to program in C or how the Modeler actual does the things it does. Just take a look at the vast numbers of powerful ARexx Macros, both public domain and commercially available, and the great deal of functionality they have added to the entire LightWave/Modeler combo. Recently, I was involved in some special effects shots that required at least a hundred lights which needed to randomly undulate in intensity. We also wanted the option of having them fade out towards the end. Since the shots were as long as 900 frames and we wanted to avoid repeating any motions which might make the lights look mechanical or programmed, I was asked to whip up a quick and dirty ARexx script to do the job. Once the LightWave envelopes were generated, I was then asked to give the macro a gui interface and add some features that might make it a little more useful for future effects shots. That's where Modeler came in and and I felt the finished Modeler Macro seemed a perfect tutorial for those not yet initiated into Arexx programming the Modeler. ARexx - Basic principles ARexx is actually just a typical programming language with an attitude. You'll find all of the standard programming tools of a Basic, C, or Pascal style language that you'd expect, but also the extra added bonus of commands that come from virtually all other Amiga programs. Image processing programs like ImageFX and ADPro come with entire reference manuals listing special commands that allow you to load, process, and save image data...all from YOUR ARexx script! So, I like to think of ARexx as just a new version of all the programming languages I have used in the past, with Amiga software and hardware control functions thrown in. Until recently, I used ARexx primarily for batch processing of files, so I didn't have to manually Load Object, Flip Polygons (wait), Reduce Faces (wait), and Save Object for every Public Domain Imagine object I wanted to convert to LightWave using Pixel 3D Professional, for example. Writing ARexx Scripts Since ARexx programs are just text files that the RX program interprets, you can use any word processor like Final Writer or a text editor like CygnusEd to write and edit your ARexx script. If you use a Word Processor, make sure you import and export your file as ASCII text, as the word processor's own internal file format will certainly add strange control characters that mean nothing to ARexx and make your script unusable. The Program - line by line Line 1 - The Author Line The first line of any ARexx script must be a comment line. It is just ARexx's way of signifying that this is an ARexx program, and not a letter you are sending to your mother. Comments begin with the /* characters and end with the */ characters. Comments are usually used to document your script for others or yourself if you come back to it t*NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF*s. UPPERMAX = This value determines the highest intensity of our envelope. NOTE: Similar to LOWMIN and LOWMAX, UPPERMIN and UPPERMAX give us a range for the peaks (or maximum values) of our intensity envelope. The peak values will never be higher than UPPERMAX (i.e. 40) and never lower than UPPERMIN (i.e. 20). ENVELOPES = The total number of Envelopes you want to generate using these settings. Lines 27-35 Once we have set up the requesters, we usually need to send them some default values. Note that we CALL the REQ_SETVAL() function for each requester to do this. The first argument is the ID name of the requester (for example, RampID). The second is the actual value (for example, a 1 or 0 for the Boolean ON or OFF). The rest of the functions and there default values should be self-evident. Line 37 The function REQ_POST() sets up the requester with the Okay, Cancel, and Reset options already enabled. It waits for the user to input everything and select either Okay or Cancel. Reset automatically clears all variables. If okay is pressed, then X is set to 1 by this function. If Cancel is pressed, then X is set to 0. Line 39 A standard IF - THEN conditional statement. If X=1 (therefore we know Okay was pressed) then DO everything that follows. If this check was failed (therefore, Cancel must have been pressed and X must be 0) then the program drops to the END statement that matches this IF statement (i.e. line 130). The next command after that is EXIT and so the program ends. With just those few lines, we set up an entire program front-end, prompted the user for lots of input, with default values given as examples, and waited for the user to either abort the program, reset the values, or tell us to proceed! The Modeler and ARexx do all the work for us and our interface is guaranteed to behave and look professional like all of the other Macros written for the Modeler. Lines 40-48 Now that the user has told us to begin, we need to read back in the values he or she entered using the REQ_GETVAL() function. The only argument we pass to the function is the name of our ID (for example, EndFrameID). We then assign the value returned by the function to a variable (for example, ENDFRAME). We do the exact same thing for all of our requesters. Lines 51-55 We cannot always be sure our users give us exactly what we want. To avoid generating an error later on in the program, we check to make sure that the LOWMIN value is indeed less than the LOWMAX value. From the user's stand point it doesn't matter, as long as the range is between two values, but internally our own functions require these two values to be ordered correctly. First we check to see IF LOWMIN is greater than LOWMAX. If it is, then we DO the next series of commands. If it isn't then we skip to the END of the IF statement (i.e. line 55) and proceed on with the program. Assuming our user entered in a LOWMIN value that was greater than the LOWMAX value, we need to switch these around before proceeding. First we assign the LOWMIN value to a temporary variable called TEMP. Now that is is saved for later, we set LOWMIN equal to LOWMAX, and then assign LOWMAX the old LOWMIN value stored in TEMP. The old variable switcheroo is a classic programming example. Now we can proceed on with our program Lines 57-61 These lines perform the exact same function as Lines 51-55 above, except they assure than HIGHMIN is always less than HIGHMAX. Line 63-67 These lines again perform the same function as the previous two examples, but instead insure than our MINFRAME offset value is less than our MAXFRAME offset value. Lines 69-70 Now that we are sure all our data is useful, we need to prompt the user for some additional input. We use the GETFILENAME() function to get a path and root file name for the envelope(s) we are about to create. The first argument passed to the function is a title for the requester, while the second is a default path, which I chose to be LightWave's Envelopes drawer. As before in our program, the Modeler does all the work with the requester in one line! The entire path and filename is returned to the program and we assign that to a variable called ROOT_NAME. We then check to see IF ROOT_NAME equals "(none)", which is the function's message for "no response." If it is then we EXIT the program, otherwise we proceed with our program. Lines 72-73 Using the GETFILENAME() function, we can ask our user to input a path and a root name for our envelope(s). The first argument passed to the funtion lets us name our requester for the benefit of our user. The second argument lets us automatically log in to a default path (you will need to change this to suit your needs). The second line checks to see if the user failed to enter anything or selected abort, in which case the requester returns the string "(none)" and therefore we exit the program. Line 76 Here begins the main body of our program. This first line begins a loop (called a DO loop) where we will step the variable N from 1 to the variable ENVELOPES which our user entered earlier (Default is 1). All of the program between this line and the matching END statement for this DO loop (i.e. line 129) will be run again and again until N = ENVELOPES, at which point the program drops to line 130, which ENDS the main IF conditional, and then EXITs the program. Lines 77-80 Users who have previously programmed will recognize the following lines as one of those annoying little file-management necessities. Since we might be creating hundreds of envelopes, we can give them all unique names by appending a number to the end of the root name. However, when you append the number 2 directly to a filename (i.e. Random.2) and a 10 to that same root name (i.e. Random.10) programs and requesters that sort those values return them in the wrong order (i.e. Random.10, Random.2). What we really need is to 'pad' out the number with leading zeros (i.e. Random.002, Random.010, Random.100). The first line sets our PAD variable to a null string "", with nothing in it. Then we check to see if N, our envelope number, is less than 100. If it is, add a "0" to our PAD string. Next, if N is less than 10, we need to add another "0" to our PAD string. Finally, our FILE_NAME is *NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF*ble to 0 each time we begin writing a new envelope. The LASTFRAME variable is used to keep track of the last frame number we have processed during our program. Line 88 This line initializes our K variable to 1 each time we begin writing a new envelope. K is used to keep track of the actual number of key frames we have created in a given envelope. Line 89 We need to decide whether our envelope begins within a valley or a peak (i.e. using LowMin/LowMax or UpperMin/UpperMax as va*NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF*of the previously created key frame. In this fashion, our key frames keep increasing one after another by a random amount. NOTE that we are creating an ARRAY of the variables INTENSITY and FRAME by creating variables with the ".k" suffix. For example, if K is 56, then we are working with INTENSITY.56 and FRAME.56. Each of these variables is unique and can have their own distinct values. ARexx automatically sets up variable space for our array as we go! This is usually a BIG pain in most programming languages. Line 97 There is only one instance when we know what the frame value of a given key frame before hand. The first key frame (i.e. K=1) is always 0. In this line we check to see if K=1, and if it is we make the FRAME value equal to 0 automatically. Line 98 We update the LASTKEY to our current FRAME value, so that next time around we build a new key frame starting on the value of this one. Line 99 We add 1 to K, which keeps internal track of our total number of created key frames so far. Line 100 Through a clever and standard programmer's trick, we force MINMAX to constantly toggle between 0 and 1 every time we pass through this program loop by subtracting MINMAX from 1 and then assigning the result to MINMAX again. If MINMAX was 0 before then 1 - 0 = 1, so MINMAX becomes 1. If MINMAX was 1 before then 1 - 1 = 0, so MINMAX now becomes 0 the next time around. Line 101 To update our meter to show we have proceeded one time through our loop, we call the METER_STEP() function. There are no arguments as Modeler again takes care of all its own housekeeping. Line 102 The END of this DO loop. The program now checks to see if the updated LASTFRAME value is greater than the ENDFRAMES value (see Line 92). If it isn't we start this loop again, creating more random intensities, offsetting new frame numbers, toggling MINMAX, increasing our key frame total, K. If it finally is, we drop to the next line of our program. Line 103 Now that we have completed all of our calculations, it is time to close that requester we created in Line 91 that we've been stepping through during that last loop. We CALL the METER_END() function with no arguments to do this. Line 106 We automatically added 1 to K at the end of that last loop, but since LASTFRAME has to be greater than ENDFRAMES by this point in the program, there never were any values assigned for INTENSITY and FRAME for this last value of K. Since this is our total number of key frames pointer, we should make sure this is accurate by subtracting 1 from the final value of K. Everything matches up now. Line 107 Remember that envelope file we opened up and initialized way back at lines 82-85? Well, LightWave requires one more thing before it starts reading all your intensity and key frame values...the total number of key frames in the envelope - our variable K. Good thing we kept track of that, huh? We call the WRITELN() function and send the value of K as the second argument to the program file identified as 'out' by us. You should be getting use to the WRITELN() function by now. Line 109 Because of a feature I added in later, I have to run our intensities through some more calculations if the user has requested us to ramp the intensities down to our LOWMIN value across the envelope. Since we did not know the final key frame until after everything was calculated, we could not perform this function until now. We begin with a simple IF statement that checks to see if RAMP=1, meaning 'yes, ramp the values'. If it is, we do everything between lines 110 and 115. If not, we drop through to the END statement on line 116 and proceed with the program. Note that ARexx assumes the line IF RAMP THEN DO means if RAMP is BOOLEAN TRUE (numerical shortcut is 1). You do not need to expressly say IF RAMP=1 THEN DO. Notice that we could change line 37 to read IF X THEN as well, but I wanted to give you an example of both for this programming tutorial. Notice how well Boolean logic works with Boolean requester function like the RampID. Line 110 Let's start a new meter for our ramp operation by calling the METER_BEGIN() function like we did in line 91 earlier. However, since we now know the total number of key frames we have to process (i.e. K), we can tell the meter that we are sure to step K times through the meter as our first argument. Line 111 We set up a DO loop that will iterate the variable 'I' starting from 1 and ending with K. The END of this loop is on line 114. Line 112 Now we are going to Ramp our intensities lower until the final key frame is always equal to LOWMIN. Our formula does the following: A) First it subtracts the LOWMIN value from the previous INTENSITY value so we are only going to scale in the range greater than LOWMIN. B) Then we calculate the percentage of ramping by dividing the current FRAME value by the maximum frame value represented by LASTFRAME value. This value will range between 0 (at the beginning) and 1 (at the end) during the envelope. C) Since we want to ramp DOWN, we subtract the result from B) from 1 so that the percentage is reversed. D) We then multiply the percentage from C) times the adjusted FRAME value. E) Finally, we add LOWMIN again so that everything is shifted back the way we started and reassign the new value to FRAME. Can yo*NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULADF**NULA