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 three years later.
Since  the  first  line  of  any  ARexx  script  must  be  a  comment, most
programmer's  find this a convenient place to put their name for posterity.
Note  that  comment  lines  don't  affect the operation or function of your
program, and are simply ignored when it is run.



Lines 3 - 12

     For  most Modeler macros, you'll want these lines at the start of your
script.   Basically,  they initialize special ARexx math functions, turn on
some  special  debugging tools, and generally check to make sure everything
is  set  up right to start.  Many initialization functions like this can be
just  grabbed  from  the  start  of another ARexx script and left alone.  I
don't worry about them, so probably neither should you.



Line 15

     This  line  calls  (or  performs)  Modeler's  requester  and interface
definition,  named  REQ_BEGIN().   The  text  that  follows this command in
quotations  becomes  the  name of the requester.  The Modeler automatically
formats this and all requester commands for you!



Line 17

     This  is  the  first  of  the  requesters, where we actually want some
user-input  to  come  back.  Again, we use the REQ_ADDCONTROL() function to
set  up  our  new  feature, but this time we pass only two arguments to the
function.   The  first  is  a text message describing to the user what this
function means.  The second is the letter 'B' which tells the function that
this  is  a BOOLEAN, or ON/OFF, style requester.  The modeler automatically
creates the Check-Mark gadget for you!  I gave this feature the name RampID
so  I  would know that this is my Ramp to Minimum toggle.  This feature, if
on,  tells  the  macro  to  decrease  the  overall  intensity of all of the
envelope  key  frames until they equal the user's lowest intensity value by
the  end of the envelope.  In other words, the intensities fade towards the
end of the envelope.



Lines 18-25

     All  of  these  lines  are  essentially  the  same.   This  time,  the
REQ_ADDCONTROL()  function  is  passed a letter 'N' (as the second variable
again)  to signify that this is going to be a numeric requester, asking for
a  value  from the user.  The first argument is again a description of what
number  the user needs to enter, and the third argument tells the requester
what  units  the  number  will  be in.  Since we are working with arbitrary
values  like  frames  and  intensities,  not meters, we send a 0 for all of
these, which means 'unitless'.

     Note  the  strange  %1  and %2 characters attached to these functions.
The  %  character  stands  for  INTEGER DIVIDE, meaning it is the same as a
standard  DIVIDE  command  (i.e.   '/')  but  that  the result is always an
integer.  When I take a number and INTEGER DIVIDE by 1 (i.e.  %1) I am just
forcing  whatever  value the user entered to become an integer.  We have no
use for Frame 10.333 for example!

     The MINFRAME and MAXFRAME variables are INTEGER DIVIDE by 2 (i.e.  %2)
because  if  our envelope peaks are going to be, say 30 to 50 frames apart,
each  key  frame is actually going be half that amount from every other key
frame.   Two  key  frames,  or twice that value, gets you back to where you
started  from, either a peak or valley.  I kill two birds with one stone as
I  make  sure these values are integers and they are reduced by half so our
program works properly.





Definitions of our main variables...



MINFRAME  =  The minimum distance between peaks of our undulating envelope.
For  example,  a value of 20 here means that no two high points (or two low
point for that matter) will be closer than 30 frames apart.



MAXFRAME  =  The maximum distance between peaks of our undulating envelope.
For  example,  a value of 50 here means that no two high points (or two low
point for that matter) will be farther than 50 frames apart.



NOTE:   If  you  want  the  envelope key frames to step forward evenly, say
every  40  frames between peaks, then MINFRAME and MAXFRAME should be equal
(i.e.   40 in our example above).  Having these two values random means the
intensities  vary  from high to low over a random time frame, giving them a
more organic and natural feel.



ENDFRAME = The length in frames of our envelope.  Because we are generating
our  steps between key frames, this value is a minimum guideline only.  For
example,  a  value  of  1000  means the last key frame of the envelope will
certainly not be less than 1000, but it might be slightly higher.



