Core

Core

Overview

This document describes and outlines a small API for sndkit. It is primarily designed to be a small layer on top of Graforge, a library used to build up directed audio graphs. A large portion of the functionality is based on what is found in runt-graforge. Just simpler.

In addition to maintaining an instance of graforge, the sndkit API provides a stack and a register system. The stack is the primary means of sharing data between nodes. Registers are for more persitently used values.

Tangled files

core.h and core.c.

<<core.h>>=
#ifndef SKGF_H
#define SKGF_H

#ifndef SKFLT
#define SKFLT float
#endif

<<typedefs>>
#ifdef SK_CORE_PRIV
<<structs>>
<<core>>
#endif
<<macros>>
<<funcdefs>>

#ifdef __plan9__
#pragma incomplete sk_core
#pragma incomplete sk_stack
#pragma incomplete sk_regtbl
#pragma incomplete sk_table
#endif
#endif

<<core.c>>=
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "graforge.h"
#define SK_CORE_PRIV
#include "core.h"
<<static_funcdefs>>
<<enums>>
<<funcs>>

Engine Management

All this behavior is defined in a struct called sk_core.

new/del

Creating/freeing is done with sk_core_new and sk_core_del.

<<funcdefs>>=
sk_core * sk_core_new(int sr);
void sk_core_del(sk_core *core);

<<funcs>>=
sk_core * sk_core_new(int sr)
{
    sk_core *core;
    gf_patch *patch;

    core = malloc(sizeof(sk_core));
    core->patch = malloc(gf_patch_size());

    patch = core->patch;
    gf_patch_init(patch, 64);
    gf_patch_alloc(patch, 8, 10);
    gf_patch_srate_set(patch, sr);

    sk_stack_init(&core->stack);
    sk_regtbl_init(&core->regtbl);
    sk_dict_init(&core->dict);

    sk_core_srand(core, 0);
    return core;
}

<<funcs>>=
void sk_core_del(sk_core *core)
{
    if (core == NULL) return;

    gf_patch_destroy(core->patch);
    gf_patch_free_nodes(core->patch);
    free(core->patch);
    sk_dict_clean(&core->dict);
    free(core);
    core = NULL;
}

core struct

<<typedefs>>=
typedef struct sk_core sk_core;

<<core>>=
struct sk_core {
    gf_patch *patch;
    sk_stack stack;
    sk_regtbl regtbl;
    sk_dict dict;
    unsigned long rng;
};

computing a block of audio

A internal block of audio can be computed with sk_core_compute. Usually this size is 64 samples.

<<funcdefs>>=
void sk_core_compute(sk_core *core);

<<funcs>>=
void sk_core_compute(sk_core *core)
{
    gf_patch_compute(core->patch);
}

computing seconds of audio

sk_core_computes is like sk_core_compute, but computes an approximuate number of seconds of audio. This value gets rounded to the nearest block. The number of blocks rendered gets rendered.

<<funcdefs>>=
size_t sk_core_computes(sk_core *core, SKFLT secs);

<<funcs>>=
size_t sk_core_computes(sk_core *core, SKFLT secs)
{
    size_t nblocks, n;
    nblocks = sk_core_seconds_to_blocks(core, secs);

    for (n = 0; n < nblocks; n++) {
        sk_core_compute(core);
    }

    return nblocks;
}

patch getter

Building up nodes involves interacting with the graforge API. To get the top level struct of that opaquely, use sk_core_patch.

<<funcdefs>>=
gf_patch * sk_core_patch(sk_core *core);

<<funcs>>=
gf_patch * sk_core_patch(sk_core *core)
{
    return core->patch;
}

seconds to blocks

The function sk_core_seconds_to_blocks converts seconds to a number of render blocks.

<<funcdefs>>=
size_t sk_core_seconds_to_blocks(sk_core *core, SKFLT secs);

<<funcs>>=
size_t sk_core_seconds_to_blocks(sk_core *core, SKFLT secs)
{
    size_t nblocks;
    int sr;

    sr = gf_patch_srate_get(core->patch);
    nblocks = floor((sr * secs) / 64) + 1;

    return nblocks;
}

resizing the internal block size

Note that this can only be between 1 and the max block size (hard-coded to be 64).

<<funcdefs>>=
int sk_core_blkset(sk_core *core, int sz);

<<funcs>>=
int sk_core_blkset(sk_core *core, int sz)
{
    return gf_patch_blksize_set(core->patch, sz);
}

Stack getter

<<funcdefs>>=
sk_stack* sk_core_stack(sk_core *core);

<<funcs>>=
sk_stack* sk_core_stack(sk_core *core)
{
    return &core->stack;
}

Dictionary Getter

<<funcdefs>>=
sk_dict* sk_core_dict(sk_core *core);

<<funcs>>=
sk_dict* sk_core_dict(sk_core *core)
{
    return &core->dict;
}

setting the sample rate

Just a wrapper for the graforge patch gf_patch_srate_set.

<<funcdefs>>=
void sk_core_srate_set(sk_core *core, int sr);

<<funcs>>=
void sk_core_srate_set(sk_core *core, int sr)
{
    gf_patch_srate_set(core->patch, sr);
}

Stack

Core Data Types

An item on a stack is contained in a thing known as a stacklet. A stacklet has integer for a type flag, a floating point number for numerical values, and a generic pointer for C structures.

<<typedefs>>=
typedef struct sk_stacklet sk_stacklet;

<<structs>>=
struct sk_stacklet {
    int type;
    SKFLT f;
    void *ptr;
};
<<stack_struct>>

