Getting Started

Setup

First of all we need to get the system ready, by preparing some animations:

You only need to do this once, or every time you want to add new animations to the system.

Run

Now we are ready to play and modify animations:

Persistency

Lastly, if you want to save a session to recover it later, see Loading and Saving.

Next

For more advanced topics, pick one from the menu, or scroll down.

For a quick reference of all the available commands see the cheat sheet.

Installing Animations

To use Animatron you’ll need some animations to work with.

Animations are just collections of .png files or spritesheets (like the ones used in videogames).

When using image collections, it gathers them from named folders, using the folder name as the animation identifier.

The default directory to store animations is: user://assets/animations where user:// depends on the system you’re on:

  • Linux: ~/.local/share/animatron/

  • macOS: ~/Library/Application Support/

  • Windows: %APPDATA%\

For example, on Linux, having a collection of .png images in this directory ~/.local/share/animatron/assets/animations/whatever/ would allow us to create an actor with:

/load whatever
/create myactor whatever

This works with symlinks as well (shortcuts), so you don’t need to have the actual folders in that path.

If you want to use images that are in other directories, you can change the assets path with:

/assets/path path/to/your/custom/directory

We strongly recommend using your own animations, but you can find some of ours in this link.

Loading Animations (Assets)

Assets are anything that Animatron can load from disc. Animations are image assets that are loaded into the program.

In this tutorial we’ll learn how to load assets into Animatron.

Read how to install animations if you haven’t already.

We’ll assume that the parent folder for our animations is: /home/me/animations. We’ll imagine that our animations directory tree looks something like this:

animations
├── foo-jump
│   ├── 0001.png
│   ├── 0002.png
│   ├── 0003.png
│   ├── 0004.png
│   ├── 0005.png
│   ├── 0006.png
│   ├── 0007.png
│   └── ...
├── mama
│   ├── 00001.png
│   ├── 00002.png
│   ├── 00003.png
│   ├── 00004.png
│   └── ...
└── ...

To load the animation called foo-jump, first we need to tell Animatron where it can find it by setting the path to the image assets parent folder.

(assets-path-set! "/home/me/animations")        ; Linux and MacOS
(assets-path-set! "C:\\Users\\me\\animations")  ; Windows -- note the double back-slash '\\'

Then we can load it by name

(define-animation foo-jump)

Nothing seems to happen, because we just loaded the animation. To play it we need to create an actor

Creating Actors

We call actor the object playing an animation. We need to give the actor a name so we can send commands to it later. A name can be anything starting with a letter followed by an arbitrary number of letters, numbers and hyphens (-). Other symbols are not allowed.

Create an actor named foo which will play our foo-jump animation.

(define-actor foo foo-jump)
Warning
Names are case-sensitive; this means that foo is different from Foo and FOO.

Creating a group of actors

Many actors can be create at once with

(define-many-actors bar 10 foo-jump)

The previous command creates a group named bar, with 10 actors using the same animation foo-jump.

The first actor in the group is named bar-00, the second bar-01, and so on until the last actor bar-09.

Controlling stuff with commands

A command is a message we send to objects in Animatron in order to change something. Most of the commands we’ll use will modify actors' behaviour or appearance. There are commands for non-actors as well.

An actor command always has the same structure: (COMMAND-NAME ACTOR-NAME ARGUMENTS).

For example, let’s double the playback speed of the animation played by an actor named foo:

(speed! foo 2)  ; normal speed is 1

A command ending with an exclamation mark (!) usually means that the command is changing something in the system.

Some actor commands don’t have arguments:

(stop! foo)

Commands with no arguments at all are usually to get info from the system —note the lack of exclamation mark (!):

(list-actors)   ; prints a message in the console

For the full list of commands see the cheat sheet.

Controlling more than one actor

Commands can be sent to all members of a group, at once, by using the command with the group name as we would for a single actor.

