11. Uxn Evaluator

11.1. Overview

The Uxn Evaluator in GestVM takes in an input phasor. When the phasor resets, it evaluates Uxn code until it hits a BRK statement.

When a reset happens, the interpolator will set the next value to be the current value.

Uxn will also evaluate at initialization.

The Uxn VM requires a user-defined function for uxn_halt. The one below is a placeholder and currently doesn't do anything.

<<uxn_halt>>=
int uxn_halt(Uxn *u, Uint8 error, char *name, int id)
{
    /* doing nothing for now */
	return 0;
}

11.2. The Uxn Struct

A little bit of cleverness is utlized to allow Uxn to see GestVM as a device. An instance of Uxn is wrapped inside of a special pointer called gestvm_uxn. Uxn is the first item, followed by a pointer to gestvm.

When the GestVM uxn device is called, the instance of Uxn can be recast as a gestvm_uxn struct, and then have access to the instance of gestvm.

The reason why gestvm is a pointer is to allow for multiple instances of gestvm to run inside a single Rom.

<<typedefs>>=
typedef struct gestvm_uxn gestvm_uxn;
<<uxn_struct>>=
struct gestvm_uxn {
    Uxn u;
    gestvm *gvm;
};

Initialize with gestvm_uxn_init.

<<funcdefs>>=
void gestvm_uxn_init(gestvm_uxn *u);
<<funcs>>=
void gestvm_uxn_init(gestvm_uxn *u)
{
    uxn_boot(&u->u);
    u->gvm = NULL;

<<zero_out_devices>>
<<console_device>>
<<gestvm_device>>
}
<<funcdefs>>=
size_t gestvm_uxn_sizeof(void);
<<funcs>>=
size_t gestvm_uxn_sizeof(void)
{
    return sizeof(gestvm_uxn);
}
<<funcdefs>>=
void gestvm_uxn_set(gestvm_uxn *gu, gestvm *gvm);
gestvm *gestvm_uxn_get(gestvm_uxn *gu);
<<funcs>>=
void gestvm_uxn_set(gestvm_uxn *gu, gestvm *gvm)
{
    gu->gvm = gvm;
}

gestvm *gestvm_uxn_get(gestvm_uxn *gu)
{
    return gu->gvm;
}

11.3. Storing the Instance of Uxn in GestVM

It is fairly convenient to store a pointer to the current instance of Uxn.

<<gestvm>>=
gestvm_uxn *u;
<<init>>=
gvm->u = u;

11.4. Loading a ROM

The function gestvm_load will load a ROM file into Uxn.

<<funcdefs>>=
int gestvm_load(gestvm_uxn *gu, const char *rom);

This loader is based on the loader found in Uxn, and is modified to support a non-standard symbol table.

Symbol tables start at the beginning of the file and have the magic word "SYM", followed by the table size. If the table does indeed exist, the loader will use the size to skip it and things behave as usual.

<<funcs>>=
int gestvm_load(gestvm_uxn *gu, const char *rom)
{
	FILE *f;
	int r;
    Uxn *u;
    char sym[3];

	if (!(f = fopen(rom, "rb"))) return 1;

    sym[0] = sym[1] = sym[2] = 0;

    fread(sym, 1, 3, f);

    if (sym[0] == 'S' && sym[1] == 'Y' && sym[2] == 'M') {
        unsigned char b[2];
        unsigned short sz;
        b[0] = b[1] = 0;
        fread(b, 1, 2, f);
        sz = b[0] | (b[1] << 8);
        fseek(f, sz, SEEK_CUR);

    } else fseek(f, 0L, SEEK_SET);


    u = &gu->u;

	r = fread(u->ram.dat + PAGE_PROGRAM,
              1, sizeof(u->ram.dat) - PAGE_PROGRAM, f);
	fclose(f);
	if(r < 1) return 1;
	return 0;
}

11.5. Symbol Look-up

GestVM ROMs can optionally use a non-standard symbol-lookup table that keeps a list of Uxn labels and their pointer address.

Looking up a particular symbol in a ROM can be done with gestvm_lookup. On error, it will return 0. Otherwise it will return an address.

<<funcdefs>>=
unsigned int gestvm_lookup(const char *rom, const char *sym);

The symbol table is structured as a linear list and is stored at the beginning of the file. The header has the letters "SYM", followed by the table size (in bytes) as a 16-bit word. Following this are the entries. An entry consists of the string size, the string itself, and the address.