A stacklet is initialized with the function sk_stacklet_init.

<<funcdefs>>=
void sk_stacklet_init(sk_stacklet *s);

<<funcs>>=
void sk_stacklet_init(sk_stacklet *s)
{
    s->type = SK_TYPE_NONE;
    s->f = 0;
    s->ptr = NULL;
}

<<funcdefs>>=
SKFLT sk_stacklet_constant(sk_stacklet *s);

<<funcs>>=
SKFLT sk_stacklet_constant(sk_stacklet *s)
{
    return s->f;
}

An array of stacklets forms the foundation of a sk_stack.

<<typedefs>>=
typedef struct sk_stack sk_stack;

The stack will be hard coded to be 16.

An integer pos is used to keep track of position.

<<stack_struct>>=
#define SK_STACKSIZE 16
struct sk_stack {
    sk_stacklet stack[SK_STACKSIZE];
    int pos;
};

A stack is initialized with sk_stack_init.

<<funcdefs>>=
void sk_stack_init(sk_stack *s);

The position is set to be negative, indicating an empty stack.

<<funcs>>=
void sk_stack_init(sk_stack *s)
{
    int i;

    for (i = 0; i < SK_STACKSIZE; i++) {
        sk_stacklet_init(&s->stack[i]);
    }

    s->pos = -1;
}

Types

The typeflag currently supports the following types:

<<enums>>=
enum {
   SK_TYPE_NONE,
   SK_TYPE_CONSTANT,
   SK_TYPE_CABLE,
   SK_TYPE_TABLE,
   SK_TYPE_GENERIC
};

<<funcdefs>>=
int sk_stacklet_isnone(sk_stacklet *s);
int sk_stacklet_isconstant(sk_stacklet *s);
int sk_stacklet_iscable(sk_stacklet *s);
int sk_stacklet_istable(sk_stacklet *s);
int sk_stacklet_isgeneric(sk_stacklet *s);

<<funcs>>=
int sk_stacklet_isnone(sk_stacklet *s)
{
    return s->type == SK_TYPE_NONE;
}

int sk_stacklet_isconstant(sk_stacklet *s)
{
    return s->type == SK_TYPE_CONSTANT;
}

int sk_stacklet_iscable(sk_stacklet *s)
{
    return s->type == SK_TYPE_CABLE;
}

int sk_stacklet_istable(sk_stacklet *s)
{
    return s->type == SK_TYPE_TABLE;
}

int sk_stacklet_isgeneric(sk_stacklet *s)
{
    return s->type == SK_TYPE_GENERIC;
}

Push/Pop

Push and pop are the core operations for the stack. Both return non-zero values on error.

sk_stack_pop will pop a value off the stack and save it to the stacklet variable s.

sk_stack_push will push an initialized stacklet to the stack, and save that value to stacklet variable s to be filled with some item.

<<funcdefs>>=
int sk_stack_pop(sk_stack *stack, sk_stacklet **out);
int sk_stack_push(sk_stack *stack, sk_stacklet **out);

<<funcs>>=
int sk_stack_pop(sk_stack *stack, sk_stacklet **out)
{
    sk_stacklet *s;
    /* no items on stack */
    if (stack->pos < 0) return 1;

    /* stack overflow */
    if (stack->pos >= SK_STACKSIZE) return 2;

    s = &stack->stack[stack->pos];
    stack->pos--;

    *out = s;
    return 0;
}

<<funcs>>=
int sk_stack_push(sk_stack *stack, sk_stacklet **out)
{
    sk_stacklet *s;

    if (stack->pos >= (SK_STACKSIZE - 1)) return 1;
    stack->pos++;
    s = &stack->stack[stack->pos];

    sk_stacklet_init(s);
    *out = s;
    return 0;
}

Peak

The function sk_stack_peak will look at the last item on the stack, but not pop it off the stack.

<<funcdefs>>=
int sk_stack_peak(sk_stack *stack, sk_stacklet **out);

<<funcs>>=
int sk_stack_peak(sk_stack *stack, sk_stacklet **out)
{
    sk_stacklet *s;
    if (stack->pos < 0) return 1;
    if (stack->pos >= SK_STACKSIZE) return 2;

    s = &stack->stack[stack->pos];
    *out = s;
    return 0;
}

Dup

dup is an operation that duplicates an item on the stack.

The basic operation can be done with sk_stack_dup. The operation will store the duplicated stack item to out if the argument is not NULL.

<<funcdefs>>=
int sk_stack_dup(sk_stack *stack, sk_stacklet **out);

<<funcs>>=
int sk_stack_dup(sk_stack *stack, sk_stacklet **out)
{
    int rc;
    sk_stacklet *a, *b;

    rc = sk_stack_peak(stack, &a);
    SK_ERROR_CHECK(rc);

    rc = sk_stack_push(stack, &b);
    SK_ERROR_CHECK(rc);

    *b = *a;

    if (out != NULL) *out = b;

    return 0;
}


sk_core_dup will call sk_stack_dup on the internal stack, but also will call a dup operation on the graforge stack if the item is a graforge cable.

<<funcdefs>>=
int sk_core_dup(sk_core *core);

<<funcs>>=
int sk_core_dup(sk_core *core)
{
    sk_stacklet *s;
    int rc;

    rc = sk_stack_dup(&core->stack, &s);
    SK_ERROR_CHECK(rc);

    if (s->type == SK_TYPE_CABLE) {
        gf_stack *stack;
        stack = gf_patch_stack(core->patch);
        gf_stack_dup(stack);
    }

    return 0;
}

