Set name, "Actuation", Unit name, Type

ProgramMsg("X", "Actuation", "Lamp.$.Base", "L");

Configures the unit (segment) as either a rotator (default) or linear actuator. The only extra argument is the type, which is either "R" or "L".


Set name, "Attach", Unit name, Parent unit name

ProgramMsg("X", "Attach", "Lamp.$.Arm1", "Lamp.$.Base");

Tells a given unit (segment) to connect itself to a parent segment so that its rotation/sliding is done in relation to it. Only later do you define where it is in relation to that parent segment using the Arm operation.

It is critical that you call this operation before you attempt to configure any of this unit's parent or other ancestor segments. As a rule of thumb, make attaching all a machine's segments your first step before configuring anything else about it.

If your machine consists of one or more units that have no parent segments and are simply directly placed in the scene, you do not need to invoke the Attach operation.


Set name, "Arm", Unit name, [Arm vector], [Axis vector]

ProgramMsg("X", "Arm", "Lamp.$.Base", "0,4,0", "0,0,0");

Places a unit in the scene or relative to some other segment in the same machine. This operation sets both the arm and the axis at once, so you can leave either argument blank to keep its current value. See the Assembling a machine section for an explanation of "arm" and "axis".

To be precise, this represents where the axis of rotation (if this segment rotates) is in relation to the world or the parent segment. And it indicates the plane of rotation it will rotate in.

When placing a machine's base unit in the world, the most common use for the axis is to rotate it around the vertical (Z) axis. To do this, to use "0,0,<angle>" for the Axis vector, where <angle> represents the direction you want it to face. For example, "0,0,-90".

Note that this is technically not how you orient your 3D model that represents the unit. Use the Offset operation for that.

See the Positions and orientations section for options for representing the arm and axis values.


Set name, "Bpm", Rate

ProgramMsg("X", "Bpm", "145");

Tells all machines in the given set how fast to run. A rate of 60 BPM is equal to one beat per second; 120 BPM is two beats per second; and so on.

If you wish to synchronize your machines to music, the Song BPM website and others allow you to look up the BPM rates for many songs. You can also do a Google search for "BPM <artist> <song>". For example, "bpm madonna ray of light".

Note that this is one of the few operations that does not take a unit name as an argument. That's because all machines in a given set are assumed to run at the same rate. If some need to run at different rates, you should split your set up into several sets.


Set name, "ClearProg", Unit name

ProgramMsg("X", "ClearProg", "Lamp.$.Base");

Clears all named programs and the default program for the given unit(s). Most applications don't need this.


Set name, "Offset", Unit name, [Offset vector], [Tilt vector]

ProgramMsg("X", "Offset", "Lamp.$.Base", "10,0,0", "0,0,90");

While you use the Arm operation to define the position and orientation of the abstract segment's axis/center-point, you use Offset to precisely place the given unit and "tilt" its orientation relative to that segment.

See the Assembling a machine section for an explanation of "offset" and "tilt". See the Positions and orientations section for options for representing the offset and tilt values.


Set name, "Prog", Unit name, Program name, Program

ProgramMsg("X", "Prog", "Lamp.$.Base", "", "S:1/4 | S:-1/4");

Sends the contents of a named program (or the default program) to the given unit(s). See the Unit programming section for details.

You can refer to the default program either with a blank or with an asterisk (*) character.

Note that when this operation is executed during live programming, it is automatically followed up by a call to Start that program by all the machines in the current set. This makes it easy to see the impact of your newly added program. This does not apply when this operation is called by custom script, however.


Set name, "Ready", Unit name, ["Yes" or "No"]

ProgramMsg("X", "Ready", "Lamp.*");

The unit script will not start managing the position of an object until it is marked as ready. This also gets set to Yes by the Start operation, so this is usually unnecessary. However, if it will be a while before Start gets called, you should call this once you are done configuring a machine or a set of them. You could do this scene-wide using:

ProgramMsg("*", "Ready", "*");

If you do not supply a Yes or No argument, Yes is assumed, as in the examples above. Prior to any call to Ready or Start, No is the unit's default state.

If you are curious, this was a late addition. I was finding that all objects were starting out at the scene's origin, colliding and causing mayhem when there were a large number of units. This could delay successful configuration and readiness of all the machines for a long time. This operation lets the units float free and generally not collide while their configurations get doled out by your script(s). It thus speeds up the configuration process and reduces the initial stress on the scene server.