(define-many-actors bar 10 foo-jump)    ; create the group
(speed! bar 2)                          ; all actors in the group will play at double speed
(speed! bar-09 0.5)                     ; only actor `foos-09` will play at half the speed
Warning
When using a random value for a command, all members of the group will get the same random value.

To set a different random value to each member of a group, use (for-each-in):

(for-each-in (all bar) (lambda (each-actor)
       (position! each-actor (random 1.0) (random 1.0))))

This function iterates over every member of the group named bar. On each iteration, we can set the variable name for the current actor being iterated. Here we used each-actor (the variable we want to use is the third argument - on the first line). Then we can re-use the variable name in the function call in the body, to access that specific actor.

Routines

A routine is an action that happens repeatedly every given time. This allows us to automate stuff while we code other things.

To define a routine we need to give it a name, how many times we want it to repeat, how much time it must wait between calls, and a function telling it what to do.

(define-routine bar             ; define a routine named _bar_
                3               ; number of repetitions
                1.5             ; interval between each repetition, in seconds
                (lambda (tick)                  ; function definition (1)
                  (angle! foo (* tick 10))))    ; function body (2)
  1. function definition: takes one argument which counts how many times the function has been called by the routine. The word lambda means it’s an anonymous function (only called by the routine).

  2. function body: what to do every time the routine calls the function. In this case, it sets the angle of an actor named foo to a value obtained by multiplying the current tick count (how many times the function has been called until now) by 10 degrees.

Infinite Routines

To let a routine run forever, set the value of repeats to INF.

(define-routine bar INF 0.3 (lambda (t) (rotate! foo 5)))

Stop the routine at any time using its name:

(routine-stop! bar)

Sequencing Animations

Animations for any given actor can be sequenced. This means that we can tell Animatron which animation to play after the current animation in a given actor reaches the end.

Supose we have two animations named anim-a and anim-b, and an actor named foo which is playing anim-a.

(define-animation anim-a)
(define-animation anim-b)
(define-actor foo anim-a)

We can tell foo to play a sequence of animations with (add-animation-sequence) which takes 3 or more parameters:

  1. the actor’s name

  2. the name of the animation that the actor is currently playing

  3. and one or more names of animations to choose from.

(add-animation-sequence! ACTOR-NAME CURRENT-ANIMATION NEXT-ANIMATION-1 ... NEXT-ANIMATION-N)

The actor will play CURRENT-ANIMATION until the end, and then choose one from all the possible NEXT-ANIMATION-…​.

For example, let’s tell foo to play anim-a then anim-b:

(add-animation-sequence! foo anim-a anim-b)

This will play anim-a once, and then loop anim-b forever.

Alternatively, we can add anim-a and anim-b as a next possible animations to anim-a like this:

(add-animation-sequence! foo anim-a anim-b anim-a)

This will play anim-a (the current animation) until the last frame, and then choose to either play anim-a again, or play anim-b. If the system chooses to repeat anim-a it will play it until the last frame and choose again between both possibilities. Once it starts playing anim-b it will loop it for ever, because we have not set a sequence to play after anim-b. Let’s create one:

(add-animation-sequence! foo anim-b anim-a)

The actor foo will now play anim-b once and then play anim-a again, checking wether to repeat anim-a after that or play anim-b, which would play anim-a when it reaches the end, …​ and so forth.

We have created an generative sequence of animations.

Listing Animation Sequences

We can print a list of the animation sequences for a given actor. Continuing with the example above, we would do:

(list-animation-sequences foo)
; => anim-a: ["anim-a", "animb-b"]
;    anim-b: ["anim-a"]

Removing Animation Sequences

Continuing with the example, we can remove the sequence of anim-a with:

(remove-animation-squence! foo anim-a)

Whenever the actor starts playing anim-a again, it will loop it forever, because we removed its sequence.

Executing Commands With Animations

We can tell Animatron to trigger stuff when any actor’s animation reaches the last frame. To do this we use (on-actor-animation-finished) which takes 2 or more arguments.

  1. the actor’s name

  2. one or more expressions which will be called every time the animation reaches the last frame