Drop

drop is an operation that drops an item on the stack.

The basic operation is done with sk_stack_drop.

The dropped value will be saved to out if out is not NULL.

<<funcdefs>>=
int sk_stack_drop(sk_stack *stack, sk_stacklet **out);

<<funcs>>=
int sk_stack_drop(sk_stack *stack, sk_stacklet **out)
{
    int rc;
    sk_stacklet *s;

    rc = sk_stack_pop(stack, &s);

    SK_ERROR_CHECK(rc);

    if (out != NULL) *out = s;
    return 0;
}

The function sk_core_drop performs a drop on the stack in the core struct. If the item is a graforge cable, it will also perform a drop on the graforge buffer stack.

<<funcdefs>>=
int sk_core_drop(sk_core *core);

<<funcs>>=
int sk_core_drop(sk_core *core)
{
    int rc;
    sk_stacklet *s;
    rc = sk_stack_drop(&core->stack, &s);

    SK_ERROR_CHECK(rc);

    if (s->type == SK_TYPE_CABLE) {
        gf_stack *stack;
        stack = gf_patch_stack(core->patch);
        gf_stack_pop(stack, NULL);
    }

    return 0;
}

Swap

swap will swap the positions of the last two items on the stack.

The basic operation is done with sk_stack_swap.

<<funcdefs>>=
int sk_stack_swap(sk_stack *stack,
                  sk_stacklet **s1,
                  sk_stacklet **s2);

A gentle reminder: the stack must have at least 2 items on the stack, meaning the index position must be at least 1.

<<funcs>>=
int sk_stack_swap(sk_stack *stack,
                  sk_stacklet **out1,
                  sk_stacklet **out2)
{
    sk_stacklet tmp;
    int pos;

    pos = stack->pos;
    if (pos < 1) return 1;


    tmp = stack->stack[pos];

    stack->stack[pos] = stack->stack[pos - 1];
    stack->stack[pos - 1] = tmp;

    if (out1 != NULL) *out1 = &stack->stack[pos - 1];
    if (out2 != NULL) *out2 = &stack->stack[pos];

    return 0;
}

The function sk_core_swap does a swap, and will also swap on the graforge buffer stack if both items are cables.

<<funcdefs>>=
int sk_core_swap(sk_core *core);

<<funcs>>=
int sk_core_swap(sk_core *core)
{
    int rc;
    sk_stacklet *s[2];
    rc = sk_stack_swap(&core->stack, &s[0], &s[1]);

    SK_ERROR_CHECK(rc);

    if (s[0]->type == SK_TYPE_CABLE && s[1]->type == SK_TYPE_CABLE) {
        gf_stack *stack;
        stack = gf_patch_stack(core->patch);
        gf_stack_swap(stack);
    }

    return 0;
}

Get Stack Position

<<funcdefs>>=
int sk_core_stackpos(sk_core *core);

<<funcs>>=
int sk_core_stackpos(sk_core *core)
{
    return core->stack.pos;
}

Parameters and Cables

sndkit_param is an abstraction used to deal with graforge cables, and is designed to link up with the sndkit stack and graforge nodes.

A sndkit parameter can either be a graforge cable from a node or a constant value. If it is cable, it will properly manage the buffer stack in graforge. If it is a constant, it will only manipulate the sndkit stack.

Struct

A parameter is stored in a struct called sk_param.

<<typedefs>>=
typedef struct {
    char type;
    union {
        gf_cable *c;
        SKFLT f;
    } data;
} sk_param;

<<funcdefs>>=
gf_cable* sk_param_cable(sk_param *p);
SKFLT sk_param_constant(sk_param *p);

<<funcs>>=
gf_cable* sk_param_cable(sk_param *p)
{
    return p->data.c;
}

SKFLT sk_param_constant(sk_param *p)
{
    return p->data.f;
}

Getting a Parameter

Get a parameter from the core stack via sk_param_get.

<<funcdefs>>=
int sk_param_get(sk_core *core, sk_param *p);

Getting a parameter is a matter of popping from the stack and checking the type. A constant will set the constant value and flag in the param struct. A cable will set the cable value and flag in the param struct, and will also pop from the buffer stack.

<<funcs>>=
int sk_param_get(sk_core *core, sk_param *p)
{
    sk_stack *stk;
    sk_stacklet *s;
    int rc;

    stk = &core->stack;

    rc = sk_stack_pop(stk, &s);
    SK_ERROR_CHECK(rc);

    if (s->type == SK_TYPE_CONSTANT) {
        p->type = 0;
        p->data.f = s->f;
    } else if (s->type == SK_TYPE_CABLE) {
        p->type = 1;
        p->data.c = (gf_cable *)s->ptr;
        gf_cable_pop(p->data.c);
    } else {
        /* Wrong type! */
        return 1;
    }

    return 0;
}

For situations where only constants are allowed, use sk_param_get_constant.

<<funcdefs>>=
int sk_param_get_constant(sk_core *core, SKFLT *val);

<<funcs>>=
int sk_param_get_constant(sk_core *core, SKFLT *val)
{
    sk_stack *stk;
    sk_stacklet *s;
    int rc;

    stk = &core->stack;

    rc = sk_stack_pop(stk, &s);
    SK_ERROR_CHECK(rc);

    if (s->type != SK_TYPE_CONSTANT) {
        /* Wrong type! */
        return 1;
    }

    *val = s->f;
    return 0;
}

