9. WIP Trig Graforge Nodes

9.1. Handling sample-accuracy

Trig interfaces with audio-rate signals using wires. Wires read/write at clock-rate (every time the clock triggers), while the signals run at audio rate.

To preserve the sample-accuracy of these signals, a fixed-size event stack is used for each wire. Any time a clock trigger occurs inside of a render block, the signal is stored for it to be retrieved again at the right time later.

In an event stack, nitems refers to the total of events the event stack is holding. These are held in the valsarray.

The input variable is used to mark whether or not the wire is being used as an input signal, and is used for processing events in trigex.

The ref variable holds a pointer reference to the corresponding wire in the VM, and is used by trigwget.

In order to process events in the right sample accurate position, a reference of the the clock event stack is supplied as the variable ces. This gets set when the page is first initialized.

last caches the last value from the previous compute call.

<<trig_typedefs>>=
typedef struct wire_evstack wire_evstack;
<<trig_other_structs>>=
struct wire_evstack {
    int nitems;
    GFFLT vals[4];
    int input;
    float *ref;
    clock_evstack *ces;
    float last;
};

Block positions are stored in an array for the clock signal, which correspond to each event stack.

<<trig_typedefs>>=
typedef struct clock_evstack clock_evstack;
<<trig_other_structs>>=
struct clock_evstack {
    int nitems;
    int vals[4];
};

A fixed event size of 4 is decent enough size. Given that the default block size of 64 is used, this means the clock can be a maximum of around 2kHz, way faster than any clock that will be practically used.

9.2. Trig Clock (trigclk)

This sets the clock signal for Trig. It stores any triggers as block events in an event stack.

9.2.1. Trigclk Node Function

<<trig_function_declarations>>=
static int node_trigclk(gf_node *node, page_trig_d *trig);
<<trig_functions>>=
<<trig_nodeclk_functions>>
static int node_trigclk(gf_node *node, page_trig_d *trig)
{
    gf_node_cables_alloc(node, 1);
    gf_node_set_data(node, trig);
    gf_node_set_destroy(node, trigclk_destroy);
    gf_node_set_compute(node, trigclk_compute);
    return GF_OK;
}

9.2.2. Trigclk Compute

<<trig_nodeclk_functions>>=
static void trigclk_compute(gf_node *n)
{
    int blksize;
    int s;
    gf_cable *in;
    page_trig_d *trig;
    clock_evstack *ces;

    trig = gf_node_get_data(n);
    blksize = gf_node_blksize(n);

    ces = &trig->ces;

    gf_node_get_cable(n, 0, &in);

    ces->nitems = 0;

    update_and_clear(trig);

    for(s = 0; s < blksize; s++) {
        GFFLT smp;
        smp = gf_cable_get(in, s);
        if (smp != 0) {
            if (ces->nitems < 4) {
                ces->vals[ces->nitems] = s;
                ces->nitems++;
            }
        }
    }

}

9.2.3. Trigclk Destroy

<<trig_nodeclk_functions>>=
static void trigclk_destroy(gf_node *node)
{
     gf_node_cables_free(node);
}

9.3. Trig Execute (trigex)

This will step through the trigger VM at the clock rate. Should be called just once per patch.

9.3.1. Trigex Node Function

<<trig_function_declarations>>=
static int node_trigex(gf_node *node, page_trig_d *trig);
<<trig_functions>>=
<<node_trigex_functions>>
static int node_trigex(gf_node *node, page_trig_d *trig)
{
    gf_node_set_data(node, trig);
    gf_node_set_destroy(node, trigex_destroy);
    gf_node_set_compute(node, trigex_compute);
    return GF_OK;
}

9.3.2. Trigex Compute

9.3.2.1. Overview

There are many moving parts that occur in trigex. Some explanation is warranted.

Events from trigclk get processed here. If there are no events to process, the function exits.

In the block loop, the next event is searched for. When the event is found, the node prepares to step through the VM.

Before the VM can step, input signals are processed. While the trig VM doesn't make a distinction between inputs and outputs, graforge does. The function will iterate through all 8 wire event stacks. If the wire event stack is marked as an input cable are there are events to process, it will copy the event over to the cable.

After input signals are copied over, trig_vm_step can be called.

9.3.2.2. Setting wires at audio rate

Wires used for output signals are taken out of the VM using a special callback interface. Any time a write command happens in the VM, it triggers a user-defined callback. In this case, this callback pops a value onto the event stack.

