Cacophony

Tutorial: Writing a Custom Effect

« Home

In this tutorial, we will create a new effect for the Cacophony player, and explore the structure of an effect script, how to include it in your video, and how to work with the Cacophony API including timing and mouse-based interactions.


Script Outline

The outline of an effect script is made up of three parts:

  1. Variable declarations
  2. A function for each effect
  3. Registering the effects

This takes the basic form:

// Variables for this set of effects
var myeffect_var1,
    myeffect_var2;

// Functions to handle the effects themselves
function myeffect_one (data) {
    // d:{param1:value, param2:value} become
    // data.param1 and data.param2 here
}

// Register the effects
_e['myeffect_one'] = myeffect_one;

Note that everything uses a consistent naming convention of prefix_ to ensure names of variables and functions are unique to your effect.

Also note how parameters are passed into a function as a single data value, which is the d:{} object from the story file. For optional parameters, checking for their existence and setting default values is as easy as:

myeffect_var1 = (data.param1) ? data.param1 : 'default';

Registering the effects lets Cacophony know about them, so only registered functions can be called from the storyline.


Using Your Effect

Once you've created your effect script, you simply include it in your HTML file as you normally would:

<script type="text/javascript" src="js/myeffect.js"></script>

Now you can use your new effect in your story line like any other:

_s[0] = [
    {a:'myeffect_one', d:{param1:'value', param2:'value'}}
];

A Real Example

Now let's look at a real example that explores some of the different ways of interacting with the Cacophony player. We're going to dissect the sparkles.js effect and see how each piece comes into play. You'll find the final script in the src/effects/sparkles.js file. You can also see an example of the effect here.

Basic Outline

First, we need functions to be registered, and an array to store the list of sparkle objects we'll be creating.

var sparkles = [];

function sparkles_on (data) {
    // Create a sparkle object and add it to the canvas.
}

function sparkles_off () {
	// Remove the sparkle objects from the canvas.
}

// Register the effects.
_e['sparkles_on'] = sparkles_on;
_e['sparkles_off'] = sparkles_off;

Timing and Intervals

Next, we want to update the effect on an interval, so we'll use the cacophony.addInterval() and cacophony.removeInterval() methods to control this, and create a new function for that as well.

var sparkles = [],
    sparkles_interval = false;

function sparkles_on (data) {
    // Create a sparkle object and add it to the canvas.

    // Update every 25ms.
    sparkles_interval = cacophony.addInterval (sparkles_update, 25);
}

function sparkles_off () {
    // Remove the interval updates.
    cacophony.removeInterval (sparkles_interval);
    sparkles_interval = false;

    // Remove the sparkle objects from the canvas.
}

function sparkles_update () {
    // Update the sparkles on interval.
}

// Register the effects.
_e['sparkles_on'] = sparkles_on;
_e['sparkles_off'] = sparkles_off;

We can also use setTimeout() directly, but we need to make sure our callback functions "pause" if the video itself is paused. See the flicker.js code for examples of that.

Using the Mouse

We also want our effect to react to the mouse, so let's create another function for that and register it as a mousemove callback.

var sparkles = [],
    sparkles_interval = false;

function sparkles_on (data) {
    // Create a sparkle object and add it to the canvas.

    // Update every 25ms.
    sparkles_interval = cacophony.addInterval (sparkles_update, 25);
}

function sparkles_off () {
    // Remove the interval updates.
    cacophony.removeInterval (sparkles_interval);
    sparkles_interval = false;

    // Remove the sparkle objects from the canvas.
}

function sparkles_update () {
    // Update the sparkles on interval.
}

function sparkles_mousemove () {
    // Update the sparkles on mousemove.
}

// Register a mousemove callback for the effect.
cacophony.mousemove (function () {
    if (sparkles_interval === false) {
        // Don't call if the effect is not active.
        return;
    }

    sparkles_mousemove ();
});

// Register the effects.
_e['sparkles_on'] = sparkles_on;
_e['sparkles_off'] = sparkles_off;

Notice that we check the sparkles_interval value before calling our callback function. This is so we don't bother to run it unless the effect is active. Cacophony will only call mousemove functions if the player is playing, but we can further limit when it's called with our own state checking.

Adding/Manipulating Objects

Now we're ready to add the code to draw on our canvas. To do this, we use the Cake.js library. We'll add 50 circles of random sizes, and remove them again in sparkles_off(). We'll also create a helper function for randomizing the initial values for size, position, direction, and opacity, called sparkles_rand().

var sparkles = [],
    sparkles_interval = false;
    
// Helper function to get a random value between min and max,
// optionally rounded.
function sparkles_rand (min, max, round) {
    var rand = min + (Math.random () * (max - min));
    return (round) ? Math.round (rand) : rand;
}

function sparkles_on (data) {
    // Create a sparkle object and add it to the canvas.
    for (var i = 0; i < 50; i++) {
        // Create a new sparkle with random position, opacity,
        // direction, and speed.

        // Create a circle between 1-5px
        sparkles[i] = new Circle (sparkles_rand (1, 5));

        // Set its position to within 50px of center
        sparkles[i].x = cacophony.width / 2 + sparkles_rand (-50, 50);
        sparkles[i].y = cacophony.height / 2 + sparkles_rand (-50, 50);

        // Give it a random opacity
        sparkles[i]._opacity = Math.random();
        sparkles[i].fill = 'rgba(255,255,255,' + sparkles[i]._opacity + ')';

        cacophony.canvas.append (sparkles[i]);
    }

    // Update every 25ms.
    sparkles_interval = cacophony.addInterval (sparkles_update, 25);
}

