13. Targets and Behaviors
Targets
in a Gesture are analogous to breakpoints in
a line segment generator. The way in which a line travels
from point A to point B in time is known as a behavior
.
Targets can be contained inside of a node. If the tree reaches a node with a target, it sets it to be that target when the node starts.
Behaviors are contained inside of a Target.
13.1. Struct Declaration
A target in gest is known as a gest_target
typedef struct gest_target gest_target;
A target stores 3 main things: a scalar value, a callback, and some user data. The callback is a function that takes 4 arguments, the gest struct, the internal value, the position, the next value, and user data. It returns a floating point value.
Targets need to managed in their own linked list, in addition to being referenced in the node they belong to. Targets need to know what target is coming next (if there is one coming next).
<<gest_behavior>>
<<gest_actionlist_struct>>
struct gest_target {
SKFLT value;
gest_behavior behavior;
gest_behavior *curbehavior;
void *ud; /* user data attached to this target */
gest_target *next;
gest_target* (*get)(gest_d *, gest_target *);
gest_metatarget *meta;
<<gest_target>>
};
13.2. Initialization
void gest_target_init(gest_target *t);
void gest_target_init(gest_target *t)
{
t->value = 0;
t->ud = NULL;
t->next = NULL;
t->get = NULL;
t->meta = NULL;
gest_behavior_init(&t->behavior);
t->curbehavior = &t->behavior;
<<gest_target_init>>
}
13.3. Binding Targets to Ramp Tree Nodes
Every target created is bound to exactly one node in a ramp tree. Such a binding indicates a terminal leaf node in the tree.
Creating targets are an important operation because it is the thing that moves the tree forward (from left to right) in population.
static gest_target * mktarget(gest_d *g);
static gest_target * mktarget(gest_d *g)
{
gest_target *t;
gest_node *last;
gest_node *curnode;
t = NULL;
last = NULL;
curnode = g->curnode;
<<create_target>>
if (g->mtpos > 0) {
<<tie_to_metatarget>>
return t;
}
if (curnode == NULL) {
return NULL;
}
<<check_current_node>>
<<tie_to_node>>
<<move_forward>>
return t;
}
Data for a new target is allocated and initialized. What to do with the target remains to be seen.
t = gest_alloc(g, sizeof(gest_target));
gest_target_init(t);
A check is done to see if the current node can have a target applied in the first place.
{
int size;
size = node_count(curnode, &last);
if (curnode != NULL && size >= curnode->modifier) {
return NULL;
}
}
Technically, a target is bound to the last created node, which is always a monoramp. But these monoramps don't have to be explicitely created. Example: "polyramp(3), target, target, target" will create a polyramp node with 3 divisions and populate each one of those divisisons (a monoramp) with a target (called one after another 3 times). So, how to deal with that? By checking the type of the last created child. If it exists at all, it is always going to be a monoramp and never a polyramp, due to the left-to-right method of population (new polyramps always get selected to be the active node). A monoramp's target can be checked if it is occupied. A new target is bound to an unoccupied monoramp target. Otherwise, a new monoramp with a modifier of 1 is created to house the new target.
{
int rc;
rc = last != NULL &&
last->type == NODE_MONORAMP &&
last->meta == NULL &&
last->target == NULL &&
last->nchildren == 0;
if (rc) {
last->target = t;
} else {
rc = curnode->type == NODE_MONORAMP &&
curnode->target == NULL;
if (rc) {
curnode->target = t;
} else {
gest_node *mr;
mr = mkmonoramp(g, curnode, 1);
mr->target = t;
}
}
}
Targets are the things that move the ramp tree forward in a left-to-right fashion when it is being populated.
A movement to the next available node happens when the current node has been filled up. When this happens, it will attempt to move up a level to find free slots there. This will continue to happen until a free slot is found, or it reaches the end of the phrase.
{
gest_node *next;
gest_node *curr;
int metahunt;
gest_node *mnpar;
next = NULL;
curr = curnode;
metahunt = 0;
mnpar = NULL;
<<check_metanode_stack>>
while (next == NULL) {
int size;
gest_node *last;
int limit;
<<check_for_metanode_parent>>
/* is there any room in the current node? */
size = node_count(curr, &last);
if (curr->type == NODE_MONORAMP) {
limit = curr->nchildren;
} else {
limit = curr->modifier;
}
/* no room ... */
if (size >= limit) {
/* onto the next... */
/* we've reached the top */
if (curr == curr->parent) break;
/* try one level up */
curr = curr->parent;
} else {
/* this node has room! */
next = curr;
}
}
set_curnode(g, next);
}
If there is a metatarget being active populated, the target will be sent there instead of being tied to a node. The most recent metatarget in play is retrieved via the metatarget stack, and a target is appeneded. If it has reached the end, the metatarget itself is popped off the stack here.
gest_metatarget *mt;
/* get from top of stack */
mt = g->mtstack[g->mtpos - 1];
/* append to targets array */
mt->targets[mt->pos++] = t;
/* pop off stack if reached the end */
if (mt->pos >= mt->size) {
g->mtpos--;
g->mtstack[g->mtpos] = NULL;
mt->pos = 0;
}
13.4. Temporal Weight
Targets have temporal mass and inertia. These values can be used to slow down or speed up the global tempo in the conductor signal generator. Mass is the amount used to speed up or slowdown the tempo. Inertia is the speed at which it changes.
When a target changes global inertia or mass, it does so through an action.
The current actions implemented include directly setting
the mass and inertia values. The action callbacks for these
are update_mass
and update_inertia
.
static void update_mass(gest_d *g, void *ud, int pos);
static void update_inertia(gest_d *g, void *ud, int pos);
static void update_mass(gest_d *g, void *ud, int pos)
{
SKFLT *mass;
mass = (SKFLT *) ud;
g->mass = *mass;
}
static void update_inertia(gest_d *g, void *ud, int pos)
{
SKFLT *inertia;
inertia = (SKFLT *) ud;
g->inertia = *inertia;
}
These can be appended to targets with action_mass
and action_inertia
.
static void action_mass(gest_d *g, gest_target *t, SKFLT mass);
static void action_inertia(gest_d *g, gest_target *t, SKFLT interia);
static void action_mass(gest_d *g, gest_target *t, SKFLT mass)
{
SKFLT *pmass;
pmass = gest_alloc(g, sizeof(SKFLT));
*pmass = mass;
append_action(g, t, update_mass, pmass);
}
static void action_inertia(gest_d *g, gest_target *t, SKFLT inertia)
{
SKFLT *pinertia;
pinertia = gest_alloc(g, sizeof(SKFLT));
*pinertia = inertia;
append_action(g, t, update_inertia, pinertia);
}
13.5. Mix Callback
The mix
callback is the callback used to interpolate
between the current value and the next value, given an
alpha value generated from the ramp tree.
SKFLT (*mix)(gest_d *, SKFLT, SKFLT, SKFLT);
By default, mixing is just a linear crossfade between
two the two values. This is defined in the callback
default_mix
:
t->mix = default_mix;
static SKFLT default_mix(gest_d *g, SKFLT x, SKFLT y, SKFLT a);
static SKFLT default_mix(gest_d *g, SKFLT x, SKFLT y, SKFLT a)
{
return (1 - a)*x + a*y;
}
The mix callback can be updated using the function
gest_target_mix
.
void gest_target_mix(gest_target *t,
SKFLT (*mix)(gest_d *, SKFLT, SKFLT, SKFLT));
void gest_target_mix(gest_target *t,
SKFLT (*mix)(gest_d *, SKFLT, SKFLT, SKFLT))
{
t->mix = mix;
}
13.6. Set/get user data
void gest_target_data_set(gest_target *t, void *ud);
void* gest_target_data_get(gest_target *t);
void gest_target_data_set(gest_target *t, void *ud)
{
t->ud = ud;
}
void* gest_target_data_get(gest_target *t)
{
return t->ud;
}
13.7. Behaviors
Behaviors are the things that dictate how one target goes to the next target. A behavior is a callback function. It is a function that takes in a value from 0 to 1, and then returns a new value assumed to also be in the same range. Behaviors can also have their own state to manage things like parameters and constants.
typedef struct gest_behavior gest_behavior;
typedef SKFLT (* gest_bfunc) (gest_d *, SKFLT, void *);
struct gest_behavior {
gest_bfunc tick;
void *ud;
gest_behavior * (*get)(gest_d *, gest_behavior *b);
gest_metabehavior *meta;
};
Initialize the behavior with gest_behavior_init
.
void gest_behavior_init(gest_behavior *b);
void gest_behavior_init(gest_behavior *b)
{
b->tick = NULL;
b->ud = NULL;
b->get = NULL;
b->meta = NULL;
}
Set the behavior with gest_behavior_set
.
void gest_behavior_set(gest_behavior *b, gest_bfunc tick, void *ud);
void gest_behavior_set(gest_behavior *b, gest_bfunc tick, void *ud)
{
b->tick = tick;
b->ud = ud;
}
call the tick function with gest_behavior_tick
.
SKFLT gest_behavior_tick(gest_behavior *b, gest_d *g, SKFLT a);
SKFLT gest_behavior_tick(gest_behavior *b, gest_d *g, SKFLT a)
{
return b->tick(g, a, b->ud);
}
13.8. Behavior Getter From Target
static gest_behavior * target_behavior(gest_d *g, gest_target *t);
static gest_behavior * target_behavior(gest_d *g, gest_target *t)
{
gest_behavior *b;
if (t == NULL) return NULL;
b = &t->behavior;
if (b != NULL && b->get != NULL) {
while (1) {
b = b->get(g, b);
if (b->meta == NULL || b->get == NULL) break;
}
}
return b;
}
prev | home | next