Each unit — or, more accurately, each segment — has a joint that rotates or slides along a track relative to its parent segment. That motion is guided by a unit program, or simply "program" that tells it when to shift (rotate or slide), by how much, and using what pattern of motion during each beat in time. Each unit's program can be expressed using a compact language.
Unit programs express what happens to a unit during each successive beat, usually culminating in a repeating loop. Consider a unit that simply oscillates a half turn one way and then back the other. This can be expressed as:
S:1/2; E:L | S:-1/2; E:L
Each program is expressed as a single line of text like this. Beats are separated by pipe (|) symbols. Attributes are separated by semicolons (;). And each attribute begins with a attribute name (e.g, "S"), followed by a colon (:), followed by a single argument (e.g., "1/2"). Note that you can have a trailing semicolon after the last attribute in a beat, but this is optional and a matter of taste.
Space characters are optional and useful for improving readability. And technically, carriage return, line feed, and horizontal tab characters get treated as ordinary spaces. The following are valid and equivalent to the above:
S : 1 / 2 ; E : L |
S : - 1 / 2 ; E : L
The unit programming language is completely case-insensitive. The following is equivalent to the above programs:
s:1/2; e:l | s:-1/2; e:l
Numeric attribute values that represent whole integer values are always expressed as strings of digits, like "845215". Attributes values that represent potentially fractional values can be expressed as decimals (e.g., "5.17431") or using fractions (e.g., "3/16", "-3.1415 / 2"). I have found that fractions are almost always a preferable way to think about and accurately express the breakdown of the beat-driven cyclic motion in Clockworks.
In the simple case, a program describes each beat on the assumption that each program beat runs for the same period of time as the overall beat, but that's not strictly true. We use the term "pace" to describe how long a program's single beat is relative to the overall "master beat".
For example, a full rotation at a pace of 1/2 means that the rotation takes place over two master beats. We call this "half time". Conversely, a full rotation at a pace of 2, or "double time", means that two whole rotations take place during a single master beat. So if the master beat rate is 60 BPM, or one beat per second, a half-time beat takes two seconds and a double-time beat takes half a second.
In a unit program, the default pace is 1. You can change the pace using the pace ("P") attribute. During the rest of the loop, this new pace will continue until there is another pace attribute found or the loop completes, whence the pace will return to 1. For example:
P:1/4; S:1 | S:-1 | P:1; S:1/4
For a rotator segment, this can be read as "complete a full rotation over four beats; complete a full turn in the opposite direction over four more; and then make a quarter turn in a full beat".
Clockwork's unit programming language is not a full-fledged programming language. It does not offer if-then type conditionals, variables, and many other familiar features. However, it does offer a few basic program control features.
A program runs in an infinite loop by default. However, you can make that finite by using the loop repetitions ("LR") attribute on the first beat. For example:
This shifts in quarter increments over four beats and then stops all motion, exiting the program.
You can also repeat a single beat more than one time using the repeat ("R") attribute on that beat. For example:
S:-1/12; R:12 | S:1/12; R:12
If this controlled the hour hand of a clock, it would turn the hand forward one hour at a time twelve times and then do the same in the opposite direction.
Although the "R" attribute typically takes an integer value like 2 or 10, it can alternatively take the value "F", which means "forever". This will get the program stuck in an infinite loop on that single beat. This is primarily useful when you have one program used to run an initial movement followed by a call to another program that should loop forever thereafter.
The "R" argument can also take zero as a value. This basically means to skip the passage of time for this beat, but to execute the setup and shifting, skipping to the end immediately.
There is also a first-repeat ("FR") argument that is designed to override the repeat ("R") argument on the first repetition of the loop. This has a few esoteric uses, like causing a string of identical units to kick off their loops at slightly different times, thus staggering their animations. The first might have "FR:0; R:0" as its first beat, the second might have "FR:1; R:0" for its first beat, the third might have "FR:2; R:0", and so on.
Finally, it might not be obvious, but a beat can be entirely empty. This literally means "do nothing during this beat". Here's an example:
S:1 | | S:-1 |
In this example, the unit does a full shift in one direction, does nothing for a beat, does a full shift in the other direction, does nothing for another beat, and then loops back to start over. This program describes four full beats, even though the unit only moves during two of them.
One program can run another using the run program ("RP") beat attribute. Let's say you had programs P1 and P2. Here's how P1 might call P2:
S:1 | RP:P2; R:2 | S:-1
This simple program requires careful explanation. The first beat simply shifts (rotates or slides) the segment a full unit. But as the second beat begins, control is handed over to P2, starting at its first beat. There is no delay in time before this starts. If P2 describes a four-beat program, for example, all four beats execute and then control returns to P1 to carry on its next beat. However, in this example, we have "R:2" as an attribute of this pseudo-beat. That means we actually run the P2 program a second time. By this time, 9 beats have passed and we are ready for our last beat, where we rotate (or slide) a full turn in the opposite direction. Ten beats have passed and now we go back to our first beat in the loop to start over.
The main program loops forever by default and only stops if the first beat's "LR" attribute is set to some finite number. But if a program is called by the main program or some other one, that called program's loop-repetition value is taken from the calling beat's own repetition attribute, which defaults to 1. So by default, a called program will only loop once before returning, not run forever. If you want it to run forever and never return, add the "R:F" attribute value (repeat this beat forever) to the beat with the "RP" (run program) attribute.
If the beat that runs another program ("RP") contains an integer value for the beat repetition attribute ("R") and the called program has an integer value for its own loop repetition ("LR"), the two numbers are multiplied to get the actual number of repetitions. If, for example, we have "R:2" on the beat that does the calling and "LR:3" on the program called, that program will loop 6 times.
In the special case where the caller's "R" attribute (or "FR", first-repetition) is zero, the run-program ("RP") attribute will be ignored. This is useful for only running a program with the first loop iteration or only after it. For example:
FR:1; R:0; RP:Prog1 | FR:0; RP:Prog2
In this example, Prog1 will only be run on this program's first loop iteration and Prog2 will be run on every loop repetition after that first one.