function sparkles_off () {
    // Remove the interval updates.
    cacophony.removeInterval (sparkles_interval);
    sparkles_interval = false;

    // Remove the sparkle objects from the canvas.
    for (var i = 0; i < sparkles.length; i++) {
        cacophony.canvas.remove (sparkles[i]);
    }
    sparkles = [];
}

function sparkles_update () {
    // Update the sparkles on interval.
}

function sparkles_mousemove () {
    // Update the sparkles on mousemove.
}

// Register a mousemove callback for the effect.
cacophony.mousemove (function () {
    if (sparkles_interval === false) {
        // Don't call if the effect is not active.
        return;
    }

    sparkles_mousemove ();
});

// Register the effects.
_e['sparkles_on'] = sparkles_on;
_e['sparkles_off'] = sparkles_off;

The Cacophony Object

You can see we've made use a couple new properties and methods of the cacophony object, specifically:

These are likely the methods you'll use the most often in your effects. For a full list of properties and methods, see the API documentation.

Updating Our Objects

We should now have a bunch of randomized circles drawn onto our video, but they don't do anything yet. Let's make them move on each interval, and also change their direction when they get too close to the mouse.

var sparkles = [],
    sparkles_interval = false;
    
// Helper function to get a random value between min and max,
// optionally rounded.
function sparkles_rand (min, max, round) {
    var rand = min + (Math.random () * (max - min));
    return (round) ? Math.round (rand) : rand;
}

function sparkles_on (data) {
    // Create a sparkle object and add it to the canvas.
    for (var i = 0; i < 50; i++) {
        // Create a new sparkle with random position, opacity,
        // direction, and speed.

        // Create a circle between 1-5px
        sparkles[i] = new Circle (sparkles_rand (1, 5));

        // Set its position to within 50px of center
        sparkles[i].x = cacophony.width / 2 + sparkles_rand (-50, 50);
        sparkles[i].y = cacophony.height / 2 + sparkles_rand (-50, 50);

        // Give it a random opacity
        sparkles[i]._opacity = Math.random();
        sparkles[i].fill = 'rgba(255,255,255,' + sparkles[i]._opacity + ')';

        // Give it a random initial direction and speed in x and y
        sparkles[i]._dir = [
            sparkles_rand (-2.5, 2.5),
            sparkles_rand (-2.5, 2.5)
        ];

        cacophony.canvas.append (sparkles[i]);
    }

    // Update every 25ms.
    sparkles_interval = cacophony.addInterval (sparkles_update, 25);
}

function sparkles_off () {
    // Remove the interval updates.
    cacophony.removeInterval (sparkles_interval);
    sparkles_interval = false;

    // Remove the sparkle objects from the canvas.
    for (var i = 0; i < sparkles.length; i++) {
        cacophony.canvas.remove (sparkles[i]);
    }
    sparkles = [];
}

function sparkles_update () {
    // Update the sparkles on interval.

    for (var i = 0; i < sparkles.length; i++) {
        // Detect edges and reverse course
        if (sparkles[i].x < 0 || sparkles[i].y > cacophony.width) {
            sparkles[i]._dir[0] *= -1;
        }
        if (sparkles[i].y < 0 || sparkles[i].y > cacophony.height) {
            sparkles[i]._dir[1] *= -1;
        }

        // Update the position
        sparkles[i].x += sparkles[i]._dir[0];
        sparkles[i].y += sparkles[i]._dir[1];
    }
}

function sparkles_mousemove () {
    // Update the sparkles on mousemove.

    for (var i = 0; i < sparkles.length; i++) {
    	if (cacophony.dist (sparkles[i].x, sparkles[i].y,
                  cacophony.mousex, cacophony.mousey) <= 25) {

            if ((cacophony.mousex > sparkles[i].x &&
                 sparkles[i]._dir[0] > 0) ||
                (cacophony.mousex < sparkles[i].x &&
                 sparkles[i]._dir[0] < 0)) {
                sparkles[i]._dir[0] *= -1;
            }

            if ((cacophony.mousey > sparkles[i].y &&
                 sparkles[i]._dir[1] > 0) ||
                (cacophony.mousey < sparkles[i].y &&
                 sparkles[i]._dir[1] < 0)) {
                sparkles[i]._dir[1] *= -1;
            }
        }
    }
}

// Register a mousemove callback for the effect.
cacophony.mousemove (function () {
    if (sparkles_interval === false) {
        // Don't call if the effect is not active.
        return;
    }

    sparkles_mousemove ();
});

// Register the effects.
_e['sparkles_on'] = sparkles_on;
_e['sparkles_off'] = sparkles_off;

Now you should be able to see the circles bouncing around whenever they hit the walls or your mouse. Note the two use of three new methods again here:

There are a few more changes to the final sparkles effect, so take a look at that script to look for those, but this is enough to show you how to create a basic effect.


Where to From Here

Cacophony includes a number of built-in effects, all documented with usage examples, that you can learn from in the src/effects folder. This is a great place to learn how existing effects have been made and get ideas for your own.

You can also refer to these in a more convenient format in the API documentation, where comments are more clearly outlined and the syntax for the related code is highlighted.

Questions are also welcome on the Cacophony Google Group, so join in and get involved!


Brought to you by Johnny Broadway