For situations where only cables are allowed, use sk_param_get_cable.

<<funcdefs>>=
int sk_param_get_cable(sk_core *core, sk_param *p);

<<funcs>>=
int sk_param_get_cable(sk_core *core, sk_param *p)
{
    sk_stack *stk;
    sk_stacklet *s;
    int rc;

    stk = &core->stack;

    rc = sk_stack_pop(stk, &s);
    SK_ERROR_CHECK(rc);

    if (s->type == SK_TYPE_CABLE) {
        p->type = 1;
        p->data.c = (gf_cable *)s->ptr;
        gf_cable_pop(p->data.c);
    } else {
        /* Wrong type! */
        return 1;
    }

    return 0;
}

Setting a Parameter

Set a parameter with sk_param_set.

<<funcdefs>>=
int sk_param_set(sk_core *core,
                 gf_node *node,
                 sk_param *p,
                 int cid);

<<funcs>>=
int sk_param_set(sk_core *core,
                 gf_node *node,
                 sk_param *p,
                 int cid)
{
    gf_cable *c;

    gf_node_get_cable(node, cid, &c);
    if (p->type == 0) {
        gf_cable_set_value(c, p->data.f);
    } else {
        int rc;
        rc = gf_cable_connect(p->data.c, c);
        SK_GF_ERROR_CHECK(rc);
    }
    return 0;
}

Setting a parameter will properly assign the internal value to a cable of a node. This node's cable is referenced by its index position. A constant parameter will set the node cable as a consant. A cable parameter will be connected to the node cable.

Pushing Constants

Variables that do not change can be pushed to the stack as constants, using the function sk_core_constant.

<<funcdefs>>=
int sk_core_constant(sk_core *core, SKFLT x);

<<funcs>>=
int sk_core_constant(sk_core *core, SKFLT x)
{
    int rc;
    sk_stacklet *s;
    sk_stack *stk;

    stk = &core->stack;

    rc = sk_stack_push(stk, &s);
    SK_ERROR_CHECK(rc);

    s->type = SK_TYPE_CONSTANT;
    s->f = x;

    return 0;
}

Pushing Output

An signal cable from a node is pushed to the stack via sk_param_out.

<<funcdefs>>=
int sk_param_out(sk_core *core,
                 gf_node *node,
                 int cid);

Cables need to be pushed in the order they are created in the patch. The onus is on the developers of the node to make sure this is done properly. Don't worry, this is less tricky than it sounds. If done correctly, this process can be mostly automated or abstracted away.

<<funcs>>=
int sk_param_out(sk_core *core,
                 gf_node *node,
                 int cid)
{
    gf_cable *c;
    sk_stacklet *s;
    sk_stack *stk;
    int rc;

    stk = &core->stack;

    rc = sk_stack_push(stk, &s);
    SK_ERROR_CHECK(rc);
    rc = gf_node_get_cable(node, cid, &c);
    SK_GF_ERROR_CHECK(rc);

    s->type = SK_TYPE_CABLE;
    s->ptr = c;

    return 0;
}

sk_param_out will take an output cable of a node (referenced by index), and push it onto the sndkit stack. It will also push the cable's buffer onto the stack.

Pushing/Popping Generic Pointers

<<funcdefs>>=
int sk_core_generic_push(sk_core *core, void *ptr);
int sk_core_generic_pop(sk_core *core, void **ptr);

<<funcs>>=
int sk_core_generic_push(sk_core *core, void *ptr)
{
    int rc;
    sk_stacklet *s;

    rc = sk_stack_push(&core->stack, &s);
    SK_ERROR_CHECK(rc);

    s->type = SK_TYPE_GENERIC;
    s->ptr = ptr;

    return rc;
}

<<funcs>>=
int sk_core_generic_pop(sk_core *core, void **ptr)
{
    int rc;
    sk_stacklet *s;

    rc = sk_stack_pop(&core->stack, &s);

    SK_ERROR_CHECK(rc);

    if (s->type != SK_TYPE_GENERIC) {
        return 1;
    }

    if (ptr != NULL) *ptr = s->ptr;

    return rc;
}

Checking parameter types

sk_param_isconstant will check if a parameter is a consant. sk_param_iscable will check if a parameter is a cable.

<<funcdefs>>=
int sk_param_isconstant(sk_param *p);
int sk_param_iscable(sk_param *p);

<<funcs>>=
int sk_param_isconstant(sk_param *p)
{
    return p->type == 0;
}

int sk_param_iscable(sk_param *p)
{
    return p->type == 1;
}

Registers

A register interface is used alongside the stack interface to store and retrieve data. A value stored in a register can be directly referenced by an id value.

Registers are very useful for storing data that is used more than once throughout the patch, or for situations where using stack operations to manipulate the day becomes tedious. Examples of this include cables containing clock signals and lookup tables.

structs

A single register entry is encapsulated in a struct called sk_register_entry.

<<typedefs>>=
typedef struct sk_register_entry sk_register_entry;

A register entry contains an sk_stacklet as well as a flag to indicate the current state of the register.

<<structs>>=
struct sk_register_entry {
    sk_stacklet data;
    int flags;
};

A register is initialized with sk_register_entry_init, which will initialize the stacklet and zero out the flags.

<<funcdefs>>=
void sk_register_entry_init(sk_register_entry *e);

<<funcs>>=
void sk_register_entry_init(sk_register_entry *e)
{
    sk_stacklet_init(&e->data);
    e->flags = 0;
}