LOWMIN = This value determines the lowest intensity of our envelope.



LOWMAX = This value determines the maximum range of the lowest intensities.



NOTE:   If  LOWMIN  is  0 and LOWMAX is 10, the valleys (or low key frames)
will  never be lower than 0 and never higher than 10.  If LOWMIN and LOWMAX
are equal (for example, both are 0), then there will be no variation of the
low  values  (i.e.   the  low  key frames will always be 0).  Again, having
these  values  different  makes  our  lights  dim  in  a more unpredictable
fashion.



UPPERMIN  =  This  value  determines  the  minimum  range  of  our  highest
intensities.



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 set to
equal  our ROOT_NAME combined with our PAD string and finally combined with
N, our envelope number.  NOTE that strings are combined, or concatenated in
programmer's  parlance,  with  the  ||  characters  (i.e.  strike shift '\'
twice).



Lines 82-85

     We  are  now  ready  to open our new envelope file!  We call the ARexx
OPEN()  function  to do this.  The first argument (i.e.  'out') is the name
our  program  uses  to  address this file.  It can be anything we want, but
make  sure that each file you have open at the same time has a unique name,
to  keep from getting the open files confused).  The second argument is the
actual  AmigaDos  path  and file name of the file we are opening (i.e.  the
variable  FILE_NAME  we  just created above).  The third argument 'W' tells
the  OPEN()  function  that  we want to Write to this file.  To read from a
file use the 'F' argument.

     Now  that  are  file is created and open, we send it some initializing
lines  that  LightWave requires to recognize this text file as a legitimate
LightWave  envelope file).  The WRITELN() function sends a complete line of
text  to  our  file.   We call the function with the ARexx file name of our
file  (i.e.   'out')  as the first argument, and the actual text we want to
write out (i.e.  'LWEN') and the second argument.  The next two lines write
additional  data  required  by LightWave.  Do not change any of these three
lines or LightWave will not recognize your file!



Line 87

     This  line  initializes our LASTFRAME variable 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 values).  We could
have  just picked a value, either 0 for valley or 1 for peak for all of our
envelopes, but again I chose to make the results as random as possible.

     My  variable  MINMAX  is going to alternate between 0 and 1 throughout
the  program, so I want it to start with a random value between 0 and 1.  I
assign  MINMAX  the  result  of  the  ARexx  RANDOM()  function,  using the
arguments  0 and 1 as the minimum and maximum values for the function.  The
result of the RANDOM() function is always an integer, so it can only return
a 0 or a 1.



Line 91

     Before  we  begin  our  loop  to generate random values and key frames
(i.e.   line  92), we are going to get a little fancy and take advantage of
one of Modeler's built-in interface features, the progress meter!

     We  call the METER_BEGIN() function with two arguments.  The first one
is  the number of steps we expect our meter to be updated by.  For example,
if  we  knew  we were going to be processing 10 key frames, we might send a
value  of  10  here,  so that we'd see the meter update 10 times during our
upcoming  loop.   The second argument is the name or title that we pick for
the meter requester.

     Our  only  problem  here is that we don't really know exactly how many
key  frames  we  are  going  to create because the offsets between them are
generated  randomly  (see below).  Since the meter will error if we step it
too  many  times, but not if we step it too few, it is better to err on the
side  of  caution.  We know that the minimum distance between key frames is
the  variable  MINFRAME  and the maximum frame count of our envelope is the
variable  ENDFRAMES,  so  the maximum number of key frames we could have in
our  envelope is ENDFRAMES%MINFRAME (remember % is just an INTEGER divide).
We pass that function as the first argument to our meter function.



Line 92

     Now  we  are  ready  to  begin  generating  the  key  frames and their
intensities  one after another, until we reach or exceed the EndFrame value
of  our  program.   Our  control  for this DO loop is the UNTIL keyword and
LASTFRAME  >  ENDFRAMES  condition.   In  other  words,  the  program lines
contained  with  the DO loop and its accompanying END statement (i.e.  line
102)  will  be  repeated  forever  until the value of the LASTFRAME variable
exceeds the value of the ENDFRAMES variable.



