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
.
#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
#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
.
sk_core * sk_core_new(int sr);
void sk_core_del(sk_core *core);
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;
}
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
typedef struct sk_core sk_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.
void sk_core_compute(sk_core *core);
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.
size_t sk_core_computes(sk_core *core, SKFLT secs);
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
.
gf_patch * sk_core_patch(sk_core *core);
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.
size_t sk_core_seconds_to_blocks(sk_core *core, SKFLT secs);
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).
int sk_core_blkset(sk_core *core, int sz);
int sk_core_blkset(sk_core *core, int sz)
{
return gf_patch_blksize_set(core->patch, sz);
}
Stack getter
sk_stack* sk_core_stack(sk_core *core);
sk_stack* sk_core_stack(sk_core *core)
{
return &core->stack;
}
Dictionary Getter
sk_dict* sk_core_dict(sk_core *core);
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
.
void sk_core_srate_set(sk_core *core, int sr);
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.
typedef struct sk_stacklet sk_stacklet;
struct sk_stacklet {
int type;
SKFLT f;
void *ptr;
};
<<stack_struct>>
A stacklet is initialized with the function
sk_stacklet_init
.
void sk_stacklet_init(sk_stacklet *s);
void sk_stacklet_init(sk_stacklet *s)
{
s->type = SK_TYPE_NONE;
s->f = 0;
s->ptr = NULL;
}
SKFLT sk_stacklet_constant(sk_stacklet *s);
SKFLT sk_stacklet_constant(sk_stacklet *s)
{
return s->f;
}
An array of stacklets forms the foundation of a sk_stack
.
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.
#define SK_STACKSIZE 16
struct sk_stack {
sk_stacklet stack[SK_STACKSIZE];
int pos;
};
A stack is initialized with sk_stack_init
.
void sk_stack_init(sk_stack *s);
The position is set to be negative, indicating an empty stack.
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:
enum {
SK_TYPE_NONE,
SK_TYPE_CONSTANT,
SK_TYPE_CABLE,
SK_TYPE_TABLE,
SK_TYPE_GENERIC
};
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);
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.
int sk_stack_pop(sk_stack *stack, sk_stacklet **out);
int sk_stack_push(sk_stack *stack, sk_stacklet **out);
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;
}
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.
int sk_stack_peak(sk_stack *stack, sk_stacklet **out);
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
.
int sk_stack_dup(sk_stack *stack, sk_stacklet **out);
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.
int sk_core_dup(sk_core *core);
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
.
int sk_stack_drop(sk_stack *stack, sk_stacklet **out);
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.
int sk_core_drop(sk_core *core);
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
.
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.
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.
int sk_core_swap(sk_core *core);
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
int sk_core_stackpos(sk_core *core);
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
.
typedef struct {
char type;
union {
gf_cable *c;
SKFLT f;
} data;
} sk_param;
gf_cable* sk_param_cable(sk_param *p);
SKFLT sk_param_constant(sk_param *p);
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
.
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.
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
.
int sk_param_get_constant(sk_core *core, SKFLT *val);
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
.
int sk_param_get_cable(sk_core *core, sk_param *p);
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
.
int sk_param_set(sk_core *core,
gf_node *node,
sk_param *p,
int cid);
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
.
int sk_core_constant(sk_core *core, SKFLT x);
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
.
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.
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
int sk_core_generic_push(sk_core *core, void *ptr);
int sk_core_generic_pop(sk_core *core, void **ptr);
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;
}
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.
int sk_param_isconstant(sk_param *p);
int sk_param_iscable(sk_param *p);
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
.
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.
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.
void sk_register_entry_init(sk_register_entry *e);
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.
typedef struct sk_regtbl sk_regtbl;
#define SK_REGSIZE 16
struct sk_regtbl {
sk_register_entry r[SK_REGSIZE];
};
Registers are initialized with sk_regtbl_init
.
void sk_regtbl_init(sk_regtbl *rs);
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.
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);
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.
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;
}
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;
}
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
.
int sk_core_regmrk(sk_core *core, int pos);
int sk_register_mark(sk_regtbl *rt, int pos);
int sk_register_mark(sk_regtbl *rt, int pos)
{
if (pos < 0 || pos >= SK_REGSIZE) return 1;
rt->r[pos].flags |= 1;
return 0;
}
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
.
int sk_core_regclr(sk_core *core, int pos);
int sk_register_clear(sk_regtbl *rt, int pos);
int sk_register_clear(sk_regtbl *rt, int pos)
{
if (pos < 0 || pos >= SK_REGSIZE) return 1;
rt->r[pos].flags = 0;
return 0;
}
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.
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.
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.
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.
int sk_core_hold(sk_core *core);
int sk_core_unhold(sk_core *core);
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;
}
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.
typedef struct sk_table sk_table;
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.
int sk_core_table_new(sk_core *core, unsigned long sz);
int sk_core_table_init(sk_core *core, SKFLT *tab, unsigned long sz);
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;
}
void sk_table_init(sk_table *tab, SKFLT *data, unsigned long sz);
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
.
size_t sk_table_size(sk_table *t);
SKFLT* sk_table_data(sk_table *t);
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
.
int sk_core_table_push(sk_core *core, sk_table *tab);
int sk_core_table_pop(sk_core *core, sk_table **tab);
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;
}
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
.
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.
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;
}
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.
#define SK_ERROR_CHECK(rc) if (rc) return rc;
#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
.
void sk_core_srand(sk_core *core, unsigned long val);
void sk_core_srand(sk_core *core, unsigned long val)
{
core->rng = val;
}
A random number is generated with sk_core_rand
.
unsigned long sk_core_rand(sk_core *core);
This will return a value between 0 and SK_CORE_RANDMAX
.
#define SK_CORE_RANDMAX 2147483648
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.
SKFLT sk_core_randf(sk_core *core);
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
typedef struct sk_dict sk_dict;
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;
};
size_t sk_dict_sizeof(void);
size_t sk_dict_sizeof(void)
{
return sizeof(sk_dict);
}
Dictionary Hash Function
CJB hash function.
static int hash(const char *str, int sz, int mod);
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
void sk_dict_init(sk_dict *d);
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
int sk_dict_clean(sk_dict *d);
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.
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);
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;
}
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
.
int sk_core_append(sk_core *core,
const char *key,
int sz,
void *p,
void (*del)(void*));
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
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);
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;
}
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
.
int sk_core_lookup(sk_core *core,
const char *key,
int sz,
void **p);
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
int sk_dict_remove(sk_dict *d, const char *key, int sz);
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
.
int sk_core_remove(sk_core *core,
const char *key,
int sz);
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
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).
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.
int sk_core_append_table(sk_core *core,
const char *key,
int sz,
int tabsz);
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;
}