A register collection is encapsulated in a struct called sk_regtbl, and is a fixed array of sk_register_entry values. The size is defined via a macro.

<<typedefs>>=
typedef struct sk_regtbl sk_regtbl;

<<structs>>=
#define SK_REGSIZE 16
struct sk_regtbl {
    sk_register_entry r[SK_REGSIZE];
};

Registers are initialized with sk_regtbl_init.

<<funcdefs>>=
void sk_regtbl_init(sk_regtbl *rs);

<<funcs>>=
void sk_regtbl_init(sk_regtbl *rs)
{
    int i;

    for (i = 0; i < SK_REGSIZE; i++) {
        sk_register_entry_init(&rs->r[i]);
    }
}

regset/regget

Setting/getting values are done with sk_core_regget and sk_core_regset, making calls to the underlying functions sk_register_set and sk_register_get.

Values are assumed to be encapsulated in a sk_stacklet, and registers are addressed by id.

Will return a non-zero value on error.

<<funcdefs>>=
int sk_core_regget(sk_core *core, int pos);
int sk_register_get(sk_regtbl *rt, int pos, sk_stacklet *s);
int sk_core_regset(sk_core *core, int pos);
int sk_register_set(sk_regtbl *rt, int pos, sk_stacklet *s);

<<funcs>>=
int sk_register_get(sk_regtbl *rt, int pos, sk_stacklet *s)
{
    if (pos < 0 || pos >= SK_REGSIZE) return 1;

    *s = rt->r[pos].data;
    return 0;
}

Things get a bit more involved the item in the register is a graforge cable. This requires pushing the buffer contained inside of the cable back onto the buffer stack.

<<funcs>>=
int sk_core_regget(sk_core *core, int pos)
{
    int rc;
    sk_stacklet *s;

    rc = sk_stack_push(&core->stack, &s);
    SK_ERROR_CHECK(rc);
    rc = sk_register_get(&core->regtbl, pos, s);
    SK_ERROR_CHECK(rc);

    /* also push to buffer stack if cable */
    if (s->type == SK_TYPE_CABLE) {
        gf_cable *c;
        gf_buffer *b;
        gf_stack *bstack;
        c = (gf_cable *) s->ptr;
        b = gf_cable_get_buffer(c);
        bstack = gf_patch_stack(core->patch);
        gf_stack_push_buffer(bstack, b);
    }

    return 0;
}

<<funcs>>=
int sk_register_set(sk_regtbl *rt, int pos, sk_stacklet *s)
{
    if (pos < 0 || pos >= SK_REGSIZE) return 1;

    rt->r[pos].data = *s;
    return 0;
}

<<funcs>>=
int sk_core_regset(sk_core *core, int pos)
{
    int rc;
    sk_stacklet *s;

    rc = sk_stack_pop(&core->stack, &s);
    SK_ERROR_CHECK(rc);
    rc = sk_register_set(&core->regtbl, pos, s);
    SK_ERROR_CHECK(rc);

    return 0;
}

regmrk

sk_core_regmrk, marks a register as being used. this makes an underlying call to sk_register_mark.

<<funcdefs>>=
int sk_core_regmrk(sk_core *core, int pos);
int sk_register_mark(sk_regtbl *rt, int pos);

<<funcs>>=
int sk_register_mark(sk_regtbl *rt, int pos)
{
    if (pos < 0 || pos >= SK_REGSIZE) return 1;

    rt->r[pos].flags |= 1;
    return 0;
}

<<funcs>>=
int sk_core_regmrk(sk_core *core, int pos)
{
    return sk_register_mark(&core->regtbl, pos);
}

regclr

sk_core_regclr clears the register, making it free to be claimed. This makes an underlying call to sk_register_clear.

<<funcdefs>>=
int sk_core_regclr(sk_core *core, int pos);
int sk_register_clear(sk_regtbl *rt, int pos);

<<funcs>>=
int sk_register_clear(sk_regtbl *rt, int pos)
{
    if (pos < 0 || pos >= SK_REGSIZE) return 1;

    rt->r[pos].flags = 0;
    return 0;
}

<<funcs>>=
int sk_core_regclr(sk_core *core, int pos)
{
    return sk_register_clear(&core->regtbl, pos);
}

regnxt

sk_core_regnxt returns the next free register, which makes an underlying call to sk_register_nextfree.

start indicates which register position to start at. Leave this to be 0 if there is no preference.

<<funcdefs>>=
int sk_core_regnxt(sk_core *core, int start, int *pos);
int sk_register_nxtfree(sk_regtbl *rt, int start);

sk_register_nextfree will iterate through the registers until it finds one that is free. it will return the id of this register.

<<funcs>>=
int sk_register_nxtfree(sk_regtbl *rt, int start)
{
    int pos;
    int i;
    sk_register_entry *reg;

    reg = rt->r;

    if (start < 0 || start >= SK_REGSIZE) start = 0;

    pos = start;

    for (i = 0; i < SK_REGSIZE; i++) {
        if (!(reg[pos].flags & 1)) return pos;
        pos = (pos + 1) % SK_REGSIZE;
    }

    return -1;
}

sk_core_regnxt returns a non-zero error code if there are no available registers. This is done to make it play better with the SK_ERROR_CHECK paradigm.

<<funcs>>=
int sk_core_regnxt(sk_core *core, int start, int *pos)
{
    *pos = sk_register_nxtfree(&core->regtbl, start);

    if (*pos < 0) return 1;

    return 0;
}

Buffer Operations

