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.

<<struct_contents>>=
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.

<<function_declarations>>=
gf_patch * monolith_graforge_get(monolith_d *m);
<<functions>>=
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.

<<core_janet_entries>>=
{
"monolith/graforge",
j_graforge,
"Returns graforge data."
},
<<janet_functions>>=
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.

<<primitive_entries>>=
{"monolith:graforge", pp_graforge, 0, 0, {___,___,___}},
<<scheme_functions>>=
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.

<<struct_contents>>=
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.

<<init>>=
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);
<<cleanup>>=
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.

<<struct_contents>>=
unsigned int sr;
<<function_declarations>>=
void monolith_sr_set(monolith_d *m, unsigned int sr);
unsigned int monolith_sr_get(monolith_d *sr);
<<functions>>=
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;
}
<<primitive_entries>>=
{"monolith:srate-set", pp_srate_set, 1, 1, {INT,___,___}},
<<scheme_functions>>=
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.

<<primitive_entries>>=
{"monolith:pw-srate-get", pp_gf_srate_get, 1, 1, {S9_T_POINTER,___,___}},
<<scheme_functions>>=
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.

<<function_declarations>>=
void monolith_seed(monolith_d *m, unsigned int seed);
<<functions>>=
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.

<<primitive_entries>>=
{"monolith:seed", pp_seed, 1, 1, {INT,___,___}},
<<scheme_functions>>=
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

<<function_declarations>>=
#ifdef SOUNDPIPE_H
int monolith_ftbl_create(monolith_d *m,
                         const char *key,
                         size_t keylen,
                         sp_ftbl **pft,
                         size_t size);
#endif
<<functions>>=
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)

<<primitive_entries>>=
{"monolith:ftbl-create", pp_ftbl_create, 2, 2, {STR,INT,___}},
<<scheme_functions>>=
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)

<<core_janet_entries>>=
{
"monolith/ftbl-create",
j_ftbl_create,
"creates an ftable inside of monolith dictionary\n"
},
<<janet_functions>>=
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.

<<function_declarations>>=
#ifdef SOUNDPIPE_H
int monolith_ftbl_get(monolith_d *m,
                      const char *key,
                      size_t len,
                      sp_ftbl **pft);
#endif
<<functions>>=
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_loader>>=
monolith_runt_keyword(m, "monft", 5, rproc_monft, m);
<<static_function_declarations>>=
static int rproc_monft(runt_vm *vm, runt_ptr p);
<<functions>>=
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.

<<function_declarations>>=
#ifdef SOUNDPIPE_H
int monolith_ftbl_save(sp_ftbl *ft, const char *filename);
int monolith_ftbl_load(sp_ftbl *ft, const char *filename);
#endif
<<functions>>=
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;
}
<<functions>>=
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.

<<primitive_entries>>=
{"monolith:ftbl-pop-n-save", pp_ftbl_pop_n_save, 1, 1, {STR,___,___}},
<<scheme_functions>>=
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.

<<primitive_entries>>=
{"monolith:ftbl-pop-n-load", pp_ftbl_pop_n_load, 1, 1, {STR,___,___}},
<<scheme_functions>>=
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

<<function_declarations>>=
#ifdef SOUNDPIPE_H
void monolith_dict_entry_ftbl(monolith_dict_entry *ent,
                              sp_ftbl *ft);
#endif
<<functions>>=
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

<<function_declarations>>=
void monolith_realloc(monolith_d *m,
                      int nbuf,
                      int stack_size,
                      int blksize);
<<functions>>=
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

<<primitive_entries>>=
{"monolith:realloc", pp_realloc, 3, 3, {INT,INT,INT}},
<<scheme_functions>>=
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

<<function_declarations>>=
void monolith_reset_err(monolith_d *m);

A reset is a matter of explicitely setting things back to GF_OK.

<<functions>>=
void monolith_reset_err(monolith_d *m)
{
    gf_patch_err(m->patch, GF_OK);
}

6.6.2. Reset the PW error flag in Scheme

Done with monolith:reset-err.

<<primitive_entries>>=
{"monolith:reset-err", pp_reset_err, 0, 0, {___,___,___}},
<<scheme_functions>>=
static cell pp_reset_err(cell x)
{
    monolith_d *m;

    m = monolith_data_get();
    monolith_reset_err(m);
    return UNSPECIFIC;
}



prev | home | next