<<trig_static_funcdefs>>=
static void wire_cb(trig_vm *vm, void *ud, int w, float x);
<<trig_functions>>=
static void wire_cb(trig_vm *vm, void *ud, int w, float x)
{
    /* TODO: prevent double-writing */
    page_trig_d *trig;
    wire_evstack *wes;

    trig = ud;

    wes = &trig->wes[w];

    if (wes->nitems >= 4) return;

    wes->vals[wes->nitems] = x;
    wes->nitems++;
}
<<trig_wire_setter>>=
trig_vm_setter(&trig->tvm, wire_cb, trig);
9.3.2.3. What happens if the VM writes to cable more than once?

What happens if the VM writes to a cable more than once? Without some kind of protection, the VM is at risk of overpopulating the event stack for that wire! By the time output cables are being written to, the clock event stack has been populated containing a list of ordered events and buffer offsets. The current buffer position can also be kept track. With this information, it can be determined if the callback has been called at this sample.

Input cables are a bit more forgiving: the only potential risk here is if a two signals write to the same cable. The solution here is a straight forward one: just reset the event stack at the beginning of every block, making only the last signal the valid one.

9.3.2.4. The Callback Itself

<<node_trigex_functions>>=
static void trigex_compute(gf_node *n)
{
    int blksize;
    int s;
    page_trig_d *trig;
    clock_evstack *ces;
    int nitems;
    int pos;
    trig_vm *tvm;

    trig = gf_node_get_data(n);
    blksize = gf_node_blksize(n);

    ces = &trig->ces;
    nitems = ces->nitems;
    tvm = &trig->tvm;

    if (trig->please_reset) {
        trig_state_reset(&tvm->istate);
        trig->please_reset = 0;
    }

    if (nitems == 0) return;

    pos = 0;

    /* clear output wires */
    for (s = 0; s < 8; s++) {
        if (!trig->wes[s].input) {
            trig->wes[s].nitems = 0;
        }
    }

    for(s = 0; s < blksize; s++) {
        if (pos >= nitems) break;

        if (s == ces->vals[pos]) {
            wire_evstack *wes;
            int w;
            wes = trig->wes;

            for (w = 0; w < 8; w++) {
                if (wes[w].input && wes[w].nitems > 0) {
                    float val;
                    val = wes[w].vals[pos];
                    trig_vm_wire_set(tvm, w, val);
                }
            }
            trig_vm_step(tvm);
            pos++;
        }
    }

    draw_position(trig, tvm->istate.pos);
    check_and_draw(trig);
}

9.3.3. Trigex Destroy

<<node_trigex_functions>>=
static void trigex_destroy(gf_node *node)
{
     gf_node_cables_free(node);
}

9.4. WIP Wire Get (trigwget)

This returns a wire. Should be called after trigex. The wire number is specified as an init-time constant, and should be a number between 1 and 8 (inclusive).

9.4.1. Trigwget Node Function

At init-time the wire used is used to specify which wire to choose from. Internally, the wires are addressed using zero indexed indices (0, 1, 2, 3, etc). However, the grid interface makes them 1-indexed (1, 2, 3, 4) because it works out better visually. To keep the end-user interface consistent, the wire id will be 1-indexed, then converted internally.

<<trig_function_declarations>>=
static int node_trigwget(gf_node *node,
                         page_trig_d *trig,
                         int wire);
<<trig_functions>>=
<<node_trigwget_functions>>
static int node_trigwget(gf_node *node,
                         page_trig_d *trig,
                         int wire)
{
    if (wire < 1 || wire > 8) {
        fprintf(stderr, "Invalid wire %d\n", wire);
        return GF_NOT_OK;
    }
    gf_node_cables_alloc(node, 1);
    gf_node_set_block(node, 0);
    gf_node_set_data(node, &trig->wes[wire - 1]);
    gf_node_set_destroy(node, trigwget_destroy);
    gf_node_set_compute(node, trigwget_compute);
    return GF_OK;
}

9.4.2. Trigwget Compute

Reading from a wire is a matter of parsing the event stack for that wire. If the event stack is empty, then the value is read from the VM wire directly.

