15. Metathings

All things meta. Generally speaking, anything in this section refers to any component in Gest capable of changing itself after it has been created.

15.1. Metatargets

Metatargets will probably end up in a larger category of meta-things, including metaramps and metabehaviors. But metatargets come first.

15.1.1. Overview

A metatarget is a target containing one or more targets inside of itself. Whenever it gets selected it can choose one of these targets. In other words, this allows a particular target to dynamically change every time the phrase it is contained in is played.

When metatargets are created, the number of targets must be known ahead of time. Targets created after the metatarget get automatically appended to the metatarget until there is no more room left.

15.1.2. Struct Declaration

A metatarget is a special struct stored inside of a target inside via user-data pointer provided. It contains an array of target pointers, the size, and variable storing the current position.

<<typedefs>>=
typedef struct gest_metatarget gest_metatarget;
<<structs>>=
struct gest_metatarget {
    int pos;
    int size;
    gest_target *parent;
    gest_target **targets;
};

15.1.3. Metatarget Allocation

A metatarget is allocated with the function metatarget_alloc.

<<static_funcdefs>>=
static gest_metatarget * metatarget_alloc(gest_d *g, int sz);
<<funcs>>=
static gest_metatarget * metatarget_alloc(gest_d *g, int sz)
{
    gest_metatarget *mt;
    int t;

    mt = gest_alloc(g, sizeof(gest_metatarget));
    mt->size = sz;
    mt->pos = 0;
    mt->targets = gest_alloc(g, sizeof(gest_target) * sz);

    for (t = 0; t < sz; t++) {
        mt->targets[t] = NULL;
    }

    return mt;
}

15.1.4. Metatarget Population

Metatarget population in Gest works by caching the metatarget inside of the Gest struct. When a metatarget is first created, it is set to be that value and the position is set to be 0. Every time a target is added, it will append to a metatarget instead of binding to a phrase and increment the position. When the position reaches the limit, the metatarget cache value is emptied.

Nested metatargets will need to use what will be a fixed-size metatarget stack. When a value is emptied, it is also popped from the stack.

A depth of 4 for now will be used. If I need it to go deeper than that, it will be changed.

<<gest_d>>=
#define GEST_MTSTACK_SIZE 4
gest_metatarget *mtstack[GEST_MTSTACK_SIZE];
int mtpos;
<<init>>=
{
    int i;

    for (i = 0; i < GEST_MTSTACK_SIZE; i++) {
        g->mtstack[i] = NULL;
    }

    g->mtpos = 0;
}

See gest_addmetatarget function for more usage on this stack.

15.1.5. Sequential Target Selector

By default, a metatarget will choose targets in sequence. This is done by overriding the default "get" callback in the target.

<<static_funcdefs>>=
static gest_target *get_seq(gest_d *g, gest_target *t);
<<funcs>>=
static gest_target *get_seq(gest_d *g, gest_target *t)
{
    gest_metatarget *mt;

    mt = t->meta;

    if (mt->pos >= mt->size) mt->pos = 0;

    return mt->targets[mt->pos++];
}

15.1.6. Random Target Selector

The random target chooser target_random will randomly choose a target from a metatarget.

<<static_funcdefs>>=
static gest_target *target_random(gest_d *g, gest_target *t);
<<funcs>>=
static gest_target *target_random(gest_d *g, gest_target *t)
{
    gest_metatarget *mt;

    mt = t->meta;

    return mt->targets[gest_randi(g, mt->size)];
}

15.2. Metabehaviors

15.2.1. Overview

The metabehavior is a special kind of behavior that is a collection of behaviors. Every time a target a metabehavior belongs to is selected, it has the ability to change which behavior is used.

15.2.2. Struct Declaration

Data for the metabehavior is encapsulated in a struct called gest_metabehavior. It contains an array of behaviors, the size, and a variable storing the current position.

<<typedefs>>=
typedef struct gest_metabehavior gest_metabehavior;

Note that unlike metatargets, the behaviors array is an array of behaviors, not an array of pointers. Behaviors don't naturally get allocated the same way targets do in the gest system, so they are explicitely allocated here.