Graforge works by reading and writing to fixed-size blocks of samples known as buffers. Buffers are manipulated using a stack, and are managed/queried from a pool.

After being used by a node, buffers are usually immediately returned to the buffer pool to be re-used. But, sometimes signals stored in those buffers need to be saved for later on in the patch. In order to do this, one must explicitely hold the buffer and then unhold it when it is done being used. If buffers are not unheld, it creates a sort of resource leak which will dry up the buffer pool and cause graforge to lock up.

Holding and unholding buffers can be done with sk_core_hold and sk_core_unhold. These will peak at the last item on the stack, presumably a cable, and it will hold the buffer contained inside of it. In the case of sk_core_unhold, the item will be popped from the stack.

If something goes wrong, a non-zero value is returned.

<<funcdefs>>=
int sk_core_hold(sk_core *core);
int sk_core_unhold(sk_core *core);

<<funcs>>=
int sk_core_hold(sk_core *core)
{
    int rc;
    sk_stacklet *s;
    rc = sk_stack_peak(&core->stack, &s);
    SK_ERROR_CHECK(rc);

    if (s->type != SK_TYPE_CABLE) {
        /* Wrong type, kiddo */
        return 2;
    }

    rc = gf_patch_bhold(core->patch, NULL);
    SK_GF_ERROR_CHECK(rc);

    return 0;
}

<<funcs>>=
int sk_core_unhold(sk_core *core)
{
    sk_param cable;
    gf_buffer *buf;
    gf_cable *c;
    int rc;

    rc = sk_param_get(core, &cable);
    SK_ERROR_CHECK(rc);

    if (cable.type != 1) {
        /* Your princess is is another castle. */
        return 2;
    }

    c = cable.data.c;
    buf = gf_cable_get_buffer(c);
    rc = gf_patch_bunhold(core->patch, buf);
    SK_GF_ERROR_CHECK(rc);

    return 0;
}

Tables

A small abstraction for dealing with tables that are managed by graforge.

Table Struct

a struct called sk_table. Contains a SKFLT array and it's size.

<<typedefs>>=
typedef struct sk_table sk_table;

<<structs>>=
struct sk_table {
    SKFLT *tab;
    unsigned long sz;
};

Creating a New Table

Called sk_core_table_new. Allocates a new table and wraps it around a graforge pointer. This table will be automatically freed when the patch is freed.

The table itself is then pushed to the stack.

<<funcdefs>>=
int sk_core_table_new(sk_core *core, unsigned long sz);
int sk_core_table_init(sk_core *core, SKFLT *tab, unsigned long sz);

<<funcs>>=
static void free_table(gf_pointer *p)
{
    sk_table *tab;

    tab = gf_pointer_data(p);

    free(tab->tab);
    free(tab);
}

int sk_core_table_new(sk_core *core, unsigned long sz)
{
    sk_table *tab;
    int rc;
    SKFLT *data;

    tab = malloc(sizeof(sk_table));

    if (tab == NULL) return 1;
    data = malloc(sz * sizeof(SKFLT));
    memset(data, 0, sz * sizeof(SKFLT));

    sk_table_init(tab, data, sz);

    gf_patch_append_userdata(core->patch, free_table, tab);

    rc = sk_core_table_push(core, tab);
    SK_ERROR_CHECK(rc);

    return 0;
}

<<funcdefs>>=
void sk_table_init(sk_table *tab, SKFLT *data, unsigned long sz);

<<funcs>>=
void sk_table_init(sk_table *tab, SKFLT *data, unsigned long sz)
{
    tab->tab = data;
    tab->sz = sz;
}

Getting Table Data and Size

Getter functions sk_table_size and sk_table_data.

<<funcdefs>>=
size_t sk_table_size(sk_table *t);
SKFLT* sk_table_data(sk_table *t);

<<funcs>>=
size_t sk_table_size(sk_table *t)
{
    return t->sz;
}

SKFLT* sk_table_data(sk_table *t)
{
    return t->tab;
}

Pushing/Popping Table

sk_core_pop_table and sk_core_push_table.

<<funcdefs>>=
int sk_core_table_push(sk_core *core, sk_table *tab);
int sk_core_table_pop(sk_core *core, sk_table **tab);

<<funcs>>=
int sk_core_table_push(sk_core *core, sk_table *tab)
{
    int rc;
    sk_stacklet *s;

    rc = sk_stack_push(&core->stack, &s);

    SK_ERROR_CHECK(rc);

    s->type = SK_TYPE_TABLE;
    s->ptr = tab;

    return rc;
}

<<funcs>>=
int sk_core_table_pop(sk_core *core, sk_table **tab)
{
    int rc;
    sk_stacklet *s;

    rc = sk_stack_pop(&core->stack, &s);

    SK_ERROR_CHECK(rc);

    if (s->type != SK_TYPE_TABLE) {
        fprintf(stderr, "uh oh type is %d\n", s->type);
        return 1;
    }

    *tab = (sk_table *)s->ptr;

    return rc;
}

Dumping Raw Table Information

Raw table data can be written to disk format using sk_table_dump.

The function sk_core_tabdump will pop the table off the stack and then call sk_table_dump.

<<funcdefs>>=
int sk_table_dump(sk_table *tab, const char *filename);
int sk_core_tabdump(sk_core *core, const char *filename);

Note that tables are representing in floating-point format, and will use the endian encoding used by the OS when writing/reading from disk.


<<funcs>>=
int sk_table_dump(sk_table *tab, const char *filename)
{
    FILE *fp;

    fp = fopen(filename, "w");

    if (fp == NULL) return 1;

    fwrite(tab->tab, sizeof(SKFLT), tab->sz, fp);

    fclose(fp);

    return 0;
}