Symbol lookup is a linear operation that probes the list and attempts to find a matching string. Upon finding a match, the corresponding address is returned. (Note: while technically an Uxn address could be zero, this wouldn't practically happen because that would be in the zero page).

Note: this function doesn't do granular error reporting. File not found, symbol not found, no symbol table, will all return 0.

<<funcs>>=
unsigned int gestvm_lookup(const char *rom, const char *sym)
{
    unsigned char symlen;
    unsigned short sz;
    FILE *fp;
    unsigned char buf[64];
    unsigned int addr;

    symlen = strlen(sym);
    addr = 0;

    fp = fopen(rom, "r");

    if (fp == NULL) {
        return 0;
    }

    memset(buf, 0, 64);

    fread(buf, 1, 3, fp);

    if (buf[0] != 'S' || buf[1] != 'Y' || buf[2] != 'M') {
        return 0;
    }

    sz = 0;
    fread(buf, 1, 2, fp);

    sz = buf[0] + (buf[1] << 8);

    while (sz) {
        unsigned char len;
        fread(&len, 1, 1, fp);

        if (len == symlen) {
            int i;
            int match;
            fread(buf, 1, len, fp);
            match = 1;
            for (i = 0; i < len; i++) {
                if (buf[i] != sym[i]) {
                    match = 0;
                    break;
                }
            }

            if (match) {
                fread(buf, 1, 2, fp);
                addr = buf[0] + (buf[1] << 8);
                break;
            } else {
                fseek(fp, 2, SEEK_CUR);
            }
        } else {
            fseek(fp, len + 2, SEEK_CUR);
        }

        sz -= (len + 2 + 1);
    }

    fclose(fp);

    return addr;
}

11.6. Program Pointer

In order for one ROM to support concurrent reads, each GestVM instance much have their own program pointer.

<<gestvm>>=
unsigned int ptr;
<<init>>=
gvm->ptr = 0;

The program pointer can be set with gestvm_pointer.

<<funcdefs>>=
void gestvm_pointer(gestvm *gvm, unsigned int ptr);
<<funcs>>=
void gestvm_pointer(gestvm *gvm, unsigned int ptr)
{
    gvm->ptr = ptr;
}

11.7. Device Callbacks

Every virtual device in Uxn has a special callback.

11.7.1. System Device Callbacks

nil_dei and nil_deo are empty devices which Uxn is set to by default. These come from the uxncli program.

<<static_funcdefs>>=
static Uint8 nil_dei(Device *d, Uint8 port);
static void nil_deo(Device *d, Uint8 port);
<<funcs>>=
static void nil_deo(Device *d, Uint8 port)
{
	if(port == 0x1) d->vector = peek16(d->dat, 0x0);
}

static Uint8 nil_dei(Device *d, Uint8 port)
{
	return d->dat[port];
}
<<zero_out_devices>>=
{
    int i;

    for (i = 0x0; i <= 0xf; i++) {
        uxn_port(&u->u, i, nil_dei, nil_deo);
    }
}

The console_deo is useful way to print stuff to standard out (often it is used in a macro called EMITin uxntal.) This too comes from the uxncli program.

<<static_funcdefs>>=
static void console_deo(Device *d, Uint8 port);
<<funcs>>=
static void console_deo(Device *d, Uint8 port)
{
	if(port == 0x1)
		d->vector = peek16(d->dat, 0x0);
	if(port > 0x7)
		write(port - 0x7, (char *)&d->dat[port], 1);
}
<<console_device>>=
uxn_port(&u->u, 0x1, nil_dei, console_deo);

11.7.2. GestVM Device Callback

The GestVM device callback is the means for which Uxn is able to communicate with GestVM. It is managed through the callback gestvm_deo.

<<static_funcdefs>>=
static void gestvm_deo(Device *d, Uint8 port);

Uxn can send an 8-bit unsigned byte to one of 16 ports. These ports are configured to configure the state of various aspects of the Gesture Synthesizer system.

<<funcs>>=
static void gestvm_deo(Device *d, Uint8 port)
{
    gestvm_uxn *gu;
    gestvm *gvm;

    gu = (gestvm_uxn *)d->u;
    gvm = gu->gvm;

    switch (port) {
<<port_commands>>
        default:
            break;
    }
}
<<gestvm_device>>=
uxn_port(&u->u, 0x2, nil_dei, gestvm_deo);

11.8. Uxn Port Commands

They are in order of signal flow.

11.8.1. Weight Mass (0)

<<port_commands>>=
case 0:
    uxn_mass(gvm, d->dat[port]);
    break;

11.8.2. Weight Inertia (1)

<<port_commands>>=
case 1:
    uxn_inertia(gvm, d->dat[port]);
    break;

11.8.3. Skewer Type (2)

<<port_commands>>=
case 2:
    gvm->skewer = find_skewer(d->dat[port]);
    break;

11.8.4. Skewer Length (3)

<<port_commands>>=
case 3: {
    int skewdur = d->dat[port];

    if (skewdur > 0) {
        gvm->skewdur = skewdur;
        gvm->update_skewer = 1;
        gvm->update_rephasor = 1;
    }
    break;
}

11.8.5. Rephasor Numerator (4)

<<port_commands>>=
case 4: {
    int num;

    num = d->dat[port];

    if (num > 0 && num != gvm->num) {
        gvm->num = num;
        gvm->update_rephasor = 1;
    }
    break;
}

11.8.6. Rephasor Denominator (5)

<<port_commands>>=
case 5: {
    int den;

    den = d->dat[port];

    if (den > 0 && den != gvm->den) {
        gvm->den = den;
        gvm->update_rephasor = 1;
    }
    break;
}

11.8.7. Interpolator Next (6)

<<port_commands>>=
case 6:
    gvm->nxt = (SKFLT) d->dat[port];
    break;

11.8.8. Interpolator Behavior (7)

<<port_commands>>=
case 7:
    gvm->behavior = find_behavior(d->dat[port]);
    break;

11.9. Tick

<<gestvm>>=
SKFLT lphs;

Set to an arbitrarily high value so that the VM evaluates on startup.

<<init>>=
gvm->lphs = 999;
<<static_funcdefs>>=
static void vm_tick(gestvm *gvm, SKFLT phs);
<<funcs>>=
static void vm_tick(gestvm *gvm, SKFLT phs)
{
    if (phs < gvm->lphs) {
        gvm->u->gvm = gvm;
        gvm->cur = gvm->nxt;
        uxn_eval(&gvm->u->u, gvm->ptr);
        gvm->ptr = gvm->u->u.ram.ptr;
    }

    gvm->lphs = phs;
}

11.10. Eval

gestvm_eval wraps a call to uxn_eval.

<<funcdefs>>=
void gestvm_eval(gestvm_uxn *gu, unsigned int addr);
<<funcs>>=
void gestvm_eval(gestvm_uxn *gu, unsigned int addr)
{
    uxn_eval(&gu->u, addr);
}



prev | home | next