; load animation and create an actor
(define-animation foo-jump)
(define-actor foo foo-jump)

; execute code at the end of each loop
(on-actor-animation-finished foo (speed! foo (random-range 1.0 2.0)))

The example above will change the speed at which the foo plays the animation for every loop.

We can add more than one expression:

(on-actor-animation-finished foo
  (speed! foo (random-range 1.0 2.0))
  (opacity! foo (random-range 0.1 1)))

To cancel it, we call the procedure with only the actor’s name without any expressions:

(on-actor-animation-finished foo) ; do nothing

Tele-Animatron

Each Animatron instance (running program) can control other Animatron instances running in the same machine or in other machines that are in the same network.

In order to do this, we need to define a tele (old Greek word meaning "far away", like in television). A tele has an IP and a PORT:

(define-tele another-animatron "192.168.1.41" 56101)

Now we can send regular Animatron code to it with the (tele) command, which accepts 2 or more arguments (the destination tele name and an arbitrary number of expressions):

(tele another-animatron
      (define-animation foo-jump)

(tele another-animatron
      (define-actor foo foo-jump)
      (speed! foo (random-range 1.0 2.0))
Warning
(system) commands will not be accepted from another instance of Animatron. See Security.

Receiving OSC

Animatron can be controlled using OSC messages on port 56101.

Some of its commands are already mapped to OSC. To see a full list of existing messages, use:

(list-osc-commands)

For example, if we have an actor named foo, we could change its speed by sending it the message /actor/speed foo 1.5.

Using liblo it would look like this:

oscsend localhost 56101 /actor/speed sf foo 1.5     # using liblo

Here’s an example using SuperCollider:

n = NetAddr("localhost", 56101);
n.sendMsg("/actor/speed", 1.5);

We can replace localhost with any IP address of any device in the network, so we can control any number of animatron instances from remote devices, as long as they are in the same network.

Ignoring OSC

Sometimes applications send a lot of messages that we might not want to implement. This will create a lot of warnings in the console. To avoid this, we can tell Animatron to ignore a specific incoming OSC message with (osc-ignore! your-osc-msg-address).

(osc-ignore! "/some/message")

Custom OSC messages

External applications or devices will send their own OSC messages, and it may not be possible to change them. For example, F-Droid’s Sensor2Osc might be sending /gyroscope with 3 arguments for x, y and z. To add this OSC message to the incomming listener, we use (osc-add-input-command!) which takes 2 arguments:

  1. the OSC message path, for example "/gyroscome"

  2. a function which takes the same number of arguments as the incoming OSC message, in this case x, y and z.

(osc-add-input-command! "/gyroscope" (lambda (x y z)
    (angle! my-actor (* 360 x))
    (size! my-actor y))

The function can be declared elsewhere:

(define (my-gyroscope . args)  ;  the dot (.) means it accepts an arbitrary number of arguments, including none.
    (angle! my-actor (list-ref args 0)) ; x
    (size! my-actor (list-ref args 1))  ; y
    )

Now we are ready to add an OSC message that will trigger this command:

(osc-add-input-command! "/gyroscope" my-gyroscope) ; this will call (my-gyroscope) with as many arguments as /gyroscope sends.

Notice that args is a list. To access its elements in the function called by OSC, we used (list-ref args INDEX).

The previous example should rotate my-actor and change its size whenever the device is moved.

Sending OSC

Animatron can send messages to other applications and devices in the same network.

(osc-send "127.0.0.1" 8000 "/foo" "hi there" 1 2.3)
(osc-send *localhost* 8000 "/foo" "hi there" 1 2.3)     ; same as above
(osc-send "192.168.0.127" 8000 "/foo" "hi there" 1 2.3) ; imaginary remote machine in the same network

In the examples above, we are sending the same message on different IPs, but always on port 8000.

MIDI

Receiving MIDI

Incoming MIDI messages are handled like OSC.
MIDI messages are internally converted to emulate OSC messages. The path for MIDI messages can take 3 different forms:

  • /<MESSAGE_TYPE> <CHANNEL> <NUM> <VAL>

  • /<MESSAGE_TYPE>/<CHANNEL> <NUM> <VAL>

  • /<MESSAGE_TYPE>/<CHANNEL>/<NUM> <VAL>

<MESSAGE_TYPE> is noteon, noteoff, cc, program_change, …​
<CHANNEL> is the MIDI channel number from 0 to 15.
<NUM> is the MIDI note or control number from 0 to 127.
<VAL> is the MIDI note velocity or control value from 0 to 127.

For example, imagine a device sending a MIDI Control Change (CC) message on channel 1 with control number 9 and a value of 63. We can use it in 3 different ways:

; this will capture any CC message and pass the channel, control number and value as arguments to the function.
(midi-add-input-command! "/cc" (lambda (ch num val)
  (log-debug (format #f "/cc: ~A ~A ~A" ch num val))))
; => /cc: 0 8 63

; this will capture only CC messages on channel 1, passing control number and value to the function.
; see note below why we set the channel number to 0 instead of 1.
(midi-add-input-command! "/cc/0" (lambda (num val)
  (log-debug (format #f "/cc/0: ~A ~A" num val))))
; => /cc/0: 8 63

; this will capture only CC messages from control #9 on channel 1
; see note below why we set the control number to 8 instead of 9.
(midi-add-input-command! "/cc/0/8" (lambda (val)
  (log-debug (format #f "/cc/0/8: ~A" val))))
; => /cc/0/8: 63
Warning
MIDI standard channels range from 1 to 16, and notes and control numbers start counting from 1. Animatron starts counting both from 0, following other programming language standards (namely SuperCollider).

Like with OSC, we can create a dedicated function that will be called when a MIDI message arrives.

(define (bla . args)
    (message (car args)))
Note
Make sure to insert the dot . between the function name and the arguments.

Then we can map it to an incoming MIDI messages, which is described like an OSC message:

(midi-add-input-command! "/cc/0/0" bla) ; call `bla` when MIDI CC 0 on CH 0 arrives

Audio

Audio volume can be used to control actors using (with-audio-volume). It’s argument is a lambda which must have one argument, which is the volume (you can name it however you want).

(define-animation square)
(define-actor foo square)

(with-audio-volume (lambda (vol)
    (size! foo vol)))

Loading and Saving

Saving Code

The only way to save code is by copy-pasting it to a text editor (NOT a word processor).

  1. open a text editor like Notepad or TextEdit

  2. paste the code

  3. save it as simple text format. You can name it whatever you want. The extension is not important, but since this is Scheme code, it makes sense to use .scm.

Loading Code

To load and execute the code from a file, simply use:

(load "/path/to/your/file.scm")         ; Linux and MacOS
(load "C:\\path\\to\\your\\file.scm")   ; Windows

The code in the file will be executed when loaded. This means that any commands that are not in a function will be called, immediately, in order. Saved files can be used as presets, color-palettes, config files for performances, …​

Animatron can load a file when starting from the command line. Open a terminal and type:

animatron /path/to/your/file.scm        ; Linux and MacOS
animatron C:\\path\\to\\your\\file.scm  ; Windows

Loading File to the Editor

The editor can be set to show the contents of a file with:

(file->editor "/path/to/your/file.scm")         ; Linux and MacOS
(file->editor "C:\\path\\to\\your\\file.scm")   ; Windows

Security

If someone sends a system command over OSC, an alert will pop-up blocking the application until accepted.

This is the default so you become aware when such an intrusion occurs. But if the intruder keeps sending the messages, it would continuously block Animatron making it impossible to do anything. To disable the pop-up use:

(alert-intrusions-set! #f)  ; setting it to #t re-enables the alert

The intrusions will still be stopped and logged even if the alert is disabled.