<<funcs>>=
int sk_core_tabdump(sk_core *core, const char *filename)
{
    int rc;
    sk_table *tab;

    rc = sk_core_table_pop(core, &tab);
    SK_ERROR_CHECK(rc);

    return sk_table_dump(tab, filename);
}

Error Checking

SK_ERROR_CHECK is a convenient macro used that will check an error code and exit if it is non-zero.

<<macros>>=
#define SK_ERROR_CHECK(rc) if (rc) return rc;

<<macros>>=
#define SK_GF_ERROR_CHECK(rc) if(rc != GF_OK) {\
    fprintf(stderr, "Error: %s\n", gf_error(rc));\
    return 1;\
}

Random Number Generator

A simple random number generator (RNG) is included with the core API, based on the LCG found in Soundpipe.

Seed the RNG with sk_core_srand.

<<funcdefs>>=
void sk_core_srand(sk_core *core, unsigned long val);

<<funcs>>=
void sk_core_srand(sk_core *core, unsigned long val)
{
    core->rng = val;
}

A random number is generated with sk_core_rand.

<<funcdefs>>=
unsigned long sk_core_rand(sk_core *core);

This will return a value between 0 and SK_CORE_RANDMAX.

<<macros>>=
#define SK_CORE_RANDMAX 2147483648

<<funcs>>=
unsigned long sk_core_rand(sk_core *core)
{
    core->rng = (1103515245 * core->rng + 12345) % SK_CORE_RANDMAX;
    return core->rng;
}

sk_core_randf returns a random number between 0 and 1.

<<funcdefs>>=
SKFLT sk_core_randf(sk_core *core);

<<funcs>>=
SKFLT sk_core_randf(sk_core *core)
{
    return (SKFLT)sk_core_rand(core) / SK_CORE_RANDMAX;
}

Data Dictionary

The Data Dictionary is an implementation of a small and simple hash table for storing allocated C data such as structs.

This interface is designed to make it easier to create bindings in high level languages. Because it's a dictionary, it allows objects to be referenced as strings, rather than trying to wrap the pointer data more directly. Also, memory is explicitely managed within the dictionary rather than at the whims of the garbage collection. Every word entry has a user-defined destructor callback that gets called when an entry is deleted.

The kinds of data expected to be managed are expected to be more persistent during the runtime of the program.

Struct

<<typedefs>>=
typedef struct sk_dict sk_dict;

<<structs>>=
struct dict_entry {
    char *key;
    int sz;
    /* void *val; */
    sk_stacklet s;
    void (*del)(void*);
    struct dict_entry *nxt;
};

struct sk_dict {
    struct dict_entry *ent[64];
    int sz;
};

<<funcdefs>>=
size_t sk_dict_sizeof(void);

<<funcs>>=
size_t sk_dict_sizeof(void)
{
    return sizeof(sk_dict);
}

Dictionary Hash Function

CJB hash function.

<<static_funcdefs>>=
static int hash(const char *str, int sz, int mod);

<<funcs>>=
static int hash(const char *str, int sz, int mod)
{
    unsigned long h;
    int i;

    h = 5381;
    i = 0;

    for(i = 0; i < sz; i++) {
        h = ((h << 5) + h) ^ str[i];
        h %= 0x7FFFFFFF;
    }

    return h % mod;
}

Initializing the Dictionary

<<funcdefs>>=
void sk_dict_init(sk_dict *d);

<<funcs>>=
void sk_dict_init(sk_dict *d)
{
    int n;

    for (n = 0; n < 64; n++) {
        d->ent[n] = NULL;
    }

    d->sz = 0;
}

Freeing the dictionary

<<funcdefs>>=
int sk_dict_clean(sk_dict *d);

<<funcs>>=
int sk_dict_clean(sk_dict *d)
{
    int e;

    for (e = 0; e < 64; e++) {
        struct dict_entry *ent, *nxt;
        ent = d->ent[e];

        while (ent != NULL) {
            nxt = ent->nxt;
            if (ent->del != NULL) ent->del(ent->s.ptr);
            free(ent->key);
            free(ent);
            ent = nxt;
        }
    }

    return 0;
}

Append An Entry

sk_dict_append is the main way to add pointers to the dictionary.

The function sk_dict_sappend will do the same thing, and return the underlying stacklet inside of the allocated entry so it can be modified with typeflags or pushed onto the stack.

<<funcdefs>>=
int sk_dict_append(sk_dict *d,
                   const char *key,
                   int sz,
                   void *p,
                   void (*del)(void*));

int sk_dict_sappend(sk_dict *d,
                    const char *key,
                    int sz,
                    void *p,
                    void (*del)(void*),
                    sk_stacklet **s);

<<funcs>>=
int sk_dict_sappend(sk_dict *d,
                    const char *key,
                    int sz,
                    void *p,
                    void (*del)(void*),
                    sk_stacklet **s)
{
    int pos;
    struct dict_entry *ent;
    int i;

    pos = hash(key, sz, 64);

    ent = d->ent[pos];

    while (ent != NULL) {
        if (ent->sz == sz && !strcmp(key, ent->key)) {
            return 1;
        }
        ent = ent->nxt;
    }

    ent = malloc(sizeof(struct dict_entry));
    ent->key = malloc(sz + 1);
    ent->sz = sz;

    for (i = 0; i < sz; i++) ent->key[i] = key[i];
    ent->key[sz] = '\0';

    sk_stacklet_init(&ent->s);
    ent->s.type = SK_TYPE_GENERIC;
    ent->s.ptr = p;
    ent->del = del;
    ent->nxt = d->ent[pos];
    d->ent[pos] = ent;

    if (s != NULL) *s = &ent->s;

    return 0;
}