<<structs>>=
struct gest_metabehavior {
    int pos;
    int size;
    gest_behavior *parent;
    gest_behavior *behaviors;
};

15.2.3. Metabehavior allocation

A metabehavior is allocated with the function metabehavior_alloc.

<<static_funcdefs>>=
static gest_metabehavior * metabehavior_alloc(gest_d *g, int sz);
<<funcs>>=
static gest_metabehavior * metabehavior_alloc(gest_d *g, int sz)
{
    gest_metabehavior *mb;
    int b;

    mb = gest_alloc(g, sizeof(gest_metatarget));
    mb->size = sz;
    mb->pos = 0;
    mb->behaviors = gest_alloc(g, sizeof(gest_behavior) * sz);

    for (b = 0; b < sz; b++) {
        gest_behavior_init(&mb->behaviors[b]);
        gest_behavior_set(&mb->behaviors[b], linear, NULL);
    }

    return mb;
}

15.2.4. Population (via Stack)

Similar to the metatargets, population is done with a stack of metabehaviors.

<<gest_d>>=
#define GEST_MBSTACK_SIZE 4
gest_metabehavior *mbstack[GEST_MBSTACK_SIZE];
int mbpos;
<<init>>=
{
    int i;

    for (i = 0; i < GEST_MBSTACK_SIZE; i++) {
        g->mbstack[i] = NULL;
    }

    g->mbpos = 0;
}

All behaviors can automatically be appended to metabehaviors instead of targets when they use set_behavior.

First, it will check the metabehavior stack, and append there. Otherwise it will set to the current target directly.

<<static_funcdefs>>=
static void set_behavior(gest_d *g, gest_bfunc tick, void *ud);
<<funcs>>=
static void set_behavior(gest_d *g, gest_bfunc tick, void *ud)
{
    gest_behavior *b;

    b = &g->curtarget->behavior;

    if (g->mbpos > 0) {
        gest_metabehavior *mb;

        mb = g->mbstack[g->mbpos - 1];

        b = &mb->behaviors[mb->pos++];

        if (mb->pos >= mb->size) {
            mb->pos = 0;
            g->mbstack[g->mbpos - 1] = NULL;
            g->mbpos--; /* don't be clever */
        }
    }

    gest_behavior_set(b, tick, ud);
}

15.2.5. Sequentional Behavior Selector

<<static_funcdefs>>=
static gest_behavior* behave_seq(gest_d *g, gest_behavior *b);
<<funcs>>=
static gest_behavior* behave_seq(gest_d *g, gest_behavior *b)
{
    gest_metabehavior *mb;

    mb = b->meta;

    if (mb->pos >= mb->size) mb->pos = 0;

    return &mb->behaviors[mb->pos++];
}

15.2.6. Random Behavior Selector

<<static_funcdefs>>=
static gest_behavior* behave_random(gest_d *g, gest_behavior *b);
<<funcs>>=
static gest_behavior* behave_random(gest_d *g, gest_behavior *b)
{
    gest_metabehavior *mb;

    mb = b->meta;

    return &mb->behaviors[gest_randi(g, mb->size)];
}

15.3. Metanodes

Metanodes are ramp tree nodes whose underlying children can change. A metanode is essentially a monoramp that can have a fixed number of possibilities within the bounds of the ramp it takes up. Every time this node gets reached, it has an opportunity to select one of these possibilities.

15.3.1. Struct Declaration

Called gest_metanode. Basically an array of pointers to gest_node types.

<<typedefs>>=
typedef struct gest_metanode gest_metanode;
<<structs>>=
struct gest_metanode {
    int pos;
    int size;
    gest_node *parent;
    gest_node **nodes;
};

15.3.2. Allocation

metanode_alloc.

<<static_funcdefs>>=
static gest_metanode* metanode_alloc(gest_d *g,
                                     gest_node *parent,
                                     int sz);