<<node_trigwget_functions>>=
static void trigwget_compute(gf_node *n)
{
    int blksize;
    int s;
    clock_evstack *ces;
    wire_evstack *wes;
    int nitems;
    int pos;
    GFFLT val;
    gf_cable *out;

    wes = gf_node_get_data(n);
    blksize = gf_node_blksize(n);

    ces = wes->ces;
    nitems = ces->nitems;

    gf_node_get_cable(n, 0, &out);

    if (nitems == 0) {
        val = *wes->ref;
    } else {
        val = wes->last;
    }

    pos = 0;

    for(s = 0; s < blksize; s++) {
        if (pos < nitems) {
            if (s == ces->vals[pos]) {
                val = wes->vals[pos];
                pos++;
            }
        }

        gf_cable_set(out, s, val);
    }

    wes->last = val;
}

9.4.3. Trigwget Destroy

<<node_trigwget_functions>>=
static void trigwget_destroy(gf_node *node)
{
     gf_node_cables_free(node);
}

9.5. TODO Wire Set (trigwset)

Copies a signal to a wire. Copies the signal to event stacks, so should be called after trigclk and before trigex.

When wires are set, the event stack that corresponds to it is reset at the top of the block. If multiple signals write to the same wire, only the last signal gets chosen.

When a trigwset node is created, it makes a note in that wire's event stack that it is being used at an input signal. That way, the trigwset node is able to differentiate between output cables in the midst of being written to and input cables ready to be parsed. When this node is destroyed, it is unmarked.

9.6. WIP Trig Re-Execute (trigrex)

trigrex creates another program reader that can work concurrently with the main reader, but with a different starting position. Use this to have trig play multiple patterns simultaneously.

The trigrex node expects to be sometime called after the main trigex function.

9.6.1. Trigrex Node Function

The node function will allocate a new instance trig_state, and then store the starting position and trig_vm data.

<<trig_function_declarations>>=
static int node_trigrex(gf_node *node,
                        page_trig_d *trig,
                        int pos);
<<trig_functions>>=
<<node_trigrex_functions>>
static int node_trigrex(gf_node *node,
                        page_trig_d *trig,
                        int pos)
{
    trig_state *ts;
    int rc;
    void *ud;
    gf_patch *patch;

    rc = gf_node_get_patch(node, &patch);

    if (pos > 32 || pos < 1) {
        printf("trigrex: pos %d must be in range 1-32\n",
               pos);
        return GF_NOT_OK;
    }

    if (rc != GF_OK) return rc;

    rc = gf_memory_alloc(patch, sizeof(trig_state), &ud);
    ts = ud;

    trig_state_init(ts);
    trig_state_ud_set(ts, trig);
    trig_state_ipos(ts, pos);
    trig_state_reset(ts);

    gf_node_set_data(node, ts);
    gf_node_set_destroy(node, trigrex_destroy);
    gf_node_set_compute(node, trigrex_compute);
    return GF_OK;
}

9.6.2. Trigrex Compute

This behaves very similarly to trigex. It iterates through the blocks event stack, but instead of calling trig_vm_step, it calls trig_state_step.

<<node_trigrex_functions>>=
static void trigrex_compute(gf_node *n)
{
    int blksize;
    int s;
    page_trig_d *trig;
    clock_evstack *ces;
    int nitems;
    int pos;
    trig_vm *tvm;
    trig_state *ts;

    ts = gf_node_get_data(n);
    blksize = gf_node_blksize(n);

    trig = trig_state_ud_get(ts);
    ces = &trig->ces;
    nitems = ces->nitems;
    tvm = &trig->tvm;

    if (trig->please_reset) {
        trig_state_reset(ts);
    }

    if (nitems == 0) return;

    pos = 0;

    for(s = 0; s < blksize; s++) {
        if (pos >= nitems) break;

        if (s == ces->vals[pos]) {
            wire_evstack *wes;
            int w;

            wes = trig->wes;

            for (w = 0; w < 8; w++) {
                if (wes[w].input && wes[w].nitems > 0) {
                    float val;
                    val = wes[w].vals[pos];
                    trig_vm_wire_set(tvm, w, val);
                }
            }
            trig_vm_step_state(tvm, ts);
            pos++;
        }
    }

    draw_position(trig, ts->pos);
}

9.6.3. Trigrex Destroy

<<node_trigrex_functions>>=
static void trigrex_destroy(gf_node *node)
{
     void *ud;
     gf_patch *patch;
     int rc;

     rc = gf_node_get_patch(node, &patch);
     if (rc != GF_OK) return;

     ud = gf_node_get_data(node);
     gf_memory_free(patch, &ud);

     gf_node_cables_free(node);
}



prev | home | next