You can also later turn off a unit's readiness. Although this is a dubious option, it can be used to dramatically reduce server-side CPU utilization and potentially network bandwidth to clients. But keep in mind that the units you un-ready will now be floating free. If nothing touches them, they should look exactly the same as if all you did was call the Stop operation, though. Since all units got marked as having zero mass during initialization, they won't fall because of gravity or rotate in place once their resting positions and orientations get set. Before a call to Ready or Start, however, all bets are off.


Set name, "Start", [Program name], [BPM rate], [Start time ticks]

ProgramMsg("X", "Start", "Prog1", "145");

You can use the Start and Stop operations without any extra arguments for simple start and stop of the currently loaded program. However, Start is the essential way of selecting which program to load and start running. If you want to specify the default program, use asterisk (*).

You can optionally specify the BPM rate for the program to run at, too. This is equivalent to calling the Bpm operation separately.

You almost never need to send the start time, as the controller takes responsibility for injecting this value. The purpose for this is to guarantee synchronization of all units, since messages cannot be guaranteed to arrive at the same time at different units. This value nails down exactly when the first beat of the program was officially started. Think of it as being like back-dating a letter.

If you do decide to pass in the start time, it must be expressed in "ticks". You can get this using code like the following:

ProgramMsg(SetName, "Start", "Prog1", "127", "" + DateTime.Now.Ticks);

Note that this is one of the few operations that does not take a unit name as an argument.


Set name, "Stop"

ProgramMsg("X", "Stop");

Stops the current program from running, if any. Restores all the units to their default positions and orientations.

This will not change which program is currently loaded. So if the user calls Start with no arguments, the same program will restart again.

Note that this is one of the few operations that does not take a unit name as an argument.


Set name, "Track", Unit name, Vector

ProgramMsg("X", "Track", "Lamp.$.Base", "0,-10,0");

This only applies to linear actuators. It defines the "track" that this unit can travel along from the "zero" point at the arm out in the direction the track points and to a distance reflected by the track's length. This track is, like the arm and axis, relative to the parent segment's orientation at a given time.

See the Assembling a machine section for more about tracks.


Set name, "UpdateRate", Unit name, Delay in milliseconds

ProgramMsg("X", "UpdateRate", "*", "20");

Clockworks units have their own self-regulated framerate. The faster this rate the smoother the animation. However, the faster the framerate, the more CPU consumed and the more bandwidth required for sending updates to clients in your scene.

Update rate is expressed in milliseconds between updates. The larger this number, the lower the framerate. The default is 10 ms, which amounts to 100 frames per second (FPS).

Because of how Clockworks works, the update rate you choose does not affect how fast things move. If your BPM rate is 120 BPM, your programs will run at 120 BPM no matter what update rate you select. The update rate only affects how smooth the animation appears to visitors.

If you find that your scene is bogging down on the server — usually seen in delays in responding to requests to run other programs — then consider lowering the framerate. One good starting point is to halve the framerate by doubling the delay from its default of 10 ms between frames to 20 ms, or 50 FPS. This still presents a very smooth animation. For comparison, analog TV operates at 30 FPS and standard theaters still play movies at 24 FPS. Newer movies are starting to be filmed and presented at 48 FPS. Just about the lowest you would want to go would be 10 FPS, or a delay of 100.

To compute the delay value to pass into this function from FPS, divide 1,000 by the FPS value. For instance, 1,000 / 30 = 33.33 ms. To find out the framerate from the delay, divide 1,000 by the delay. For example, 1,000 / 15 = 66.67 FPS.

Strictly speaking, Clockworks cannot guarantee any framerate. If the server is bogged down, the script engine will generally reduce the framerate. This is the main reason that the update rate is expressed in terms of a delay, because it is the minimum delay between updates and not the guaranteed maximum.


Set name, "Version", Unit name

ProgramMsg("X", "Version", "Lamp.$.Base");

This returns the unit name and the name and version of the script running in an actual unit (not an abstract segment). It should return "<unit name>: Clockworks - Unit 2.0" or some later version. The version text is displayed in public chat.

Keep in mind that if you address a large number of units using a broad name filter (e.g., "*"), you may get a lot of chat messages at once.

This operation may come in handy later as users buy products from the store and have to deal with bugs resolved or features added to Clockworks along the way.