<<funcs>>=
static gest_metanode* metanode_alloc(gest_d *g,
                                     gest_node *parent,
                                     int sz)
{
    gest_metanode *mn;
    int i;

    mn = gest_alloc(g, sizeof(gest_metanode));
    mn->size = sz;
    mn->pos = 0;
    mn->parent = NULL;
    mn->nodes = gest_alloc(g, sizeof(gest_node *) * sz);

    for (i = 0; i < sz; i++) {
        gest_node *n;

        n = gest_alloc(g, sizeof(gest_node));
        gest_node_init(n);
        n->type = parent->type;
        n->modifier = parent->modifier;
        n->parent = parent;
        n->id = g->nodepos;
        g->nodepos++;
        mn->nodes[i] = n;
    }

    return mn;
}

15.3.3. Population and the Stack

When a metanode is created, the instance gets pushed onto the stack.

<<gest_d>>=
#define GEST_MNSTACK_SIZE 4
gest_metanode *mnstack[GEST_MNSTACK_SIZE];
int mnpos;
<<init>>=
{
    int i;

    for (i = 0; i < GEST_MNSTACK_SIZE; i++) {
        g->mnstack[i] = NULL;
    }

    g->mnpos = 0;
}

The metanode has all traits of a monoramp, with the addition a non-null metanode struct.

When there is an active metanode, it interrupts the population of the ramp tree. Normally, tree population moves left to right with the use of targets. When a target reaches the end of a node, it goes on to the next consecutive node. When a metanode is active, the next node will loop back to the metanode, populating it until it fills up.


How does gest know when to loop back to the metanode? Well, it first knows to check when there is a metanode on the stack.

<<check_metanode_stack>>=
if (g->mnpos > 0) {
    gest_metanode *mn;
    metahunt = 1;

    mn = g->mnstack[g->mnpos - 1];

    if (mn != NULL) mnpar = mn->parent;
}

A metanode belongs to a monoramp node, and this address will be known. During the search for the next node, it will check for this address, and if it finds it, it will be time to loop back.

<<check_for_metanode_parent>>=
if (metahunt && curr == mnpar) {
<<loopback>>
}

A loop-back rewinds the tree to the monoramp contained residing inside of the metanode. Along the way, it saves underlying tree it created inside of the metanode, and resets monoramp to be re-populated again.

<<loopback>>=
gest_metanode *mn;
next = curr;
/* store children in metanode */

mn = g->mnstack[g->mnpos - 1];

next = mn->nodes[mn->pos++];

if (mn->pos >= mn->size) {
    g->mnpos--;
    g->mnstack[g->mnpos] = NULL;
    mn->pos = 0;
}

set_curnode(g, next);
break;

Monoramps always have exactly one child. This child is the thing that gets stored.

After storing, the metaramp will check and see if there are any more slots available. If not, it will pop itself off the stack.

During performance, Gest is constantly traversing up and down the Ramp Tree, touching nodes going both ways. A metanode only changes its underlying children when it dives downwards to find a target in dive_to_target.

15.3.4. Sequential Node Selector

<<static_funcdefs>>=
static gest_node* node_seq(gest_d *g, gest_node *n);
<<funcs>>=
static gest_node* node_seq(gest_d *g, gest_node *n)
{
    gest_metanode *mn;

    mn = n->meta;

    if (mn->pos >= mn->size) mn->pos = 0;

    return mn->nodes[mn->pos++];
}

15.3.5. Random Node Selector

<<static_funcdefs>>=
static gest_node* node_random(gest_d *g, gest_node *n);
<<funcs>>=
static gest_node* node_random(gest_d *g, gest_node *n)
{
    gest_metanode *mn;

    mn = n->meta;

    return mn->nodes[gest_randi(g, mn->size)];
}

15.4. Metaphrases

If you've been reading up to this point, you'll no doubt know what to expect here. A metaphraseis a special phrase containing phrases inside of it. Every time the phrase gets selected, the metaphrase gets an opportunity to select one of the phrases.

15.4.1. Struct Declaration

Similar to before, a gest_metaphrase has a position, size, and array of phrases.

<<typedefs>>=
typedef struct gest_metaphrase gest_metaphrase;
<<structs>>=
struct gest_metaphrase {
    int pos;
    int size;
    gest_phrase *parent;
    gest_phrase **phrases;
};

15.4.2. Allocation

Allocated with metaphrase_alloc. The number of phrases must be known, as well as the parent phrase.

