version: beta 1 (February 24, 2001)
by Bret Battey -- www.BatHatMedia.com
The Animation Extension to Common Music (AECM) is a tool for programmers who have a functional understanding of LISP, Common Music, and MaxScript programming. It allows said programmer to use a single LISP programming environment to procedurally generate sonic and visual events.
It does so by extending Rick Taube's Common Music (CM), a LISP based music composition environment. CM can generate output for a number of different synthesis and sound control protocols, including Csound, Common Lisp Music, and MIDI. The AECM extensions allows CM to also generate scripts to control a computer animation environment.
The current version of AECM works with Common Music 1.4 and generates MaxScript, the scripting language for 3-D Studio Max 2.5. It has been tested only with Allegro Common Lisp 50b5.
AECM essentially allows the programmer to write MaxScript via LISP-style functions rather than MaxScript commands. Each AECM animation function called will be translated into one or more lines of MaxScript code and stored as an object in a CM container. These objects can then be written out to a MaxScript code file and executed in 3-D Studio Max.
In the end, though, the programmer is essentially programming MaxScript -- just in a LISP style. AECM does not provide one-for-one equivalents to all MaxScript functions. Rather, it provides a small set of generic functions that are capable of generating the needed MaxScript functions when given valid parameters. AECM does not substitute for a strong understanding of MaxScript.
AECM is fairly simple in its implementation. Ease of understanding and expansion were given higher priority than LISP-machismo. A single LISP file needs to be loaded. This file contains the definitions of the MaxScript output syntax and objects and methods and functions for writing MaxScript. These functions can be easily expanded by the user. This is a good thing, since I have developed AECM primarily for my own projects and have only included features necessary to support those projects. Many aspects of MaxScript are not covered and may be best supported by the user writing his or her own routines based on the given models.
AECM interprets floating-point time values as being in seconds and integer time values as being in frames. In the resulting MaxScript, an s or f will be appended to the time value accordingly. Note that a time value of 0 is not valid, since frame counting begins at 1. On the other hand, a time value of 0.0 is valid, since time does start at 0 seconds.
To start using AECM, simply load the aecm.lisp file into your LISP environment.
init-ms |
Initializes important AECM variables. This function should be run before any other AECM functions are executed. The function should be re-executed anytime you want to re-run your CM/AECM code.
make-node (type name &rest specs) |
A generic function for making nodes, used rather than the specific constructor functions such as found in MaxScript.
make-node returns a value which can be assigned to a variable in order to reference the node later. The value will be type_name of type ms-node-constructor.
Example: (make-node 'sphere 'sph01 'radius 2.0 'hemisphere .89)
The resulting MaxScript is:
SPHERE_SPH01 = SPHERE name:"SPH01" RADIUS:2.0 HEMISPHERE:0.89
edit-node (name &rest specs) |
A generic function for editing nodes.
Example: (make-node 'sphere 'sph01) (edit-node 'sph01 'radius 20.0 'hemisphere 1.0)
The resulting MaxScript is:
SPHERE_SPH01 = SPHERE name:"SPH01" $SPH01.RADIUS = 20.0 $SPH01.HEMISPHERE = 1.0
Example:
(setf mynode (make-node 'sphere 'sph01)) (edit-node mynode 'radius 20.0 'hemisphere 1.0)
The resulting MaxScript is:
SPHERE_SPH01 = SPHERE name:"SPH01" SPHERE_SPH01.RADIUS = 20.0 SPHERE_SPH01.HEMISPHERE = 1.0
edit-node-at-time (time name &rest specs) |
An alternative version of edit-node which prints an "at-time" version of the edit. This can be placed inside animate-on and animate-off commands to provide quick and dirty animation. However, this approach provides no means for AECM to reference and alter the resulting animation key frames and so provides no means for doing the kind of detail keyframe attribute editing required for effective animation.
Example:
(edit-node-at-time 20 'sph01 'radius 20.0 'hemisphere 1.0)
The resulting MaxScript is:
at time 20f $SPH01.RADIUS = 20.0 at time 20f $SPH01.HEMISPHERE = 1.0
add-modifier (name modifier modname) |
A generic function for adding modifers to nodes.
Example: (add-modifier 'sph02 'noiseModifier 'foo)
The resulting MaxScript is:
addModifier $SPH02 (NOISEMODIFIER name:"FOO")
assign-controller (name attribute &optional (type 'bezier_float)) |
Assign a controller to the attribute of a node.
Example: (assign-controller 'sph01 'rotation 'tcb_rotation)
The resulting MaxScript is:
$SPH01.ROTATION.controller = TCB_ROTATION()
make-key (time name attribute &rest specs) |
Assign a controller to the attribute of a node.
Example: (make-node 'box 'myBox)
(assign-controller 'myBox 'position 'bezier_position)
(make-key 0.0 'myBox 'position 'value (point3 0 4 30) 'outtangenttype bz-slow)
The resulting MaxScript is:
BOX_MYBOX = BOX name:"MYBOX"
$MYBOX.POSITION.controller = BEZIER_POSITION()
addNewKey $MYBOX.POSITION.controller 0.0s
$MYBOX.POSITION.keys[1].VALUE = [0.0,4.0,30.0]
$MYBOX.POSITION.keys[1].OUTTANGENTTYPE = #slow
Example:
In this following example, a keyframe
is established in the angle attribute of the bend modifier of a box. To do this,
one must create a complex attribute parameter as per the needs of MaxScript.
No doubt about it, this can be a royal pain in portions of the posterior anatomy.
(make-node 'box 'myBox)
(add-modifier 'myBox 'bend 'myBend)
(edit-node 'myBox "modifiers[#myBend].center" (point3 0 0 6)))
(assign-controller 'myBox "modifiers[#myBend].angle" 'bezier_float)
(make-key 0.0 'myBox "modifiers[#myBend].angle" 'value 80 'outtangenttype bz-smooth)
The resulting MaxScript is:
BOX_MYBOX = BOX name:"MYBOX"
addModifier $MYBOX (BEND name:"MYBEND")
$MYBOX.modifiers[#myBend].center = [0.0,0.0,6.0]
$MYBOX.modifiers[#myBend].angle.controller = BEZIER_FLOAT()
addNewKey $MYBOX.modifiers[#myBend].angle.controller 0.0s
$MYBOX.modifiers[#myBend].angle.keys[1].VALUE = 80
$MYBOX.modifiers[#myBend].angle.keys[1].OUTTANGENTTYPE = #smooth
add-string (string) |
When all else fails, you can add a string of your choosing directly to the MaxScript.
Example: (add-string "animate on (")
(edit-node-at-time 20 'sph01 :hemisphere .25)
(add-string ")")
The resulting MaxScript is:
animate on(
at time 20f $SPH01.HEMISPHERE = 0.25
)
nodenum (name1 number &optional (name2 NIL) |
Helps to create node names based on dynamically changing variables (such as loop counters).
Example: (loop for i below 4 do
(make-node 'sphere (nodenum 'sph i)))
The resulting MaxScript is:
SPHERE_SPH0 = SPHERE name:"SPH0"
SPHERE_SPH1 = SPHERE name:"SPH1"
SPHERE_SPH2 = SPHERE name:"SPH2"
SPHERE_SPH3 = SPHERE name:"SPH3"
Example: (loop for i below 4 do
(edit-node (nodenum 'sph i ".modifiers[#myBend]") 'center (point3 3 4 5))
The resulting MaxScript is:
$SPH0.modifiers[#myBend].CENTER = [3.0,4.0,5.0]
$SPH1.modifiers[#myBend].CENTER = [3.0,4.0,5.0]
$SPH2.modifiers[#myBend].CENTER = [3.0,4.0,5.0]
$SPH3.modifiers[#myBend].CENTER = [3.0,4.0,5.0]
get-available-object (starttime endtime datalist) |
In an animation environment, all objects that will be used in the animation exist in the animation. To make objects that appear and disappear, the object attributes are altered to make the object appear and disappear at the desired time. (Note that in 3-D Studio Max 2.5, the "visibility" attribute does not seem to be accessible via MaxScript.) If you are generating many such events (such as when creating a one-to-one note and object correspondence in a dense musical texture), the sheer number of objects in the animation environment may greaty slow down rendering.
get-available-object can be used to keep track of what objects are "active" at a particular point and time. Inactive objects can the be reused to create a new event rather than requiring a new object to be created.
get-available-object returns three values which can be accessed using multiple-value-bind:
Mixing seconds and frames time values in the use of get-available-object will brighten your day in a similar fashion to dropping your CPU in the bathtub.
Example: (progn
(init-ms)
(merge foo ()
(let ((start 1.0) (dur 4.0) sphname (dlist '()))
(loop for i below 8 do
(multiple-value-bind (num neednew newlist)
(get-available-object start (+ start dur) dlist)
(setf dlist newlist)
(setf sphname (nodenum 'sph num))
(if neednew
(progn
(make-node 'sphere sphname 'radius 0 'pos (point3 (* i 10) 0 0))
(assign-controller sphname 'radius 'bezier_float)
(make-key 0.0 sphname 'radius
'value 0.0 'outtangenttype bz-step)))
(make-key start sphname 'radius
'value 10 'outtangenttype bz-fast)
(make-key (+ start dur) sphname 'radius
'value 0 'intangenttype bz-slow 'outtangenttype bz-step))
(incf start 1)
(setf dur (+ 2 (mod (+ dur 1) 5))))))
(write-ms "foo" "overlaps.ms"))
The resulting MaxScript is will generate 8 "events" using only 5 spheres. In much more dense scenarios, the object reduction can result in a dramatic improvment in rendering time.
The following functions provide a LISP-style means for generating some common data types in MaxScript. Usually these are more convenient to use than using format on the fly in LISP to combine variables into the correctly formatted string.
angleaxis (degree axis) |
(MaxScript hint: angleaxis
is the type you want to use to create rotations of greater than 360 degrees.)
CM(1): (angleaxis 470 'y)
"(angleaxis 470.0 Y_AXIS)"
eulerangles (x y z) |
CM(31): (eulerangles 12 34 56)
"(eulerangles 12.0 34.0 56.0)"
color (r g b) |
CM(1): (color 12 45 89)
"(color 12 45 89)"
point3 (x y z) |
CM(1): (point3 3 6 0) "[3.0,6.0,0.0]"
quat1 (degree axis) |
CM(1): (quat1 60 'x)
"(quat 60.0 X_AXIS)"
The following parameters and constants are defined in aecm.lisp, and are a good candidate for expansion. Most of them serve as shortcuts to provide certain strings needed by MaxScript that require special control characters to imbed in LISP strings.
ms-frames-per-second | Set this to correspond to your animation setting. Defaults to 30. The figure is used in some internal calculations in AECM. |
System | |
*aecm-version* | |
Bezier keyframe input and output tangent types | |
bz-linear | |
bz-smooth | |
bz-step | |
bz-slow | |
bz-custom | |
Blur types | |
blur-object | |
blur-non | |
blur-image |
All AECM commands are queued in the order in which they are executed in the LISP code. This means that some thought must be given to how one orders the code. However, as long as nodes are created before any edits that reference those nodes, things will work fine. In practice this is quite easy to achieve. Also, since the animation is enacted via keyframes and keyframes have a time attribute, it does not matter what order the keyframes are generated in.
Due to the architecture of CM, one can freely create sound events and animation events within the same algorithm. The extension of the output file you request determines which type of events get written to the file. To generate a file of MaxScript commands, one requests an output filename ending with the extension .ms.
write-ms (obj-name &optional (file "test.ms") |
You can use CM's API open and mix commands manually to output your script, but the write-ms command packages them handily together for you. In fact, it will even work for other output types, too.
Example: (progn
(merge foo ()
(add-string "What a silly example")
(object midi-note start 0 rhythm 2 note 60 duration 2))
(write-ms "foo" "bar.ms")
(write-ms "foo" "bar.midi"))
More details later, perhaps, but for now note that most of the make and edit functions work in the following way, as modeled in the aecm.lisp file.
A string giving a valid MaxScript attribute followed by a valid value to which the attribute will be assigned. Example: 'radius 2.0
To come.
Last modified 01-02-24