6. Graforge and Soundpipe
Graforge is the underlying audio engine used for synthesis. It is controlled via the Runt programming language.
6.1. Graforge Top
The top-level data struct for graforge is gf_patch
.
gf_patch *patch;
This patch is required by anything that wishes to create a
node in the audio graph. It can be retrieved using the
function monolith_graforge_get
.
gf_patch * monolith_graforge_get(monolith_d *m);
gf_patch * monolith_graforge_get(monolith_d *m)
{
return m->patch;
}
Graforge data can be returned as an unsafe type for use in
Janet via monolith/graforge
. From there, it can be used
with the Graforge Janet API.
{
"monolith/graforge",
j_graforge,
"Returns graforge data."
},
static Janet j_graforge(int32_t argc, Janet *argv)
{
monolith_d *m;
m = monolith_data_get();
return janet_wrap_pointer(monolith_graforge_get(m));
}
Graforge can be returned in scheme with monolith:graforge
.
{"monolith:graforge", pp_graforge, 0, 0, {___,___,___}},
static cell pp_graforge(cell p) {
monolith_d *m;
m = monolith_data_get();
return s9_make_pointer(monolith_graforge_get(m));
}
Soundpipe, a DSP library, is the main synthesis library used
by inside of graforge. It has a core data type called
sp_data
.
sp_data *sp;
The soundpipe and graforge structs are allocated and
initialized at init-time, and freed at cleanup.
The samplerate is set to a default value of
44100. This can be set again with monolith_sr_set
.
m->patch = malloc(gf_patch_size());
sp_create(&m->sp);
monolith_sr_set(m, 44100);
gf_patch_init(m->patch, MONOLITH_BLKSIZE);
gf_patch_alloc(m->patch, 8, 10);
gf_patch_srate_set(m->patch, m->sr);
gf_patch_data_set(m->patch, m->sp);
gf_patch_destroy(m->patch);
gf_patch_free_nodes(m->patch);
sp_destroy(&m->sp);
free(m->patch);
6.2. samplerate
The samplerate is stored in a variable called sr
.
unsigned int sr;
void monolith_sr_set(monolith_d *m, unsigned int sr);
unsigned int monolith_sr_get(monolith_d *sr);
void monolith_sr_set(monolith_d *m, unsigned int sr)
{
m->sr = sr;
gf_patch_srate_set(m->patch, m->sr);
m->sp->sr = m->sr;
}
unsigned int monolith_sr_get(monolith_d *m)
{
return m->sr;
}
{"monolith:srate-set", pp_srate_set, 1, 1, {INT,___,___}},
static cell pp_srate_set(cell x)
{
int sr;
sr = integer_value(NULL, car(x));
monolith_sr_set(monolith_data_get(), sr);
return UNSPECIFIC;
}
The function monolith:pw-srate-get
gets the samplerate
from a graforge pointer (presumably via monolith:graforge
).
This is mostly for testing purposes.
{"monolith:pw-srate-get", pp_gf_srate_get, 1, 1, {S9_T_POINTER,___,___}},
static cell pp_gf_srate_get(cell x)
{
int sr;
gf_patch *patch;
patch = s9_to_pointer(car(x));
sr = gf_patch_srate_get(patch);
return s9_make_integer(sr);
}
6.3. Seeding
6.3.1. Seeding In C
Soundpipe has an internal RNG that can be seeded with
monolith_seed
. This will also set the system RNG seed as
well.
void monolith_seed(monolith_d *m, unsigned int seed);
void monolith_seed(monolith_d *m, unsigned int seed)
{
sp_srand(m->sp, seed);
srand(seed);
}
6.3.2. Seeding in Scheme
Seeding in scheme can be done with the functin
monolith:seed
.
{"monolith:seed", pp_seed, 1, 1, {INT,___,___}},
static cell pp_seed(cell x)
{
unsigned int seed;
monolith_d *m;
m = monolith_data_get();
seed = integer_value(NULL, car(x));
monolith_seed(m, seed);
return UNSPECIFIC;
}
6.4. ftables
Soundpipe ftables can be stored inside of the monolith dictionary, rather than being allocated inside of a patch instance. This has advantage of remaining persistant between compilations.
6.4.1. creating an ftable
the function monolith_ftbl_create
will allocate +
initialize a soundpipe ftable and store it in a dictionary.
The soundpipe header guard is used for this declaration due
to the fact that sp_ftbl
is used. soundpipe needs to be
included prior to monolith in order for this to be declared.
6.4.1.1. creating an ftable in C
#ifdef SOUNDPIPE_H
int monolith_ftbl_create(monolith_d *m,
const char *key,
size_t keylen,
sp_ftbl **pft,
size_t size);
#endif
int monolith_ftbl_create(monolith_d *m,
const char *key,
size_t keylen,
sp_ftbl **pft,
size_t size)
{
monolith_dict_entry *ent;
int rc;
sp_ftbl *ft;
sp_data *sp;
rc = monolith_dict_newentry(&m->dict, &ent,
key, keylen);
if (rc != MONOLITH_OK) {
fprintf(stderr, "Unable to create entry ");
fwrite(key, 1, keylen, stderr);
fprintf(stderr, "\n");
return MONOLITH_NOTOK;
}
sp = m->sp;
sp_ftbl_create(sp, &ft, size);
ent->type = MONOLITH_ENTRY_FTBL;
ent->ud = ft;
if (pft != NULL) *pft = ft;
return MONOLITH_OK;
}
6.4.1.2. ftable creation in scheme (monolith:ftbl-create)
{"monolith:ftbl-create", pp_ftbl_create, 2, 2, {STR,INT,___}},
static cell pp_ftbl_create(cell x)
{
const char *key;
int size;
monolith_d *m;
m = monolith_data_get();
key = s9_string(car(x));
x = cdr(x);
size = integer_value(NULL, car(x));
monolith_ftbl_create(m, key, strlen(key), NULL, size);
return UNSPECIFIC;
}
6.4.1.3. ftable creation in janet (monolith/ftbl-create)
{
"monolith/ftbl-create",
j_ftbl_create,
"creates an ftable inside of monolith dictionary\n"
},
static Janet j_ftbl_create(int32_t argc, Janet *argv)
{
const char *key;
int size;
monolith_d *m;
janet_fixarity(argc, 2);
m = monolith_data_get();
key = (const char *)janet_unwrap_string(argv[0]);
size = janet_unwrap_integer(argv[1]);
monolith_ftbl_create(m, key, strlen(key), NULL, size);
return janet_wrap_nil();
}
6.4.2. destroying the ftable
This ftable is destroyed when the entry list is destroyed
inside of monolith_dict_entrylist_free
.
6.4.3. accessing an ftable
6.4.3.1. monolith_ftbl_get
This will attempt to lookup an ftable and store it in an
sp_ftbl
pointer.
#ifdef SOUNDPIPE_H
int monolith_ftbl_get(monolith_d *m,
const char *key,
size_t len,
sp_ftbl **pft);
#endif
int monolith_ftbl_get(monolith_d *m,
const char *key,
size_t len,
sp_ftbl **pft)
{
monolith_dict_entry *ent;
int rc;
ent = NULL;
rc = monolith_dict_find(&m->dict, &ent, key, len);
if (rc != MONOLITH_OK) {
return rc;
}
if (ent->type != MONOLITH_ENTRY_FTBL) {
return MONOLITH_NOTOK;
}
if (pft != NULL) *pft = ent->ud;
return MONOLITH_OK;
}
6.4.3.2. DONE monft
CLOSED: [2020-01-05 Sun 22:39]
All this word will need is the string storing the ftable. It
will call monolith_ftbl_get
and then push it to the runt
stack.
monolith_runt_keyword(m, "monft", 5, rproc_monft, m);
static int rproc_monft(runt_vm *vm, runt_ptr p);
static int rproc_monft(runt_vm *vm, runt_ptr p)
{
runt_int rc;
sp_ftbl *ft;
const char *str;
runt_stacklet *s;
monolith_d *m;
rc = runt_ppop(vm, &s);
RUNT_ERROR_CHECK(rc);
str = runt_to_string(s->p);
rc = runt_ppush(vm, &s);
RUNT_ERROR_CHECK(rc);
m = runt_to_cptr(p);
rc = monolith_ftbl_get(m, str, strlen(str), &ft);
if (rc != MONOLITH_OK) return RUNT_NOT_OK;
rgf_stacklet_ftable(vm, s, ft);
return RUNT_OK;
}
6.4.4. saving/loading an ftable
6.4.4.1. Low Level C functions
The raw sample contents of ftables can be saved and loaded
from disk using monolith_ftbl_save
and
monolith_ftbl_load
. Tables written to disk will be stored
as floating point values. No formal attention to endianness
or precision will be made at this point, but it is assumed
that the values will be 32-bit little-endian floats.
These dumps are raw files without a header: information like size will not be stored.
Note that not all things that include monolith.h
include
soundpipe.h
, so there is a macro that checks for this.
#ifdef SOUNDPIPE_H
int monolith_ftbl_save(sp_ftbl *ft, const char *filename);
int monolith_ftbl_load(sp_ftbl *ft, const char *filename);
#endif
int monolith_ftbl_save(sp_ftbl *ft, const char *filename)
{
FILE *fp;
fp = fopen(filename, "w");
if (fp == NULL) return MONOLITH_NOTOK;
fwrite(ft->tbl, sizeof(SPFLOAT), ft->size, fp);
fclose(fp);
return MONOLITH_OK;
}
int monolith_ftbl_load(sp_ftbl *ft, const char *filename)
{
FILE *fp;
int size;
fp = fopen(filename, "r");
if (fp == NULL) return MONOLITH_NOTOK;
fseek(fp, 0, SEEK_END);
size = ftell(fp) / sizeof(SPFLOAT);
if (size > ft->size) {
fprintf(stderr, "ftable of size %ld is too small\n", ft->size);
fprintf(stderr, "Make size at least %d\n", size);
}
fseek(fp, 0, SEEK_SET);
fread(ft->tbl, sizeof(SPFLOAT), size, fp);
fclose(fp);
return MONOLITH_OK;
}
6.4.4.2. Pop-n-Save (Scheme)
monolith:ftbl-pop-n-save
will pop an item off the Runt
stack (presumably an ftable), and save it to disk.
{"monolith:ftbl-pop-n-save", pp_ftbl_pop_n_save, 1, 1, {STR,___,___}},
static cell pp_ftbl_pop_n_save(cell x)
{
const char *filename;
monolith_d *m;
runt_vm *vm;
int rc;
sp_ftbl *ft;
m = monolith_data_get();
if (!m->runt_loaded) {
return error("Runt is not yet loaded", UNSPECIFIC);
}
vm = monolith_runt_vm(m);
filename = s9_string(car(x));
rc = rgf_get_ftable(vm, &ft);
MONOLITH_SCHEME_ERROR_CHECK(rc, "Could not get ftable");
rc = monolith_ftbl_save(ft, filename);
if (rc != MONOLITH_OK) {
return error("Could not save file", car(x));
}
return UNSPECIFIC;
}
6.4.4.3. Pop-n-Load (Scheme)
monolith:ftbl-pop-n-load
will pop an item off the Runt
stack (presumably an ftable), and load it from disk.
{"monolith:ftbl-pop-n-load", pp_ftbl_pop_n_load, 1, 1, {STR,___,___}},
static cell pp_ftbl_pop_n_load(cell x)
{
const char *filename;
monolith_d *m;
runt_vm *vm;
int rc;
sp_ftbl *ft;
m = monolith_data_get();
if (!m->runt_loaded) {
return error("Runt is not yet loaded", UNSPECIFIC);
}
vm = monolith_runt_vm(m);
filename = s9_string(car(x));
rc = rgf_get_ftable(vm, &ft);
MONOLITH_SCHEME_ERROR_CHECK(rc, "Could not get ftable");
rc = monolith_ftbl_load(ft, filename);
if (rc != MONOLITH_OK) {
return error("Could not load file", car(x));
}
return UNSPECIFIC;
}
6.4.5. initializaing an ftable dictionary entry
#ifdef SOUNDPIPE_H
void monolith_dict_entry_ftbl(monolith_dict_entry *ent,
sp_ftbl *ft);
#endif
void monolith_dict_entry_ftbl(monolith_dict_entry *ent,
sp_ftbl *ft)
{
ent->type = MONOLITH_ENTRY_FTBL;
ent->ud = ft;
}
6.5. Re-allocating graforge
For graphics, the block size in graforge needs to be changed from the default in order to better sync up with frames. Call this with caution, preferrably before starting anything.
6.5.1. Reallocation in C
void monolith_realloc(monolith_d *m,
int nbuf,
int stack_size,
int blksize);
void monolith_realloc(monolith_d *m,
int nbuf,
int stack_size,
int blksize)
{
gf_patch_realloc(m->patch, nbuf, stack_size, blksize);
}
6.5.2. Reallocation in Scheme
{"monolith:realloc", pp_realloc, 3, 3, {INT,INT,INT}},
static cell pp_realloc(cell x)
{
int nbuf, nstack, blksize;
monolith_d *m;
m = monolith_data_get();
nbuf = integer_value(NULL, car(x));
x = cdr(x);
nstack = integer_value(NULL, car(x));
x = cdr(x);
blksize = integer_value(NULL, car(x));
x = cdr(x);
monolith_realloc(m, nbuf, nstack, blksize);
return UNSPECIFIC;
}
6.6. Resetting Graforge Error Flag
When Graforge has a global unrecoverable error (such as running out of buffers in the buffer pool), it sets a flag that must explicitly be reset in order to work.
6.6.1. Reset the PW error Flag in C
void monolith_reset_err(monolith_d *m);
A reset is a matter of explicitely setting things back to
GF_OK
.
void monolith_reset_err(monolith_d *m)
{
gf_patch_err(m->patch, GF_OK);
}
6.6.2. Reset the PW error flag in Scheme
{"monolith:reset-err", pp_reset_err, 0, 0, {___,___,___}},
static cell pp_reset_err(cell x)
{
monolith_d *m;
m = monolith_data_get();
monolith_reset_err(m);
return UNSPECIFIC;
}
prev | home | next