<<funcs>>=
int sk_dict_append(sk_dict *d,
                   const char *key,
                   int sz,
                   void *p,
                   void (*del)(void*))
{
    return sk_dict_sappend(d, key, sz, p, del, NULL);
}

The core interface can do this with sk_core_append.

<<funcdefs>>=
int sk_core_append(sk_core *core,
                   const char *key,
                   int sz,
                   void *p,
                   void (*del)(void*));

<<funcs>>=
int sk_core_append(sk_core *core,
                   const char *key,
                   int sz,
                   void *p,
                   void (*del)(void*))
{
    return sk_dict_append(&core->dict, key, sz, p, del);
}

Lookup An Entry

<<funcdefs>>=
int sk_dict_lookup_stacklet(sk_dict *d,
                            const char *key,
                            int sz,
                            sk_stacklet **s);

int sk_dict_lookup(sk_dict *d,
                   const char *key,
                   int sz,
                   void **p);

<<funcs>>=
int sk_dict_lookup_stacklet(sk_dict *d,
                            const char *key,
                            int sz,
                            sk_stacklet **s)
{
    int pos;
    struct dict_entry *ent;

    pos = hash(key, sz, 64);

    ent = d->ent[pos];

    while (ent != NULL) {
        if (ent->sz == sz && !strncmp(key, ent->key, sz)) {
            *s = &ent->s;
            return 0;
        }
        ent = ent->nxt;
    }

    return 1;
}

<<funcs>>=
int sk_dict_lookup(sk_dict *d,
                   const char *key,
                   int sz,
                   void **p)
{
    int rc;
    sk_stacklet *s;

    rc = sk_dict_lookup_stacklet(d, key, sz, &s);

    if (rc) return rc;

    *p = s->ptr;
    return 0;
}

Do lookup on the core internal dictionary with sk_core_lookup.

<<funcdefs>>=
int sk_core_lookup(sk_core *core,
                   const char *key,
                   int sz,
                   void **p);

<<funcs>>=
int sk_core_lookup(sk_core *core,
                   const char *key,
                   int sz,
                   void **p)
{
    return sk_dict_lookup(&core->dict, key, sz, p);
}

Remove An Entry

<<funcdefs>>=
int sk_dict_remove(sk_dict *d, const char *key, int sz);

<<funcs>>=
int sk_dict_remove(sk_dict *d, const char *key, int sz)
{
    int pos;
    struct dict_entry *ent, *prv;

    pos = hash(key, sz, 64);

    ent = d->ent[pos];

    prv = NULL;

    while (ent != NULL) {
        if (ent->sz == sz && !strncmp(key, ent->key, sz)) {

            if (prv == NULL) {
                d->ent[pos] = ent->nxt;
            } else {
                prv->nxt = ent->nxt;
            }
            if (ent->del) ent->del(ent->s.ptr);
            free(ent->key);
            free(ent);
            return 0;
        }
        prv = ent;
        ent = ent->nxt;
    }
    return 1;
}

Do lookup on the core internal dictionary with sk_core_remove.

<<funcdefs>>=
int sk_core_remove(sk_core *core,
                   const char *key,
                   int sz);

<<funcs>>=
int sk_core_remove(sk_core *core,
                   const char *key,
                   int sz)
{
    return sk_dict_remove(&core->dict, key, sz);
}

Grab: Lookup and Push onto the Stack

<<funcdefs>>=
int sk_core_grab(sk_core *core,
                 const char *key,
                 int sz);

Entries contain their own instance of a stacklet, which contains a pointer value and a type flag. The stacklet gets copied to the stack, preserving the underlying type flag. (AKA if something's stored as a table, it will be pushed as a table).

<<funcs>>=
int sk_core_grab(sk_core *core,
                 const char *key,
                 int sz)
{
    int rc;
    sk_stacklet *s;
    sk_stacklet *out;

    rc = sk_dict_lookup_stacklet(&core->dict, key, sz, &s);

    if (rc) return 1;

    rc = sk_stack_push(&core->stack, &out);

    if (rc) return 2;

    *out = *s;

    return 0;
}

Append a Table

sk_core_append_table will allocate append a table to the dictionary.

<<funcdefs>>=
int sk_core_append_table(sk_core *core,
                         const char *key,
                         int sz,
                         int tabsz);

<<funcs>>=
static void deltab(void *ptr)
{
    sk_table *tab;

    tab = ptr;

    free(tab->tab);
    free(tab);
}

int sk_core_append_table(sk_core *core,
                         const char *key,
                         int sz,
                         int tabsz)
{
    int rc;
    sk_table *tab;
    sk_stacklet *s;
    sk_stacklet *out;

    tab = calloc(1, sizeof(sk_table));

    tab->tab = calloc(tabsz, sizeof(SKFLT));
    tab->sz = tabsz;

    s = NULL;
    rc = sk_dict_sappend(&core->dict, key, sz, tab, deltab, &s);
    SK_ERROR_CHECK(rc);

    out = NULL;

    s->type = SK_TYPE_TABLE;

    rc = sk_stack_push(&core->stack, &out);
    SK_ERROR_CHECK(rc);

    *out = *s;

    return 0;
}