Line 94

     If  our  MINMAX  variable  = 0 then we have a valley and our INTENSITY
should   be  based  on  the  RANDOM()  value  between  LOWMIN  and  LOWMAX.
Otherwise, proceed to the next line of our program.



Line 95

     If  our  MINMAX  variable  =  1  then we have a peak and our INTENSITY
should  be  based  on  the  RANDOM()  value  between UPPERMIN and UPPERMAX.
Otherwise,  proceed to the next line of our program.  By this time, we have
covered both of the possible values of MINMAX, 0 and 1.



Line 96

     The actual frame number for the above INTENSITY variable is determined
by  adding  a  RANDOM()  frame  offset between MINFRAME and MAXFRAME to the
LASTKEY  value  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  you  figure  out  how to ramp up to UPPERMAX across the envelope?
This  is  our  only nasty formula requiring some genuine math logic.  Sorry
about that.



Line 113

     We  tell  the progress meter to step forward since we've completed one
key frame by calling the METER_STEP() function as before.



Line 114

     END the loop and step forward until it reaches the final value of K.



Line 115

     We  are done with the ramp progress meter by this point in the program
so close the meter with a CALL METER_END() function.



Line 116

     END  of  the  conditional IF statement that started this whole ramping
mess.  On with the rest of the program.



Line 119

     Let's  open  one  final  progress  meter  to  begin  wrapping  up this
envelope.  Again we'll step K times through the meter.



Line 120

     We're  going  to  loop  from  1 to K again, with a DO loop like above.
This loop ENDs on Line 125.



Line 121

     By calling the WRITELN() function again, we write our INTENSITY values
to  the  'out'  file.   We  divide  the INTENSITY by 100 because that's how
LightWave likes it.



Line 122

     The  WRITELN()  function  sends  the  FRAME  and preset spline control
settings  (i.e.   "0.0  0.0  0.0"  is Tension Continuity Bias) to our 'out'
file.

     You  should now be able to figure a way for the user to set default or
random spline settings in your version of the program.



Line 123

     Let's step that meter now that we've written out another key frame.



Line 125

     END  of the DO loop in line 120 that sends us back to the next I value
until it reaches K.



Line 126

     We have finished stepping our meter, so let's close it out.



Line 127

     We  CALL  the  CLOSE()  function  to  close  the 'out' file we've been
creating  all  this  time.   It  is  now  complete  and  can  be  loaded in
LightWave's intensity envelope editor.



Line 129

     This is the END of the DO loop in line 76.  We'll start creating a new
envelope  until  N reaches the value of ENVELOPES at which point we will go
to the next line of the program



Line 130

     This is the END of our conditional IF that started this whole process.
The  use  has  either  pressed  Cancel  or  all  of the envelopes have been
generated.  In either case, proceed to the next line of the program.



Line 131

     EXIT the program and return Modeler control to the user.



END OF PROGRAM





     WHEW!!!   Hopefully, this showed you enough of the basic Modeler macro
programming   functions  and  philosophy  to  get  you  started.   In  your
Toaster/Arexx_Examples/LWM   directory   there   is   a  text  file  called
ModelerARexx.doc.   Everything else you need to know to program the Modeler
can be found there and most of it shouldn't look like APL to you after this
example.   For  examples, just hunt and peck through the abundance of Arexx
macros until you find something you need.

     Before wrapping this example up, I'd like to thank Stuart Ferguson for
adding these features to the new Modeler.  Maybe next program I'll actually
use  more  of  the  Modeler's features than just its interface handling!  I
have  some  old  celestial  mechanics code I did in high school that's just
dying for a rewrite.

     I  would also like to thank Arnie Cachelin of NewTek for his abundance
of  Modeler  ARexx  macros I stole from heavily to produce this program.  I
can only hope you readers do the same from me.