<<static_funcdefs>>=
static gest_metaphrase * metaphrase_alloc(gest_d *g, int sz);
<<funcs>>=
static gest_metaphrase * metaphrase_alloc(gest_d *g, int sz)
{
    gest_metaphrase *mp;
    int i;

    mp = gest_alloc(g, sizeof(gest_metaphrase));
    mp->size = sz;
    mp->pos = 0;
    mp->phrases = gest_alloc(g, sizeof(gest_phrase *) * sz);

    for (i = 0; i < sz; i++) {
        mp->phrases[i] = NULL;
    }

    return mp;
}

15.4.3. Population and the Stack

Metaphrases are populated via a stack. They can be nested! This is similar to the constructs that have preceded it.

Populating a metaphrase is similar to making a regular phrase, except it starts with a different beginning, but with the same ending.

Phrases get selected during the process when the next node is found.

<<gest_d>>=
#define GEST_MPSTACK_SIZE 4
gest_metaphrase *mpstack[GEST_MPSTACK_SIZE];
int mppos;
<<init>>=
{
    int i;

    for (i = 0; i < GEST_MPSTACK_SIZE; i++) {
        g->mpstack[i] = NULL;
    }

    g->mppos = 0;
}

append newly created phrase to last metaphrase on stack.

some fancy footwork below, so pay attention.

appending of the metaphrase happens when a new phrase is created, but before it is set to be the selected phrase.

if a metaphrase is in play, it will be on the stack. The last item is peaked at. If the position is all filled up, it means the newly created phrase is actually the next phrase. Phrases use pointers to indicate the next phrase, so all the phrases managed by the metaphrase must have their next pointers explicitely set. The metaphrase is then popped from the stack. It is also assumed that the currently selected phrase at this moment is the last phrase of the metaphrase. This doesn't need to be selected anymore, so it is cleared.

Most of the time, the metaphrase has phrases to fill. In this situation, it will append the newly created phrase to the next available slot in the metaphrase.

<<append_metaphrase>>=
if (g->mppos > 0) {
    gest_metaphrase *mp;
    mp = g->mpstack[g->mppos - 1];

    if (mp->pos >= mp->size) {
        int i;

        /* pop from stack */
        g->mppos--;
        g->mpstack[g->mppos] = NULL;

        /* current phrase last phrase in metaphrase, clear it */
        g->phrase_selected = NULL;

        /* all phrases should point to newly made phrase */
        for (i = 0; i < mp->size; i++) {
            mp->phrases[i]->next = phrase;
        }

    } else {
        /* append to next available slot */
        mp->phrases[mp->pos] = phrase;
    }

}
<<metaphrase_next_position>>=
if (g->mppos > 0) {
    gest_metaphrase *mp;
    mp = g->mpstack[g->mppos - 1];
    mp->pos++;
}
<<metaphrase_loopit>>=
if (g->mppos > 0) {
    gest_metaphrase *mp;
    mp = g->mpstack[g->mppos - 1];

    if (mp->pos >= mp->size) {
        int i;

        /* pop from stack */
        g->mppos--;
        g->mpstack[g->mppos] = NULL;

        /* current phrase last phrase in metaphrase, clear it */
        g->phrase_selected = NULL;

        /* all phrases should point to newly made phrase */
        for (i = 0; i < mp->size; i++) {
            mp->phrases[i]->next = g->phrase_top;
        }
    }

}

15.4.4. Sequential Phrase Selector

<<static_funcdefs>>=
static gest_phrase* phrase_seq(gest_d *g, gest_phrase *ph);
<<funcs>>=
static gest_phrase* phrase_seq(gest_d *g, gest_phrase *ph)
{
    gest_metaphrase *mp;

    mp = ph->meta;

    if (mp->pos >= mp->size) mp->pos = 0;

    return mp->phrases[mp->pos++];
}

15.4.5. Random Phrase Selector

<<static_funcdefs>>=
static gest_phrase* phrase_random(gest_d *g, gest_phrase *ph);
<<funcs>>=
static gest_phrase* phrase_random(gest_d *g, gest_phrase *ph)
{
    gest_metaphrase *mp;
    mp = ph->meta;
    return mp->phrases[gest_randi(g, mp->size)];
}



prev | home | next